diff options
Diffstat (limited to 'spec/frontend/packages_and_registries')
61 files changed, 1522 insertions, 753 deletions
diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/components/delete_button_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/components/delete_button_spec.js index ad67128502a..ff11c8843bb 100644 --- a/spec/frontend/packages_and_registries/container_registry/explorer/components/delete_button_spec.js +++ b/spec/frontend/packages_and_registries/container_registry/explorer/components/delete_button_spec.js @@ -11,8 +11,8 @@ describe('delete_button', () => { tooltipTitle: 'Bar tooltipTitle', }; - const findButton = () => wrapper.find(GlButton); - const findTooltip = () => wrapper.find(GlTooltip); + const findButton = () => wrapper.findComponent(GlButton); + const findTooltip = () => wrapper.findComponent(GlTooltip); const mountComponent = (props) => { wrapper = shallowMount(component, { diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/delete_alert_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/delete_alert_spec.js index 9680e273add..4a026f35822 100644 --- a/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/delete_alert_spec.js +++ b/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/delete_alert_spec.js @@ -13,8 +13,8 @@ import { describe('Delete alert', () => { let wrapper; - const findAlert = () => wrapper.find(GlAlert); - const findLink = () => wrapper.find(GlLink); + const findAlert = () => wrapper.findComponent(GlAlert); + const findLink = () => wrapper.findComponent(GlLink); const mountComponent = (propsData) => { wrapper = shallowMount(component, { stubs: { GlSprintf }, propsData }); 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 9982286c625..b37edac83f7 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 @@ -120,7 +120,7 @@ describe('Details Header', () => { return waitForPromises(); }); - it('shows image.name ', () => { + it('shows image.name', () => { expect(findTitle().text()).toContain('foo'); }); @@ -289,7 +289,7 @@ describe('Details Header', () => { ); }); - describe('visibility and updated at ', () => { + describe('visibility and updated at', () => { it('has last updated text', async () => { mountComponent(); await waitForMetadataItems(); diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/partial_cleanup_alert_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/partial_cleanup_alert_spec.js index 1a27481a828..ce5ecfe4608 100644 --- a/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/partial_cleanup_alert_spec.js +++ b/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/partial_cleanup_alert_spec.js @@ -9,7 +9,7 @@ import { describe('Partial Cleanup alert', () => { let wrapper; - const findAlert = () => wrapper.find(GlAlert); + const findAlert = () => wrapper.findComponent(GlAlert); const findRunLink = () => wrapper.find('[data-testid="run-link"'); const findHelpLink = () => wrapper.find('[data-testid="help-link"'); diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/status_alert_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/status_alert_spec.js index a11b102d9a6..d83a5099bcd 100644 --- a/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/status_alert_spec.js +++ b/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/status_alert_spec.js @@ -14,8 +14,8 @@ import { describe('Status Alert', () => { let wrapper; - const findLink = () => wrapper.find(GlLink); - const findAlert = () => wrapper.find(GlAlert); + const findLink = () => wrapper.findComponent(GlLink); + const findAlert = () => wrapper.findComponent(GlAlert); const findMessage = () => wrapper.find('[data-testid="message"]'); const mountComponent = (propsData) => { diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row_spec.js index 84f01f10f21..96c670eaad2 100644 --- a/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row_spec.js +++ b/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row_spec.js @@ -32,7 +32,7 @@ describe('tags list row', () => { const findShortRevision = () => wrapper.find('[data-testid="digest"]'); const findClipboardButton = () => wrapper.findComponent(ClipboardButton); const findTimeAgoTooltip = () => wrapper.findComponent(TimeAgoTooltip); - const findDetailsRows = () => wrapper.findAll(DetailsRow); + const findDetailsRows = () => wrapper.findAllComponents(DetailsRow); const findPublishedDateDetail = () => wrapper.find('[data-testid="published-date-detail"]'); const findManifestDetail = () => wrapper.find('[data-testid="manifest-detail"]'); const findConfigurationDetail = () => wrapper.find('[data-testid="configuration-detail"]'); @@ -359,7 +359,7 @@ describe('tags list row', () => { mountComponent(); await nextTick(); - expect(finderFunction().find(ClipboardButton).exists()).toBe(clipboard); + expect(finderFunction().findComponent(ClipboardButton).exists()).toBe(clipboard); }); it('is disabled when the component is disabled', async () => { diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/tags_loader_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/tags_loader_spec.js index e5df260a260..88e79c513bc 100644 --- a/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/tags_loader_spec.js +++ b/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/tags_loader_spec.js @@ -5,7 +5,7 @@ import { GlSkeletonLoader } from '../../stubs'; describe('TagsLoader component', () => { let wrapper; - const findGlSkeletonLoaders = () => wrapper.findAll(GlSkeletonLoader); + const findGlSkeletonLoaders = () => wrapper.findAllComponents(GlSkeletonLoader); const mountComponent = () => { wrapper = shallowMount(component, { @@ -25,7 +25,7 @@ describe('TagsLoader component', () => { wrapper = null; }); - it('produces the correct amount of loaders ', () => { + it('produces the correct amount of loaders', () => { mountComponent(); expect(findGlSkeletonLoaders().length).toBe(1); }); 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 61503d0f3bf..535faebdd4e 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 @@ -16,7 +16,7 @@ describe('cleanup_status', () => { let wrapper; const findMainIcon = () => wrapper.findByTestId('main-icon'); - const findMainIconName = () => wrapper.findByTestId('main-icon').find(GlIcon); + const findMainIconName = () => wrapper.findByTestId('main-icon').findComponent(GlIcon); const findExtraInfoIcon = () => wrapper.findByTestId('extra-info'); const findPopover = () => wrapper.findComponent(GlPopover); 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 d12933526bc..0b59fe2d8ce 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 @@ -233,7 +233,7 @@ describe('Image List Row', () => { it('contains a tag icon', () => { mountComponent(); - const icon = findTagsCount().find(GlIcon); + const icon = findTagsCount().findComponent(GlIcon); expect(icon.exists()).toBe(true); expect(icon.props('name')).toBe('tag'); }); diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/image_list_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/image_list_spec.js index e0119954ed4..042b8383571 100644 --- a/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/image_list_spec.js +++ b/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/image_list_spec.js @@ -8,8 +8,8 @@ import { imagesListResponse, pageInfo as defaultPageInfo } from '../../mock_data describe('Image List', () => { let wrapper; - const findRow = () => wrapper.findAll(ImageListRow); - const findPagination = () => wrapper.find(GlKeysetPagination); + const findRow = () => wrapper.findAllComponents(ImageListRow); + const findPagination = () => wrapper.findComponent(GlKeysetPagination); const mountComponent = (props) => { wrapper = shallowMount(Component, { diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/registry_header_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/registry_header_spec.js index a006de9f00c..e6d81d4a28f 100644 --- a/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/registry_header_spec.js +++ b/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/registry_header_spec.js @@ -17,7 +17,7 @@ jest.mock('~/lib/utils/datetime_utility', () => ({ describe('registry_header', () => { let wrapper; - const findTitleArea = () => wrapper.find(TitleArea); + const findTitleArea = () => wrapper.findComponent(TitleArea); const findCommandsSlot = () => wrapper.find('[data-testid="commands-slot"]'); const findImagesCountSubHeader = () => wrapper.find('[data-testid="images-count"]'); const findExpirationPolicySubHeader = () => wrapper.find('[data-testid="expiration-policy"]'); 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 1d161888a4d..ee6470a9df8 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 @@ -45,16 +45,16 @@ describe('Details Page', () => { let wrapper; let apolloProvider; - const findDeleteModal = () => wrapper.find(DeleteModal); - const findPagination = () => wrapper.find(GlKeysetPagination); - const findTagsLoader = () => wrapper.find(TagsLoader); - const findTagsList = () => wrapper.find(TagsList); - const findDeleteAlert = () => wrapper.find(DeleteAlert); - const findDetailsHeader = () => wrapper.find(DetailsHeader); - const findEmptyState = () => wrapper.find(GlEmptyState); - const findPartialCleanupAlert = () => wrapper.find(PartialCleanupAlert); - const findStatusAlert = () => wrapper.find(StatusAlert); - const findDeleteImage = () => wrapper.find(DeleteImage); + const findDeleteModal = () => wrapper.findComponent(DeleteModal); + const findPagination = () => wrapper.findComponent(GlKeysetPagination); + const findTagsLoader = () => wrapper.findComponent(TagsLoader); + const findTagsList = () => wrapper.findComponent(TagsList); + const findDeleteAlert = () => wrapper.findComponent(DeleteAlert); + const findDetailsHeader = () => wrapper.findComponent(DetailsHeader); + const findEmptyState = () => wrapper.findComponent(GlEmptyState); + const findPartialCleanupAlert = () => wrapper.findComponent(PartialCleanupAlert); + const findStatusAlert = () => wrapper.findComponent(StatusAlert); + const findDeleteImage = () => wrapper.findComponent(DeleteImage); const routeId = 1; diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/pages/index_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/pages/index_spec.js index 5f4cb8969bc..add772d27ef 100644 --- a/spec/frontend/packages_and_registries/container_registry/explorer/pages/index_spec.js +++ b/spec/frontend/packages_and_registries/container_registry/explorer/pages/index_spec.js @@ -4,7 +4,7 @@ import component from '~/packages_and_registries/container_registry/explorer/pag describe('List Page', () => { let wrapper; - const findRouterView = () => wrapper.find({ ref: 'router-view' }); + const findRouterView = () => wrapper.findComponent({ ref: 'router-view' }); const mountComponent = () => { wrapper = shallowMount(component, { diff --git a/spec/frontend/packages_and_registries/harbor_registry/components/details/artifacts_list_row_spec.js b/spec/frontend/packages_and_registries/harbor_registry/components/details/artifacts_list_row_spec.js new file mode 100644 index 00000000000..a2e5cbdce8b --- /dev/null +++ b/spec/frontend/packages_and_registries/harbor_registry/components/details/artifacts_list_row_spec.js @@ -0,0 +1,143 @@ +import { GlSprintf } from '@gitlab/ui'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import { n__ } from '~/locale'; +import ArtifactsListRow from '~/packages_and_registries/harbor_registry/components/details/artifacts_list_row.vue'; +import RealListItem from '~/vue_shared/components/registry/list_item.vue'; +import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; +import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; +import { numberToHumanSize } from '~/lib/utils/number_utils'; +import { harborArtifactsList, defaultConfig } from '../../mock_data'; + +describe('Harbor artifact list row', () => { + let wrapper; + + const ListItem = { + ...RealListItem, + data() { + return { + detailsSlots: [], + isDetailsShown: true, + }; + }, + }; + + const RouterLinkStub = { + props: { + to: { + type: Object, + }, + }, + render(createElement) { + return createElement('a', {}, this.$slots.default); + }, + }; + + const findListItem = () => wrapper.findComponent(ListItem); + const findClipboardButton = () => wrapper.findAllComponents(ClipboardButton); + const findTimeAgoTooltip = () => wrapper.findComponent(TimeAgoTooltip); + const findByTestId = (testId) => wrapper.findByTestId(testId); + + const $route = { + params: { + project: defaultConfig.harborIntegrationProjectName, + image: 'test-repository', + }, + }; + + const mountComponent = ({ propsData, config = defaultConfig }) => { + wrapper = shallowMountExtended(ArtifactsListRow, { + stubs: { + GlSprintf, + ListItem, + 'router-link': RouterLinkStub, + }, + mocks: { + $route, + }, + propsData, + provide() { + return { + ...config, + }; + }, + }); + }; + + afterEach(() => { + wrapper.destroy(); + }); + + describe('list item', () => { + beforeEach(() => { + mountComponent({ + propsData: { + artifact: harborArtifactsList[0], + }, + }); + }); + + it('exists', () => { + expect(findListItem().exists()).toBe(true); + }); + + it('has the correct artifact name', () => { + expect(findByTestId('name').text()).toBe(harborArtifactsList[0].digest); + }); + + it('has the correct tags count', () => { + const tagsCount = harborArtifactsList[0].tags.length; + expect(findByTestId('tags-count').text()).toBe(n__('%d tag', '%d tags', tagsCount)); + }); + + it('has correct digest', () => { + expect(findByTestId('digest').text()).toBe('Digest: mock_sh'); + }); + describe('time', () => { + it('has the correct push time', () => { + expect(findByTestId('time').text()).toBe('Published'); + expect(findTimeAgoTooltip().attributes()).toMatchObject({ + time: harborArtifactsList[0].pushTime, + }); + }); + }); + + describe('clipboard button', () => { + it('exists', () => { + expect(findClipboardButton()).toHaveLength(2); + }); + + it('has the correct props', () => { + expect(findClipboardButton().at(0).attributes()).toMatchObject({ + text: `docker pull demo.harbor.com/test-project/test-repository@${harborArtifactsList[0].digest}`, + title: `docker pull demo.harbor.com/test-project/test-repository@${harborArtifactsList[0].digest}`, + }); + + expect(findClipboardButton().at(1).attributes()).toMatchObject({ + text: harborArtifactsList[0].digest, + title: harborArtifactsList[0].digest, + }); + }); + }); + + describe('size', () => { + it('calculated correctly', () => { + expect(findByTestId('size').text()).toBe( + numberToHumanSize(Number(harborArtifactsList[0].size)), + ); + }); + + it('when size is missing', () => { + const artifactInfo = harborArtifactsList[0]; + artifactInfo.size = null; + + mountComponent({ + propsData: { + artifact: artifactInfo, + }, + }); + + expect(findByTestId('size').text()).toBe('0 bytes'); + }); + }); + }); +}); diff --git a/spec/frontend/packages_and_registries/harbor_registry/components/details/artifacts_list_spec.js b/spec/frontend/packages_and_registries/harbor_registry/components/details/artifacts_list_spec.js new file mode 100644 index 00000000000..b9d6dc2679e --- /dev/null +++ b/spec/frontend/packages_and_registries/harbor_registry/components/details/artifacts_list_spec.js @@ -0,0 +1,75 @@ +import { shallowMount } from '@vue/test-utils'; +import { GlEmptyState } from '@gitlab/ui'; +import TagsLoader from '~/packages_and_registries/shared/components/tags_loader.vue'; +import RegistryList from '~/packages_and_registries/shared/components/registry_list.vue'; +import ArtifactsList from '~/packages_and_registries/harbor_registry/components/details/artifacts_list.vue'; +import ArtifactsListRow from '~/packages_and_registries/harbor_registry/components/details/artifacts_list_row.vue'; +import { defaultConfig, harborArtifactsList } from '../../mock_data'; + +describe('Harbor artifacts list', () => { + let wrapper; + + const findTagsLoader = () => wrapper.findComponent(TagsLoader); + const findGlEmptyState = () => wrapper.findComponent(GlEmptyState); + const findRegistryList = () => wrapper.findComponent(RegistryList); + const findArtifactsListRow = () => wrapper.findAllComponents(ArtifactsListRow); + + const mountComponent = ({ propsData, config = defaultConfig }) => { + wrapper = shallowMount(ArtifactsList, { + propsData, + stubs: { RegistryList }, + provide() { + return { + ...config, + }; + }, + }); + }; + + afterEach(() => { + wrapper.destroy(); + }); + + describe('when isLoading is true', () => { + beforeEach(() => { + mountComponent({ + propsData: { + isLoading: true, + pageInfo: {}, + filter: '', + artifacts: [], + }, + }); + }); + + it('show the loader', () => { + expect(findTagsLoader().exists()).toBe(true); + }); + + it('does not show the list', () => { + expect(findGlEmptyState().exists()).toBe(false); + expect(findRegistryList().exists()).toBe(false); + }); + }); + + describe('registry list', () => { + beforeEach(() => { + mountComponent({ + propsData: { + isLoading: false, + pageInfo: {}, + filter: '', + artifacts: harborArtifactsList, + }, + }); + }); + + it('exists', () => { + expect(findRegistryList().exists()).toBe(true); + }); + + it('one artifact row exist', () => { + expect(findArtifactsListRow()).toHaveLength(harborArtifactsList.length); + }); + }); +}); diff --git a/spec/frontend/packages_and_registries/harbor_registry/components/details/details_header_spec.js b/spec/frontend/packages_and_registries/harbor_registry/components/details/details_header_spec.js new file mode 100644 index 00000000000..e8cc2b2e22d --- /dev/null +++ b/spec/frontend/packages_and_registries/harbor_registry/components/details/details_header_spec.js @@ -0,0 +1,85 @@ +import { shallowMount } from '@vue/test-utils'; +import { nextTick } from 'vue'; +import DetailsHeader from '~/packages_and_registries/harbor_registry/components/details/details_header.vue'; +import TitleArea from '~/vue_shared/components/registry/title_area.vue'; +import { ROOT_IMAGE_TEXT } from '~/packages_and_registries/harbor_registry/constants/index'; + +describe('Harbor Details Header', () => { + let wrapper; + + const findByTestId = (testId) => wrapper.find(`[data-testid="${testId}"]`); + const findTitle = () => findByTestId('title'); + const findArtifactsCount = () => findByTestId('artifacts-count'); + + const mountComponent = ({ propsData }) => { + wrapper = shallowMount(DetailsHeader, { + propsData, + stubs: { + TitleArea, + }, + }); + }; + + afterEach(() => { + wrapper.destroy(); + }); + + describe('artifact name', () => { + describe('missing image name', () => { + beforeEach(() => { + mountComponent({ propsData: { imagesDetail: { name: '', artifactCount: 1 } } }); + }); + + it('root image', () => { + expect(findTitle().text()).toBe(ROOT_IMAGE_TEXT); + }); + }); + + describe('with artifact name present', () => { + beforeEach(() => { + mountComponent({ propsData: { imagesDetail: { name: 'shao/flinkx', artifactCount: 1 } } }); + }); + + it('shows artifact.name', () => { + expect(findTitle().text()).toContain('shao/flinkx'); + }); + }); + }); + + describe('metadata items', () => { + describe('artifacts count', () => { + it('displays "-- artifacts" while loading', async () => { + mountComponent({ propsData: { imagesDetail: {} } }); + await nextTick(); + + expect(findArtifactsCount().props('text')).toBe('-- artifacts'); + }); + + it('when there is more than one artifact has the correct text', async () => { + mountComponent({ propsData: { imagesDetail: { name: 'shao/flinkx', artifactCount: 10 } } }); + + await nextTick(); + + expect(findArtifactsCount().props('text')).toBe('10 artifacts'); + }); + + it('when there is one artifact has the correct text', async () => { + mountComponent({ + propsData: { imagesDetail: { name: 'shao/flinkx', artifactCount: 1 } }, + }); + await nextTick(); + + expect(findArtifactsCount().props('text')).toBe('1 artifact'); + }); + + it('has the correct icon', async () => { + mountComponent({ + propsData: { imagesDetail: { name: 'shao/flinkx', artifactCount: 1 } }, + }); + await nextTick(); + + expect(findArtifactsCount().props('icon')).toBe('package'); + }); + }); + }); +}); diff --git a/spec/frontend/packages_and_registries/harbor_registry/components/list/harbor_list_header_spec.js b/spec/frontend/packages_and_registries/harbor_registry/components/list/harbor_list_header_spec.js index 636f3eeb04a..7a6169d300c 100644 --- a/spec/frontend/packages_and_registries/harbor_registry/components/list/harbor_list_header_spec.js +++ b/spec/frontend/packages_and_registries/harbor_registry/components/list/harbor_list_header_spec.js @@ -7,14 +7,15 @@ import MetadataItem from '~/vue_shared/components/registry/metadata_item.vue'; import { HARBOR_REGISTRY_TITLE, LIST_INTRO_TEXT, + HARBOR_REGISTRY_HELP_PAGE_PATH, } from '~/packages_and_registries/harbor_registry/constants/index'; describe('harbor_list_header', () => { let wrapper; - const findTitleArea = () => wrapper.find(TitleArea); + const findTitleArea = () => wrapper.findComponent(TitleArea); const findCommandsSlot = () => wrapper.find('[data-testid="commands-slot"]'); - const findImagesMetaDataItem = () => wrapper.find(MetadataItem); + const findImagesMetaDataItem = () => wrapper.findComponent(MetadataItem); const mountComponent = async (propsData, slots) => { wrapper = shallowMount(HarborListHeader, { @@ -77,10 +78,10 @@ describe('harbor_list_header', () => { describe('info messages', () => { describe('default message', () => { it('is correctly bound to title_area props', () => { - mountComponent({ helpPagePath: 'foo' }); + mountComponent(); expect(findTitleArea().props('infoMessages')).toEqual([ - { text: LIST_INTRO_TEXT, link: 'foo' }, + { text: LIST_INTRO_TEXT, link: HARBOR_REGISTRY_HELP_PAGE_PATH }, ]); }); }); diff --git a/spec/frontend/packages_and_registries/harbor_registry/components/list/harbor_list_row_spec.js b/spec/frontend/packages_and_registries/harbor_registry/components/list/harbor_list_row_spec.js index 8560c4f78f7..b62d4e8836b 100644 --- a/spec/frontend/packages_and_registries/harbor_registry/components/list/harbor_list_row_spec.js +++ b/spec/frontend/packages_and_registries/harbor_registry/components/list/harbor_list_row_spec.js @@ -1,25 +1,24 @@ import { shallowMount, RouterLinkStub as RouterLink } from '@vue/test-utils'; -import { GlIcon, GlSprintf, GlSkeletonLoader } from '@gitlab/ui'; +import { GlIcon, GlSkeletonLoader } from '@gitlab/ui'; import HarborListRow from '~/packages_and_registries/harbor_registry/components/list/harbor_list_row.vue'; import ListItem from '~/vue_shared/components/registry/list_item.vue'; import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; -import { harborListResponse } from '../../mock_data'; +import { harborImagesList } from '../../mock_data'; describe('Harbor List Row', () => { let wrapper; - const [item] = harborListResponse.repositories; + const item = harborImagesList[0]; - const findDetailsLink = () => wrapper.find(RouterLink); + const findDetailsLink = () => wrapper.findComponent(RouterLink); const findClipboardButton = () => wrapper.findComponent(ClipboardButton); - const findTagsCount = () => wrapper.find('[data-testid="tags-count"]'); + const findArtifactsCount = () => wrapper.find('[data-testid="artifacts-count"]'); const findSkeletonLoader = () => wrapper.findComponent(GlSkeletonLoader); const mountComponent = (props) => { wrapper = shallowMount(HarborListRow, { stubs: { RouterLink, - GlSprintf, ListItem, }, propsData: { @@ -42,7 +41,8 @@ describe('Harbor List Row', () => { expect(findDetailsLink().props('to')).toMatchObject({ name: 'details', params: { - id: item.id, + image: 'nginx', + project: 'nginx', }, }); }); @@ -56,17 +56,17 @@ describe('Harbor List Row', () => { }); }); - describe('tags count', () => { + describe('artifacts count', () => { it('exists', () => { mountComponent(); - expect(findTagsCount().exists()).toBe(true); + expect(findArtifactsCount().exists()).toBe(true); }); - it('contains a tag icon', () => { + it('contains a package icon', () => { mountComponent(); - const icon = findTagsCount().find(GlIcon); + const icon = findArtifactsCount().findComponent(GlIcon); expect(icon.exists()).toBe(true); - expect(icon.props('name')).toBe('tag'); + expect(icon.props('name')).toBe('package'); }); describe('loading state', () => { @@ -76,23 +76,23 @@ describe('Harbor List Row', () => { expect(findSkeletonLoader().exists()).toBe(true); }); - it('hides the tags count while loading', () => { + it('hides the artifacts count while loading', () => { mountComponent({ metadataLoading: true }); - expect(findTagsCount().exists()).toBe(false); + expect(findArtifactsCount().exists()).toBe(false); }); }); - describe('tags count text', () => { - it('with one tag in the image', () => { + describe('artifacts count text', () => { + it('with one artifact in the image', () => { mountComponent({ item: { ...item, artifactCount: 1 } }); - expect(findTagsCount().text()).toMatchInterpolatedText('1 Tag'); + expect(findArtifactsCount().text()).toMatchInterpolatedText('1 artifact'); }); - it('with more than one tag in the image', () => { + it('with more than one artifact in the image', () => { mountComponent({ item: { ...item, artifactCount: 3 } }); - expect(findTagsCount().text()).toMatchInterpolatedText('3 Tags'); + expect(findArtifactsCount().text()).toMatchInterpolatedText('3 artifacts'); }); }); }); diff --git a/spec/frontend/packages_and_registries/harbor_registry/components/list/harbor_list_spec.js b/spec/frontend/packages_and_registries/harbor_registry/components/list/harbor_list_spec.js index f018eff58c9..e7e74a0da58 100644 --- a/spec/frontend/packages_and_registries/harbor_registry/components/list/harbor_list_spec.js +++ b/spec/frontend/packages_and_registries/harbor_registry/components/list/harbor_list_spec.js @@ -2,19 +2,19 @@ import { shallowMount } from '@vue/test-utils'; import HarborList from '~/packages_and_registries/harbor_registry/components/list/harbor_list.vue'; import HarborListRow from '~/packages_and_registries/harbor_registry/components/list/harbor_list_row.vue'; import RegistryList from '~/packages_and_registries/shared/components/registry_list.vue'; -import { harborListResponse } from '../../mock_data'; +import { harborImagesList } from '../../mock_data'; describe('Harbor List', () => { let wrapper; - const findHarborListRow = () => wrapper.findAll(HarborListRow); + const findHarborListRow = () => wrapper.findAllComponents(HarborListRow); const mountComponent = (props) => { wrapper = shallowMount(HarborList, { stubs: { RegistryList }, propsData: { - images: harborListResponse.repositories, - pageInfo: harborListResponse.pageInfo, + images: harborImagesList, + pageInfo: {}, ...props, }, }); @@ -28,7 +28,7 @@ describe('Harbor List', () => { it('contains one list element for each image', () => { mountComponent(); - expect(findHarborListRow().length).toBe(harborListResponse.repositories.length); + expect(findHarborListRow().length).toBe(harborImagesList.length); }); it('passes down the metadataLoading prop', () => { diff --git a/spec/frontend/packages_and_registries/harbor_registry/components/tags/tags_header_spec.js b/spec/frontend/packages_and_registries/harbor_registry/components/tags/tags_header_spec.js new file mode 100644 index 00000000000..5e299a269e3 --- /dev/null +++ b/spec/frontend/packages_and_registries/harbor_registry/components/tags/tags_header_spec.js @@ -0,0 +1,52 @@ +import { nextTick } from 'vue'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import TagsHeader from '~/packages_and_registries/harbor_registry/components/tags/tags_header.vue'; +import TitleArea from '~/vue_shared/components/registry/title_area.vue'; +import { mockArtifactDetail, MOCK_SHA_DIGEST } from '../../mock_data'; + +describe('Harbor Tags Header', () => { + let wrapper; + + const findTitle = () => wrapper.findByTestId('title'); + const findTagsCount = () => wrapper.findByTestId('tags-count'); + + const mountComponent = ({ propsData }) => { + wrapper = shallowMountExtended(TagsHeader, { + propsData, + stubs: { + TitleArea, + }, + }); + }; + + const mockPageInfo = { + page: 1, + perPage: 20, + total: 1, + totalPages: 1, + }; + + afterEach(() => { + wrapper.destroy(); + }); + + beforeEach(() => { + mountComponent({ + propsData: { artifactDetail: mockArtifactDetail, pageInfo: mockPageInfo, tagsLoading: false }, + }); + }); + + describe('tags title', () => { + it('should be artifact digest', () => { + expect(findTitle().text()).toBe(`sha256:${MOCK_SHA_DIGEST}`); + }); + }); + + describe('tags count', () => { + it('would has the correct text', async () => { + await nextTick(); + + expect(findTagsCount().props('text')).toBe('1 tag'); + }); + }); +}); diff --git a/spec/frontend/packages_and_registries/harbor_registry/components/tags/tags_list_row_spec.js b/spec/frontend/packages_and_registries/harbor_registry/components/tags/tags_list_row_spec.js new file mode 100644 index 00000000000..6fe3dabc603 --- /dev/null +++ b/spec/frontend/packages_and_registries/harbor_registry/components/tags/tags_list_row_spec.js @@ -0,0 +1,75 @@ +import { GlSprintf } from '@gitlab/ui'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import ListItem from '~/vue_shared/components/registry/list_item.vue'; +import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; +import TagsListRow from '~/packages_and_registries/harbor_registry/components/tags/tags_list_row.vue'; +import { defaultConfig, harborTagsList } from '../../mock_data'; + +describe('Harbor tag list row', () => { + let wrapper; + + const findListItem = () => wrapper.find(ListItem); + const findClipboardButton = () => wrapper.find(ClipboardButton); + const findByTestId = (testId) => wrapper.findByTestId(testId); + + const $route = { + params: { + project: defaultConfig.harborIntegrationProjectName, + image: 'test-repository', + }, + }; + + const mountComponent = ({ propsData, config = defaultConfig }) => { + wrapper = shallowMountExtended(TagsListRow, { + stubs: { + ListItem, + GlSprintf, + }, + propsData, + mocks: { + $route, + }, + provide() { + return { + ...config, + }; + }, + }); + }; + + afterEach(() => { + wrapper.destroy(); + }); + + describe('list item', () => { + beforeEach(() => { + mountComponent({ + propsData: { + tag: harborTagsList[0], + }, + }); + }); + + it('exists', () => { + expect(findListItem().exists()).toBe(true); + }); + + it('has the correct tag name', () => { + expect(findByTestId('name').text()).toBe(harborTagsList[0].name); + }); + + describe(' clipboard button', () => { + it('exists', () => { + expect(findClipboardButton().exists()).toBe(true); + }); + + it('has the correct props', () => { + const pullCommand = `docker pull demo.harbor.com/test-project/test-repository:${harborTagsList[0].name}`; + expect(findClipboardButton().attributes()).toMatchObject({ + text: pullCommand, + title: pullCommand, + }); + }); + }); + }); +}); diff --git a/spec/frontend/packages_and_registries/harbor_registry/components/tags/tags_list_spec.js b/spec/frontend/packages_and_registries/harbor_registry/components/tags/tags_list_spec.js new file mode 100644 index 00000000000..6bcf6611d07 --- /dev/null +++ b/spec/frontend/packages_and_registries/harbor_registry/components/tags/tags_list_spec.js @@ -0,0 +1,66 @@ +import { shallowMount } from '@vue/test-utils'; +import TagsList from '~/packages_and_registries/harbor_registry/components/tags/tags_list.vue'; +import TagsLoader from '~/packages_and_registries/shared/components/tags_loader.vue'; +import TagsListRow from '~/packages_and_registries/harbor_registry/components/tags/tags_list_row.vue'; +import RegistryList from '~/packages_and_registries/shared/components/registry_list.vue'; +import { defaultConfig, harborTagsResponse } from '../../mock_data'; + +describe('Harbor Tags List', () => { + let wrapper; + + const findTagsLoader = () => wrapper.find(TagsLoader); + const findTagsListRows = () => wrapper.findAllComponents(TagsListRow); + const findRegistryList = () => wrapper.find(RegistryList); + + const mountComponent = ({ propsData, config = defaultConfig }) => { + wrapper = shallowMount(TagsList, { + propsData, + stubs: { RegistryList }, + provide() { + return { + ...config, + }; + }, + }); + }; + + afterEach(() => { + wrapper.destroy(); + }); + + describe('when isLoading is true', () => { + beforeEach(() => { + mountComponent({ + propsData: { + isLoading: true, + pageInfo: {}, + tags: [], + }, + }); + }); + + it('show the loader', () => { + expect(findTagsLoader().exists()).toBe(true); + }); + }); + + describe('tags list', () => { + beforeEach(() => { + mountComponent({ + propsData: { + isLoading: false, + pageInfo: {}, + tags: harborTagsResponse, + }, + }); + }); + + it('should render correctly', () => { + expect(findRegistryList().exists()).toBe(true); + }); + + it('one tag row exists', () => { + expect(findTagsListRows()).toHaveLength(harborTagsResponse.length); + }); + }); +}); diff --git a/spec/frontend/packages_and_registries/harbor_registry/mock_data.js b/spec/frontend/packages_and_registries/harbor_registry/mock_data.js index 85399c22e79..b8989b6092e 100644 --- a/spec/frontend/packages_and_registries/harbor_registry/mock_data.js +++ b/spec/frontend/packages_and_registries/harbor_registry/mock_data.js @@ -1,175 +1,114 @@ -export const harborListResponse = { - repositories: [ - { - artifactCount: 1, - creationTime: '2022-03-02T06:35:53.205Z', - id: 25, - name: 'shao/flinkx', - projectId: 21, - pullCount: 0, - updateTime: '2022-03-02T06:35:53.205Z', - location: 'demo.harbor.com/gitlab-cn/build/cng-images/gitlab-kas', - }, - { - artifactCount: 1, - creationTime: '2022-03-02T06:35:53.205Z', - id: 26, - name: 'shao/flinkx1', - projectId: 21, - pullCount: 0, - updateTime: '2022-03-02T06:35:53.205Z', - location: 'demo.harbor.com/gitlab-cn/build/cng-images/gitlab-kas', - }, - { - artifactCount: 1, - creationTime: '2022-03-02T06:35:53.205Z', - id: 27, - name: 'shao/flinkx2', - projectId: 21, - pullCount: 0, - updateTime: '2022-03-02T06:35:53.205Z', - location: 'demo.harbor.com/gitlab-cn/build/cng-images/gitlab-kas', - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: false, - hasPreviousPage: false, - }, +export const harborImageDetailEmptyResponse = { + data: null, }; -export const harborTagsResponse = { - tags: [ - { - digest: 'sha256:7f386a1844faf341353e1c20f2f39f11f397604fedc475435d13f756eeb235d1', - location: - 'registry.gitlab.com/gitlab-org/gitlab/gitlab-ee-qa/cache:02310e655103823920157bc4410ea361dc638bc2cda59667d2cb1f2a988e264c', - path: - 'gitlab-org/gitlab/gitlab-ee-qa/cache:02310e655103823920157bc4410ea361dc638bc2cda59667d2cb1f2a988e264c', - name: '02310e655103823920157bc4410ea361dc638bc2cda59667d2cb1f2a988e264c', - revision: 'f53bde3d44699e04e11cf15fb415046a0913e2623d878d89bc21adb2cbda5255', - shortRevision: 'f53bde3d4', - createdAt: '2022-03-02T23:59:05+00:00', - totalSize: '6623124', - }, - { - digest: 'sha256:4554416b84c4568fe93086620b637064ed029737aabe7308b96d50e3d9d92ed7', - location: - 'registry.gitlab.com/gitlab-org/gitlab/gitlab-ee-qa/cache:02deb4dddf177212b50e883d5e4f6c03731fad1a18cd27261736cd9dbba79160', - path: - 'gitlab-org/gitlab/gitlab-ee-qa/cache:02deb4dddf177212b50e883d5e4f6c03731fad1a18cd27261736cd9dbba79160', - name: '02deb4dddf177212b50e883d5e4f6c03731fad1a18cd27261736cd9dbba79160', - revision: 'e1fe52d8bab66d71bd54a6b8784d3b9edbc68adbd6ea87f5fa44d9974144ef9e', - shortRevision: 'e1fe52d8b', - createdAt: '2022-02-10T01:09:56+00:00', - totalSize: '920760', - }, - { - digest: 'sha256:14f37b60e52b9ce0e9f8f7094b311d265384798592f783487c30aaa3d58e6345', - location: - 'registry.gitlab.com/gitlab-org/gitlab/gitlab-ee-qa/cache:03bc5971bab1e849ba52a20a31e7273053f22b2ddb1d04bd6b77d53a2635727a', - path: - 'gitlab-org/gitlab/gitlab-ee-qa/cache:03bc5971bab1e849ba52a20a31e7273053f22b2ddb1d04bd6b77d53a2635727a', - name: '03bc5971bab1e849ba52a20a31e7273053f22b2ddb1d04bd6b77d53a2635727a', - revision: 'c72770c6eb93c421bc496964b4bffc742b1ec2e642cdab876be7afda1856029f', - shortRevision: 'c72770c6e', - createdAt: '2021-12-22T04:48:48+00:00', - totalSize: '48609053', - }, - { - digest: 'sha256:e925e3b8277ea23f387ed5fba5e78280cfac7cfb261a78cf046becf7b6a3faae', - location: - 'registry.gitlab.com/gitlab-org/gitlab/gitlab-ee-qa/cache:03f495bc5714bff78bb14293320d336afdf47fd47ddff0c3c5f09f8da86d5d19', - path: - 'gitlab-org/gitlab/gitlab-ee-qa/cache:03f495bc5714bff78bb14293320d336afdf47fd47ddff0c3c5f09f8da86d5d19', - name: '03f495bc5714bff78bb14293320d336afdf47fd47ddff0c3c5f09f8da86d5d19', - revision: '1ac2a43194f4e15166abdf3f26e6ec92215240490b9cac834d63de1a3d87494a', - shortRevision: '1ac2a4319', - createdAt: '2022-03-09T11:02:27+00:00', - totalSize: '35141894', - }, - { - digest: 'sha256:7d8303fd5c077787a8c879f8f66b69e2b5605f48ccd3f286e236fb0749fcc1ca', - location: - 'registry.gitlab.com/gitlab-org/gitlab/gitlab-ee-qa/cache:05a4e58231e54b70aab2d6f22ba4fbe10e48aa4ddcbfef11c5662241c2ae4fda', - path: - 'gitlab-org/gitlab/gitlab-ee-qa/cache:05a4e58231e54b70aab2d6f22ba4fbe10e48aa4ddcbfef11c5662241c2ae4fda', - name: '05a4e58231e54b70aab2d6f22ba4fbe10e48aa4ddcbfef11c5662241c2ae4fda', - revision: 'cf8fee086701016e1a84e6824f0c896951fef4cce9d4745459558b87eec3232c', - shortRevision: 'cf8fee086', - createdAt: '2022-01-21T11:31:43+00:00', - totalSize: '48716070', - }, - { - digest: 'sha256:b33611cefe20e4a41a6e0dce356a5d7ef3c177ea7536a58652f5b3a4f2f83549', - location: - 'registry.gitlab.com/gitlab-org/gitlab/gitlab-ee-qa/cache:093d2746876997723541aec8b88687a4cdb3b5bbb0279c5089b7891317741a9a', - path: - 'gitlab-org/gitlab/gitlab-ee-qa/cache:093d2746876997723541aec8b88687a4cdb3b5bbb0279c5089b7891317741a9a', - name: '093d2746876997723541aec8b88687a4cdb3b5bbb0279c5089b7891317741a9a', - revision: '1a4b48198b13d55242c5164e64d41c4e9f75b5d9506bc6e0efc1534dd0dd1f15', - shortRevision: '1a4b48198', - createdAt: '2022-01-21T11:31:51+00:00', - totalSize: '6623127', - }, - { - digest: 'sha256:d25c3c020e2dbd4711a67b9fe308f4cbb7b0bb21815e722a02f91c570dc5d519', - location: - 'registry.gitlab.com/gitlab-org/gitlab/gitlab-ee-qa/cache:09698b3fae81dfd6e02554dbc82930f304a6356c8f541c80e8598a42aed985f7', - path: - 'gitlab-org/gitlab/gitlab-ee-qa/cache:09698b3fae81dfd6e02554dbc82930f304a6356c8f541c80e8598a42aed985f7', - name: '09698b3fae81dfd6e02554dbc82930f304a6356c8f541c80e8598a42aed985f7', - revision: '03e2e2777dde01c30469ee8c710973dd08a7a4f70494d7dc1583c24b525d7f61', - shortRevision: '03e2e2777', - createdAt: '2022-03-02T23:58:20+00:00', - totalSize: '911377', - }, - { - digest: 'sha256:fb760e4d2184e9e8e39d6917534d4610fe01009734698a5653b2de1391ba28f4', - location: - 'registry.gitlab.com/gitlab-org/gitlab/gitlab-ee-qa/cache:09b830c3eaf80d547f3b523d8e242a2c411085c349dab86c520f36c7b7644f95', - path: - 'gitlab-org/gitlab/gitlab-ee-qa/cache:09b830c3eaf80d547f3b523d8e242a2c411085c349dab86c520f36c7b7644f95', - name: '09b830c3eaf80d547f3b523d8e242a2c411085c349dab86c520f36c7b7644f95', - revision: '350e78d60646bf6967244448c6aaa14d21ecb9a0c6cf87e9ff0361cbe59b9012', - shortRevision: '350e78d60', - createdAt: '2022-01-19T13:49:14+00:00', - totalSize: '48710241', - }, - { - digest: 'sha256:407250f380cea92729cbc038c420e74900f53b852e11edc6404fe75a0fd2c402', - location: - 'registry.gitlab.com/gitlab-org/gitlab/gitlab-ee-qa/cache:0d03504a17b467eafc8c96bde70af26c74bd459a32b7eb2dd189dd6b3c121557', - path: - 'gitlab-org/gitlab/gitlab-ee-qa/cache:0d03504a17b467eafc8c96bde70af26c74bd459a32b7eb2dd189dd6b3c121557', - name: '0d03504a17b467eafc8c96bde70af26c74bd459a32b7eb2dd189dd6b3c121557', - revision: '76038370b7f3904364891457c4a6a234897255e6b9f45d0a852bf3a7e5257e18', - shortRevision: '76038370b', - createdAt: '2022-01-24T12:56:22+00:00', - totalSize: '280065', - }, - { - digest: 'sha256:ada87f25218542951ce6720c27f3d0758e90c2540bd129f5cfb9e15b31e07b07', - location: - 'registry.gitlab.com/gitlab-org/gitlab/gitlab-ee-qa/cache:0eb20a4a7cac2ebea821d420b3279654fe550fd8502f1785c1927aa84e5949eb', - path: - 'gitlab-org/gitlab/gitlab-ee-qa/cache:0eb20a4a7cac2ebea821d420b3279654fe550fd8502f1785c1927aa84e5949eb', - name: '0eb20a4a7cac2ebea821d420b3279654fe550fd8502f1785c1927aa84e5949eb', - revision: '3d4b49a7bbb36c48bb721f4d0e76e7950bec3878ee29cdfdd6da39f575d6d37f', - shortRevision: '3d4b49a7b', - createdAt: '2022-02-17T17:37:52+00:00', - totalSize: '48655767', - }, - ], - totalCount: 100, - pageInfo: { - hasNextPage: false, - hasPreviousPage: false, +export const MOCK_SHA_DIGEST = 'mock_sha_digest_value'; + +export const harborImageDetailResponse = { + artifactCount: 10, + creationTime: '2022-03-02T06:35:53.205Z', + id: 25, + name: 'shao/flinkx', + projectId: 21, + pullCount: 0, + updateTime: '2022-03-02T06:35:53.205Z', + location: 'demo.harbor.com/gitlab-cn/build/cng-images/gitlab-kas', +}; + +export const harborArtifactsResponse = [ + { + id: 1, + digest: `sha256:${MOCK_SHA_DIGEST}`, + size: 773928, + push_time: '2022-05-19T15:54:47.821Z', + tags: ['latest'], + }, +]; + +export const harborArtifactsList = [ + { + id: 1, + digest: `sha256:${MOCK_SHA_DIGEST}`, + size: 773928, + pushTime: '2022-05-19T15:54:47.821Z', + tags: ['latest'], + }, +]; + +export const harborTagsResponse = [ + { + repository_id: 4, + artifact_id: 5, + id: 4, + name: 'latest', + pull_time: '0001-01-01T00:00:00.000Z', + push_time: '2022-05-27T18:21:27.903Z', + signed: false, + immutable: false, + }, +]; + +export const harborTagsList = [ + { + repositoryId: 4, + artifactId: 5, + id: 4, + name: 'latest', + pullTime: '0001-01-01T00:00:00.000Z', + pushTime: '2022-05-27T18:21:27.903Z', + signed: false, + immutable: false, }, +]; + +export const defaultConfig = { + noContainersImage: 'noContainersImage', + repositoryUrl: 'demo.harbor.com', + harborIntegrationProjectName: 'test-project', + projectName: 'Flight', + endpoint: '/flightjs/Flight/-/harbor/repositories', + connectionError: false, + invalidPathError: false, + isGroupPage: false, + containersErrorImage: 'containersErrorImage', }; +export const defaultFullPath = 'flightjs/Flight'; + +export const harborImagesResponse = [ + { + id: 1, + name: 'nginx/nginx', + artifact_count: 1, + creation_time: '2022-05-29T10:07:16.812Z', + update_time: '2022-05-29T10:07:16.812Z', + project_id: 4, + pull_count: 0, + location: 'https://demo.goharbor.io/harbor/projects/4/repositories/nginx', + }, +]; + +export const harborImagesList = [ + { + id: 1, + name: 'nginx/nginx', + artifactCount: 1, + creationTime: '2022-05-29T10:07:16.812Z', + updateTime: '2022-05-29T10:07:16.812Z', + projectId: 4, + pullCount: 0, + location: 'https://demo.goharbor.io/harbor/projects/4/repositories/nginx', + }, +]; + export const dockerCommands = { dockerBuildCommand: 'foofoo', dockerPushCommand: 'barbar', dockerLoginCommand: 'bazbaz', }; + +export const mockArtifactDetail = { + project: 'test-project', + image: 'test-repository', + digest: `sha256:${MOCK_SHA_DIGEST}`, +}; diff --git a/spec/frontend/packages_and_registries/harbor_registry/pages/details_spec.js b/spec/frontend/packages_and_registries/harbor_registry/pages/details_spec.js new file mode 100644 index 00000000000..8fd50bea280 --- /dev/null +++ b/spec/frontend/packages_and_registries/harbor_registry/pages/details_spec.js @@ -0,0 +1,162 @@ +import { shallowMount } from '@vue/test-utils'; +import { nextTick } from 'vue'; +import { GlFilteredSearchToken } from '@gitlab/ui'; +import { s__ } from '~/locale'; +import HarborDetailsPage from '~/packages_and_registries/harbor_registry/pages/details.vue'; +import TagsLoader from '~/packages_and_registries/shared/components/tags_loader.vue'; +import ArtifactsList from '~/packages_and_registries/harbor_registry/components/details/artifacts_list.vue'; +import waitForPromises from 'helpers/wait_for_promises'; +import DetailsHeader from '~/packages_and_registries/harbor_registry/components/details/details_header.vue'; +import PersistedSearch from '~/packages_and_registries/shared/components/persisted_search.vue'; +import { OPERATOR_IS_ONLY } from '~/vue_shared/components/filtered_search_bar/constants'; +import { + NAME_SORT_FIELD, + TOKEN_TYPE_TAG_NAME, +} from '~/packages_and_registries/harbor_registry/constants/index'; +import { harborArtifactsResponse, harborArtifactsList, defaultConfig } from '../mock_data'; + +let mockHarborArtifactsResponse; + +jest.mock('~/rest_api', () => ({ + getHarborArtifacts: () => mockHarborArtifactsResponse, +})); + +describe('Harbor Details Page', () => { + let wrapper; + + const findTagsLoader = () => wrapper.findComponent(TagsLoader); + const findArtifactsList = () => wrapper.findComponent(ArtifactsList); + const findDetailsHeader = () => wrapper.findComponent(DetailsHeader); + const findPersistedSearch = () => wrapper.findComponent(PersistedSearch); + + const waitForHarborDetailRequest = async () => { + await waitForPromises(); + await nextTick(); + }; + + const $route = { + params: { + project: 'test-project', + image: 'test-repository', + }, + }; + + const breadCrumbState = { + updateName: jest.fn(), + updateHref: jest.fn(), + }; + + const defaultHeaders = { + 'x-page': '1', + 'X-Per-Page': '20', + 'X-TOTAL': '1', + 'X-Total-Pages': '1', + }; + + const mountComponent = ({ config = defaultConfig } = {}) => { + wrapper = shallowMount(HarborDetailsPage, { + mocks: { + $route, + }, + provide() { + return { + breadCrumbState, + ...config, + }; + }, + }); + }; + + beforeEach(() => { + mockHarborArtifactsResponse = Promise.resolve({ + data: harborArtifactsResponse, + headers: defaultHeaders, + }); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + describe('when isLoading is true', () => { + it('shows the loader', () => { + mountComponent(); + + expect(findTagsLoader().exists()).toBe(true); + }); + + it('does not show the list', () => { + mountComponent(); + + expect(findArtifactsList().exists()).toBe(false); + }); + }); + + describe('artifacts list', () => { + it('exists', async () => { + mountComponent(); + + findPersistedSearch().vm.$emit('update', { sort: 'NAME_ASC', filters: [] }); + await waitForHarborDetailRequest(); + + expect(findArtifactsList().exists()).toBe(true); + }); + + it('has the correct props bound', async () => { + mountComponent(); + + findPersistedSearch().vm.$emit('update', { sort: 'NAME_ASC', filters: [] }); + await waitForHarborDetailRequest(); + + expect(findArtifactsList().props()).toMatchObject({ + isLoading: false, + filter: '', + artifacts: harborArtifactsList, + pageInfo: { + page: 1, + perPage: 20, + total: 1, + totalPages: 1, + }, + }); + }); + }); + + describe('persisted search', () => { + it('has the correct props', () => { + mountComponent(); + + expect(findPersistedSearch().props()).toMatchObject({ + sortableFields: [NAME_SORT_FIELD], + defaultOrder: NAME_SORT_FIELD.orderBy, + defaultSort: 'asc', + tokens: [ + { + type: TOKEN_TYPE_TAG_NAME, + icon: 'tag', + title: s__('HarborRegistry|Tag'), + unique: true, + token: GlFilteredSearchToken, + operators: OPERATOR_IS_ONLY, + }, + ], + }); + }); + }); + + describe('header', () => { + it('has the correct props', async () => { + mountComponent(); + + findPersistedSearch().vm.$emit('update', { sort: 'NAME_ASC', filters: [] }); + await waitForHarborDetailRequest(); + + expect(findDetailsHeader().props()).toMatchObject({ + imagesDetail: { + name: 'test-project/test-repository', + artifactCount: 1, + }, + }); + }); + }); +}); diff --git a/spec/frontend/packages_and_registries/harbor_registry/pages/index_spec.js b/spec/frontend/packages_and_registries/harbor_registry/pages/index_spec.js index 55fc8066f65..942cf9bad2c 100644 --- a/spec/frontend/packages_and_registries/harbor_registry/pages/index_spec.js +++ b/spec/frontend/packages_and_registries/harbor_registry/pages/index_spec.js @@ -4,7 +4,7 @@ import component from '~/packages_and_registries/harbor_registry/pages/index.vue describe('List Page', () => { let wrapper; - const findRouterView = () => wrapper.find({ ref: 'router-view' }); + const findRouterView = () => wrapper.findComponent({ ref: 'router-view' }); const mountComponent = () => { wrapper = shallowMount(component, { diff --git a/spec/frontend/packages_and_registries/harbor_registry/pages/list_spec.js b/spec/frontend/packages_and_registries/harbor_registry/pages/list_spec.js index 61ee36a2794..97d30e6fe99 100644 --- a/spec/frontend/packages_and_registries/harbor_registry/pages/list_spec.js +++ b/spec/frontend/packages_and_registries/harbor_registry/pages/list_spec.js @@ -5,15 +5,14 @@ import HarborListHeader from '~/packages_and_registries/harbor_registry/componen import HarborRegistryList from '~/packages_and_registries/harbor_registry/pages/list.vue'; import PersistedSearch from '~/packages_and_registries/shared/components/persisted_search.vue'; import waitForPromises from 'helpers/wait_for_promises'; -// import { harborListResponse } from '~/packages_and_registries/harbor_registry/mock_api.js'; import HarborList from '~/packages_and_registries/harbor_registry/components/list/harbor_list.vue'; import CliCommands from '~/packages_and_registries/shared/components/cli_commands.vue'; import { SORT_FIELDS } from '~/packages_and_registries/harbor_registry/constants/index'; -import { harborListResponse, dockerCommands } from '../mock_data'; +import { harborImagesResponse, defaultConfig, harborImagesList } from '../mock_data'; let mockHarborListResponse; -jest.mock('~/packages_and_registries/harbor_registry/mock_api.js', () => ({ - harborListResponse: () => mockHarborListResponse, +jest.mock('~/rest_api', () => ({ + getHarborRepositoriesList: () => mockHarborListResponse, })); describe('Harbor List Page', () => { @@ -24,34 +23,43 @@ describe('Harbor List Page', () => { await nextTick(); }; - beforeEach(() => { - mockHarborListResponse = Promise.resolve(harborListResponse); - }); - const findHarborListHeader = () => wrapper.findComponent(HarborListHeader); const findPersistedSearch = () => wrapper.findComponent(PersistedSearch); const findSkeletonLoader = () => wrapper.findComponent(GlSkeletonLoader); const findHarborList = () => wrapper.findComponent(HarborList); const findCliCommands = () => wrapper.findComponent(CliCommands); + const defaultHeaders = { + 'x-page': '1', + 'X-Per-Page': '20', + 'X-TOTAL': '1', + 'X-Total-Pages': '1', + }; + const fireFirstSortUpdate = () => { findPersistedSearch().vm.$emit('update', { sort: 'UPDATED_DESC', filters: [] }); }; - const mountComponent = ({ config = { isGroupPage: false } } = {}) => { + const mountComponent = ({ config = defaultConfig } = {}) => { wrapper = shallowMount(HarborRegistryList, { stubs: { HarborListHeader, }, provide() { return { - config, - ...dockerCommands, + ...config, }; }, }); }; + beforeEach(() => { + mockHarborListResponse = Promise.resolve({ + data: harborImagesResponse, + headers: defaultHeaders, + }); + }); + afterEach(() => { wrapper.destroy(); }); @@ -64,7 +72,7 @@ describe('Harbor List Page', () => { expect(findHarborListHeader().exists()).toBe(true); expect(findHarborListHeader().props()).toMatchObject({ - imagesCount: 3, + imagesCount: 1, metadataLoading: false, }); }); @@ -117,6 +125,16 @@ describe('Harbor List Page', () => { await nextTick(); expect(findHarborList().exists()).toBe(true); + expect(findHarborList().props()).toMatchObject({ + images: harborImagesList, + metadataLoading: false, + pageInfo: { + page: 1, + perPage: 20, + total: 1, + totalPages: 1, + }, + }); }); }); diff --git a/spec/frontend/packages_and_registries/harbor_registry/pages/tags_spec.js b/spec/frontend/packages_and_registries/harbor_registry/pages/tags_spec.js new file mode 100644 index 00000000000..7e0f05e736b --- /dev/null +++ b/spec/frontend/packages_and_registries/harbor_registry/pages/tags_spec.js @@ -0,0 +1,125 @@ +import { nextTick } from 'vue'; +import { shallowMount } from '@vue/test-utils'; +import HarborTagsPage from '~/packages_and_registries/harbor_registry/pages/harbor_tags.vue'; +import TagsHeader from '~/packages_and_registries/harbor_registry/components/tags/tags_header.vue'; +import TagsList from '~/packages_and_registries/harbor_registry/components/tags/tags_list.vue'; +import waitForPromises from 'helpers/wait_for_promises'; +import { defaultConfig, harborTagsResponse, mockArtifactDetail } from '../mock_data'; + +let mockHarborTagsResponse; + +jest.mock('~/rest_api', () => ({ + getHarborTags: () => mockHarborTagsResponse, +})); + +describe('Harbor Tags page', () => { + let wrapper; + + const findTagsHeader = () => wrapper.find(TagsHeader); + const findTagsList = () => wrapper.find(TagsList); + + const waitForHarborTagsRequest = async () => { + await waitForPromises(); + await nextTick(); + }; + + const breadCrumbState = { + updateName: jest.fn(), + updateHref: jest.fn(), + }; + + const $route = { + params: mockArtifactDetail, + }; + + const defaultHeaders = { + 'x-page': '1', + 'X-Per-Page': '20', + 'X-TOTAL': '1', + 'X-Total-Pages': '1', + }; + + const mountComponent = ({ endpoint = defaultConfig.endpoint } = {}) => { + wrapper = shallowMount(HarborTagsPage, { + mocks: { + $route, + }, + provide() { + return { + breadCrumbState, + endpoint, + }; + }, + }); + }; + + beforeEach(() => { + mockHarborTagsResponse = Promise.resolve({ + data: harborTagsResponse, + headers: defaultHeaders, + }); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + it('contains tags header', () => { + mountComponent(); + + expect(findTagsHeader().exists()).toBe(true); + }); + + it('contains tags list', () => { + mountComponent(); + + expect(findTagsList().exists()).toBe(true); + }); + + describe('header', () => { + it('has the correct props', async () => { + mountComponent(); + + await waitForHarborTagsRequest(); + expect(findTagsHeader().props()).toMatchObject({ + artifactDetail: mockArtifactDetail, + pageInfo: { + page: 1, + perPage: 20, + total: 1, + totalPages: 1, + }, + tagsLoading: false, + }); + }); + }); + + describe('list', () => { + it('has the correct props', async () => { + mountComponent(); + + await waitForHarborTagsRequest(); + expect(findTagsList().props()).toMatchObject({ + tags: [ + { + repositoryId: 4, + artifactId: 5, + id: 4, + name: 'latest', + pullTime: '0001-01-01T00:00:00.000Z', + pushTime: '2022-05-27T18:21:27.903Z', + signed: false, + immutable: false, + }, + ], + isLoading: false, + pageInfo: { + page: 1, + perPage: 20, + total: 1, + totalPages: 1, + }, + }); + }); + }); +}); diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/app_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/app_spec.js index 69c78e64e22..e74375b7705 100644 --- a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/app_spec.js +++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/app_spec.js @@ -76,8 +76,8 @@ describe('PackagesApp', () => { const packageTitle = () => wrapper.findComponent(TerraformTitle); const emptyState = () => wrapper.findComponent(GlEmptyState); const deleteButton = () => wrapper.find('.js-delete-button'); - const findDeleteModal = () => wrapper.find({ ref: 'deleteModal' }); - const findDeleteFileModal = () => wrapper.find({ ref: 'deleteFileModal' }); + const findDeleteModal = () => wrapper.findComponent({ ref: 'deleteModal' }); + const findDeleteFileModal = () => wrapper.findComponent({ ref: 'deleteFileModal' }); const versionsTab = () => wrapper.find('.js-versions-tab > a'); const packagesLoader = () => wrapper.findComponent(PackagesListLoader); const packagesVersionRows = () => wrapper.findAllComponents(PackageListRow); diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/package_files_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/package_files_spec.js index 95de2f0bb0b..b76d7c2b57b 100644 --- a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/package_files_spec.js +++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/package_files_spec.js @@ -17,8 +17,8 @@ describe('Package Files', () => { const findFirstRowDownloadLink = () => findFirstRow().find('[data-testid="download-link"]'); const findFirstRowCommitLink = () => findFirstRow().find('[data-testid="commit-link"]'); const findSecondRowCommitLink = () => findSecondRow().find('[data-testid="commit-link"]'); - const findFirstRowFileIcon = () => findFirstRow().find(FileIcon); - const findFirstRowCreatedAt = () => findFirstRow().find(TimeAgoTooltip); + const findFirstRowFileIcon = () => findFirstRow().findComponent(FileIcon); + const findFirstRowCreatedAt = () => findFirstRow().findComponent(TimeAgoTooltip); const findFirstActionMenu = () => findFirstRow().findComponent(GlDropdown); const findActionMenuDelete = () => findFirstActionMenu().find('[data-testid="delete-file"]'); const findFirstToggleDetailsButton = () => findFirstRow().findComponent(GlButton); diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/package_history_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/package_history_spec.js index f10f05f4a0d..c6b5138639e 100644 --- a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/package_history_spec.js +++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/package_history_spec.js @@ -36,8 +36,8 @@ describe('Package History', () => { }); const findHistoryElement = (testId) => wrapper.find(`[data-testid="${testId}"]`); - const findElementLink = (container) => container.find(GlLink); - const findElementTimeAgo = (container) => container.find(TimeAgoTooltip); + const findElementLink = (container) => container.findComponent(GlLink); + const findElementTimeAgo = (container) => container.findComponent(TimeAgoTooltip); const findTitle = () => wrapper.find('[data-testid="title"]'); const findTimeline = () => wrapper.find('[data-testid="timeline"]'); diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/infrastructure_title_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/infrastructure_title_spec.js index 72d08d5683b..93d013bb458 100644 --- a/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/infrastructure_title_spec.js +++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/infrastructure_title_spec.js @@ -7,8 +7,8 @@ describe('Infrastructure Title', () => { let wrapper; let store; - const findTitleArea = () => wrapper.find(TitleArea); - const findMetadataItem = () => wrapper.find(MetadataItem); + const findTitleArea = () => wrapper.findComponent(TitleArea); + const findMetadataItem = () => wrapper.findComponent(MetadataItem); const exampleProps = { helpUrl: 'http://example.gitlab.com/help' }; diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/packages_list_app_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/packages_list_app_spec.js index 31616e0b2f5..db1d3f3f633 100644 --- a/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/packages_list_app_spec.js +++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/packages_list_app_spec.js @@ -31,9 +31,9 @@ describe('packages_list_app', () => { const GlLoadingIcon = { name: 'gl-loading-icon', template: '<div>loading</div>' }; const emptyListHelpUrl = 'helpUrl'; - const findEmptyState = () => wrapper.find(GlEmptyState); - const findListComponent = () => wrapper.find(PackageList); - const findInfrastructureSearch = () => wrapper.find(InfrastructureSearch); + const findEmptyState = () => wrapper.findComponent(GlEmptyState); + const findListComponent = () => wrapper.findComponent(PackageList); + const findInfrastructureSearch = () => wrapper.findComponent(InfrastructureSearch); const createStore = ({ filter = [], packageCount = 0 } = {}) => { store = new Vuex.Store({ @@ -151,7 +151,7 @@ describe('packages_list_app', () => { describe('empty state', () => { it('generate the correct empty list link', () => { - const link = findListComponent().find(GlLink); + const link = findListComponent().findComponent(GlLink); expect(link.attributes('href')).toBe(emptyListHelpUrl); expect(link.text()).toBe('publish and share your packages'); diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/packages_list_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/packages_list_spec.js index fed82653016..fb5ee4e6884 100644 --- a/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/packages_list_spec.js +++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/packages_list_spec.js @@ -20,11 +20,11 @@ describe('packages_list', () => { const EmptySlotStub = { name: 'empty-slot-stub', template: '<div>bar</div>' }; - const findPackagesListLoader = () => wrapper.find(PackagesListLoader); - const findPackageListPagination = () => wrapper.find(GlPagination); - const findPackageListDeleteModal = () => wrapper.find(GlModal); - const findEmptySlot = () => wrapper.find(EmptySlotStub); - const findPackagesListRow = () => wrapper.find(PackagesListRow); + const findPackagesListLoader = () => wrapper.findComponent(PackagesListLoader); + const findPackageListPagination = () => wrapper.findComponent(GlPagination); + const findPackageListDeleteModal = () => wrapper.findComponent(GlModal); + const findEmptySlot = () => wrapper.findComponent(EmptySlotStub); + const findPackagesListRow = () => wrapper.findComponent(PackagesListRow); const createStore = (isGroupPage, packages, isLoading) => { const state = { diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/shared/__snapshots__/package_list_row_spec.js.snap b/spec/frontend/packages_and_registries/infrastructure_registry/components/shared/__snapshots__/package_list_row_spec.js.snap index 67c3b8b795a..91824dee5b0 100644 --- a/spec/frontend/packages_and_registries/infrastructure_registry/components/shared/__snapshots__/package_list_row_spec.js.snap +++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/shared/__snapshots__/package_list_row_spec.js.snap @@ -3,7 +3,7 @@ exports[`packages_list_row renders 1`] = ` <div class="gl-display-flex gl-flex-direction-column gl-border-b-solid gl-border-t-solid gl-border-t-1 gl-border-b-1 gl-border-t-transparent gl-border-b-gray-100" - data-qa-selector="package_row" + data-testid="package-row" > <div class="gl-display-flex gl-align-items-center gl-py-3" diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/shared/infrastructure_icon_and_name_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/shared/infrastructure_icon_and_name_spec.js index abb0d23b6e4..db90bb4c25f 100644 --- a/spec/frontend/packages_and_registries/infrastructure_registry/components/shared/infrastructure_icon_and_name_spec.js +++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/shared/infrastructure_icon_and_name_spec.js @@ -5,7 +5,7 @@ import InfrastructureIconAndName from '~/packages_and_registries/infrastructure_ describe('InfrastructureIconAndName', () => { let wrapper; - const findIcon = () => wrapper.find(GlIcon); + const findIcon = () => wrapper.findComponent(GlIcon); const mountComponent = () => { wrapper = shallowMount(InfrastructureIconAndName, {}); diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/nuget_installation_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/nuget_installation_spec.js index d324d43258c..9449c40c7c6 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/details/nuget_installation_spec.js +++ b/spec/frontend/packages_and_registries/package_registry/components/details/nuget_installation_spec.js @@ -71,7 +71,7 @@ describe('NugetInstallation', () => { }); }); - it('it has docs link', () => { + it('has docs link', () => { expect(findSetupDocsLink().attributes()).toMatchObject({ href: NUGET_HELP_PATH, target: '_blank', diff --git a/spec/frontend/packages_and_registries/package_registry/components/list/__snapshots__/package_list_row_spec.js.snap b/spec/frontend/packages_and_registries/package_registry/components/list/__snapshots__/package_list_row_spec.js.snap index 031afa62890..5be05ddf629 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/list/__snapshots__/package_list_row_spec.js.snap +++ b/spec/frontend/packages_and_registries/package_registry/components/list/__snapshots__/package_list_row_spec.js.snap @@ -3,7 +3,7 @@ exports[`packages_list_row renders 1`] = ` <div class="gl-display-flex gl-flex-direction-column gl-border-b-solid gl-border-t-solid gl-border-t-1 gl-border-b-1 gl-border-t-transparent gl-border-b-gray-100" - data-qa-selector="package_row" + data-testid="package-row" > <div class="gl-display-flex gl-align-items-center gl-py-3" diff --git a/spec/frontend/packages_and_registries/package_registry/components/list/package_list_row_spec.js b/spec/frontend/packages_and_registries/package_registry/components/list/package_list_row_spec.js index eb1e76377ff..b5a512b8806 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/list/package_list_row_spec.js +++ b/spec/frontend/packages_and_registries/package_registry/components/list/package_list_row_spec.js @@ -30,10 +30,10 @@ describe('packages_list_row', () => { const packageWithTags = { ...packageWithoutTags, tags: { nodes: packageTags() } }; const packageCannotDestroy = { ...packageData(), canDestroy: false }; - const findPackageTags = () => wrapper.find(PackageTags); - const findPackagePath = () => wrapper.find(PackagePath); + const findPackageTags = () => wrapper.findComponent(PackageTags); + const findPackagePath = () => wrapper.findComponent(PackagePath); const findDeleteDropdown = () => wrapper.findByTestId('action-delete'); - const findPackageIconAndName = () => wrapper.find(PackageIconAndName); + const findPackageIconAndName = () => wrapper.findComponent(PackageIconAndName); const findPackageLink = () => wrapper.findByTestId('details-link'); const findWarningIcon = () => wrapper.findByTestId('warning-icon'); const findLeftSecondaryInfos = () => wrapper.findByTestId('left-secondary-infos'); diff --git a/spec/frontend/packages_and_registries/package_registry/components/list/packages_list_spec.js b/spec/frontend/packages_and_registries/package_registry/components/list/packages_list_spec.js index 660f00a2b31..3e3607a361c 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/list/packages_list_spec.js +++ b/spec/frontend/packages_and_registries/package_registry/components/list/packages_list_spec.js @@ -190,7 +190,7 @@ describe('packages_list', () => { }); }); - describe('pagination ', () => { + describe('pagination', () => { beforeEach(() => { mountComponent({ pageInfo: { hasPreviousPage: true } }); }); diff --git a/spec/frontend/packages_and_registries/package_registry/components/list/packages_title_spec.js b/spec/frontend/packages_and_registries/package_registry/components/list/packages_title_spec.js index 23e5c7330d5..b47515e15c3 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/list/packages_title_spec.js +++ b/spec/frontend/packages_and_registries/package_registry/components/list/packages_title_spec.js @@ -7,8 +7,8 @@ describe('PackageTitle', () => { let wrapper; let store; - const findTitleArea = () => wrapper.find(TitleArea); - const findMetadataItem = () => wrapper.find(MetadataItem); + const findTitleArea = () => wrapper.findComponent(TitleArea); + const findMetadataItem = () => wrapper.findComponent(MetadataItem); const mountComponent = (propsData = { helpUrl: 'foo' }) => { wrapper = shallowMount(PackageTitle, { diff --git a/spec/frontend/packages_and_registries/package_registry/components/list/tokens/package_type_token_spec.js b/spec/frontend/packages_and_registries/package_registry/components/list/tokens/package_type_token_spec.js index d0c111bae2d..8f3c8667c47 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/list/tokens/package_type_token_spec.js +++ b/spec/frontend/packages_and_registries/package_registry/components/list/tokens/package_type_token_spec.js @@ -6,8 +6,8 @@ import { PACKAGE_TYPES } from '~/packages_and_registries/package_registry/consta describe('packages_filter', () => { let wrapper; - const findFilteredSearchToken = () => wrapper.find(GlFilteredSearchToken); - const findFilteredSearchSuggestions = () => wrapper.findAll(GlFilteredSearchSuggestion); + const findFilteredSearchToken = () => wrapper.findComponent(GlFilteredSearchToken); + const findFilteredSearchSuggestions = () => wrapper.findAllComponents(GlFilteredSearchSuggestion); const mountComponent = ({ attrs, listeners } = {}) => { wrapper = shallowMount(component, { @@ -24,13 +24,13 @@ describe('packages_filter', () => { wrapper = null; }); - it('it binds all of his attrs to filtered search token', () => { + it('binds all of his attrs to filtered search token', () => { mountComponent({ attrs: { foo: 'bar' } }); expect(findFilteredSearchToken().attributes('foo')).toBe('bar'); }); - it('it binds all of his events to filtered search token', () => { + it('binds all of his events to filtered search token', () => { const clickListener = jest.fn(); mountComponent({ listeners: { click: clickListener } }); diff --git a/spec/frontend/packages_and_registries/package_registry/pages/details_spec.js b/spec/frontend/packages_and_registries/package_registry/pages/details_spec.js index de78e6bb87b..83158d1cc5e 100644 --- a/spec/frontend/packages_and_registries/package_registry/pages/details_spec.js +++ b/spec/frontend/packages_and_registries/package_registry/pages/details_spec.js @@ -178,7 +178,7 @@ describe('PackagesApp', () => { ${PACKAGE_TYPE_PYPI} | ${true} ${PACKAGE_TYPE_NPM} | ${false} `( - `It is $visible that the component is visible when the package is $packageType`, + `is $visible that the component is visible when the package is $packageType`, async ({ packageType, visible }) => { createComponent({ resolver: jest.fn().mockResolvedValue( @@ -328,8 +328,8 @@ describe('PackagesApp', () => { findPackageFiles().vm.$emit('delete-files', [fileToDelete]); - expect(showDeletePackageSpy).not.toBeCalled(); - expect(showDeleteFileSpy).toBeCalled(); + expect(showDeletePackageSpy).not.toHaveBeenCalled(); + expect(showDeleteFileSpy).toHaveBeenCalled(); }); it('when its the only file opens delete package confirmation modal', async () => { @@ -357,8 +357,8 @@ describe('PackagesApp', () => { findPackageFiles().vm.$emit('delete-files', [fileToDelete]); - expect(showDeletePackageSpy).toBeCalled(); - expect(showDeleteFileSpy).not.toBeCalled(); + expect(showDeletePackageSpy).toHaveBeenCalled(); + expect(showDeleteFileSpy).not.toHaveBeenCalled(); }); it('confirming on the modal sets the loading state', async () => { @@ -443,7 +443,7 @@ describe('PackagesApp', () => { findPackageFiles().vm.$emit('delete-files', packageFiles()); - expect(showDeleteFilesSpy).toBeCalled(); + expect(showDeleteFilesSpy).toHaveBeenCalled(); }); it('confirming on the modal sets the loading state', async () => { @@ -532,7 +532,7 @@ describe('PackagesApp', () => { findPackageFiles().vm.$emit('delete-files', packageFiles()); - expect(showDeletePackageSpy).toBeCalled(); + expect(showDeletePackageSpy).toHaveBeenCalled(); }); }); }); diff --git a/spec/frontend/packages_and_registries/settings/group/components/__snapshots__/settings_titles_spec.js.snap b/spec/frontend/packages_and_registries/settings/group/components/__snapshots__/settings_titles_spec.js.snap deleted file mode 100644 index 5b56cb7f74e..00000000000 --- a/spec/frontend/packages_and_registries/settings/group/components/__snapshots__/settings_titles_spec.js.snap +++ /dev/null @@ -1,18 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`settings_titles renders properly 1`] = ` -<div> - <h5 - class="gl-border-b-solid gl-border-b-1 gl-border-gray-200 gl-pb-3" - > - - foo - - </h5> - - <p> - bar - </p> - -</div> -`; 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 9d4c7f4737b..796d89231f4 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 @@ -169,7 +169,7 @@ describe('DependencyProxySettings', () => { toggleName | toggleFinder | localErrorMock | optimisticResponse ${'enable proxy'} | ${findEnableProxyToggle} | ${dependencyProxySettingMutationMock} | ${updateGroupDependencyProxySettingsOptimisticResponse} ${'enable ttl policies'} | ${findEnableTtlPoliciesToggle} | ${dependencyProxyUpdateTllPolicyMutationMock} | ${updateDependencyProxyImageTtlGroupPolicyOptimisticResponse} - `('$toggleName settings update ', ({ optimisticResponse, toggleFinder, localErrorMock }) => { + `('$toggleName settings update', ({ optimisticResponse, toggleFinder, localErrorMock }) => { describe('success state', () => { it('emits a success event', async () => { 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 deleted file mode 100644 index 3eecdeb5b1f..00000000000 --- a/spec/frontend/packages_and_registries/settings/group/components/duplicates_settings_spec.js +++ /dev/null @@ -1,143 +0,0 @@ -import { GlSprintf, GlToggle, GlFormGroup, GlFormInput } from '@gitlab/ui'; -import { shallowMount } from '@vue/test-utils'; -import component from '~/packages_and_registries/settings/group/components/duplicates_settings.vue'; - -import { - DUPLICATES_TOGGLE_LABEL, - DUPLICATES_SETTING_EXCEPTION_TITLE, - DUPLICATES_SETTINGS_EXCEPTION_LEGEND, -} from '~/packages_and_registries/settings/group/constants'; - -describe('Duplicates Settings', () => { - let wrapper; - - const defaultProps = { - duplicatesAllowed: false, - duplicateExceptionRegex: 'foo', - modelNames: { - allowed: 'allowedModel', - exception: 'exceptionModel', - }, - }; - - const mountComponent = (propsData = defaultProps) => { - wrapper = shallowMount(component, { - propsData, - stubs: { - GlSprintf, - }, - }); - }; - - afterEach(() => { - wrapper.destroy(); - }); - - const findToggle = () => wrapper.findComponent(GlToggle); - - const findInputGroup = () => wrapper.findComponent(GlFormGroup); - const findInput = () => wrapper.findComponent(GlFormInput); - - it('has a toggle', () => { - mountComponent(); - - expect(findToggle().exists()).toBe(true); - expect(findToggle().props()).toMatchObject({ - label: DUPLICATES_TOGGLE_LABEL, - value: !defaultProps.duplicatesAllowed, - }); - }); - - it('toggle emits an update event', () => { - mountComponent(); - - findToggle().vm.$emit('change', false); - - expect(wrapper.emitted('update')).toStrictEqual([ - [{ [defaultProps.modelNames.allowed]: true }], - ]); - }); - - describe('when the duplicates are disabled', () => { - it('shows a form group with an input field', () => { - mountComponent(); - - expect(findInputGroup().exists()).toBe(true); - - expect(findInputGroup().attributes()).toMatchObject({ - 'label-for': 'maven-duplicated-settings-regex-input', - label: DUPLICATES_SETTING_EXCEPTION_TITLE, - description: DUPLICATES_SETTINGS_EXCEPTION_LEGEND, - }); - }); - - it('shows an input field', () => { - mountComponent(); - - expect(findInput().exists()).toBe(true); - - expect(findInput().attributes()).toMatchObject({ - id: 'maven-duplicated-settings-regex-input', - value: defaultProps.duplicateExceptionRegex, - }); - }); - - it('input change event emits an update event', () => { - mountComponent(); - - findInput().vm.$emit('change', 'bar'); - - expect(wrapper.emitted('update')).toStrictEqual([ - [{ [defaultProps.modelNames.exception]: 'bar' }], - ]); - }); - - describe('valid state', () => { - it('form group has correct props', () => { - mountComponent(); - - expect(findInputGroup().attributes()).toMatchObject({ - state: 'true', - 'invalid-feedback': '', - }); - }); - }); - - describe('invalid state', () => { - it('form group has correct props', () => { - const propsWithError = { - ...defaultProps, - duplicateExceptionRegexError: 'some error string', - }; - - mountComponent(propsWithError); - - expect(findInputGroup().attributes()).toMatchObject({ - 'invalid-feedback': propsWithError.duplicateExceptionRegexError, - }); - }); - }); - }); - - describe('when the duplicates are enabled', () => { - it('hides the form input group', () => { - mountComponent({ ...defaultProps, duplicatesAllowed: true }); - - expect(findInputGroup().exists()).toBe(false); - }); - }); - - describe('loading', () => { - beforeEach(() => { - mountComponent({ ...defaultProps, loading: true }); - }); - - it('disables the enable toggle', () => { - expect(findToggle().props('disabled')).toBe(true); - }); - - it('disables the form input', () => { - expect(findInput().attributes('disabled')).toBe('true'); - }); - }); -}); diff --git a/spec/frontend/packages_and_registries/settings/group/components/exceptions_input_spec.js b/spec/frontend/packages_and_registries/settings/group/components/exceptions_input_spec.js new file mode 100644 index 00000000000..86f14961690 --- /dev/null +++ b/spec/frontend/packages_and_registries/settings/group/components/exceptions_input_spec.js @@ -0,0 +1,108 @@ +import { GlSprintf, GlFormGroup, GlFormInput } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; +import component from '~/packages_and_registries/settings/group/components/exceptions_input.vue'; + +import { DUPLICATES_SETTING_EXCEPTION_TITLE } from '~/packages_and_registries/settings/group/constants'; + +describe('Exceptions Input', () => { + let wrapper; + + const defaultProps = { + duplicatesAllowed: false, + duplicateExceptionRegex: 'foo', + id: 'maven-duplicated-settings-regex-input', + name: 'exceptionModel', + }; + + const mountComponent = (propsData = defaultProps) => { + wrapper = shallowMount(component, { + propsData, + stubs: { + GlSprintf, + }, + }); + }; + + afterEach(() => { + wrapper.destroy(); + }); + + const findInputGroup = () => wrapper.findComponent(GlFormGroup); + const findInput = () => wrapper.findComponent(GlFormInput); + + it('shows a form group with an input field', () => { + mountComponent(); + + expect(findInputGroup().exists()).toBe(true); + + expect(findInputGroup().attributes()).toMatchObject({ + 'label-for': defaultProps.id, + label: DUPLICATES_SETTING_EXCEPTION_TITLE, + 'label-sr-only': '', + }); + }); + + it('shows an input field', () => { + mountComponent(); + + expect(findInput().exists()).toBe(true); + + expect(findInput().attributes()).toMatchObject({ + id: 'maven-duplicated-settings-regex-input', + value: defaultProps.duplicateExceptionRegex, + }); + }); + + it('input change event emits an update event', () => { + mountComponent(); + + findInput().vm.$emit('change', 'bar'); + + expect(wrapper.emitted('update')).toStrictEqual([[{ [defaultProps.name]: 'bar' }]]); + }); + + describe('valid state', () => { + beforeEach(() => { + mountComponent(); + }); + + it('form group has correct props', () => { + expect(findInputGroup().attributes('input-feedback')).toBeUndefined(); + }); + + it('form input has correct props', () => { + expect(findInput().attributes('state')).toBe('true'); + }); + }); + + describe('invalid state', () => { + const propsWithError = { + ...defaultProps, + duplicateExceptionRegexError: 'some error string', + }; + + beforeEach(() => { + mountComponent(propsWithError); + }); + + it('form group has correct props', () => { + expect(findInputGroup().attributes('invalid-feedback')).toBe( + propsWithError.duplicateExceptionRegexError, + ); + }); + + it('form input has correct props', () => { + expect(findInput().attributes('state')).toBeUndefined(); + }); + }); + + describe('loading', () => { + beforeEach(() => { + mountComponent({ ...defaultProps, loading: true }); + }); + + it('disables the form input', () => { + expect(findInput().attributes('disabled')).toBe('true'); + }); + }); +}); diff --git a/spec/frontend/packages_and_registries/settings/group/components/generic_settings_spec.js b/spec/frontend/packages_and_registries/settings/group/components/generic_settings_spec.js deleted file mode 100644 index 4eafeedd55e..00000000000 --- a/spec/frontend/packages_and_registries/settings/group/components/generic_settings_spec.js +++ /dev/null @@ -1,54 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import GenericSettings from '~/packages_and_registries/settings/group/components/generic_settings.vue'; -import SettingsTitles from '~/packages_and_registries/settings/group/components/settings_titles.vue'; - -describe('generic_settings', () => { - let wrapper; - - const mountComponent = () => { - wrapper = shallowMount(GenericSettings, { - scopedSlots: { - default: '<div data-testid="default-slot">{{props.modelNames}}</div>', - }, - }); - }; - - const findSettingsTitle = () => wrapper.findComponent(SettingsTitles); - const findDefaultSlot = () => wrapper.find('[data-testid="default-slot"]'); - - afterEach(() => { - wrapper.destroy(); - }); - - describe('title component', () => { - it('has a title component', () => { - mountComponent(); - - expect(findSettingsTitle().exists()).toBe(true); - }); - - it('passes the correct props', () => { - mountComponent(); - - expect(findSettingsTitle().props()).toMatchObject({ - title: 'Generic', - subTitle: 'Settings for Generic packages', - }); - }); - }); - - describe('default slot', () => { - it('accept a default slots', () => { - mountComponent(); - - expect(findDefaultSlot().exists()).toBe(true); - }); - - it('binds model names', () => { - mountComponent(); - - expect(findDefaultSlot().text()).toContain('genericDuplicatesAllowed'); - expect(findDefaultSlot().text()).toContain('genericDuplicateExceptionRegex'); - }); - }); -}); diff --git a/spec/frontend/packages_and_registries/settings/group/components/maven_settings_spec.js b/spec/frontend/packages_and_registries/settings/group/components/maven_settings_spec.js deleted file mode 100644 index 22644b97b43..00000000000 --- a/spec/frontend/packages_and_registries/settings/group/components/maven_settings_spec.js +++ /dev/null @@ -1,54 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import MavenSettings from '~/packages_and_registries/settings/group/components/maven_settings.vue'; -import SettingsTitles from '~/packages_and_registries/settings/group/components/settings_titles.vue'; - -describe('maven_settings', () => { - let wrapper; - - const mountComponent = () => { - wrapper = shallowMount(MavenSettings, { - scopedSlots: { - default: '<div data-testid="default-slot">{{props.modelNames}}</div>', - }, - }); - }; - - const findSettingsTitle = () => wrapper.findComponent(SettingsTitles); - const findDefaultSlot = () => wrapper.find('[data-testid="default-slot"]'); - - afterEach(() => { - wrapper.destroy(); - }); - - describe('title component', () => { - it('has a title component', () => { - mountComponent(); - - expect(findSettingsTitle().exists()).toBe(true); - }); - - it('passes the correct props', () => { - mountComponent(); - - expect(findSettingsTitle().props()).toMatchObject({ - title: 'Maven', - subTitle: 'Settings for Maven packages', - }); - }); - }); - - describe('default slot', () => { - it('accept a default slots', () => { - mountComponent(); - - expect(findDefaultSlot().exists()).toBe(true); - }); - - it('binds model names', () => { - mountComponent(); - - expect(findDefaultSlot().text()).toContain('mavenDuplicatesAllowed'); - expect(findDefaultSlot().text()).toContain('mavenDuplicateExceptionRegex'); - }); - }); -}); 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 274930ce668..13eba39ec8c 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,13 +1,13 @@ import Vue, { nextTick } from 'vue'; +import { GlToggle } from '@gitlab/ui'; import VueApollo from 'vue-apollo'; -import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper'; import createMockApollo from 'helpers/mock_apollo_helper'; import waitForPromises from 'helpers/wait_for_promises'; -import DuplicatesSettings from '~/packages_and_registries/settings/group/components/duplicates_settings.vue'; -import GenericSettings from '~/packages_and_registries/settings/group/components/generic_settings.vue'; +import ExceptionsInput from '~/packages_and_registries/settings/group/components/exceptions_input.vue'; import component from '~/packages_and_registries/settings/group/components/packages_settings.vue'; -import MavenSettings from '~/packages_and_registries/settings/group/components/maven_settings.vue'; import { + DUPLICATES_TOGGLE_LABEL, PACKAGE_SETTINGS_HEADER, PACKAGE_SETTINGS_DESCRIPTION, } from '~/packages_and_registries/settings/group/constants'; @@ -35,6 +35,7 @@ describe('Packages Settings', () => { }; const mountComponent = ({ + mountFn = shallowMountExtended, mutationResolver = jest.fn().mockResolvedValue(groupPackageSettingsMutationMock()), } = {}) => { Vue.use(VueApollo); @@ -43,7 +44,7 @@ describe('Packages Settings', () => { apolloProvider = createMockApollo(requestHandlers); - wrapper = shallowMountExtended(component, { + wrapper = mountFn(component, { apolloProvider, provide: defaultProvide, propsData: { @@ -51,8 +52,6 @@ describe('Packages Settings', () => { }, stubs: { SettingsBlock, - MavenSettings, - GenericSettings, }, }); }; @@ -63,11 +62,15 @@ describe('Packages Settings', () => { const findSettingsBlock = () => wrapper.findComponent(SettingsBlock); const findDescription = () => wrapper.findByTestId('description'); - const findMavenSettings = () => wrapper.findComponent(MavenSettings); - const findMavenDuplicatedSettings = () => findMavenSettings().findComponent(DuplicatesSettings); - const findGenericSettings = () => wrapper.findComponent(GenericSettings); - const findGenericDuplicatedSettings = () => - findGenericSettings().findComponent(DuplicatesSettings); + const findMavenSettings = () => wrapper.findByTestId('maven-settings'); + const findGenericSettings = () => wrapper.findByTestId('generic-settings'); + + const findMavenDuplicatedSettingsToggle = () => findMavenSettings().findComponent(GlToggle); + const findGenericDuplicatedSettingsToggle = () => findGenericSettings().findComponent(GlToggle); + const findMavenDuplicatedSettingsExceptionsInput = () => + findMavenSettings().findComponent(ExceptionsInput); + const findGenericDuplicatedSettingsExceptionsInput = () => + findGenericSettings().findComponent(ExceptionsInput); const fillApolloCache = () => { apolloProvider.defaultClient.cache.writeQuery({ @@ -80,7 +83,7 @@ describe('Packages Settings', () => { }; const emitMavenSettingsUpdate = (override) => { - findMavenDuplicatedSettings().vm.$emit('update', { + findGenericDuplicatedSettingsExceptionsInput().vm.$emit('update', { mavenDuplicateExceptionRegex: ')', ...override, }); @@ -106,27 +109,46 @@ describe('Packages Settings', () => { describe('maven settings', () => { it('exists', () => { - mountComponent(); + mountComponent({ mountFn: mountExtended }); + + expect(findMavenSettings().find('td').text()).toBe('Maven'); + }); + + it('renders toggle', () => { + mountComponent({ mountFn: mountExtended }); - expect(findMavenSettings().exists()).toBe(true); + const { mavenDuplicatesAllowed } = packageSettings(); + + expect(findMavenDuplicatedSettingsToggle().exists()).toBe(true); + + expect(findMavenDuplicatedSettingsToggle().props()).toMatchObject({ + label: DUPLICATES_TOGGLE_LABEL, + value: mavenDuplicatesAllowed, + disabled: false, + labelPosition: 'hidden', + }); }); - it('assigns duplication allowness and exception props', async () => { - mountComponent(); + it('renders ExceptionsInput and assigns duplication allowness and exception props', () => { + mountComponent({ mountFn: mountExtended }); const { mavenDuplicatesAllowed, mavenDuplicateExceptionRegex } = packageSettings(); - expect(findMavenDuplicatedSettings().props()).toMatchObject({ + expect(findMavenDuplicatedSettingsExceptionsInput().exists()).toBe(true); + + expect(findMavenDuplicatedSettingsExceptionsInput().props()).toMatchObject({ duplicatesAllowed: mavenDuplicatesAllowed, duplicateExceptionRegex: mavenDuplicateExceptionRegex, duplicateExceptionRegexError: '', loading: false, + name: 'mavenDuplicateExceptionRegex', + id: 'maven-duplicated-settings-regex-input', }); }); it('on update event calls the mutation', () => { const mutationResolver = jest.fn().mockResolvedValue(groupPackageSettingsMutationMock()); - mountComponent({ mutationResolver }); + mountComponent({ mountFn: mountExtended, mutationResolver }); fillApolloCache(); @@ -140,31 +162,47 @@ describe('Packages Settings', () => { describe('generic settings', () => { it('exists', () => { - mountComponent(); + mountComponent({ mountFn: mountExtended }); - expect(findGenericSettings().exists()).toBe(true); + expect(findGenericSettings().find('td').text()).toBe('Generic'); }); - it('assigns duplication allowness and exception props', async () => { - mountComponent(); + it('renders toggle', () => { + mountComponent({ mountFn: mountExtended }); + + const { genericDuplicatesAllowed } = packageSettings(); + + expect(findGenericDuplicatedSettingsToggle().exists()).toBe(true); + expect(findGenericDuplicatedSettingsToggle().props()).toMatchObject({ + label: DUPLICATES_TOGGLE_LABEL, + value: genericDuplicatesAllowed, + disabled: false, + labelPosition: 'hidden', + }); + }); + + it('renders ExceptionsInput and assigns duplication allowness and exception props', async () => { + mountComponent({ mountFn: mountExtended }); const { genericDuplicatesAllowed, genericDuplicateExceptionRegex } = packageSettings(); - expect(findGenericDuplicatedSettings().props()).toMatchObject({ + expect(findGenericDuplicatedSettingsExceptionsInput().props()).toMatchObject({ duplicatesAllowed: genericDuplicatesAllowed, duplicateExceptionRegex: genericDuplicateExceptionRegex, duplicateExceptionRegexError: '', loading: false, + name: 'genericDuplicateExceptionRegex', + id: 'generic-duplicated-settings-regex-input', }); }); it('on update event calls the mutation', async () => { const mutationResolver = jest.fn().mockResolvedValue(groupPackageSettingsMutationMock()); - mountComponent({ mutationResolver }); + mountComponent({ mountFn: mountExtended, mutationResolver }); fillApolloCache(); - findMavenDuplicatedSettings().vm.$emit('update', { + findGenericDuplicatedSettingsExceptionsInput().vm.$emit('update', { genericDuplicateExceptionRegex: ')', }); @@ -176,9 +214,11 @@ describe('Packages Settings', () => { describe('settings update', () => { describe('success state', () => { - it('emits a success event', async () => { - mountComponent(); + beforeEach(() => { + mountComponent({ mountFn: mountExtended }); + }); + it('emits a success event', async () => { fillApolloCache(); emitMavenSettingsUpdate(); @@ -189,11 +229,12 @@ describe('Packages Settings', () => { it('has an optimistic response', () => { const mavenDuplicateExceptionRegex = 'latest[main]something'; - mountComponent(); fillApolloCache(); - expect(findMavenDuplicatedSettings().props('duplicateExceptionRegex')).toBe(''); + expect( + findGenericDuplicatedSettingsExceptionsInput().props('duplicateExceptionRegex'), + ).toBe(''); emitMavenSettingsUpdate({ mavenDuplicateExceptionRegex }); @@ -209,7 +250,7 @@ describe('Packages Settings', () => { // note this is a complex test that covers all the path around errors that are shown in the form // it's one single it case, due to the expensive preparation and execution const mutationResolver = jest.fn().mockResolvedValue(groupPackageSettingsMutationErrorMock); - mountComponent({ mutationResolver }); + mountComponent({ mountFn: mountExtended, mutationResolver }); fillApolloCache(); @@ -218,9 +259,9 @@ describe('Packages Settings', () => { await waitForPromises(); // errors are bound to the component - expect(findMavenDuplicatedSettings().props('duplicateExceptionRegexError')).toBe( - groupPackageSettingsMutationErrorMock.errors[0].extensions.problems[0].message, - ); + expect( + findMavenDuplicatedSettingsExceptionsInput().props('duplicateExceptionRegexError'), + ).toBe(groupPackageSettingsMutationErrorMock.errors[0].extensions.problems[0].message); // general error message is shown @@ -231,7 +272,9 @@ describe('Packages Settings', () => { await nextTick(); // errors are reset on mutation call - expect(findMavenDuplicatedSettings().props('duplicateExceptionRegexError')).toBe(''); + expect( + findMavenDuplicatedSettingsExceptionsInput().props('duplicateExceptionRegexError'), + ).toBe(''); }); it.each` @@ -239,7 +282,7 @@ describe('Packages Settings', () => { ${'local'} | ${jest.fn().mockResolvedValue(groupPackageSettingsMutationMock({ errors: ['foo'] }))} ${'network'} | ${jest.fn().mockRejectedValue()} `('mutation payload with $type error', async ({ mutationResolver }) => { - mountComponent({ mutationResolver }); + mountComponent({ mountFn: mountExtended, mutationResolver }); fillApolloCache(); emitMavenSettingsUpdate(); diff --git a/spec/frontend/packages_and_registries/settings/group/components/settings_titles_spec.js b/spec/frontend/packages_and_registries/settings/group/components/settings_titles_spec.js deleted file mode 100644 index fcfad4b42b8..00000000000 --- a/spec/frontend/packages_and_registries/settings/group/components/settings_titles_spec.js +++ /dev/null @@ -1,35 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import SettingsTitles from '~/packages_and_registries/settings/group/components/settings_titles.vue'; - -describe('settings_titles', () => { - let wrapper; - - const defaultProps = { - title: 'foo', - subTitle: 'bar', - }; - - const mountComponent = (propsData = defaultProps) => { - wrapper = shallowMount(SettingsTitles, { - propsData, - }); - }; - - const findSubTitle = () => wrapper.find('p'); - - afterEach(() => { - wrapper.destroy(); - }); - - it('renders properly', () => { - mountComponent(); - - expect(wrapper.element).toMatchSnapshot(); - }); - - it('does not render the subtitle paragraph when no subtitle is passed', () => { - mountComponent({ title: defaultProps.title }); - - expect(findSubTitle().exists()).toBe(false); - }); -}); diff --git a/spec/frontend/packages_and_registries/settings/project/settings/components/cleanup_image_tags_spec.js b/spec/frontend/packages_and_registries/settings/project/settings/components/cleanup_image_tags_spec.js new file mode 100644 index 00000000000..8b60f31512b --- /dev/null +++ b/spec/frontend/packages_and_registries/settings/project/settings/components/cleanup_image_tags_spec.js @@ -0,0 +1,164 @@ +import { GlAlert, GlSprintf, GlLink } from '@gitlab/ui'; +import Vue from 'vue'; +import VueApollo from 'vue-apollo'; +import createMockApollo from 'helpers/mock_apollo_helper'; +import waitForPromises from 'helpers/wait_for_promises'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import component from '~/packages_and_registries/settings/project/components/cleanup_image_tags.vue'; +import ContainerExpirationPolicyForm from '~/packages_and_registries/settings/project/components/container_expiration_policy_form.vue'; +import { + CONTAINER_CLEANUP_POLICY_TITLE, + CONTAINER_CLEANUP_POLICY_DESCRIPTION, + FETCH_SETTINGS_ERROR_MESSAGE, + UNAVAILABLE_FEATURE_INTRO_TEXT, + UNAVAILABLE_USER_FEATURE_TEXT, +} from '~/packages_and_registries/settings/project/constants'; +import expirationPolicyQuery from '~/packages_and_registries/settings/project/graphql/queries/get_expiration_policy.query.graphql'; + +import { + expirationPolicyPayload, + emptyExpirationPolicyPayload, + containerExpirationPolicyData, +} from '../mock_data'; + +describe('Cleanup image tags project settings', () => { + let wrapper; + let fakeApollo; + + const defaultProvidedValues = { + projectPath: 'path', + isAdmin: false, + adminSettingsPath: 'settingsPath', + enableHistoricEntries: false, + helpPagePath: 'helpPagePath', + showCleanupPolicyLink: false, + }; + + const findFormComponent = () => wrapper.findComponent(ContainerExpirationPolicyForm); + const findAlert = () => wrapper.findComponent(GlAlert); + const findTitle = () => wrapper.findByTestId('title'); + const findDescription = () => wrapper.findByTestId('description'); + + const mountComponent = (provide = defaultProvidedValues, config) => { + wrapper = shallowMountExtended(component, { + stubs: { + GlSprintf, + }, + provide, + ...config, + }); + }; + + const mountComponentWithApollo = ({ provide = defaultProvidedValues, resolver } = {}) => { + Vue.use(VueApollo); + + const requestHandlers = [[expirationPolicyQuery, resolver]]; + + fakeApollo = createMockApollo(requestHandlers); + mountComponent(provide, { + apolloProvider: fakeApollo, + }); + }; + + afterEach(() => { + wrapper.destroy(); + }); + + describe('isEdited status', () => { + it.each` + description | apiResponse | workingCopy | result + ${'empty response and no changes from user'} | ${emptyExpirationPolicyPayload()} | ${{}} | ${false} + ${'empty response and changes from user'} | ${emptyExpirationPolicyPayload()} | ${{ enabled: true }} | ${true} + ${'response and no changes'} | ${expirationPolicyPayload()} | ${containerExpirationPolicyData()} | ${false} + ${'response and changes'} | ${expirationPolicyPayload()} | ${{ ...containerExpirationPolicyData(), nameRegex: '12345' }} | ${true} + ${'response and empty'} | ${expirationPolicyPayload()} | ${{}} | ${true} + `('$description', async ({ apiResponse, workingCopy, result }) => { + mountComponentWithApollo({ + provide: { ...defaultProvidedValues, enableHistoricEntries: true }, + resolver: jest.fn().mockResolvedValue(apiResponse), + }); + await waitForPromises(); + + findFormComponent().vm.$emit('input', workingCopy); + + await waitForPromises(); + + expect(findFormComponent().props('isEdited')).toBe(result); + }); + }); + + it('renders the setting form', async () => { + mountComponentWithApollo({ + resolver: jest.fn().mockResolvedValue(expirationPolicyPayload()), + }); + await waitForPromises(); + + expect(findFormComponent().exists()).toBe(true); + expect(findTitle().text()).toMatchInterpolatedText(CONTAINER_CLEANUP_POLICY_TITLE); + expect(findDescription().text()).toMatchInterpolatedText(CONTAINER_CLEANUP_POLICY_DESCRIPTION); + }); + + describe('the form is disabled', () => { + it('hides the form', () => { + mountComponent(); + + expect(findFormComponent().exists()).toBe(false); + }); + + it('shows an alert', () => { + mountComponent(); + + const text = findAlert().text(); + expect(text).toContain(UNAVAILABLE_FEATURE_INTRO_TEXT); + expect(text).toContain(UNAVAILABLE_USER_FEATURE_TEXT); + }); + + describe('an admin is visiting the page', () => { + it('shows the admin part of the alert message', () => { + mountComponent({ ...defaultProvidedValues, isAdmin: true }); + + const sprintf = findAlert().findComponent(GlSprintf); + expect(sprintf.text()).toBe('administration settings'); + expect(sprintf.findComponent(GlLink).attributes('href')).toBe( + defaultProvidedValues.adminSettingsPath, + ); + }); + }); + }); + + describe('fetchSettingsError', () => { + beforeEach(async () => { + mountComponentWithApollo({ + resolver: jest.fn().mockRejectedValue(new Error('GraphQL error')), + }); + await waitForPromises(); + }); + + it('hides the form', () => { + expect(findFormComponent().exists()).toBe(false); + }); + + it('shows an alert', () => { + expect(findAlert().html()).toContain(FETCH_SETTINGS_ERROR_MESSAGE); + }); + }); + + describe('empty API response', () => { + it.each` + enableHistoricEntries | isShown + ${true} | ${true} + ${false} | ${false} + `('is $isShown that the form is shown', async ({ enableHistoricEntries, isShown }) => { + mountComponentWithApollo({ + provide: { + ...defaultProvidedValues, + enableHistoricEntries, + }, + resolver: jest.fn().mockResolvedValue(emptyExpirationPolicyPayload()), + }); + await waitForPromises(); + + expect(findFormComponent().exists()).toBe(isShown); + }); + }); +}); diff --git a/spec/frontend/packages_and_registries/settings/project/settings/components/container_expiration_policy_form_spec.js b/spec/frontend/packages_and_registries/settings/project/settings/components/container_expiration_policy_form_spec.js index ca44e77e694..8e08864bdb8 100644 --- a/spec/frontend/packages_and_registries/settings/project/settings/components/container_expiration_policy_form_spec.js +++ b/spec/frontend/packages_and_registries/settings/project/settings/components/container_expiration_policy_form_spec.js @@ -2,13 +2,11 @@ import { shallowMount } from '@vue/test-utils'; import VueApollo from 'vue-apollo'; import Vue, { nextTick } from 'vue'; import createMockApollo from 'helpers/mock_apollo_helper'; +import { useMockLocationHelper } from 'helpers/mock_window_location_helper'; import waitForPromises from 'helpers/wait_for_promises'; import { GlCard, GlLoadingIcon } from 'jest/packages_and_registries/shared/stubs'; import component from '~/packages_and_registries/settings/project/components/container_expiration_policy_form.vue'; -import { - UPDATE_SETTINGS_ERROR_MESSAGE, - UPDATE_SETTINGS_SUCCESS_MESSAGE, -} from '~/packages_and_registries/settings/project/constants'; +import { UPDATE_SETTINGS_ERROR_MESSAGE } from '~/packages_and_registries/settings/project/constants'; import updateContainerExpirationPolicyMutation from '~/packages_and_registries/settings/project/graphql/mutations/update_container_expiration_policy.mutation.graphql'; import expirationPolicyQuery from '~/packages_and_registries/settings/project/graphql/queries/get_expiration_policy.query.graphql'; import Tracking from '~/tracking'; @@ -20,6 +18,7 @@ describe('Container Expiration Policy Settings Form', () => { const defaultProvidedValues = { projectPath: 'path', + projectSettingsPath: 'settings-path', }; const { @@ -36,7 +35,7 @@ describe('Container Expiration Policy Settings Form', () => { label: 'docker_container_retention_and_expiration_policies', }; - const findForm = () => wrapper.find({ ref: 'form-element' }); + const findForm = () => wrapper.find('form'); const findCancelButton = () => wrapper.find('[data-testid="cancel-button"'); const findSaveButton = () => wrapper.find('[data-testid="save-button"'); @@ -208,7 +207,9 @@ describe('Container Expiration Policy Settings Form', () => { }); it('validation event updates buttons disabled state', async () => { - mountComponent(); + mountComponent({ + props: { ...defaultProps, isEdited: true }, + }); expect(findSaveButton().props('disabled')).toBe(false); @@ -229,52 +230,22 @@ describe('Container Expiration Policy Settings Form', () => { }); describe('form', () => { - describe('form reset event', () => { - it('calls the appropriate function', () => { - mountComponent(); - - findForm().trigger('reset'); - - expect(wrapper.emitted('reset')).toEqual([[]]); - }); - - it('tracks the reset event', () => { - mountComponent(); - - findForm().trigger('reset'); - - expect(Tracking.event).toHaveBeenCalledWith(undefined, 'reset_form', trackingPayload); - }); - - it('resets the errors objects', async () => { - mountComponent({ - data: { apiErrors: { nameRegex: 'bar' }, localErrors: { nameRegexKeep: false } }, - }); - - findForm().trigger('reset'); - - await nextTick(); + describe('form submit event', () => { + useMockLocationHelper(); - expect(findKeepRegexInput().props('error')).toBe(''); - expect(findRemoveRegexInput().props('error')).toBe(''); - expect(findSaveButton().props('disabled')).toBe(false); - }); - }); - - describe('form submit event ', () => { it('save has type submit', () => { mountComponent(); expect(findSaveButton().attributes('type')).toBe('submit'); }); - it('dispatches the correct apollo mutation', () => { + it('dispatches the correct apollo mutation', async () => { const mutationResolver = jest.fn().mockResolvedValue(expirationPolicyMutationPayload()); mountComponentWithApollo({ mutationResolver, }); - findForm().trigger('submit'); + await submitForm(); expect(mutationResolver).toHaveBeenCalled(); }); @@ -286,9 +257,7 @@ describe('Container Expiration Policy Settings Form', () => { queryPayload: expirationPolicyPayload({ keepN: null, cadence: null, olderThan: null }), }); - await waitForPromises(); - - findForm().trigger('submit'); + await submitForm(); expect(mutationResolver).toHaveBeenCalledWith({ input: { @@ -303,24 +272,26 @@ describe('Container Expiration Policy Settings Form', () => { }); }); - it('tracks the submit event', () => { + it('tracks the submit event', async () => { mountComponentWithApollo({ mutationResolver: jest.fn().mockResolvedValue(expirationPolicyMutationPayload()), }); - findForm().trigger('submit'); + await submitForm(); expect(Tracking.event).toHaveBeenCalledWith(undefined, 'submit_form', trackingPayload); }); - it('show a success toast when submit succeed', async () => { + it('redirects to package and registry project settings page when submitted successfully', async () => { mountComponentWithApollo({ mutationResolver: jest.fn().mockResolvedValue(expirationPolicyMutationPayload()), }); await submitForm(); - expect(wrapper.vm.$toast.show).toHaveBeenCalledWith(UPDATE_SETTINGS_SUCCESS_MESSAGE); + expect(window.location.href.endsWith('settings-path?showSetupSuccessAlert=true')).toBe( + true, + ); }); describe('when submit fails', () => { @@ -348,6 +319,7 @@ describe('Container Expiration Policy Settings Form', () => { await submitForm(); expect(wrapper.vm.$toast.show).toHaveBeenCalledWith(UPDATE_SETTINGS_ERROR_MESSAGE); + expect(window.location.href).toBeUndefined(); }); it('parses the error messages', async () => { @@ -375,24 +347,24 @@ describe('Container Expiration Policy Settings Form', () => { describe('form actions', () => { describe('cancel button', () => { - it('has type reset', () => { + it('links to project package and registry settings path', () => { mountComponent(); - expect(findCancelButton().attributes('type')).toBe('reset'); + expect(findCancelButton().attributes('href')).toBe( + defaultProvidedValues.projectSettingsPath, + ); }); it.each` - isLoading | isEdited | mutationLoading - ${true} | ${true} | ${true} - ${false} | ${true} | ${true} - ${false} | ${false} | ${true} - ${true} | ${false} | ${false} - ${false} | ${false} | ${false} + isLoading | mutationLoading + ${true} | ${true} + ${false} | ${true} + ${true} | ${false} `( - 'when isLoading is $isLoading, isEdited is $isEdited and mutationLoading is $mutationLoading is disabled', - ({ isEdited, isLoading, mutationLoading }) => { + 'is disabled when isLoading is $isLoading and mutationLoading is $mutationLoading', + ({ isLoading, mutationLoading }) => { mountComponent({ - props: { ...defaultProps, isEdited, isLoading }, + props: { ...defaultProps, isLoading }, data: { mutationLoading }, }); @@ -409,18 +381,19 @@ describe('Container Expiration Policy Settings Form', () => { }); it.each` - isLoading | localErrors | mutationLoading - ${true} | ${{}} | ${true} - ${true} | ${{}} | ${false} - ${false} | ${{}} | ${true} - ${false} | ${{ foo: false }} | ${true} - ${true} | ${{ foo: false }} | ${false} - ${false} | ${{ foo: false }} | ${false} + isLoading | isEdited | localErrors | mutationLoading + ${true} | ${false} | ${{}} | ${true} + ${true} | ${false} | ${{}} | ${false} + ${false} | ${false} | ${{}} | ${true} + ${false} | ${false} | ${{}} | ${false} + ${false} | ${false} | ${{ foo: false }} | ${true} + ${true} | ${false} | ${{ foo: false }} | ${false} + ${false} | ${false} | ${{ foo: false }} | ${false} `( - 'when isLoading is $isLoading, localErrors is $localErrors and mutationLoading is $mutationLoading is disabled', - ({ localErrors, isLoading, mutationLoading }) => { + 'is disabled when isLoading is $isLoading, isEdited is $isEdited, localErrors is $localErrors and mutationLoading is $mutationLoading', + ({ localErrors, isEdited, isLoading, mutationLoading }) => { mountComponent({ - props: { ...defaultProps, isLoading }, + props: { ...defaultProps, isEdited, isLoading }, data: { mutationLoading, localErrors }, }); diff --git a/spec/frontend/packages_and_registries/settings/project/settings/components/container_expiration_policy_spec.js b/spec/frontend/packages_and_registries/settings/project/settings/components/container_expiration_policy_spec.js index d83c717da6a..35baeaeac61 100644 --- a/spec/frontend/packages_and_registries/settings/project/settings/components/container_expiration_policy_spec.js +++ b/spec/frontend/packages_and_registries/settings/project/settings/components/container_expiration_policy_spec.js @@ -1,12 +1,14 @@ -import { GlAlert, GlSprintf, GlLink } from '@gitlab/ui'; -import { shallowMount } from '@vue/test-utils'; +import { GlAlert, GlSprintf, GlLink, GlCard } from '@gitlab/ui'; import Vue from 'vue'; import VueApollo from 'vue-apollo'; import createMockApollo from 'helpers/mock_apollo_helper'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import waitForPromises from 'helpers/wait_for_promises'; import component from '~/packages_and_registries/settings/project/components/container_expiration_policy.vue'; -import ContainerExpirationPolicyForm from '~/packages_and_registries/settings/project/components/container_expiration_policy_form.vue'; import { + CONTAINER_CLEANUP_POLICY_EDIT_RULES, + CONTAINER_CLEANUP_POLICY_SET_RULES, + CONTAINER_CLEANUP_POLICY_RULES_DESCRIPTION, FETCH_SETTINGS_ERROR_MESSAGE, UNAVAILABLE_FEATURE_INTRO_TEXT, UNAVAILABLE_USER_FEATURE_TEXT, @@ -14,11 +16,7 @@ import { import expirationPolicyQuery from '~/packages_and_registries/settings/project/graphql/queries/get_expiration_policy.query.graphql'; import SettingsBlock from '~/vue_shared/components/settings/settings_block.vue'; -import { - expirationPolicyPayload, - emptyExpirationPolicyPayload, - containerExpirationPolicyData, -} from '../mock_data'; +import { expirationPolicyPayload, emptyExpirationPolicyPayload } from '../mock_data'; describe('Container expiration policy project settings', () => { let wrapper; @@ -28,17 +26,19 @@ describe('Container expiration policy project settings', () => { projectPath: 'path', isAdmin: false, adminSettingsPath: 'settingsPath', + cleanupSettingsPath: 'cleanupSettingsPath', enableHistoricEntries: false, helpPagePath: 'helpPagePath', - showCleanupPolicyLink: false, }; - const findFormComponent = () => wrapper.find(ContainerExpirationPolicyForm); - const findAlert = () => wrapper.find(GlAlert); - const findSettingsBlock = () => wrapper.find(SettingsBlock); + const findFormComponent = () => wrapper.findComponent(GlCard); + const findDescription = () => wrapper.findByTestId('description'); + const findButton = () => wrapper.findByTestId('rules-button'); + const findAlert = () => wrapper.findComponent(GlAlert); + const findSettingsBlock = () => wrapper.findComponent(SettingsBlock); const mountComponent = (provide = defaultProvidedValues, config) => { - wrapper = shallowMount(component, { + wrapper = shallowMountExtended(component, { stubs: { GlSprintf, SettingsBlock, @@ -63,37 +63,19 @@ describe('Container expiration policy project settings', () => { wrapper.destroy(); }); - describe('isEdited status', () => { - it.each` - description | apiResponse | workingCopy | result - ${'empty response and no changes from user'} | ${emptyExpirationPolicyPayload()} | ${{}} | ${false} - ${'empty response and changes from user'} | ${emptyExpirationPolicyPayload()} | ${{ enabled: true }} | ${true} - ${'response and no changes'} | ${expirationPolicyPayload()} | ${containerExpirationPolicyData()} | ${false} - ${'response and changes'} | ${expirationPolicyPayload()} | ${{ ...containerExpirationPolicyData(), nameRegex: '12345' }} | ${true} - ${'response and empty'} | ${expirationPolicyPayload()} | ${{}} | ${true} - `('$description', async ({ apiResponse, workingCopy, result }) => { - mountComponentWithApollo({ - provide: { ...defaultProvidedValues, enableHistoricEntries: true }, - resolver: jest.fn().mockResolvedValue(apiResponse), - }); - await waitForPromises(); - - findFormComponent().vm.$emit('input', workingCopy); - - await waitForPromises(); - - expect(findFormComponent().props('isEdited')).toBe(result); - }); - }); - it('renders the setting form', async () => { mountComponentWithApollo({ resolver: jest.fn().mockResolvedValue(expirationPolicyPayload()), }); await waitForPromises(); - expect(findFormComponent().exists()).toBe(true); expect(findSettingsBlock().exists()).toBe(true); + expect(findFormComponent().exists()).toBe(true); + expect(findDescription().text()).toMatchInterpolatedText( + CONTAINER_CLEANUP_POLICY_RULES_DESCRIPTION, + ); + expect(findButton().text()).toMatchInterpolatedText(CONTAINER_CLEANUP_POLICY_EDIT_RULES); + expect(findButton().attributes('href')).toBe(defaultProvidedValues.cleanupSettingsPath); }); describe('the form is disabled', () => { @@ -115,9 +97,9 @@ describe('Container expiration policy project settings', () => { it('shows the admin part of the alert message', () => { mountComponent({ ...defaultProvidedValues, isAdmin: true }); - const sprintf = findAlert().find(GlSprintf); + const sprintf = findAlert().findComponent(GlSprintf); expect(sprintf.text()).toBe('administration settings'); - expect(sprintf.find(GlLink).attributes('href')).toBe( + expect(sprintf.findComponent(GlLink).attributes('href')).toBe( defaultProvidedValues.adminSettingsPath, ); }); @@ -157,6 +139,10 @@ describe('Container expiration policy project settings', () => { await waitForPromises(); expect(findFormComponent().exists()).toBe(isShown); + if (isShown) { + expect(findButton().text()).toMatchInterpolatedText(CONTAINER_CLEANUP_POLICY_SET_RULES); + expect(findButton().attributes('href')).toBe(defaultProvidedValues.cleanupSettingsPath); + } }); }); }); 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 8b99ac6b06c..ae41fdf65e0 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 @@ -14,8 +14,8 @@ describe('ExpirationDropdown', () => { ], }; - const findFormSelect = () => wrapper.find(GlFormSelect); - const findFormGroup = () => wrapper.find(GlFormGroup); + const findFormSelect = () => wrapper.findComponent(GlFormSelect); + const findFormGroup = () => wrapper.findComponent(GlFormGroup); const findDescription = () => wrapper.find('[data-testid="description"]'); const findOptions = () => wrapper.findAll('[data-testid="option"]'); diff --git a/spec/frontend/packages_and_registries/settings/project/settings/components/expiration_input_spec.js b/spec/frontend/packages_and_registries/settings/project/settings/components/expiration_input_spec.js index 6b681924fcf..1cea0704154 100644 --- a/spec/frontend/packages_and_registries/settings/project/settings/components/expiration_input_spec.js +++ b/spec/frontend/packages_and_registries/settings/project/settings/components/expiration_input_spec.js @@ -16,11 +16,11 @@ describe('ExpirationInput', () => { const tagsRegexHelpPagePath = 'fooPath'; - const findInput = () => wrapper.find(GlFormInput); - const findFormGroup = () => wrapper.find(GlFormGroup); + const findInput = () => wrapper.findComponent(GlFormInput); + const findFormGroup = () => wrapper.findComponent(GlFormGroup); const findLabel = () => wrapper.find('[data-testid="label"]'); const findDescription = () => wrapper.find('[data-testid="description"]'); - const findDescriptionLink = () => wrapper.find(GlLink); + const findDescriptionLink = () => wrapper.findComponent(GlLink); const mountComponent = (props) => { wrapper = shallowMount(component, { diff --git a/spec/frontend/packages_and_registries/settings/project/settings/components/expiration_run_text_spec.js b/spec/frontend/packages_and_registries/settings/project/settings/components/expiration_run_text_spec.js index 94f7783afe7..653f2a8b40e 100644 --- a/spec/frontend/packages_and_registries/settings/project/settings/components/expiration_run_text_spec.js +++ b/spec/frontend/packages_and_registries/settings/project/settings/components/expiration_run_text_spec.js @@ -11,8 +11,8 @@ describe('ExpirationToggle', () => { let wrapper; const value = 'foo'; - const findInput = () => wrapper.find(GlFormInput); - const findFormGroup = () => wrapper.find(GlFormGroup); + const findInput = () => wrapper.findComponent(GlFormInput); + const findFormGroup = () => wrapper.findComponent(GlFormGroup); const mountComponent = (propsData) => { wrapper = shallowMount(component, { diff --git a/spec/frontend/packages_and_registries/settings/project/settings/components/expiration_toggle_spec.js b/spec/frontend/packages_and_registries/settings/project/settings/components/expiration_toggle_spec.js index 45039614e49..55a66cebd83 100644 --- a/spec/frontend/packages_and_registries/settings/project/settings/components/expiration_toggle_spec.js +++ b/spec/frontend/packages_and_registries/settings/project/settings/components/expiration_toggle_spec.js @@ -10,7 +10,7 @@ import { describe('ExpirationToggle', () => { let wrapper; - const findToggle = () => wrapper.find(GlToggle); + const findToggle = () => wrapper.findComponent(GlToggle); const findDescription = () => wrapper.find('[data-testid="description"]'); const mountComponent = (propsData) => { 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 index 86f45d78bae..daf0ee85fdf 100644 --- 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 @@ -39,7 +39,7 @@ describe('Packages Cleanup Policy Settings Form', () => { label: 'packages_cleanup_policies', }; - const findForm = () => wrapper.find({ ref: 'form-element' }); + const findForm = () => wrapper.findComponent({ ref: 'form-element' }); const findSaveButton = () => wrapper.findByTestId('save-button'); const findKeepNDuplicatedPackageFilesDropdown = () => wrapper.findByTestId('keep-n-duplicated-package-files-dropdown'); 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 f576bc79eae..07d13839c61 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,41 +1,99 @@ +import { GlAlert } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; +import waitForPromises from 'helpers/wait_for_promises'; +import setWindowLocation from 'helpers/set_window_location_helper'; +import * as commonUtils from '~/lib/utils/common_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'; +import { + SHOW_SETUP_SUCCESS_ALERT, + UPDATE_SETTINGS_SUCCESS_MESSAGE, +} from '~/packages_and_registries/settings/project/constants'; + +jest.mock('~/lib/utils/common_utils'); describe('Registry Settings app', () => { let wrapper; - const findContainerExpirationPolicy = () => wrapper.find(ContainerExpirationPolicy); - const findPackagesCleanupPolicy = () => wrapper.find(PackagesCleanupPolicy); + const findContainerExpirationPolicy = () => wrapper.findComponent(ContainerExpirationPolicy); + const findPackagesCleanupPolicy = () => wrapper.findComponent(PackagesCleanupPolicy); + const findAlert = () => wrapper.findComponent(GlAlert); afterEach(() => { wrapper.destroy(); wrapper = null; }); - const mountComponent = (provide) => { + const defaultProvide = { + showContainerRegistrySettings: true, + showPackageRegistrySettings: true, + }; + + const mountComponent = (provide = defaultProvide) => { wrapper = shallowMount(component, { provide, }); }; - 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, + describe('container policy success alert handling', () => { + const originalLocation = window.location.href; + const search = `?${SHOW_SETUP_SUCCESS_ALERT}=true`; + + beforeEach(() => { + setWindowLocation(search); + }); + + afterEach(() => { + setWindowLocation(originalLocation); + }); + + it(`renders alert if the query string contains ${SHOW_SETUP_SUCCESS_ALERT}`, async () => { + mountComponent(); + + await waitForPromises(); + + expect(findAlert().exists()).toBe(true); + expect(findAlert().props()).toMatchObject({ + dismissible: true, + variant: 'success', }); + expect(findAlert().text()).toMatchInterpolatedText(UPDATE_SETTINGS_SUCCESS_MESSAGE); + }); + + it('calls historyReplaceState with a clean url', () => { + mountComponent(); + + expect(commonUtils.historyReplaceState).toHaveBeenCalledWith(originalLocation); + }); + + it(`does nothing if the query string does not contain ${SHOW_SETUP_SUCCESS_ALERT}`, () => { + setWindowLocation('?'); + mountComponent(); - expect(findContainerExpirationPolicy().exists()).toBe(showContainerRegistrySettings); - expect(findPackagesCleanupPolicy().exists()).toBe(showPackageRegistrySettings); - }, - ); + expect(findAlert().exists()).toBe(false); + expect(commonUtils.historyReplaceState).not.toHaveBeenCalled(); + }); + }); + + describe('settings', () => { + 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/container_registry/explorer/components/list_page/cli_commands_spec.js b/spec/frontend/packages_and_registries/shared/components/cli_commands_spec.js index 7727bf167fe..18084766db9 100644 --- a/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/cli_commands_spec.js +++ b/spec/frontend/packages_and_registries/shared/components/cli_commands_spec.js @@ -15,15 +15,15 @@ import { import Tracking from '~/tracking'; import CodeInstruction from '~/vue_shared/components/registry/code_instruction.vue'; -import { dockerCommands } from '../../mock_data'; +import { dockerCommands } from 'jest/packages_and_registries/container_registry/explorer/mock_data'; Vue.use(Vuex); describe('cli_commands', () => { let wrapper; - const findDropdownButton = () => wrapper.find(GlDropdown); - const findCodeInstruction = () => wrapper.findAll(CodeInstruction); + const findDropdownButton = () => wrapper.findComponent(GlDropdown); + const findCodeInstruction = () => wrapper.findAllComponents(CodeInstruction); const mountComponent = () => { wrapper = mount(QuickstartDropdown, { diff --git a/spec/frontend/packages_and_registries/shared/components/package_icon_and_name_spec.js b/spec/frontend/packages_and_registries/shared/components/package_icon_and_name_spec.js index d6d1970cb12..a0ff6ca01b5 100644 --- a/spec/frontend/packages_and_registries/shared/components/package_icon_and_name_spec.js +++ b/spec/frontend/packages_and_registries/shared/components/package_icon_and_name_spec.js @@ -5,7 +5,7 @@ import PackageIconAndName from '~/packages_and_registries/shared/components/pack describe('PackageIconAndName', () => { let wrapper; - const findIcon = () => wrapper.find(GlIcon); + const findIcon = () => wrapper.findComponent(GlIcon); const mountComponent = () => { wrapper = shallowMount(PackageIconAndName, { |