diff options
Diffstat (limited to 'spec/frontend/packages_and_registries/harbor_registry/components')
9 files changed, 525 insertions, 28 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); + }); + }); +}); |