diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-03-18 20:02:30 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-03-18 20:02:30 +0000 |
commit | 41fe97390ceddf945f3d967b8fdb3de4c66b7dea (patch) | |
tree | 9c8d89a8624828992f06d892cd2f43818ff5dcc8 /spec/frontend/environments | |
parent | 0804d2dc31052fb45a1efecedc8e06ce9bc32862 (diff) | |
download | gitlab-ce-41fe97390ceddf945f3d967b8fdb3de4c66b7dea.tar.gz |
Add latest changes from gitlab-org/gitlab@14-9-stable-eev14.9.0-rc42
Diffstat (limited to 'spec/frontend/environments')
10 files changed, 503 insertions, 652 deletions
diff --git a/spec/frontend/environments/delete_environment_modal_spec.js b/spec/frontend/environments/delete_environment_modal_spec.js index 50c4ca00009..48e4f661c1d 100644 --- a/spec/frontend/environments/delete_environment_modal_spec.js +++ b/spec/frontend/environments/delete_environment_modal_spec.js @@ -5,8 +5,11 @@ 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 waitForPromises from 'helpers/wait_for_promises'; +import createFlash from '~/flash'; import { resolvedEnvironment } from './graphql/mock_data'; +jest.mock('~/flash'); Vue.use(VueApollo); describe('~/environments/components/delete_environment_modal.vue', () => { @@ -54,6 +57,34 @@ describe('~/environments/components/delete_environment_modal.vue', () => { await nextTick(); + expect(createFlash).not.toHaveBeenCalled(); + + expect(deleteResolver).toHaveBeenCalledWith( + expect.anything(), + { environment: resolvedEnvironment }, + expect.anything(), + expect.anything(), + ); + }); + + it('should flash a message on error', async () => { + createComponent({ apolloProvider: mockApollo }); + + deleteResolver.mockRejectedValue(); + + wrapper.findComponent(GlModal).vm.$emit('primary'); + + await waitForPromises(); + + expect(createFlash).toHaveBeenCalledWith( + expect.objectContaining({ + message: s__( + 'Environments|An error occurred while deleting the environment. Check if the environment stopped; if not, stop it and try again.', + ), + captureError: true, + }), + ); + expect(deleteResolver).toHaveBeenCalledWith( expect.anything(), { environment: resolvedEnvironment }, diff --git a/spec/frontend/environments/enable_review_app_modal_spec.js b/spec/frontend/environments/enable_review_app_modal_spec.js index 17ae10a2884..b6dac811ea6 100644 --- a/spec/frontend/environments/enable_review_app_modal_spec.js +++ b/spec/frontend/environments/enable_review_app_modal_spec.js @@ -4,10 +4,17 @@ 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'; +// hardcode uniqueId for determinism +jest.mock('lodash/uniqueId', () => (x) => `${x}77`); + +const EXPECTED_COPY_PRE_ID = 'enable-review-app-copy-string-77'; + describe('Enable Review App Button', () => { let wrapper; let modal; + const findCopyString = () => wrapper.find(`#${EXPECTED_COPY_PRE_ID}`); + afterEach(() => { wrapper.destroy(); }); @@ -30,12 +37,15 @@ describe('Enable Review App Button', () => { }); it('renders the defaultBranchName copy', () => { - const findCopyString = () => wrapper.findByTestId('enable-review-app-copy-string'); expect(findCopyString().text()).toContain('- main'); }); it('renders the copyToClipboard button', () => { - expect(wrapper.findComponent(ModalCopyButton).exists()).toBe(true); + expect(wrapper.findComponent(ModalCopyButton).props()).toMatchObject({ + modalId: 'fake-id', + target: `#${EXPECTED_COPY_PRE_ID}`, + title: 'Copy snippet text', + }); }); it('emits change events from the modal up', () => { diff --git a/spec/frontend/environments/environment_actions_spec.js b/spec/frontend/environments/environment_actions_spec.js index 336c207428e..ada79e2d415 100644 --- a/spec/frontend/environments/environment_actions_spec.js +++ b/spec/frontend/environments/environment_actions_spec.js @@ -7,8 +7,15 @@ import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; import EnvironmentActions from '~/environments/components/environment_actions.vue'; import eventHub from '~/environments/event_hub'; import actionMutation from '~/environments/graphql/mutations/action.mutation.graphql'; +import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal'; import createMockApollo from 'helpers/mock_apollo_helper'; +jest.mock('~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal', () => { + return { + confirmAction: jest.fn(), + }; +}); + const scheduledJobAction = { name: 'scheduled action', playPath: `${TEST_HOST}/scheduled/job/action`, @@ -50,7 +57,7 @@ describe('EnvironmentActions Component', () => { afterEach(() => { wrapper.destroy(); - wrapper = null; + confirmAction.mockReset(); }); it('should render a dropdown button with 2 icons', () => { @@ -105,7 +112,7 @@ describe('EnvironmentActions Component', () => { let emitSpy; const clickAndConfirm = async ({ confirm = true } = {}) => { - jest.spyOn(window, 'confirm').mockImplementation(() => confirm); + confirmAction.mockResolvedValueOnce(confirm); findDropdownItem(scheduledJobAction).vm.$emit('click'); await nextTick(); @@ -124,7 +131,7 @@ describe('EnvironmentActions Component', () => { }); it('emits postAction event', () => { - expect(window.confirm).toHaveBeenCalled(); + expect(confirmAction).toHaveBeenCalled(); expect(emitSpy).toHaveBeenCalledWith({ endpoint: scheduledJobAction.playPath }); }); @@ -134,13 +141,13 @@ describe('EnvironmentActions Component', () => { }); describe('when postAction event is denied', () => { - beforeEach(() => { + beforeEach(async () => { createComponentWithScheduledJobs({ mountFn: mount }); clickAndConfirm({ confirm: false }); }); it('does not emit postAction event if confirmation is cancelled', () => { - expect(window.confirm).toHaveBeenCalled(); + expect(confirmAction).toHaveBeenCalled(); expect(emitSpy).not.toHaveBeenCalled(); }); }); diff --git a/spec/frontend/environments/environment_folder_spec.js b/spec/frontend/environments/environment_folder_spec.js new file mode 100644 index 00000000000..f2027252f05 --- /dev/null +++ b/spec/frontend/environments/environment_folder_spec.js @@ -0,0 +1,132 @@ +import VueApollo from 'vue-apollo'; +import Vue, { nextTick } from 'vue'; +import { GlCollapse, GlIcon } from '@gitlab/ui'; +import waitForPromises from 'helpers/wait_for_promises'; +import createMockApollo from 'helpers/mock_apollo_helper'; +import { mountExtended } from 'helpers/vue_test_utils_helper'; +import { stubTransition } from 'helpers/stub_transition'; +import { __, s__ } from '~/locale'; +import EnvironmentsFolder from '~/environments/components/environment_folder.vue'; +import EnvironmentItem from '~/environments/components/new_environment_item.vue'; +import { resolvedEnvironmentsApp, resolvedFolder } from './graphql/mock_data'; + +Vue.use(VueApollo); + +describe('~/environments/components/environments_folder.vue', () => { + let wrapper; + let environmentFolderMock; + let nestedEnvironment; + + const findLink = () => wrapper.findByRole('link', { name: s__('Environments|Show all') }); + + const createApolloProvider = () => { + const mockResolvers = { Query: { folder: environmentFolderMock } }; + + return createMockApollo([], mockResolvers); + }; + + const createWrapper = (propsData, apolloProvider) => + mountExtended(EnvironmentsFolder, { + apolloProvider, + propsData: { + scope: 'available', + ...propsData, + }, + stubs: { transition: stubTransition() }, + provide: { helpPagePath: '/help' }, + }); + + beforeEach(async () => { + environmentFolderMock = jest.fn(); + [nestedEnvironment] = resolvedEnvironmentsApp.environments; + environmentFolderMock.mockReturnValue(resolvedFolder); + }); + + afterEach(() => { + wrapper?.destroy(); + }); + + describe('default', () => { + let folderName; + let button; + + beforeEach(async () => { + wrapper = createWrapper({ nestedEnvironment }, createApolloProvider()); + + await nextTick(); + await waitForPromises(); + folderName = wrapper.findByText(nestedEnvironment.name); + button = wrapper.findByRole('button', { name: __('Expand') }); + }); + + it('displays the name of the folder', () => { + expect(folderName.text()).toBe(nestedEnvironment.name); + }); + + describe('collapse', () => { + let icons; + let collapse; + + beforeEach(() => { + collapse = wrapper.findComponent(GlCollapse); + icons = wrapper.findAllComponents(GlIcon); + }); + + it('is collapsed by default', () => { + const link = findLink(); + + expect(collapse.attributes('visible')).toBeUndefined(); + const iconNames = icons.wrappers.map((i) => i.props('name')).slice(0, 2); + expect(iconNames).toEqual(['angle-right', 'folder-o']); + expect(folderName.classes('gl-font-weight-bold')).toBe(false); + expect(link.exists()).toBe(false); + }); + + it('opens on click', async () => { + await button.trigger('click'); + + const link = findLink(); + + expect(button.attributes('aria-label')).toBe(__('Collapse')); + expect(collapse.attributes('visible')).toBe('visible'); + const iconNames = icons.wrappers.map((i) => i.props('name')).slice(0, 2); + expect(iconNames).toEqual(['angle-down', 'folder-open']); + expect(folderName.classes('gl-font-weight-bold')).toBe(true); + expect(link.attributes('href')).toBe(nestedEnvironment.latest.folderPath); + }); + + it('displays all environments when opened', async () => { + await button.trigger('click'); + + const names = resolvedFolder.environments.map((e) => + expect.stringMatching(e.nameWithoutType), + ); + const environments = wrapper + .findAllComponents(EnvironmentItem) + .wrappers.map((w) => w.text()); + expect(environments).toEqual(expect.arrayContaining(names)); + }); + }); + }); + + it.each(['available', 'stopped'])( + 'with scope=%s, fetches environments with scope', + async (scope) => { + wrapper = createWrapper({ nestedEnvironment, scope }, createApolloProvider()); + + await nextTick(); + await waitForPromises(); + + expect(environmentFolderMock).toHaveBeenCalledTimes(1); + expect(environmentFolderMock).toHaveBeenCalledWith( + {}, + { + environment: nestedEnvironment.latest, + scope, + }, + expect.anything(), + expect.anything(), + ); + }, + ); +}); diff --git a/spec/frontend/environments/environment_item_spec.js b/spec/frontend/environments/environment_item_spec.js index b930259149f..0b36d2a940d 100644 --- a/spec/frontend/environments/environment_item_spec.js +++ b/spec/frontend/environments/environment_item_spec.js @@ -79,7 +79,7 @@ describe('Environment item', () => { describe('With user information', () => { it('should render user avatar with link to profile', () => { - expect(wrapper.find('.js-deploy-user-container').attributes('href')).toEqual( + expect(wrapper.find('.js-deploy-user-container').props('linkHref')).toEqual( environment.last_deployment.user.web_url, ); }); diff --git a/spec/frontend/environments/environments_app_spec.js b/spec/frontend/environments/environments_app_spec.js index 92d1820681c..91b75c850bd 100644 --- a/spec/frontend/environments/environments_app_spec.js +++ b/spec/frontend/environments/environments_app_spec.js @@ -1,282 +1,355 @@ -import { GlTabs } from '@gitlab/ui'; -import { mount, shallowMount } from '@vue/test-utils'; -import MockAdapter from 'axios-mock-adapter'; -import { nextTick } from 'vue'; -import { extendedWrapper } from 'helpers/vue_test_utils_helper'; -import Container from '~/environments/components/container.vue'; -import DeployBoard from '~/environments/components/deploy_board.vue'; -import EmptyState from '~/environments/components/empty_state.vue'; -import EnableReviewAppModal from '~/environments/components/enable_review_app_modal.vue'; +import Vue, { nextTick } from 'vue'; +import VueApollo from 'vue-apollo'; +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/environments_app.vue'; -import axios from '~/lib/utils/axios_utils'; -import * as urlUtils from '~/lib/utils/url_utility'; -import { environment, folder } from './mock_data'; +import EnvironmentsFolder from '~/environments/components/environment_folder.vue'; +import EnvironmentsItem from '~/environments/components/new_environment_item.vue'; +import EmptyState from '~/environments/components/empty_state.vue'; +import StopEnvironmentModal from '~/environments/components/stop_environment_modal.vue'; +import CanaryUpdateModal from '~/environments/components/canary_update_modal.vue'; +import { resolvedEnvironmentsApp, resolvedFolder, resolvedEnvironment } from './graphql/mock_data'; -describe('Environment', () => { - let mock; - let wrapper; +Vue.use(VueApollo); - const mockData = { - endpoint: 'environments.json', - canCreateEnvironment: true, - newEnvironmentPath: 'environments/new', - helpPagePath: 'help', - userCalloutsPath: '/callouts', - lockPromotionSvgPath: '/assets/illustrations/lock-promotion.svg', - helpCanaryDeploymentsPath: 'help/canary-deployments', +describe('~/environments/components/environments_app.vue', () => { + let wrapper; + let environmentAppMock; + let environmentFolderMock; + let paginationMock; + let environmentToStopMock; + let environmentToChangeCanaryMock; + let weightMock; + + const createApolloProvider = () => { + const mockResolvers = { + Query: { + environmentApp: environmentAppMock, + folder: environmentFolderMock, + pageInfo: paginationMock, + environmentToStop: environmentToStopMock, + environmentToDelete: jest.fn().mockResolvedValue(resolvedEnvironment), + environmentToRollback: jest.fn().mockResolvedValue(resolvedEnvironment), + environmentToChangeCanary: environmentToChangeCanaryMock, + weight: weightMock, + }, + }; + + return createMockApollo([], mockResolvers); }; - const mockRequest = (response, body) => { - mock.onGet(mockData.endpoint).reply(response, body, { - 'X-nExt-pAge': '2', - 'x-page': '1', - 'X-Per-Page': '1', - 'X-Prev-Page': '', - 'X-TOTAL': '37', - 'X-Total-Pages': '2', + const createWrapper = ({ provide = {}, apolloProvider } = {}) => + mountExtended(EnvironmentsApp, { + provide: { + newEnvironmentPath: '/environments/new', + canCreateEnvironment: true, + defaultBranchName: 'main', + helpPagePath: '/help', + projectId: '1', + ...provide, + }, + apolloProvider, }); - }; - const createWrapper = (shallow = false, props = {}) => { - const fn = shallow ? shallowMount : mount; - wrapper = extendedWrapper(fn(EnvironmentsApp, { propsData: { ...mockData, ...props } })); - return axios.waitForAll(); + const createWrapperWithMocked = async ({ + provide = {}, + environmentsApp, + folder, + environmentToStop = {}, + environmentToChangeCanary = {}, + weight = 0, + pageInfo = { + total: 20, + perPage: 5, + nextPage: 3, + page: 2, + previousPage: 1, + __typename: 'LocalPageInfo', + }, + location = '?scope=available&page=2', + }) => { + setWindowLocation(location); + environmentAppMock.mockReturnValue(environmentsApp); + environmentFolderMock.mockReturnValue(folder); + paginationMock.mockReturnValue(pageInfo); + environmentToStopMock.mockReturnValue(environmentToStop); + environmentToChangeCanaryMock.mockReturnValue(environmentToChangeCanary); + weightMock.mockReturnValue(weight); + const apolloProvider = createApolloProvider(); + wrapper = createWrapper({ apolloProvider, provide }); + + await waitForPromises(); + await nextTick(); }; - const findEnableReviewAppButton = () => wrapper.findByTestId('enable-review-app'); - const findEnableReviewAppModal = () => wrapper.findAll(EnableReviewAppModal); - const findNewEnvironmentButton = () => wrapper.findByTestId('new-environment'); - const findEnvironmentsTabAvailable = () => wrapper.find('.js-environments-tab-available > a'); - const findEnvironmentsTabStopped = () => wrapper.find('.js-environments-tab-stopped > a'); - beforeEach(() => { - mock = new MockAdapter(axios); + environmentAppMock = jest.fn(); + environmentFolderMock = jest.fn(); + environmentToStopMock = jest.fn(); + environmentToChangeCanaryMock = jest.fn(); + weightMock = jest.fn(); + paginationMock = jest.fn(); }); afterEach(() => { wrapper.destroy(); - mock.restore(); }); - describe('successful request', () => { - describe('without environments', () => { - beforeEach(() => { - mockRequest(200, { environments: [] }); - return createWrapper(); - }); + it('should request available environments if the scope is invalid', async () => { + await createWrapperWithMocked({ + environmentsApp: resolvedEnvironmentsApp, + folder: resolvedFolder, + location: '?scope=bad&page=2', + }); - it('should render the empty state', () => { - expect(wrapper.find(EmptyState).exists()).toBe(true); - }); + expect(environmentAppMock).toHaveBeenCalledWith( + expect.anything(), + expect.objectContaining({ scope: 'available', page: 2 }), + expect.anything(), + expect.anything(), + ); + }); + + it('should show all the folders that are fetched', async () => { + await createWrapperWithMocked({ + environmentsApp: resolvedEnvironmentsApp, + folder: resolvedFolder, }); - describe('with paginated environments', () => { - const environmentList = [environment]; + const text = wrapper.findAllComponents(EnvironmentsFolder).wrappers.map((w) => w.text()); - beforeEach(() => { - mockRequest(200, { - environments: environmentList, - stopped_count: 1, - available_count: 0, - }); - return createWrapper(); - }); + expect(text).toContainEqual(expect.stringMatching('review')); + expect(text).not.toContainEqual(expect.stringMatching('production')); + }); - it('should render a container table with environments', () => { - const containerTable = wrapper.find(Container); + it('should show all the environments that are fetched', async () => { + await createWrapperWithMocked({ + environmentsApp: resolvedEnvironmentsApp, + folder: resolvedFolder, + }); - expect(containerTable.exists()).toBe(true); - expect(containerTable.props('environments').length).toEqual(environmentList.length); - expect(containerTable.find('.environment-name').text()).toEqual(environmentList[0].name); - }); + const text = wrapper.findAllComponents(EnvironmentsItem).wrappers.map((w) => w.text()); - describe('pagination', () => { - it('should render pagination', () => { - expect(wrapper.findAll('.gl-pagination li').length).toEqual(9); - }); - - it('should make an API request when page is clicked', () => { - jest.spyOn(wrapper.vm, 'updateContent').mockImplementation(() => {}); - - wrapper.find('.gl-pagination li:nth-child(3) .page-link').trigger('click'); - expect(wrapper.vm.updateContent).toHaveBeenCalledWith({ - scope: 'available', - page: '2', - nested: true, - }); - }); - - it('should make an API request when using tabs', () => { - jest.spyOn(wrapper.vm, 'updateContent').mockImplementation(() => {}); - findEnvironmentsTabStopped().trigger('click'); - expect(wrapper.vm.updateContent).toHaveBeenCalledWith({ - scope: 'stopped', - page: '1', - nested: true, - }); - }); - - it('should not make the same API request when clicking on the current scope tab', () => { - // component starts at available - jest.spyOn(wrapper.vm, 'updateContent').mockImplementation(() => {}); - findEnvironmentsTabAvailable().trigger('click'); - expect(wrapper.vm.updateContent).toHaveBeenCalledTimes(0); - }); - }); + expect(text).not.toContainEqual(expect.stringMatching('review')); + expect(text).toContainEqual(expect.stringMatching('production')); + }); - describe('deploy boards', () => { - beforeEach(() => { - const deployEnvironment = { - ...environment, - rollout_status: { - status: 'found', - }, - }; - - mockRequest(200, { - environments: [deployEnvironment], - stopped_count: 1, - available_count: 0, - }); - - return createWrapper(); - }); - - it('should render deploy boards', () => { - expect(wrapper.find(DeployBoard).exists()).toBe(true); - }); - - it('should render arrow to open deploy boards', () => { - expect( - wrapper.find('.deploy-board-icon [data-testid="chevron-down-icon"]').exists(), - ).toBe(true); - }); - }); + it('should show an empty state with no environments', async () => { + await createWrapperWithMocked({ + environmentsApp: { ...resolvedEnvironmentsApp, environments: [] }, }); + + expect(wrapper.findComponent(EmptyState).exists()).toBe(true); }); - describe('unsuccessful request', () => { - beforeEach(() => { - mockRequest(500, {}); - return createWrapper(); + it('should show a button to create a new environment', async () => { + await createWrapperWithMocked({ + environmentsApp: resolvedEnvironmentsApp, + folder: resolvedFolder, }); - it('should render empty state', () => { - expect(wrapper.find(EmptyState).exists()).toBe(true); - }); + const button = wrapper.findByRole('link', { name: s__('Environments|New environment') }); + expect(button.attributes('href')).toBe('/environments/new'); }); - describe('expandable folders', () => { - beforeEach(() => { - mockRequest(200, { - environments: [folder], - stopped_count: 1, - available_count: 0, - }); + 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: '' }, + }); - mock.onGet(environment.folder_path).reply(200, { environments: [environment] }); + const button = wrapper.findByRole('link', { name: s__('Environments|New environment') }); + expect(button.exists()).toBe(false); + }); - return createWrapper().then(() => { - // open folder - wrapper.find('.folder-name').trigger('click'); - return axios.waitForAll(); - }); + it('should show a button to open the review app modal', async () => { + await createWrapperWithMocked({ + environmentsApp: resolvedEnvironmentsApp, + folder: resolvedFolder, }); - it('should open a closed folder', () => { - expect(wrapper.find('.folder-icon[data-testid="chevron-right-icon"]').exists()).toBe(false); - }); + const button = wrapper.findByRole('button', { name: s__('Environments|Enable review app') }); + button.trigger('click'); - it('should close an opened folder', async () => { - expect(wrapper.find('.folder-icon[data-testid="chevron-down-icon"]').exists()).toBe(true); + await nextTick(); - // close folder - wrapper.find('.folder-name').trigger('click'); - await nextTick(); - expect(wrapper.find('.folder-icon[data-testid="chevron-down-icon"]').exists()).toBe(false); - }); + expect(wrapper.findByText(s__('ReviewApp|Enable Review App')).exists()).toBe(true); + }); - it('should show children environments', () => { - expect(wrapper.findAll('.js-child-row').length).toEqual(1); + 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, }); - it('should show a button to show all environments', () => { - expect(wrapper.find('.text-center > a.btn').text()).toContain('Show all'); - }); + const button = wrapper.findByRole('button', { name: s__('Environments|Enable review app') }); + expect(button.exists()).toBe(false); }); - describe('environment button', () => { - describe('when user can create environment', () => { - beforeEach(() => { - mockRequest(200, { environments: [] }); - return createWrapper(true); + describe('tabs', () => { + it('should show tabs for available and stopped environmets', async () => { + await createWrapperWithMocked({ + environmentsApp: resolvedEnvironmentsApp, + folder: resolvedFolder, }); - it('should render', () => { - expect(findNewEnvironmentButton().exists()).toBe(true); + 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', page: 1 }), + expect.anything(), + expect.anything(), + ); }); + }); - describe('when user can not create environment', () => { - beforeEach(() => { - mockRequest(200, { environments: [] }); - return createWrapper(true, { ...mockData, canCreateEnvironment: false }); + describe('modals', () => { + it('should pass the environment to stop to the stop environment modal', async () => { + await createWrapperWithMocked({ + environmentsApp: resolvedEnvironmentsApp, + folder: resolvedFolder, + environmentToStop: resolvedEnvironment, }); - it('should not render', () => { - expect(findNewEnvironmentButton().exists()).toBe(false); + const modal = wrapper.findComponent(StopEnvironmentModal); + + expect(modal.props('environment')).toMatchObject(resolvedEnvironment); + }); + + it('should pass the environment to change canary to the canary update modal', async () => { + await createWrapperWithMocked({ + environmentsApp: resolvedEnvironmentsApp, + folder: resolvedFolder, + environmentToChangeCanary: resolvedEnvironment, + weight: 10, }); + + const modal = wrapper.findComponent(CanaryUpdateModal); + + expect(modal.props('environment')).toMatchObject(resolvedEnvironment); }); }); - describe('review app modal', () => { - describe('when it is not possible to enable a review app', () => { - beforeEach(() => { - mockRequest(200, { environments: [] }); - return createWrapper(true); + describe('pagination', () => { + it('should sync page from query params on load', async () => { + await createWrapperWithMocked({ + environmentsApp: resolvedEnvironmentsApp, + folder: resolvedFolder, }); - it('should not render the enable review app button', () => { - expect(findEnableReviewAppButton().exists()).toBe(false); - }); + expect(wrapper.findComponent(GlPagination).props('value')).toBe(2); + }); - it('should not render a review app modal', () => { - const modal = findEnableReviewAppModal(); - expect(modal).toHaveLength(0); - expect(modal.exists()).toBe(false); + 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(), + ); }); - describe('when it is possible to enable a review app', () => { - beforeEach(() => { - mockRequest(200, { environments: [], review_app: { can_setup_review_app: true } }); - return createWrapper(true); + 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'), }); - it('should render the enable review app button', () => { - expect(findEnableReviewAppButton().exists()).toBe(true); - expect(findEnableReviewAppButton().text()).toContain('Enable review app'); + 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, }); - it('should render only one review app modal', () => { - const modal = findEnableReviewAppModal(); - expect(modal).toHaveLength(1); - expect(modal.at(0).exists()).toBe(true); + const page = 1; + const pageButton = wrapper.findByRole('link', { + name: sprintf(__('Go to page %{page}'), { page }), }); - }); - }); - describe('tabs', () => { - beforeEach(() => { - mockRequest(200, { environments: [] }); - jest - .spyOn(urlUtils, 'getParameterByName') - .mockImplementation((param) => (param === 'scope' ? 'stopped' : null)); - return createWrapper(true); + pageButton.trigger('click'); + + await nextTick(); + await waitForPromises(); + + expect(environmentAppMock).toHaveBeenCalledWith( + expect.anything(), + expect.objectContaining({ page }), + expect.anything(), + expect.anything(), + ); }); - it('selects the tab for the parameter', () => { - expect(wrapper.findComponent(GlTabs).attributes('value')).toBe('1'); + 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'); }); }); }); diff --git a/spec/frontend/environments/graphql/resolvers_spec.js b/spec/frontend/environments/graphql/resolvers_spec.js index 21d7e09bad5..26f0659204a 100644 --- a/spec/frontend/environments/graphql/resolvers_spec.js +++ b/spec/frontend/environments/graphql/resolvers_spec.js @@ -7,6 +7,7 @@ import environmentToDelete from '~/environments/graphql/queries/environment_to_d import environmentToStopQuery from '~/environments/graphql/queries/environment_to_stop.query.graphql'; import createMockApollo from 'helpers/mock_apollo_helper'; import pollIntervalQuery from '~/environments/graphql/queries/poll_interval.query.graphql'; +import isEnvironmentStoppingQuery from '~/environments/graphql/queries/is_environment_stopping.query.graphql'; import pageInfoQuery from '~/environments/graphql/queries/page_info.query.graphql'; import { TEST_HOST } from 'helpers/test_constants'; import { @@ -123,10 +124,11 @@ describe('~/frontend/environments/graphql/resolvers', () => { }); describe('folder', () => { it('should fetch the folder url passed to it', async () => { - mock.onGet(ENDPOINT, { params: { per_page: 3 } }).reply(200, folder); + mock.onGet(ENDPOINT, { params: { per_page: 3, scope: 'available' } }).reply(200, folder); const environmentFolder = await mockResolvers.Query.folder(null, { environment: { folderPath: ENDPOINT }, + scope: 'available', }); expect(environmentFolder).toEqual(resolvedFolder); @@ -136,11 +138,36 @@ describe('~/frontend/environments/graphql/resolvers', () => { it('should post to the stop environment path', async () => { mock.onPost(ENDPOINT).reply(200); - await mockResolvers.Mutation.stopEnvironment(null, { environment: { stopPath: ENDPOINT } }); + const client = { writeQuery: jest.fn() }; + const environment = { stopPath: ENDPOINT }; + await mockResolvers.Mutation.stopEnvironment(null, { environment }, { client }); expect(mock.history.post).toContainEqual( expect.objectContaining({ url: ENDPOINT, method: 'post' }), ); + + expect(client.writeQuery).toHaveBeenCalledWith({ + query: isEnvironmentStoppingQuery, + variables: { environment }, + data: { isEnvironmentStopping: true }, + }); + }); + it('should set is stopping to false if stop fails', async () => { + mock.onPost(ENDPOINT).reply(500); + + const client = { writeQuery: jest.fn() }; + const environment = { stopPath: ENDPOINT }; + await mockResolvers.Mutation.stopEnvironment(null, { environment }, { client }); + + expect(mock.history.post).toContainEqual( + expect.objectContaining({ url: ENDPOINT, method: 'post' }), + ); + + expect(client.writeQuery).toHaveBeenCalledWith({ + query: isEnvironmentStoppingQuery, + variables: { environment }, + data: { isEnvironmentStopping: false }, + }); }); }); describe('rollbackEnvironment', () => { diff --git a/spec/frontend/environments/new_environment_folder_spec.js b/spec/frontend/environments/new_environment_folder_spec.js deleted file mode 100644 index 460263587be..00000000000 --- a/spec/frontend/environments/new_environment_folder_spec.js +++ /dev/null @@ -1,100 +0,0 @@ -import VueApollo from 'vue-apollo'; -import Vue, { nextTick } from 'vue'; -import { GlCollapse, GlIcon } from '@gitlab/ui'; -import waitForPromises from 'helpers/wait_for_promises'; -import createMockApollo from 'helpers/mock_apollo_helper'; -import { mountExtended } from 'helpers/vue_test_utils_helper'; -import { stubTransition } from 'helpers/stub_transition'; -import { __, s__ } from '~/locale'; -import EnvironmentsFolder from '~/environments/components/new_environment_folder.vue'; -import EnvironmentItem from '~/environments/components/new_environment_item.vue'; -import { resolvedEnvironmentsApp, resolvedFolder } from './graphql/mock_data'; - -Vue.use(VueApollo); - -describe('~/environments/components/new_environments_folder.vue', () => { - let wrapper; - let environmentFolderMock; - let nestedEnvironment; - let folderName; - let button; - - const findLink = () => wrapper.findByRole('link', { name: s__('Environments|Show all') }); - - const createApolloProvider = () => { - const mockResolvers = { Query: { folder: environmentFolderMock } }; - - return createMockApollo([], mockResolvers); - }; - - const createWrapper = (propsData, apolloProvider) => - mountExtended(EnvironmentsFolder, { - apolloProvider, - propsData, - stubs: { transition: stubTransition() }, - provide: { helpPagePath: '/help' }, - }); - - beforeEach(async () => { - environmentFolderMock = jest.fn(); - [nestedEnvironment] = resolvedEnvironmentsApp.environments; - environmentFolderMock.mockReturnValue(resolvedFolder); - wrapper = createWrapper({ nestedEnvironment }, createApolloProvider()); - - await nextTick(); - await waitForPromises(); - folderName = wrapper.findByText(nestedEnvironment.name); - button = wrapper.findByRole('button', { name: __('Expand') }); - }); - - afterEach(() => { - wrapper?.destroy(); - }); - - it('displays the name of the folder', () => { - expect(folderName.text()).toBe(nestedEnvironment.name); - }); - - describe('collapse', () => { - let icons; - let collapse; - - beforeEach(() => { - collapse = wrapper.findComponent(GlCollapse); - icons = wrapper.findAllComponents(GlIcon); - }); - - it('is collapsed by default', () => { - const link = findLink(); - - expect(collapse.attributes('visible')).toBeUndefined(); - const iconNames = icons.wrappers.map((i) => i.props('name')).slice(0, 2); - expect(iconNames).toEqual(['angle-right', 'folder-o']); - expect(folderName.classes('gl-font-weight-bold')).toBe(false); - expect(link.exists()).toBe(false); - }); - - it('opens on click', async () => { - await button.trigger('click'); - - const link = findLink(); - - expect(button.attributes('aria-label')).toBe(__('Collapse')); - expect(collapse.attributes('visible')).toBe('visible'); - const iconNames = icons.wrappers.map((i) => i.props('name')).slice(0, 2); - expect(iconNames).toEqual(['angle-down', 'folder-open']); - expect(folderName.classes('gl-font-weight-bold')).toBe(true); - expect(link.attributes('href')).toBe(nestedEnvironment.latest.folderPath); - }); - - it('displays all environments when opened', async () => { - await button.trigger('click'); - - const names = resolvedFolder.environments.map((e) => - expect.stringMatching(e.nameWithoutType), - ); - const environments = wrapper.findAllComponents(EnvironmentItem).wrappers.map((w) => w.text()); - expect(environments).toEqual(expect.arrayContaining(names)); - }); - }); -}); diff --git a/spec/frontend/environments/new_environment_item_spec.js b/spec/frontend/environments/new_environment_item_spec.js index db596688dad..1d7a33fb95b 100644 --- a/spec/frontend/environments/new_environment_item_spec.js +++ b/spec/frontend/environments/new_environment_item_spec.js @@ -24,7 +24,7 @@ describe('~/environments/components/new_environment_item.vue', () => { mountExtended(EnvironmentItem, { apolloProvider, propsData: { environment: resolvedEnvironment, ...propsData }, - provide: { helpPagePath: '/help' }, + provide: { helpPagePath: '/help', projectId: '1' }, stubs: { transition: stubTransition() }, }); diff --git a/spec/frontend/environments/new_environments_app_spec.js b/spec/frontend/environments/new_environments_app_spec.js deleted file mode 100644 index 42e3608109b..00000000000 --- a/spec/frontend/environments/new_environments_app_spec.js +++ /dev/null @@ -1,329 +0,0 @@ -import Vue, { nextTick } from 'vue'; -import VueApollo from 'vue-apollo'; -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 EnvironmentsItem from '~/environments/components/new_environment_item.vue'; -import StopEnvironmentModal from '~/environments/components/stop_environment_modal.vue'; -import CanaryUpdateModal from '~/environments/components/canary_update_modal.vue'; -import { resolvedEnvironmentsApp, resolvedFolder, resolvedEnvironment } from './graphql/mock_data'; - -Vue.use(VueApollo); - -describe('~/environments/components/new_environments_app.vue', () => { - let wrapper; - let environmentAppMock; - let environmentFolderMock; - let paginationMock; - let environmentToStopMock; - let environmentToChangeCanaryMock; - let weightMock; - - const createApolloProvider = () => { - const mockResolvers = { - Query: { - environmentApp: environmentAppMock, - folder: environmentFolderMock, - pageInfo: paginationMock, - environmentToStop: environmentToStopMock, - environmentToDelete: jest.fn().mockResolvedValue(resolvedEnvironment), - environmentToRollback: jest.fn().mockResolvedValue(resolvedEnvironment), - environmentToChangeCanary: environmentToChangeCanaryMock, - weight: weightMock, - }, - }; - - return createMockApollo([], mockResolvers); - }; - - const createWrapper = ({ provide = {}, apolloProvider } = {}) => - mountExtended(EnvironmentsApp, { - provide: { - newEnvironmentPath: '/environments/new', - canCreateEnvironment: true, - defaultBranchName: 'main', - helpPagePath: '/help', - ...provide, - }, - apolloProvider, - }); - - const createWrapperWithMocked = async ({ - provide = {}, - environmentsApp, - folder, - environmentToStop = {}, - environmentToChangeCanary = {}, - weight = 0, - 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); - environmentToStopMock.mockReturnValue(environmentToStop); - environmentToChangeCanaryMock.mockReturnValue(environmentToChangeCanary); - weightMock.mockReturnValue(weight); - const apolloProvider = createApolloProvider(); - wrapper = createWrapper({ apolloProvider, provide }); - - await waitForPromises(); - await nextTick(); - }; - - beforeEach(() => { - environmentAppMock = jest.fn(); - environmentFolderMock = jest.fn(); - environmentToStopMock = jest.fn(); - environmentToChangeCanaryMock = jest.fn(); - weightMock = jest.fn(); - paginationMock = jest.fn(); - }); - - afterEach(() => { - wrapper.destroy(); - }); - - it('should show all the folders that are fetched', async () => { - 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 all the environments that are fetched', async () => { - await createWrapperWithMocked({ - environmentsApp: resolvedEnvironmentsApp, - folder: resolvedFolder, - }); - - const text = wrapper.findAllComponents(EnvironmentsItem).wrappers.map((w) => w.text()); - - expect(text).not.toContainEqual(expect.stringMatching('review')); - expect(text).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, - }); - - 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', page: 1 }), - expect.anything(), - expect.anything(), - ); - }); - }); - - describe('modals', () => { - it('should pass the environment to stop to the stop environment modal', async () => { - await createWrapperWithMocked({ - environmentsApp: resolvedEnvironmentsApp, - folder: resolvedFolder, - environmentToStop: resolvedEnvironment, - }); - - const modal = wrapper.findComponent(StopEnvironmentModal); - - expect(modal.props('environment')).toMatchObject(resolvedEnvironment); - }); - - it('should pass the environment to change canary to the canary update modal', async () => { - await createWrapperWithMocked({ - environmentsApp: resolvedEnvironmentsApp, - folder: resolvedFolder, - environmentToChangeCanary: resolvedEnvironment, - weight: 10, - }); - - const modal = wrapper.findComponent(CanaryUpdateModal); - - expect(modal.props('environment')).toMatchObject(resolvedEnvironment); - }); - }); - - 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'); - }); - }); -}); |