diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-12-20 13:37:47 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-12-20 13:37:47 +0000 |
commit | aee0a117a889461ce8ced6fcf73207fe017f1d99 (patch) | |
tree | 891d9ef189227a8445d83f35c1b0fc99573f4380 /spec/frontend/environments | |
parent | 8d46af3258650d305f53b819eabf7ab18d22f59e (diff) | |
download | gitlab-ce-aee0a117a889461ce8ced6fcf73207fe017f1d99.tar.gz |
Add latest changes from gitlab-org/gitlab@14-6-stable-eev14.6.0-rc42
Diffstat (limited to 'spec/frontend/environments')
9 files changed, 683 insertions, 36 deletions
diff --git a/spec/frontend/environments/confirm_rollback_modal_spec.js b/spec/frontend/environments/confirm_rollback_modal_spec.js index d62aaec4f69..b699f953945 100644 --- a/spec/frontend/environments/confirm_rollback_modal_spec.js +++ b/spec/frontend/environments/confirm_rollback_modal_spec.js @@ -1,6 +1,9 @@ import { GlModal, GlSprintf } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; +import Vue, { nextTick } from 'vue'; +import VueApollo from 'vue-apollo'; import ConfirmRollbackModal from '~/environments/components/confirm_rollback_modal.vue'; +import createMockApollo from 'helpers/mock_apollo_helper'; import eventHub from '~/environments/event_hub'; describe('Confirm Rollback Modal Component', () => { @@ -17,6 +20,17 @@ describe('Confirm Rollback Modal Component', () => { modalId: 'test', }; + const envWithLastDeploymentGraphql = { + name: 'test', + lastDeployment: { + commit: { + shortId: 'abc0123', + }, + 'last?': true, + }, + modalId: 'test', + }; + const envWithoutLastDeployment = { name: 'test', modalId: 'test', @@ -26,7 +40,7 @@ describe('Confirm Rollback Modal Component', () => { const retryPath = 'test/-/jobs/123/retry'; - const createComponent = (props = {}) => { + const createComponent = (props = {}, options = {}) => { component = shallowMount(ConfirmRollbackModal, { propsData: { ...props, @@ -34,6 +48,7 @@ describe('Confirm Rollback Modal Component', () => { stubs: { GlSprintf, }, + ...options, }); }; @@ -101,4 +116,121 @@ describe('Confirm Rollback Modal Component', () => { }); }, ); + + describe('graphql', () => { + describe.each` + hasMultipleCommits | environmentData | retryUrl | primaryPropsAttrs + ${true} | ${envWithLastDeploymentGraphql} | ${null} | ${[{ variant: 'danger' }]} + ${false} | ${envWithoutLastDeployment} | ${retryPath} | ${[{ variant: 'danger' }, { 'data-method': 'post' }, { href: retryPath }]} + `( + 'when hasMultipleCommits=$hasMultipleCommits', + ({ hasMultipleCommits, environmentData, retryUrl, primaryPropsAttrs }) => { + Vue.use(VueApollo); + + let apolloProvider; + let rollbackResolver; + + beforeEach(() => { + rollbackResolver = jest.fn(); + apolloProvider = createMockApollo([], { + Mutation: { rollbackEnvironment: rollbackResolver }, + }); + environment = environmentData; + }); + + it('should set contain the commit hash and ask for confirmation', () => { + createComponent( + { + environment: { + ...environment, + lastDeployment: { + ...environment.lastDeployment, + 'last?': false, + }, + }, + hasMultipleCommits, + retryUrl, + graphql: true, + }, + { apolloProvider }, + ); + const modal = component.find(GlModal); + + expect(modal.text()).toContain('commit abc0123'); + expect(modal.text()).toContain('Are you sure you want to continue?'); + }); + + it('should show "Rollback" when isLastDeployment is false', () => { + createComponent( + { + environment: { + ...environment, + lastDeployment: { + ...environment.lastDeployment, + 'last?': false, + }, + }, + hasMultipleCommits, + retryUrl, + graphql: true, + }, + { apolloProvider }, + ); + const modal = component.find(GlModal); + + expect(modal.attributes('title')).toContain('Rollback'); + expect(modal.attributes('title')).toContain('test'); + expect(modal.props('actionPrimary').text).toBe('Rollback'); + expect(modal.props('actionPrimary').attributes).toEqual(primaryPropsAttrs); + }); + + it('should show "Re-deploy" when isLastDeployment is true', () => { + createComponent( + { + environment: { + ...environment, + lastDeployment: { + ...environment.lastDeployment, + 'last?': true, + }, + }, + hasMultipleCommits, + graphql: true, + }, + { apolloProvider }, + ); + + const modal = component.find(GlModal); + + expect(modal.attributes('title')).toContain('Re-deploy'); + expect(modal.attributes('title')).toContain('test'); + expect(modal.props('actionPrimary').text).toBe('Re-deploy'); + }); + + it('should commit the "rollback" mutation when "ok" is clicked', async () => { + const env = { ...environmentData, isLastDeployment: true }; + + createComponent( + { + environment: env, + hasMultipleCommits, + graphql: true, + }, + { apolloProvider }, + ); + + const modal = component.find(GlModal); + modal.vm.$emit('ok'); + + await nextTick(); + expect(rollbackResolver).toHaveBeenCalledWith( + expect.anything(), + { environment: env }, + expect.anything(), + expect.anything(), + ); + }); + }, + ); + }); }); diff --git a/spec/frontend/environments/delete_environment_modal_spec.js b/spec/frontend/environments/delete_environment_modal_spec.js new file mode 100644 index 00000000000..50c4ca00009 --- /dev/null +++ b/spec/frontend/environments/delete_environment_modal_spec.js @@ -0,0 +1,64 @@ +import { GlModal } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; +import Vue, { nextTick } from 'vue'; +import VueApollo from 'vue-apollo'; +import { s__, sprintf } from '~/locale'; +import DeleteEnvironmentModal from '~/environments/components/delete_environment_modal.vue'; +import createMockApollo from 'helpers/mock_apollo_helper'; +import { resolvedEnvironment } from './graphql/mock_data'; + +Vue.use(VueApollo); + +describe('~/environments/components/delete_environment_modal.vue', () => { + let mockApollo; + let deleteResolver; + let wrapper; + + const createComponent = ({ props = {}, apolloProvider } = {}) => { + wrapper = shallowMount(DeleteEnvironmentModal, { + propsData: { + graphql: true, + environment: resolvedEnvironment, + ...props, + }, + apolloProvider, + }); + }; + + beforeEach(() => { + deleteResolver = jest.fn(); + mockApollo = createMockApollo([], { + Mutation: { deleteEnvironment: deleteResolver }, + }); + }); + + it('should confirm the environment to delete', () => { + createComponent({ apolloProvider: mockApollo }); + + expect(wrapper.text()).toBe( + sprintf( + s__( + `Environments|Deleting the '%{environmentName}' environment cannot be undone. Do you want to delete it anyway?`, + ), + { + environmentName: resolvedEnvironment.name, + }, + ), + ); + }); + + it('should send the delete mutation on primary', async () => { + createComponent({ apolloProvider: mockApollo }); + + wrapper.findComponent(GlModal).vm.$emit('primary'); + + await nextTick(); + + expect(deleteResolver).toHaveBeenCalledWith( + expect.anything(), + { environment: resolvedEnvironment }, + expect.anything(), + expect.anything(), + ); + }); +}); diff --git a/spec/frontend/environments/enable_review_app_modal_spec.js b/spec/frontend/environments/enable_review_app_modal_spec.js index 9a3f13f19d5..17ae10a2884 100644 --- a/spec/frontend/environments/enable_review_app_modal_spec.js +++ b/spec/frontend/environments/enable_review_app_modal_spec.js @@ -1,10 +1,12 @@ import { shallowMount } from '@vue/test-utils'; +import { GlModal } from '@gitlab/ui'; import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import EnableReviewAppButton from '~/environments/components/enable_review_app_modal.vue'; import ModalCopyButton from '~/vue_shared/components/modal_copy_button.vue'; describe('Enable Review App Button', () => { let wrapper; + let modal; afterEach(() => { wrapper.destroy(); @@ -16,12 +18,15 @@ describe('Enable Review App Button', () => { shallowMount(EnableReviewAppButton, { propsData: { modalId: 'fake-id', + visible: true, }, provide: { defaultBranchName: 'main', }, }), ); + + modal = wrapper.findComponent(GlModal); }); it('renders the defaultBranchName copy', () => { @@ -32,5 +37,15 @@ describe('Enable Review App Button', () => { it('renders the copyToClipboard button', () => { expect(wrapper.findComponent(ModalCopyButton).exists()).toBe(true); }); + + it('emits change events from the modal up', () => { + modal.vm.$emit('change', false); + + expect(wrapper.emitted('change')).toEqual([[false]]); + }); + + it('passes visible to the modal', () => { + expect(modal.props('visible')).toBe(true); + }); }); }); diff --git a/spec/frontend/environments/environment_delete_spec.js b/spec/frontend/environments/environment_delete_spec.js index 2d8cff0c74a..057cb9858c4 100644 --- a/spec/frontend/environments/environment_delete_spec.js +++ b/spec/frontend/environments/environment_delete_spec.js @@ -1,37 +1,71 @@ import { GlDropdownItem } from '@gitlab/ui'; - import { shallowMount } from '@vue/test-utils'; +import Vue from 'vue'; +import VueApollo from 'vue-apollo'; +import setEnvironmentToDelete from '~/environments/graphql/mutations/set_environment_to_delete.mutation.graphql'; import DeleteComponent from '~/environments/components/environment_delete.vue'; import eventHub from '~/environments/event_hub'; +import createMockApollo from 'helpers/mock_apollo_helper'; +import { resolvedEnvironment } from './graphql/mock_data'; describe('External URL Component', () => { let wrapper; - const createWrapper = () => { + const createWrapper = (props = {}, options = {}) => { wrapper = shallowMount(DeleteComponent, { + ...options, propsData: { - environment: {}, + environment: resolvedEnvironment, + ...props, }, }); }; const findDropdownItem = () => wrapper.find(GlDropdownItem); - beforeEach(() => { - jest.spyOn(window, 'confirm'); + describe('event hub', () => { + beforeEach(() => { + createWrapper(); + }); - createWrapper(); - }); + it('should render a dropdown item to delete the environment', () => { + expect(findDropdownItem().exists()).toBe(true); + expect(wrapper.text()).toEqual('Delete environment'); + expect(findDropdownItem().attributes('variant')).toBe('danger'); + }); - it('should render a dropdown item to delete the environment', () => { - expect(findDropdownItem().exists()).toBe(true); - expect(wrapper.text()).toEqual('Delete environment'); - expect(findDropdownItem().attributes('variant')).toBe('danger'); + it('emits requestDeleteEnvironment in the event hub when button is clicked', () => { + jest.spyOn(eventHub, '$emit'); + findDropdownItem().vm.$emit('click'); + expect(eventHub.$emit).toHaveBeenCalledWith('requestDeleteEnvironment', resolvedEnvironment); + }); }); - it('emits requestDeleteEnvironment in the event hub when button is clicked', () => { - jest.spyOn(eventHub, '$emit'); - findDropdownItem().vm.$emit('click'); - expect(eventHub.$emit).toHaveBeenCalledWith('requestDeleteEnvironment', wrapper.vm.environment); + describe('graphql', () => { + Vue.use(VueApollo); + let mockApollo; + + beforeEach(() => { + mockApollo = createMockApollo(); + createWrapper( + { graphql: true, environment: resolvedEnvironment }, + { apolloProvider: mockApollo }, + ); + }); + + it('should render a dropdown item to delete the environment', () => { + expect(findDropdownItem().exists()).toBe(true); + expect(wrapper.text()).toEqual('Delete environment'); + expect(findDropdownItem().attributes('variant')).toBe('danger'); + }); + + it('emits requestDeleteEnvironment in the event hub when button is clicked', () => { + jest.spyOn(mockApollo.defaultClient, 'mutate'); + findDropdownItem().vm.$emit('click'); + expect(mockApollo.defaultClient.mutate).toHaveBeenCalledWith({ + mutation: setEnvironmentToDelete, + variables: { environment: resolvedEnvironment }, + }); + }); }); }); diff --git a/spec/frontend/environments/environment_rollback_spec.js b/spec/frontend/environments/environment_rollback_spec.js index cde675cd9e7..7eff46baaf7 100644 --- a/spec/frontend/environments/environment_rollback_spec.js +++ b/spec/frontend/environments/environment_rollback_spec.js @@ -1,7 +1,11 @@ +import Vue from 'vue'; +import VueApollo from 'vue-apollo'; import { GlDropdownItem } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; import RollbackComponent from '~/environments/components/environment_rollback.vue'; import eventHub from '~/environments/event_hub'; +import setEnvironmentToRollback from '~/environments/graphql/mutations/set_environment_to_rollback.mutation.graphql'; +import createMockApollo from 'helpers/mock_apollo_helper'; describe('Rollback Component', () => { const retryUrl = 'https://gitlab.com/retry'; @@ -50,4 +54,29 @@ describe('Rollback Component', () => { name: 'test', }); }); + + it('should trigger a graphql mutation when graphql is enabled', () => { + Vue.use(VueApollo); + + const apolloProvider = createMockApollo(); + jest.spyOn(apolloProvider.defaultClient, 'mutate'); + const environment = { + name: 'test', + }; + const wrapper = shallowMount(RollbackComponent, { + propsData: { + retryUrl, + graphql: true, + environment, + }, + apolloProvider, + }); + const button = wrapper.find(GlDropdownItem); + button.vm.$emit('click'); + + expect(apolloProvider.defaultClient.mutate).toHaveBeenCalledWith({ + mutation: setEnvironmentToRollback, + variables: { environment }, + }); + }); }); diff --git a/spec/frontend/environments/graphql/mock_data.js b/spec/frontend/environments/graphql/mock_data.js index e56b6448b7d..e75d3ac0321 100644 --- a/spec/frontend/environments/graphql/mock_data.js +++ b/spec/frontend/environments/graphql/mock_data.js @@ -469,6 +469,33 @@ export const folder = { stopped_count: 0, }; +export const resolvedEnvironment = { + id: 41, + globalId: 'gid://gitlab/Environment/41', + name: 'review/hello', + state: 'available', + externalUrl: 'https://example.org', + environmentType: 'review', + nameWithoutType: 'hello', + lastDeployment: null, + hasStopAction: false, + rolloutStatus: null, + environmentPath: '/h5bp/html5-boilerplate/-/environments/41', + stopPath: '/h5bp/html5-boilerplate/-/environments/41/stop', + cancelAutoStopPath: '/h5bp/html5-boilerplate/-/environments/41/cancel_auto_stop', + deletePath: '/api/v4/projects/8/environments/41', + folderPath: '/h5bp/html5-boilerplate/-/environments/folders/review', + createdAt: '2021-10-04T19:27:00.527Z', + updatedAt: '2021-10-04T19:27:00.527Z', + canStop: true, + logsPath: '/h5bp/html5-boilerplate/-/logs?environment_name=review%2Fhello', + logsApiPath: '/h5bp/html5-boilerplate/-/logs/k8s.json?environment_name=review%2Fhello', + enableAdvancedLogsQuerying: false, + canDelete: false, + hasOpenedAlert: false, + __typename: 'LocalEnvironment', +}; + export const resolvedFolder = { availableCount: 2, environments: [ diff --git a/spec/frontend/environments/graphql/resolvers_spec.js b/spec/frontend/environments/graphql/resolvers_spec.js index 4d2a0818996..d8d26b74504 100644 --- a/spec/frontend/environments/graphql/resolvers_spec.js +++ b/spec/frontend/environments/graphql/resolvers_spec.js @@ -1,18 +1,33 @@ import MockAdapter from 'axios-mock-adapter'; import axios from '~/lib/utils/axios_utils'; import { resolvers } from '~/environments/graphql/resolvers'; +import environmentToRollback from '~/environments/graphql/queries/environment_to_rollback.query.graphql'; +import environmentToDelete from '~/environments/graphql/queries/environment_to_delete.query.graphql'; +import createMockApollo from 'helpers/mock_apollo_helper'; +import pollIntervalQuery from '~/environments/graphql/queries/poll_interval.query.graphql'; +import pageInfoQuery from '~/environments/graphql/queries/page_info.query.graphql'; import { TEST_HOST } from 'helpers/test_constants'; -import { environmentsApp, resolvedEnvironmentsApp, folder, resolvedFolder } from './mock_data'; +import { + environmentsApp, + resolvedEnvironmentsApp, + resolvedEnvironment, + folder, + resolvedFolder, +} from './mock_data'; const ENDPOINT = `${TEST_HOST}/environments`; describe('~/frontend/environments/graphql/resolvers', () => { let mockResolvers; let mock; + let mockApollo; + let localState; beforeEach(() => { mockResolvers = resolvers(ENDPOINT); mock = new MockAdapter(axios); + mockApollo = createMockApollo(); + localState = mockApollo.defaultClient.localState; }); afterEach(() => { @@ -21,10 +36,87 @@ describe('~/frontend/environments/graphql/resolvers', () => { describe('environmentApp', () => { it('should fetch environments and map them to frontend data', async () => { - mock.onGet(ENDPOINT, { params: { nested: true } }).reply(200, environmentsApp); + const cache = { writeQuery: jest.fn() }; + const scope = 'available'; + mock + .onGet(ENDPOINT, { params: { nested: true, scope, page: 1 } }) + .reply(200, environmentsApp, {}); - const app = await mockResolvers.Query.environmentApp(); + const app = await mockResolvers.Query.environmentApp(null, { scope, page: 1 }, { cache }); expect(app).toEqual(resolvedEnvironmentsApp); + expect(cache.writeQuery).toHaveBeenCalledWith({ + query: pollIntervalQuery, + data: { interval: undefined }, + }); + }); + it('should set the poll interval when there is one', async () => { + const cache = { writeQuery: jest.fn() }; + const scope = 'stopped'; + const interval = 3000; + mock + .onGet(ENDPOINT, { params: { nested: true, scope, page: 1 } }) + .reply(200, environmentsApp, { + 'poll-interval': interval, + }); + + await mockResolvers.Query.environmentApp(null, { scope, page: 1 }, { cache }); + expect(cache.writeQuery).toHaveBeenCalledWith({ + query: pollIntervalQuery, + data: { interval }, + }); + }); + it('should set page info if there is any', async () => { + const cache = { writeQuery: jest.fn() }; + const scope = 'stopped'; + mock + .onGet(ENDPOINT, { params: { nested: true, scope, page: 1 } }) + .reply(200, environmentsApp, { + 'x-next-page': '2', + 'x-page': '1', + 'X-Per-Page': '2', + 'X-Prev-Page': '', + 'X-TOTAL': '37', + 'X-Total-Pages': '5', + }); + + await mockResolvers.Query.environmentApp(null, { scope, page: 1 }, { cache }); + expect(cache.writeQuery).toHaveBeenCalledWith({ + query: pageInfoQuery, + data: { + pageInfo: { + total: 37, + perPage: 2, + previousPage: NaN, + totalPages: 5, + nextPage: 2, + page: 1, + __typename: 'LocalPageInfo', + }, + }, + }); + }); + it('should not set page info if there is none', async () => { + const cache = { writeQuery: jest.fn() }; + const scope = 'stopped'; + mock + .onGet(ENDPOINT, { params: { nested: true, scope, page: 1 } }) + .reply(200, environmentsApp, {}); + + await mockResolvers.Query.environmentApp(null, { scope, page: 1 }, { cache }); + expect(cache.writeQuery).toHaveBeenCalledWith({ + query: pageInfoQuery, + data: { + pageInfo: { + __typename: 'LocalPageInfo', + nextPage: NaN, + page: NaN, + perPage: NaN, + previousPage: NaN, + total: NaN, + totalPages: NaN, + }, + }, + }); }); }); describe('folder', () => { @@ -42,7 +134,7 @@ describe('~/frontend/environments/graphql/resolvers', () => { it('should post to the stop environment path', async () => { mock.onPost(ENDPOINT).reply(200); - await mockResolvers.Mutations.stopEnvironment(null, { environment: { stopPath: ENDPOINT } }); + await mockResolvers.Mutation.stopEnvironment(null, { environment: { stopPath: ENDPOINT } }); expect(mock.history.post).toContainEqual( expect.objectContaining({ url: ENDPOINT, method: 'post' }), @@ -53,7 +145,7 @@ describe('~/frontend/environments/graphql/resolvers', () => { it('should post to the retry environment path', async () => { mock.onPost(ENDPOINT).reply(200); - await mockResolvers.Mutations.rollbackEnvironment(null, { + await mockResolvers.Mutation.rollbackEnvironment(null, { environment: { retryUrl: ENDPOINT }, }); @@ -66,7 +158,7 @@ describe('~/frontend/environments/graphql/resolvers', () => { it('should DELETE to the delete environment path', async () => { mock.onDelete(ENDPOINT).reply(200); - await mockResolvers.Mutations.deleteEnvironment(null, { + await mockResolvers.Mutation.deleteEnvironment(null, { environment: { deletePath: ENDPOINT }, }); @@ -79,7 +171,7 @@ describe('~/frontend/environments/graphql/resolvers', () => { it('should post to the auto stop path', async () => { mock.onPost(ENDPOINT).reply(200); - await mockResolvers.Mutations.cancelAutoStop(null, { + await mockResolvers.Mutation.cancelAutoStop(null, { environment: { autoStopPath: ENDPOINT }, }); @@ -88,4 +180,34 @@ describe('~/frontend/environments/graphql/resolvers', () => { ); }); }); + describe('setEnvironmentToRollback', () => { + it('should write the given environment to the cache', () => { + localState.client.writeQuery = jest.fn(); + mockResolvers.Mutation.setEnvironmentToRollback( + null, + { environment: resolvedEnvironment }, + localState, + ); + + expect(localState.client.writeQuery).toHaveBeenCalledWith({ + query: environmentToRollback, + data: { environmentToRollback: resolvedEnvironment }, + }); + }); + }); + describe('setEnvironmentToDelete', () => { + it('should write the given environment to the cache', () => { + localState.client.writeQuery = jest.fn(); + mockResolvers.Mutation.setEnvironmentToDelete( + null, + { environment: resolvedEnvironment }, + localState, + ); + + expect(localState.client.writeQuery).toHaveBeenCalledWith({ + query: environmentToDelete, + data: { environmentToDelete: resolvedEnvironment }, + }); + }); + }); }); diff --git a/spec/frontend/environments/new_environment_folder_spec.js b/spec/frontend/environments/new_environment_folder_spec.js index 5696e187a86..27d27d5869a 100644 --- a/spec/frontend/environments/new_environment_folder_spec.js +++ b/spec/frontend/environments/new_environment_folder_spec.js @@ -3,8 +3,8 @@ import Vue from 'vue'; import { GlCollapse, GlIcon } from '@gitlab/ui'; import createMockApollo from 'helpers/mock_apollo_helper'; import { mountExtended } from 'helpers/vue_test_utils_helper'; +import { __, s__ } from '~/locale'; import EnvironmentsFolder from '~/environments/components/new_environment_folder.vue'; -import { s__ } from '~/locale'; import { resolvedEnvironmentsApp, resolvedFolder } from './graphql/mock_data'; Vue.use(VueApollo); @@ -14,6 +14,7 @@ describe('~/environments/components/new_environments_folder.vue', () => { let environmentFolderMock; let nestedEnvironment; let folderName; + let button; const findLink = () => wrapper.findByRole('link', { name: s__('Environments|Show all') }); @@ -32,6 +33,7 @@ describe('~/environments/components/new_environments_folder.vue', () => { environmentFolderMock.mockReturnValue(resolvedFolder); wrapper = createWrapper({ nestedEnvironment }, createApolloProvider()); folderName = wrapper.findByText(nestedEnvironment.name); + button = wrapper.findByRole('button', { name: __('Expand') }); }); afterEach(() => { @@ -61,10 +63,11 @@ describe('~/environments/components/new_environments_folder.vue', () => { }); it('opens on click', async () => { - await folderName.trigger('click'); + await button.trigger('click'); const link = findLink(); + expect(button.attributes('aria-label')).toBe(__('Collapse')); expect(collapse.attributes('visible')).toBe('true'); expect(icons.wrappers.map((i) => i.props('name'))).toEqual(['angle-down', 'folder-open']); expect(folderName.classes('gl-font-weight-bold')).toBe(true); diff --git a/spec/frontend/environments/new_environments_app_spec.js b/spec/frontend/environments/new_environments_app_spec.js index 0ad8e8f442c..1e9bd4d64c9 100644 --- a/spec/frontend/environments/new_environments_app_spec.js +++ b/spec/frontend/environments/new_environments_app_spec.js @@ -1,8 +1,11 @@ -import Vue from 'vue'; +import Vue, { nextTick } from 'vue'; import VueApollo from 'vue-apollo'; -import { mount } from '@vue/test-utils'; +import { GlPagination } from '@gitlab/ui'; +import { mountExtended } from 'helpers/vue_test_utils_helper'; import createMockApollo from 'helpers/mock_apollo_helper'; import waitForPromises from 'helpers/wait_for_promises'; +import setWindowLocation from 'helpers/set_window_location_helper'; +import { sprintf, __, s__ } from '~/locale'; import EnvironmentsApp from '~/environments/components/new_environments_app.vue'; import EnvironmentsFolder from '~/environments/components/new_environment_folder.vue'; import { resolvedEnvironmentsApp, resolvedFolder } from './graphql/mock_data'; @@ -13,20 +16,59 @@ describe('~/environments/components/new_environments_app.vue', () => { let wrapper; let environmentAppMock; let environmentFolderMock; + let paginationMock; const createApolloProvider = () => { const mockResolvers = { - Query: { environmentApp: environmentAppMock, folder: environmentFolderMock }, + Query: { + environmentApp: environmentAppMock, + folder: environmentFolderMock, + pageInfo: paginationMock, + }, }; return createMockApollo([], mockResolvers); }; - const createWrapper = (apolloProvider) => mount(EnvironmentsApp, { apolloProvider }); + const createWrapper = ({ provide = {}, apolloProvider } = {}) => + mountExtended(EnvironmentsApp, { + provide: { + newEnvironmentPath: '/environments/new', + canCreateEnvironment: true, + defaultBranchName: 'main', + ...provide, + }, + apolloProvider, + }); + + const createWrapperWithMocked = async ({ + provide = {}, + environmentsApp, + folder, + pageInfo = { + total: 20, + perPage: 5, + nextPage: 3, + page: 2, + previousPage: 1, + __typename: 'LocalPageInfo', + }, + }) => { + setWindowLocation('?scope=available&page=2'); + environmentAppMock.mockReturnValue(environmentsApp); + environmentFolderMock.mockReturnValue(folder); + paginationMock.mockReturnValue(pageInfo); + const apolloProvider = createApolloProvider(); + wrapper = createWrapper({ apolloProvider, provide }); + + await waitForPromises(); + await nextTick(); + }; beforeEach(() => { environmentAppMock = jest.fn(); environmentFolderMock = jest.fn(); + paginationMock = jest.fn(); }); afterEach(() => { @@ -34,17 +76,196 @@ describe('~/environments/components/new_environments_app.vue', () => { }); it('should show all the folders that are fetched', async () => { - environmentAppMock.mockReturnValue(resolvedEnvironmentsApp); - environmentFolderMock.mockReturnValue(resolvedFolder); - const apolloProvider = createApolloProvider(); - wrapper = createWrapper(apolloProvider); - - await waitForPromises(); - await Vue.nextTick(); + await createWrapperWithMocked({ + environmentsApp: resolvedEnvironmentsApp, + folder: resolvedFolder, + }); const text = wrapper.findAllComponents(EnvironmentsFolder).wrappers.map((w) => w.text()); expect(text).toContainEqual(expect.stringMatching('review')); expect(text).not.toContainEqual(expect.stringMatching('production')); }); + + it('should show a button to create a new environment', async () => { + await createWrapperWithMocked({ + environmentsApp: resolvedEnvironmentsApp, + folder: resolvedFolder, + }); + + const button = wrapper.findByRole('link', { name: s__('Environments|New environment') }); + expect(button.attributes('href')).toBe('/environments/new'); + }); + + it('should not show a button to create a new environment if the user has no permissions', async () => { + await createWrapperWithMocked({ + environmentsApp: resolvedEnvironmentsApp, + folder: resolvedFolder, + provide: { canCreateEnvironment: false, newEnvironmentPath: '' }, + }); + + const button = wrapper.findByRole('link', { name: s__('Environments|New environment') }); + expect(button.exists()).toBe(false); + }); + + it('should show a button to open the review app modal', async () => { + await createWrapperWithMocked({ + environmentsApp: resolvedEnvironmentsApp, + folder: resolvedFolder, + }); + + const button = wrapper.findByRole('button', { name: s__('Environments|Enable review app') }); + button.trigger('click'); + + await nextTick(); + + expect(wrapper.findByText(s__('ReviewApp|Enable Review App')).exists()).toBe(true); + }); + + it('should not show a button to open the review app modal if review apps are configured', async () => { + await createWrapperWithMocked({ + environmentsApp: { + ...resolvedEnvironmentsApp, + reviewApp: { canSetupReviewApp: false }, + }, + folder: resolvedFolder, + }); + await waitForPromises(); + await nextTick(); + + const button = wrapper.findByRole('button', { name: s__('Environments|Enable review app') }); + expect(button.exists()).toBe(false); + }); + + describe('tabs', () => { + it('should show tabs for available and stopped environmets', async () => { + await createWrapperWithMocked({ + environmentsApp: resolvedEnvironmentsApp, + folder: resolvedFolder, + }); + + const [available, stopped] = wrapper.findAllByRole('tab').wrappers; + + expect(available.text()).toContain(__('Available')); + expect(available.text()).toContain(resolvedEnvironmentsApp.availableCount); + expect(stopped.text()).toContain(__('Stopped')); + expect(stopped.text()).toContain(resolvedEnvironmentsApp.stoppedCount); + }); + + it('should change the requested scope on tab change', async () => { + await createWrapperWithMocked({ + environmentsApp: resolvedEnvironmentsApp, + folder: resolvedFolder, + }); + const stopped = wrapper.findByRole('tab', { + name: `${__('Stopped')} ${resolvedEnvironmentsApp.stoppedCount}`, + }); + + stopped.trigger('click'); + + await nextTick(); + await waitForPromises(); + + expect(environmentAppMock).toHaveBeenCalledWith( + expect.anything(), + expect.objectContaining({ scope: 'stopped' }), + expect.anything(), + expect.anything(), + ); + }); + }); + + describe('pagination', () => { + it('should sync page from query params on load', async () => { + await createWrapperWithMocked({ + environmentsApp: resolvedEnvironmentsApp, + folder: resolvedFolder, + }); + + expect(wrapper.findComponent(GlPagination).props('value')).toBe(2); + }); + + it('should change the requested page on next page click', async () => { + await createWrapperWithMocked({ + environmentsApp: resolvedEnvironmentsApp, + folder: resolvedFolder, + }); + const next = wrapper.findByRole('link', { + name: __('Go to next page'), + }); + + next.trigger('click'); + + await nextTick(); + await waitForPromises(); + + expect(environmentAppMock).toHaveBeenCalledWith( + expect.anything(), + expect.objectContaining({ page: 3 }), + expect.anything(), + expect.anything(), + ); + }); + + it('should change the requested page on previous page click', async () => { + await createWrapperWithMocked({ + environmentsApp: resolvedEnvironmentsApp, + folder: resolvedFolder, + }); + const prev = wrapper.findByRole('link', { + name: __('Go to previous page'), + }); + + prev.trigger('click'); + + await nextTick(); + await waitForPromises(); + + expect(environmentAppMock).toHaveBeenCalledWith( + expect.anything(), + expect.objectContaining({ page: 1 }), + expect.anything(), + expect.anything(), + ); + }); + + it('should change the requested page on specific page click', async () => { + await createWrapperWithMocked({ + environmentsApp: resolvedEnvironmentsApp, + folder: resolvedFolder, + }); + + const page = 1; + const pageButton = wrapper.findByRole('link', { + name: sprintf(__('Go to page %{page}'), { page }), + }); + + pageButton.trigger('click'); + + await nextTick(); + await waitForPromises(); + + expect(environmentAppMock).toHaveBeenCalledWith( + expect.anything(), + expect.objectContaining({ page }), + expect.anything(), + expect.anything(), + ); + }); + + it('should sync the query params to the new page', async () => { + await createWrapperWithMocked({ + environmentsApp: resolvedEnvironmentsApp, + folder: resolvedFolder, + }); + const next = wrapper.findByRole('link', { + name: __('Go to next page'), + }); + + next.trigger('click'); + + await nextTick(); + expect(window.location.search).toBe('?scope=available&page=3'); + }); + }); }); |