diff options
Diffstat (limited to 'spec/frontend/packages_and_registries')
114 files changed, 1301 insertions, 1202 deletions
diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/components/delete_button_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/components/delete_button_spec.js index ff11c8843bb..8ba7e40d728 100644 --- a/spec/frontend/packages_and_registries/container_registry/explorer/components/delete_button_spec.js +++ b/spec/frontend/packages_and_registries/container_registry/explorer/components/delete_button_spec.js @@ -27,11 +27,6 @@ describe('delete_button', () => { }); }; - afterEach(() => { - wrapper.destroy(); - wrapper = null; - }); - describe('tooltip', () => { it('the title is controlled by tooltipTitle prop', () => { mountComponent(); diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/components/delete_image_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/components/delete_image_spec.js index 620c96e8c9e..5a7cbdcff5b 100644 --- a/spec/frontend/packages_and_registries/container_registry/explorer/components/delete_image_spec.js +++ b/spec/frontend/packages_and_registries/container_registry/explorer/components/delete_image_spec.js @@ -46,11 +46,6 @@ describe('Delete Image', () => { }); }; - afterEach(() => { - wrapper.destroy(); - wrapper = null; - }); - it('executes apollo mutate on doDelete', () => { const mutate = jest.fn().mockResolvedValue({}); mountComponent({ mutate }); diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/delete_modal_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/components/delete_modal_spec.js index 16c9485e69e..1eaabf1ad09 100644 --- a/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/delete_modal_spec.js +++ b/spec/frontend/packages_and_registries/container_registry/explorer/components/delete_modal_spec.js @@ -1,14 +1,14 @@ import { GlSprintf, GlFormInput } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; import { nextTick } from 'vue'; -import component from '~/packages_and_registries/container_registry/explorer/components/details_page/delete_modal.vue'; +import component from '~/packages_and_registries/container_registry/explorer/components/delete_modal.vue'; import { REMOVE_TAG_CONFIRMATION_TEXT, REMOVE_TAGS_CONFIRMATION_TEXT, DELETE_IMAGE_CONFIRMATION_TITLE, DELETE_IMAGE_CONFIRMATION_TEXT, } from '~/packages_and_registries/container_registry/explorer/constants'; -import { GlModal } from '../../stubs'; +import { GlModal } from '../stubs'; describe('Delete Modal', () => { let wrapper; @@ -30,15 +30,10 @@ describe('Delete Modal', () => { const expectPrimaryActionStatus = (disabled = true) => expect(findModal().props('actionPrimary')).toMatchObject( expect.objectContaining({ - attributes: [{ variant: 'danger' }, { disabled }], + attributes: { variant: 'danger', disabled }, }), ); - afterEach(() => { - wrapper.destroy(); - wrapper = null; - }); - it('contains a GlModal', () => { mountComponent(); expect(findModal().exists()).toBe(true); @@ -80,7 +75,7 @@ describe('Delete Modal', () => { }); describe('delete button', () => { - const itemsToBeDeleted = [{ project: { path: 'foo' } }]; + let itemsToBeDeleted = [{ project: { path: 'foo' } }]; it('is disabled by default', () => { mountComponent({ deleteImage: true }); @@ -107,6 +102,17 @@ describe('Delete Modal', () => { expectPrimaryActionStatus(false); }); + + it('if the user types the image name when available', async () => { + itemsToBeDeleted = [{ name: 'foo' }]; + mountComponent({ deleteImage: true, itemsToBeDeleted }); + + findInputComponent().vm.$emit('input', 'foo'); + + await nextTick(); + + expectPrimaryActionStatus(false); + }); }); }); diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/delete_alert_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/delete_alert_spec.js index d45b993b5a2..9d187439ca3 100644 --- a/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/delete_alert_spec.js +++ b/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/delete_alert_spec.js @@ -19,11 +19,6 @@ describe('Delete alert', () => { wrapper = shallowMount(component, { stubs: { GlSprintf }, propsData }); }; - afterEach(() => { - wrapper.destroy(); - wrapper = null; - }); - describe('when deleteAlertType is null', () => { it('does not show the alert', () => { mountComponent(); 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 b37edac83f7..01089422376 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,10 +1,10 @@ import { GlDropdownItem, GlIcon, GlDropdown } from '@gitlab/ui'; -import { shallowMount } from '@vue/test-utils'; import VueApollo from 'vue-apollo'; import Vue, { nextTick } from 'vue'; import { numberToHumanSize } from '~/lib/utils/number_utils'; import { useFakeDate } from 'helpers/fake_date'; import createMockApollo from 'helpers/mock_apollo_helper'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; import waitForPromises from 'helpers/wait_for_promises'; import component from '~/packages_and_registries/container_registry/explorer/components/details_page/details_header.vue'; @@ -22,37 +22,27 @@ import { } from '~/packages_and_registries/container_registry/explorer/constants'; import getContainerRepositoryMetadata from '~/packages_and_registries/container_registry/explorer/graphql/queries/get_container_repository_metadata.query.graphql'; import TitleArea from '~/vue_shared/components/registry/title_area.vue'; -import { imageTagsCountMock } from '../../mock_data'; +import { containerRepositoryMock, imageTagsCountMock } from '../../mock_data'; describe('Details Header', () => { let wrapper; let apolloProvider; const defaultImage = { - name: 'foo', - updatedAt: '2020-11-03T13:29:21Z', - canDelete: true, - project: { - visibility: 'public', - path: 'path', - containerExpirationPolicy: { - enabled: false, - }, - }, + ...containerRepositoryMock, }; // set the date to Dec 4, 2020 useFakeDate(2020, 11, 4); - const findByTestId = (testId) => wrapper.find(`[data-testid="${testId}"]`); - const findLastUpdatedAndVisibility = () => findByTestId('updated-and-visibility'); - const findTitle = () => findByTestId('title'); - const findTagsCount = () => findByTestId('tags-count'); - const findCleanup = () => findByTestId('cleanup'); + const findCreatedAndVisibility = () => wrapper.findByTestId('created-and-visibility'); + const findTitle = () => wrapper.findByTestId('title'); + const findTagsCount = () => wrapper.findByTestId('tags-count'); + const findCleanup = () => wrapper.findByTestId('cleanup'); const findDeleteButton = () => wrapper.findComponent(GlDropdownItem); const findInfoIcon = () => wrapper.findComponent(GlIcon); const findMenu = () => wrapper.findComponent(GlDropdown); - const findSize = () => findByTestId('image-size'); + const findSize = () => wrapper.findByTestId('image-size'); const waitForMetadataItems = async () => { // Metadata items are printed by a loop in the title-area and it takes two ticks for them to be available @@ -69,11 +59,11 @@ describe('Details Header', () => { const requestHandlers = [[getContainerRepositoryMetadata, resolver]]; apolloProvider = createMockApollo(requestHandlers); - wrapper = shallowMount(component, { + wrapper = shallowMountExtended(component, { apolloProvider, propsData, directives: { - GlTooltip: createMockDirective(), + GlTooltip: createMockDirective('gl-tooltip'), }, stubs: { TitleArea, @@ -85,9 +75,7 @@ describe('Details Header', () => { afterEach(() => { // if we want to mix createMockApollo and manual mocks we need to reset everything - wrapper.destroy(); apolloProvider = undefined; - wrapper = null; }); describe('image name', () => { @@ -99,7 +87,7 @@ describe('Details Header', () => { }); it('root image shows project path name', () => { - expect(findTitle().text()).toBe('path'); + expect(findTitle().text()).toBe('gitlab-test'); }); it('has an icon', () => { @@ -121,7 +109,7 @@ describe('Details Header', () => { }); it('shows image.name', () => { - expect(findTitle().text()).toContain('foo'); + expect(findTitle().text()).toContain('rails-12009'); }); it('has no icon', () => { @@ -249,7 +237,7 @@ describe('Details Header', () => { expect(findCleanup().props('icon')).toBe('expire'); }); - it('when the expiration policy is disabled', async () => { + it('when cleanup is not scheduled', async () => { mountComponent(); await waitForMetadataItems(); @@ -289,12 +277,12 @@ describe('Details Header', () => { ); }); - describe('visibility and updated at', () => { - it('has last updated text', async () => { + describe('visibility and created at', () => { + it('has created text', async () => { mountComponent(); await waitForMetadataItems(); - expect(findLastUpdatedAndVisibility().props('text')).toBe('Last updated 1 month ago'); + expect(findCreatedAndVisibility().props('text')).toBe('Created Nov 3, 2020 13:29'); }); describe('visibility icon', () => { @@ -302,7 +290,7 @@ describe('Details Header', () => { mountComponent(); await waitForMetadataItems(); - expect(findLastUpdatedAndVisibility().props('icon')).toBe('eye'); + expect(findCreatedAndVisibility().props('icon')).toBe('eye'); }); it('shows an eye slashed when the project is not public', async () => { mountComponent({ @@ -310,7 +298,7 @@ describe('Details Header', () => { }); await waitForMetadataItems(); - expect(findLastUpdatedAndVisibility().props('icon')).toBe('eye-slash'); + expect(findCreatedAndVisibility().props('icon')).toBe('eye-slash'); }); }); }); diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/partial_cleanup_alert_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/partial_cleanup_alert_spec.js index ce5ecfe4608..d6c1b2c3f51 100644 --- a/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/partial_cleanup_alert_spec.js +++ b/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/partial_cleanup_alert_spec.js @@ -23,11 +23,6 @@ describe('Partial Cleanup alert', () => { }); }; - afterEach(() => { - wrapper.destroy(); - wrapper = null; - }); - it(`gl-alert has the correct properties`, () => { mountComponent(); diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/status_alert_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/status_alert_spec.js index d83a5099bcd..3e1fd14475d 100644 --- a/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/status_alert_spec.js +++ b/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/status_alert_spec.js @@ -27,11 +27,6 @@ describe('Status Alert', () => { }); }; - afterEach(() => { - wrapper.destroy(); - wrapper = null; - }); - it.each` status | title | variant | message | link ${DELETE_SCHEDULED} | ${SCHEDULED_FOR_DELETION_STATUS_TITLE} | ${'info'} | ${SCHEDULED_FOR_DELETION_STATUS_MESSAGE} | ${PACKAGE_DELETE_HELP_PAGE_PATH} 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 fa0d76762df..f74dfcb029d 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 @@ -50,16 +50,11 @@ describe('tags list row', () => { }, propsData, directives: { - GlTooltip: createMockDirective(), + GlTooltip: createMockDirective('gl-tooltip'), }, }); }; - afterEach(() => { - wrapper.destroy(); - wrapper = null; - }); - describe('checkbox', () => { it('exists', () => { mountComponent(); @@ -158,7 +153,7 @@ describe('tags list row', () => { it('is disabled when the component is disabled', () => { mountComponent({ ...defaultProps, disabled: true }); - expect(findClipboardButton().attributes('disabled')).toBe('true'); + expect(findClipboardButton().attributes('disabled')).toBeDefined(); }); }); @@ -283,26 +278,30 @@ describe('tags list row', () => { textSrOnly: true, category: 'tertiary', right: true, + disabled: false, }); }); - it.each` - canDelete | digest | disabled | buttonDisabled - ${true} | ${null} | ${true} | ${true} - ${false} | ${'foo'} | ${true} | ${true} - ${false} | ${null} | ${true} | ${true} - ${true} | ${'foo'} | ${true} | ${true} - ${true} | ${'foo'} | ${false} | ${false} - `( - 'is $visible that is visible when canDelete is $canDelete and digest is $digest and disabled is $disabled', - ({ canDelete, digest, disabled, buttonDisabled }) => { - mountComponent({ ...defaultProps, tag: { ...tag, canDelete, digest }, disabled }); + it('has the correct classes', () => { + mountComponent(); - expect(findAdditionalActionsMenu().props('disabled')).toBe(buttonDisabled); - expect(findAdditionalActionsMenu().classes('gl-opacity-0')).toBe(buttonDisabled); - expect(findAdditionalActionsMenu().classes('gl-pointer-events-none')).toBe(buttonDisabled); - }, - ); + expect(findAdditionalActionsMenu().classes('gl-opacity-0')).toBe(false); + expect(findAdditionalActionsMenu().classes('gl-pointer-events-none')).toBe(false); + }); + + it('is not rendered when tag.canDelete is false', () => { + mountComponent({ ...defaultProps, tag: { ...tag, canDelete: false } }); + + expect(findAdditionalActionsMenu().exists()).toBe(false); + }); + + it('is hidden when disabled prop is set to true', () => { + mountComponent({ ...defaultProps, disabled: true }); + + expect(findAdditionalActionsMenu().props('disabled')).toBe(true); + expect(findAdditionalActionsMenu().classes('gl-opacity-0')).toBe(true); + expect(findAdditionalActionsMenu().classes('gl-pointer-events-none')).toBe(true); + }); describe('delete button', () => { it('exists and has the correct attrs', () => { 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 1017ff06a25..0cbb9eab018 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 @@ -4,13 +4,15 @@ 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 Tracking from '~/tracking'; 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/shared/components/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 deleteContainerRepositoryTagsMutation from '~/packages_and_registries/container_registry/explorer/graphql/mutations/delete_container_repository_tags.mutation.graphql'; + import { GRAPHQL_PAGE_SIZE, NO_TAGS_TITLE, @@ -19,7 +21,13 @@ import { NO_TAGS_MATCHING_FILTERS_DESCRIPTION, } from '~/packages_and_registries/container_registry/explorer/constants/index'; import { FILTERED_SEARCH_TERM } from '~/vue_shared/components/filtered_search_bar/constants'; -import { tagsMock, imageTagsMock, tagsPageInfo } from '../../mock_data'; +import { + graphQLDeleteImageRepositoryTagsMock, + tagsMock, + imageTagsMock, + tagsPageInfo, +} from '../../mock_data'; +import { DeleteModal } from '../../stubs'; describe('Tags List', () => { let wrapper; @@ -31,6 +39,7 @@ describe('Tags List', () => { noContainersImage: 'noContainersImage', }; + const findDeleteModal = () => wrapper.findComponent(DeleteModal); const findPersistedSearch = () => wrapper.findComponent(PersistedSearch); const findTagsListRow = () => wrapper.findAllComponents(TagsListRow); const findRegistryList = () => wrapper.findComponent(RegistryList); @@ -42,20 +51,23 @@ describe('Tags List', () => { }; const waitForApolloRequestRender = async () => { + fireFirstSortUpdate(); await waitForPromises(); - await nextTick(); }; - const mountComponent = ({ propsData = { isMobile: false, id: 1 } } = {}) => { + const mountComponent = ({ propsData = { isMobile: false, id: 1 }, mutationResolver } = {}) => { Vue.use(VueApollo); - const requestHandlers = [[getContainerRepositoryTagsQuery, resolver]]; + const requestHandlers = [ + [getContainerRepositoryTagsQuery, resolver], + [deleteContainerRepositoryTagsMutation, mutationResolver], + ]; apolloProvider = createMockApollo(requestHandlers); wrapper = shallowMount(component, { apolloProvider, propsData, - stubs: { RegistryList }, + stubs: { RegistryList, DeleteModal }, provide() { return { config: defaultConfig, @@ -66,17 +78,13 @@ describe('Tags List', () => { beforeEach(() => { resolver = jest.fn().mockResolvedValue(imageTagsMock()); - }); - - afterEach(() => { - wrapper.destroy(); + jest.spyOn(Tracking, 'event'); }); describe('registry list', () => { - beforeEach(() => { + beforeEach(async () => { mountComponent(); - fireFirstSortUpdate(); - return waitForApolloRequestRender(); + await waitForApolloRequestRender(); }); it('has a persisted search', () => { @@ -98,6 +106,7 @@ describe('Tags List', () => { pagination: tagsPageInfo, items: tags, idProperty: 'name', + hiddenDelete: false, }); }); @@ -129,11 +138,46 @@ describe('Tags List', () => { }); }); - it('emits a delete event when list emits delete', () => { - const eventPayload = 'foo'; - findRegistryList().vm.$emit('delete', eventPayload); + describe('delete event', () => { + describe('single item', () => { + beforeEach(() => { + findRegistryList().vm.$emit('delete', [tags[0]]); + }); + + it('opens the modal', () => { + expect(DeleteModal.methods.show).toHaveBeenCalled(); + }); + + it('sets modal props', () => { + expect(findDeleteModal().props('itemsToBeDeleted')).toMatchObject([tags[0]]); + }); + + it('tracks a single delete event', () => { + expect(Tracking.event).toHaveBeenCalledWith(undefined, 'click_button', { + label: 'registry_tag_delete', + }); + }); + }); - expect(wrapper.emitted('delete')).toEqual([[eventPayload]]); + describe('multiple items', () => { + beforeEach(() => { + findRegistryList().vm.$emit('delete', tags); + }); + + it('opens the modal', () => { + expect(DeleteModal.methods.show).toHaveBeenCalled(); + }); + + it('sets modal props', () => { + expect(findDeleteModal().props('itemsToBeDeleted')).toMatchObject(tags); + }); + + it('tracks multiple delete event', () => { + expect(Tracking.event).toHaveBeenCalledWith(undefined, 'click_button', { + label: 'bulk_registry_tag_delete', + }); + }); + }); }); }); }); @@ -141,7 +185,6 @@ describe('Tags List', () => { describe('list rows', () => { it('one row exist for each tag', async () => { mountComponent(); - fireFirstSortUpdate(); await waitForApolloRequestRender(); @@ -150,7 +193,6 @@ describe('Tags List', () => { it('the correct props are bound to it', async () => { mountComponent({ propsData: { disabled: true, id: 1 } }); - fireFirstSortUpdate(); await waitForApolloRequestRender(); @@ -165,7 +207,6 @@ describe('Tags List', () => { describe('events', () => { it('select event update the selected items', async () => { mountComponent(); - fireFirstSortUpdate(); await waitForApolloRequestRender(); findTagsListRow().at(0).vm.$emit('select'); @@ -175,23 +216,63 @@ describe('Tags List', () => { expect(findTagsListRow().at(0).attributes('selected')).toBe('true'); }); - it('delete event emit a delete event', async () => { - mountComponent(); - fireFirstSortUpdate(); - await waitForApolloRequestRender(); + describe('delete event', () => { + let mutationResolver; - findTagsListRow().at(0).vm.$emit('delete'); - expect(wrapper.emitted('delete')[0][0][0].name).toBe(tags[0].name); + beforeEach(async () => { + mutationResolver = jest.fn().mockResolvedValue(graphQLDeleteImageRepositoryTagsMock); + resolver = jest.fn().mockResolvedValue(imageTagsMock()); + mountComponent({ mutationResolver }); + + await waitForApolloRequestRender(); + findTagsListRow().at(0).vm.$emit('delete'); + }); + + it('opens the modal', () => { + expect(DeleteModal.methods.show).toHaveBeenCalled(); + }); + + it('tracks a single delete event', () => { + expect(Tracking.event).toHaveBeenCalledWith(undefined, 'click_button', { + label: 'registry_tag_delete', + }); + }); + + it('confirmDelete event calls apollo mutation with the right parameters and refetches the tags list query', async () => { + findDeleteModal().vm.$emit('confirmDelete'); + + expect(mutationResolver).toHaveBeenCalledWith( + expect.objectContaining({ tagNames: [tags[0].name] }), + ); + + await waitForPromises(); + + expect(resolver).toHaveBeenLastCalledWith({ + first: GRAPHQL_PAGE_SIZE, + name: '', + sort: 'NAME_ASC', + id: '1', + }); + }); }); }); }); + describe('when user does not have permission to delete list rows', () => { + it('sets registry list hiddenDelete prop to true', async () => { + resolver = jest.fn().mockResolvedValue(imageTagsMock({ canDelete: false })); + mountComponent(); + await waitForApolloRequestRender(); + + expect(findRegistryList().props('hiddenDelete')).toBe(true); + }); + }); + describe('when the list of tags is empty', () => { - beforeEach(() => { - resolver = jest.fn().mockResolvedValue(imageTagsMock([])); + beforeEach(async () => { + resolver = jest.fn().mockResolvedValue(imageTagsMock({ nodes: [] })); mountComponent(); - fireFirstSortUpdate(); - return waitForApolloRequestRender(); + await waitForApolloRequestRender(); }); it('does not show the loader', () => { @@ -217,7 +298,7 @@ describe('Tags List', () => { filters: [{ type: FILTERED_SEARCH_TERM, value: { data: 'foo' } }], }); - await waitForApolloRequestRender(); + await waitForPromises(); expect(findEmptyState().props()).toMatchObject({ svgPath: defaultConfig.noContainersImage, @@ -228,6 +309,175 @@ describe('Tags List', () => { }); }); + describe('modal', () => { + it('exists', async () => { + mountComponent(); + await waitForApolloRequestRender(); + + expect(findDeleteModal().exists()).toBe(true); + }); + + describe('cancel event', () => { + it('tracks cancel_delete', async () => { + mountComponent(); + await waitForApolloRequestRender(); + + findDeleteModal().vm.$emit('cancel'); + + expect(Tracking.event).toHaveBeenCalledWith(undefined, 'cancel_delete', { + label: 'registry_tag_delete', + }); + }); + }); + + describe('confirmDelete event', () => { + let mutationResolver; + + describe('when mutation', () => { + beforeEach(() => { + mutationResolver = jest.fn().mockResolvedValue(graphQLDeleteImageRepositoryTagsMock); + mountComponent({ mutationResolver }); + + return waitForApolloRequestRender(); + }); + + it('is started renders loader', async () => { + findRegistryList().vm.$emit('delete', [tags[0]]); + + findDeleteModal().vm.$emit('confirmDelete'); + await nextTick(); + + expect(findTagsLoader().exists()).toBe(true); + expect(findTagsListRow().exists()).toBe(false); + }); + + it('ends, loader is hidden', async () => { + findRegistryList().vm.$emit('delete', [tags[0]]); + + findDeleteModal().vm.$emit('confirmDelete'); + await waitForPromises(); + + expect(findTagsLoader().exists()).toBe(false); + expect(findTagsListRow().exists()).toBe(true); + }); + }); + + describe.each([ + { + description: 'rejection', + mutationMock: jest.fn().mockRejectedValue(), + }, + { + description: 'error', + mutationMock: jest.fn().mockResolvedValue({ + data: { + destroyContainerRepositoryTags: { + errors: [new Error()], + }, + }, + }), + }, + ])('when mutation fails with $description', ({ mutationMock }) => { + beforeEach(() => { + mutationResolver = mutationMock; + mountComponent({ mutationResolver }); + + return waitForApolloRequestRender(); + }); + + it('when one item is selected to be deleted calls apollo mutation with the right parameters and emits delete event with right arguments', async () => { + findRegistryList().vm.$emit('delete', [tags[0]]); + + resolver.mockClear(); + + findDeleteModal().vm.$emit('confirmDelete'); + + expect(mutationResolver).toHaveBeenCalledWith( + expect.objectContaining({ tagNames: [tags[0].name] }), + ); + + expect(resolver).not.toHaveBeenCalled(); + + await waitForPromises(); + + expect(wrapper.emitted('delete')).toHaveLength(1); + expect(wrapper.emitted('delete')[0][0]).toEqual('danger_tag'); + }); + + it('when more than one item is selected to be deleted calls apollo mutation with the right parameters and emits delete event with right arguments', async () => { + findRegistryList().vm.$emit('delete', tagsMock); + resolver.mockClear(); + + findDeleteModal().vm.$emit('confirmDelete'); + + expect(mutationResolver).toHaveBeenCalledWith( + expect.objectContaining({ tagNames: tagsMock.map((t) => t.name) }), + ); + + expect(resolver).not.toHaveBeenCalled(); + + await waitForPromises(); + + expect(wrapper.emitted('delete')).toHaveLength(1); + expect(wrapper.emitted('delete')[0][0]).toEqual('danger_tags'); + }); + }); + + describe('when mutation is successful', () => { + beforeEach(() => { + mutationResolver = jest.fn().mockResolvedValue(graphQLDeleteImageRepositoryTagsMock); + mountComponent({ mutationResolver }); + + return waitForApolloRequestRender(); + }); + + it('and one item is selected to be deleted calls apollo mutation with the right parameters and refetches the tags list query', async () => { + findRegistryList().vm.$emit('delete', [tags[0]]); + + findDeleteModal().vm.$emit('confirmDelete'); + + expect(mutationResolver).toHaveBeenCalledWith( + expect.objectContaining({ tagNames: [tags[0].name] }), + ); + + expect(resolver).toHaveBeenLastCalledWith({ + first: GRAPHQL_PAGE_SIZE, + name: '', + sort: 'NAME_ASC', + id: '1', + }); + + await waitForPromises(); + + expect(wrapper.emitted('delete')).toHaveLength(1); + expect(wrapper.emitted('delete')[0][0]).toEqual('success_tag'); + }); + + it('and more than one item is selected to be deleted calls apollo mutation with the right parameters and refetches the tags list query', async () => { + findRegistryList().vm.$emit('delete', tagsMock); + + findDeleteModal().vm.$emit('confirmDelete'); + + expect(mutationResolver).toHaveBeenCalledWith( + expect.objectContaining({ tagNames: tagsMock.map((t) => t.name) }), + ); + + expect(resolver).toHaveBeenLastCalledWith({ + first: GRAPHQL_PAGE_SIZE, + name: '', + sort: 'NAME_ASC', + id: '1', + }); + + await waitForPromises(); + + expect(wrapper.emitted('delete')).toHaveLength(1); + expect(wrapper.emitted('delete')[0][0]).toEqual('success_tags'); + }); + }); + }); + }); + describe('loading state', () => { it.each` isImageLoading | queryExecuting | loadingVisible @@ -239,7 +489,6 @@ 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/components/details_page/tags_loader_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/tags_loader_spec.js index 88e79c513bc..8896185ce67 100644 --- a/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/tags_loader_spec.js +++ b/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/tags_loader_spec.js @@ -20,11 +20,6 @@ describe('TagsLoader component', () => { }); }; - afterEach(() => { - wrapper.destroy(); - wrapper = null; - }); - it('produces the correct amount of loaders', () => { mountComponent(); expect(findGlSkeletonLoaders().length).toBe(1); diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/cleanup_status_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/cleanup_status_spec.js index 535faebdd4e..0d1d2c53cab 100644 --- a/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/cleanup_status_spec.js +++ b/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/cleanup_status_spec.js @@ -36,10 +36,6 @@ describe('cleanup_status', () => { }); }; - afterEach(() => { - wrapper.destroy(); - }); - it.each` status | visible | text ${UNFINISHED_STATUS} | ${true} | ${CLEANUP_STATUS_UNFINISHED} diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/group_empty_state_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/group_empty_state_spec.js index d2086943e4f..900ea61e4ea 100644 --- a/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/group_empty_state_spec.js +++ b/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/group_empty_state_spec.js @@ -26,10 +26,6 @@ describe('Registry Group Empty state', () => { }); }); - afterEach(() => { - wrapper.destroy(); - }); - it('to match the default snapshot', () => { expect(wrapper.element).toMatchSnapshot(); }); diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/image_list_row_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/image_list_row_spec.js index 75068591007..5d8df45415e 100644 --- a/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/image_list_row_spec.js +++ b/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/image_list_row_spec.js @@ -1,4 +1,4 @@ -import { GlIcon, GlSprintf, GlSkeletonLoader, GlButton } from '@gitlab/ui'; +import { GlSprintf, GlSkeletonLoader, GlButton } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; import { createMockDirective } from 'helpers/vue_mock_directive'; import { mockTracking } from 'helpers/tracking_helper'; @@ -49,16 +49,11 @@ describe('Image List Row', () => { config: {}, }, directives: { - GlTooltip: createMockDirective(), + GlTooltip: createMockDirective('gl-tooltip'), }, }); }; - afterEach(() => { - wrapper.destroy(); - wrapper = null; - }); - describe('image title and path', () => { it('renders shortened name of image and contains a link to the details page', () => { mountComponent(); @@ -150,7 +145,7 @@ describe('Image List Row', () => { }); it('the clipboard button is disabled', () => { - expect(findClipboardButton().attributes('disabled')).toBe('true'); + expect(findClipboardButton().attributes('disabled')).toBeDefined(); }); }); }); @@ -206,13 +201,6 @@ describe('Image List Row', () => { expect(findTagsCount().exists()).toBe(true); }); - it('contains a tag icon', () => { - mountComponent(); - const icon = findTagsCount().findComponent(GlIcon); - expect(icon.exists()).toBe(true); - expect(icon.props('name')).toBe('tag'); - }); - describe('loading state', () => { it('shows a loader when metadataLoading is true', () => { mountComponent({ metadataLoading: true }); @@ -231,12 +219,12 @@ describe('Image List Row', () => { it('with one tag in the image', () => { mountComponent({ item: { ...item, tagsCount: 1 } }); - expect(findTagsCount().text()).toMatchInterpolatedText('1 Tag'); + expect(findTagsCount().text()).toMatchInterpolatedText('1 tag'); }); it('with more than one tag in the image', () => { mountComponent({ item: { ...item, tagsCount: 3 } }); - expect(findTagsCount().text()).toMatchInterpolatedText('3 Tags'); + expect(findTagsCount().text()).toMatchInterpolatedText('3 tags'); }); }); }); diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/image_list_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/image_list_spec.js index 042b8383571..6c771887b88 100644 --- a/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/image_list_spec.js +++ b/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/image_list_spec.js @@ -21,11 +21,6 @@ describe('Image List', () => { }); }; - afterEach(() => { - wrapper.destroy(); - wrapper = null; - }); - describe('list', () => { it('contains one list element for each image', () => { mountComponent(); diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/project_empty_state_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/project_empty_state_spec.js index 8cfa8128021..e4d13143484 100644 --- a/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/project_empty_state_spec.js +++ b/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/project_empty_state_spec.js @@ -34,10 +34,6 @@ describe('Registry Project Empty state', () => { }); }); - afterEach(() => { - wrapper.destroy(); - }); - it('to match the default snapshot', () => { expect(wrapper.element).toMatchSnapshot(); }); diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/registry_header_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/registry_header_spec.js index bcc8e41fce8..b7f3698e155 100644 --- a/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/registry_header_spec.js +++ b/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/registry_header_spec.js @@ -35,11 +35,6 @@ describe('registry_header', () => { await nextTick(); }; - afterEach(() => { - wrapper.destroy(); - wrapper = null; - }); - describe('header', () => { it('has a title', () => { mountComponent({ metadataLoading: true }); @@ -86,7 +81,7 @@ describe('registry_header', () => { }); }); - describe('expiration policy', () => { + describe('cleanup policy', () => { it('when is disabled', async () => { await mountComponent({ expirationPolicy: { enabled: false }, @@ -116,11 +111,11 @@ describe('registry_header', () => { const cleanupLink = findSetupCleanUpLink(); expect(text.exists()).toBe(true); - expect(text.props('text')).toBe('Expiration policy will run in '); + expect(text.props('text')).toBe('Cleanup will run in '); expect(cleanupLink.exists()).toBe(true); expect(cleanupLink.text()).toBe(SET_UP_CLEANUP); }); - it('when the expiration policy is completely disabled', async () => { + it('when the cleanup policy is not scheduled', async () => { await mountComponent({ expirationPolicy: { enabled: true }, expirationPolicyHelpPagePath: 'foo', diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/mock_data.js b/spec/frontend/packages_and_registries/container_registry/explorer/mock_data.js index e5b99f15e8c..8ca74f5077e 100644 --- a/spec/frontend/packages_and_registries/container_registry/explorer/mock_data.js +++ b/spec/frontend/packages_and_registries/container_registry/explorer/mock_data.js @@ -127,7 +127,6 @@ export const containerRepositoryMock = { location: 'host.docker.internal:5000/gitlab-org/gitlab-test/rails-12009', canDelete: true, createdAt: '2020-11-03T13:29:21Z', - updatedAt: '2020-11-03T13:29:21Z', expirationPolicyStartedAt: null, expirationPolicyCleanupStatus: 'UNSCHEDULED', project: { @@ -177,11 +176,12 @@ export const tagsMock = [ }, ]; -export const imageTagsMock = (nodes = tagsMock) => ({ +export const imageTagsMock = ({ nodes = tagsMock, canDelete = true } = {}) => ({ data: { containerRepository: { id: containerRepositoryMock.id, tagsCount: nodes.length, + canDelete, tags: { nodes, pageInfo: { ...tagsPageInfo }, 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 26f0e506829..7fed81acead 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 @@ -22,22 +22,15 @@ import { 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'; -import getContainerRepositoryTagsQuery from '~/packages_and_registries/container_registry/explorer/graphql/queries/get_container_repository_tags.query.graphql'; -import getContainerRepositoriesDetails from '~/packages_and_registries/container_registry/explorer/graphql/queries/get_container_repositories_details.query.graphql'; import component from '~/packages_and_registries/container_registry/explorer/pages/details.vue'; import Tracking from '~/tracking'; import { graphQLImageDetailsMock, - graphQLDeleteImageRepositoryTagsMock, - graphQLProjectImageRepositoriesDetailsMock, containerRepositoryMock, graphQLEmptyImageDetailsMock, - tagsMock, - imageTagsMock, } from '../mock_data'; import { DeleteModal } from '../stubs'; @@ -69,13 +62,6 @@ describe('Details Page', () => { isGroupPage: false, }; - const cleanTags = tagsMock.map((t) => { - const result = { ...t }; - // eslint-disable-next-line no-underscore-dangle - delete result.__typename; - return result; - }); - const waitForApolloRequestRender = async () => { await waitForPromises(); await nextTick(); @@ -83,20 +69,12 @@ describe('Details Page', () => { const mountComponent = ({ resolver = jest.fn().mockResolvedValue(graphQLImageDetailsMock()), - mutationResolver = jest.fn().mockResolvedValue(graphQLDeleteImageRepositoryTagsMock), - tagsResolver = jest.fn().mockResolvedValue(graphQLImageDetailsMock(imageTagsMock())), - detailsResolver = jest.fn().mockResolvedValue(graphQLProjectImageRepositoriesDetailsMock), options, config = defaultConfig, } = {}) => { Vue.use(VueApollo); - const requestHandlers = [ - [getContainerRepositoryDetailsQuery, resolver], - [deleteContainerRepositoryTagsMutation, mutationResolver], - [getContainerRepositoryTagsQuery, tagsResolver], - [getContainerRepositoriesDetails, detailsResolver], - ]; + const requestHandlers = [[getContainerRepositoryDetailsQuery, resolver]]; apolloProvider = createMockApollo(requestHandlers); @@ -127,11 +105,6 @@ describe('Details Page', () => { jest.spyOn(Tracking, 'event'); }); - afterEach(() => { - wrapper.destroy(); - wrapper = null; - }); - describe('when isLoading is true', () => { it('shows the loader', () => { mountComponent(); @@ -189,50 +162,6 @@ describe('Details Page', () => { isMobile: false, }); }); - - describe('deleteEvent', () => { - describe('single item', () => { - let tagToBeDeleted; - beforeEach(async () => { - mountComponent(); - - await waitForApolloRequestRender(); - - [tagToBeDeleted] = cleanTags; - findTagsList().vm.$emit('delete', [tagToBeDeleted]); - }); - - it('open the modal', async () => { - expect(DeleteModal.methods.show).toHaveBeenCalled(); - }); - - it('tracks a single delete event', () => { - expect(Tracking.event).toHaveBeenCalledWith(undefined, 'click_button', { - label: 'registry_tag_delete', - }); - }); - }); - - describe('multiple items', () => { - beforeEach(async () => { - mountComponent(); - - await waitForApolloRequestRender(); - - findTagsList().vm.$emit('delete', cleanTags); - }); - - it('open the modal', () => { - expect(DeleteModal.methods.show).toHaveBeenCalled(); - }); - - it('tracks a single delete event', () => { - expect(Tracking.event).toHaveBeenCalledWith(undefined, 'click_button', { - label: 'bulk_registry_tag_delete', - }); - }); - }); - }); }); describe('modal', () => { @@ -253,61 +182,24 @@ describe('Details Page', () => { findDeleteModal().vm.$emit('cancel'); expect(Tracking.event).toHaveBeenCalledWith(undefined, 'cancel_delete', { - label: 'registry_tag_delete', + label: 'registry_image_delete', }); }); }); - describe('confirmDelete event', () => { - let mutationResolver; - let tagsResolver; - let detailsResolver; - + describe('tags list delete event', () => { beforeEach(() => { - mutationResolver = jest.fn().mockResolvedValue(graphQLDeleteImageRepositoryTagsMock); - tagsResolver = jest.fn().mockResolvedValue(graphQLImageDetailsMock(imageTagsMock())); - detailsResolver = jest.fn().mockResolvedValue(graphQLProjectImageRepositoriesDetailsMock); - mountComponent({ mutationResolver, tagsResolver, detailsResolver }); + mountComponent(); return waitForApolloRequestRender(); }); - describe('when one item is selected to be deleted', () => { - it('calls apollo mutation with the right parameters and refetches the tags list query', async () => { - findTagsList().vm.$emit('delete', [cleanTags[0]]); - - await nextTick(); - - findDeleteModal().vm.$emit('confirmDelete'); - - expect(mutationResolver).toHaveBeenCalledWith( - expect.objectContaining({ tagNames: [cleanTags[0].name] }), - ); - - await waitForPromises(); + it('sets delete alert modal deleteAlertType value', async () => { + findTagsList().vm.$emit('delete', 'success_tag'); - expect(tagsResolver).toHaveBeenCalled(); - expect(detailsResolver).toHaveBeenCalled(); - }); - }); - - describe('when more than one item is selected to be deleted', () => { - it('calls apollo mutation with the right parameters and refetches the tags list query', async () => { - findTagsList().vm.$emit('delete', tagsMock); - - await nextTick(); - - findDeleteModal().vm.$emit('confirmDelete'); - - expect(mutationResolver).toHaveBeenCalledWith( - expect.objectContaining({ tagNames: tagsMock.map((t) => t.name) }), - ); - - await waitForPromises(); + await nextTick(); - expect(tagsResolver).toHaveBeenCalled(); - expect(detailsResolver).toHaveBeenCalled(); - }); + expect(findDeleteAlert().props('deleteAlertType')).toBe('success_tag'); }); }); }); diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/pages/list_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/pages/list_spec.js index 1e514d85e82..1823bbfe533 100644 --- a/spec/frontend/packages_and_registries/container_registry/explorer/pages/list_spec.js +++ b/spec/frontend/packages_and_registries/container_registry/explorer/pages/list_spec.js @@ -1,8 +1,8 @@ -import { GlSkeletonLoader, GlSprintf, GlAlert } from '@gitlab/ui'; +import { GlSkeletonLoader, GlSprintf, GlAlert, GlButton } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; import Vue, { nextTick } from 'vue'; - import VueApollo from 'vue-apollo'; +import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; import createMockApollo from 'helpers/mock_apollo_helper'; import waitForPromises from 'helpers/wait_for_promises'; import getContainerRepositoriesQuery from 'shared_queries/container_registry/get_container_repositories.query.graphql'; @@ -16,6 +16,7 @@ import { DELETE_IMAGE_SUCCESS_MESSAGE, DELETE_IMAGE_ERROR_MESSAGE, SORT_FIELDS, + SETTINGS_TEXT, } from '~/packages_and_registries/container_registry/explorer/constants'; import deleteContainerRepositoryMutation from '~/packages_and_registries/container_registry/explorer/graphql/mutations/delete_container_repository.mutation.graphql'; import getContainerRepositoriesDetails from '~/packages_and_registries/container_registry/explorer/graphql/queries/get_container_repositories_details.query.graphql'; @@ -36,18 +37,19 @@ import { graphQLProjectImageRepositoriesDetailsMock, dockerCommands, } from '../mock_data'; -import { GlModal, GlEmptyState } from '../stubs'; +import { GlEmptyState, DeleteModal } from '../stubs'; describe('List Page', () => { let wrapper; let apolloProvider; - const findDeleteModal = () => wrapper.findComponent(GlModal); + const findDeleteModal = () => wrapper.findComponent(DeleteModal); const findSkeletonLoader = () => wrapper.findComponent(GlSkeletonLoader); const findEmptyState = () => wrapper.findComponent(GlEmptyState); const findCliCommands = () => wrapper.findComponent(CliCommands); + const findSettingsLink = () => wrapper.findComponent(GlButton); const findProjectEmptyState = () => wrapper.findComponent(ProjectEmptyState); const findGroupEmptyState = () => wrapper.findComponent(GroupEmptyState); const findRegistryHeader = () => wrapper.findComponent(RegistryHeader); @@ -89,7 +91,7 @@ describe('List Page', () => { wrapper = shallowMount(component, { apolloProvider, stubs: { - GlModal, + DeleteModal, GlEmptyState, GlSprintf, RegistryHeader, @@ -110,13 +112,12 @@ describe('List Page', () => { ...dockerCommands, }; }, + directives: { + GlTooltip: createMockDirective('gl-tooltip'), + }, }); }; - afterEach(() => { - wrapper.destroy(); - }); - it('contains registry header', async () => { mountComponent(); fireFirstSortUpdate(); @@ -126,6 +127,42 @@ describe('List Page', () => { expect(findRegistryHeader().props()).toMatchObject({ imagesCount: 2, metadataLoading: false, + helpPagePath: '', + hideExpirationPolicyData: false, + showCleanupPolicyLink: false, + expirationPolicy: {}, + cleanupPoliciesSettingsPath: '', + }); + }); + + describe('link to settings', () => { + beforeEach(() => { + const config = { + showContainerRegistrySettings: true, + cleanupPoliciesSettingsPath: 'bar', + }; + mountComponent({ config }); + }); + + it('is rendered', () => { + expect(findSettingsLink().exists()).toBe(true); + }); + + it('has the right icon', () => { + expect(findSettingsLink().props('icon')).toBe('settings'); + }); + + it('has the right attributes', () => { + expect(findSettingsLink().attributes()).toMatchObject({ + 'aria-label': SETTINGS_TEXT, + href: 'bar', + }); + }); + + it('sets tooltip with right label', () => { + const tooltip = getBinding(findSettingsLink().element, 'gl-tooltip'); + + expect(tooltip.value).toBe(SETTINGS_TEXT); }); }); @@ -239,6 +276,14 @@ describe('List Page', () => { expect(findCliCommands().exists()).toBe(false); }); + + it('link to settings is not visible', async () => { + mountComponent({ resolver, config }); + + await waitForApolloRequestRender(); + + expect(findSettingsLink().exists()).toBe(false); + }); }); }); @@ -310,7 +355,7 @@ describe('List Page', () => { await selectImageForDeletion(); - findDeleteModal().vm.$emit('primary'); + findDeleteModal().vm.$emit('confirmDelete'); expect(mutationResolver).toHaveBeenCalledWith({ id: deletedContainerRepository.id }); }); @@ -468,11 +513,15 @@ describe('List Page', () => { expect(findDeleteModal().exists()).toBe(true); }); - it('contains a description with the path of the item to delete', async () => { - await waitForPromises(); - findImageList().vm.$emit('delete', { path: 'foo' }); + it('contains the deleted image as props', async () => { await waitForPromises(); - expect(findDeleteModal().html()).toContain('foo'); + findImageList().vm.$emit('delete', deletedContainerRepository); + await nextTick(); + + expect(findDeleteModal().props()).toEqual({ + itemsToBeDeleted: [deletedContainerRepository], + deleteImage: true, + }); }); }); diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/stubs.js b/spec/frontend/packages_and_registries/container_registry/explorer/stubs.js index 7d281a53a59..0d80028adf6 100644 --- a/spec/frontend/packages_and_registries/container_registry/explorer/stubs.js +++ b/spec/frontend/packages_and_registries/container_registry/explorer/stubs.js @@ -6,7 +6,7 @@ import { } from '@gitlab/ui'; import { RouterLinkStub } from '@vue/test-utils'; import { stubComponent } from 'helpers/stub_component'; -import RealDeleteModal from '~/packages_and_registries/container_registry/explorer/components/details_page/delete_modal.vue'; +import RealDeleteModal from '~/packages_and_registries/container_registry/explorer/components/delete_modal.vue'; import RealListItem from '~/vue_shared/components/registry/list_item.vue'; export const GlModal = stubComponent(RealGlModal, { diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/utils_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/utils_spec.js index 5063759a620..d7a9c200c7b 100644 --- a/spec/frontend/packages_and_registries/container_registry/explorer/utils_spec.js +++ b/spec/frontend/packages_and_registries/container_registry/explorer/utils_spec.js @@ -1,6 +1,23 @@ -import { timeTilRun } from '~/packages_and_registries/container_registry/explorer/utils'; +import { + getImageName, + timeTilRun, +} from '~/packages_and_registries/container_registry/explorer/utils'; describe('Container registry utilities', () => { + describe('getImageName', () => { + it('returns name when present', () => { + const result = getImageName({ name: 'foo' }); + + expect(result).toBe('foo'); + }); + + it('returns project path when name is empty', () => { + const result = getImageName({ name: '', project: { path: 'foo' } }); + + expect(result).toBe('foo'); + }); + }); + describe('timeTilRun', () => { beforeEach(() => { jest.spyOn(Date, 'now').mockImplementation(() => new Date('2063-04-04T00:42:00Z').getTime()); diff --git a/spec/frontend/packages_and_registries/dependency_proxy/app_spec.js b/spec/frontend/packages_and_registries/dependency_proxy/app_spec.js index 601f8abd34d..1928dbf72b6 100644 --- a/spec/frontend/packages_and_registries/dependency_proxy/app_spec.js +++ b/spec/frontend/packages_and_registries/dependency_proxy/app_spec.js @@ -5,7 +5,6 @@ import { GlFormInputGroup, GlFormGroup, GlModal, - GlSkeletonLoader, GlSprintf, GlEmptyState, } from '@gitlab/ui'; @@ -13,6 +12,7 @@ import Vue, { nextTick } from 'vue'; import VueApollo from 'vue-apollo'; import MockAdapter from 'axios-mock-adapter'; import createMockApollo from 'helpers/mock_apollo_helper'; +import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import waitForPromises from 'helpers/wait_for_promises'; import { GRAPHQL_PAGE_SIZE } from '~/packages_and_registries/dependency_proxy/constants'; @@ -31,11 +31,6 @@ import { proxyDetailsQuery, proxyData, pagination, proxyManifests } from './mock const dummyApiVersion = 'v3000'; const dummyGrouptId = 1; const dummyUrlRoot = '/gitlab'; -const dummyGon = { - api_version: dummyApiVersion, - relative_url_root: dummyUrlRoot, -}; -let originalGon; const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/groups/${dummyGrouptId}/dependency_proxy/cache`; Vue.use(VueApollo); @@ -51,6 +46,7 @@ describe('DependencyProxyApp', () => { groupId: dummyGrouptId, noManifestsIllustration: 'noManifestsIllustration', canClearCache: true, + settingsPath: 'path', }; function createComponent({ provide = provideDefaults } = {}) { @@ -71,56 +67,46 @@ describe('DependencyProxyApp', () => { GlSprintf, TitleArea, }, + directives: { + GlTooltip: createMockDirective('gl-tooltip'), + }, }); } const findClipBoardButton = () => wrapper.findComponent(ClipboardButton); const findFormGroup = () => wrapper.findComponent(GlFormGroup); const findFormInputGroup = () => wrapper.findComponent(GlFormInputGroup); - const findSkeletonLoader = () => wrapper.findComponent(GlSkeletonLoader); - const findMainArea = () => wrapper.findByTestId('main-area'); const findProxyCountText = () => wrapper.findByTestId('proxy-count'); const findManifestList = () => wrapper.findComponent(ManifestsList); const findEmptyState = () => wrapper.findComponent(GlEmptyState); const findClearCacheDropdownList = () => wrapper.findComponent(GlDropdown); const findClearCacheModal = () => wrapper.findComponent(GlModal); const findClearCacheAlert = () => wrapper.findComponent(GlAlert); + const findSettingsLink = () => wrapper.findByTestId('settings-link'); beforeEach(() => { resolver = jest.fn().mockResolvedValue(proxyDetailsQuery()); - originalGon = window.gon; - window.gon = { ...dummyGon }; + window.gon = { + api_version: dummyApiVersion, + relative_url_root: dummyUrlRoot, + }; mock = new MockAdapter(axios); mock.onDelete(expectedUrl).reply(HTTP_STATUS_ACCEPTED, {}); }); afterEach(() => { - wrapper.destroy(); - window.gon = originalGon; mock.restore(); }); describe('when the dependency proxy is available', () => { describe('when is loading', () => { - it('renders the skeleton loader', () => { - createComponent(); - - expect(findSkeletonLoader().exists()).toBe(true); - }); - it('does not render a form group with label', () => { createComponent(); expect(findFormGroup().exists()).toBe(false); }); - - it('does not show the main section', () => { - createComponent(); - - expect(findMainArea().exists()).toBe(false); - }); }); describe('when the app is loaded', () => { @@ -130,10 +116,6 @@ describe('DependencyProxyApp', () => { return waitForPromises(); }); - it('renders the main area', () => { - expect(findMainArea().exists()).toBe(true); - }); - it('renders a form group with a label', () => { expect(findFormGroup().attributes('label')).toBe( DependencyProxyApp.i18n.proxyImagePrefix, @@ -157,6 +139,29 @@ describe('DependencyProxyApp', () => { expect(findProxyCountText().text()).toBe('Contains 2 blobs of images (1024 Bytes)'); }); + describe('link to settings', () => { + it('is rendered', () => { + expect(findSettingsLink().exists()).toBe(true); + }); + + it('has the right icon', () => { + expect(findSettingsLink().props('icon')).toBe('settings'); + }); + + it('has the right attributes', () => { + expect(findSettingsLink().attributes()).toMatchObject({ + 'aria-label': DependencyProxyApp.i18n.settingsText, + href: 'path', + }); + }); + + it('sets tooltip with right label', () => { + const tooltip = getBinding(findSettingsLink().element, 'gl-tooltip'); + + expect(tooltip.value).toBe(DependencyProxyApp.i18n.settingsText); + }); + }); + describe('manifest lists', () => { describe('when there are no manifests', () => { beforeEach(() => { @@ -189,6 +194,7 @@ describe('DependencyProxyApp', () => { it('shows list', () => { expect(findManifestList().props()).toMatchObject({ + dependencyProxyImagePrefix: proxyData().dependencyProxyImagePrefix, manifests: proxyManifests(), pagination: pagination(), }); @@ -218,13 +224,6 @@ describe('DependencyProxyApp', () => { }); describe('triggering page event on list', () => { - it('re-renders the skeleton loader', async () => { - findManifestList().vm.$emit('next-page'); - await nextTick(); - - expect(findSkeletonLoader().exists()).toBe(true); - }); - it('renders form group with label', async () => { findManifestList().vm.$emit('next-page'); await nextTick(); @@ -233,13 +232,6 @@ describe('DependencyProxyApp', () => { expect.stringMatching(DependencyProxyApp.i18n.proxyImagePrefix), ); }); - - it('does not show the main section', async () => { - findManifestList().vm.$emit('next-page'); - await nextTick(); - - expect(findMainArea().exists()).toBe(false); - }); }); it('shows the clear cache dropdown list', () => { @@ -274,9 +266,7 @@ describe('DependencyProxyApp', () => { beforeEach(() => { createComponent({ provide: { - groupPath: 'gitlab-org', - groupId: dummyGrouptId, - noManifestsIllustration: 'noManifestsIllustration', + ...provideDefaults, canClearCache: false, }, }); @@ -285,6 +275,10 @@ describe('DependencyProxyApp', () => { it('does not show the clear cache dropdown list', () => { expect(findClearCacheDropdownList().exists()).toBe(false); }); + + it('does not show link to settings', () => { + expect(findSettingsLink().exists()).toBe(false); + }); }); }); }); diff --git a/spec/frontend/packages_and_registries/dependency_proxy/components/manifest_list_spec.js b/spec/frontend/packages_and_registries/dependency_proxy/components/manifest_list_spec.js index 2f415bfd6f9..4149f728cd8 100644 --- a/spec/frontend/packages_and_registries/dependency_proxy/components/manifest_list_spec.js +++ b/spec/frontend/packages_and_registries/dependency_proxy/components/manifest_list_spec.js @@ -1,9 +1,9 @@ -import { GlKeysetPagination } from '@gitlab/ui'; +import { GlKeysetPagination, GlSkeletonLoader } from '@gitlab/ui'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import ManifestRow from '~/packages_and_registries/dependency_proxy/components/manifest_row.vue'; - import Component from '~/packages_and_registries/dependency_proxy/components/manifests_list.vue'; import { + proxyData, proxyManifests, pagination, } from 'jest/packages_and_registries/dependency_proxy/mock_data'; @@ -12,8 +12,10 @@ describe('Manifests List', () => { let wrapper; const defaultProps = { + dependencyProxyImagePrefix: proxyData().dependencyProxyImagePrefix, manifests: proxyManifests(), pagination: pagination(), + loading: false, }; const createComponent = (propsData = defaultProps) => { @@ -24,10 +26,8 @@ describe('Manifests List', () => { const findRows = () => wrapper.findAllComponents(ManifestRow); const findPagination = () => wrapper.findComponent(GlKeysetPagination); - - afterEach(() => { - wrapper.destroy(); - }); + const findMainArea = () => wrapper.findByTestId('main-area'); + const findSkeletonLoader = () => wrapper.findComponent(GlSkeletonLoader); it('has the correct title', () => { createComponent(); @@ -44,8 +44,27 @@ describe('Manifests List', () => { it('binds a manifest to each row', () => { createComponent(); - expect(findRows().at(0).props()).toMatchObject({ - manifest: defaultProps.manifests[0], + expect(findRows().at(0).props('manifest')).toBe(defaultProps.manifests[0]); + }); + + it('binds a dependencyProxyImagePrefix to each row', () => { + createComponent(); + + expect(findRows().at(0).props('dependencyProxyImagePrefix')).toBe( + proxyData().dependencyProxyImagePrefix, + ); + }); + + describe('loading', () => { + it.each` + loading | expectLoader | expectContent + ${false} | ${false} | ${true} + ${true} | ${true} | ${false} + `('when loading is $loading', ({ loading, expectLoader, expectContent }) => { + createComponent({ ...defaultProps, loading }); + + expect(findSkeletonLoader().exists()).toBe(expectLoader); + expect(findMainArea().exists()).toBe(expectContent); }); }); diff --git a/spec/frontend/packages_and_registries/dependency_proxy/components/manifest_row_spec.js b/spec/frontend/packages_and_registries/dependency_proxy/components/manifest_row_spec.js index be3236d1f9c..5f47a1b8098 100644 --- a/spec/frontend/packages_and_registries/dependency_proxy/components/manifest_row_spec.js +++ b/spec/frontend/packages_and_registries/dependency_proxy/components/manifest_row_spec.js @@ -1,15 +1,17 @@ import { GlSprintf } from '@gitlab/ui'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; import ListItem from '~/vue_shared/components/registry/list_item.vue'; import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; import Component from '~/packages_and_registries/dependency_proxy/components/manifest_row.vue'; import { MANIFEST_PENDING_DESTRUCTION_STATUS } from '~/packages_and_registries/dependency_proxy/constants'; -import { proxyManifests } from 'jest/packages_and_registries/dependency_proxy/mock_data'; +import { proxyData, proxyManifests } from 'jest/packages_and_registries/dependency_proxy/mock_data'; describe('Manifest Row', () => { let wrapper; const defaultProps = { + dependencyProxyImagePrefix: proxyData().dependencyProxyImagePrefix, manifest: proxyManifests()[0], }; @@ -24,15 +26,13 @@ describe('Manifest Row', () => { }); }; + const findClipboardButton = () => wrapper.findComponent(ClipboardButton); const findListItem = () => wrapper.findComponent(ListItem); const findCachedMessages = () => wrapper.findByTestId('cached-message'); + const findDigest = () => wrapper.findByTestId('manifest-row-short-digest'); const findTimeAgoTooltip = () => wrapper.findComponent(TimeagoTooltip); const findStatus = () => wrapper.findByTestId('status'); - afterEach(() => { - wrapper.destroy(); - }); - describe('With a manifest on the DEFAULT status', () => { beforeEach(() => { createComponent(); @@ -42,12 +42,18 @@ describe('Manifest Row', () => { expect(findListItem().exists()).toBe(true); }); - it('displays the name', () => { - expect(wrapper.text()).toContain('alpine'); + it('displays the name with tag & digest', () => { + expect(wrapper.text()).toContain('alpine:latest'); + expect(findDigest().text()).toMatchInterpolatedText('Digest: 995efde'); }); - it('displays the version', () => { - expect(wrapper.text()).toContain('latest'); + it('displays the name & digest for manifests that contain digest in image name', () => { + createComponent({ + ...defaultProps, + manifest: proxyManifests()[1], + }); + expect(wrapper.text()).toContain('alpine'); + expect(findDigest().text()).toMatchInterpolatedText('Digest: e95efde'); }); it('displays the cached time', () => { @@ -86,4 +92,35 @@ describe('Manifest Row', () => { expect(findStatus().text()).toBe('Scheduled for deletion'); }); }); + + describe('clipboard button', () => { + beforeEach(() => { + createComponent(); + }); + + it('exists', () => { + expect(findClipboardButton().exists()).toBe(true); + }); + + it('passes the correct title prop', () => { + expect(findClipboardButton().attributes('title')).toBe(Component.i18n.copyImagePathTitle); + }); + + it('has the correct copy text when image name contains tag name', () => { + expect(findClipboardButton().attributes('text')).toBe( + 'gdk.test:3000/private-group/dependency_proxy/containers/alpine:latest', + ); + }); + + it('has the correct copy text when image name contains digest', () => { + createComponent({ + ...defaultProps, + manifest: proxyManifests()[1], + }); + + expect(findClipboardButton().attributes('text')).toBe( + 'gdk.test:3000/private-group/dependency_proxy/containers/alpine@sha256:e95efde2e81b21d1ea7066aa77a59298a62a9e9fbb4b77f36c189774ec9b1089', + ); + }); + }); }); diff --git a/spec/frontend/packages_and_registries/dependency_proxy/mock_data.js b/spec/frontend/packages_and_registries/dependency_proxy/mock_data.js index 37c8eb669ba..4d0be0a0c09 100644 --- a/spec/frontend/packages_and_registries/dependency_proxy/mock_data.js +++ b/spec/frontend/packages_and_registries/dependency_proxy/mock_data.js @@ -11,13 +11,15 @@ export const proxyManifests = () => [ { id: 'proxy-1', createdAt: '2021-09-22T09:45:28Z', + digest: 'sha256:995efde2e81b21d1ea7066aa77a59298a62a9e9fbb4b77f36c189774ec9b1089', imageName: 'alpine:latest', status: 'DEFAULT', }, { id: 'proxy-2', createdAt: '2021-09-21T09:45:28Z', - imageName: 'alpine:stable', + digest: 'sha256:e95efde2e81b21d1ea7066aa77a59298a62a9e9fbb4b77f36c189774ec9b1089', + imageName: 'alpine:sha256:e95efde2e81b21d1ea7066aa77a59298a62a9e9fbb4b77f36c189774ec9b1089', status: 'DEFAULT', }, ]; 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 index a2e5cbdce8b..1e9b9b1ce47 100644 --- 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 @@ -63,10 +63,6 @@ describe('Harbor artifact list row', () => { }); }; - afterEach(() => { - wrapper.destroy(); - }); - describe('list item', () => { beforeEach(() => { mountComponent({ 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 index b9d6dc2679e..786a4715731 100644 --- 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 @@ -26,10 +26,6 @@ describe('Harbor artifacts list', () => { }); }; - afterEach(() => { - wrapper.destroy(); - }); - describe('when isLoading is true', () => { beforeEach(() => { mountComponent({ 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 index e8cc2b2e22d..d8fb91c085c 100644 --- 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 @@ -20,10 +20,6 @@ describe('Harbor Details Header', () => { }); }; - afterEach(() => { - wrapper.destroy(); - }); - describe('artifact name', () => { describe('missing image name', () => { beforeEach(() => { 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 7a6169d300c..9a7ad759dba 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 @@ -29,10 +29,6 @@ describe('harbor_list_header', () => { await nextTick(); }; - afterEach(() => { - wrapper.destroy(); - }); - describe('header', () => { it('has a title', () => { mountComponent({ metadataLoading: true }); 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 b62d4e8836b..1e031e0557a 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 @@ -28,10 +28,6 @@ describe('Harbor List Row', () => { }); }; - afterEach(() => { - wrapper.destroy(); - }); - describe('image title and path', () => { it('contains a link to the details page', () => { mountComponent(); 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 e7e74a0da58..a1803ecf7fb 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 @@ -20,10 +20,6 @@ describe('Harbor List', () => { }); }; - afterEach(() => { - wrapper.destroy(); - }); - describe('list', () => { it('contains one list element for each image', () => { mountComponent(); 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 index 5e299a269e3..9370ff1fdd4 100644 --- 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 @@ -26,10 +26,6 @@ describe('Harbor Tags Header', () => { totalPages: 1, }; - afterEach(() => { - wrapper.destroy(); - }); - beforeEach(() => { mountComponent({ propsData: { artifactDetail: mockArtifactDetail, pageInfo: mockPageInfo, tagsLoading: false }, 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 index 849215e286b..0b2ce01ebf6 100644 --- 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 @@ -37,10 +37,6 @@ describe('Harbor tag list row', () => { }); }; - afterEach(() => { - wrapper.destroy(); - }); - describe('list item', () => { beforeEach(() => { mountComponent({ 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 index 4c6b2b6daaa..e2a2a584b7d 100644 --- 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 @@ -24,10 +24,6 @@ describe('Harbor Tags List', () => { }); }; - afterEach(() => { - wrapper.destroy(); - }); - describe('when isLoading is true', () => { beforeEach(() => { mountComponent({ 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 index 69765d31674..90c3d9082f7 100644 --- a/spec/frontend/packages_and_registries/harbor_registry/pages/details_spec.js +++ b/spec/frontend/packages_and_registries/harbor_registry/pages/details_spec.js @@ -74,10 +74,6 @@ describe('Harbor Details Page', () => { }); }); - afterEach(() => { - wrapper.destroy(); - }); - describe('when isLoading is true', () => { it('shows the loader', () => { mountComponent(); 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 97d30e6fe99..1bc2657822e 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 @@ -60,10 +60,6 @@ describe('Harbor List Page', () => { }); }); - afterEach(() => { - wrapper.destroy(); - }); - it('contains harbor registry header', async () => { mountComponent(); fireFirstSortUpdate(); @@ -78,7 +74,7 @@ describe('Harbor List Page', () => { }); describe('isLoading is true', () => { - it('shows the skeleton loader', async () => { + it('shows the skeleton loader', () => { mountComponent(); fireFirstSortUpdate(); @@ -97,7 +93,7 @@ describe('Harbor List Page', () => { expect(findCliCommands().exists()).toBe(false); }); - it('title has the metadataLoading props set to true', async () => { + it('title has the metadataLoading props set to true', () => { mountComponent(); fireFirstSortUpdate(); 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 index 10901c6ec1e..6002faa1fa3 100644 --- a/spec/frontend/packages_and_registries/harbor_registry/pages/tags_spec.js +++ b/spec/frontend/packages_and_registries/harbor_registry/pages/tags_spec.js @@ -60,10 +60,6 @@ describe('Harbor Tags page', () => { }); }); - afterEach(() => { - wrapper.destroy(); - }); - it('contains tags header', () => { mountComponent(); diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/app_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/app_spec.js index e74375b7705..f8130287c12 100644 --- a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/app_spec.js +++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/app_spec.js @@ -86,10 +86,6 @@ describe('PackagesApp', () => { const findTerraformInstallation = () => wrapper.findComponent(TerraformInstallation); const findPackageFiles = () => wrapper.findComponent(PackageFiles); - afterEach(() => { - wrapper.destroy(); - }); - it('renders the app and displays the package title', async () => { createComponent(); diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/details_title_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/details_title_spec.js index b504f7489ab..148e87699f1 100644 --- a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/details_title_spec.js +++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/details_title_spec.js @@ -39,10 +39,6 @@ describe('PackageTitle', () => { const pipelineProject = () => wrapper.find('[data-testid="pipeline-project"]'); const packageRef = () => wrapper.find('[data-testid="package-ref"]'); - afterEach(() => { - wrapper.destroy(); - }); - describe('module title', () => { it('is correctly bound', async () => { await createComponent(); 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 d7caa8ca2d8..7352afff051 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 @@ -23,10 +23,6 @@ describe('FileSha', () => { }); } - afterEach(() => { - wrapper.destroy(); - }); - it('renders', () => { createComponent(); diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/package_files_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/package_files_spec.js index b76d7c2b57b..c3e0818fc11 100644 --- a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/package_files_spec.js +++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/package_files_spec.js @@ -37,11 +37,6 @@ describe('Package Files', () => { }); }; - afterEach(() => { - wrapper.destroy(); - wrapper = null; - }); - describe('rows', () => { it('renders a single file for an npm package', () => { createComponent(); diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/package_history_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/package_history_spec.js index 0cbe2755f7e..a650aba464e 100644 --- a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/package_history_spec.js +++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/package_history_spec.js @@ -30,11 +30,6 @@ describe('Package History', () => { }); }; - afterEach(() => { - wrapper.destroy(); - wrapper = null; - }); - const findHistoryElement = (testId) => wrapper.find(`[data-testid="${testId}"]`); const findElementLink = (container) => container.findComponent(GlLink); const findElementTimeAgo = (container) => container.findComponent(TimeAgoTooltip); diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/terraform_installation_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/terraform_installation_spec.js index 78c1b840dbc..94797f01d16 100644 --- a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/terraform_installation_spec.js +++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/terraform_installation_spec.js @@ -30,10 +30,6 @@ describe('TerraformInstallation', () => { createComponent(); }); - afterEach(() => { - wrapper.destroy(); - }); - it('renders all the messages', () => { expect(wrapper.element).toMatchSnapshot(); }); diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/store/actions_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/store/actions_spec.js index bb970336b94..ea4d268d84e 100644 --- a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/store/actions_spec.js +++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/store/actions_spec.js @@ -1,6 +1,6 @@ import testAction from 'helpers/vuex_action_helper'; import Api from '~/api'; -import { createAlert, VARIANT_SUCCESS, VARIANT_WARNING } from '~/flash'; +import { createAlert, VARIANT_SUCCESS, VARIANT_WARNING } from '~/alert'; import { FETCH_PACKAGE_VERSIONS_ERROR } from '~/packages_and_registries/infrastructure_registry/details/constants'; import { fetchPackageVersions, @@ -15,7 +15,7 @@ import { } from '~/packages_and_registries/shared/constants'; import { npmPackage as packageEntity } from '../../mock_data'; -jest.mock('~/flash.js'); +jest.mock('~/alert'); jest.mock('~/api.js'); describe('Actions Package details store', () => { @@ -53,7 +53,7 @@ describe('Actions Package details store', () => { expect(Api.projectPackage).toHaveBeenCalledWith(packageEntity.project_id, packageEntity.id); }); - it('should create flash on API error', async () => { + it('should create alert on API error', async () => { Api.projectPackage = jest.fn().mockRejectedValue(); await testAction( @@ -83,7 +83,7 @@ describe('Actions Package details store', () => { packageEntity.id, ); }); - it('should create flash on API error', async () => { + it('should create alert on API error', async () => { Api.deleteProjectPackage = jest.fn().mockRejectedValue(); await testAction(deletePackage, undefined, { packageEntity }, [], []); @@ -118,7 +118,7 @@ describe('Actions Package details store', () => { }); }); - it('should create flash on API error', async () => { + it('should create alert on API error', async () => { Api.deleteProjectPackageFile = jest.fn().mockRejectedValue(); await testAction(deletePackageFile, fileId, { packageEntity }, [], []); expect(createAlert).toHaveBeenCalledWith({ 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 801cde8582e..d0841c6110f 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 @@ -49,9 +49,7 @@ exports[`packages_list_app renders 1`] = ` Learn how to <b-link-stub class="gl-link" - event="click" href="helpUrl" - routertag="a" target="_blank" > publish and share your packages diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/infrastructure_search_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/infrastructure_search_spec.js index a086c20a5e7..a89247c0a97 100644 --- a/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/infrastructure_search_spec.js +++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/infrastructure_search_spec.js @@ -55,11 +55,6 @@ describe('Infrastructure Search', () => { }); }; - afterEach(() => { - wrapper.destroy(); - wrapper = null; - }); - it('has a registry search component', () => { mountComponent(); diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/infrastructure_title_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/infrastructure_title_spec.js index aca6b0942cc..12859b1d77c 100644 --- a/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/infrastructure_title_spec.js +++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/infrastructure_title_spec.js @@ -22,11 +22,6 @@ describe('Infrastructure Title', () => { }); }; - afterEach(() => { - wrapper.destroy(); - wrapper = null; - }); - describe('title area', () => { beforeEach(() => { mountComponent(); @@ -37,7 +32,7 @@ describe('Infrastructure Title', () => { }); it('has the correct title', () => { - expect(findTitleArea().props('title')).toBe('Infrastructure Registry'); + expect(findTitleArea().props('title')).toBe('Terraform Module Registry'); }); describe('with no modules', () => { diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/packages_list_app_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/packages_list_app_spec.js index d237023d0cd..47d36d11e35 100644 --- a/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/packages_list_app_spec.js +++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/packages_list_app_spec.js @@ -3,7 +3,7 @@ import { shallowMount } from '@vue/test-utils'; import Vue from 'vue'; import Vuex from 'vuex'; import setWindowLocation from 'helpers/set_window_location_helper'; -import { createAlert, VARIANT_INFO } from '~/flash'; +import { createAlert, VARIANT_INFO } from '~/alert'; import * as commonUtils from '~/lib/utils/common_utils'; import PackageListApp from '~/packages_and_registries/infrastructure_registry/list/components/packages_list_app.vue'; import { DELETE_PACKAGE_SUCCESS_MESSAGE } from '~/packages_and_registries/infrastructure_registry/list/constants'; @@ -14,7 +14,7 @@ import InfrastructureSearch from '~/packages_and_registries/infrastructure_regis import { FILTERED_SEARCH_TERM } from '~/vue_shared/components/filtered_search_bar/constants'; jest.mock('~/lib/utils/common_utils'); -jest.mock('~/flash'); +jest.mock('~/alert'); Vue.use(Vuex); @@ -72,10 +72,6 @@ describe('packages_list_app', () => { mountComponent(); }); - afterEach(() => { - wrapper.destroy(); - }); - it('renders', () => { createStore({ packageCount: 1 }); mountComponent(); @@ -217,7 +213,7 @@ describe('packages_list_app', () => { setWindowLocation(originalLocation); }); - it(`creates a flash if the query string contains ${SHOW_DELETE_SUCCESS_ALERT}`, () => { + it(`creates an alert if the query string contains ${SHOW_DELETE_SUCCESS_ALERT}`, () => { mountComponent(); expect(createAlert).toHaveBeenCalledWith({ 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 0164d92ce34..51445942eaa 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 @@ -4,13 +4,13 @@ import Vue from 'vue'; import { last } from 'lodash'; import Vuex from 'vuex'; import stubChildren from 'helpers/stub_children'; +import { mockTracking, unmockTracking } from 'helpers/tracking_helper'; import PackagesList from '~/packages_and_registries/infrastructure_registry/list/components/packages_list.vue'; import PackagesListRow from '~/packages_and_registries/infrastructure_registry/shared/package_list_row.vue'; import PackagesListLoader from '~/packages_and_registries/shared/components/packages_list_loader.vue'; import DeletePackageModal from '~/packages_and_registries/shared/components/delete_package_modal.vue'; import { TRACKING_ACTIONS } from '~/packages_and_registries/shared/constants'; import { TRACK_CATEGORY } from '~/packages_and_registries/infrastructure_registry/shared/constants'; -import Tracking from '~/tracking'; import { packageList } from '../../mock_data'; Vue.use(Vuex); @@ -72,11 +72,6 @@ describe('packages_list', () => { }); }; - afterEach(() => { - wrapper.destroy(); - wrapper = null; - }); - describe('when is loading', () => { beforeEach(() => { mountComponent({ @@ -179,23 +174,23 @@ describe('packages_list', () => { }); describe('tracking', () => { - let eventSpy; + let trackingSpy = null; 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' } }); - }); - - it('deleteItemConfirmation calls event', () => { - wrapper.vm.deleteItemConfirmation(); - expect(eventSpy).toHaveBeenCalledWith( - TRACK_CATEGORY, - TRACKING_ACTIONS.DELETE_PACKAGE, - expect.any(Object), - ); + trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn); + }); + + afterEach(() => { + unmockTracking(); + }); + + it('deleteItemConfirmation calls event', async () => { + await findPackageListDeleteModal().vm.$emit('ok'); + + expect(trackingSpy).toHaveBeenCalledWith(TRACK_CATEGORY, TRACKING_ACTIONS.DELETE_PACKAGE, { + category: TRACK_CATEGORY, + }); }); }); }); diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/list/stores/actions_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/stores/actions_spec.js index 2c185e040f4..4f051264172 100644 --- a/spec/frontend/packages_and_registries/infrastructure_registry/components/list/stores/actions_spec.js +++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/stores/actions_spec.js @@ -2,14 +2,14 @@ import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; import testAction from 'helpers/vuex_action_helper'; import Api from '~/api'; -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import { HTTP_STATUS_BAD_REQUEST, HTTP_STATUS_OK } from '~/lib/utils/http_status'; import { MISSING_DELETE_PATH_ERROR } from '~/packages_and_registries/infrastructure_registry/list/constants'; import * as actions from '~/packages_and_registries/infrastructure_registry/list/stores/actions'; import * as types from '~/packages_and_registries/infrastructure_registry/list/stores/mutation_types'; import { DELETE_PACKAGE_ERROR_MESSAGE } from '~/packages_and_registries/shared/constants'; -jest.mock('~/flash.js'); +jest.mock('~/alert'); jest.mock('~/api.js'); describe('Actions Package list store', () => { @@ -96,7 +96,7 @@ describe('Actions Package list store', () => { }); }); - it('should create flash on API error', async () => { + it('should create alert on API error', async () => { Api.projectPackages = jest.fn().mockRejectedValue(); await testAction( actions.requestPackagesList, @@ -198,7 +198,7 @@ describe('Actions Package list store', () => { ); }); - it('should stop the loading and call create flash on api error', async () => { + it('should stop the loading and call create alert on api error', async () => { mock.onDelete(payload._links.delete_api_path).replyOnce(HTTP_STATUS_BAD_REQUEST); await testAction( actions.requestDeletePackage, diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/shared/package_list_row_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/shared/package_list_row_spec.js index 721bdd34a4f..d00d7180f75 100644 --- a/spec/frontend/packages_and_registries/infrastructure_registry/components/shared/package_list_row_spec.js +++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/shared/package_list_row_spec.js @@ -49,16 +49,11 @@ describe('packages_list_row', () => { disableDelete, }, directives: { - GlTooltip: createMockDirective(), + GlTooltip: createMockDirective('gl-tooltip'), }, }); }; - afterEach(() => { - wrapper.destroy(); - wrapper = null; - }); - it('renders', () => { mountComponent(); expect(wrapper.element).toMatchSnapshot(); @@ -143,7 +138,7 @@ describe('packages_list_row', () => { }); it('details link is disabled', () => { - expect(findPackageLink().attributes('disabled')).toBe('true'); + expect(findPackageLink().attributes('disabled')).toBeDefined(); }); it('has a warning icon', () => { diff --git a/spec/frontend/packages_and_registries/package_registry/components/delete_modal_spec.js b/spec/frontend/packages_and_registries/package_registry/components/delete_modal_spec.js index 9c1ebf5a2eb..4ab81182c9a 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/delete_modal_spec.js +++ b/spec/frontend/packages_and_registries/package_registry/components/delete_modal_spec.js @@ -1,7 +1,14 @@ -import { GlModal as RealGlModal } from '@gitlab/ui'; +import { GlModal as RealGlModal, GlLink, GlSprintf } from '@gitlab/ui'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import { stubComponent } from 'helpers/stub_component'; import DeleteModal from '~/packages_and_registries/package_registry/components/delete_modal.vue'; +import { + DELETE_PACKAGE_MODAL_PRIMARY_ACTION, + DELETE_PACKAGE_WITH_REQUEST_FORWARDING_PRIMARY_ACTION, + DELETE_PACKAGES_REQUEST_FORWARDING_MODAL_CONTENT, + DELETE_PACKAGES_WITH_REQUEST_FORWARDING_PRIMARY_ACTION, + REQUEST_FORWARDING_HELP_PAGE_PATH, +} from '~/packages_and_registries/package_registry/constants'; const GlModal = stubComponent(RealGlModal, { methods: { @@ -14,22 +21,30 @@ describe('DeleteModal', () => { const defaultItemsToBeDeleted = [ { - name: 'package 01', + name: 'package-1', + version: '1.0.0', }, { - name: 'package 02', + name: 'package-2', + version: '1.0.0', }, ]; const findModal = () => wrapper.findComponent(GlModal); + const findLink = () => wrapper.findComponent(GlLink); - const mountComponent = ({ itemsToBeDeleted = defaultItemsToBeDeleted } = {}) => { + const mountComponent = ({ + itemsToBeDeleted = defaultItemsToBeDeleted, + showRequestForwardingContent = false, + } = {}) => { wrapper = shallowMountExtended(DeleteModal, { propsData: { itemsToBeDeleted, + showRequestForwardingContent, }, stubs: { GlModal, + GlSprintf, }, }); }; @@ -45,16 +60,81 @@ describe('DeleteModal', () => { it('passes actionPrimary prop', () => { expect(findModal().props('actionPrimary')).toStrictEqual({ text: 'Permanently delete', - attributes: [{ variant: 'danger' }, { category: 'primary' }], + attributes: { variant: 'danger', category: 'primary' }, }); }); it('renders description', () => { - expect(findModal().text()).toContain( + expect(findModal().text()).toMatchInterpolatedText( 'You are about to delete 2 packages. This operation is irreversible.', ); }); + it('with only one item to be deleted renders correct description', () => { + mountComponent({ itemsToBeDeleted: [defaultItemsToBeDeleted[0]] }); + + expect(findModal().text()).toMatchInterpolatedText( + 'You are about to delete version 1.0.0 of package-1. Are you sure?', + ); + }); + + it('sets the right action primary text', () => { + expect(findModal().props('actionPrimary')).toMatchObject({ + text: DELETE_PACKAGE_MODAL_PRIMARY_ACTION, + }); + }); + + describe('when showRequestForwardingContent is set', () => { + it('renders correct description', () => { + mountComponent({ showRequestForwardingContent: true }); + + expect(findModal().text()).toMatchInterpolatedText( + DELETE_PACKAGES_REQUEST_FORWARDING_MODAL_CONTENT, + ); + }); + + it('contains link to help page', () => { + mountComponent({ showRequestForwardingContent: true }); + + expect(findLink().exists()).toBe(true); + expect(findLink().attributes('href')).toBe(REQUEST_FORWARDING_HELP_PAGE_PATH); + }); + + it('sets the right action primary text', () => { + mountComponent({ showRequestForwardingContent: true }); + + expect(findModal().props('actionPrimary')).toMatchObject({ + text: DELETE_PACKAGES_WITH_REQUEST_FORWARDING_PRIMARY_ACTION, + }); + }); + + describe('and only one item to be deleted', () => { + beforeEach(() => { + mountComponent({ + showRequestForwardingContent: true, + itemsToBeDeleted: [defaultItemsToBeDeleted[0]], + }); + }); + + it('renders correct description', () => { + expect(findModal().text()).toMatchInterpolatedText( + 'Deleting this package while request forwarding is enabled for the project can pose a security risk. Do you want to delete package-1 version 1.0.0 anyway? What are the risks?', + ); + }); + + it('contains link to help page', () => { + expect(findLink().exists()).toBe(true); + expect(findLink().attributes('href')).toBe(REQUEST_FORWARDING_HELP_PAGE_PATH); + }); + + it('sets the right action primary text', () => { + expect(findModal().props('actionPrimary')).toMatchObject({ + text: DELETE_PACKAGE_WITH_REQUEST_FORWARDING_PRIMARY_ACTION, + }); + }); + }); + }); + it('emits confirm when primary event is emitted', () => { expect(wrapper.emitted('confirm')).toBeUndefined(); 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 67f1906f6fd..9b429c39faa 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 @@ -89,8 +89,9 @@ exports[`MavenInstallation maven renders all the messages 1`] = ` /> <code-instruction-stub + class="gl-w-20 gl-mt-5" copytext="Copy Maven command" - instruction="mvn dependency:get -Dartifact=appGroup:appName:appVersion" + instruction="mvn install" label="Maven Command" trackingaction="copy_maven_command" trackinglabel="code_instruction" diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/package_title_spec.js.snap b/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/package_title_spec.js.snap deleted file mode 100644 index 047fa04947c..00000000000 --- a/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/package_title_spec.js.snap +++ /dev/null @@ -1,199 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`PackageTitle renders with tags 1`] = ` -<div - class="gl-display-flex gl-flex-direction-column" - data-qa-selector="package_title" -> - <div - class="gl-display-flex gl-justify-content-space-between gl-py-3" - > - <div - class="gl-flex-direction-column gl-flex-grow-1" - > - <div - class="gl-display-flex" - > - <!----> - - <div - class="gl-display-flex gl-flex-direction-column" - > - <h2 - class="gl-font-size-h1 gl-mt-3 gl-mb-0" - data-testid="title" - > - @gitlab-org/package-15 - </h2> - - <div - class="gl-display-flex gl-align-items-center gl-text-gray-500 gl-mt-3" - > - <div - class="gl-display-flex gl-gap-3" - data-testid="sub-header" - > - v - 1.0.0 - published - <time-ago-tooltip-stub - cssclass="" - time="2020-08-17T14:23:32Z" - tooltipplacement="top" - /> - - <package-tags-stub - hidelabel="true" - tagdisplaylimit="2" - tags="[object Object],[object Object],[object Object]" - /> - </div> - </div> - </div> - </div> - - <div - class="gl-display-flex gl-flex-wrap gl-align-items-center gl-mt-3" - > - <div - class="gl-display-flex gl-align-items-center gl-mr-5" - > - <metadata-item-stub - data-testid="package-type" - icon="package" - link="" - size="s" - text="npm" - texttooltip="" - /> - </div> - <div - class="gl-display-flex gl-align-items-center gl-mr-5" - > - <metadata-item-stub - data-testid="package-size" - icon="disk" - link="" - size="s" - text="800.00 KiB" - texttooltip="" - /> - </div> - <div - class="gl-display-flex gl-align-items-center gl-mr-5" - > - <metadata-item-stub - data-testid="package-last-downloaded-at" - icon="download" - link="" - size="m" - text="Last downloaded Aug 17, 2021" - texttooltip="" - /> - </div> - </div> - </div> - - <!----> - </div> - - <p /> -</div> -`; - -exports[`PackageTitle renders without tags 1`] = ` -<div - class="gl-display-flex gl-flex-direction-column" - data-qa-selector="package_title" -> - <div - class="gl-display-flex gl-justify-content-space-between gl-py-3" - > - <div - class="gl-flex-direction-column gl-flex-grow-1" - > - <div - class="gl-display-flex" - > - <!----> - - <div - class="gl-display-flex gl-flex-direction-column" - > - <h2 - class="gl-font-size-h1 gl-mt-3 gl-mb-0" - data-testid="title" - > - @gitlab-org/package-15 - </h2> - - <div - class="gl-display-flex gl-align-items-center gl-text-gray-500 gl-mt-3" - > - <div - class="gl-display-flex gl-gap-3" - data-testid="sub-header" - > - v - 1.0.0 - published - <time-ago-tooltip-stub - cssclass="" - time="2020-08-17T14:23:32Z" - tooltipplacement="top" - /> - - <!----> - </div> - </div> - </div> - </div> - - <div - class="gl-display-flex gl-flex-wrap gl-align-items-center gl-mt-3" - > - <div - class="gl-display-flex gl-align-items-center gl-mr-5" - > - <metadata-item-stub - data-testid="package-type" - icon="package" - link="" - size="s" - text="npm" - texttooltip="" - /> - </div> - <div - class="gl-display-flex gl-align-items-center gl-mr-5" - > - <metadata-item-stub - data-testid="package-size" - icon="disk" - link="" - size="s" - text="800.00 KiB" - texttooltip="" - /> - </div> - <div - class="gl-display-flex gl-align-items-center gl-mr-5" - > - <metadata-item-stub - data-testid="package-last-downloaded-at" - icon="download" - link="" - size="m" - text="Last downloaded Aug 17, 2021" - texttooltip="" - /> - </div> - </div> - </div> - - <!----> - </div> - - <p /> -</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 b2375da7b11..b4ea6543446 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 @@ -20,7 +20,7 @@ exports[`PypiInstallation renders all the messages 1`] = ` <!----> <button aria-expanded="false" - aria-haspopup="true" + aria-haspopup="menu" class="btn dropdown-toggle btn-default btn-md gl-button gl-dropdown-toggle" id="__BVID__27__BV_toggle_" type="button" @@ -42,7 +42,7 @@ exports[`PypiInstallation renders all the messages 1`] = ` role="img" > <use - href="#chevron-down" + href="file-mock#chevron-down" /> </svg> </button> @@ -59,7 +59,6 @@ exports[`PypiInstallation renders all the messages 1`] = ` </div> <fieldset - aria-describedby="installation-pip-command-group__BV_description_" class="form-group gl-form-group" id="installation-pip-command-group" > @@ -75,12 +74,7 @@ exports[`PypiInstallation renders all the messages 1`] = ` <!----> </legend> - <div - aria-labelledby="installation-pip-command-group__BV_label_" - class="bv-no-focus-ring" - role="group" - tabindex="-1" - > + <div> <div data-testid="pip-command" id="installation-pip-command" @@ -128,7 +122,7 @@ exports[`PypiInstallation renders all the messages 1`] = ` role="img" > <use - href="#copy-to-clipboard" + href="file-mock#copy-to-clipboard" /> </svg> diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/additional_metadata_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/additional_metadata_spec.js index 4f3d780b149..2e59c27cc1b 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/details/additional_metadata_spec.js +++ b/spec/frontend/packages_and_registries/package_registry/components/details/additional_metadata_spec.js @@ -65,11 +65,6 @@ describe('Package Additional metadata', () => { jest.spyOn(Sentry, 'captureException').mockImplementation(); }); - afterEach(() => { - wrapper.destroy(); - wrapper = null; - }); - const findTitle = () => wrapper.findByTestId('title'); const findMainArea = () => wrapper.findByTestId('main'); const findComponentIs = () => wrapper.findByTestId('component-is'); 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 0aba8f7efc7..a6298ebdea7 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 @@ -34,10 +34,6 @@ describe('ComposerInstallation', () => { }); } - afterEach(() => { - wrapper.destroy(); - }); - describe('install command switch', () => { it('has the installation title component', () => { createComponent(); 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 bf9425def9a..70534b1d0a6 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 @@ -33,10 +33,6 @@ describe('ConanInstallation', () => { createComponent(); }); - afterEach(() => { - wrapper.destroy(); - }); - it('renders all the messages', () => { expect(wrapper.element).toMatchSnapshot(); }); diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/dependency_row_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/dependency_row_spec.js index 9aed5b90c73..19aedf120b2 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/details/dependency_row_spec.js +++ b/spec/frontend/packages_and_registries/package_registry/components/details/dependency_row_spec.js @@ -19,10 +19,6 @@ describe('DependencyRow', () => { const dependencyVersion = () => wrapper.findByTestId('version-pattern'); const dependencyFramework = () => wrapper.findByTestId('target-framework'); - afterEach(() => { - wrapper.destroy(); - }); - describe('renders', () => { it('full dependency', () => { createComponent(); 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 feed7a7c46c..a9428773a60 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 @@ -23,10 +23,6 @@ describe('FileSha', () => { }); } - afterEach(() => { - wrapper.destroy(); - }); - it('renders', () => { createComponent(); diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/installation_title_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/installation_title_spec.js index 5fe795f768e..a2d30be13c2 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/details/installation_title_spec.js +++ b/spec/frontend/packages_and_registries/package_registry/components/details/installation_title_spec.js @@ -20,10 +20,6 @@ describe('InstallationTitle', () => { }); } - afterEach(() => { - wrapper.destroy(); - }); - it('has a title', () => { createComponent(); diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/installations_commands_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/installations_commands_spec.js index 8bb05b00e65..d35d95e319f 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/details/installations_commands_spec.js +++ b/spec/frontend/packages_and_registries/package_registry/components/details/installations_commands_spec.js @@ -40,10 +40,6 @@ describe('InstallationCommands', () => { const pypiInstallation = () => wrapper.findComponent(PypiInstallation); const composerInstallation = () => wrapper.findComponent(ComposerInstallation); - afterEach(() => { - wrapper.destroy(); - }); - describe('installation instructions', () => { describe.each` packageEntity | selector 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 fc60039db30..5ea81dccf7d 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 @@ -35,7 +35,7 @@ describe('MavenInstallation', () => { <artifactId>appName</artifactId> <version>appVersion</version> </dependency>`; - const mavenCommandStr = 'mvn dependency:get -Dartifact=appGroup:appName:appVersion'; + const mavenCommandStr = 'mvn install'; const mavenSetupXml = `<repositories> <repository> <id>gitlab-maven</id> @@ -79,10 +79,6 @@ describe('MavenInstallation', () => { }); } - afterEach(() => { - wrapper.destroy(); - }); - describe('install command switch', () => { it('has the installation title component', () => { createComponent(); diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/metadata/composer_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/metadata/composer_spec.js index bb6846d354f..f2f3b8507c3 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/details/metadata/composer_spec.js +++ b/spec/frontend/packages_and_registries/package_registry/components/details/metadata/composer_spec.js @@ -18,11 +18,6 @@ describe('Composer Metadata', () => { }); }; - afterEach(() => { - wrapper.destroy(); - wrapper = null; - }); - const findComposerTargetSha = () => wrapper.findByTestId('composer-target-sha'); const findComposerTargetShaCopyButton = () => wrapper.findComponent(ClipboardButton); const findComposerJson = () => wrapper.findByTestId('composer-json'); diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/metadata/conan_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/metadata/conan_spec.js index e7e47401aa1..2832dc3a712 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/details/metadata/conan_spec.js +++ b/spec/frontend/packages_and_registries/package_registry/components/details/metadata/conan_spec.js @@ -19,11 +19,6 @@ describe('Conan Metadata', () => { }); }; - afterEach(() => { - wrapper.destroy(); - wrapper = null; - }); - const findConanRecipe = () => wrapper.findByTestId('conan-recipe'); beforeEach(() => { diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/metadata/maven_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/metadata/maven_spec.js index 8680d983042..7b253a26fc7 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/details/metadata/maven_spec.js +++ b/spec/frontend/packages_and_registries/package_registry/components/details/metadata/maven_spec.js @@ -19,11 +19,6 @@ describe('Maven Metadata', () => { }); }; - afterEach(() => { - wrapper.destroy(); - wrapper = null; - }); - const findMavenApp = () => wrapper.findByTestId('maven-app'); const findMavenGroup = () => wrapper.findByTestId('maven-group'); diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/metadata/nuget_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/metadata/nuget_spec.js index af3692023f0..9fb467f9af1 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/details/metadata/nuget_spec.js +++ b/spec/frontend/packages_and_registries/package_registry/components/details/metadata/nuget_spec.js @@ -19,11 +19,6 @@ describe('Nuget Metadata', () => { }); }; - afterEach(() => { - wrapper.destroy(); - wrapper = null; - }); - const findNugetSource = () => wrapper.findByTestId('nuget-source'); const findNugetLicense = () => wrapper.findByTestId('nuget-license'); const findElementLink = (container) => container.findComponent(GlLink); diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/metadata/pypi_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/metadata/pypi_spec.js index d7c6ea8379d..67f5fbc9e80 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/details/metadata/pypi_spec.js +++ b/spec/frontend/packages_and_registries/package_registry/components/details/metadata/pypi_spec.js @@ -20,11 +20,6 @@ describe('Package Additional Metadata', () => { }); }; - afterEach(() => { - wrapper.destroy(); - wrapper = null; - }); - const findPypiRequiredPython = () => wrapper.findByTestId('pypi-required-python'); beforeEach(() => { 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 8c0e2d948ca..e711f9ee45d 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 @@ -51,10 +51,6 @@ describe('NpmInstallation', () => { createComponent(); }); - afterEach(() => { - wrapper.destroy(); - }); - it('renders all the messages', () => { expect(wrapper.element).toMatchSnapshot(); }); 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 9449c40c7c6..bcc0b78bfce 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 @@ -36,10 +36,6 @@ describe('NugetInstallation', () => { createComponent(); }); - afterEach(() => { - wrapper.destroy(); - }); - it('renders all the messages', () => { expect(wrapper.element).toMatchSnapshot(); }); 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 529a6a22ddf..1dcac017ccf 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 @@ -48,10 +48,6 @@ describe('Package Files', () => { }); }; - afterEach(() => { - wrapper.destroy(); - }); - describe('rows', () => { it('renders a single file for an npm package', () => { createComponent(); diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/package_history_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/package_history_spec.js index bb2fa9eb6f5..ed470f63b8a 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/details/package_history_spec.js +++ b/spec/frontend/packages_and_registries/package_registry/components/details/package_history_spec.js @@ -63,11 +63,6 @@ describe('Package History', () => { jest.spyOn(Sentry, 'captureException').mockImplementation(); }); - afterEach(() => { - wrapper.destroy(); - wrapper = null; - }); - const findPackageHistoryLoader = () => wrapper.findComponent(PackageHistoryLoader); const findHistoryElement = (testId) => wrapper.findByTestId(testId); const findElementLink = (container) => container.findComponent(GlLink); diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/package_title_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/package_title_spec.js index 1fda77f2aaa..fc0ca0e898f 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/details/package_title_spec.js +++ b/spec/frontend/packages_and_registries/package_registry/components/details/package_title_spec.js @@ -38,7 +38,7 @@ describe('PackageTitle', () => { }, provide, directives: { - GlResizeObserver: createMockDirective(), + GlResizeObserver: createMockDirective('gl-resize-observer'), }, }); await nextTick(); @@ -55,21 +55,17 @@ describe('PackageTitle', () => { const findSubHeaderText = () => wrapper.findByTestId('sub-header'); const findSubHeaderTimeAgo = () => wrapper.findComponent(TimeAgoTooltip); - afterEach(() => { - wrapper.destroy(); - }); - describe('renders', () => { it('without tags', async () => { await createComponent({ ...packageData(), packageFiles: { nodes: packageFiles() } }); - expect(wrapper.element).toMatchSnapshot(); + expect(findPackageTags().exists()).toBe(false); }); it('with tags', async () => { await createComponent(); - expect(wrapper.element).toMatchSnapshot(); + expect(findPackageTags().exists()).toBe(true); }); it('with tags on mobile', async () => { diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/package_versions_list_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/package_versions_list_spec.js index 27c0ab96cfc..e9f2a2c5095 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/details/package_versions_list_spec.js +++ b/spec/frontend/packages_and_registries/package_registry/components/details/package_versions_list_spec.js @@ -1,5 +1,11 @@ +import Vue from 'vue'; +import VueApollo from 'vue-apollo'; +import { GlAlert } from '@gitlab/ui'; +import * as Sentry from '@sentry/browser'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import createMockApollo from 'helpers/mock_apollo_helper'; import { stubComponent } from 'helpers/stub_component'; +import waitForPromises from 'helpers/wait_for_promises'; import DeleteModal from '~/packages_and_registries/package_registry/components/delete_modal.vue'; import PackageVersionsList from '~/packages_and_registries/package_registry/components/details/package_versions_list.vue'; import PackagesListLoader from '~/packages_and_registries/shared/components/packages_list_loader.vue'; @@ -7,39 +13,53 @@ import RegistryList from '~/packages_and_registries/shared/components/registry_l import VersionRow from '~/packages_and_registries/package_registry/components/details/version_row.vue'; import Tracking from '~/tracking'; import { + CANCEL_DELETE_PACKAGE_VERSION_TRACKING_ACTION, CANCEL_DELETE_PACKAGE_VERSIONS_TRACKING_ACTION, + DELETE_PACKAGE_VERSION_TRACKING_ACTION, DELETE_PACKAGE_VERSIONS_TRACKING_ACTION, + REQUEST_DELETE_PACKAGE_VERSION_TRACKING_ACTION, REQUEST_DELETE_PACKAGE_VERSIONS_TRACKING_ACTION, + GRAPHQL_PAGE_SIZE, } from '~/packages_and_registries/package_registry/constants'; -import { packageData } from '../../mock_data'; +import getPackageVersionsQuery from '~/packages_and_registries/package_registry/graphql//queries/get_package_versions.query.graphql'; +import { + emptyPackageVersionsQuery, + packageVersionsQuery, + packageVersions, + pagination, +} from '../../mock_data'; + +Vue.use(VueApollo); describe('PackageVersionsList', () => { let wrapper; + let apolloProvider; const EmptySlotStub = { name: 'empty-slot-stub', template: '<div>empty message</div>' }; - const packageList = [ - packageData({ - name: 'version 1', - }), - packageData({ - id: `gid://gitlab/Packages::Package/112`, - name: 'version 2', - }), - ]; const uiElements = { + findAlert: () => wrapper.findComponent(GlAlert), findLoader: () => wrapper.findComponent(PackagesListLoader), findRegistryList: () => wrapper.findComponent(RegistryList), findEmptySlot: () => wrapper.findComponent(EmptySlotStub), - findListRow: () => wrapper.findAllComponents(VersionRow), + findListRow: () => wrapper.findComponent(VersionRow), + findAllListRow: () => wrapper.findAllComponents(VersionRow), findDeletePackagesModal: () => wrapper.findComponent(DeleteModal), }; - const mountComponent = (props) => { + + const mountComponent = ({ + props = {}, + resolver = jest.fn().mockResolvedValue(packageVersionsQuery()), + } = {}) => { + const requestHandlers = [[getPackageVersionsQuery, resolver]]; + apolloProvider = createMockApollo(requestHandlers); + wrapper = shallowMountExtended(PackageVersionsList, { + apolloProvider, propsData: { - versions: packageList, - pageInfo: {}, - isLoading: false, + packageId: packageVersionsQuery().data.package.id, + isMutationLoading: false, + count: packageVersions().length, ...props, }, stubs: { @@ -56,9 +76,13 @@ describe('PackageVersionsList', () => { }); }; + beforeEach(() => { + jest.spyOn(Sentry, 'captureException').mockImplementation(); + }); + describe('when list is loading', () => { beforeEach(() => { - mountComponent({ isLoading: true, versions: [] }); + mountComponent({ props: { isMutationLoading: true } }); }); it('displays loader', () => { expect(uiElements.findLoader().exists()).toBe(true); @@ -75,11 +99,24 @@ describe('PackageVersionsList', () => { it('does not display registry list', () => { expect(uiElements.findRegistryList().exists()).toBe(false); }); + + it('does not display alert', () => { + expect(uiElements.findAlert().exists()).toBe(false); + }); }); describe('when list is loaded and has no data', () => { - beforeEach(() => { - mountComponent({ isLoading: false, versions: [] }); + const resolver = jest.fn().mockResolvedValue(emptyPackageVersionsQuery); + beforeEach(async () => { + mountComponent({ + props: { isMutationLoading: false, count: 0 }, + resolver, + }); + await waitForPromises(); + }); + + it('skips graphql query', () => { + expect(resolver).not.toHaveBeenCalled(); }); it('displays empty slot message', () => { @@ -97,11 +134,44 @@ describe('PackageVersionsList', () => { it('does not display registry list', () => { expect(uiElements.findRegistryList().exists()).toBe(false); }); + + it('does not display alert', () => { + expect(uiElements.findAlert().exists()).toBe(false); + }); + }); + + describe('if load fails, alert', () => { + beforeEach(async () => { + mountComponent({ resolver: jest.fn().mockRejectedValue() }); + + await waitForPromises(); + }); + + it('is displayed', () => { + expect(uiElements.findAlert().exists()).toBe(true); + }); + + it('shows error message', () => { + expect(uiElements.findAlert().text()).toMatchInterpolatedText('Failed to load version data'); + }); + + it('is not dismissible', () => { + expect(uiElements.findAlert().props('dismissible')).toBe(false); + }); + + it('is of variant danger', () => { + expect(uiElements.findAlert().attributes('variant')).toBe('danger'); + }); + + it('error is logged in sentry', () => { + expect(Sentry.captureException).toHaveBeenCalled(); + }); }); describe('when list is loaded with data', () => { - beforeEach(() => { + beforeEach(async () => { mountComponent(); + await waitForPromises(); }); it('displays package registry list', () => { @@ -110,7 +180,7 @@ describe('PackageVersionsList', () => { it('binds the right props', () => { expect(uiElements.findRegistryList().props()).toMatchObject({ - items: packageList, + items: packageVersions(), pagination: {}, isLoading: false, hiddenDelete: true, @@ -118,17 +188,17 @@ describe('PackageVersionsList', () => { }); it('displays package version rows', () => { - expect(uiElements.findListRow().exists()).toEqual(true); - expect(uiElements.findListRow()).toHaveLength(packageList.length); + expect(uiElements.findAllListRow().exists()).toEqual(true); + expect(uiElements.findAllListRow()).toHaveLength(packageVersions().length); }); it('binds the correct props', () => { - expect(uiElements.findListRow().at(0).props()).toMatchObject({ - packageEntity: expect.objectContaining(packageList[0]), + expect(uiElements.findAllListRow().at(0).props()).toMatchObject({ + packageEntity: expect.objectContaining(packageVersions()[0]), }); - expect(uiElements.findListRow().at(1).props()).toMatchObject({ - packageEntity: expect.objectContaining(packageList[1]), + expect(uiElements.findAllListRow().at(1).props()).toMatchObject({ + packageEntity: expect.objectContaining(packageVersions()[1]), }); }); @@ -142,20 +212,94 @@ describe('PackageVersionsList', () => { }); describe('when user interacts with pagination', () => { - beforeEach(() => { - mountComponent({ pageInfo: { hasNextPage: true } }); + const resolver = jest.fn().mockResolvedValue(packageVersionsQuery()); + + beforeEach(async () => { + mountComponent({ resolver }); + await waitForPromises(); + }); + + it('when list emits next-page fetches the next set of records', async () => { + uiElements.findRegistryList().vm.$emit('next-page'); + await waitForPromises(); + + expect(resolver).toHaveBeenLastCalledWith( + expect.objectContaining({ after: pagination().endCursor, first: GRAPHQL_PAGE_SIZE }), + ); }); - it('emits prev-page event when registry list emits prev event', () => { + it('when list emits prev-page fetches the prev set of records', async () => { uiElements.findRegistryList().vm.$emit('prev-page'); + await waitForPromises(); - expect(wrapper.emitted('prev-page')).toHaveLength(1); + expect(resolver).toHaveBeenLastCalledWith( + expect.objectContaining({ before: pagination().startCursor, last: GRAPHQL_PAGE_SIZE }), + ); }); + }); - it('emits next-page when registry list emits next event', () => { - uiElements.findRegistryList().vm.$emit('next-page'); + describe.each` + description | finderFunction | deletePayload + ${'when the user can destroy the package'} | ${uiElements.findListRow} | ${packageVersions()[0]} + ${'when the user can bulk destroy packages and deletes only one package'} | ${uiElements.findRegistryList} | ${[packageVersions()[0]]} + `('$description', ({ finderFunction, deletePayload }) => { + let eventSpy; + const category = 'UI::NpmPackages'; + const { findDeletePackagesModal } = uiElements; + + beforeEach(async () => { + eventSpy = jest.spyOn(Tracking, 'event'); + mountComponent({ props: { canDestroy: true } }); + await waitForPromises(); + finderFunction().vm.$emit('delete', deletePayload); + }); + + it('passes itemsToBeDeleted to the modal', () => { + expect(findDeletePackagesModal().props('itemsToBeDeleted')).toStrictEqual([ + packageVersions()[0], + ]); + }); + + it('requesting delete tracks the right action', () => { + expect(eventSpy).toHaveBeenCalledWith( + category, + REQUEST_DELETE_PACKAGE_VERSION_TRACKING_ACTION, + expect.any(Object), + ); + }); + + describe('when modal confirms', () => { + beforeEach(() => { + findDeletePackagesModal().vm.$emit('confirm'); + }); + + it('emits delete when modal confirms', () => { + expect(wrapper.emitted('delete')[0][0]).toEqual([packageVersions()[0]]); + }); - expect(wrapper.emitted('next-page')).toHaveLength(1); + it('tracks the right action', () => { + expect(eventSpy).toHaveBeenCalledWith( + category, + DELETE_PACKAGE_VERSION_TRACKING_ACTION, + expect.any(Object), + ); + }); + }); + + it.each(['confirm', 'cancel'])('resets itemsToBeDeleted when modal emits %s', async (event) => { + await findDeletePackagesModal().vm.$emit(event); + + expect(findDeletePackagesModal().props('itemsToBeDeleted')).toEqual([]); + }); + + it('canceling delete tracks the right action', () => { + findDeletePackagesModal().vm.$emit('cancel'); + + expect(eventSpy).toHaveBeenCalledWith( + category, + CANCEL_DELETE_PACKAGE_VERSION_TRACKING_ACTION, + expect.any(Object), + ); }); }); @@ -163,14 +307,15 @@ describe('PackageVersionsList', () => { let eventSpy; const { findDeletePackagesModal, findRegistryList } = uiElements; - beforeEach(() => { + beforeEach(async () => { eventSpy = jest.spyOn(Tracking, 'event'); - mountComponent({ canDestroy: true }); + mountComponent({ props: { canDestroy: true } }); + await waitForPromises(); }); it('binds the right props', () => { expect(uiElements.findRegistryList().props()).toMatchObject({ - items: packageList, + items: packageVersions(), pagination: {}, isLoading: false, hiddenDelete: false, @@ -180,11 +325,13 @@ describe('PackageVersionsList', () => { describe('upon deletion', () => { beforeEach(() => { - findRegistryList().vm.$emit('delete', packageList); + findRegistryList().vm.$emit('delete', packageVersions()); }); it('passes itemsToBeDeleted to the modal', () => { - expect(findDeletePackagesModal().props('itemsToBeDeleted')).toStrictEqual(packageList); + expect(findDeletePackagesModal().props('itemsToBeDeleted')).toStrictEqual( + packageVersions(), + ); expect(wrapper.emitted('delete')).toBeUndefined(); }); @@ -202,7 +349,7 @@ describe('PackageVersionsList', () => { }); it('emits delete event', () => { - expect(wrapper.emitted('delete')[0]).toEqual([packageList]); + expect(wrapper.emitted('delete')[0]).toEqual([packageVersions()]); }); it('tracks the right action', () => { @@ -234,4 +381,15 @@ describe('PackageVersionsList', () => { }); }); }); + + describe('with isRequestForwardingEnabled prop', () => { + const { findDeletePackagesModal } = uiElements; + + it.each([true, false])('sets modal prop showRequestForwardingContent to %s', async (value) => { + mountComponent({ props: { isRequestForwardingEnabled: value } }); + await waitForPromises(); + + expect(findDeletePackagesModal().props('showRequestForwardingContent')).toBe(value); + }); + }); }); 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 4a27f8011df..3f4358bb3b0 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 @@ -29,10 +29,13 @@ password = <your personal access token>`; const findInstallationTitle = () => wrapper.findComponent(InstallationTitle); const findSetupDocsLink = () => wrapper.findByTestId('pypi-docs-link'); - function createComponent() { + function createComponent(props = {}) { wrapper = mountExtended(PypiInstallation, { propsData: { - packageEntity, + packageEntity: { + ...packageEntity, + ...props, + }, }, stubs: { GlSprintf, @@ -44,10 +47,6 @@ password = <your personal access token>`; createComponent(); }); - afterEach(() => { - wrapper.destroy(); - }); - describe('install command switch', () => { it('has the installation title component', () => { expect(findInstallationTitle().exists()).toBe(true); @@ -86,6 +85,12 @@ password = <your personal access token>`; }); }); + it('does not have a link to personal access token docs when package is public', () => { + createComponent({ publicPackage: true }); + + expect(findAccessTokenLink().exists()).toBe(false); + }); + it('has a link to the docs', () => { expect(findSetupDocsLink().attributes()).toMatchObject({ href: PYPI_HELP_PATH, diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/version_row_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/version_row_spec.js index 67340822fa5..f7c8e909ff6 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/details/version_row_spec.js +++ b/spec/frontend/packages_and_registries/package_registry/components/details/version_row_spec.js @@ -1,4 +1,4 @@ -import { GlFormCheckbox, GlIcon, GlLink, GlSprintf, GlTruncate } from '@gitlab/ui'; +import { GlDropdownItem, GlFormCheckbox, GlIcon, GlLink, GlSprintf, GlTruncate } from '@gitlab/ui'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import ListItem from '~/vue_shared/components/registry/list_item.vue'; @@ -24,6 +24,7 @@ describe('VersionRow', () => { const findPackageName = () => wrapper.findComponent(GlTruncate); const findWarningIcon = () => wrapper.findComponent(GlIcon); const findBulkDeleteAction = () => wrapper.findComponent(GlFormCheckbox); + const findDeleteDropdownItem = () => wrapper.findComponent(GlDropdownItem); function createComponent({ packageEntity = packageVersion, selected = false } = {}) { wrapper = shallowMountExtended(VersionRow, { @@ -36,15 +37,11 @@ describe('VersionRow', () => { GlTruncate, }, directives: { - GlTooltip: createMockDirective(), + GlTooltip: createMockDirective('gl-tooltip'), }, }); } - afterEach(() => { - wrapper.destroy(); - }); - it('has a link to the version detail', () => { createComponent(); @@ -112,6 +109,31 @@ describe('VersionRow', () => { }); }); + describe('delete button', () => { + it('does not exist when package cannot be destroyed', () => { + createComponent({ packageEntity: { ...packageVersion, canDestroy: false } }); + + expect(findDeleteDropdownItem().exists()).toBe(false); + }); + + it('exists and has the correct props', () => { + createComponent(); + + expect(findDeleteDropdownItem().exists()).toBe(true); + expect(findDeleteDropdownItem().attributes()).toMatchObject({ + variant: 'danger', + }); + }); + + it('emits the delete event when the delete button is clicked', () => { + createComponent(); + + findDeleteDropdownItem().vm.$emit('click'); + + expect(wrapper.emitted('delete')).toHaveLength(1); + }); + }); + describe(`when the package is in ${PACKAGE_ERROR_STATUS} status`, () => { beforeEach(() => { createComponent({ diff --git a/spec/frontend/packages_and_registries/package_registry/components/functional/delete_packages_spec.js b/spec/frontend/packages_and_registries/package_registry/components/functional/delete_packages_spec.js index 689b53fa2a4..04546c4cea4 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/functional/delete_packages_spec.js +++ b/spec/frontend/packages_and_registries/package_registry/components/functional/delete_packages_spec.js @@ -3,7 +3,7 @@ import VueApollo from 'vue-apollo'; import waitForPromises from 'helpers/wait_for_promises'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import createMockApollo from 'helpers/mock_apollo_helper'; -import { createAlert, VARIANT_SUCCESS, VARIANT_WARNING } from '~/flash'; +import { createAlert, VARIANT_SUCCESS, VARIANT_WARNING } from '~/alert'; import DeletePackages from '~/packages_and_registries/package_registry/components/functional/delete_packages.vue'; import destroyPackagesMutation from '~/packages_and_registries/package_registry/graphql/mutations/destroy_packages.mutation.graphql'; @@ -14,7 +14,7 @@ import { packagesListQuery, } from '../../mock_data'; -jest.mock('~/flash'); +jest.mock('~/alert'); describe('DeletePackages', () => { let wrapper; @@ -67,10 +67,6 @@ describe('DeletePackages', () => { mutationResolver = jest.fn().mockResolvedValue(packagesDestroyMutation()); }); - afterEach(() => { - wrapper.destroy(); - }); - it('binds deletePackages method to the default slot', () => { createComponent(); 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 ec8e77fa923..c647230bc5f 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 @@ -66,11 +66,12 @@ exports[`packages_list_row renders 1`] = ` <!----> - <package-icon-and-name-stub> - - npm - - </package-icon-and-name-stub> + <span + class="gl-ml-2" + data-testid="package-type" + > + · npm + </span> <!----> </div> @@ -95,6 +96,7 @@ exports[`packages_list_row renders 1`] = ` Created <timeago-tooltip-stub cssclass="" + datetimeformat="DATE_WITH_TIME_FORMAT" time="2020-08-17T14:23:32Z" tooltipplacement="top" /> 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 2a78cfb13f9..81ad47b1e13 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,5 +1,5 @@ import { GlFormCheckbox, GlSprintf, GlTruncate } from '@gitlab/ui'; -import Vue, { nextTick } from 'vue'; +import Vue from 'vue'; import VueRouter from 'vue-router'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; @@ -9,7 +9,6 @@ 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'; -import PackageIconAndName from '~/packages_and_registries/shared/components/package_icon_and_name.vue'; import PublishMethod from '~/packages_and_registries/package_registry/components/list/publish_method.vue'; import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; import { PACKAGE_ERROR_STATUS } from '~/packages_and_registries/package_registry/constants'; @@ -39,7 +38,7 @@ describe('packages_list_row', () => { const findPackageTags = () => wrapper.findComponent(PackageTags); const findPackagePath = () => wrapper.findComponent(PackagePath); const findDeleteDropdown = () => wrapper.findByTestId('action-delete'); - const findPackageIconAndName = () => wrapper.findComponent(PackageIconAndName); + const findPackageType = () => wrapper.findByTestId('package-type'); const findPackageLink = () => wrapper.findByTestId('details-link'); const findWarningIcon = () => wrapper.findByTestId('warning-icon'); const findLeftSecondaryInfos = () => wrapper.findByTestId('left-secondary-infos'); @@ -67,15 +66,11 @@ describe('packages_list_row', () => { selected, }, directives: { - GlTooltip: createMockDirective(), + GlTooltip: createMockDirective('gl-tooltip'), }, }); }; - afterEach(() => { - wrapper.destroy(); - }); - it('renders', () => { mountComponent(); expect(wrapper.element).toMatchSnapshot(); @@ -136,12 +131,11 @@ describe('packages_list_row', () => { }); }); - it('emits the delete event when the delete button is clicked', async () => { + it('emits the delete event when the delete button is clicked', () => { mountComponent({ packageEntity: packageWithoutTags }); findDeleteDropdown().vm.$emit('click'); - await nextTick(); expect(wrapper.emitted('delete')).toHaveLength(1); }); }); @@ -237,10 +231,10 @@ describe('packages_list_row', () => { expect(findLeftSecondaryInfos().text()).toContain('published by Administrator'); }); - it('has icon and name component', () => { + it('has package type with middot', () => { mountComponent(); - expect(findPackageIconAndName().text()).toBe(packageWithoutTags.packageType.toLowerCase()); + expect(findPackageType().text()).toBe(`· ${packageWithoutTags.packageType.toLowerCase()}`); }); }); diff --git a/spec/frontend/packages_and_registries/package_registry/components/list/packages_list_spec.js b/spec/frontend/packages_and_registries/package_registry/components/list/packages_list_spec.js index 610640e0ca3..483b7a9383d 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/list/packages_list_spec.js +++ b/spec/frontend/packages_and_registries/package_registry/components/list/packages_list_spec.js @@ -4,7 +4,6 @@ import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import { stubComponent } from 'helpers/stub_component'; import PackagesListRow from '~/packages_and_registries/package_registry/components/list/package_list_row.vue'; import PackagesListLoader from '~/packages_and_registries/shared/components/packages_list_loader.vue'; -import DeletePackageModal from '~/packages_and_registries/shared/components/delete_package_modal.vue'; import DeleteModal from '~/packages_and_registries/package_registry/components/delete_modal.vue'; import RegistryList from '~/packages_and_registries/shared/components/registry_list.vue'; import { @@ -17,7 +16,7 @@ import { } from '~/packages_and_registries/package_registry/constants'; import PackagesList from '~/packages_and_registries/package_registry/components/list/packages_list.vue'; import Tracking from '~/tracking'; -import { packageData } from '../../mock_data'; +import { defaultPackageGroupSettings, packageData } from '../../mock_data'; describe('packages_list', () => { let wrapper; @@ -39,18 +38,20 @@ describe('packages_list', () => { list: [firstPackage, secondPackage], isLoading: false, pageInfo: {}, + groupSettings: defaultPackageGroupSettings, }; const EmptySlotStub = { name: 'empty-slot-stub', template: '<div>bar</div>' }; const findPackagesListLoader = () => wrapper.findComponent(PackagesListLoader); - const findPackageListDeleteModal = () => wrapper.findComponent(DeletePackageModal); const findEmptySlot = () => wrapper.findComponent(EmptySlotStub); const findRegistryList = () => wrapper.findComponent(RegistryList); const findPackagesListRow = () => wrapper.findComponent(PackagesListRow); const findErrorPackageAlert = () => wrapper.findComponent(GlAlert); const findDeletePackagesModal = () => wrapper.findComponent(DeleteModal); + const showMock = jest.fn(); + const mountComponent = (props) => { wrapper = shallowMountExtended(PackagesList, { propsData: { @@ -58,10 +59,9 @@ describe('packages_list', () => { ...props, }, stubs: { - DeletePackageModal, DeleteModal: stubComponent(DeleteModal, { methods: { - show: jest.fn(), + show: showMock, }, }), GlSprintf, @@ -73,10 +73,6 @@ describe('packages_list', () => { }); }; - afterEach(() => { - wrapper.destroy(); - }); - describe('when is loading', () => { beforeEach(() => { mountComponent({ isLoading: true }); @@ -123,15 +119,20 @@ describe('packages_list', () => { }); describe('layout', () => { - it("doesn't contain a visible modal component", () => { + beforeEach(() => { mountComponent(); + }); - expect(findPackageListDeleteModal().props('itemToBeDeleted')).toBeNull(); + it('modal component is not shown', () => { + expect(showMock).not.toHaveBeenCalled(); }); - it('does not have an error alert displayed', () => { - mountComponent(); + it('modal component props is empty', () => { + expect(findDeletePackagesModal().props('itemsToBeDeleted')).toEqual([]); + expect(findDeletePackagesModal().props('showRequestForwardingContent')).toBe(false); + }); + it('does not have an error alert displayed', () => { expect(findErrorPackageAlert().exists()).toBe(false); }); }); @@ -150,8 +151,8 @@ describe('packages_list', () => { finderFunction().vm.$emit('delete', deletePayload); }); - it('passes itemToBeDeleted to the modal', () => { - expect(findPackageListDeleteModal().props('itemToBeDeleted')).toStrictEqual(firstPackage); + it('passes itemsToBeDeleted to the modal', () => { + expect(findDeletePackagesModal().props('itemsToBeDeleted')).toStrictEqual([firstPackage]); }); it('requesting delete tracks the right action', () => { @@ -162,9 +163,13 @@ describe('packages_list', () => { ); }); + it('modal component is shown', () => { + expect(showMock).toHaveBeenCalledTimes(1); + }); + describe('when modal confirms', () => { beforeEach(() => { - findPackageListDeleteModal().vm.$emit('ok'); + findDeletePackagesModal().vm.$emit('confirm'); }); it('emits delete when modal confirms', () => { @@ -180,14 +185,14 @@ describe('packages_list', () => { }); }); - it.each(['ok', 'cancel'])('resets itemToBeDeleted when modal emits %s', async (event) => { - await findPackageListDeleteModal().vm.$emit(event); + it.each(['confirm', 'cancel'])('resets itemsToBeDeleted when modal emits %s', async (event) => { + await findDeletePackagesModal().vm.$emit(event); - expect(findPackageListDeleteModal().props('itemToBeDeleted')).toBeNull(); + expect(findDeletePackagesModal().props('itemsToBeDeleted')).toEqual([]); }); it('canceling delete tracks the right action', () => { - findPackageListDeleteModal().vm.$emit('cancel'); + findDeletePackagesModal().vm.$emit('cancel'); expect(eventSpy).toHaveBeenCalledWith( category, @@ -241,7 +246,7 @@ describe('packages_list', () => { it.each(['confirm', 'cancel'])('resets itemsToBeDeleted when modal emits %s', async (event) => { await findDeletePackagesModal().vm.$emit(event); - expect(findDeletePackagesModal().props('itemsToBeDeleted')).toHaveLength(0); + expect(findDeletePackagesModal().props('itemsToBeDeleted')).toEqual([]); }); it('canceling delete tracks the right action', () => { @@ -262,7 +267,7 @@ describe('packages_list', () => { return nextTick(); }); - it('should display an alert message', () => { + it('should display an alert', () => { expect(findErrorPackageAlert().exists()).toBe(true); expect(findErrorPackageAlert().props('title')).toBe( 'There was an error publishing a error package package', @@ -277,7 +282,9 @@ describe('packages_list', () => { await nextTick(); - expect(findPackageListDeleteModal().text()).toContain(errorPackage.name); + expect(showMock).toHaveBeenCalledTimes(1); + + expect(findDeletePackagesModal().props('itemsToBeDeleted')).toStrictEqual([errorPackage]); }); }); diff --git a/spec/frontend/packages_and_registries/package_registry/components/list/packages_search_spec.js b/spec/frontend/packages_and_registries/package_registry/components/list/packages_search_spec.js index a884959ab62..82fa5b76367 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/list/packages_search_spec.js +++ b/spec/frontend/packages_and_registries/package_registry/components/list/packages_search_spec.js @@ -46,10 +46,6 @@ describe('Package Search', () => { extractFilterAndSorting.mockReturnValue(defaultQueryParamsMock); }); - afterEach(() => { - wrapper.destroy(); - }); - it('has a registry search component', async () => { mountComponent(); @@ -58,7 +54,7 @@ describe('Package Search', () => { expect(findRegistrySearch().exists()).toBe(true); }); - it('registry search is mounted after mount', async () => { + it('registry search is mounted after mount', () => { mountComponent(); expect(findRegistrySearch().exists()).toBe(false); diff --git a/spec/frontend/packages_and_registries/package_registry/components/list/packages_title_spec.js b/spec/frontend/packages_and_registries/package_registry/components/list/packages_title_spec.js index b47515e15c3..1296458155a 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/list/packages_title_spec.js +++ b/spec/frontend/packages_and_registries/package_registry/components/list/packages_title_spec.js @@ -20,11 +20,6 @@ describe('PackageTitle', () => { }); }; - afterEach(() => { - wrapper.destroy(); - wrapper = null; - }); - describe('title area', () => { it('exists', () => { mountComponent(); diff --git a/spec/frontend/packages_and_registries/package_registry/components/list/publish_method_spec.js b/spec/frontend/packages_and_registries/package_registry/components/list/publish_method_spec.js index fcbd7cc6a50..e9119b736c2 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/list/publish_method_spec.js +++ b/spec/frontend/packages_and_registries/package_registry/components/list/publish_method_spec.js @@ -19,10 +19,6 @@ describe('publish_method', () => { }); }; - afterEach(() => { - wrapper.destroy(); - }); - it('renders', () => { mountComponent(); expect(wrapper.element).toMatchSnapshot(); diff --git a/spec/frontend/packages_and_registries/package_registry/components/list/tokens/package_type_token_spec.js b/spec/frontend/packages_and_registries/package_registry/components/list/tokens/package_type_token_spec.js index 8f3c8667c47..c98f5f32344 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/list/tokens/package_type_token_spec.js +++ b/spec/frontend/packages_and_registries/package_registry/components/list/tokens/package_type_token_spec.js @@ -19,11 +19,6 @@ describe('packages_filter', () => { }); }; - afterEach(() => { - wrapper.destroy(); - wrapper = null; - }); - it('binds all of his attrs to filtered search token', () => { mountComponent({ attrs: { foo: 'bar' } }); 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 d897be1f344..5fb53566d4e 100644 --- a/spec/frontend/packages_and_registries/package_registry/mock_data.js +++ b/spec/frontend/packages_and_registries/package_registry/mock_data.js @@ -16,7 +16,7 @@ export const packagePipelines = (extend) => [ ref: 'master', sha: 'b83d6e391c22777fca1ed3012fce84f633d7fed0', project: { - id: '1', + id: '14', name: 'project14', webUrl: 'http://gdk.test:3000/namespace14/project14', __typename: 'Project', @@ -103,12 +103,20 @@ export const linksData = { }, }; +export const defaultPackageGroupSettings = { + mavenPackageRequestsForwarding: true, + npmPackageRequestsForwarding: true, + pypiPackageRequestsForwarding: true, + __typename: 'PackageSettings', +}; + export const packageVersions = () => [ { createdAt: '2021-08-10T09:33:54Z', id: 'gid://gitlab/Packages::Package/243', name: '@gitlab-org/package-15', status: 'DEFAULT', + packageType: 'NPM', canDestroy: true, tags: { nodes: packageTags() }, version: '1.0.1', @@ -120,6 +128,7 @@ export const packageVersions = () => [ id: 'gid://gitlab/Packages::Package/244', name: '@gitlab-org/package-15', status: 'DEFAULT', + packageType: 'NPM', canDestroy: true, tags: { nodes: packageTags() }, version: '1.0.2', @@ -130,7 +139,7 @@ export const packageVersions = () => [ export const packageData = (extend) => ({ __typename: 'Package', - id: 'gid://gitlab/Packages::Package/111', + id: 'gid://gitlab/Packages::Package/1', canDestroy: true, name: '@gitlab-org/package-15', packageType: 'NPM', @@ -147,6 +156,7 @@ export const packageData = (extend) => ({ 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', + publicPackage: false, pypiSetupUrl: 'http://gdk.test:3000/api/v4/projects/1/packages/pypi', ...extend, }); @@ -209,7 +219,10 @@ export const pagination = (extend) => ({ ...extend, }); -export const packageDetailsQuery = (extendPackage) => ({ +export const packageDetailsQuery = ({ + extendPackage = {}, + packageSettings = defaultPackageGroupSettings, +} = {}) => ({ data: { package: { ...packageData(), @@ -225,6 +238,12 @@ export const packageDetailsQuery = (extendPackage) => ({ path: 'projectPath', name: 'gitlab-test', fullPath: 'gitlab-test', + group: { + id: '1', + packageSettings, + __typename: 'Group', + }, + __typename: 'Project', }, tags: { nodes: packageTags(), @@ -243,14 +262,6 @@ export const packageDetailsQuery = (extendPackage) => ({ }, versions: { count: packageVersions().length, - nodes: packageVersions(), - pageInfo: { - hasNextPage: true, - hasPreviousPage: false, - endCursor: 'endCursor', - startCursor: 'startCursor', - }, - __typename: 'PackageConnection', }, dependencyLinks: { nodes: dependencyLinks(), @@ -297,6 +308,41 @@ export const packageMetadataQuery = (packageType) => { }; }; +export const packageVersionsQuery = (versions = packageVersions()) => ({ + data: { + package: { + id: 'gid://gitlab/Packages::Package/111', + versions: { + count: versions.length, + nodes: versions, + pageInfo: pagination(), + __typename: 'PackageConnection', + }, + __typename: 'PackageDetailsType', + }, + }, +}); + +export const emptyPackageVersionsQuery = { + data: { + package: { + id: 'gid://gitlab/Packages::Package/111', + versions: { + count: 0, + nodes: [], + pageInfo: { + hasNextPage: false, + hasPreviousPage: false, + endCursor: 'endCursor', + startCursor: 'startCursor', + }, + __typename: 'PackageConnection', + }, + __typename: 'PackageDetailsType', + }, + }, +}; + export const packagesDestroyMutation = () => ({ data: { destroyPackages: { @@ -351,7 +397,12 @@ export const packageDestroyFilesMutationError = () => ({ ], }); -export const packagesListQuery = ({ type = 'group', extend = {}, extendPagination = {} } = {}) => ({ +export const packagesListQuery = ({ + type = 'group', + extend = {}, + extendPagination = {}, + packageSettings = defaultPackageGroupSettings, +} = {}) => ({ data: { [type]: { id: '1', @@ -378,6 +429,14 @@ export const packagesListQuery = ({ type = 'group', extend = {}, extendPaginatio pageInfo: pagination(extendPagination), __typename: 'PackageConnection', }, + ...(type === 'group' && { packageSettings }), + ...(type === 'project' && { + group: { + id: '1', + packageSettings, + __typename: 'Group', + }, + }), ...extend, __typename: capitalize(type), }, diff --git a/spec/frontend/packages_and_registries/package_registry/pages/details_spec.js b/spec/frontend/packages_and_registries/package_registry/pages/details_spec.js index b494965a3cb..0962b4fa757 100644 --- a/spec/frontend/packages_and_registries/package_registry/pages/details_spec.js +++ b/spec/frontend/packages_and_registries/package_registry/pages/details_spec.js @@ -1,4 +1,4 @@ -import { GlEmptyState, GlTabs, GlTab, GlSprintf } from '@gitlab/ui'; +import { GlEmptyState, GlModal, GlTabs, GlTab, GlSprintf, GlLink } from '@gitlab/ui'; import Vue, { nextTick } from 'vue'; import VueApollo from 'vue-apollo'; @@ -6,8 +6,8 @@ import createMockApollo from 'helpers/mock_apollo_helper'; import { useMockLocationHelper } from 'helpers/mock_window_location_helper'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import waitForPromises from 'helpers/wait_for_promises'; -import { createAlert } from '~/flash'; - +import { createAlert } from '~/alert'; +import { stubComponent } from 'helpers/stub_component'; import AdditionalMetadata from '~/packages_and_registries/package_registry/components/details/additional_metadata.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'; @@ -18,6 +18,7 @@ import PackageTitle from '~/packages_and_registries/package_registry/components/ import DeletePackages from '~/packages_and_registries/package_registry/components/functional/delete_packages.vue'; import PackageVersionsList from '~/packages_and_registries/package_registry/components/details/package_versions_list.vue'; import { + REQUEST_FORWARDING_HELP_PAGE_PATH, FETCH_PACKAGE_DETAILS_ERROR_MESSAGE, PACKAGE_TYPE_COMPOSER, DELETE_PACKAGE_FILE_SUCCESS_MESSAGE, @@ -33,6 +34,7 @@ import { import destroyPackageFilesMutation from '~/packages_and_registries/package_registry/graphql/mutations/destroy_package_files.mutation.graphql'; import getPackageDetails from '~/packages_and_registries/package_registry/graphql/queries/get_package_details.query.graphql'; +import getPackageVersionsQuery from '~/packages_and_registries/package_registry/graphql//queries/get_package_versions.query.graphql'; import { packageDetailsQuery, packageData, @@ -42,12 +44,14 @@ import { packageFiles, packageDestroyFilesMutation, packageDestroyFilesMutationError, - pagination, + defaultPackageGroupSettings, } from '../mock_data'; -jest.mock('~/flash'); +jest.mock('~/alert'); useMockLocationHelper(); +Vue.use(VueApollo); + describe('PackagesApp', () => { let wrapper; let apolloProvider; @@ -57,7 +61,7 @@ describe('PackagesApp', () => { }; const provide = { - packageId: '111', + packageId: '1', emptyListIllustration: 'svgPath', projectListUrl: 'projectListUrl', groupListUrl: 'groupListUrl', @@ -66,14 +70,13 @@ describe('PackagesApp', () => { }; const { __typename, ...packageWithoutTypename } = packageData(); + const showMock = jest.fn(); function createComponent({ resolver = jest.fn().mockResolvedValue(packageDetailsQuery()), filesDeleteMutationResolver = jest.fn().mockResolvedValue(packageDestroyFilesMutation()), routeId = '1', } = {}) { - Vue.use(VueApollo); - const requestHandlers = [ [getPackageDetails, resolver], [destroyPackageFilesMutation, filesDeleteMutationResolver], @@ -86,17 +89,11 @@ describe('PackagesApp', () => { stubs: { PackageTitle, DeletePackages, - GlModal: { - template: ` - <div> - <slot name="modal-title"></slot> - <p><slot></slot></p> - </div> - `, + GlModal: stubComponent(GlModal, { methods: { - show: jest.fn(), + show: showMock, }, - }, + }), GlSprintf, GlTabs, GlTab, @@ -130,10 +127,7 @@ describe('PackagesApp', () => { const findDependencyRows = () => wrapper.findAllComponents(DependencyRow); const findDeletePackageModal = () => wrapper.findAllComponents(DeletePackages).at(1); const findDeletePackages = () => wrapper.findComponent(DeletePackages); - - afterEach(() => { - wrapper.destroy(); - }); + const findLink = () => wrapper.findComponent(GlLink); it('renders an empty state component', async () => { createComponent({ resolver: jest.fn().mockResolvedValue(emptyPackageDetailsQuery) }); @@ -193,7 +187,9 @@ describe('PackagesApp', () => { createComponent({ resolver: jest.fn().mockResolvedValue( packageDetailsQuery({ - packageType, + extendPackage: { + packageType, + }, }), ), }); @@ -248,16 +244,55 @@ describe('PackagesApp', () => { }); }); - it('shows the delete confirmation modal when delete is clicked', async () => { - createComponent(); + describe('when delete button is clicked', () => { + describe('with request forwarding enabled', () => { + beforeEach(async () => { + const resolver = jest.fn().mockResolvedValue( + packageDetailsQuery({ + packageSettings: { + ...defaultPackageGroupSettings, + npmPackageRequestsForwarding: true, + }, + }), + ); + createComponent({ resolver }); - await waitForPromises(); + await waitForPromises(); - await findDeleteButton().trigger('click'); + await findDeleteButton().trigger('click'); + }); - expect(findDeleteModal().find('p').text()).toBe( - 'You are about to delete version 1.0.0 of @gitlab-org/package-15. Are you sure?', - ); + it('shows the delete confirmation modal with request forwarding content', () => { + expect(findDeleteModal().text()).toBe( + 'Deleting this package while request forwarding is enabled for the project can pose a security risk. Do you want to delete @gitlab-org/package-15 version 1.0.0 anyway? What are the risks?', + ); + }); + + it('contains link to help page', () => { + expect(findLink().exists()).toBe(true); + expect(findLink().attributes('href')).toBe(REQUEST_FORWARDING_HELP_PAGE_PATH); + }); + }); + + it('shows the delete confirmation modal without request forwarding content', async () => { + const resolver = jest.fn().mockResolvedValue( + packageDetailsQuery({ + packageSettings: { + ...defaultPackageGroupSettings, + npmPackageRequestsForwarding: false, + }, + }), + ); + createComponent({ resolver }); + + await waitForPromises(); + + await findDeleteButton().trigger('click'); + + expect(findDeleteModal().text()).toBe( + 'You are about to delete version 1.0.0 of @gitlab-org/package-15. Are you sure?', + ); + }); }); describe('successful request', () => { @@ -311,7 +346,9 @@ describe('PackagesApp', () => { createComponent({ resolver: jest .fn() - .mockResolvedValue(packageDetailsQuery({ packageType: PACKAGE_TYPE_COMPOSER })), + .mockResolvedValue( + packageDetailsQuery({ extendPackage: { packageType: PACKAGE_TYPE_COMPOSER } }), + ), }); await waitForPromises(); @@ -322,7 +359,7 @@ describe('PackagesApp', () => { describe('deleting a file', () => { const [fileToDelete] = packageFiles(); - const doDeleteFile = async () => { + const doDeleteFile = () => { findPackageFiles().vm.$emit('delete-files', [fileToDelete]); findDeleteFileModal().vm.$emit('primary'); @@ -335,25 +372,33 @@ describe('PackagesApp', () => { await waitForPromises(); - const showDeleteFileSpy = jest.spyOn(wrapper.vm.$refs.deleteFileModal, 'show'); - const showDeletePackageSpy = jest.spyOn(wrapper.vm.$refs.deleteModal, 'show'); - findPackageFiles().vm.$emit('delete-files', [fileToDelete]); - expect(showDeletePackageSpy).not.toHaveBeenCalled(); - expect(showDeleteFileSpy).toHaveBeenCalled(); + expect(showMock).toHaveBeenCalledTimes(1); + + await waitForPromises(); + + expect(findDeleteFileModal().text()).toBe( + 'You are about to delete foo-1.0.1.tgz. This is a destructive action that may render your package unusable. Are you sure?', + ); }); it('when its the only file opens delete package confirmation modal', async () => { const [packageFile] = packageFiles(); const resolver = jest.fn().mockResolvedValue( packageDetailsQuery({ - packageFiles: { - pageInfo: { - hasNextPage: false, + extendPackage: { + packageFiles: { + pageInfo: { + hasNextPage: false, + }, + nodes: [packageFile], + __typename: 'PackageFileConnection', }, - nodes: [packageFile], - __typename: 'PackageFileConnection', + }, + packageSettings: { + ...defaultPackageGroupSettings, + npmPackageRequestsForwarding: false, }, }), ); @@ -364,17 +409,13 @@ describe('PackagesApp', () => { await waitForPromises(); - const showDeleteFileSpy = jest.spyOn(wrapper.vm.$refs.deleteFileModal, 'show'); - const showDeletePackageSpy = jest.spyOn(wrapper.vm.$refs.deleteModal, 'show'); - findPackageFiles().vm.$emit('delete-files', [fileToDelete]); - expect(showDeletePackageSpy).toHaveBeenCalled(); - expect(showDeleteFileSpy).not.toHaveBeenCalled(); + expect(showMock).toHaveBeenCalledTimes(1); await waitForPromises(); - expect(findDeleteModal().find('p').text()).toBe( + expect(findDeleteModal().text()).toBe( 'Deleting the last package asset will remove version 1.0.0 of @gitlab-org/package-15. Are you sure?', ); }); @@ -444,7 +485,7 @@ describe('PackagesApp', () => { }); describe('deleting multiple files', () => { - const doDeleteFiles = async () => { + const doDeleteFiles = () => { findPackageFiles().vm.$emit('delete-files', packageFiles()); findDeleteFilesModal().vm.$emit('primary'); @@ -486,6 +527,8 @@ describe('PackagesApp', () => { await doDeleteFiles(); + expect(resolver).toHaveBeenCalledTimes(2); + expect(createAlert).toHaveBeenCalledWith( expect.objectContaining({ message: DELETE_PACKAGE_FILES_SUCCESS_MESSAGE, @@ -532,11 +575,17 @@ describe('PackagesApp', () => { it('opens the delete package confirmation modal', async () => { const resolver = jest.fn().mockResolvedValue( packageDetailsQuery({ - packageFiles: { - pageInfo: { - hasNextPage: false, + extendPackage: { + packageFiles: { + pageInfo: { + hasNextPage: false, + }, + nodes: packageFiles(), }, - nodes: packageFiles(), + }, + packageSettings: { + ...defaultPackageGroupSettings, + npmPackageRequestsForwarding: false, }, }), ); @@ -546,15 +595,13 @@ describe('PackagesApp', () => { await waitForPromises(); - const showDeletePackageSpy = jest.spyOn(wrapper.vm.$refs.deleteModal, 'show'); - findPackageFiles().vm.$emit('delete-files', packageFiles()); - expect(showDeletePackageSpy).toHaveBeenCalled(); + expect(showMock).toHaveBeenCalledTimes(1); await waitForPromises(); - expect(findDeleteModal().find('p').text()).toBe( + expect(findDeleteModal().text()).toBe( 'Deleting all package assets will remove version 1.0.0 of @gitlab-org/package-15. Are you sure?', ); }); @@ -576,10 +623,10 @@ describe('PackagesApp', () => { createComponent({ resolver: jest.fn().mockResolvedValue( packageDetailsQuery({ - versions: { - count: 0, - nodes: [], - pageInfo: pagination({ hasNextPage: false, hasPreviousPage: false }), + extendPackage: { + versions: { + count: 0, + }, }, }), ), @@ -595,61 +642,62 @@ describe('PackagesApp', () => { }); it('binds the correct props', async () => { - const versionNodes = packageVersions(); createComponent(); await waitForPromises(); expect(findVersionsList().props()).toMatchObject({ canDestroy: true, - versions: expect.arrayContaining(versionNodes), + count: packageVersions().length, + isMutationLoading: false, + packageId: 'gid://gitlab/Packages::Package/1', + isRequestForwardingEnabled: true, }); }); describe('delete packages', () => { - it('exists and has the correct props', async () => { + beforeEach(async () => { createComponent(); - await waitForPromises(); - - expect(findDeletePackages().props()).toMatchObject({ - refetchQueries: [{ query: getPackageDetails, variables: {} }], - showSuccessAlert: true, - }); }); - it('deletePackages is bound to package-versions-list delete event', async () => { - createComponent(); - - await waitForPromises(); + it('exists and has the correct props', () => { + expect(findDeletePackages().props('showSuccessAlert')).toBe(true); + expect(findDeletePackages().props('refetchQueries')).toEqual([ + { + query: getPackageVersionsQuery, + variables: { + first: 20, + id: 'gid://gitlab/Packages::Package/1', + }, + }, + ]); + }); + it('deletePackages is bound to package-versions-list delete event', () => { findVersionsList().vm.$emit('delete', [{ id: 1 }]); expect(findDeletePackages().emitted('start')).toEqual([[]]); }); it('start and end event set loading correctly', async () => { - createComponent(); - - await waitForPromises(); - findDeletePackages().vm.$emit('start'); await nextTick(); - expect(findVersionsList().props('isLoading')).toBe(true); + expect(findVersionsList().props('isMutationLoading')).toBe(true); findDeletePackages().vm.$emit('end'); await nextTick(); - expect(findVersionsList().props('isLoading')).toBe(false); + expect(findVersionsList().props('isMutationLoading')).toBe(false); }); }); }); describe('dependency links', () => { - it('does not show the dependency links for a non nuget package', async () => { + it('does not show the dependency links for a non nuget package', () => { createComponent(); expect(findDependenciesCountBadge().exists()).toBe(false); @@ -659,8 +707,10 @@ describe('PackagesApp', () => { createComponent({ resolver: jest.fn().mockResolvedValue( packageDetailsQuery({ - packageType: PACKAGE_TYPE_NUGET, - dependencyLinks: { nodes: [] }, + extendPackage: { + packageType: PACKAGE_TYPE_NUGET, + dependencyLinks: { nodes: [] }, + }, }), ), }); @@ -676,7 +726,9 @@ describe('PackagesApp', () => { createComponent({ resolver: jest.fn().mockResolvedValue( packageDetailsQuery({ - packageType: PACKAGE_TYPE_NUGET, + extendPackage: { + packageType: PACKAGE_TYPE_NUGET, + }, }), ), }); diff --git a/spec/frontend/packages_and_registries/package_registry/pages/list_spec.js b/spec/frontend/packages_and_registries/package_registry/pages/list_spec.js index a2ec527ce12..2ee24200ed3 100644 --- a/spec/frontend/packages_and_registries/package_registry/pages/list_spec.js +++ b/spec/frontend/packages_and_registries/package_registry/pages/list_spec.js @@ -1,17 +1,18 @@ -import { GlEmptyState, GlSprintf, GlLink } from '@gitlab/ui'; +import { GlButton, GlEmptyState, GlSprintf, GlLink } from '@gitlab/ui'; import Vue, { nextTick } from 'vue'; import VueApollo from 'vue-apollo'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import createMockApollo from 'helpers/mock_apollo_helper'; import waitForPromises from 'helpers/wait_for_promises'; +import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; +import { s__ } from '~/locale'; +import { WORKSPACE_GROUP, WORKSPACE_PROJECT } from '~/issues/constants'; import ListPage from '~/packages_and_registries/package_registry/pages/list.vue'; import PackageTitle from '~/packages_and_registries/package_registry/components/list/package_title.vue'; import PackageSearch from '~/packages_and_registries/package_registry/components/list/package_search.vue'; import OriginalPackageList from '~/packages_and_registries/package_registry/components/list/packages_list.vue'; import DeletePackages from '~/packages_and_registries/package_registry/components/functional/delete_packages.vue'; import { - PROJECT_RESOURCE_TYPE, - GROUP_RESOURCE_TYPE, GRAPHQL_PAGE_SIZE, EMPTY_LIST_HELP_URL, PACKAGE_HELP_URL, @@ -21,7 +22,7 @@ import getPackagesQuery from '~/packages_and_registries/package_registry/graphql import destroyPackagesMutation from '~/packages_and_registries/package_registry/graphql/mutations/destroy_packages.mutation.graphql'; import { packagesListQuery, packageData, pagination } from '../mock_data'; -jest.mock('~/flash'); +jest.mock('~/alert'); describe('PackagesListApp', () => { let wrapper; @@ -31,6 +32,7 @@ describe('PackagesListApp', () => { emptyListIllustration: 'emptyListIllustration', isGroupPage: true, fullPath: 'gitlab-org', + settingsPath: 'settings-path', }; const PackageList = { @@ -50,6 +52,7 @@ describe('PackagesListApp', () => { const findListComponent = () => wrapper.findComponent(PackageList); const findEmptyState = () => wrapper.findComponent(GlEmptyState); const findDeletePackages = () => wrapper.findComponent(DeletePackages); + const findSettingsLink = () => wrapper.findComponent(GlButton); const mountComponent = ({ resolver = jest.fn().mockResolvedValue(packagesListQuery()), @@ -72,17 +75,17 @@ describe('PackagesListApp', () => { GlLoadingIcon, GlSprintf, GlLink, + PackageTitle, PackageList, DeletePackages, }, + directives: { + GlTooltip: createMockDirective('gl-tooltip'), + }, }); }; - afterEach(() => { - wrapper.destroy(); - }); - - const waitForFirstRequest = async () => { + const waitForFirstRequest = () => { // emit a search update so the query is executed findSearch().vm.$emit('update', { sort: 'NAME_DESC', filters: [] }); return waitForPromises(); @@ -108,6 +111,52 @@ describe('PackagesListApp', () => { }); }); + describe('link to settings', () => { + describe('when settings path is not provided', () => { + beforeEach(() => { + mountComponent({ + provide: { + ...defaultProvide, + settingsPath: '', + }, + }); + }); + + it('is not rendered', () => { + expect(findSettingsLink().exists()).toBe(false); + }); + }); + + describe('when settings path is provided', () => { + const label = s__('PackageRegistry|Configure in settings'); + + beforeEach(() => { + mountComponent(); + }); + + it('is rendered', () => { + expect(findSettingsLink().exists()).toBe(true); + }); + + it('has the right icon', () => { + expect(findSettingsLink().props('icon')).toBe('settings'); + }); + + it('has the right attributes', () => { + expect(findSettingsLink().attributes()).toMatchObject({ + 'aria-label': label, + href: defaultProvide.settingsPath, + }); + }); + + it('sets tooltip with right label', () => { + const tooltip = getBinding(findSettingsLink().element, 'gl-tooltip'); + + expect(tooltip.value).toBe(label); + }); + }); + }); + describe('search component', () => { it('exists', () => { mountComponent(); @@ -146,6 +195,11 @@ describe('PackagesListApp', () => { list: expect.arrayContaining([expect.objectContaining({ id: packageData().id })]), isLoading: false, pageInfo: expect.objectContaining({ endCursor: pagination().endCursor }), + groupSettings: expect.objectContaining({ + mavenPackageRequestsForwarding: true, + npmPackageRequestsForwarding: true, + pypiPackageRequestsForwarding: true, + }), }); }); @@ -171,14 +225,14 @@ describe('PackagesListApp', () => { }); describe.each` - type | sortType - ${PROJECT_RESOURCE_TYPE} | ${'sort'} - ${GROUP_RESOURCE_TYPE} | ${'groupSort'} + type | sortType + ${WORKSPACE_PROJECT} | ${'sort'} + ${WORKSPACE_GROUP} | ${'groupSort'} `('$type query', ({ type, sortType }) => { let provide; let resolver; - const isGroupPage = type === GROUP_RESOURCE_TYPE; + const isGroupPage = type === WORKSPACE_GROUP; beforeEach(() => { provide = { ...defaultProvide, isGroupPage }; @@ -196,11 +250,25 @@ describe('PackagesListApp', () => { expect.objectContaining({ isGroupPage, [sortType]: 'NAME_DESC' }), ); }); + + it('list component has group settings prop set', () => { + expect(findListComponent().props()).toMatchObject({ + groupSettings: expect.objectContaining({ + mavenPackageRequestsForwarding: true, + npmPackageRequestsForwarding: true, + pypiPackageRequestsForwarding: true, + }), + }); + }); }); - describe('empty state', () => { + describe.each` + description | resolverResponse + ${'empty response'} | ${packagesListQuery({ extend: { nodes: [] } })} + ${'error response'} | ${{ data: { group: null } }} + `(`$description renders empty state`, ({ resolverResponse }) => { beforeEach(() => { - const resolver = jest.fn().mockResolvedValue(packagesListQuery({ extend: { nodes: [] } })); + const resolver = jest.fn().mockResolvedValue(resolverResponse); mountComponent({ resolver }); return waitForFirstRequest(); diff --git a/spec/frontend/packages_and_registries/settings/group/components/dependency_proxy_settings_spec.js b/spec/frontend/packages_and_registries/settings/group/components/dependency_proxy_settings_spec.js index 796d89231f4..6dd4b9f2d20 100644 --- a/spec/frontend/packages_and_registries/settings/group/components/dependency_proxy_settings_spec.js +++ b/spec/frontend/packages_and_registries/settings/group/components/dependency_proxy_settings_spec.js @@ -28,7 +28,7 @@ import { dependencyProxyUpdateTllPolicyMutationMock, } from '../mock_data'; -jest.mock('~/flash'); +jest.mock('~/alert'); jest.mock('~/packages_and_registries/settings/group/graphql/utils/optimistic_responses'); describe('DependencyProxySettings', () => { @@ -82,10 +82,6 @@ describe('DependencyProxySettings', () => { .mockResolvedValue(dependencyProxyUpdateTllPolicyMutationMock()); }); - afterEach(() => { - wrapper.destroy(); - }); - const findSettingsBlock = () => wrapper.findComponent(SettingsBlock); const findEnableProxyToggle = () => wrapper.findByTestId('dependency-proxy-setting-toggle'); const findEnableTtlPoliciesToggle = () => diff --git a/spec/frontend/packages_and_registries/settings/group/components/exceptions_input_spec.js b/spec/frontend/packages_and_registries/settings/group/components/exceptions_input_spec.js index 86f14961690..dd1edbaa3fd 100644 --- a/spec/frontend/packages_and_registries/settings/group/components/exceptions_input_spec.js +++ b/spec/frontend/packages_and_registries/settings/group/components/exceptions_input_spec.js @@ -23,10 +23,6 @@ describe('Exceptions Input', () => { }); }; - afterEach(() => { - wrapper.destroy(); - }); - const findInputGroup = () => wrapper.findComponent(GlFormGroup); const findInput = () => wrapper.findComponent(GlFormInput); @@ -102,7 +98,7 @@ describe('Exceptions Input', () => { }); it('disables the form input', () => { - expect(findInput().attributes('disabled')).toBe('true'); + expect(findInput().attributes('disabled')).toBeDefined(); }); }); }); diff --git a/spec/frontend/packages_and_registries/settings/group/components/group_settings_app_spec.js b/spec/frontend/packages_and_registries/settings/group/components/group_settings_app_spec.js index 7edc321867c..3ce8e91d43d 100644 --- a/spec/frontend/packages_and_registries/settings/group/components/group_settings_app_spec.js +++ b/spec/frontend/packages_and_registries/settings/group/components/group_settings_app_spec.js @@ -19,7 +19,7 @@ import { dependencyProxyImageTtlPolicy, } from '../mock_data'; -jest.mock('~/flash'); +jest.mock('~/alert'); describe('Group Settings App', () => { let wrapper; @@ -55,10 +55,6 @@ describe('Group Settings App', () => { show = jest.fn(); }); - afterEach(() => { - wrapper.destroy(); - }); - const findAlert = () => wrapper.findComponent(GlAlert); const findPackageSettings = () => wrapper.findComponent(PackagesSettings); const findPackageForwardingSettings = () => wrapper.findComponent(PackagesForwardingSettings); diff --git a/spec/frontend/packages_and_registries/settings/group/components/package_settings_spec.js b/spec/frontend/packages_and_registries/settings/group/components/package_settings_spec.js index 807f332f4d3..49e76cfbae0 100644 --- a/spec/frontend/packages_and_registries/settings/group/components/package_settings_spec.js +++ b/spec/frontend/packages_and_registries/settings/group/components/package_settings_spec.js @@ -23,7 +23,7 @@ import { groupPackageSettingsMutationErrorMock, } from '../mock_data'; -jest.mock('~/flash'); +jest.mock('~/alert'); jest.mock('~/packages_and_registries/settings/group/graphql/utils/optimistic_responses'); describe('Packages Settings', () => { @@ -56,10 +56,6 @@ describe('Packages Settings', () => { }); }; - afterEach(() => { - wrapper.destroy(); - }); - const findSettingsBlock = () => wrapper.findComponent(SettingsBlock); const findDescription = () => wrapper.findByTestId('description'); const findMavenSettings = () => wrapper.findByTestId('maven-settings'); @@ -181,7 +177,7 @@ describe('Packages Settings', () => { }); }); - it('renders ExceptionsInput and assigns duplication allowness and exception props', async () => { + it('renders ExceptionsInput and assigns duplication allowness and exception props', () => { mountComponent({ mountFn: mountExtended }); const { genericDuplicatesAllowed, genericDuplicateExceptionRegex } = packageSettings; @@ -196,7 +192,7 @@ describe('Packages Settings', () => { }); }); - it('on update event calls the mutation', async () => { + it('on update event calls the mutation', () => { const mutationResolver = jest.fn().mockResolvedValue(groupPackageSettingsMutationMock()); mountComponent({ mountFn: mountExtended, mutationResolver }); diff --git a/spec/frontend/packages_and_registries/settings/group/components/packages_forwarding_settings_spec.js b/spec/frontend/packages_and_registries/settings/group/components/packages_forwarding_settings_spec.js index a0b257a9496..8a66a685733 100644 --- a/spec/frontend/packages_and_registries/settings/group/components/packages_forwarding_settings_spec.js +++ b/spec/frontend/packages_and_registries/settings/group/components/packages_forwarding_settings_spec.js @@ -1,12 +1,13 @@ import Vue from 'vue'; -import { GlButton } from '@gitlab/ui'; +import { GlButton, GlLink, GlSprintf } from '@gitlab/ui'; import VueApollo from 'vue-apollo'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import createMockApollo from 'helpers/mock_apollo_helper'; import waitForPromises from 'helpers/wait_for_promises'; +import { s__ } from '~/locale'; import component from '~/packages_and_registries/settings/group/components/packages_forwarding_settings.vue'; import { - PACKAGE_FORWARDING_SETTINGS_DESCRIPTION, + REQUEST_FORWARDING_HELP_PAGE_PATH, PACKAGE_FORWARDING_SETTINGS_HEADER, } from '~/packages_and_registries/settings/group/constants'; @@ -25,7 +26,7 @@ import { mavenProps, } from '../mock_data'; -jest.mock('~/flash'); +jest.mock('~/alert'); jest.mock('~/packages_and_registries/settings/group/graphql/utils/optimistic_responses'); describe('Packages Forwarding Settings', () => { @@ -60,6 +61,7 @@ describe('Packages Forwarding Settings', () => { forwardSettings, }, stubs: { + GlSprintf, SettingsBlock, }, }); @@ -72,6 +74,7 @@ describe('Packages Forwarding Settings', () => { const findMavenForwardingSettings = () => wrapper.findByTestId('maven'); const findNpmForwardingSettings = () => wrapper.findByTestId('npm'); const findPyPiForwardingSettings = () => wrapper.findByTestId('pypi'); + const findRequestForwardingDocsLink = () => wrapper.findComponent(GlLink); const fillApolloCache = () => { apolloProvider.defaultClient.cache.writeQuery({ @@ -111,8 +114,18 @@ describe('Packages Forwarding Settings', () => { it('has the correct description text', () => { mountComponent(); - expect(findDescription().text()).toMatchInterpolatedText( - PACKAGE_FORWARDING_SETTINGS_DESCRIPTION, + expect(findDescription().text()).toBe( + s__( + 'PackageRegistry|Forward package requests to a public registry if the packages are not found in the GitLab package registry.', + ), + ); + }); + + it('has the right help link', () => { + mountComponent(); + + expect(findRequestForwardingDocsLink().attributes('href')).toBe( + REQUEST_FORWARDING_HELP_PAGE_PATH, ); }); diff --git a/spec/frontend/packages_and_registries/settings/project/settings/components/cleanup_image_tags_spec.js b/spec/frontend/packages_and_registries/settings/project/settings/components/cleanup_image_tags_spec.js index 2bb99fb8e8f..cbe68df5343 100644 --- a/spec/frontend/packages_and_registries/settings/project/settings/components/cleanup_image_tags_spec.js +++ b/spec/frontend/packages_and_registries/settings/project/settings/components/cleanup_image_tags_spec.js @@ -61,10 +61,6 @@ describe('Cleanup image tags project settings', () => { }); }; - afterEach(() => { - wrapper.destroy(); - }); - describe('isEdited status', () => { it.each` description | apiResponse | workingCopy | result @@ -130,7 +126,7 @@ describe('Cleanup image tags project settings', () => { }); describe('an admin is visiting the page', () => { - it('shows the admin part of the alert message', async () => { + it('shows the admin part of the alert', async () => { mountComponentWithApollo({ provide: { ...defaultProvidedValues, isAdmin: true }, resolver: jest.fn().mockResolvedValue(nullExpirationPolicyPayload()), diff --git a/spec/frontend/packages_and_registries/settings/project/settings/components/container_expiration_policy_form_spec.js b/spec/frontend/packages_and_registries/settings/project/settings/components/container_expiration_policy_form_spec.js index cbb5aa52694..a68087f7f57 100644 --- a/spec/frontend/packages_and_registries/settings/project/settings/components/container_expiration_policy_form_spec.js +++ b/spec/frontend/packages_and_registries/settings/project/settings/components/container_expiration_policy_form_spec.js @@ -46,7 +46,7 @@ describe('Container Expiration Policy Settings Form', () => { const findOlderThanDropdown = () => wrapper.find('[data-testid="older-than-dropdown"]'); const findRemoveRegexInput = () => wrapper.find('[data-testid="remove-regex-input"]'); - const submitForm = async () => { + const submitForm = () => { findForm().trigger('submit'); return waitForPromises(); }; @@ -124,10 +124,6 @@ describe('Container Expiration Policy Settings Form', () => { jest.spyOn(Tracking, 'event'); }); - afterEach(() => { - wrapper.destroy(); - }); - describe.each` model | finder | fieldName | type | defaultValue ${'enabled'} | ${findEnableToggle} | ${'Enable'} | ${'toggle'} | ${false} diff --git a/spec/frontend/packages_and_registries/settings/project/settings/components/container_expiration_policy_spec.js b/spec/frontend/packages_and_registries/settings/project/settings/components/container_expiration_policy_spec.js index 43484d26d76..c9dd9ce7a45 100644 --- a/spec/frontend/packages_and_registries/settings/project/settings/components/container_expiration_policy_spec.js +++ b/spec/frontend/packages_and_registries/settings/project/settings/components/container_expiration_policy_spec.js @@ -63,10 +63,6 @@ describe('Container expiration policy project settings', () => { }); }; - afterEach(() => { - wrapper.destroy(); - }); - it('renders the setting form', async () => { mountComponentWithApollo({ resolver: jest.fn().mockResolvedValue(expirationPolicyPayload()), @@ -113,7 +109,7 @@ describe('Container expiration policy project settings', () => { }); describe('an admin is visiting the page', () => { - it('shows the admin part of the alert message', async () => { + it('shows the admin part of the alert', async () => { mountComponentWithApollo({ provide: { ...defaultProvidedValues, isAdmin: true }, resolver: jest.fn().mockResolvedValue(nullExpirationPolicyPayload()), diff --git a/spec/frontend/packages_and_registries/settings/project/settings/components/expiration_dropdown_spec.js b/spec/frontend/packages_and_registries/settings/project/settings/components/expiration_dropdown_spec.js index ae41fdf65e0..058fe427106 100644 --- a/spec/frontend/packages_and_registries/settings/project/settings/components/expiration_dropdown_spec.js +++ b/spec/frontend/packages_and_registries/settings/project/settings/components/expiration_dropdown_spec.js @@ -32,11 +32,6 @@ describe('ExpirationDropdown', () => { }); }; - afterEach(() => { - wrapper.destroy(); - wrapper = null; - }); - describe('structure', () => { it('has a form-select component', () => { mountComponent(); diff --git a/spec/frontend/packages_and_registries/settings/project/settings/components/expiration_input_spec.js b/spec/frontend/packages_and_registries/settings/project/settings/components/expiration_input_spec.js index 1cea0704154..be12d108d1e 100644 --- a/spec/frontend/packages_and_registries/settings/project/settings/components/expiration_input_spec.js +++ b/spec/frontend/packages_and_registries/settings/project/settings/components/expiration_input_spec.js @@ -38,11 +38,6 @@ describe('ExpirationInput', () => { }); }; - afterEach(() => { - wrapper.destroy(); - wrapper = null; - }); - describe('structure', () => { it('has a label', () => { mountComponent(); diff --git a/spec/frontend/packages_and_registries/settings/project/settings/components/expiration_run_text_spec.js b/spec/frontend/packages_and_registries/settings/project/settings/components/expiration_run_text_spec.js index 653f2a8b40e..f950a9d5add 100644 --- a/spec/frontend/packages_and_registries/settings/project/settings/components/expiration_run_text_spec.js +++ b/spec/frontend/packages_and_registries/settings/project/settings/components/expiration_run_text_spec.js @@ -23,11 +23,6 @@ describe('ExpirationToggle', () => { }); }; - afterEach(() => { - wrapper.destroy(); - wrapper = null; - }); - describe('structure', () => { it('has an input component', () => { mountComponent(); diff --git a/spec/frontend/packages_and_registries/settings/project/settings/components/expiration_toggle_spec.js b/spec/frontend/packages_and_registries/settings/project/settings/components/expiration_toggle_spec.js index 55a66cebd83..ec7b89aa927 100644 --- a/spec/frontend/packages_and_registries/settings/project/settings/components/expiration_toggle_spec.js +++ b/spec/frontend/packages_and_registries/settings/project/settings/components/expiration_toggle_spec.js @@ -23,11 +23,6 @@ describe('ExpirationToggle', () => { }); }; - afterEach(() => { - wrapper.destroy(); - wrapper = null; - }); - describe('structure', () => { it('has a toggle component', () => { mountComponent(); diff --git a/spec/frontend/packages_and_registries/settings/project/settings/components/packages_cleanup_policy_form_spec.js b/spec/frontend/packages_and_registries/settings/project/settings/components/packages_cleanup_policy_form_spec.js index 0fbbf4ae58f..50b72d3ad72 100644 --- a/spec/frontend/packages_and_registries/settings/project/settings/components/packages_cleanup_policy_form_spec.js +++ b/spec/frontend/packages_and_registries/settings/project/settings/components/packages_cleanup_policy_form_spec.js @@ -48,7 +48,7 @@ describe('Packages Cleanup Policy Settings Form', () => { wrapper.findByTestId('keep-n-duplicated-package-files-dropdown'); const findNextRunAt = () => wrapper.findByTestId('next-run-at'); - const submitForm = async () => { + const submitForm = () => { findForm().trigger('submit'); return waitForPromises(); }; @@ -115,7 +115,6 @@ describe('Packages Cleanup Policy Settings Form', () => { }); afterEach(() => { - wrapper.destroy(); fakeApollo = null; }); diff --git a/spec/frontend/packages_and_registries/settings/project/settings/components/packages_cleanup_policy_spec.js b/spec/frontend/packages_and_registries/settings/project/settings/components/packages_cleanup_policy_spec.js index 6dfeeca6862..94277d34f30 100644 --- a/spec/frontend/packages_and_registries/settings/project/settings/components/packages_cleanup_policy_spec.js +++ b/spec/frontend/packages_and_registries/settings/project/settings/components/packages_cleanup_policy_spec.js @@ -47,7 +47,6 @@ describe('Packages cleanup policy project settings', () => { }; afterEach(() => { - wrapper.destroy(); fakeApollo = null; }); diff --git a/spec/frontend/packages_and_registries/settings/project/settings/components/registry_settings_app_spec.js b/spec/frontend/packages_and_registries/settings/project/settings/components/registry_settings_app_spec.js index 07d13839c61..12425909454 100644 --- a/spec/frontend/packages_and_registries/settings/project/settings/components/registry_settings_app_spec.js +++ b/spec/frontend/packages_and_registries/settings/project/settings/components/registry_settings_app_spec.js @@ -20,11 +20,6 @@ describe('Registry Settings app', () => { const findPackagesCleanupPolicy = () => wrapper.findComponent(PackagesCleanupPolicy); const findAlert = () => wrapper.findComponent(GlAlert); - afterEach(() => { - wrapper.destroy(); - wrapper = null; - }); - const defaultProvide = { showContainerRegistrySettings: true, showPackageRegistrySettings: true, @@ -84,7 +79,7 @@ describe('Registry Settings app', () => { ${false} | ${true} ${false} | ${false} `( - 'container expiration policy $showContainerRegistrySettings and package cleanup policy is $showPackageRegistrySettings', + 'container cleanup policy $showContainerRegistrySettings and package cleanup policy is $showPackageRegistrySettings', ({ showContainerRegistrySettings, showPackageRegistrySettings }) => { mountComponent({ showContainerRegistrySettings, diff --git a/spec/frontend/packages_and_registries/shared/components/__snapshots__/registry_breadcrumb_spec.js.snap b/spec/frontend/packages_and_registries/shared/components/__snapshots__/registry_breadcrumb_spec.js.snap index e6e89806ce0..e9ee6ebdb5c 100644 --- a/spec/frontend/packages_and_registries/shared/components/__snapshots__/registry_breadcrumb_spec.js.snap +++ b/spec/frontend/packages_and_registries/shared/components/__snapshots__/registry_breadcrumb_spec.js.snap @@ -5,7 +5,6 @@ exports[`Registry Breadcrumb when is not rootRoute renders 1`] = ` aria-label="Breadcrumb" class="gl-breadcrumbs" > - <ol class="breadcrumb gl-breadcrumb-list" > @@ -16,29 +15,10 @@ exports[`Registry Breadcrumb when is not rootRoute renders 1`] = ` class="" target="_self" > + <!----> <span> </span> - - <span - class="gl-breadcrumb-separator" - data-testid="separator" - > - <span - class="gl-mx-n5" - > - <svg - aria-hidden="true" - class="gl-icon s8" - data-testid="chevron-lg-right-icon" - role="img" - > - <use - href="#chevron-lg-right" - /> - </svg> - </span> - </span> </a> </li> @@ -52,11 +32,10 @@ exports[`Registry Breadcrumb when is not rootRoute renders 1`] = ` href="#" target="_self" > + <!----> <span> </span> - - <!----> </a> </li> @@ -70,7 +49,6 @@ exports[`Registry Breadcrumb when is rootRoute renders 1`] = ` aria-label="Breadcrumb" class="gl-breadcrumbs" > - <ol class="breadcrumb gl-breadcrumb-list" > @@ -82,11 +60,10 @@ exports[`Registry Breadcrumb when is rootRoute renders 1`] = ` class="" target="_self" > + <!----> <span> </span> - - <!----> </a> </li> diff --git a/spec/frontend/packages_and_registries/shared/components/cli_commands_spec.js b/spec/frontend/packages_and_registries/shared/components/cli_commands_spec.js index 18084766db9..41482e6e681 100644 --- a/spec/frontend/packages_and_registries/shared/components/cli_commands_spec.js +++ b/spec/frontend/packages_and_registries/shared/components/cli_commands_spec.js @@ -38,11 +38,6 @@ describe('cli_commands', () => { mountComponent(); }); - afterEach(() => { - wrapper.destroy(); - wrapper = null; - }); - it('shows the correct text on the button', () => { expect(findDropdownButton().text()).toContain(QUICK_START); }); diff --git a/spec/frontend/packages_and_registries/shared/components/delete_package_modal_spec.js b/spec/frontend/packages_and_registries/shared/components/delete_package_modal_spec.js index 357dab593e8..ba5ba8f9884 100644 --- a/spec/frontend/packages_and_registries/shared/components/delete_package_modal_spec.js +++ b/spec/frontend/packages_and_registries/shared/components/delete_package_modal_spec.js @@ -19,11 +19,6 @@ describe('DeletePackageModal', () => { }); }; - afterEach(() => { - wrapper.destroy(); - wrapper = null; - }); - describe('when itemToBeDeleted prop is defined', () => { beforeEach(() => { mountComponent(); diff --git a/spec/frontend/packages_and_registries/shared/components/package_icon_and_name_spec.js b/spec/frontend/packages_and_registries/shared/components/package_icon_and_name_spec.js deleted file mode 100644 index a0ff6ca01b5..00000000000 --- a/spec/frontend/packages_and_registries/shared/components/package_icon_and_name_spec.js +++ /dev/null @@ -1,32 +0,0 @@ -import { GlIcon } from '@gitlab/ui'; -import { shallowMount } from '@vue/test-utils'; -import PackageIconAndName from '~/packages_and_registries/shared/components/package_icon_and_name.vue'; - -describe('PackageIconAndName', () => { - let wrapper; - - const findIcon = () => wrapper.findComponent(GlIcon); - - const mountComponent = () => { - wrapper = shallowMount(PackageIconAndName, { - slots: { - default: 'test', - }, - }); - }; - - it('has an icon', () => { - mountComponent(); - - const icon = findIcon(); - - expect(icon.exists()).toBe(true); - expect(icon.props('name')).toBe('package'); - }); - - it('renders the slot content', () => { - mountComponent(); - - expect(wrapper.text()).toBe('test'); - }); -}); diff --git a/spec/frontend/packages_and_registries/shared/components/package_path_spec.js b/spec/frontend/packages_and_registries/shared/components/package_path_spec.js index 93425d4f399..3ffbb6f435c 100644 --- a/spec/frontend/packages_and_registries/shared/components/package_path_spec.js +++ b/spec/frontend/packages_and_registries/shared/components/package_path_spec.js @@ -9,7 +9,7 @@ describe('PackagePath', () => { wrapper = shallowMount(PackagePath, { propsData, directives: { - GlTooltip: createMockDirective(), + GlTooltip: createMockDirective('gl-tooltip'), }, }); }; @@ -24,11 +24,6 @@ describe('PackagePath', () => { const findItem = (name) => wrapper.find(`[data-testid="${name}"]`); const findTooltip = (w) => getBinding(w.element, 'gl-tooltip'); - afterEach(() => { - wrapper.destroy(); - wrapper = null; - }); - describe.each` path | rootUrl | shouldExist | shouldNotExist ${'foo/bar'} | ${'/foo/bar'} | ${[]} | ${[ROOT_CHEVRON, ELLIPSIS_ICON, ELLIPSIS_CHEVRON, LEAF_LINK]} @@ -91,12 +86,12 @@ describe('PackagePath', () => { }); it('root link is disabled', () => { - expect(findItem(ROOT_LINK).attributes('disabled')).toBe('true'); + expect(findItem(ROOT_LINK).attributes('disabled')).toBeDefined(); }); if (shouldExist.includes(LEAF_LINK)) { it('the last link is disabled', () => { - expect(findItem(LEAF_LINK).attributes('disabled')).toBe('true'); + expect(findItem(LEAF_LINK).attributes('disabled')).toBeDefined(); }); } }); diff --git a/spec/frontend/packages_and_registries/shared/components/package_tags_spec.js b/spec/frontend/packages_and_registries/shared/components/package_tags_spec.js index 33e96c0775e..b025517ae47 100644 --- a/spec/frontend/packages_and_registries/shared/components/package_tags_spec.js +++ b/spec/frontend/packages_and_registries/shared/components/package_tags_spec.js @@ -20,10 +20,6 @@ describe('PackageTags', () => { const tagBadges = () => wrapper.findAll('[data-testid="tagBadge"]'); const moreBadge = () => wrapper.find('[data-testid="moreBadge"]'); - afterEach(() => { - if (wrapper) wrapper.destroy(); - }); - describe('tag label', () => { it('shows the tag label by default', () => { createComponent(); diff --git a/spec/frontend/packages_and_registries/shared/components/packages_list_loader_spec.js b/spec/frontend/packages_and_registries/shared/components/packages_list_loader_spec.js index 0005162e0bb..e43a9f57255 100644 --- a/spec/frontend/packages_and_registries/shared/components/packages_list_loader_spec.js +++ b/spec/frontend/packages_and_registries/shared/components/packages_list_loader_spec.js @@ -17,11 +17,6 @@ describe('PackagesListLoader', () => { beforeEach(createComponent); - afterEach(() => { - wrapper.destroy(); - wrapper = null; - }); - describe('desktop loader', () => { it('produces the right loader', () => { expect(findDesktopShapes().findAll('rect[width="1000"]')).toHaveLength(20); 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 index db9f96bff39..c1e86080d29 100644 --- a/spec/frontend/packages_and_registries/shared/components/persisted_search_spec.js +++ b/spec/frontend/packages_and_registries/shared/components/persisted_search_spec.js @@ -43,10 +43,6 @@ describe('Persisted Search', () => { extractFilterAndSorting.mockReturnValue(defaultQueryParamsMock); }); - afterEach(() => { - wrapper.destroy(); - }); - it('has a registry search component', async () => { mountComponent(); @@ -55,7 +51,7 @@ describe('Persisted Search', () => { expect(findRegistrySearch().exists()).toBe(true); }); - it('registry search is mounted after mount', async () => { + it('registry search is mounted after mount', () => { mountComponent(); expect(findRegistrySearch().exists()).toBe(false); diff --git a/spec/frontend/packages_and_registries/shared/components/publish_method_spec.js b/spec/frontend/packages_and_registries/shared/components/publish_method_spec.js index fa8f8f7641a..167599a54ea 100644 --- a/spec/frontend/packages_and_registries/shared/components/publish_method_spec.js +++ b/spec/frontend/packages_and_registries/shared/components/publish_method_spec.js @@ -20,11 +20,6 @@ describe('publish_method', () => { }); }; - afterEach(() => { - wrapper.destroy(); - wrapper = null; - }); - it('renders', () => { mountComponent(packageWithPipeline); expect(wrapper.element).toMatchSnapshot(); diff --git a/spec/frontend/packages_and_registries/shared/components/registry_breadcrumb_spec.js b/spec/frontend/packages_and_registries/shared/components/registry_breadcrumb_spec.js index 15db454ac68..c1f1a25d53b 100644 --- a/spec/frontend/packages_and_registries/shared/components/registry_breadcrumb_spec.js +++ b/spec/frontend/packages_and_registries/shared/components/registry_breadcrumb_spec.js @@ -31,10 +31,6 @@ describe('Registry Breadcrumb', () => { nameGenerator.mockClear(); }); - afterEach(() => { - wrapper.destroy(); - }); - describe('when is rootRoute', () => { beforeEach(() => { mountComponent(routes[0]); diff --git a/spec/frontend/packages_and_registries/shared/components/registry_list_spec.js b/spec/frontend/packages_and_registries/shared/components/registry_list_spec.js index 2e2d5e26d33..66fca2ce12e 100644 --- a/spec/frontend/packages_and_registries/shared/components/registry_list_spec.js +++ b/spec/frontend/packages_and_registries/shared/components/registry_list_spec.js @@ -36,10 +36,6 @@ describe('Registry List', () => { const findScopedSlotFirstValue = (index) => findScopedSlots().at(index).find('span'); const findScopedSlotIsSelectedValue = (index) => findScopedSlots().at(index).find('p'); - afterEach(() => { - wrapper.destroy(); - }); - describe('header', () => { it('renders the title passed in the prop', () => { mountComponent(); @@ -62,7 +58,7 @@ describe('Registry List', () => { it('sets disabled prop to true when items length is 0', () => { mountComponent({ propsData: { ...defaultPropsData, items: [] } }); - expect(findSelectAll().attributes('disabled')).toBe('true'); + expect(findSelectAll().attributes('disabled')).toBeDefined(); }); it('when few are selected, sets indeterminate prop to true', async () => { @@ -111,10 +107,21 @@ describe('Registry List', () => { expect(findDeleteSelected().text()).toBe(component.i18n.deleteSelected); }); - it('is hidden when hiddenDelete is true', () => { - mountComponent({ propsData: { ...defaultPropsData, hiddenDelete: true } }); + describe('when hiddenDelete is true', () => { + beforeEach(() => { + mountComponent({ propsData: { ...defaultPropsData, hiddenDelete: true } }); + }); - expect(findDeleteSelected().exists()).toBe(false); + it('is hidden', () => { + expect(findDeleteSelected().exists()).toBe(false); + }); + + it('populates the first slot prop correctly', () => { + expect(findScopedSlots().at(0).exists()).toBe(true); + + // it's the first slot + expect(findScopedSlotFirstValue(0).text()).toBe('false'); + }); }); it('is disabled when isLoading is true', () => { diff --git a/spec/frontend/packages_and_registries/shared/components/settings_block_spec.js b/spec/frontend/packages_and_registries/shared/components/settings_block_spec.js index a4c1b989dac..664a821c275 100644 --- a/spec/frontend/packages_and_registries/shared/components/settings_block_spec.js +++ b/spec/frontend/packages_and_registries/shared/components/settings_block_spec.js @@ -15,10 +15,6 @@ describe('SettingsBlock', () => { }); }; - afterEach(() => { - wrapper.destroy(); - }); - const findDefaultSlot = () => wrapper.findByTestId('default-slot'); const findTitleSlot = () => wrapper.findByTestId('title-slot'); const findDescriptionSlot = () => wrapper.findByTestId('description-slot'); |