diff options
Diffstat (limited to 'spec/frontend/packages_and_registries')
37 files changed, 558 insertions, 256 deletions
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 f06300efa29..5278e730ec9 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 @@ -1,7 +1,6 @@ -import { GlDropdownItem, GlIcon } from '@gitlab/ui'; +import { GlDropdownItem, GlIcon, GlDropdown } from '@gitlab/ui'; import { shallowMount, createLocalVue } from '@vue/test-utils'; import VueApollo from 'vue-apollo'; -import { GlDropdown } from 'jest/packages_and_registries/container_registry/explorer/stubs'; import { useFakeDate } from 'helpers/fake_date'; import createMockApollo from 'helpers/mock_apollo_helper'; import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; @@ -51,6 +50,7 @@ describe('Details Header', () => { const findCleanup = () => findByTestId('cleanup'); const findDeleteButton = () => wrapper.findComponent(GlDropdownItem); const findInfoIcon = () => wrapper.findComponent(GlIcon); + const findMenu = () => wrapper.findComponent(GlDropdown); const waitForMetadataItems = async () => { // Metadata items are printed by a loop in the title-area and it takes two ticks for them to be available @@ -139,51 +139,53 @@ describe('Details Header', () => { }); }); - describe('delete button', () => { - it('exists', () => { - mountComponent(); + describe('menu', () => { + it.each` + canDelete | disabled | isVisible + ${true} | ${false} | ${true} + ${true} | ${true} | ${false} + ${false} | ${false} | ${false} + ${false} | ${true} | ${false} + `( + 'when canDelete is $canDelete and disabled is $disabled is $isVisible that the menu is visible', + ({ canDelete, disabled, isVisible }) => { + mountComponent({ propsData: { image: { ...defaultImage, canDelete }, disabled } }); - expect(findDeleteButton().exists()).toBe(true); - }); + expect(findMenu().exists()).toBe(isVisible); + }, + ); - it('has the correct text', () => { - mountComponent(); + describe('delete button', () => { + it('exists', () => { + mountComponent(); - expect(findDeleteButton().text()).toBe('Delete image repository'); - }); + expect(findDeleteButton().exists()).toBe(true); + }); - it('has the correct props', () => { - mountComponent(); + it('has the correct text', () => { + mountComponent(); - expect(findDeleteButton().attributes()).toMatchObject( - expect.objectContaining({ - variant: 'danger', - }), - ); - }); + expect(findDeleteButton().text()).toBe('Delete image repository'); + }); - it('emits the correct event', () => { - mountComponent(); + it('has the correct props', () => { + mountComponent(); - findDeleteButton().vm.$emit('click'); + expect(findDeleteButton().attributes()).toMatchObject( + expect.objectContaining({ + variant: 'danger', + }), + ); + }); - expect(wrapper.emitted('delete')).toEqual([[]]); - }); + it('emits the correct event', () => { + mountComponent(); - it.each` - canDelete | disabled | isDisabled - ${true} | ${false} | ${undefined} - ${true} | ${true} | ${'true'} - ${false} | ${false} | ${'true'} - ${false} | ${true} | ${'true'} - `( - 'when canDelete is $canDelete and disabled is $disabled is $isDisabled that the button is disabled', - ({ canDelete, disabled, isDisabled }) => { - mountComponent({ propsData: { image: { ...defaultImage, canDelete }, disabled } }); + findDeleteButton().vm.$emit('click'); - expect(findDeleteButton().attributes('disabled')).toBe(isDisabled); - }, - ); + expect(wrapper.emitted('delete')).toEqual([[]]); + }); + }); }); describe('metadata items', () => { diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/empty_state_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/empty_state_spec.js deleted file mode 100644 index f14284e9efe..00000000000 --- a/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/empty_state_spec.js +++ /dev/null @@ -1,54 +0,0 @@ -import { GlEmptyState } from '@gitlab/ui'; -import { shallowMount } from '@vue/test-utils'; -import component from '~/packages_and_registries/container_registry/explorer/components/details_page/empty_state.vue'; -import { - NO_TAGS_TITLE, - NO_TAGS_MESSAGE, - MISSING_OR_DELETED_IMAGE_TITLE, - MISSING_OR_DELETED_IMAGE_MESSAGE, -} from '~/packages_and_registries/container_registry/explorer/constants'; - -describe('EmptyTagsState component', () => { - let wrapper; - - const findEmptyState = () => wrapper.find(GlEmptyState); - - const mountComponent = (propsData) => { - wrapper = shallowMount(component, { - stubs: { - GlEmptyState, - }, - propsData, - }); - }; - - afterEach(() => { - wrapper.destroy(); - wrapper = null; - }); - - it('contains gl-empty-state', () => { - mountComponent(); - expect(findEmptyState().exists()).toBe(true); - }); - - it.each` - isEmptyImage | title | description - ${false} | ${NO_TAGS_TITLE} | ${NO_TAGS_MESSAGE} - ${true} | ${MISSING_OR_DELETED_IMAGE_TITLE} | ${MISSING_OR_DELETED_IMAGE_MESSAGE} - `( - 'when isEmptyImage is $isEmptyImage has the correct props', - ({ isEmptyImage, title, description }) => { - mountComponent({ - noContainersImage: 'foo', - isEmptyImage, - }); - - expect(findEmptyState().props()).toMatchObject({ - title, - description, - svgPath: 'foo', - }); - }, - ); -}); 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 00b1d03b7c2..057312828ff 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 @@ -75,16 +75,19 @@ describe('tags list row', () => { }); it.each` - digest | disabled - ${'foo'} | ${true} - ${null} | ${false} - ${null} | ${true} - ${'foo'} | ${true} - `('is disabled when the digest $digest and disabled is $disabled', ({ digest, disabled }) => { - mountComponent({ tag: { ...tag, digest }, disabled }); + digest | disabled | isDisabled + ${'foo'} | ${true} | ${'true'} + ${null} | ${true} | ${'true'} + ${null} | ${false} | ${undefined} + ${'foo'} | ${false} | ${undefined} + `( + 'disabled attribute is set to $isDisabled when the digest $digest and disabled is $disabled', + ({ digest, disabled, isDisabled }) => { + mountComponent({ tag: { ...tag, digest }, disabled }); - expect(findCheckbox().attributes('disabled')).toBe('true'); - }); + expect(findCheckbox().attributes('disabled')).toBe(isDisabled); + }, + ); it('is wired to the selected prop', () => { mountComponent({ ...defaultProps, selected: true }); diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/tags_list_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/tags_list_spec.js index 56f12e2f0bb..0dcf988c814 100644 --- a/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/tags_list_spec.js +++ b/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/tags_list_spec.js @@ -1,16 +1,25 @@ import { shallowMount, createLocalVue } from '@vue/test-utils'; import { nextTick } from 'vue'; +import { GlEmptyState } from '@gitlab/ui'; import VueApollo from 'vue-apollo'; import createMockApollo from 'helpers/mock_apollo_helper'; import waitForPromises from 'helpers/wait_for_promises'; import { stripTypenames } from 'helpers/graphql_helpers'; -import EmptyTagsState from '~/packages_and_registries/container_registry/explorer/components/details_page/empty_state.vue'; + import component from '~/packages_and_registries/container_registry/explorer/components/details_page/tags_list.vue'; import TagsListRow from '~/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row.vue'; import TagsLoader from '~/packages_and_registries/container_registry/explorer/components/details_page/tags_loader.vue'; import RegistryList from '~/packages_and_registries/shared/components/registry_list.vue'; +import PersistedSearch from '~/packages_and_registries/shared/components/persisted_search.vue'; import getContainerRepositoryTagsQuery from '~/packages_and_registries/container_registry/explorer/graphql/queries/get_container_repository_tags.query.graphql'; -import { GRAPHQL_PAGE_SIZE } from '~/packages_and_registries/container_registry/explorer/constants/index'; +import { + GRAPHQL_PAGE_SIZE, + NO_TAGS_TITLE, + NO_TAGS_MESSAGE, + NO_TAGS_MATCHING_FILTERS_TITLE, + NO_TAGS_MATCHING_FILTERS_DESCRIPTION, +} from '~/packages_and_registries/container_registry/explorer/constants/index'; +import { FILTERED_SEARCH_TERM } from '~/packages_and_registries/shared/constants'; import { tagsMock, imageTagsMock, tagsPageInfo } from '../../mock_data'; const localVue = createLocalVue(); @@ -21,11 +30,20 @@ describe('Tags List', () => { let resolver; const tags = [...tagsMock]; + const defaultConfig = { + noContainersImage: 'noContainersImage', + }; + + const findPersistedSearch = () => wrapper.findComponent(PersistedSearch); const findTagsListRow = () => wrapper.findAllComponents(TagsListRow); const findRegistryList = () => wrapper.findComponent(RegistryList); - const findEmptyState = () => wrapper.findComponent(EmptyTagsState); + const findEmptyState = () => wrapper.findComponent(GlEmptyState); const findTagsLoader = () => wrapper.findComponent(TagsLoader); + const fireFirstSortUpdate = () => { + findPersistedSearch().vm.$emit('update', { sort: 'NAME_ASC', filters: [] }); + }; + const waitForApolloRequestRender = async () => { await waitForPromises(); await nextTick(); @@ -44,7 +62,7 @@ describe('Tags List', () => { stubs: { RegistryList }, provide() { return { - config: {}, + config: defaultConfig, }; }, }); @@ -61,10 +79,23 @@ describe('Tags List', () => { describe('registry list', () => { beforeEach(() => { mountComponent(); - + fireFirstSortUpdate(); return waitForApolloRequestRender(); }); + it('has a persisted search', () => { + expect(findPersistedSearch().props()).toMatchObject({ + defaultOrder: 'NAME', + defaultSort: 'asc', + sortableFields: [ + { + label: 'Name', + orderBy: 'NAME', + }, + ], + }); + }); + it('binds the correct props', () => { expect(findRegistryList().props()).toMatchObject({ title: '2 tags', @@ -75,11 +106,13 @@ describe('Tags List', () => { }); describe('events', () => { - it('prev-page fetch the previous page', () => { + it('prev-page fetch the previous page', async () => { findRegistryList().vm.$emit('prev-page'); expect(resolver).toHaveBeenCalledWith({ first: null, + name: '', + sort: 'NAME_ASC', before: tagsPageInfo.startCursor, last: GRAPHQL_PAGE_SIZE, id: '1', @@ -92,6 +125,8 @@ describe('Tags List', () => { expect(resolver).toHaveBeenCalledWith({ after: tagsPageInfo.endCursor, first: GRAPHQL_PAGE_SIZE, + name: '', + sort: 'NAME_ASC', id: '1', }); }); @@ -108,6 +143,7 @@ describe('Tags List', () => { describe('list rows', () => { it('one row exist for each tag', async () => { mountComponent(); + fireFirstSortUpdate(); await waitForApolloRequestRender(); @@ -116,6 +152,7 @@ describe('Tags List', () => { it('the correct props are bound to it', async () => { mountComponent({ propsData: { disabled: true, id: 1 } }); + fireFirstSortUpdate(); await waitForApolloRequestRender(); @@ -130,7 +167,7 @@ describe('Tags List', () => { describe('events', () => { it('select event update the selected items', async () => { mountComponent(); - + fireFirstSortUpdate(); await waitForApolloRequestRender(); findTagsListRow().at(0).vm.$emit('select'); @@ -142,7 +179,7 @@ describe('Tags List', () => { it('delete event emit a delete event', async () => { mountComponent(); - + fireFirstSortUpdate(); await waitForApolloRequestRender(); findTagsListRow().at(0).vm.$emit('delete'); @@ -154,32 +191,45 @@ describe('Tags List', () => { describe('when the list of tags is empty', () => { beforeEach(() => { resolver = jest.fn().mockResolvedValue(imageTagsMock([])); - }); - - it('has the empty state', async () => { mountComponent(); - - await waitForApolloRequestRender(); - - expect(findEmptyState().exists()).toBe(true); + fireFirstSortUpdate(); + return waitForApolloRequestRender(); }); - it('does not show the loader', async () => { - mountComponent(); - - await waitForApolloRequestRender(); - + it('does not show the loader', () => { expect(findTagsLoader().exists()).toBe(false); }); - it('does not show the list', async () => { - mountComponent(); + it('does not show the list', () => { + expect(findRegistryList().exists()).toBe(false); + }); - await waitForApolloRequestRender(); + describe('empty state', () => { + it('default empty state', () => { + expect(findEmptyState().props()).toMatchObject({ + svgPath: defaultConfig.noContainersImage, + title: NO_TAGS_TITLE, + description: NO_TAGS_MESSAGE, + }); + }); - expect(findRegistryList().exists()).toBe(false); + it('when filtered shows a filtered message', async () => { + findPersistedSearch().vm.$emit('update', { + sort: 'NAME_ASC', + filters: [{ type: FILTERED_SEARCH_TERM, value: { data: 'foo' } }], + }); + + await waitForApolloRequestRender(); + + expect(findEmptyState().props()).toMatchObject({ + svgPath: defaultConfig.noContainersImage, + title: NO_TAGS_MATCHING_FILTERS_TITLE, + description: NO_TAGS_MATCHING_FILTERS_DESCRIPTION, + }); + }); }); }); + describe('loading state', () => { it.each` isImageLoading | queryExecuting | loadingVisible @@ -191,7 +241,7 @@ describe('Tags List', () => { 'when the isImageLoading is $isImageLoading, and is $queryExecuting that the query is still executing is $loadingVisible that the loader is shown', async ({ isImageLoading, queryExecuting, loadingVisible }) => { mountComponent({ propsData: { isImageLoading, isMobile: false, id: 1 } }); - + fireFirstSortUpdate(); if (!queryExecuting) { await waitForApolloRequestRender(); } 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 9b821ba8ef3..7992bead60a 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 @@ -1,4 +1,4 @@ -import { GlKeysetPagination } from '@gitlab/ui'; +import { GlKeysetPagination, GlEmptyState } from '@gitlab/ui'; import { shallowMount, createLocalVue } from '@vue/test-utils'; import VueApollo from 'vue-apollo'; import { nextTick } from 'vue'; @@ -8,7 +8,6 @@ import axios from '~/lib/utils/axios_utils'; import DeleteImage from '~/packages_and_registries/container_registry/explorer/components/delete_image.vue'; import DeleteAlert from '~/packages_and_registries/container_registry/explorer/components/details_page/delete_alert.vue'; import DetailsHeader from '~/packages_and_registries/container_registry/explorer/components/details_page/details_header.vue'; -import EmptyTagsState from '~/packages_and_registries/container_registry/explorer/components/details_page/empty_state.vue'; import PartialCleanupAlert from '~/packages_and_registries/container_registry/explorer/components/details_page/partial_cleanup_alert.vue'; import StatusAlert from '~/packages_and_registries/container_registry/explorer/components/details_page/status_alert.vue'; import TagsList from '~/packages_and_registries/container_registry/explorer/components/details_page/tags_list.vue'; @@ -20,6 +19,8 @@ import { ALERT_DANGER_IMAGE, MISSING_OR_DELETED_IMAGE_BREADCRUMB, ROOT_IMAGE_TEXT, + MISSING_OR_DELETED_IMAGE_TITLE, + MISSING_OR_DELETED_IMAGE_MESSAGE, } from '~/packages_and_registries/container_registry/explorer/constants'; import deleteContainerRepositoryTagsMutation from '~/packages_and_registries/container_registry/explorer/graphql/mutations/delete_container_repository_tags.mutation.graphql'; import getContainerRepositoryDetailsQuery from '~/packages_and_registries/container_registry/explorer/graphql/queries/get_container_repository_details.query.graphql'; @@ -50,7 +51,7 @@ describe('Details Page', () => { const findTagsList = () => wrapper.find(TagsList); const findDeleteAlert = () => wrapper.find(DeleteAlert); const findDetailsHeader = () => wrapper.find(DetailsHeader); - const findEmptyState = () => wrapper.find(EmptyTagsState); + const findEmptyState = () => wrapper.find(GlEmptyState); const findPartialCleanupAlert = () => wrapper.find(PartialCleanupAlert); const findStatusAlert = () => wrapper.find(StatusAlert); const findDeleteImage = () => wrapper.find(DeleteImage); @@ -61,6 +62,10 @@ describe('Details Page', () => { updateName: jest.fn(), }; + const defaultConfig = { + noContainersImage: 'noContainersImage', + }; + const cleanTags = tagsMock.map((t) => { const result = { ...t }; // eslint-disable-next-line no-underscore-dangle @@ -78,7 +83,7 @@ describe('Details Page', () => { mutationResolver = jest.fn().mockResolvedValue(graphQLDeleteImageRepositoryTagsMock), tagsResolver = jest.fn().mockResolvedValue(graphQLImageDetailsMock(imageTagsMock)), options, - config = {}, + config = defaultConfig, } = {}) => { localVue.use(VueApollo); @@ -154,7 +159,11 @@ describe('Details Page', () => { await waitForApolloRequestRender(); - expect(findEmptyState().exists()).toBe(true); + expect(findEmptyState().props()).toMatchObject({ + description: MISSING_OR_DELETED_IMAGE_MESSAGE, + svgPath: defaultConfig.noContainersImage, + title: MISSING_OR_DELETED_IMAGE_TITLE, + }); }); }); diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/__snapshots__/file_sha_spec.js.snap b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/__snapshots__/file_sha_spec.js.snap index 881d441e116..f95564e3fad 100644 --- a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/__snapshots__/file_sha_spec.js.snap +++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/__snapshots__/file_sha_spec.js.snap @@ -15,11 +15,14 @@ exports[`FileSha renders 1`] = ` foo <gl-button-stub - aria-label="Copy this value" + aria-label="Copy SHA" + aria-live="polite" buttontextclasses="" category="tertiary" + data-clipboard-handle-tooltip="false" data-clipboard-text="foo" icon="copy-to-clipboard" + id="clipboard-button-1" size="small" title="Copy SHA" variant="default" diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/file_sha_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/file_sha_spec.js index 9ce590bfb51..d7caa8ca2d8 100644 --- a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/file_sha_spec.js +++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/file_sha_spec.js @@ -4,6 +4,8 @@ import FileSha from '~/packages_and_registries/infrastructure_registry/details/c import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; import DetailsRow from '~/vue_shared/components/registry/details_row.vue'; +jest.mock('lodash/uniqueId', () => (prefix) => (prefix ? `${prefix}1` : 1)); + describe('FileSha', () => { let wrapper; diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/__snapshots__/packages_list_app_spec.js.snap b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/__snapshots__/packages_list_app_spec.js.snap index 99a7b8e427a..7cdf21dde46 100644 --- a/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/__snapshots__/packages_list_app_spec.js.snap +++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/__snapshots__/packages_list_app_spec.js.snap @@ -10,10 +10,10 @@ exports[`packages_list_app renders 1`] = ` <div> <section - class="row empty-state text-center" + class="gl-display-flex empty-state gl-text-center gl-flex-direction-column" > <div - class="col-12" + class="gl-max-w-full" > <div class="svg-250 svg-content" @@ -28,10 +28,10 @@ exports[`packages_list_app renders 1`] = ` </div> <div - class="col-12" + class="gl-max-w-full gl-m-auto" > <div - class="text-content gl-mx-auto gl-my-0 gl-p-5" + class="gl-mx-auto gl-my-0 gl-p-5" > <h1 class="gl-font-size-h-display gl-line-height-36 h4" 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 2fb76b98925..26569f20e94 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 @@ -134,6 +134,8 @@ describe('packages_list', () => { }); it('deleteItemConfirmation resets itemToBeDeleted', () => { + // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details + // eslint-disable-next-line no-restricted-syntax wrapper.setData({ itemToBeDeleted: 1 }); wrapper.vm.deleteItemConfirmation(); expect(wrapper.vm.itemToBeDeleted).toEqual(null); @@ -141,6 +143,8 @@ describe('packages_list', () => { it('deleteItemConfirmation emit package:delete', () => { const itemToBeDeleted = { id: 2 }; + // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details + // eslint-disable-next-line no-restricted-syntax wrapper.setData({ itemToBeDeleted }); wrapper.vm.deleteItemConfirmation(); return wrapper.vm.$nextTick(() => { @@ -149,6 +153,8 @@ describe('packages_list', () => { }); it('deleteItemCanceled resets itemToBeDeleted', () => { + // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details + // eslint-disable-next-line no-restricted-syntax wrapper.setData({ itemToBeDeleted: 1 }); wrapper.vm.deleteItemCanceled(); expect(wrapper.vm.itemToBeDeleted).toEqual(null); @@ -194,6 +200,8 @@ describe('packages_list', () => { beforeEach(() => { mountComponent(); eventSpy = jest.spyOn(Tracking, 'event'); + // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details + // eslint-disable-next-line no-restricted-syntax wrapper.setData({ itemToBeDeleted: { package_type: 'conan' } }); }); diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/conan_installation_spec.js.snap b/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/conan_installation_spec.js.snap index e9f80d5f512..b3d0d88be4d 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/conan_installation_spec.js.snap +++ b/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/conan_installation_spec.js.snap @@ -23,14 +23,18 @@ exports[`ConanInstallation renders all the messages 1`] = ` <code-instruction-stub copytext="Copy Conan Setup Command" - instruction="conan remote add gitlab conanPath" + instruction="conan remote add gitlab http://gdk.test:3000/api/v4/projects/1/packages/conan" label="Add Conan Remote" trackingaction="copy_conan_setup_command" trackinglabel="code_instruction" /> - - <gl-sprintf-stub - message="For more information on the Conan registry, %{linkStart}see the documentation%{linkEnd}." - /> + For more information on the Conan registry, + <gl-link-stub + href="/help/user/packages/conan_repository/index" + target="_blank" + > + see the documentation + </gl-link-stub> + . </div> `; diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/file_sha_spec.js.snap b/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/file_sha_spec.js.snap index 881d441e116..f95564e3fad 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/file_sha_spec.js.snap +++ b/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/file_sha_spec.js.snap @@ -15,11 +15,14 @@ exports[`FileSha renders 1`] = ` foo <gl-button-stub - aria-label="Copy this value" + aria-label="Copy SHA" + aria-live="polite" buttontextclasses="" category="tertiary" + data-clipboard-handle-tooltip="false" data-clipboard-text="foo" icon="copy-to-clipboard" + id="clipboard-button-1" size="small" title="Copy SHA" variant="default" diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/maven_installation_spec.js.snap b/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/maven_installation_spec.js.snap index 4865b8205ab..67f1906f6fd 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/maven_installation_spec.js.snap +++ b/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/maven_installation_spec.js.snap @@ -19,7 +19,7 @@ exports[`MavenInstallation groovy renders all the messages 1`] = ` <code-instruction-stub copytext="Copy add Gradle Groovy DSL repository command" instruction="maven { - url 'mavenPath' + url 'http://gdk.test:3000/api/v4/projects/1/packages/maven' }" label="Add Gradle Groovy DSL repository command" multiline="true" @@ -47,7 +47,7 @@ exports[`MavenInstallation kotlin renders all the messages 1`] = ` <code-instruction-stub copytext="Copy add Gradle Kotlin DSL repository command" - instruction="maven(\\"mavenPath\\")" + instruction="maven(\\"http://gdk.test:3000/api/v4/projects/1/packages/maven\\")" label="Add Gradle Kotlin DSL repository command" multiline="true" trackingaction="copy_kotlin_add_to_source_command" @@ -64,9 +64,15 @@ exports[`MavenInstallation maven renders all the messages 1`] = ` /> <p> - <gl-sprintf-stub - message="Copy and paste this inside your %{codeStart}pom.xml%{codeEnd} %{codeStart}dependencies%{codeEnd} block." - /> + Copy and paste this inside your + <code> + pom.xml + </code> + + <code> + dependencies + </code> + block. </p> <code-instruction-stub @@ -97,9 +103,11 @@ exports[`MavenInstallation maven renders all the messages 1`] = ` </h3> <p> - <gl-sprintf-stub - message="If you haven't already done so, you will need to add the below to your %{codeStart}pom.xml%{codeEnd} file." - /> + If you haven't already done so, you will need to add the below to your + <code> + pom.xml + </code> + file. </p> <code-instruction-stub @@ -107,19 +115,19 @@ exports[`MavenInstallation maven renders all the messages 1`] = ` instruction="<repositories> <repository> <id>gitlab-maven</id> - <url>mavenPath</url> + <url>http://gdk.test:3000/api/v4/projects/1/packages/maven</url> </repository> </repositories> <distributionManagement> <repository> <id>gitlab-maven</id> - <url>mavenPath</url> + <url>http://gdk.test:3000/api/v4/projects/1/packages/maven</url> </repository> <snapshotRepository> <id>gitlab-maven</id> - <url>mavenPath</url> + <url>http://gdk.test:3000/api/v4/projects/1/packages/maven</url> </snapshotRepository> </distributionManagement>" label="" @@ -127,9 +135,13 @@ exports[`MavenInstallation maven renders all the messages 1`] = ` trackingaction="copy_maven_setup_xml" trackinglabel="code_instruction" /> - - <gl-sprintf-stub - message="For more information on the Maven registry, %{linkStart}see the documentation%{linkEnd}." - /> + For more information on the Maven registry, + <gl-link-stub + href="/help/user/packages/maven_repository/index" + target="_blank" + > + see the documentation + </gl-link-stub> + . </div> `; diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/npm_installation_spec.js.snap b/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/npm_installation_spec.js.snap index d5649e39561..4520ae9c328 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/npm_installation_spec.js.snap +++ b/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/npm_installation_spec.js.snap @@ -32,14 +32,18 @@ exports[`NpmInstallation renders all the messages 1`] = ` <code-instruction-stub copytext="Copy npm setup command" - instruction="echo @gitlab-org:registry=npmPath/ >> .npmrc" + instruction="echo @gitlab-org:registry=npmInstanceUrl/ >> .npmrc" label="" trackingaction="copy_npm_setup_command" trackinglabel="code_instruction" /> - - <gl-sprintf-stub - message="You may also need to setup authentication using an auth token. %{linkStart}See the documentation%{linkEnd} to find out more." - /> + You may also need to setup authentication using an auth token. + <gl-link-stub + href="/help/user/packages/npm_registry/index" + target="_blank" + > + See the documentation + </gl-link-stub> + to find out more. </div> `; diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/nuget_installation_spec.js.snap b/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/nuget_installation_spec.js.snap index 29ddd7b77ed..92930a6309a 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/nuget_installation_spec.js.snap +++ b/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/nuget_installation_spec.js.snap @@ -23,14 +23,18 @@ exports[`NugetInstallation renders all the messages 1`] = ` <code-instruction-stub copytext="Copy NuGet Setup Command" - instruction="nuget source Add -Name \\"GitLab\\" -Source \\"nugetPath\\" -UserName <your_username> -Password <your_token>" + instruction="nuget source Add -Name \\"GitLab\\" -Source \\"http://gdk.test:3000/api/v4/projects/1/packages/nuget/index.json\\" -UserName <your_username> -Password <your_token>" label="Add NuGet Source" trackingaction="copy_nuget_setup_command" trackinglabel="code_instruction" /> - - <gl-sprintf-stub - message="For more information on the NuGet registry, %{linkStart}see the documentation%{linkEnd}." - /> + For more information on the NuGet registry, + <gl-link-stub + href="/help/user/packages/nuget_repository/index" + target="_blank" + > + see the documentation + </gl-link-stub> + . </div> `; diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/pypi_installation_spec.js.snap b/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/pypi_installation_spec.js.snap index 158bbbc3463..06ae8645101 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/pypi_installation_spec.js.snap +++ b/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/pypi_installation_spec.js.snap @@ -10,7 +10,7 @@ exports[`PypiInstallation renders all the messages 1`] = ` <code-instruction-stub copytext="Copy Pip command" data-testid="pip-command" - instruction="pip install @gitlab-org/package-15 --extra-index-url pypiPath" + instruction="pip install @gitlab-org/package-15 --extra-index-url http://__token__:<your_personal_token>@gdk.test:3000/api/v4/projects/1/packages/pypi/simple" label="Pip Command" trackingaction="copy_pip_install_command" trackinglabel="code_instruction" @@ -23,16 +23,18 @@ exports[`PypiInstallation renders all the messages 1`] = ` </h3> <p> - <gl-sprintf-stub - message="If you haven't already done so, you will need to add the below to your %{codeStart}.pypirc%{codeEnd} file." - /> + If you haven't already done so, you will need to add the below to your + <code> + .pypirc + </code> + file. </p> <code-instruction-stub copytext="Copy .pypirc content" data-testid="pypi-setup-content" instruction="[gitlab] -repository = pypiSetupPath +repository = http://gdk.test:3000/api/v4/projects/1/packages/pypi username = __token__ password = <your personal access token>" label="" @@ -40,9 +42,13 @@ password = <your personal access token>" trackingaction="copy_pypi_setup_command" trackinglabel="code_instruction" /> - - <gl-sprintf-stub - message="For more information on the PyPi registry, %{linkStart}see the documentation%{linkEnd}." - /> + For more information on the PyPi registry, + <gl-link-stub + href="/help/user/packages/pypi_repository/index" + target="_blank" + > + see the documentation + </gl-link-stub> + . </div> `; diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/composer_installation_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/composer_installation_spec.js index aedf20e873a..0aba8f7efc7 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/details/composer_installation_spec.js +++ b/spec/frontend/packages_and_registries/package_registry/components/details/composer_installation_spec.js @@ -7,6 +7,7 @@ import { TRACKING_ACTION_COPY_COMPOSER_REGISTRY_INCLUDE_COMMAND, TRACKING_ACTION_COPY_COMPOSER_PACKAGE_INCLUDE_COMMAND, PACKAGE_TYPE_COMPOSER, + COMPOSER_HELP_PATH, } from '~/packages_and_registries/package_registry/constants'; const packageEntity = { ...packageData(), packageType: PACKAGE_TYPE_COMPOSER }; @@ -24,9 +25,6 @@ describe('ComposerInstallation', () => { function createComponent(groupListUrl = 'groupListUrl') { wrapper = shallowMountExtended(ComposerInstallation, { provide: { - composerHelpPath: 'composerHelpPath', - composerConfigRepositoryName: 'composerConfigRepositoryName', - composerPath: 'composerPath', groupListUrl, }, propsData: { packageEntity }, @@ -61,7 +59,7 @@ describe('ComposerInstallation', () => { const registryIncludeCommand = findRegistryInclude(); expect(registryIncludeCommand.exists()).toBe(true); expect(registryIncludeCommand.props()).toMatchObject({ - instruction: `composer config repositories.composerConfigRepositoryName '{"type": "composer", "url": "composerPath"}'`, + instruction: `composer config repositories.${packageEntity.composerConfigRepositoryUrl} '{"type": "composer", "url": "${packageEntity.composerUrl}"}'`, copyText: 'Copy registry include', trackingAction: TRACKING_ACTION_COPY_COMPOSER_REGISTRY_INCLUDE_COMMAND, }); @@ -96,7 +94,7 @@ describe('ComposerInstallation', () => { 'For more information on Composer packages in GitLab, see the documentation.', ); expect(findHelpLink().attributes()).toMatchObject({ - href: 'composerHelpPath', + href: COMPOSER_HELP_PATH, target: '_blank', }); }); diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/conan_installation_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/conan_installation_spec.js index 6b642cc21b7..bf9425def9a 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/details/conan_installation_spec.js +++ b/spec/frontend/packages_and_registries/package_registry/components/details/conan_installation_spec.js @@ -1,8 +1,12 @@ +import { GlLink, GlSprintf } from '@gitlab/ui'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import { packageData } from 'jest/packages_and_registries/package_registry/mock_data'; import ConanInstallation from '~/packages_and_registries/package_registry/components/details/conan_installation.vue'; import InstallationTitle from '~/packages_and_registries/package_registry/components/details/installation_title.vue'; -import { PACKAGE_TYPE_CONAN } from '~/packages_and_registries/package_registry/constants'; +import { + PACKAGE_TYPE_CONAN, + CONAN_HELP_PATH, +} from '~/packages_and_registries/package_registry/constants'; import CodeInstructions from '~/vue_shared/components/registry/code_instruction.vue'; const packageEntity = { ...packageData(), packageType: PACKAGE_TYPE_CONAN }; @@ -12,16 +16,16 @@ describe('ConanInstallation', () => { const findCodeInstructions = () => wrapper.findAllComponents(CodeInstructions); const findInstallationTitle = () => wrapper.findComponent(InstallationTitle); + const findSetupDocsLink = () => wrapper.findComponent(GlLink); function createComponent() { wrapper = shallowMountExtended(ConanInstallation, { - provide: { - conanHelpPath: 'conanHelpPath', - conanPath: 'conanPath', - }, propsData: { packageEntity, }, + stubs: { + GlSprintf, + }, }); } @@ -58,8 +62,15 @@ describe('ConanInstallation', () => { describe('setup commands', () => { it('renders the correct command', () => { expect(findCodeInstructions().at(1).props('instruction')).toBe( - 'conan remote add gitlab conanPath', + `conan remote add gitlab ${packageEntity.conanUrl}`, ); }); + + it('has a link to the docs', () => { + expect(findSetupDocsLink().attributes()).toMatchObject({ + href: CONAN_HELP_PATH, + target: '_blank', + }); + }); }); }); diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/file_sha_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/file_sha_spec.js index ebfbbe5b864..feed7a7c46c 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/details/file_sha_spec.js +++ b/spec/frontend/packages_and_registries/package_registry/components/details/file_sha_spec.js @@ -4,6 +4,8 @@ import FileSha from '~/packages_and_registries/package_registry/components/detai import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; import DetailsRow from '~/vue_shared/components/registry/details_row.vue'; +jest.mock('lodash/uniqueId', () => (prefix) => (prefix ? `${prefix}1` : 1)); + describe('FileSha', () => { let wrapper; diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/maven_installation_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/maven_installation_spec.js index eed7e903833..fc60039db30 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/details/maven_installation_spec.js +++ b/spec/frontend/packages_and_registries/package_registry/components/details/maven_installation_spec.js @@ -1,3 +1,4 @@ +import { GlLink, GlSprintf } from '@gitlab/ui'; import { nextTick } from 'vue'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; @@ -16,6 +17,7 @@ import { TRACKING_ACTION_COPY_KOTLIN_INSTALL_COMMAND, TRACKING_ACTION_COPY_KOTLIN_ADD_TO_SOURCE_COMMAND, PACKAGE_TYPE_MAVEN, + MAVEN_HELP_PATH, } from '~/packages_and_registries/package_registry/constants'; import CodeInstructions from '~/vue_shared/components/registry/code_instruction.vue'; @@ -28,9 +30,6 @@ describe('MavenInstallation', () => { metadata: mavenMetadata(), }; - const mavenHelpPath = 'mavenHelpPath'; - const mavenPath = 'mavenPath'; - const xmlCodeBlock = `<dependency> <groupId>appGroup</groupId> <artifactId>appName</artifactId> @@ -40,43 +39,43 @@ describe('MavenInstallation', () => { const mavenSetupXml = `<repositories> <repository> <id>gitlab-maven</id> - <url>${mavenPath}</url> + <url>${packageEntity.mavenUrl}</url> </repository> </repositories> <distributionManagement> <repository> <id>gitlab-maven</id> - <url>${mavenPath}</url> + <url>${packageEntity.mavenUrl}</url> </repository> <snapshotRepository> <id>gitlab-maven</id> - <url>${mavenPath}</url> + <url>${packageEntity.mavenUrl}</url> </snapshotRepository> </distributionManagement>`; const gradleGroovyInstallCommandText = `implementation 'appGroup:appName:appVersion'`; const gradleGroovyAddSourceCommandText = `maven { - url '${mavenPath}' + url '${packageEntity.mavenUrl}' }`; const gradleKotlinInstallCommandText = `implementation("appGroup:appName:appVersion")`; - const gradleKotlinAddSourceCommandText = `maven("${mavenPath}")`; + const gradleKotlinAddSourceCommandText = `maven("${packageEntity.mavenUrl}")`; const findCodeInstructions = () => wrapper.findAllComponents(CodeInstructions); const findInstallationTitle = () => wrapper.findComponent(InstallationTitle); + const findSetupDocsLink = () => wrapper.findComponent(GlLink); function createComponent({ data = {} } = {}) { wrapper = shallowMountExtended(MavenInstallation, { - provide: { - mavenHelpPath, - mavenPath, - }, propsData: { packageEntity, }, data() { return data; }, + stubs: { + GlSprintf, + }, }); } @@ -148,6 +147,13 @@ describe('MavenInstallation', () => { trackingAction: TRACKING_ACTION_COPY_MAVEN_SETUP, }); }); + + it('has a setup link', () => { + expect(findSetupDocsLink().attributes()).toMatchObject({ + href: MAVEN_HELP_PATH, + target: '_blank', + }); + }); }); }); diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/npm_installation_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/npm_installation_spec.js index b89410ede13..8c0e2d948ca 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/details/npm_installation_spec.js +++ b/spec/frontend/packages_and_registries/package_registry/components/details/npm_installation_spec.js @@ -1,4 +1,4 @@ -import { GlFormRadioGroup } from '@gitlab/ui'; +import { GlLink, GlSprintf, GlFormRadioGroup } from '@gitlab/ui'; import { nextTick } from 'vue'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; @@ -15,6 +15,7 @@ import { YARN_PACKAGE_MANAGER, PROJECT_PACKAGE_ENDPOINT_TYPE, INSTANCE_PACKAGE_ENDPOINT_TYPE, + NPM_HELP_PATH, } from '~/packages_and_registries/package_registry/constants'; import CodeInstructions from '~/vue_shared/components/registry/code_instruction.vue'; @@ -29,13 +30,12 @@ describe('NpmInstallation', () => { const findCodeInstructions = () => wrapper.findAllComponents(CodeInstructions); const findInstallationTitle = () => wrapper.findComponent(InstallationTitle); const findEndPointTypeSector = () => wrapper.findComponent(GlFormRadioGroup); + const findSetupDocsLink = () => wrapper.findComponent(GlLink); function createComponent({ data = {} } = {}) { wrapper = shallowMountExtended(NpmInstallation, { provide: { - npmHelpPath: 'npmHelpPath', - npmPath: 'npmPath', - npmProjectPath: 'npmProjectPath', + npmInstanceUrl: 'npmInstanceUrl', }, propsData: { packageEntity, @@ -43,6 +43,7 @@ describe('NpmInstallation', () => { data() { return data; }, + stubs: { GlSprintf }, }); } @@ -58,6 +59,13 @@ describe('NpmInstallation', () => { expect(wrapper.element).toMatchSnapshot(); }); + it('has a setup link', () => { + expect(findSetupDocsLink().attributes()).toMatchObject({ + href: NPM_HELP_PATH, + target: '_blank', + }); + }); + describe('endpoint type selector', () => { it('has the endpoint type selector', () => { expect(findEndPointTypeSector().exists()).toBe(true); @@ -109,7 +117,7 @@ describe('NpmInstallation', () => { it('renders the correct setup command', () => { expect(findCodeInstructions().at(1).props()).toMatchObject({ - instruction: 'echo @gitlab-org:registry=npmPath/ >> .npmrc', + instruction: 'echo @gitlab-org:registry=npmInstanceUrl/ >> .npmrc', multiline: false, trackingAction: TRACKING_ACTION_COPY_NPM_SETUP_COMMAND, }); @@ -121,7 +129,7 @@ describe('NpmInstallation', () => { await nextTick(); expect(findCodeInstructions().at(1).props()).toMatchObject({ - instruction: `echo @gitlab-org:registry=npmProjectPath/ >> .npmrc`, + instruction: `echo @gitlab-org:registry=${packageEntity.npmUrl}/ >> .npmrc`, multiline: false, trackingAction: TRACKING_ACTION_COPY_NPM_SETUP_COMMAND, }); @@ -131,7 +139,7 @@ describe('NpmInstallation', () => { await nextTick(); expect(findCodeInstructions().at(1).props()).toMatchObject({ - instruction: `echo @gitlab-org:registry=npmPath/ >> .npmrc`, + instruction: `echo @gitlab-org:registry=npmInstanceUrl/ >> .npmrc`, multiline: false, trackingAction: TRACKING_ACTION_COPY_NPM_SETUP_COMMAND, }); @@ -153,7 +161,7 @@ describe('NpmInstallation', () => { it('renders the correct registry command', () => { expect(findCodeInstructions().at(1).props()).toMatchObject({ - instruction: 'echo \\"@gitlab-org:registry\\" \\"npmPath/\\" >> .yarnrc', + instruction: 'echo \\"@gitlab-org:registry\\" \\"npmInstanceUrl/\\" >> .yarnrc', multiline: false, trackingAction: TRACKING_ACTION_COPY_YARN_SETUP_COMMAND, }); @@ -165,7 +173,7 @@ describe('NpmInstallation', () => { await nextTick(); expect(findCodeInstructions().at(1).props()).toMatchObject({ - instruction: `echo \\"@gitlab-org:registry\\" \\"npmProjectPath/\\" >> .yarnrc`, + instruction: `echo \\"@gitlab-org:registry\\" \\"${packageEntity.npmUrl}/\\" >> .yarnrc`, multiline: false, trackingAction: TRACKING_ACTION_COPY_YARN_SETUP_COMMAND, }); @@ -175,7 +183,7 @@ describe('NpmInstallation', () => { await nextTick(); expect(findCodeInstructions().at(1).props()).toMatchObject({ - instruction: 'echo \\"@gitlab-org:registry\\" \\"npmPath/\\" >> .yarnrc', + instruction: 'echo \\"@gitlab-org:registry\\" \\"npmInstanceUrl/\\" >> .yarnrc', multiline: false, trackingAction: TRACKING_ACTION_COPY_YARN_SETUP_COMMAND, }); 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 c48a3f07299..d324d43258c 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 @@ -1,3 +1,4 @@ +import { GlLink, GlSprintf } from '@gitlab/ui'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import { packageData } from 'jest/packages_and_registries/package_registry/mock_data'; import InstallationTitle from '~/packages_and_registries/package_registry/components/details/installation_title.vue'; @@ -6,6 +7,7 @@ import { TRACKING_ACTION_COPY_NUGET_INSTALL_COMMAND, TRACKING_ACTION_COPY_NUGET_SETUP_COMMAND, PACKAGE_TYPE_NUGET, + NUGET_HELP_PATH, } from '~/packages_and_registries/package_registry/constants'; import CodeInstructions from '~/vue_shared/components/registry/code_instruction.vue'; @@ -15,21 +17,18 @@ describe('NugetInstallation', () => { let wrapper; const nugetInstallationCommandStr = 'nuget install @gitlab-org/package-15 -Source "GitLab"'; - const nugetSetupCommandStr = - 'nuget source Add -Name "GitLab" -Source "nugetPath" -UserName <your_username> -Password <your_token>'; + const nugetSetupCommandStr = `nuget source Add -Name "GitLab" -Source "${packageEntity.nugetUrl}" -UserName <your_username> -Password <your_token>`; const findCodeInstructions = () => wrapper.findAllComponents(CodeInstructions); const findInstallationTitle = () => wrapper.findComponent(InstallationTitle); + const findSetupDocsLink = () => wrapper.findComponent(GlLink); function createComponent() { wrapper = shallowMountExtended(NugetInstallation, { - provide: { - nugetHelpPath: 'nugetHelpPath', - nugetPath: 'nugetPath', - }, propsData: { packageEntity, }, + stubs: { GlSprintf }, }); } @@ -71,5 +70,12 @@ describe('NugetInstallation', () => { trackingAction: TRACKING_ACTION_COPY_NUGET_SETUP_COMMAND, }); }); + + it('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/details/package_files_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/package_files_spec.js index 042b2026199..f8a4ba8f3bc 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/details/package_files_spec.js +++ b/spec/frontend/packages_and_registries/package_registry/components/details/package_files_spec.js @@ -28,8 +28,8 @@ describe('Package Files', () => { const createComponent = ({ packageFiles = [file], canDelete = true } = {}) => { wrapper = mountExtended(PackageFiles, { - provide: { canDelete }, propsData: { + canDelete, packageFiles, }, stubs: { diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/pypi_installation_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/pypi_installation_spec.js index 410c1b65348..f2fef6436a6 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/details/pypi_installation_spec.js +++ b/spec/frontend/packages_and_registries/package_registry/components/details/pypi_installation_spec.js @@ -1,3 +1,4 @@ +import { GlLink, GlSprintf } from '@gitlab/ui'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import { packageData } from 'jest/packages_and_registries/package_registry/mock_data'; import InstallationTitle from '~/packages_and_registries/package_registry/components/details/installation_title.vue'; @@ -6,6 +7,7 @@ import { PACKAGE_TYPE_PYPI, TRACKING_ACTION_COPY_PIP_INSTALL_COMMAND, TRACKING_ACTION_COPY_PYPI_SETUP_COMMAND, + PYPI_HELP_PATH, } from '~/packages_and_registries/package_registry/constants'; const packageEntity = { ...packageData(), packageType: PACKAGE_TYPE_PYPI }; @@ -13,9 +15,9 @@ const packageEntity = { ...packageData(), packageType: PACKAGE_TYPE_PYPI }; describe('PypiInstallation', () => { let wrapper; - const pipCommandStr = 'pip install @gitlab-org/package-15 --extra-index-url pypiPath'; + const pipCommandStr = `pip install @gitlab-org/package-15 --extra-index-url ${packageEntity.pypiUrl}`; const pypiSetupStr = `[gitlab] -repository = pypiSetupPath +repository = ${packageEntity.pypiSetupUrl} username = __token__ password = <your personal access token>`; @@ -23,17 +25,16 @@ password = <your personal access token>`; const setupInstruction = () => wrapper.findByTestId('pypi-setup-content'); const findInstallationTitle = () => wrapper.findComponent(InstallationTitle); + const findSetupDocsLink = () => wrapper.findComponent(GlLink); function createComponent() { wrapper = shallowMountExtended(PypiInstallation, { - provide: { - pypiHelpPath: 'pypiHelpPath', - pypiPath: 'pypiPath', - pypiSetupPath: 'pypiSetupPath', - }, propsData: { packageEntity, }, + stubs: { + GlSprintf, + }, }); } @@ -76,5 +77,12 @@ password = <your personal access token>`; trackingAction: TRACKING_ACTION_COPY_PYPI_SETUP_COMMAND, }); }); + + it('has a link to the docs', () => { + expect(findSetupDocsLink().attributes()).toMatchObject({ + href: PYPI_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 165ee962417..18a99f70756 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 @@ -22,16 +22,20 @@ exports[`packages_list_row renders 1`] = ` <div class="gl-display-flex gl-align-items-center gl-mr-3 gl-min-w-0" > - <gl-link-stub + <router-link-stub + ariacurrentvalue="page" class="gl-text-body gl-min-w-0" data-qa-selector="package_link" - href="http://gdk.test:3000/gitlab-org/gitlab-test/-/packages/111" + data-testid="details-link" + event="click" + tag="a" + to="[object Object]" > <gl-truncate-stub position="end" text="@gitlab-org/package-15" /> - </gl-link-stub> + </router-link-stub> <!----> 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 292667ec47c..9467a613b2a 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 @@ -1,7 +1,11 @@ -import { GlLink, GlSprintf } from '@gitlab/ui'; +import { GlSprintf } from '@gitlab/ui'; +import { createLocalVue } from '@vue/test-utils'; +import VueRouter from 'vue-router'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; +import { getIdFromGraphQLId } from '~/graphql_shared/utils'; + import PackagesListRow from '~/packages_and_registries/package_registry/components/list/package_list_row.vue'; import PackagePath from '~/packages_and_registries/shared/components/package_path.vue'; import PackageTags from '~/packages_and_registries/shared/components/package_tags.vue'; @@ -13,6 +17,9 @@ import { PACKAGE_ERROR_STATUS } from '~/packages_and_registries/package_registry import ListItem from '~/vue_shared/components/registry/list_item.vue'; import { packageData, packagePipelines, packageProject, packageTags } from '../../mock_data'; +const localVue = createLocalVue(); +localVue.use(VueRouter); + describe('packages_list_row', () => { let wrapper; @@ -28,7 +35,7 @@ describe('packages_list_row', () => { const findDeleteButton = () => wrapper.findByTestId('action-delete'); const findPackageIconAndName = () => wrapper.find(PackageIconAndName); const findListItem = () => wrapper.findComponent(ListItem); - const findPackageLink = () => wrapper.findComponent(GlLink); + const findPackageLink = () => wrapper.findByTestId('details-link'); const findWarningIcon = () => wrapper.findByTestId('warning-icon'); const findLeftSecondaryInfos = () => wrapper.findByTestId('left-secondary-infos'); const findPublishMethod = () => wrapper.findComponent(PublishMethod); @@ -40,6 +47,7 @@ describe('packages_list_row', () => { provide = defaultProvide, } = {}) => { wrapper = shallowMountExtended(PackagesListRow, { + localVue, provide, stubs: { ListItem, @@ -63,6 +71,15 @@ describe('packages_list_row', () => { expect(wrapper.element).toMatchSnapshot(); }); + it('has a link to navigate to the details page', () => { + mountComponent(); + + expect(findPackageLink().props()).toMatchObject({ + event: 'click', + to: { name: 'details', params: { id: getIdFromGraphQLId(packageWithoutTags.id) } }, + }); + }); + describe('tags', () => { it('renders package tags when a package has tags', () => { mountComponent({ packageEntity: packageWithTags }); @@ -120,7 +137,7 @@ describe('packages_list_row', () => { }); it('details link is disabled', () => { - expect(findPackageLink().attributes('disabled')).toBe('true'); + expect(findPackageLink().props('event')).toBe(''); }); it('has a warning icon', () => { diff --git a/spec/frontend/packages_and_registries/package_registry/mock_data.js b/spec/frontend/packages_and_registries/package_registry/mock_data.js index 4c23b52b8a2..c6a59f20998 100644 --- a/spec/frontend/packages_and_registries/package_registry/mock_data.js +++ b/spec/frontend/packages_and_registries/package_registry/mock_data.js @@ -120,12 +120,22 @@ export const packageVersions = () => [ export const packageData = (extend) => ({ id: 'gid://gitlab/Packages::Package/111', + canDestroy: true, name: '@gitlab-org/package-15', packageType: 'NPM', version: '1.0.0', createdAt: '2020-08-17T14:23:32Z', updatedAt: '2020-08-17T14:23:32Z', status: 'DEFAULT', + mavenUrl: 'http://gdk.test:3000/api/v4/projects/1/packages/maven', + npmUrl: 'http://gdk.test:3000/api/v4/projects/1/packages/npm', + nugetUrl: 'http://gdk.test:3000/api/v4/projects/1/packages/nuget/index.json', + composerConfigRepositoryUrl: 'gdk.test/22', + composerUrl: 'http://gdk.test:3000/api/v4/group/22/-/packages/composer/packages.json', + conanUrl: 'http://gdk.test:3000/api/v4/projects/1/packages/conan', + pypiUrl: + 'http://__token__:<your_personal_token>@gdk.test:3000/api/v4/projects/1/packages/pypi/simple', + pypiSetupUrl: 'http://gdk.test:3000/api/v4/projects/1/packages/pypi', ...extend, }); @@ -185,6 +195,7 @@ export const packageDetailsQuery = (extendPackage) => ({ project: { id: '1', path: 'projectPath', + name: 'gitlab-test', }, tags: { nodes: packageTags(), diff --git a/spec/frontend/packages_and_registries/package_registry/pages/__snapshots__/list_spec.js.snap b/spec/frontend/packages_and_registries/package_registry/pages/__snapshots__/list_spec.js.snap index dbe3c70c3cb..ed96abe24b1 100644 --- a/spec/frontend/packages_and_registries/package_registry/pages/__snapshots__/list_spec.js.snap +++ b/spec/frontend/packages_and_registries/package_registry/pages/__snapshots__/list_spec.js.snap @@ -11,10 +11,10 @@ exports[`PackagesListApp renders 1`] = ` <div> <section - class="row empty-state text-center" + class="gl-display-flex empty-state gl-text-center gl-flex-direction-column" > <div - class="col-12" + class="gl-max-w-full" > <div class="svg-250 svg-content" @@ -29,10 +29,10 @@ exports[`PackagesListApp renders 1`] = ` </div> <div - class="col-12" + class="gl-max-w-full gl-m-auto" > <div - class="text-content gl-mx-auto gl-my-0 gl-p-5" + class="gl-mx-auto gl-my-0 gl-p-5" > <h1 class="gl-font-size-h-display gl-line-height-36 h4" diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/app_spec.js b/spec/frontend/packages_and_registries/package_registry/pages/details_spec.js index 0bea84693f6..637e2edf3be 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/details/app_spec.js +++ b/spec/frontend/packages_and_registries/package_registry/pages/details_spec.js @@ -9,7 +9,7 @@ import waitForPromises from 'helpers/wait_for_promises'; import createFlash from '~/flash'; import AdditionalMetadata from '~/packages_and_registries/package_registry/components/details/additional_metadata.vue'; -import PackagesApp from '~/packages_and_registries/package_registry/components/details/app.vue'; +import PackagesApp from '~/packages_and_registries/package_registry/pages/details.vue'; import DependencyRow from '~/packages_and_registries/package_registry/components/details/dependency_row.vue'; import InstallationCommands from '~/packages_and_registries/package_registry/components/details/installation_commands.vue'; import PackageFiles from '~/packages_and_registries/package_registry/components/details/package_files.vue'; @@ -36,7 +36,7 @@ import { packageFiles, packageDestroyFileMutation, packageDestroyFileMutationError, -} from '../../mock_data'; +} from '../mock_data'; jest.mock('~/flash'); useMockLocationHelper(); @@ -47,21 +47,22 @@ describe('PackagesApp', () => { let wrapper; let apolloProvider; + const breadCrumbState = { + updateName: jest.fn(), + }; + const provide = { packageId: '111', - titleComponent: 'PackageTitle', - projectName: 'projectName', - canDelete: 'canDelete', - svgPath: 'svgPath', - npmPath: 'npmPath', - npmHelpPath: 'npmHelpPath', + emptyListIllustration: 'svgPath', projectListUrl: 'projectListUrl', groupListUrl: 'groupListUrl', + breadCrumbState, }; function createComponent({ resolver = jest.fn().mockResolvedValue(packageDetailsQuery()), fileDeleteMutationResolver = jest.fn().mockResolvedValue(packageDestroyFileMutation()), + routeId = '1', } = {}) { localVue.use(VueApollo); @@ -87,6 +88,13 @@ describe('PackagesApp', () => { GlTabs, GlTab, }, + mocks: { + $route: { + params: { + id: routeId, + }, + }, + }, }); } @@ -149,7 +157,7 @@ describe('PackagesApp', () => { expect(findPackageHistory().exists()).toBe(true); expect(findPackageHistory().props()).toMatchObject({ packageEntity: expect.objectContaining(packageData()), - projectName: provide.projectName, + projectName: packageDetailsQuery().data.package.project.name, }); }); @@ -175,9 +183,18 @@ describe('PackagesApp', () => { }); }); + it('calls the appropriate function to set the breadcrumbState', async () => { + const { name, version } = packageData(); + createComponent(); + + await waitForPromises(); + + expect(breadCrumbState.updateName).toHaveBeenCalledWith(`${name} v ${version}`); + }); + describe('delete package', () => { const originalReferrer = document.referrer; - const setReferrer = (value = provide.projectName) => { + const setReferrer = (value = packageDetailsQuery().data.package.project.name) => { Object.defineProperty(document, 'referrer', { value, configurable: true, @@ -244,6 +261,7 @@ describe('PackagesApp', () => { expect(findPackageFiles().exists()).toBe(true); expect(findPackageFiles().props('packageFiles')[0]).toMatchObject(expectedFile); + expect(findPackageFiles().props('canDelete')).toBe(packageData().canDestroy); }); it('does not render the package files table when the package is composer', async () => { diff --git a/spec/frontend/packages_and_registries/shared/__snapshots__/publish_method_spec.js.snap b/spec/frontend/packages_and_registries/shared/components/__snapshots__/publish_method_spec.js.snap index 5f243799bae..5f243799bae 100644 --- a/spec/frontend/packages_and_registries/shared/__snapshots__/publish_method_spec.js.snap +++ b/spec/frontend/packages_and_registries/shared/components/__snapshots__/publish_method_spec.js.snap diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/components/__snapshots__/registry_breadcrumb_spec.js.snap b/spec/frontend/packages_and_registries/shared/components/__snapshots__/registry_breadcrumb_spec.js.snap index 7044c1285d8..ceae8eebaef 100644 --- a/spec/frontend/packages_and_registries/container_registry/explorer/components/__snapshots__/registry_breadcrumb_spec.js.snap +++ b/spec/frontend/packages_and_registries/shared/components/__snapshots__/registry_breadcrumb_spec.js.snap @@ -1,7 +1,8 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Registry Breadcrumb when is not rootRoute renders 1`] = ` -<div +<nav + aria-label="Breadcrumb" class="gl-breadcrumbs" > @@ -24,19 +25,25 @@ exports[`Registry Breadcrumb when is not rootRoute renders 1`] = ` class="gl-breadcrumb-separator" data-testid="separator" > - <svg - aria-hidden="true" - class="gl-icon s8" - data-testid="angle-right-icon" - role="img" + <span + class="gl-mx-n5" > - <use - href="#angle-right" - /> - </svg> + <svg + aria-hidden="true" + class="gl-icon s8" + data-testid="angle-right-icon" + role="img" + > + <use + href="#angle-right" + /> + </svg> + </span> </span> </a> </li> + + <!----> <li class="breadcrumb-item gl-breadcrumb-item" > @@ -52,12 +59,15 @@ exports[`Registry Breadcrumb when is not rootRoute renders 1`] = ` <!----> </a> </li> + + <!----> </ol> -</div> +</nav> `; exports[`Registry Breadcrumb when is rootRoute renders 1`] = ` -<div +<nav + aria-label="Breadcrumb" class="gl-breadcrumbs" > @@ -79,6 +89,8 @@ exports[`Registry Breadcrumb when is rootRoute renders 1`] = ` <!----> </a> </li> + + <!----> </ol> -</div> +</nav> `; diff --git a/spec/frontend/packages_and_registries/shared/package_icon_and_name_spec.js b/spec/frontend/packages_and_registries/shared/components/package_icon_and_name_spec.js index d6d1970cb12..d6d1970cb12 100644 --- a/spec/frontend/packages_and_registries/shared/package_icon_and_name_spec.js +++ b/spec/frontend/packages_and_registries/shared/components/package_icon_and_name_spec.js diff --git a/spec/frontend/packages_and_registries/shared/package_path_spec.js b/spec/frontend/packages_and_registries/shared/components/package_path_spec.js index 93425d4f399..93425d4f399 100644 --- a/spec/frontend/packages_and_registries/shared/package_path_spec.js +++ b/spec/frontend/packages_and_registries/shared/components/package_path_spec.js diff --git a/spec/frontend/packages_and_registries/shared/package_tags_spec.js b/spec/frontend/packages_and_registries/shared/components/package_tags_spec.js index 33e96c0775e..33e96c0775e 100644 --- a/spec/frontend/packages_and_registries/shared/package_tags_spec.js +++ b/spec/frontend/packages_and_registries/shared/components/package_tags_spec.js diff --git a/spec/frontend/packages_and_registries/shared/packages_list_loader_spec.js b/spec/frontend/packages_and_registries/shared/components/packages_list_loader_spec.js index 0005162e0bb..0005162e0bb 100644 --- a/spec/frontend/packages_and_registries/shared/packages_list_loader_spec.js +++ b/spec/frontend/packages_and_registries/shared/components/packages_list_loader_spec.js diff --git a/spec/frontend/packages_and_registries/shared/components/persisted_search_spec.js b/spec/frontend/packages_and_registries/shared/components/persisted_search_spec.js new file mode 100644 index 00000000000..bd492a5ae8f --- /dev/null +++ b/spec/frontend/packages_and_registries/shared/components/persisted_search_spec.js @@ -0,0 +1,145 @@ +import { nextTick } from 'vue'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import RegistrySearch from '~/vue_shared/components/registry/registry_search.vue'; +import component from '~/packages_and_registries/shared/components/persisted_search.vue'; +import UrlSync from '~/vue_shared/components/url_sync.vue'; +import { useMockLocationHelper } from 'helpers/mock_window_location_helper'; +import { getQueryParams, extractFilterAndSorting } from '~/packages_and_registries/shared/utils'; + +jest.mock('~/packages_and_registries/shared/utils'); + +useMockLocationHelper(); + +describe('Persisted Search', () => { + let wrapper; + + const defaultQueryParamsMock = { + filters: ['foo'], + sorting: { sort: 'desc', orderBy: 'test' }, + }; + + const defaultProps = { + sortableFields: [ + { orderBy: 'test', label: 'test' }, + { orderBy: 'foo', label: 'foo' }, + ], + defaultOrder: 'test', + defaultSort: 'asc', + }; + + const findRegistrySearch = () => wrapper.findComponent(RegistrySearch); + const findUrlSync = () => wrapper.findComponent(UrlSync); + + const mountComponent = (propsData = defaultProps) => { + wrapper = shallowMountExtended(component, { + propsData, + stubs: { + UrlSync, + }, + }); + }; + + beforeEach(() => { + extractFilterAndSorting.mockReturnValue(defaultQueryParamsMock); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + it('has a registry search component', async () => { + mountComponent(); + + await nextTick(); + + expect(findRegistrySearch().exists()).toBe(true); + }); + + it('registry search is mounted after mount', async () => { + mountComponent(); + + expect(findRegistrySearch().exists()).toBe(false); + }); + + it('has a UrlSync component', () => { + mountComponent(); + + expect(findUrlSync().exists()).toBe(true); + }); + + it('on sorting:changed emits update event and update internal sort', async () => { + const payload = { sort: 'desc', orderBy: 'test' }; + + mountComponent(); + + await nextTick(); + + findRegistrySearch().vm.$emit('sorting:changed', payload); + + await nextTick(); + + expect(findRegistrySearch().props('sorting')).toMatchObject(payload); + + // there is always a first call on mounted that emits up default values + expect(wrapper.emitted('update')[1]).toEqual([ + { + filters: ['foo'], + sort: 'TEST_DESC', + }, + ]); + }); + + it('on filter:changed updates the filters', async () => { + const payload = ['foo']; + + mountComponent(); + + await nextTick(); + + findRegistrySearch().vm.$emit('filter:changed', payload); + + await nextTick(); + + expect(findRegistrySearch().props('filter')).toEqual(['foo']); + }); + + it('on filter:submit emits update event', async () => { + mountComponent(); + + await nextTick(); + + findRegistrySearch().vm.$emit('filter:submit'); + + expect(wrapper.emitted('update')[1]).toEqual([ + { + filters: ['foo'], + sort: 'TEST_DESC', + }, + ]); + }); + + it('on query:changed calls updateQuery from UrlSync', async () => { + jest.spyOn(UrlSync.methods, 'updateQuery').mockImplementation(() => {}); + + mountComponent(); + + await nextTick(); + + findRegistrySearch().vm.$emit('query:changed'); + + expect(UrlSync.methods.updateQuery).toHaveBeenCalled(); + }); + + it('sets the component sorting and filtering based on the querystring', async () => { + mountComponent(); + + await nextTick(); + + expect(getQueryParams).toHaveBeenCalled(); + + expect(findRegistrySearch().props()).toMatchObject({ + filter: defaultQueryParamsMock.filters, + sorting: defaultQueryParamsMock.sorting, + }); + }); +}); diff --git a/spec/frontend/packages_and_registries/shared/publish_method_spec.js b/spec/frontend/packages_and_registries/shared/components/publish_method_spec.js index fa8f8f7641a..fa8f8f7641a 100644 --- a/spec/frontend/packages_and_registries/shared/publish_method_spec.js +++ b/spec/frontend/packages_and_registries/shared/components/publish_method_spec.js diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/components/registry_breadcrumb_spec.js b/spec/frontend/packages_and_registries/shared/components/registry_breadcrumb_spec.js index e5a8438f23f..6dfe116c285 100644 --- a/spec/frontend/packages_and_registries/container_registry/explorer/components/registry_breadcrumb_spec.js +++ b/spec/frontend/packages_and_registries/shared/components/registry_breadcrumb_spec.js @@ -1,6 +1,6 @@ import { mount } from '@vue/test-utils'; -import component from '~/packages_and_registries/container_registry/explorer/components/registry_breadcrumb.vue'; +import component from '~/packages_and_registries/shared/components/registry_breadcrumb.vue'; describe('Registry Breadcrumb', () => { let wrapper; |