summaryrefslogtreecommitdiff
path: root/spec/frontend/environments/environments_app_spec.js
diff options
context:
space:
mode:
Diffstat (limited to 'spec/frontend/environments/environments_app_spec.js')
-rw-r--r--spec/frontend/environments/environments_app_spec.js497
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');
});
});
});