From b595cb0c1dec83de5bdee18284abe86614bed33b Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Wed, 20 Jul 2022 15:40:28 +0000 Subject: Add latest changes from gitlab-org/gitlab@15-2-stable-ee --- .../components/details_page/details_header_spec.js | 6 +- .../components/list_page/cleanup_status_spec.js | 12 + .../components/list_page/image_list_row_spec.js | 44 +++- .../container_registry/explorer/mock_data.js | 8 + .../explorer/pages/details_spec.js | 5 +- .../dependency_proxy/app_spec.js | 27 ++- .../components/dependency_proxy_settings_spec.js | 25 +- .../group/components/duplicates_settings_spec.js | 21 +- .../group/components/group_settings_app_spec.js | 1 - .../group/components/package_settings_spec.js | 21 -- .../container_expiration_policy_form_spec.js.snap | 3 + .../components/container_expiration_policy_spec.js | 7 +- .../components/expiration_dropdown_spec.js | 9 + .../packages_cleanup_policy_form_spec.js | 267 +++++++++++++++++++++ .../components/packages_cleanup_policy_spec.js | 81 +++++++ .../components/registry_settings_app_spec.js | 30 ++- .../settings/project/settings/mock_data.js | 30 +++ .../shared/components/settings_block_spec.js | 43 ++++ 18 files changed, 548 insertions(+), 92 deletions(-) create mode 100644 spec/frontend/packages_and_registries/settings/project/settings/components/packages_cleanup_policy_form_spec.js create mode 100644 spec/frontend/packages_and_registries/settings/project/settings/components/packages_cleanup_policy_spec.js create mode 100644 spec/frontend/packages_and_registries/shared/components/settings_block_spec.js (limited to 'spec/frontend/packages_and_registries') diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/details_header_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/details_header_spec.js index ca666e38291..9982286c625 100644 --- a/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/details_header_spec.js +++ b/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/details_header_spec.js @@ -18,7 +18,6 @@ import { CLEANUP_SCHEDULED_TOOLTIP, CLEANUP_ONGOING_TOOLTIP, CLEANUP_UNFINISHED_TOOLTIP, - ROOT_IMAGE_TEXT, ROOT_IMAGE_TOOLTIP, } from '~/packages_and_registries/container_registry/explorer/constants'; import getContainerRepositoryMetadata from '~/packages_and_registries/container_registry/explorer/graphql/queries/get_container_repository_metadata.query.graphql'; @@ -35,6 +34,7 @@ describe('Details Header', () => { canDelete: true, project: { visibility: 'public', + path: 'path', containerExpirationPolicy: { enabled: false, }, @@ -98,8 +98,8 @@ describe('Details Header', () => { return waitForPromises(); }); - it('root image ', () => { - expect(findTitle().text()).toBe(ROOT_IMAGE_TEXT); + it('root image shows project path name', () => { + expect(findTitle().text()).toBe('path'); }); it('has an icon', () => { diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/cleanup_status_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/cleanup_status_spec.js index 0581a40b6a2..a5b2b1d7cf8 100644 --- a/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/cleanup_status_spec.js +++ b/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/cleanup_status_spec.js @@ -109,5 +109,17 @@ describe('cleanup_status', () => { expect(findPopover().findComponent(GlLink).exists()).toBe(true); expect(findPopover().findComponent(GlLink).attributes('href')).toBe(cleanupPolicyHelpPage); }); + + it('id matches popover target attribute', () => { + mountComponent({ + status: UNFINISHED_STATUS, + next_run_at: '2063-04-08T01:44:03Z', + }); + + const id = findExtraInfoIcon().attributes('id'); + + expect(id).toMatch(/status-info-[0-9]+/); + expect(findPopover().props('target')).toEqual(id); + }); }); }); diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/image_list_row_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/image_list_row_spec.js index 979e1500d7d..d12933526bc 100644 --- a/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/image_list_row_spec.js +++ b/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/image_list_row_spec.js @@ -1,6 +1,7 @@ -import { GlIcon, GlSprintf, GlSkeletonLoader } from '@gitlab/ui'; +import { GlIcon, GlSprintf, GlSkeletonLoader, GlButton } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; +import { mockTracking } from 'helpers/tracking_helper'; import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import DeleteButton from '~/packages_and_registries/container_registry/explorer/components/delete_button.vue'; import CleanupStatus from '~/packages_and_registries/container_registry/explorer/components/list_page/cleanup_status.vue'; @@ -12,7 +13,6 @@ import { IMAGE_DELETE_SCHEDULED_STATUS, IMAGE_MIGRATING_STATE, SCHEDULED_STATUS, - ROOT_IMAGE_TEXT, COPY_IMAGE_PATH_TITLE, } from '~/packages_and_registries/container_registry/explorer/constants'; import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; @@ -31,13 +31,15 @@ describe('Image List Row', () => { const findCleanupStatus = () => wrapper.findComponent(CleanupStatus); const findSkeletonLoader = () => wrapper.findComponent(GlSkeletonLoader); const findListItemComponent = () => wrapper.findComponent(ListItem); + const findShowFullPathButton = () => wrapper.findComponent(GlButton); - const mountComponent = (props) => { + const mountComponent = (props, features = {}) => { wrapper = shallowMount(Component, { stubs: { RouterLink, GlSprintf, ListItem, + GlButton, }, propsData: { item, @@ -45,6 +47,9 @@ describe('Image List Row', () => { }, provide: { config: {}, + glFeatures: { + ...features, + }, }, directives: { GlTooltip: createMockDirective(), @@ -96,10 +101,10 @@ describe('Image List Row', () => { }); }); - it(`when the image has no name appends ${ROOT_IMAGE_TEXT} to the path`, () => { + it('when the image has no name lists the path', () => { mountComponent({ item: { ...item, name: '' } }); - expect(findDetailsLink().text()).toBe(`${item.path}/ ${ROOT_IMAGE_TEXT}`); + expect(findDetailsLink().text()).toBe(item.path); }); it('contains a clipboard button', () => { @@ -144,6 +149,35 @@ describe('Image List Row', () => { expect(findClipboardButton().attributes('disabled')).toBe('true'); }); }); + + describe('when containerRegistryShowShortenedPath feature enabled', () => { + let trackingSpy; + + beforeEach(() => { + mountComponent({}, { containerRegistryShowShortenedPath: true }); + trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn); + }); + + it('renders shortened name of image', () => { + expect(findShowFullPathButton().exists()).toBe(true); + expect(findDetailsLink().text()).toBe('gitlab-test/rails-12009'); + }); + + it('clicking on shortened name of image hides the button & shows full path', async () => { + const btn = findShowFullPathButton(); + const mockFocusFn = jest.fn(); + wrapper.vm.$refs.imageName.$el.focus = mockFocusFn; + + await btn.trigger('click'); + + expect(findShowFullPathButton().exists()).toBe(false); + expect(findDetailsLink().text()).toBe(item.path); + expect(mockFocusFn).toHaveBeenCalled(); + expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_show_full_path', { + label: 'registry_image_list', + }); + }); + }); }); describe('delete button', () => { diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/mock_data.js b/spec/frontend/packages_and_registries/container_registry/explorer/mock_data.js index 7e6f88fe5bc..f9739509ef9 100644 --- a/spec/frontend/packages_and_registries/container_registry/explorer/mock_data.js +++ b/spec/frontend/packages_and_registries/container_registry/explorer/mock_data.js @@ -11,6 +11,10 @@ export const imagesListResponse = [ createdAt: '2020-11-03T13:29:21Z', expirationPolicyStartedAt: null, expirationPolicyCleanupStatus: 'UNSCHEDULED', + project: { + id: 'gid://gitlab/Project/22', + path: 'gitlab-test', + }, }, { __typename: 'ContainerRepository', @@ -24,6 +28,10 @@ export const imagesListResponse = [ createdAt: '2020-09-21T06:57:43Z', expirationPolicyStartedAt: null, expirationPolicyCleanupStatus: 'UNSCHEDULED', + project: { + id: 'gid://gitlab/Project/22', + path: 'gitlab-test', + }, }, ]; diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/pages/details_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/pages/details_spec.js index 59ca47bee50..1d161888a4d 100644 --- a/spec/frontend/packages_and_registries/container_registry/explorer/pages/details_spec.js +++ b/spec/frontend/packages_and_registries/container_registry/explorer/pages/details_spec.js @@ -20,7 +20,6 @@ import { ALERT_DANGER_IMAGE, ALERT_DANGER_IMPORTING, MISSING_OR_DELETED_IMAGE_BREADCRUMB, - ROOT_IMAGE_TEXT, MISSING_OR_DELETED_IMAGE_TITLE, MISSING_OR_DELETED_IMAGE_MESSAGE, } from '~/packages_and_registries/container_registry/explorer/constants'; @@ -482,7 +481,7 @@ describe('Details Page', () => { expect(breadCrumbState.updateName).toHaveBeenCalledWith(MISSING_OR_DELETED_IMAGE_BREADCRUMB); }); - it(`when the image has no name set the breadcrumb to ${ROOT_IMAGE_TEXT}`, async () => { + it(`when the image has no name set the breadcrumb to project name`, async () => { mountComponent({ resolver: jest .fn() @@ -491,7 +490,7 @@ describe('Details Page', () => { await waitForApolloRequestRender(); - expect(breadCrumbState.updateName).toHaveBeenCalledWith(ROOT_IMAGE_TEXT); + expect(breadCrumbState.updateName).toHaveBeenCalledWith('gitlab-test'); }); }); diff --git a/spec/frontend/packages_and_registries/dependency_proxy/app_spec.js b/spec/frontend/packages_and_registries/dependency_proxy/app_spec.js index fe4a2c06f1c..f2901148e17 100644 --- a/spec/frontend/packages_and_registries/dependency_proxy/app_spec.js +++ b/spec/frontend/packages_and_registries/dependency_proxy/app_spec.js @@ -38,6 +38,8 @@ const dummyGon = { let originalGon; const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/groups/${dummyGrouptId}/dependency_proxy/cache`; +Vue.use(VueApollo); + describe('DependencyProxyApp', () => { let wrapper; let apolloProvider; @@ -51,8 +53,6 @@ describe('DependencyProxyApp', () => { }; function createComponent({ provide = provideDefaults } = {}) { - Vue.use(VueApollo); - const requestHandlers = [[getDependencyProxyDetailsQuery, resolver]]; apolloProvider = createMockApollo(requestHandlers); @@ -103,19 +103,21 @@ describe('DependencyProxyApp', () => { describe('when the dependency proxy is available', () => { describe('when is loading', () => { - beforeEach(() => { + it('renders the skeleton loader', () => { createComponent(); - }); - it('renders the skeleton loader', () => { expect(findSkeletonLoader().exists()).toBe(true); }); it('does not render a form group with label', () => { + createComponent(); + expect(findFormGroup().exists()).toBe(false); }); it('does not show the main section', () => { + createComponent(); + expect(findMainArea().exists()).toBe(false); }); }); @@ -215,23 +217,26 @@ describe('DependencyProxyApp', () => { }); describe('triggering page event on list', () => { - beforeEach(async () => { + it('re-renders the skeleton loader', async () => { findManifestList().vm.$emit('next-page'); - await nextTick(); - }); - it('re-renders the skeleton loader', () => { expect(findSkeletonLoader().exists()).toBe(true); }); - it('renders form group with label', () => { + it('renders form group with label', async () => { + findManifestList().vm.$emit('next-page'); + await nextTick(); + expect(findFormGroup().attributes('label')).toEqual( expect.stringMatching(DependencyProxyApp.i18n.proxyImagePrefix), ); }); - it('does not show the main section', () => { + it('does not show the main section', async () => { + findManifestList().vm.$emit('next-page'); + await nextTick(); + expect(findMainArea().exists()).toBe(false); }); }); diff --git a/spec/frontend/packages_and_registries/settings/group/components/dependency_proxy_settings_spec.js b/spec/frontend/packages_and_registries/settings/group/components/dependency_proxy_settings_spec.js index e60989b0949..9d4c7f4737b 100644 --- a/spec/frontend/packages_and_registries/settings/group/components/dependency_proxy_settings_spec.js +++ b/spec/frontend/packages_and_registries/settings/group/components/dependency_proxy_settings_spec.js @@ -6,13 +6,15 @@ import createMockApollo from 'helpers/mock_apollo_helper'; import waitForPromises from 'helpers/wait_for_promises'; import component from '~/packages_and_registries/settings/group/components/dependency_proxy_settings.vue'; -import { DEPENDENCY_PROXY_HEADER } from '~/packages_and_registries/settings/group/constants'; +import { + DEPENDENCY_PROXY_HEADER, + DEPENDENCY_PROXY_DESCRIPTION, +} from '~/packages_and_registries/settings/group/constants'; import updateDependencyProxySettings from '~/packages_and_registries/settings/group/graphql/mutations/update_dependency_proxy_settings.mutation.graphql'; import updateDependencyProxyImageTtlGroupPolicy from '~/packages_and_registries/settings/group/graphql/mutations/update_dependency_proxy_image_ttl_group_policy.mutation.graphql'; import getGroupPackagesSettingsQuery from '~/packages_and_registries/settings/group/graphql/queries/get_group_packages_settings.query.graphql'; import SettingsBlock from '~/vue_shared/components/settings/settings_block.vue'; -import SettingsTitles from '~/packages_and_registries/settings/group/components/settings_titles.vue'; import { updateGroupDependencyProxySettingsOptimisticResponse, updateDependencyProxyImageTtlGroupPolicyOptimisticResponse, @@ -36,7 +38,6 @@ describe('DependencyProxySettings', () => { let updateTtlPoliciesMutationResolver; const defaultProvide = { - defaultExpanded: false, groupPath: 'foo_group_path', groupDependencyProxyPath: 'group_dependency_proxy_path', }; @@ -86,7 +87,6 @@ describe('DependencyProxySettings', () => { }); const findSettingsBlock = () => wrapper.findComponent(SettingsBlock); - const findSettingsTitles = () => wrapper.findComponent(SettingsTitles); const findEnableProxyToggle = () => wrapper.findByTestId('dependency-proxy-setting-toggle'); const findEnableTtlPoliciesToggle = () => wrapper.findByTestId('dependency-proxy-ttl-policies-toggle'); @@ -108,16 +108,11 @@ describe('DependencyProxySettings', () => { expect(findSettingsBlock().exists()).toBe(true); }); - it('passes the correct props to settings block', () => { - mountComponent(); - - expect(findSettingsBlock().props('defaultExpanded')).toBe(false); - }); - - it('has the correct header text', () => { + it('has the correct header text and description', () => { mountComponent(); expect(wrapper.text()).toContain(DEPENDENCY_PROXY_HEADER); + expect(wrapper.text()).toContain(DEPENDENCY_PROXY_DESCRIPTION); }); describe('enable toggle', () => { @@ -158,14 +153,6 @@ describe('DependencyProxySettings', () => { }); describe('storage settings', () => { - it('the component has the settings title', () => { - mountComponent(); - - expect(findSettingsTitles().props()).toMatchObject({ - title: component.i18n.storageSettingsTitle, - }); - }); - describe('enable proxy ttl policies', () => { it('exists', () => { mountComponent(); diff --git a/spec/frontend/packages_and_registries/settings/group/components/duplicates_settings_spec.js b/spec/frontend/packages_and_registries/settings/group/components/duplicates_settings_spec.js index 79c2f811c08..3eecdeb5b1f 100644 --- a/spec/frontend/packages_and_registries/settings/group/components/duplicates_settings_spec.js +++ b/spec/frontend/packages_and_registries/settings/group/components/duplicates_settings_spec.js @@ -4,8 +4,6 @@ import component from '~/packages_and_registries/settings/group/components/dupli import { DUPLICATES_TOGGLE_LABEL, - DUPLICATES_ALLOWED_ENABLED, - DUPLICATES_ALLOWED_DISABLED, DUPLICATES_SETTING_EXCEPTION_TITLE, DUPLICATES_SETTINGS_EXCEPTION_LEGEND, } from '~/packages_and_registries/settings/group/constants'; @@ -36,7 +34,6 @@ describe('Duplicates Settings', () => { }); const findToggle = () => wrapper.findComponent(GlToggle); - const findToggleLabel = () => wrapper.find('[data-testid="toggle-label"'); const findInputGroup = () => wrapper.findComponent(GlFormGroup); const findInput = () => wrapper.findComponent(GlFormInput); @@ -47,7 +44,7 @@ describe('Duplicates Settings', () => { expect(findToggle().exists()).toBe(true); expect(findToggle().props()).toMatchObject({ label: DUPLICATES_TOGGLE_LABEL, - value: defaultProps.duplicatesAllowed, + value: !defaultProps.duplicatesAllowed, }); }); @@ -57,18 +54,11 @@ describe('Duplicates Settings', () => { findToggle().vm.$emit('change', false); expect(wrapper.emitted('update')).toStrictEqual([ - [{ [defaultProps.modelNames.allowed]: false }], + [{ [defaultProps.modelNames.allowed]: true }], ]); }); describe('when the duplicates are disabled', () => { - it('the toggle has the disabled message', () => { - mountComponent(); - - expect(findToggleLabel().exists()).toBe(true); - expect(findToggleLabel().text()).toMatchInterpolatedText(DUPLICATES_ALLOWED_DISABLED); - }); - it('shows a form group with an input field', () => { mountComponent(); @@ -130,13 +120,6 @@ describe('Duplicates Settings', () => { }); describe('when the duplicates are enabled', () => { - it('has the correct toggle label', () => { - mountComponent({ ...defaultProps, duplicatesAllowed: true }); - - expect(findToggleLabel().exists()).toBe(true); - expect(findToggleLabel().text()).toMatchInterpolatedText(DUPLICATES_ALLOWED_ENABLED); - }); - it('hides the form input group', () => { mountComponent({ ...defaultProps, duplicatesAllowed: true }); diff --git a/spec/frontend/packages_and_registries/settings/group/components/group_settings_app_spec.js b/spec/frontend/packages_and_registries/settings/group/components/group_settings_app_spec.js index 635195ff0a4..31fc3ad419c 100644 --- a/spec/frontend/packages_and_registries/settings/group/components/group_settings_app_spec.js +++ b/spec/frontend/packages_and_registries/settings/group/components/group_settings_app_spec.js @@ -26,7 +26,6 @@ describe('Group Settings App', () => { let show; const defaultProvide = { - defaultExpanded: false, groupPath: 'foo_group_path', }; diff --git a/spec/frontend/packages_and_registries/settings/group/components/package_settings_spec.js b/spec/frontend/packages_and_registries/settings/group/components/package_settings_spec.js index d92d42e7834..274930ce668 100644 --- a/spec/frontend/packages_and_registries/settings/group/components/package_settings_spec.js +++ b/spec/frontend/packages_and_registries/settings/group/components/package_settings_spec.js @@ -1,4 +1,3 @@ -import { GlSprintf, GlLink } from '@gitlab/ui'; import Vue, { nextTick } from 'vue'; import VueApollo from 'vue-apollo'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; @@ -11,7 +10,6 @@ import MavenSettings from '~/packages_and_registries/settings/group/components/m import { PACKAGE_SETTINGS_HEADER, PACKAGE_SETTINGS_DESCRIPTION, - PACKAGES_DOCS_PATH, } from '~/packages_and_registries/settings/group/constants'; import updateNamespacePackageSettings from '~/packages_and_registries/settings/group/graphql/mutations/update_group_packages_settings.mutation.graphql'; @@ -33,7 +31,6 @@ describe('Packages Settings', () => { let apolloProvider; const defaultProvide = { - defaultExpanded: false, groupPath: 'foo_group_path', }; @@ -53,7 +50,6 @@ describe('Packages Settings', () => { packageSettings: packageSettings(), }, stubs: { - GlSprintf, SettingsBlock, MavenSettings, GenericSettings, @@ -67,7 +63,6 @@ describe('Packages Settings', () => { const findSettingsBlock = () => wrapper.findComponent(SettingsBlock); const findDescription = () => wrapper.findByTestId('description'); - const findLink = () => wrapper.findComponent(GlLink); const findMavenSettings = () => wrapper.findComponent(MavenSettings); const findMavenDuplicatedSettings = () => findMavenSettings().findComponent(DuplicatesSettings); const findGenericSettings = () => wrapper.findComponent(GenericSettings); @@ -97,12 +92,6 @@ describe('Packages Settings', () => { expect(findSettingsBlock().exists()).toBe(true); }); - it('passes the correct props to settings block', () => { - mountComponent(); - - expect(findSettingsBlock().props('defaultExpanded')).toBe(false); - }); - it('has the correct header text', () => { mountComponent(); @@ -115,16 +104,6 @@ describe('Packages Settings', () => { expect(findDescription().text()).toMatchInterpolatedText(PACKAGE_SETTINGS_DESCRIPTION); }); - it('has the correct link', () => { - mountComponent(); - - expect(findLink().attributes()).toMatchObject({ - href: PACKAGES_DOCS_PATH, - target: '_blank', - }); - expect(findLink().text()).toBe('Learn more.'); - }); - describe('maven settings', () => { it('exists', () => { mountComponent(); diff --git a/spec/frontend/packages_and_registries/settings/project/settings/components/__snapshots__/container_expiration_policy_form_spec.js.snap b/spec/frontend/packages_and_registries/settings/project/settings/components/__snapshots__/container_expiration_policy_form_spec.js.snap index faa313118f3..108d9478788 100644 --- a/spec/frontend/packages_and_registries/settings/project/settings/components/__snapshots__/container_expiration_policy_form_spec.js.snap +++ b/spec/frontend/packages_and_registries/settings/project/settings/components/__snapshots__/container_expiration_policy_form_spec.js.snap @@ -4,6 +4,7 @@ exports[`Container Expiration Policy Settings Form Cadence matches snapshot 1`] { GlSprintf, SettingsBlock, }, - mocks: { - $toast: { - show: jest.fn(), - }, - }, provide, ...config, }); @@ -98,7 +93,7 @@ describe('Container expiration policy project settings', () => { await waitForPromises(); expect(findFormComponent().exists()).toBe(true); - expect(findSettingsBlock().props('collapsible')).toBe(false); + expect(findSettingsBlock().exists()).toBe(true); }); describe('the form is disabled', () => { diff --git a/spec/frontend/packages_and_registries/settings/project/settings/components/expiration_dropdown_spec.js b/spec/frontend/packages_and_registries/settings/project/settings/components/expiration_dropdown_spec.js index 5c9ade7f785..8b99ac6b06c 100644 --- a/spec/frontend/packages_and_registries/settings/project/settings/components/expiration_dropdown_spec.js +++ b/spec/frontend/packages_and_registries/settings/project/settings/components/expiration_dropdown_spec.js @@ -16,6 +16,7 @@ describe('ExpirationDropdown', () => { const findFormSelect = () => wrapper.find(GlFormSelect); const findFormGroup = () => wrapper.find(GlFormGroup); + const findDescription = () => wrapper.find('[data-testid="description"]'); const findOptions = () => wrapper.findAll('[data-testid="option"]'); const mountComponent = (props) => { @@ -47,6 +48,14 @@ describe('ExpirationDropdown', () => { expect(findOptions()).toHaveLength(defaultProps.formOptions.length); }); + + it('renders the description if passed', () => { + mountComponent({ + description: 'test description', + }); + + expect(findDescription().html()).toContain('test description'); + }); }); describe('model', () => { diff --git a/spec/frontend/packages_and_registries/settings/project/settings/components/packages_cleanup_policy_form_spec.js b/spec/frontend/packages_and_registries/settings/project/settings/components/packages_cleanup_policy_form_spec.js new file mode 100644 index 00000000000..86f45d78bae --- /dev/null +++ b/spec/frontend/packages_and_registries/settings/project/settings/components/packages_cleanup_policy_form_spec.js @@ -0,0 +1,267 @@ +import VueApollo from 'vue-apollo'; +import Vue from 'vue'; +import createMockApollo from 'helpers/mock_apollo_helper'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import waitForPromises from 'helpers/wait_for_promises'; +import { GlLoadingIcon } from 'jest/packages_and_registries/shared/stubs'; +import component from '~/packages_and_registries/settings/project/components/packages_cleanup_policy_form.vue'; +import { + UPDATE_SETTINGS_ERROR_MESSAGE, + UPDATE_SETTINGS_SUCCESS_MESSAGE, + KEEP_N_DUPLICATED_PACKAGE_FILES_LABEL, + KEEP_N_DUPLICATED_PACKAGE_FILES_DESCRIPTION, +} from '~/packages_and_registries/settings/project/constants'; +import updatePackagesCleanupPolicyMutation from '~/packages_and_registries/settings/project/graphql/mutations/update_packages_cleanup_policy.mutation.graphql'; +import Tracking from '~/tracking'; +import { packagesCleanupPolicyPayload, packagesCleanupPolicyMutationPayload } from '../mock_data'; + +Vue.use(VueApollo); + +describe('Packages Cleanup Policy Settings Form', () => { + let wrapper; + let fakeApollo; + + const defaultProvidedValues = { + projectPath: 'path', + }; + + const { + data: { + project: { packagesCleanupPolicy }, + }, + } = packagesCleanupPolicyPayload(); + + const defaultProps = { + value: { ...packagesCleanupPolicy }, + }; + + const trackingPayload = { + label: 'packages_cleanup_policies', + }; + + const findForm = () => wrapper.find({ ref: 'form-element' }); + const findSaveButton = () => wrapper.findByTestId('save-button'); + const findKeepNDuplicatedPackageFilesDropdown = () => + wrapper.findByTestId('keep-n-duplicated-package-files-dropdown'); + + const submitForm = async () => { + findForm().trigger('submit'); + return waitForPromises(); + }; + + const mountComponent = ({ + props = defaultProps, + data, + config, + provide = defaultProvidedValues, + } = {}) => { + wrapper = shallowMountExtended(component, { + stubs: { + GlLoadingIcon, + }, + propsData: { ...props }, + provide, + data() { + return { + ...data, + }; + }, + mocks: { + $toast: { + show: jest.fn(), + }, + }, + ...config, + }); + }; + + const mountComponentWithApollo = ({ + provide = defaultProvidedValues, + mutationResolver, + queryPayload = packagesCleanupPolicyPayload(), + } = {}) => { + const requestHandlers = [[updatePackagesCleanupPolicyMutation, mutationResolver]]; + + fakeApollo = createMockApollo(requestHandlers); + + const { + data: { + project: { packagesCleanupPolicy: value }, + }, + } = queryPayload; + + mountComponent({ + provide, + props: { + ...defaultProps, + value, + }, + config: { + apolloProvider: fakeApollo, + }, + }); + }; + + beforeEach(() => { + jest.spyOn(Tracking, 'event'); + }); + + afterEach(() => { + wrapper.destroy(); + fakeApollo = null; + }); + + describe('keepNDuplicatedPackageFiles', () => { + it('renders dropdown', () => { + mountComponent(); + + const element = findKeepNDuplicatedPackageFilesDropdown(); + + expect(element.exists()).toBe(true); + expect(element.props('label')).toMatchInterpolatedText(KEEP_N_DUPLICATED_PACKAGE_FILES_LABEL); + expect(element.props('description')).toEqual(KEEP_N_DUPLICATED_PACKAGE_FILES_DESCRIPTION); + }); + + it('input event triggers a model update', () => { + mountComponent(); + + findKeepNDuplicatedPackageFilesDropdown().vm.$emit('input', 'foo'); + expect(wrapper.emitted('input')[0][0]).toMatchObject({ + keepNDuplicatedPackageFiles: 'foo', + }); + }); + + it('shows the default option when none are selected', () => { + mountComponent({ props: { value: {} } }); + expect(findKeepNDuplicatedPackageFilesDropdown().props('value')).toEqual('ALL_PACKAGE_FILES'); + }); + + it.each` + isLoading | mutationLoading + ${true} | ${false} + ${true} | ${true} + ${false} | ${true} + `( + 'is disabled when is loading is $isLoading and mutationLoading is $mutationLoading', + ({ isLoading, mutationLoading }) => { + mountComponent({ + props: { isLoading, value: {} }, + data: { mutationLoading }, + }); + expect(findKeepNDuplicatedPackageFilesDropdown().props('disabled')).toEqual(true); + }, + ); + + it('has the correct formOptions', () => { + mountComponent(); + expect(findKeepNDuplicatedPackageFilesDropdown().props('formOptions')).toEqual( + wrapper.vm.$options.formOptions.keepNDuplicatedPackageFiles, + ); + }); + }); + + describe('form', () => { + describe('actions', () => { + describe('submit button', () => { + it('has type submit', () => { + mountComponent(); + + expect(findSaveButton().attributes('type')).toBe('submit'); + }); + + it.each` + isLoading | mutationLoading | disabled + ${true} | ${true} | ${true} + ${true} | ${false} | ${true} + ${false} | ${true} | ${true} + ${false} | ${false} | ${false} + `( + 'when isLoading is $isLoading and mutationLoading is $mutationLoading is disabled', + ({ isLoading, mutationLoading, disabled }) => { + mountComponent({ + props: { ...defaultProps, isLoading }, + data: { mutationLoading }, + }); + + expect(findSaveButton().props('disabled')).toBe(disabled); + expect(findKeepNDuplicatedPackageFilesDropdown().props('disabled')).toBe(disabled); + }, + ); + + it.each` + isLoading | mutationLoading | showLoading + ${true} | ${true} | ${true} + ${true} | ${false} | ${true} + ${false} | ${true} | ${true} + ${false} | ${false} | ${false} + `( + 'when isLoading is $isLoading and mutationLoading is $mutationLoading is $showLoading that the loading icon is shown', + ({ isLoading, mutationLoading, showLoading }) => { + mountComponent({ + props: { ...defaultProps, isLoading }, + data: { mutationLoading }, + }); + + expect(findSaveButton().props('loading')).toBe(showLoading); + }, + ); + }); + }); + + describe('form submit event', () => { + it('dispatches the correct apollo mutation', () => { + const mutationResolver = jest + .fn() + .mockResolvedValue(packagesCleanupPolicyMutationPayload()); + mountComponentWithApollo({ + mutationResolver, + }); + + findForm().trigger('submit'); + + expect(mutationResolver).toHaveBeenCalledWith({ + input: { + keepNDuplicatedPackageFiles: 'ALL_PACKAGE_FILES', + projectPath: 'path', + }, + }); + }); + + it('tracks the submit event', () => { + mountComponentWithApollo({ + mutationResolver: jest.fn().mockResolvedValue(packagesCleanupPolicyMutationPayload()), + }); + + findForm().trigger('submit'); + + expect(Tracking.event).toHaveBeenCalledWith( + undefined, + 'submit_packages_cleanup_form', + trackingPayload, + ); + }); + + it('show a success toast when submit succeed', async () => { + mountComponentWithApollo({ + mutationResolver: jest.fn().mockResolvedValue(packagesCleanupPolicyMutationPayload()), + }); + + await submitForm(); + + expect(wrapper.vm.$toast.show).toHaveBeenCalledWith(UPDATE_SETTINGS_SUCCESS_MESSAGE); + }); + + describe('when submit fails', () => { + it('shows an error', async () => { + mountComponentWithApollo({ + mutationResolver: jest.fn().mockRejectedValue(packagesCleanupPolicyMutationPayload()), + }); + + await submitForm(); + + expect(wrapper.vm.$toast.show).toHaveBeenCalledWith(UPDATE_SETTINGS_ERROR_MESSAGE); + }); + }); + }); + }); +}); diff --git a/spec/frontend/packages_and_registries/settings/project/settings/components/packages_cleanup_policy_spec.js b/spec/frontend/packages_and_registries/settings/project/settings/components/packages_cleanup_policy_spec.js new file mode 100644 index 00000000000..6dfeeca6862 --- /dev/null +++ b/spec/frontend/packages_and_registries/settings/project/settings/components/packages_cleanup_policy_spec.js @@ -0,0 +1,81 @@ +import { GlAlert, GlSprintf } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; +import Vue from 'vue'; +import VueApollo from 'vue-apollo'; +import createMockApollo from 'helpers/mock_apollo_helper'; +import waitForPromises from 'helpers/wait_for_promises'; +import component from '~/packages_and_registries/settings/project/components/packages_cleanup_policy.vue'; +import PackagesCleanupPolicyForm from '~/packages_and_registries/settings/project/components/packages_cleanup_policy_form.vue'; +import { FETCH_SETTINGS_ERROR_MESSAGE } from '~/packages_and_registries/settings/project/constants'; +import packagesCleanupPolicyQuery from '~/packages_and_registries/settings/project/graphql/queries/get_packages_cleanup_policy.query.graphql'; +import SettingsBlock from '~/vue_shared/components/settings/settings_block.vue'; + +import { packagesCleanupPolicyPayload, packagesCleanupPolicyData } from '../mock_data'; + +Vue.use(VueApollo); + +describe('Packages cleanup policy project settings', () => { + let wrapper; + let fakeApollo; + + const defaultProvidedValues = { + projectPath: 'path', + }; + + const findAlert = () => wrapper.findComponent(GlAlert); + const findFormComponent = () => wrapper.findComponent(PackagesCleanupPolicyForm); + const findSettingsBlock = () => wrapper.findComponent(SettingsBlock); + + const mountComponent = (provide = defaultProvidedValues, config) => { + wrapper = shallowMount(component, { + stubs: { + GlSprintf, + SettingsBlock, + }, + provide, + ...config, + }); + }; + + const mountComponentWithApollo = ({ provide = defaultProvidedValues, resolver } = {}) => { + const requestHandlers = [[packagesCleanupPolicyQuery, resolver]]; + + fakeApollo = createMockApollo(requestHandlers); + mountComponent(provide, { + apolloProvider: fakeApollo, + }); + }; + + afterEach(() => { + wrapper.destroy(); + fakeApollo = null; + }); + + it('renders the setting form', async () => { + mountComponentWithApollo({ + resolver: jest.fn().mockResolvedValue(packagesCleanupPolicyPayload()), + }); + await waitForPromises(); + + expect(findFormComponent().exists()).toBe(true); + expect(findFormComponent().props('value')).toEqual(packagesCleanupPolicyData); + expect(findSettingsBlock().exists()).toBe(true); + }); + + describe('fetchSettingsError', () => { + beforeEach(async () => { + mountComponentWithApollo({ + resolver: jest.fn().mockRejectedValue(new Error('GraphQL error')), + }); + await waitForPromises(); + }); + + it('the form is hidden', () => { + expect(findFormComponent().exists()).toBe(false); + }); + + it('shows an alert', () => { + expect(findAlert().html()).toContain(FETCH_SETTINGS_ERROR_MESSAGE); + }); + }); +}); diff --git a/spec/frontend/packages_and_registries/settings/project/settings/components/registry_settings_app_spec.js b/spec/frontend/packages_and_registries/settings/project/settings/components/registry_settings_app_spec.js index 337991dfae0..f576bc79eae 100644 --- a/spec/frontend/packages_and_registries/settings/project/settings/components/registry_settings_app_spec.js +++ b/spec/frontend/packages_and_registries/settings/project/settings/components/registry_settings_app_spec.js @@ -1,19 +1,41 @@ import { shallowMount } from '@vue/test-utils'; import component from '~/packages_and_registries/settings/project/components/registry_settings_app.vue'; import ContainerExpirationPolicy from '~/packages_and_registries/settings/project/components/container_expiration_policy.vue'; +import PackagesCleanupPolicy from '~/packages_and_registries/settings/project/components/packages_cleanup_policy.vue'; describe('Registry Settings app', () => { let wrapper; + const findContainerExpirationPolicy = () => wrapper.find(ContainerExpirationPolicy); + const findPackagesCleanupPolicy = () => wrapper.find(PackagesCleanupPolicy); afterEach(() => { wrapper.destroy(); wrapper = null; }); - it('renders container expiration policy component', () => { - wrapper = shallowMount(component); + const mountComponent = (provide) => { + wrapper = shallowMount(component, { + provide, + }); + }; - expect(findContainerExpirationPolicy().exists()).toBe(true); - }); + it.each` + showContainerRegistrySettings | showPackageRegistrySettings + ${true} | ${false} + ${true} | ${true} + ${false} | ${true} + ${false} | ${false} + `( + 'container expiration policy $showContainerRegistrySettings and package cleanup policy is $showPackageRegistrySettings', + ({ showContainerRegistrySettings, showPackageRegistrySettings }) => { + mountComponent({ + showContainerRegistrySettings, + showPackageRegistrySettings, + }); + + expect(findContainerExpirationPolicy().exists()).toBe(showContainerRegistrySettings); + expect(findPackagesCleanupPolicy().exists()).toBe(showPackageRegistrySettings); + }, + ); }); diff --git a/spec/frontend/packages_and_registries/settings/project/settings/mock_data.js b/spec/frontend/packages_and_registries/settings/project/settings/mock_data.js index 33406c98f4b..d4b6c66ddeb 100644 --- a/spec/frontend/packages_and_registries/settings/project/settings/mock_data.js +++ b/spec/frontend/packages_and_registries/settings/project/settings/mock_data.js @@ -40,3 +40,33 @@ export const expirationPolicyMutationPayload = ({ override, errors = [] } = {}) }, }, }); + +export const packagesCleanupPolicyData = { + keepNDuplicatedPackageFiles: 'ALL_PACKAGE_FILES', + nextRunAt: '2020-11-19T07:37:03.941Z', +}; + +export const packagesCleanupPolicyPayload = (override) => ({ + data: { + project: { + id: '1', + packagesCleanupPolicy: { + __typename: 'PackagesCleanupPolicy', + ...packagesCleanupPolicyData, + ...override, + }, + }, + }, +}); + +export const packagesCleanupPolicyMutationPayload = ({ override, errors = [] } = {}) => ({ + data: { + updatePackagesCleanupPolicy: { + packagesCleanupPolicy: { + ...packagesCleanupPolicyData, + ...override, + }, + errors, + }, + }, +}); diff --git a/spec/frontend/packages_and_registries/shared/components/settings_block_spec.js b/spec/frontend/packages_and_registries/shared/components/settings_block_spec.js new file mode 100644 index 00000000000..a4c1b989dac --- /dev/null +++ b/spec/frontend/packages_and_registries/shared/components/settings_block_spec.js @@ -0,0 +1,43 @@ +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import SettingsBlock from '~/packages_and_registries/shared/components/settings_block.vue'; + +describe('SettingsBlock', () => { + let wrapper; + + const mountComponent = (propsData) => { + wrapper = shallowMountExtended(SettingsBlock, { + propsData, + slots: { + title: '
', + description: '
', + default: '
', + }, + }); + }; + + afterEach(() => { + wrapper.destroy(); + }); + + const findDefaultSlot = () => wrapper.findByTestId('default-slot'); + const findTitleSlot = () => wrapper.findByTestId('title-slot'); + const findDescriptionSlot = () => wrapper.findByTestId('description-slot'); + + it('has a default slot', () => { + mountComponent(); + + expect(findDefaultSlot().exists()).toBe(true); + }); + + it('has a title slot', () => { + mountComponent(); + + expect(findTitleSlot().exists()).toBe(true); + }); + + it('has a description slot', () => { + mountComponent(); + + expect(findDescriptionSlot().exists()).toBe(true); + }); +}); -- cgit v1.2.3