diff options
Diffstat (limited to 'spec/frontend/packages_and_registries/container_registry')
21 files changed, 436 insertions, 300 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()); |