diff options
Diffstat (limited to 'spec/frontend/registry/explorer/components/details_page')
9 files changed, 255 insertions, 98 deletions
diff --git a/spec/frontend/registry/explorer/components/details_page/delete_alert_spec.js b/spec/frontend/registry/explorer/components/details_page/delete_alert_spec.js index 6a7fbbe367a..c2a2a4e06ea 100644 --- a/spec/frontend/registry/explorer/components/details_page/delete_alert_spec.js +++ b/spec/frontend/registry/explorer/components/details_page/delete_alert_spec.js @@ -1,5 +1,5 @@ -import { shallowMount } from '@vue/test-utils'; import { GlAlert, GlSprintf, GlLink } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; import component from '~/registry/explorer/components/details_page/delete_alert.vue'; import { DELETE_TAG_SUCCESS_MESSAGE, diff --git a/spec/frontend/registry/explorer/components/details_page/delete_modal_spec.js b/spec/frontend/registry/explorer/components/details_page/delete_modal_spec.js index 636e0a285a6..8fe659694ba 100644 --- a/spec/frontend/registry/explorer/components/details_page/delete_modal_spec.js +++ b/spec/frontend/registry/explorer/components/details_page/delete_modal_spec.js @@ -1,9 +1,11 @@ -import { shallowMount } from '@vue/test-utils'; import { GlSprintf } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; import component from '~/registry/explorer/components/details_page/delete_modal.vue'; import { REMOVE_TAG_CONFIRMATION_TEXT, REMOVE_TAGS_CONFIRMATION_TEXT, + DELETE_IMAGE_CONFIRMATION_TITLE, + DELETE_IMAGE_CONFIRMATION_TEXT, } from '~/registry/explorer/constants'; import { GlModal } from '../../stubs'; @@ -35,13 +37,13 @@ describe('Delete Modal', () => { describe('events', () => { it.each` - glEvent | localEvent - ${'ok'} | ${'confirmDelete'} - ${'cancel'} | ${'cancelDelete'} + glEvent | localEvent + ${'primary'} | ${'confirmDelete'} + ${'cancel'} | ${'cancelDelete'} `('GlModal $glEvent emits $localEvent', ({ glEvent, localEvent }) => { mountComponent(); findModal().vm.$emit(glEvent); - expect(wrapper.emitted(localEvent)).toBeTruthy(); + expect(wrapper.emitted(localEvent)).toEqual([[]]); }); }); @@ -53,27 +55,51 @@ describe('Delete Modal', () => { }); }); - describe('itemsToBeDeleted contains one element', () => { - beforeEach(() => { - mountComponent({ itemsToBeDeleted: [{ path: 'foo' }] }); - }); - it(`has the correct description`, () => { - expect(findDescription().text()).toBe(REMOVE_TAG_CONFIRMATION_TEXT.replace('%{item}', 'foo')); + describe('when we are deleting images', () => { + it('has the correct title', () => { + mountComponent({ deleteImage: true }); + + expect(wrapper.text()).toContain(DELETE_IMAGE_CONFIRMATION_TITLE); }); - it('has the correct action', () => { - expect(wrapper.text()).toContain('Remove tag'); + + it('has the correct description', () => { + mountComponent({ deleteImage: true }); + + expect(wrapper.text()).toContain(DELETE_IMAGE_CONFIRMATION_TEXT); }); }); - describe('itemsToBeDeleted contains more than element', () => { - beforeEach(() => { - mountComponent({ itemsToBeDeleted: [{ path: 'foo' }, { path: 'bar' }] }); - }); - it(`has the correct description`, () => { - expect(findDescription().text()).toBe(REMOVE_TAGS_CONFIRMATION_TEXT.replace('%{item}', '2')); + describe('when we are deleting tags', () => { + describe('itemsToBeDeleted contains one element', () => { + beforeEach(() => { + mountComponent({ itemsToBeDeleted: [{ path: 'foo' }] }); + }); + + it(`has the correct description`, () => { + expect(findDescription().text()).toBe( + REMOVE_TAG_CONFIRMATION_TEXT.replace('%{item}', 'foo'), + ); + }); + + it('has the correct title', () => { + expect(wrapper.text()).toContain('Remove tag'); + }); }); - it('has the correct action', () => { - expect(wrapper.text()).toContain('Remove tags'); + + describe('itemsToBeDeleted contains more than element', () => { + beforeEach(() => { + mountComponent({ itemsToBeDeleted: [{ path: 'foo' }, { path: 'bar' }] }); + }); + + it(`has the correct description`, () => { + expect(findDescription().text()).toBe( + REMOVE_TAGS_CONFIRMATION_TEXT.replace('%{item}', '2'), + ); + }); + + it('has the correct title', () => { + expect(wrapper.text()).toContain('Remove tags'); + }); }); }); }); diff --git a/spec/frontend/registry/explorer/components/details_page/details_header_spec.js b/spec/frontend/registry/explorer/components/details_page/details_header_spec.js index 337235e3de5..3fa3a2ae1de 100644 --- a/spec/frontend/registry/explorer/components/details_page/details_header_spec.js +++ b/spec/frontend/registry/explorer/components/details_page/details_header_spec.js @@ -1,7 +1,6 @@ +import { GlSprintf, GlButton } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; -import { GlSprintf } from '@gitlab/ui'; import { useFakeDate } from 'helpers/fake_date'; -import TitleArea from '~/vue_shared/components/registry/title_area.vue'; import component from '~/registry/explorer/components/details_page/details_header.vue'; import { DETAILS_PAGE_TITLE, @@ -15,6 +14,7 @@ import { CLEANUP_ONGOING_TOOLTIP, CLEANUP_UNFINISHED_TOOLTIP, } from '~/registry/explorer/constants'; +import TitleArea from '~/vue_shared/components/registry/title_area.vue'; describe('Details Header', () => { let wrapper; @@ -23,6 +23,7 @@ describe('Details Header', () => { name: 'foo', updatedAt: '2020-11-03T13:29:21Z', tagsCount: 10, + canDelete: true, project: { visibility: 'public', containerExpirationPolicy: { @@ -36,8 +37,10 @@ describe('Details Header', () => { 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 findDeleteButton = () => wrapper.find(GlButton); const waitForMetadataItems = async () => { // Metadata items are printed by a loop in the title-area and it takes two ticks for them to be available @@ -45,11 +48,9 @@ describe('Details Header', () => { await wrapper.vm.$nextTick(); }; - const mountComponent = (image = defaultImage) => { + const mountComponent = (propsData = { image: defaultImage }) => { wrapper = shallowMount(component, { - propsData: { - image, - }, + propsData, stubs: { GlSprintf, TitleArea, @@ -63,13 +64,65 @@ describe('Details Header', () => { }); it('has the correct title ', () => { - mountComponent({ ...defaultImage, name: '' }); - expect(wrapper.text()).toMatchInterpolatedText(DETAILS_PAGE_TITLE); + mountComponent({ image: { ...defaultImage, name: '' } }); + expect(findTitle().text()).toMatchInterpolatedText(DETAILS_PAGE_TITLE); }); it('shows imageName in the title', () => { mountComponent(); - expect(wrapper.text()).toContain('foo'); + expect(findTitle().text()).toContain('foo'); + }); + + describe('delete button', () => { + it('exists', () => { + mountComponent(); + + expect(findDeleteButton().exists()).toBe(true); + }); + + it('is hidden while loading', () => { + mountComponent({ image: defaultImage, metadataLoading: true }); + + expect(findDeleteButton().exists()).toBe(false); + }); + + it('has the correct text', () => { + mountComponent(); + + expect(findDeleteButton().text()).toBe('Delete'); + }); + + it('has the correct props', () => { + mountComponent(); + + expect(findDeleteButton().props()).toMatchObject({ + variant: 'danger', + disabled: false, + }); + }); + + it('emits the correct event', () => { + mountComponent(); + + findDeleteButton().vm.$emit('click'); + + expect(wrapper.emitted('delete')).toEqual([[]]); + }); + + it.each` + canDelete | disabled | isDisabled + ${true} | ${false} | ${false} + ${true} | ${true} | ${true} + ${false} | ${false} | ${true} + ${false} | ${true} | ${true} + `( + 'when canDelete is $canDelete and disabled is $disabled is $isDisabled that the button is disabled', + ({ canDelete, disabled, isDisabled }) => { + mountComponent({ image: { ...defaultImage, canDelete }, disabled }); + + expect(findDeleteButton().props('disabled')).toBe(isDisabled); + }, + ); }); describe('metadata items', () => { @@ -82,7 +135,7 @@ describe('Details Header', () => { }); it('when there is one tag has the correct text', async () => { - mountComponent({ ...defaultImage, tagsCount: 1 }); + mountComponent({ image: { ...defaultImage, tagsCount: 1 } }); await waitForMetadataItems(); expect(findTagsCount().props('text')).toBe('1 tag'); @@ -124,10 +177,12 @@ describe('Details Header', () => { 'when the status is $status the text is $text and the tooltip is $tooltip', async ({ status, text, tooltip }) => { mountComponent({ - ...defaultImage, - expirationPolicyCleanupStatus: status, - project: { - containerExpirationPolicy: { enabled: true, nextRunAt: '2021-01-03T14:29:21Z' }, + image: { + ...defaultImage, + expirationPolicyCleanupStatus: status, + project: { + containerExpirationPolicy: { enabled: true, nextRunAt: '2021-01-03T14:29:21Z' }, + }, }, }); await waitForMetadataItems(); @@ -156,7 +211,7 @@ describe('Details Header', () => { expect(findLastUpdatedAndVisibility().props('icon')).toBe('eye'); }); it('shows an eye slashed when the project is not public', async () => { - mountComponent({ ...defaultImage, project: { visibility: 'private' } }); + mountComponent({ image: { ...defaultImage, project: { visibility: 'private' } } }); await waitForMetadataItems(); expect(findLastUpdatedAndVisibility().props('icon')).toBe('eye-slash'); diff --git a/spec/frontend/registry/explorer/components/details_page/empty_state_spec.js b/spec/frontend/registry/explorer/components/details_page/empty_state_spec.js new file mode 100644 index 00000000000..14b15945631 --- /dev/null +++ b/spec/frontend/registry/explorer/components/details_page/empty_state_spec.js @@ -0,0 +1,54 @@ +import { GlEmptyState } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; +import component from '~/registry/explorer/components/details_page/empty_state.vue'; +import { + NO_TAGS_TITLE, + NO_TAGS_MESSAGE, + MISSING_OR_DELETED_IMAGE_TITLE, + MISSING_OR_DELETED_IMAGE_MESSAGE, +} from '~/registry/explorer/constants'; + +describe('EmptyTagsState component', () => { + let wrapper; + + const findEmptyState = () => wrapper.find(GlEmptyState); + + const mountComponent = (propsData) => { + wrapper = shallowMount(component, { + stubs: { + GlEmptyState, + }, + propsData, + }); + }; + + afterEach(() => { + wrapper.destroy(); + wrapper = null; + }); + + it('contains gl-empty-state', () => { + mountComponent(); + expect(findEmptyState().exists()).toBe(true); + }); + + it.each` + isEmptyImage | title | description + ${false} | ${NO_TAGS_TITLE} | ${NO_TAGS_MESSAGE} + ${true} | ${MISSING_OR_DELETED_IMAGE_TITLE} | ${MISSING_OR_DELETED_IMAGE_MESSAGE} + `( + 'when isEmptyImage is $isEmptyImage has the correct props', + ({ isEmptyImage, title, description }) => { + mountComponent({ + noContainersImage: 'foo', + isEmptyImage, + }); + + expect(findEmptyState().props()).toMatchObject({ + title, + description, + svgPath: 'foo', + }); + }, + ); +}); diff --git a/spec/frontend/registry/explorer/components/details_page/empty_tags_state_spec.js b/spec/frontend/registry/explorer/components/details_page/empty_tags_state_spec.js deleted file mode 100644 index 09afd9d2d84..00000000000 --- a/spec/frontend/registry/explorer/components/details_page/empty_tags_state_spec.js +++ /dev/null @@ -1,43 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import { GlEmptyState } from '@gitlab/ui'; -import component from '~/registry/explorer/components/details_page/empty_tags_state.vue'; -import { - EMPTY_IMAGE_REPOSITORY_TITLE, - EMPTY_IMAGE_REPOSITORY_MESSAGE, -} from '~/registry/explorer/constants'; - -describe('EmptyTagsState component', () => { - let wrapper; - - const findEmptyState = () => wrapper.find(GlEmptyState); - - const mountComponent = () => { - wrapper = shallowMount(component, { - stubs: { - GlEmptyState, - }, - propsData: { - noContainersImage: 'foo', - }, - }); - }; - - afterEach(() => { - wrapper.destroy(); - wrapper = null; - }); - - it('contains gl-empty-state', () => { - mountComponent(); - expect(findEmptyState().exists()).toBe(true); - }); - - it('has the correct props', () => { - mountComponent(); - expect(findEmptyState().props()).toMatchObject({ - title: EMPTY_IMAGE_REPOSITORY_TITLE, - description: EMPTY_IMAGE_REPOSITORY_MESSAGE, - svgPath: 'foo', - }); - }); -}); diff --git a/spec/frontend/registry/explorer/components/details_page/partial_cleanup_alert_spec.js b/spec/frontend/registry/explorer/components/details_page/partial_cleanup_alert_spec.js index 17821d8be31..af8a23e412c 100644 --- a/spec/frontend/registry/explorer/components/details_page/partial_cleanup_alert_spec.js +++ b/spec/frontend/registry/explorer/components/details_page/partial_cleanup_alert_spec.js @@ -1,5 +1,5 @@ -import { shallowMount } from '@vue/test-utils'; import { GlAlert, GlSprintf } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; import component from '~/registry/explorer/components/details_page/partial_cleanup_alert.vue'; import { DELETE_ALERT_TITLE, DELETE_ALERT_LINK_TEXT } from '~/registry/explorer/constants'; diff --git a/spec/frontend/registry/explorer/components/details_page/status_alert_spec.js b/spec/frontend/registry/explorer/components/details_page/status_alert_spec.js new file mode 100644 index 00000000000..b079883cefd --- /dev/null +++ b/spec/frontend/registry/explorer/components/details_page/status_alert_spec.js @@ -0,0 +1,57 @@ +import { GlLink, GlSprintf, GlAlert } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; +import component from '~/registry/explorer/components/details_page/status_alert.vue'; +import { + DELETE_SCHEDULED, + DELETE_FAILED, + PACKAGE_DELETE_HELP_PAGE_PATH, + SCHEDULED_FOR_DELETION_STATUS_TITLE, + SCHEDULED_FOR_DELETION_STATUS_MESSAGE, + FAILED_DELETION_STATUS_TITLE, + FAILED_DELETION_STATUS_MESSAGE, +} from '~/registry/explorer/constants'; + +describe('Status Alert', () => { + let wrapper; + + const findLink = () => wrapper.find(GlLink); + const findAlert = () => wrapper.find(GlAlert); + const findMessage = () => wrapper.find('[data-testid="message"]'); + + const mountComponent = (propsData) => { + wrapper = shallowMount(component, { + propsData, + stubs: { + GlSprintf, + }, + }); + }; + + 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} + ${DELETE_FAILED} | ${FAILED_DELETION_STATUS_TITLE} | ${'warning'} | ${FAILED_DELETION_STATUS_MESSAGE} | ${''} + `( + `when the status is $status, title is $title, variant is $variant, message is $message and the link is $link`, + ({ status, title, variant, message, link }) => { + mountComponent({ status }); + + expect(findMessage().text()).toMatchInterpolatedText(message); + expect(findAlert().props()).toMatchObject({ + title, + variant, + }); + if (link) { + expect(findLink().attributes()).toMatchObject({ + target: '_blank', + href: link, + }); + } + }, + ); +}); diff --git a/spec/frontend/registry/explorer/components/details_page/tags_list_row_spec.js b/spec/frontend/registry/explorer/components/details_page/tags_list_row_spec.js index c2efc71c159..8b70f84c1bd 100644 --- a/spec/frontend/registry/explorer/components/details_page/tags_list_row_spec.js +++ b/spec/frontend/registry/explorer/components/details_page/tags_list_row_spec.js @@ -1,12 +1,9 @@ -import { shallowMount } from '@vue/test-utils'; import { GlFormCheckbox, GlSprintf, GlIcon } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; -import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; -import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; -import component from '~/registry/explorer/components/details_page/tags_list_row.vue'; import DeleteButton from '~/registry/explorer/components/delete_button.vue'; -import DetailsRow from '~/vue_shared/components/registry/details_row.vue'; +import component from '~/registry/explorer/components/details_page/tags_list_row.vue'; import { REMOVE_TAG_BUTTON_TITLE, REMOVE_TAG_BUTTON_DISABLE_TOOLTIP, @@ -14,6 +11,9 @@ import { NOT_AVAILABLE_TEXT, NOT_AVAILABLE_SIZE, } from '~/registry/explorer/constants/index'; +import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; +import DetailsRow from '~/vue_shared/components/registry/details_row.vue'; +import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; import { tagsMock } from '../../mock_data'; import { ListItem } from '../../stubs'; diff --git a/spec/frontend/registry/explorer/components/details_page/tags_list_spec.js b/spec/frontend/registry/explorer/components/details_page/tags_list_spec.js index 413795a7a57..dc6760a17bd 100644 --- a/spec/frontend/registry/explorer/components/details_page/tags_list_spec.js +++ b/spec/frontend/registry/explorer/components/details_page/tags_list_spec.js @@ -1,5 +1,5 @@ -import { shallowMount } from '@vue/test-utils'; import { GlButton } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; import component from '~/registry/explorer/components/details_page/tags_list.vue'; import TagsListRow from '~/registry/explorer/components/details_page/tags_list_row.vue'; import { TAGS_LIST_TITLE, REMOVE_TAGS_BUTTON_TITLE } from '~/registry/explorer/constants/index'; @@ -70,18 +70,25 @@ describe('Tags List', () => { }); }); - it('is disabled when no item is selected', () => { - mountComponent(); + it.each` + disabled | doSelect | buttonDisabled + ${true} | ${false} | ${'true'} + ${true} | ${true} | ${'true'} + ${false} | ${false} | ${'true'} + ${false} | ${true} | ${undefined} + `( + 'is $buttonDisabled that the button is disabled when the component disabled state is $disabled and is $doSelect that the user selected a tag', + async ({ disabled, buttonDisabled, doSelect }) => { + mountComponent({ tags, disabled, isMobile: false }); - expect(findDeleteButton().attributes('disabled')).toBe('true'); - }); + if (doSelect) { + findTagsListRow().at(0).vm.$emit('select'); + await wrapper.vm.$nextTick(); + } - it('is enabled when at least one item is selected', async () => { - mountComponent(); - findTagsListRow().at(0).vm.$emit('select'); - await wrapper.vm.$nextTick(); - expect(findDeleteButton().attributes('disabled')).toBe(undefined); - }); + expect(findDeleteButton().attributes('disabled')).toBe(buttonDisabled); + }, + ); it('click event emits a deleted event with selected items', () => { mountComponent(); @@ -100,12 +107,13 @@ describe('Tags List', () => { }); it('the correct props are bound to it', () => { - mountComponent(); + mountComponent({ tags, disabled: true }); const rows = findTagsListRow(); expect(rows.at(0).attributes()).toMatchObject({ first: 'true', + disabled: 'true', }); }); |