diff options
Diffstat (limited to 'spec/frontend/environments/environments_app_spec.js')
-rw-r--r-- | spec/frontend/environments/environments_app_spec.js | 497 |
1 files changed, 285 insertions, 212 deletions
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'); }); }); }); |