Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/details_header_spec.js')
-rw-r--r--spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/details_header_spec.js304
1 files changed, 304 insertions, 0 deletions
diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/details_header_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/details_header_spec.js
new file mode 100644
index 00000000000..f06300efa29
--- /dev/null
+++ b/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/details_header_spec.js
@@ -0,0 +1,304 @@
+import { GlDropdownItem, GlIcon } from '@gitlab/ui';
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import VueApollo from 'vue-apollo';
+import { GlDropdown } from 'jest/packages_and_registries/container_registry/explorer/stubs';
+import { useFakeDate } from 'helpers/fake_date';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
+import waitForPromises from 'helpers/wait_for_promises';
+import component from '~/packages_and_registries/container_registry/explorer/components/details_page/details_header.vue';
+import {
+ UNSCHEDULED_STATUS,
+ SCHEDULED_STATUS,
+ ONGOING_STATUS,
+ UNFINISHED_STATUS,
+ CLEANUP_DISABLED_TEXT,
+ CLEANUP_DISABLED_TOOLTIP,
+ CLEANUP_SCHEDULED_TOOLTIP,
+ CLEANUP_ONGOING_TOOLTIP,
+ CLEANUP_UNFINISHED_TOOLTIP,
+ ROOT_IMAGE_TEXT,
+ ROOT_IMAGE_TOOLTIP,
+} from '~/packages_and_registries/container_registry/explorer/constants';
+import getContainerRepositoryTagCountQuery from '~/packages_and_registries/container_registry/explorer/graphql/queries/get_container_repository_tags_count.query.graphql';
+import TitleArea from '~/vue_shared/components/registry/title_area.vue';
+import { imageTagsCountMock } from '../../mock_data';
+
+describe('Details Header', () => {
+ let wrapper;
+ let apolloProvider;
+ let localVue;
+
+ const defaultImage = {
+ name: 'foo',
+ updatedAt: '2020-11-03T13:29:21Z',
+ canDelete: true,
+ project: {
+ visibility: 'public',
+ containerExpirationPolicy: {
+ enabled: false,
+ },
+ },
+ };
+
+ // 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 findDeleteButton = () => wrapper.findComponent(GlDropdownItem);
+ const findInfoIcon = () => wrapper.findComponent(GlIcon);
+
+ const waitForMetadataItems = async () => {
+ // Metadata items are printed by a loop in the title-area and it takes two ticks for them to be available
+ await wrapper.vm.$nextTick();
+ await wrapper.vm.$nextTick();
+ };
+
+ const mountComponent = ({
+ propsData = { image: defaultImage },
+ resolver = jest.fn().mockResolvedValue(imageTagsCountMock()),
+ $apollo = undefined,
+ } = {}) => {
+ const mocks = {};
+
+ if ($apollo) {
+ mocks.$apollo = $apollo;
+ } else {
+ localVue = createLocalVue();
+ localVue.use(VueApollo);
+
+ const requestHandlers = [[getContainerRepositoryTagCountQuery, resolver]];
+ apolloProvider = createMockApollo(requestHandlers);
+ }
+
+ wrapper = shallowMount(component, {
+ localVue,
+ apolloProvider,
+ propsData,
+ directives: {
+ GlTooltip: createMockDirective(),
+ },
+ mocks,
+ stubs: {
+ TitleArea,
+ GlDropdown,
+ GlDropdownItem,
+ },
+ });
+ };
+
+ afterEach(() => {
+ // if we want to mix createMockApollo and manual mocks we need to reset everything
+ wrapper.destroy();
+ apolloProvider = undefined;
+ localVue = undefined;
+ wrapper = null;
+ });
+
+ describe('image name', () => {
+ describe('missing image name', () => {
+ beforeEach(() => {
+ mountComponent({ propsData: { image: { ...defaultImage, name: '' } } });
+
+ return waitForPromises();
+ });
+
+ it('root image ', () => {
+ expect(findTitle().text()).toBe(ROOT_IMAGE_TEXT);
+ });
+
+ it('has an icon', () => {
+ expect(findInfoIcon().exists()).toBe(true);
+ expect(findInfoIcon().props('name')).toBe('information-o');
+ });
+
+ it('has a tooltip', () => {
+ const tooltip = getBinding(findInfoIcon().element, 'gl-tooltip');
+ expect(tooltip.value).toBe(ROOT_IMAGE_TOOLTIP);
+ });
+ });
+
+ describe('with image name present', () => {
+ beforeEach(() => {
+ mountComponent();
+
+ return waitForPromises();
+ });
+
+ it('shows image.name ', () => {
+ expect(findTitle().text()).toContain('foo');
+ });
+
+ it('has no icon', () => {
+ expect(findInfoIcon().exists()).toBe(false);
+ });
+ });
+ });
+
+ describe('delete button', () => {
+ it('exists', () => {
+ mountComponent();
+
+ expect(findDeleteButton().exists()).toBe(true);
+ });
+
+ it('has the correct text', () => {
+ mountComponent();
+
+ expect(findDeleteButton().text()).toBe('Delete image repository');
+ });
+
+ it('has the correct props', () => {
+ mountComponent();
+
+ expect(findDeleteButton().attributes()).toMatchObject(
+ expect.objectContaining({
+ variant: 'danger',
+ }),
+ );
+ });
+
+ it('emits the correct event', () => {
+ mountComponent();
+
+ findDeleteButton().vm.$emit('click');
+
+ expect(wrapper.emitted('delete')).toEqual([[]]);
+ });
+
+ it.each`
+ canDelete | disabled | isDisabled
+ ${true} | ${false} | ${undefined}
+ ${true} | ${true} | ${'true'}
+ ${false} | ${false} | ${'true'}
+ ${false} | ${true} | ${'true'}
+ `(
+ 'when canDelete is $canDelete and disabled is $disabled is $isDisabled that the button is disabled',
+ ({ canDelete, disabled, isDisabled }) => {
+ mountComponent({ propsData: { image: { ...defaultImage, canDelete }, disabled } });
+
+ expect(findDeleteButton().attributes('disabled')).toBe(isDisabled);
+ },
+ );
+ });
+
+ describe('metadata items', () => {
+ describe('tags count', () => {
+ it('displays "-- tags" while loading', async () => {
+ // here we are forced to mock apollo because `waitForMetadataItems` waits
+ // for two ticks, de facto allowing the promise to resolve, so there is
+ // no way to catch the component as both rendered and in loading state
+ mountComponent({ $apollo: { queries: { containerRepository: { loading: true } } } });
+
+ await waitForMetadataItems();
+
+ expect(findTagsCount().props('text')).toBe('-- tags');
+ });
+
+ it('when there is more than one tag has the correct text', async () => {
+ mountComponent();
+
+ await waitForPromises();
+ await waitForMetadataItems();
+
+ expect(findTagsCount().props('text')).toBe('13 tags');
+ });
+
+ it('when there is one tag has the correct text', async () => {
+ mountComponent({
+ resolver: jest.fn().mockResolvedValue(imageTagsCountMock({ tagsCount: 1 })),
+ });
+
+ await waitForPromises();
+ await waitForMetadataItems();
+
+ expect(findTagsCount().props('text')).toBe('1 tag');
+ });
+
+ it('has the correct icon', async () => {
+ mountComponent();
+ await waitForMetadataItems();
+
+ expect(findTagsCount().props('icon')).toBe('tag');
+ });
+ });
+
+ describe('cleanup metadata item', () => {
+ it('has the correct icon', async () => {
+ mountComponent();
+ await waitForMetadataItems();
+
+ expect(findCleanup().props('icon')).toBe('expire');
+ });
+
+ it('when the expiration policy is disabled', async () => {
+ mountComponent();
+ await waitForMetadataItems();
+
+ expect(findCleanup().props()).toMatchObject({
+ text: CLEANUP_DISABLED_TEXT,
+ textTooltip: CLEANUP_DISABLED_TOOLTIP,
+ });
+ });
+
+ it.each`
+ status | text | tooltip
+ ${UNSCHEDULED_STATUS} | ${'Cleanup will run in 1 month'} | ${''}
+ ${SCHEDULED_STATUS} | ${'Cleanup pending'} | ${CLEANUP_SCHEDULED_TOOLTIP}
+ ${ONGOING_STATUS} | ${'Cleanup in progress'} | ${CLEANUP_ONGOING_TOOLTIP}
+ ${UNFINISHED_STATUS} | ${'Cleanup incomplete'} | ${CLEANUP_UNFINISHED_TOOLTIP}
+ `(
+ 'when the status is $status the text is $text and the tooltip is $tooltip',
+ async ({ status, text, tooltip }) => {
+ mountComponent({
+ propsData: {
+ image: {
+ ...defaultImage,
+ expirationPolicyCleanupStatus: status,
+ project: {
+ containerExpirationPolicy: { enabled: true, nextRunAt: '2021-01-03T14:29:21Z' },
+ },
+ },
+ },
+ });
+ await waitForMetadataItems();
+
+ expect(findCleanup().props()).toMatchObject({
+ text,
+ textTooltip: tooltip,
+ });
+ },
+ );
+ });
+
+ describe('visibility and updated at ', () => {
+ it('has last updated text', async () => {
+ mountComponent();
+ await waitForMetadataItems();
+
+ expect(findLastUpdatedAndVisibility().props('text')).toBe('Last updated 1 month ago');
+ });
+
+ describe('visibility icon', () => {
+ it('shows an eye when the project is public', async () => {
+ mountComponent();
+ await waitForMetadataItems();
+
+ expect(findLastUpdatedAndVisibility().props('icon')).toBe('eye');
+ });
+ it('shows an eye slashed when the project is not public', async () => {
+ mountComponent({
+ propsData: { image: { ...defaultImage, project: { visibility: 'private' } } },
+ });
+ await waitForMetadataItems();
+
+ expect(findLastUpdatedAndVisibility().props('icon')).toBe('eye-slash');
+ });
+ });
+ });
+ });
+});