diff options
Diffstat (limited to 'spec/frontend/packages_and_registries/harbor_registry')
14 files changed, 947 insertions, 206 deletions
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, + }, + }); + }); + }); +}); |