summaryrefslogtreecommitdiff
path: root/spec/frontend/packages_and_registries
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-12-20 13:37:47 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2021-12-20 13:37:47 +0000
commitaee0a117a889461ce8ced6fcf73207fe017f1d99 (patch)
tree891d9ef189227a8445d83f35c1b0fc99573f4380 /spec/frontend/packages_and_registries
parent8d46af3258650d305f53b819eabf7ab18d22f59e (diff)
downloadgitlab-ce-aee0a117a889461ce8ced6fcf73207fe017f1d99.tar.gz
Add latest changes from gitlab-org/gitlab@14-6-stable-eev14.6.0-rc42
Diffstat (limited to 'spec/frontend/packages_and_registries')
-rw-r--r--spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/tags_list_spec.js214
-rw-r--r--spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/__snapshots__/project_empty_state_spec.js.snap3
-rw-r--r--spec/frontend/packages_and_registries/container_registry/explorer/mock_data.js8
-rw-r--r--spec/frontend/packages_and_registries/container_registry/explorer/pages/details_spec.js38
-rw-r--r--spec/frontend/packages_and_registries/dependency_proxy/app_spec.js23
-rw-r--r--spec/frontend/packages_and_registries/dependency_proxy/mock_data.js5
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/app_spec.js55
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/details_title_spec.js2
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/package_files_spec.js2
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/package_history_spec.js2
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/terraform_installation_spec.js2
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/details/store/actions_spec.js4
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/details/store/getters_spec.js2
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/details/store/mutations_spec.js2
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/__snapshots__/packages_list_app_spec.js.snap72
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/infrastructure_search_spec.js (renamed from spec/frontend/packages_and_registries/infrastructure_registry/components/infrastructure_search_spec.js)2
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/infrastructure_title_spec.js (renamed from spec/frontend/packages_and_registries/infrastructure_registry/components/infrastructure_title_spec.js)2
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/packages_list_app_spec.js239
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/packages_list_spec.js209
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/list/stores/actions_spec.js277
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/list/stores/getters_spec.js36
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/list/stores/mutations_spec.js87
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/list/utils_spec.js51
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/mock_data.js210
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/shared/__snapshots__/package_list_row_spec.js.snap118
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/shared/infrastructure_icon_and_name_spec.js (renamed from spec/frontend/packages_and_registries/infrastructure_registry/components/infrastructure_icon_and_name_spec.js)2
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/shared/package_list_row_spec.js161
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/version_row_spec.js.snap2
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/details/package_title_spec.js2
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/details/version_row_spec.js4
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/list/__snapshots__/package_list_row_spec.js.snap8
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/list/__snapshots__/publish_method_spec.js.snap1
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/list/package_list_row_spec.js30
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/list/packages_list_spec.js4
-rw-r--r--spec/frontend/packages_and_registries/package_registry/mock_data.js6
-rw-r--r--spec/frontend/packages_and_registries/package_registry/pages/__snapshots__/list_spec.js.snap (renamed from spec/frontend/packages_and_registries/package_registry/components/list/__snapshots__/app_spec.js.snap)14
-rw-r--r--spec/frontend/packages_and_registries/package_registry/pages/list_spec.js (renamed from spec/frontend/packages_and_registries/package_registry/components/list/app_spec.js)23
-rw-r--r--spec/frontend/packages_and_registries/settings/group/components/__snapshots__/settings_titles_spec.js.snap2
-rw-r--r--spec/frontend/packages_and_registries/settings/group/components/dependency_proxy_settings_spec.js184
-rw-r--r--spec/frontend/packages_and_registries/settings/group/components/group_settings_app_spec.js23
-rw-r--r--spec/frontend/packages_and_registries/settings/group/components/settings_titles_spec.js20
-rw-r--r--spec/frontend/packages_and_registries/settings/group/graphl/utils/cache_update_spec.js37
-rw-r--r--spec/frontend/packages_and_registries/settings/group/graphl/utils/optimistic_responses_spec.js19
-rw-r--r--spec/frontend/packages_and_registries/settings/group/mock_data.js24
-rw-r--r--spec/frontend/packages_and_registries/settings/project/settings/mock_data.js1
-rw-r--r--spec/frontend/packages_and_registries/shared/__snapshots__/publish_method_spec.js.snap43
-rw-r--r--spec/frontend/packages_and_registries/shared/components/registry_list_spec.js199
-rw-r--r--spec/frontend/packages_and_registries/shared/package_icon_and_name_spec.js32
-rw-r--r--spec/frontend/packages_and_registries/shared/package_path_spec.js104
-rw-r--r--spec/frontend/packages_and_registries/shared/package_tags_spec.js107
-rw-r--r--spec/frontend/packages_and_registries/shared/packages_list_loader_spec.js51
-rw-r--r--spec/frontend/packages_and_registries/shared/publish_method_spec.js50
-rw-r--r--spec/frontend/packages_and_registries/shared/utils_spec.js30
53 files changed, 2521 insertions, 327 deletions
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 9a42c82d7e0..56f12e2f0bb 100644
--- a/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/tags_list_spec.js
+++ b/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/tags_list_spec.js
@@ -1,18 +1,16 @@
-import { GlButton, GlKeysetPagination } from '@gitlab/ui';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
+import { stripTypenames } from 'helpers/graphql_helpers';
import EmptyTagsState from '~/packages_and_registries/container_registry/explorer/components/details_page/empty_state.vue';
import component from '~/packages_and_registries/container_registry/explorer/components/details_page/tags_list.vue';
import TagsListRow from '~/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row.vue';
import TagsLoader from '~/packages_and_registries/container_registry/explorer/components/details_page/tags_loader.vue';
-import {
- TAGS_LIST_TITLE,
- REMOVE_TAGS_BUTTON_TITLE,
-} from '~/packages_and_registries/container_registry/explorer/constants/index';
+import RegistryList from '~/packages_and_registries/shared/components/registry_list.vue';
import getContainerRepositoryTagsQuery from '~/packages_and_registries/container_registry/explorer/graphql/queries/get_container_repository_tags.query.graphql';
+import { GRAPHQL_PAGE_SIZE } from '~/packages_and_registries/container_registry/explorer/constants/index';
import { tagsMock, imageTagsMock, tagsPageInfo } from '../../mock_data';
const localVue = createLocalVue();
@@ -20,25 +18,20 @@ const localVue = createLocalVue();
describe('Tags List', () => {
let wrapper;
let apolloProvider;
+ let resolver;
const tags = [...tagsMock];
- const readOnlyTags = tags.map((t) => ({ ...t, canDelete: false }));
- const findTagsListRow = () => wrapper.findAll(TagsListRow);
- const findDeleteButton = () => wrapper.find(GlButton);
- const findListTitle = () => wrapper.find('[data-testid="list-title"]');
- const findPagination = () => wrapper.find(GlKeysetPagination);
- const findEmptyState = () => wrapper.find(EmptyTagsState);
- const findTagsLoader = () => wrapper.find(TagsLoader);
+ const findTagsListRow = () => wrapper.findAllComponents(TagsListRow);
+ const findRegistryList = () => wrapper.findComponent(RegistryList);
+ const findEmptyState = () => wrapper.findComponent(EmptyTagsState);
+ const findTagsLoader = () => wrapper.findComponent(TagsLoader);
const waitForApolloRequestRender = async () => {
await waitForPromises();
await nextTick();
};
- const mountComponent = ({
- propsData = { isMobile: false, id: 1 },
- resolver = jest.fn().mockResolvedValue(imageTagsMock()),
- } = {}) => {
+ const mountComponent = ({ propsData = { isMobile: false, id: 1 } } = {}) => {
localVue.use(VueApollo);
const requestHandlers = [[getContainerRepositoryTagsQuery, resolver]];
@@ -48,6 +41,7 @@ describe('Tags List', () => {
localVue,
apolloProvider,
propsData,
+ stubs: { RegistryList },
provide() {
return {
config: {},
@@ -56,99 +50,58 @@ describe('Tags List', () => {
});
};
+ beforeEach(() => {
+ resolver = jest.fn().mockResolvedValue(imageTagsMock());
+ });
+
afterEach(() => {
wrapper.destroy();
- wrapper = null;
});
- describe('List title', () => {
- it('exists', async () => {
+ describe('registry list', () => {
+ beforeEach(() => {
mountComponent();
- await waitForApolloRequestRender();
-
- expect(findListTitle().exists()).toBe(true);
+ return waitForApolloRequestRender();
});
- it('has the correct text', async () => {
- mountComponent();
-
- await waitForApolloRequestRender();
-
- expect(findListTitle().text()).toBe(TAGS_LIST_TITLE);
+ it('binds the correct props', () => {
+ expect(findRegistryList().props()).toMatchObject({
+ title: '2 tags',
+ pagination: stripTypenames(tagsPageInfo),
+ items: stripTypenames(tags),
+ idProperty: 'name',
+ });
});
- });
- describe('delete button', () => {
- it.each`
- inputTags | isMobile | isVisible
- ${tags} | ${false} | ${true}
- ${tags} | ${true} | ${false}
- ${readOnlyTags} | ${false} | ${false}
- ${readOnlyTags} | ${true} | ${false}
- `(
- 'is $isVisible that delete button exists when tags is $inputTags and isMobile is $isMobile',
- async ({ inputTags, isMobile, isVisible }) => {
- mountComponent({
- propsData: { tags: inputTags, isMobile, id: 1 },
- resolver: jest.fn().mockResolvedValue(imageTagsMock(inputTags)),
+ describe('events', () => {
+ it('prev-page fetch the previous page', () => {
+ findRegistryList().vm.$emit('prev-page');
+
+ expect(resolver).toHaveBeenCalledWith({
+ first: null,
+ before: tagsPageInfo.startCursor,
+ last: GRAPHQL_PAGE_SIZE,
+ id: '1',
});
-
- await waitForApolloRequestRender();
-
- expect(findDeleteButton().exists()).toBe(isVisible);
- },
- );
-
- it('has the correct text', async () => {
- mountComponent();
-
- await waitForApolloRequestRender();
-
- expect(findDeleteButton().text()).toBe(REMOVE_TAGS_BUTTON_TITLE);
- });
-
- it('has the correct props', async () => {
- mountComponent();
- await waitForApolloRequestRender();
-
- expect(findDeleteButton().attributes()).toMatchObject({
- category: 'secondary',
- variant: 'danger',
});
- });
-
- 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({ propsData: { tags, disabled, isMobile: false, id: 1 } });
-
- await waitForApolloRequestRender();
-
- if (doSelect) {
- findTagsListRow().at(0).vm.$emit('select');
- await nextTick();
- }
- expect(findDeleteButton().attributes('disabled')).toBe(buttonDisabled);
- },
- );
+ it('next-page fetch the previous page', () => {
+ findRegistryList().vm.$emit('next-page');
- it('click event emits a deleted event with selected items', async () => {
- mountComponent();
-
- await waitForApolloRequestRender();
+ expect(resolver).toHaveBeenCalledWith({
+ after: tagsPageInfo.endCursor,
+ first: GRAPHQL_PAGE_SIZE,
+ id: '1',
+ });
+ });
- findTagsListRow().at(0).vm.$emit('select');
- findDeleteButton().vm.$emit('click');
+ it('emits a delete event when list emits delete', () => {
+ const eventPayload = 'foo';
+ findRegistryList().vm.$emit('delete', eventPayload);
- expect(wrapper.emitted('delete')[0][0][0].name).toBe(tags[0].name);
+ expect(wrapper.emitted('delete')).toEqual([[eventPayload]]);
+ });
});
});
@@ -199,10 +152,12 @@ describe('Tags List', () => {
});
describe('when the list of tags is empty', () => {
- const resolver = jest.fn().mockResolvedValue(imageTagsMock([]));
+ beforeEach(() => {
+ resolver = jest.fn().mockResolvedValue(imageTagsMock([]));
+ });
it('has the empty state', async () => {
- mountComponent({ resolver });
+ mountComponent();
await waitForApolloRequestRender();
@@ -210,7 +165,7 @@ describe('Tags List', () => {
});
it('does not show the loader', async () => {
- mountComponent({ resolver });
+ mountComponent();
await waitForApolloRequestRender();
@@ -218,76 +173,13 @@ describe('Tags List', () => {
});
it('does not show the list', async () => {
- mountComponent({ resolver });
-
- await waitForApolloRequestRender();
-
- expect(findTagsListRow().exists()).toBe(false);
- expect(findListTitle().exists()).toBe(false);
- });
- });
-
- describe('pagination', () => {
- it('exists', async () => {
- mountComponent();
-
- await waitForApolloRequestRender();
-
- expect(findPagination().exists()).toBe(true);
- });
-
- it('is hidden when loading', () => {
mountComponent();
- expect(findPagination().exists()).toBe(false);
- });
-
- it('is hidden when there are no more pages', async () => {
- mountComponent({ resolver: jest.fn().mockResolvedValue(imageTagsMock([])) });
-
await waitForApolloRequestRender();
- expect(findPagination().exists()).toBe(false);
- });
-
- it('is wired to the correct pagination props', async () => {
- mountComponent();
-
- await waitForApolloRequestRender();
-
- expect(findPagination().props()).toMatchObject({
- hasNextPage: tagsPageInfo.hasNextPage,
- hasPreviousPage: tagsPageInfo.hasPreviousPage,
- });
- });
-
- it('fetch next page when user clicks next', async () => {
- const resolver = jest.fn().mockResolvedValue(imageTagsMock());
- mountComponent({ resolver });
-
- await waitForApolloRequestRender();
-
- findPagination().vm.$emit('next');
-
- expect(resolver).toHaveBeenCalledWith(
- expect.objectContaining({ after: tagsPageInfo.endCursor }),
- );
- });
-
- it('fetch previous page when user clicks prev', async () => {
- const resolver = jest.fn().mockResolvedValue(imageTagsMock());
- mountComponent({ resolver });
-
- await waitForApolloRequestRender();
-
- findPagination().vm.$emit('prev');
-
- expect(resolver).toHaveBeenCalledWith(
- expect.objectContaining({ first: null, before: tagsPageInfo.startCursor }),
- );
+ expect(findRegistryList().exists()).toBe(false);
});
});
-
describe('loading state', () => {
it.each`
isImageLoading | queryExecuting | loadingVisible
@@ -306,8 +198,6 @@ describe('Tags List', () => {
expect(findTagsLoader().exists()).toBe(loadingVisible);
expect(findTagsListRow().exists()).toBe(!loadingVisible);
- expect(findListTitle().exists()).toBe(!loadingVisible);
- expect(findPagination().exists()).toBe(!loadingVisible);
},
);
});
diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/__snapshots__/project_empty_state_spec.js.snap b/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/__snapshots__/project_empty_state_spec.js.snap
index 46b07b4c2d6..4b52e84d1a6 100644
--- a/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/__snapshots__/project_empty_state_spec.js.snap
+++ b/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/__snapshots__/project_empty_state_spec.js.snap
@@ -36,6 +36,7 @@ exports[`Registry Project Empty state to match the default snapshot 1`] = `
<gl-form-input-group-stub
class="gl-mb-4"
+ inputclass=""
predefinedoptions="[object Object]"
value=""
>
@@ -57,6 +58,7 @@ exports[`Registry Project Empty state to match the default snapshot 1`] = `
<gl-form-input-group-stub
class="gl-mb-4"
+ inputclass=""
predefinedoptions="[object Object]"
value=""
>
@@ -69,6 +71,7 @@ exports[`Registry Project Empty state to match the default snapshot 1`] = `
</gl-form-input-group-stub>
<gl-form-input-group-stub
+ inputclass=""
predefinedoptions="[object Object]"
value=""
>
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 6a835a28807..16625d913a5 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
@@ -37,6 +37,7 @@ export const graphQLImageListMock = {
data: {
project: {
__typename: 'Project',
+ id: '1',
containerRepositoriesCount: 2,
containerRepositories: {
__typename: 'ContainerRepositoryConnection',
@@ -51,6 +52,7 @@ export const graphQLEmptyImageListMock = {
data: {
project: {
__typename: 'Project',
+ id: '1',
containerRepositoriesCount: 2,
containerRepositories: {
__typename: 'ContainerRepositoryConnection',
@@ -65,6 +67,7 @@ export const graphQLEmptyGroupImageListMock = {
data: {
group: {
__typename: 'Group',
+ id: '1',
containerRepositoriesCount: 2,
containerRepositories: {
__typename: 'ContainerRepositoryConnection',
@@ -120,6 +123,7 @@ export const containerRepositoryMock = {
project: {
visibility: 'public',
path: 'gitlab-test',
+ id: '1',
containerExpirationPolicy: {
enabled: false,
nextRunAt: '2020-11-27T08:59:27Z',
@@ -167,6 +171,7 @@ export const imageTagsMock = (nodes = tagsMock) => ({
data: {
containerRepository: {
id: containerRepositoryMock.id,
+ tagsCount: nodes.length,
tags: {
nodes,
pageInfo: { ...tagsPageInfo },
@@ -191,7 +196,7 @@ export const graphQLImageDetailsMock = (override) => ({
data: {
containerRepository: {
...containerRepositoryMock,
-
+ tagsCount: tagsMock.length,
tags: {
nodes: tagsMock,
pageInfo: { ...tagsPageInfo },
@@ -242,6 +247,7 @@ export const dockerCommands = {
export const graphQLProjectImageRepositoriesDetailsMock = {
data: {
project: {
+ id: '1',
containerRepositories: {
nodes: [
{
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 adc9a64e5c9..9b821ba8ef3 100644
--- a/spec/frontend/packages_and_registries/container_registry/explorer/pages/details_spec.js
+++ b/spec/frontend/packages_and_registries/container_registry/explorer/pages/details_spec.js
@@ -1,6 +1,7 @@
import { GlKeysetPagination } from '@gitlab/ui';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import VueApollo from 'vue-apollo';
+import { nextTick } from 'vue';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import axios from '~/lib/utils/axios_utils';
@@ -22,6 +23,7 @@ import {
} 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 component from '~/packages_and_registries/container_registry/explorer/pages/details.vue';
import Tracking from '~/tracking';
@@ -32,6 +34,7 @@ import {
containerRepositoryMock,
graphQLEmptyImageDetailsMock,
tagsMock,
+ imageTagsMock,
} from '../mock_data';
import { DeleteModal } from '../stubs';
@@ -67,12 +70,13 @@ describe('Details Page', () => {
const waitForApolloRequestRender = async () => {
await waitForPromises();
- await wrapper.vm.$nextTick();
+ await nextTick();
};
const mountComponent = ({
resolver = jest.fn().mockResolvedValue(graphQLImageDetailsMock()),
mutationResolver = jest.fn().mockResolvedValue(graphQLDeleteImageRepositoryTagsMock),
+ tagsResolver = jest.fn().mockResolvedValue(graphQLImageDetailsMock(imageTagsMock)),
options,
config = {},
} = {}) => {
@@ -81,6 +85,7 @@ describe('Details Page', () => {
const requestHandlers = [
[getContainerRepositoryDetailsQuery, resolver],
[deleteContainerRepositoryTagsMutation, mutationResolver],
+ [getContainerRepositoryTagsQuery, tagsResolver],
];
apolloProvider = createMockApollo(requestHandlers);
@@ -242,38 +247,49 @@ describe('Details Page', () => {
describe('confirmDelete event', () => {
let mutationResolver;
+ let tagsResolver;
beforeEach(() => {
mutationResolver = jest.fn().mockResolvedValue(graphQLDeleteImageRepositoryTagsMock);
- mountComponent({ mutationResolver });
+ tagsResolver = jest.fn().mockResolvedValue(graphQLImageDetailsMock(imageTagsMock));
+ mountComponent({ mutationResolver, tagsResolver });
return waitForApolloRequestRender();
});
+
describe('when one item is selected to be deleted', () => {
- it('calls apollo mutation with the right parameters', async () => {
+ it('calls apollo mutation with the right parameters and refetches the tags list query', async () => {
findTagsList().vm.$emit('delete', [cleanTags[0]]);
- await wrapper.vm.$nextTick();
+ await nextTick();
findDeleteModal().vm.$emit('confirmDelete');
expect(mutationResolver).toHaveBeenCalledWith(
expect.objectContaining({ tagNames: [cleanTags[0].name] }),
);
+
+ await waitForPromises();
+
+ expect(tagsResolver).toHaveBeenCalled();
});
});
describe('when more than one item is selected to be deleted', () => {
- it('calls apollo mutation with the right parameters', async () => {
+ it('calls apollo mutation with the right parameters and refetches the tags list query', async () => {
findTagsList().vm.$emit('delete', tagsMock);
- await wrapper.vm.$nextTick();
+ await nextTick();
findDeleteModal().vm.$emit('confirmDelete');
expect(mutationResolver).toHaveBeenCalledWith(
expect.objectContaining({ tagNames: tagsMock.map((t) => t.name) }),
);
+
+ await waitForPromises();
+
+ expect(tagsResolver).toHaveBeenCalled();
});
});
});
@@ -382,7 +398,7 @@ describe('Details Page', () => {
findPartialCleanupAlert().vm.$emit('dismiss');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(axios.post).toHaveBeenCalledWith(config.userCalloutsPath, {
feature_name: config.userCalloutId,
@@ -472,7 +488,7 @@ describe('Details Page', () => {
await waitForApolloRequestRender();
findDetailsHeader().vm.$emit('delete');
- await wrapper.vm.$nextTick();
+ await nextTick();
};
it('on delete event it deletes the image', async () => {
@@ -497,13 +513,13 @@ describe('Details Page', () => {
findDeleteImage().vm.$emit('start');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findTagsLoader().exists()).toBe(true);
findDeleteImage().vm.$emit('end');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findTagsLoader().exists()).toBe(false);
});
@@ -513,7 +529,7 @@ describe('Details Page', () => {
findDeleteImage().vm.$emit('error');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findDeleteAlert().props('deleteAlertType')).toBe(ALERT_DANGER_IMAGE);
});
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 625f00a8666..44a7186904d 100644
--- a/spec/frontend/packages_and_registries/dependency_proxy/app_spec.js
+++ b/spec/frontend/packages_and_registries/dependency_proxy/app_spec.js
@@ -54,7 +54,6 @@ describe('DependencyProxyApp', () => {
}
const findProxyNotAvailableAlert = () => wrapper.findByTestId('proxy-not-available');
- const findProxyDisabledAlert = () => wrapper.findByTestId('proxy-disabled');
const findClipBoardButton = () => wrapper.findComponent(ClipboardButton);
const findFormGroup = () => wrapper.findComponent(GlFormGroup);
const findFormInputGroup = () => wrapper.findComponent(GlFormInputGroup);
@@ -219,28 +218,6 @@ describe('DependencyProxyApp', () => {
});
});
});
-
- describe('when the dependency proxy is disabled', () => {
- beforeEach(() => {
- resolver = jest
- .fn()
- .mockResolvedValue(proxyDetailsQuery({ extendSettings: { enabled: false } }));
- createComponent();
- return waitForPromises();
- });
-
- it('does not show the main area', () => {
- expect(findMainArea().exists()).toBe(false);
- });
-
- it('does not show the loader', () => {
- expect(findSkeletonLoader().exists()).toBe(false);
- });
-
- it('shows a proxy disabled alert', () => {
- expect(findProxyDisabledAlert().text()).toBe(DependencyProxyApp.i18n.proxyDisabledText);
- });
- });
});
});
});
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 8bad22b5287..2aa427bc6af 100644
--- a/spec/frontend/packages_and_registries/dependency_proxy/mock_data.js
+++ b/spec/frontend/packages_and_registries/dependency_proxy/mock_data.js
@@ -8,8 +8,8 @@ export const proxyData = () => ({
export const proxySettings = (extend = {}) => ({ enabled: true, ...extend });
export const proxyManifests = () => [
- { createdAt: '2021-09-22T09:45:28Z', imageName: 'alpine:latest' },
- { createdAt: '2021-09-21T09:45:28Z', imageName: 'alpine:stable' },
+ { id: 'proxy-1', createdAt: '2021-09-22T09:45:28Z', imageName: 'alpine:latest' },
+ { id: 'proxy-2', createdAt: '2021-09-21T09:45:28Z', imageName: 'alpine:stable' },
];
export const pagination = (extend) => ({
@@ -26,6 +26,7 @@ export const proxyDetailsQuery = ({ extendSettings = {}, extend } = {}) => ({
group: {
...proxyData(),
__typename: 'Group',
+ id: '1',
dependencyProxySetting: {
...proxySettings(extendSettings),
__typename: 'DependencyProxySetting',
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 c7c10cef504..2868af84181 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
@@ -9,15 +9,15 @@ import PackagesApp from '~/packages_and_registries/infrastructure_registry/detai
import PackageFiles from '~/packages_and_registries/infrastructure_registry/details/components/package_files.vue';
import PackageHistory from '~/packages_and_registries/infrastructure_registry/details/components/package_history.vue';
import * as getters from '~/packages_and_registries/infrastructure_registry/details/store/getters';
-import PackageListRow from '~/packages/shared/components/package_list_row.vue';
-import PackagesListLoader from '~/packages/shared/components/packages_list_loader.vue';
-import { TrackingActions } from '~/packages/shared/constants';
-import * as SharedUtils from '~/packages/shared/utils';
+import PackageListRow from '~/packages_and_registries/infrastructure_registry/shared/package_list_row.vue';
+import PackagesListLoader from '~/packages_and_registries/shared/components/packages_list_loader.vue';
+import { TRACKING_ACTIONS } from '~/packages_and_registries/shared/constants';
+import { TRACK_CATEGORY } from '~/packages_and_registries/infrastructure_registry/shared/constants';
import TerraformTitle from '~/packages_and_registries/infrastructure_registry/details/components/details_title.vue';
import TerraformInstallation from '~/packages_and_registries/infrastructure_registry/details/components/terraform_installation.vue';
import Tracking from '~/tracking';
-import { mavenPackage, mavenFiles, npmPackage } from 'jest/packages/mock_data';
+import { mavenPackage, mavenFiles, npmPackage } from '../../mock_data';
const localVue = createLocalVue();
localVue.use(Vuex);
@@ -232,87 +232,78 @@ describe('PackagesApp', () => {
describe('tracking', () => {
let eventSpy;
- let utilSpy;
- const category = 'foo';
beforeEach(() => {
eventSpy = jest.spyOn(Tracking, 'event');
- utilSpy = jest.spyOn(SharedUtils, 'packageTypeToTrackCategory').mockReturnValue(category);
});
- it('tracking category calls packageTypeToTrackCategory', () => {
- createComponent({ packageEntity: npmPackage });
- expect(wrapper.vm.tracking.category).toBe(category);
- expect(utilSpy).toHaveBeenCalledWith('npm');
- });
-
- it(`delete button on delete modal call event with ${TrackingActions.DELETE_PACKAGE}`, () => {
+ it(`delete button on delete modal call event with ${TRACKING_ACTIONS.DELETE_PACKAGE}`, () => {
createComponent({ packageEntity: npmPackage });
findDeleteModal().vm.$emit('primary');
expect(eventSpy).toHaveBeenCalledWith(
- category,
- TrackingActions.DELETE_PACKAGE,
+ TRACK_CATEGORY,
+ TRACKING_ACTIONS.DELETE_PACKAGE,
expect.any(Object),
);
});
- it(`canceling a package deletion tracks ${TrackingActions.CANCEL_DELETE_PACKAGE}`, () => {
+ it(`canceling a package deletion tracks ${TRACKING_ACTIONS.CANCEL_DELETE_PACKAGE}`, () => {
createComponent({ packageEntity: npmPackage });
findDeleteModal().vm.$emit('canceled');
expect(eventSpy).toHaveBeenCalledWith(
- category,
- TrackingActions.CANCEL_DELETE_PACKAGE,
+ TRACK_CATEGORY,
+ TRACKING_ACTIONS.CANCEL_DELETE_PACKAGE,
expect.any(Object),
);
});
- it(`request a file deletion tracks ${TrackingActions.REQUEST_DELETE_PACKAGE_FILE}`, () => {
+ it(`request a file deletion tracks ${TRACKING_ACTIONS.REQUEST_DELETE_PACKAGE_FILE}`, () => {
createComponent({ packageEntity: npmPackage });
findPackageFiles().vm.$emit('delete-file', mavenFiles[0]);
expect(eventSpy).toHaveBeenCalledWith(
- category,
- TrackingActions.REQUEST_DELETE_PACKAGE_FILE,
+ TRACK_CATEGORY,
+ TRACKING_ACTIONS.REQUEST_DELETE_PACKAGE_FILE,
expect.any(Object),
);
});
- it(`confirming a file deletion tracks ${TrackingActions.DELETE_PACKAGE_FILE}`, () => {
+ it(`confirming a file deletion tracks ${TRACKING_ACTIONS.DELETE_PACKAGE_FILE}`, () => {
createComponent({ packageEntity: npmPackage });
findPackageFiles().vm.$emit('delete-file', npmPackage);
findDeleteFileModal().vm.$emit('primary');
expect(eventSpy).toHaveBeenCalledWith(
- category,
- TrackingActions.REQUEST_DELETE_PACKAGE_FILE,
+ TRACK_CATEGORY,
+ TRACKING_ACTIONS.REQUEST_DELETE_PACKAGE_FILE,
expect.any(Object),
);
});
- it(`canceling a file deletion tracks ${TrackingActions.CANCEL_DELETE_PACKAGE_FILE}`, () => {
+ it(`canceling a file deletion tracks ${TRACKING_ACTIONS.CANCEL_DELETE_PACKAGE_FILE}`, () => {
createComponent({ packageEntity: npmPackage });
findPackageFiles().vm.$emit('delete-file', npmPackage);
findDeleteFileModal().vm.$emit('canceled');
expect(eventSpy).toHaveBeenCalledWith(
- category,
- TrackingActions.CANCEL_DELETE_PACKAGE_FILE,
+ TRACK_CATEGORY,
+ TRACKING_ACTIONS.CANCEL_DELETE_PACKAGE_FILE,
expect.any(Object),
);
});
- it(`file download link call event with ${TrackingActions.PULL_PACKAGE}`, () => {
+ it(`file download link call event with ${TRACKING_ACTIONS.PULL_PACKAGE}`, () => {
createComponent({ packageEntity: npmPackage });
findPackageFiles().vm.$emit('download-file');
expect(eventSpy).toHaveBeenCalledWith(
- category,
- TrackingActions.PULL_PACKAGE,
+ TRACK_CATEGORY,
+ TRACKING_ACTIONS.PULL_PACKAGE,
expect.any(Object),
);
});
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 a012ec4ab05..24bd80ba80c 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
@@ -1,8 +1,8 @@
import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
-import { terraformModule, mavenFiles, npmPackage } from 'jest/packages/mock_data';
import component from '~/packages_and_registries/infrastructure_registry/details/components/details_title.vue';
import TitleArea from '~/vue_shared/components/registry/title_area.vue';
+import { terraformModule, mavenFiles, npmPackage } from '../../mock_data';
const localVue = createLocalVue();
localVue.use(Vuex);
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 0c5aa30223b..6b6c33b7561 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
@@ -6,7 +6,7 @@ import component from '~/packages_and_registries/infrastructure_registry/details
import FileIcon from '~/vue_shared/components/file_icon.vue';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
-import { npmFiles, mavenFiles } from 'jest/packages/mock_data';
+import { npmFiles, mavenFiles } from '../../mock_data';
describe('Package Files', () => {
let wrapper;
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 4987af9f5b0..f10f05f4a0d 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
@@ -6,7 +6,7 @@ import { HISTORY_PIPELINES_LIMIT } from '~/packages_and_registries/shared/consta
import HistoryItem from '~/vue_shared/components/registry/history_item.vue';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
-import { mavenPackage, mockPipelineInfo } from 'jest/packages/mock_data';
+import { mavenPackage, mockPipelineInfo } from '../../mock_data';
describe('Package History', () => {
let wrapper;
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 c26784a4b75..6ff4a4c51ef 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
@@ -1,8 +1,8 @@
import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
-import { terraformModule as packageEntity } from 'jest/packages/mock_data';
import TerraformInstallation from '~/packages_and_registries/infrastructure_registry/details/components/terraform_installation.vue';
import CodeInstructions from '~/vue_shared/components/registry/code_instruction.vue';
+import { terraformModule as packageEntity } from '../../mock_data';
const localVue = createLocalVue();
localVue.use(Vuex);
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 61fa69c2f7a..b9383d6c38c 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
@@ -12,8 +12,8 @@ import {
DELETE_PACKAGE_ERROR_MESSAGE,
DELETE_PACKAGE_FILE_ERROR_MESSAGE,
DELETE_PACKAGE_FILE_SUCCESS_MESSAGE,
-} from '~/packages/shared/constants';
-import { npmPackage as packageEntity } from '../../../../../packages/mock_data';
+} from '~/packages_and_registries/shared/constants';
+import { npmPackage as packageEntity } from '../../mock_data';
jest.mock('~/flash.js');
jest.mock('~/api.js');
diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/store/getters_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/store/getters_spec.js
index 8740691a8ee..b14aaa93e1f 100644
--- a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/store/getters_spec.js
+++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/store/getters_spec.js
@@ -3,7 +3,7 @@ import {
npmPackage,
mockPipelineInfo,
mavenPackage as packageWithoutBuildInfo,
-} from 'jest/packages/mock_data';
+} from '../../mock_data';
describe('Getters PackageDetails Store', () => {
let state;
diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/store/mutations_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/store/mutations_spec.js
index 6efefea4a14..0f0c84af7da 100644
--- a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/store/mutations_spec.js
+++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/store/mutations_spec.js
@@ -1,6 +1,6 @@
import * as types from '~/packages_and_registries/infrastructure_registry/details/store/mutation_types';
import mutations from '~/packages_and_registries/infrastructure_registry/details/store/mutations';
-import { npmPackage as packageEntity } from 'jest/packages/mock_data';
+import { npmPackage as packageEntity } from '../../mock_data';
describe('Mutations package details Store', () => {
let mockState;
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
new file mode 100644
index 00000000000..99a7b8e427a
--- /dev/null
+++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/__snapshots__/packages_list_app_spec.js.snap
@@ -0,0 +1,72 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`packages_list_app renders 1`] = `
+<div>
+ <infrastructure-title-stub
+ helpurl="foo"
+ />
+
+ <infrastructure-search-stub />
+
+ <div>
+ <section
+ class="row empty-state text-center"
+ >
+ <div
+ class="col-12"
+ >
+ <div
+ class="svg-250 svg-content"
+ >
+ <img
+ alt=""
+ class="gl-max-w-full"
+ role="img"
+ src="helpSvg"
+ />
+ </div>
+ </div>
+
+ <div
+ class="col-12"
+ >
+ <div
+ class="text-content gl-mx-auto gl-my-0 gl-p-5"
+ >
+ <h1
+ class="gl-font-size-h-display gl-line-height-36 h4"
+ >
+
+ There are no packages yet
+
+ </h1>
+
+ <p
+ class="gl-mt-3"
+ >
+ Learn how to
+ <b-link-stub
+ class="gl-link"
+ event="click"
+ href="helpUrl"
+ routertag="a"
+ target="_blank"
+ >
+ publish and share your packages
+ </b-link-stub>
+ with GitLab.
+ </p>
+
+ <div
+ class="gl-display-flex gl-flex-wrap gl-justify-content-center"
+ >
+ <!---->
+
+ <!---->
+ </div>
+ </div>
+ </div>
+ </section>
+ </div>
+</div>
+`;
diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/infrastructure_search_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/infrastructure_search_spec.js
index 119b678cc37..b519ab00d06 100644
--- a/spec/frontend/packages_and_registries/infrastructure_registry/components/infrastructure_search_spec.js
+++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/infrastructure_search_spec.js
@@ -1,6 +1,6 @@
import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
-import component from '~/packages_and_registries/infrastructure_registry/components/infrastructure_search.vue';
+import component from '~/packages_and_registries/infrastructure_registry/list/components/infrastructure_search.vue';
import RegistrySearch from '~/vue_shared/components/registry/registry_search.vue';
import UrlSync from '~/vue_shared/components/url_sync.vue';
diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/infrastructure_title_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/infrastructure_title_spec.js
index db6e175b054..b0e586f189a 100644
--- a/spec/frontend/packages_and_registries/infrastructure_registry/components/infrastructure_title_spec.js
+++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/infrastructure_title_spec.js
@@ -1,5 +1,5 @@
import { shallowMount } from '@vue/test-utils';
-import component from '~/packages_and_registries/infrastructure_registry/components/infrastructure_title.vue';
+import component from '~/packages_and_registries/infrastructure_registry/list/components/infrastructure_title.vue';
import MetadataItem from '~/vue_shared/components/registry/metadata_item.vue';
import TitleArea from '~/vue_shared/components/registry/title_area.vue';
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
new file mode 100644
index 00000000000..cad75d2a858
--- /dev/null
+++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/packages_list_app_spec.js
@@ -0,0 +1,239 @@
+import { GlEmptyState, GlSprintf, GlLink } from '@gitlab/ui';
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import Vuex from 'vuex';
+import setWindowLocation from 'helpers/set_window_location_helper';
+import createFlash from '~/flash';
+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';
+import {
+ SHOW_DELETE_SUCCESS_ALERT,
+ FILTERED_SEARCH_TERM,
+} from '~/packages_and_registries/shared/constants';
+
+import * as packageUtils from '~/packages_and_registries/shared/utils';
+import InfrastructureSearch from '~/packages_and_registries/infrastructure_registry/list/components/infrastructure_search.vue';
+
+jest.mock('~/lib/utils/common_utils');
+jest.mock('~/flash');
+
+const localVue = createLocalVue();
+localVue.use(Vuex);
+
+describe('packages_list_app', () => {
+ let wrapper;
+ let store;
+
+ const PackageList = {
+ name: 'package-list',
+ template: '<div><slot name="empty-state"></slot></div>',
+ };
+ const GlLoadingIcon = { name: 'gl-loading-icon', template: '<div>loading</div>' };
+
+ const emptyListHelpUrl = 'helpUrl';
+ const findEmptyState = () => wrapper.find(GlEmptyState);
+ const findListComponent = () => wrapper.find(PackageList);
+ const findInfrastructureSearch = () => wrapper.find(InfrastructureSearch);
+
+ const createStore = (filter = []) => {
+ store = new Vuex.Store({
+ state: {
+ isLoading: false,
+ config: {
+ resourceId: 'project_id',
+ emptyListIllustration: 'helpSvg',
+ emptyListHelpUrl,
+ packageHelpUrl: 'foo',
+ },
+ filter,
+ },
+ });
+ store.dispatch = jest.fn();
+ };
+
+ const mountComponent = (provide) => {
+ wrapper = shallowMount(PackageListApp, {
+ localVue,
+ store,
+ stubs: {
+ GlEmptyState,
+ GlLoadingIcon,
+ PackageList,
+ GlSprintf,
+ GlLink,
+ },
+ provide,
+ });
+ };
+
+ beforeEach(() => {
+ createStore();
+ jest.spyOn(packageUtils, 'getQueryParams').mockReturnValue({});
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('renders', () => {
+ mountComponent();
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ it('call requestPackagesList on page:changed', () => {
+ mountComponent();
+ store.dispatch.mockClear();
+
+ const list = findListComponent();
+ list.vm.$emit('page:changed', 1);
+ expect(store.dispatch).toHaveBeenCalledWith('requestPackagesList', { page: 1 });
+ });
+
+ it('call requestDeletePackage on package:delete', () => {
+ mountComponent();
+
+ const list = findListComponent();
+ list.vm.$emit('package:delete', 'foo');
+ expect(store.dispatch).toHaveBeenCalledWith('requestDeletePackage', 'foo');
+ });
+
+ it('does call requestPackagesList only one time on render', () => {
+ mountComponent();
+
+ expect(store.dispatch).toHaveBeenCalledTimes(3);
+ expect(store.dispatch).toHaveBeenNthCalledWith(1, 'setSorting', expect.any(Object));
+ expect(store.dispatch).toHaveBeenNthCalledWith(2, 'setFilter', expect.any(Array));
+ expect(store.dispatch).toHaveBeenNthCalledWith(3, 'requestPackagesList');
+ });
+
+ describe('url query string handling', () => {
+ const defaultQueryParamsMock = {
+ search: [1, 2],
+ type: 'npm',
+ sort: 'asc',
+ orderBy: 'created',
+ };
+
+ it('calls setSorting with the query string based sorting', () => {
+ jest.spyOn(packageUtils, 'getQueryParams').mockReturnValue(defaultQueryParamsMock);
+
+ mountComponent();
+
+ expect(store.dispatch).toHaveBeenNthCalledWith(1, 'setSorting', {
+ orderBy: defaultQueryParamsMock.orderBy,
+ sort: defaultQueryParamsMock.sort,
+ });
+ });
+
+ it('calls setFilter with the query string based filters', () => {
+ jest.spyOn(packageUtils, 'getQueryParams').mockReturnValue(defaultQueryParamsMock);
+
+ mountComponent();
+
+ expect(store.dispatch).toHaveBeenNthCalledWith(2, 'setFilter', [
+ { type: 'type', value: { data: defaultQueryParamsMock.type } },
+ { type: FILTERED_SEARCH_TERM, value: { data: defaultQueryParamsMock.search[0] } },
+ { type: FILTERED_SEARCH_TERM, value: { data: defaultQueryParamsMock.search[1] } },
+ ]);
+ });
+
+ it('calls setSorting and setFilters with the results of extractFilterAndSorting', () => {
+ jest
+ .spyOn(packageUtils, 'extractFilterAndSorting')
+ .mockReturnValue({ filters: ['foo'], sorting: { sort: 'desc' } });
+
+ mountComponent();
+
+ expect(store.dispatch).toHaveBeenNthCalledWith(1, 'setSorting', { sort: 'desc' });
+ expect(store.dispatch).toHaveBeenNthCalledWith(2, 'setFilter', ['foo']);
+ });
+ });
+
+ describe('empty state', () => {
+ it('generate the correct empty list link', () => {
+ mountComponent();
+
+ const link = findListComponent().find(GlLink);
+
+ expect(link.attributes('href')).toBe(emptyListHelpUrl);
+ expect(link.text()).toBe('publish and share your packages');
+ });
+
+ it('includes the right content on the default tab', () => {
+ mountComponent();
+
+ const heading = findEmptyState().find('h1');
+
+ expect(heading.text()).toBe('There are no packages yet');
+ });
+ });
+
+ describe('filter without results', () => {
+ beforeEach(() => {
+ createStore([{ type: 'something' }]);
+ mountComponent();
+ });
+
+ it('should show specific empty message', () => {
+ expect(findEmptyState().text()).toContain('Sorry, your filter produced no results');
+ expect(findEmptyState().text()).toContain(
+ 'To widen your search, change or remove the filters above',
+ );
+ });
+ });
+
+ describe('Search', () => {
+ it('exists', () => {
+ mountComponent();
+
+ expect(findInfrastructureSearch().exists()).toBe(true);
+ });
+
+ it('on update fetches data from the store', () => {
+ mountComponent();
+ store.dispatch.mockClear();
+
+ findInfrastructureSearch().vm.$emit('update');
+
+ expect(store.dispatch).toHaveBeenCalledWith('requestPackagesList');
+ });
+ });
+
+ describe('delete alert handling', () => {
+ const originalLocation = window.location.href;
+ const search = `?${SHOW_DELETE_SUCCESS_ALERT}=true`;
+
+ beforeEach(() => {
+ createStore();
+ jest.spyOn(commonUtils, 'historyReplaceState').mockImplementation(() => {});
+ setWindowLocation(search);
+ });
+
+ afterEach(() => {
+ setWindowLocation(originalLocation);
+ });
+
+ it(`creates a flash if the query string contains ${SHOW_DELETE_SUCCESS_ALERT}`, () => {
+ mountComponent();
+
+ expect(createFlash).toHaveBeenCalledWith({
+ message: DELETE_PACKAGE_SUCCESS_MESSAGE,
+ type: 'notice',
+ });
+ });
+
+ it('calls historyReplaceState with a clean url', () => {
+ mountComponent();
+
+ expect(commonUtils.historyReplaceState).toHaveBeenCalledWith(originalLocation);
+ });
+
+ it(`does nothing if the query string does not contain ${SHOW_DELETE_SUCCESS_ALERT}`, () => {
+ setWindowLocation('?');
+ mountComponent();
+
+ expect(createFlash).not.toHaveBeenCalled();
+ expect(commonUtils.historyReplaceState).not.toHaveBeenCalled();
+ });
+ });
+});
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
new file mode 100644
index 00000000000..2fb76b98925
--- /dev/null
+++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/packages_list_spec.js
@@ -0,0 +1,209 @@
+import { GlTable, GlPagination, GlModal } from '@gitlab/ui';
+import { mount, createLocalVue } from '@vue/test-utils';
+import { last } from 'lodash';
+import Vuex from 'vuex';
+import stubChildren from 'helpers/stub_children';
+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 { 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';
+
+const localVue = createLocalVue();
+localVue.use(Vuex);
+
+describe('packages_list', () => {
+ let wrapper;
+ let store;
+
+ const EmptySlotStub = { name: 'empty-slot-stub', template: '<div>bar</div>' };
+
+ const findPackagesListLoader = () => wrapper.find(PackagesListLoader);
+ const findPackageListPagination = () => wrapper.find(GlPagination);
+ const findPackageListDeleteModal = () => wrapper.find(GlModal);
+ const findEmptySlot = () => wrapper.find(EmptySlotStub);
+ const findPackagesListRow = () => wrapper.find(PackagesListRow);
+
+ const createStore = (isGroupPage, packages, isLoading) => {
+ const state = {
+ isLoading,
+ packages,
+ pagination: {
+ perPage: 1,
+ total: 1,
+ page: 1,
+ },
+ config: {
+ isGroupPage,
+ },
+ sorting: {
+ orderBy: 'version',
+ sort: 'desc',
+ },
+ };
+ store = new Vuex.Store({
+ state,
+ getters: {
+ getList: () => packages,
+ },
+ });
+ store.dispatch = jest.fn();
+ };
+
+ const mountComponent = ({
+ isGroupPage = false,
+ packages = packageList,
+ isLoading = false,
+ ...options
+ } = {}) => {
+ createStore(isGroupPage, packages, isLoading);
+
+ wrapper = mount(PackagesList, {
+ localVue,
+ store,
+ stubs: {
+ ...stubChildren(PackagesList),
+ GlTable,
+ GlModal,
+ },
+ ...options,
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ describe('when is loading', () => {
+ beforeEach(() => {
+ mountComponent({
+ packages: [],
+ isLoading: true,
+ });
+ });
+
+ it('shows skeleton loader when loading', () => {
+ expect(findPackagesListLoader().exists()).toBe(true);
+ });
+ });
+
+ describe('when is not loading', () => {
+ beforeEach(() => {
+ mountComponent();
+ });
+
+ it('does not show skeleton loader when not loading', () => {
+ expect(findPackagesListLoader().exists()).toBe(false);
+ });
+ });
+
+ describe('layout', () => {
+ beforeEach(() => {
+ mountComponent();
+ });
+
+ it('contains a pagination component', () => {
+ const sorting = findPackageListPagination();
+ expect(sorting.exists()).toBe(true);
+ });
+
+ it('contains a modal component', () => {
+ const sorting = findPackageListDeleteModal();
+ expect(sorting.exists()).toBe(true);
+ });
+ });
+
+ describe('when the user can destroy the package', () => {
+ beforeEach(() => {
+ mountComponent();
+ });
+
+ it('setItemToBeDeleted sets itemToBeDeleted and open the modal', () => {
+ const mockModalShow = jest.spyOn(wrapper.vm.$refs.packageListDeleteModal, 'show');
+ const item = last(wrapper.vm.list);
+
+ findPackagesListRow().vm.$emit('packageToDelete', item);
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.vm.itemToBeDeleted).toEqual(item);
+ expect(mockModalShow).toHaveBeenCalled();
+ });
+ });
+
+ it('deleteItemConfirmation resets itemToBeDeleted', () => {
+ wrapper.setData({ itemToBeDeleted: 1 });
+ wrapper.vm.deleteItemConfirmation();
+ expect(wrapper.vm.itemToBeDeleted).toEqual(null);
+ });
+
+ it('deleteItemConfirmation emit package:delete', () => {
+ const itemToBeDeleted = { id: 2 };
+ wrapper.setData({ itemToBeDeleted });
+ wrapper.vm.deleteItemConfirmation();
+ return wrapper.vm.$nextTick(() => {
+ expect(wrapper.emitted('package:delete')[0]).toEqual([itemToBeDeleted]);
+ });
+ });
+
+ it('deleteItemCanceled resets itemToBeDeleted', () => {
+ wrapper.setData({ itemToBeDeleted: 1 });
+ wrapper.vm.deleteItemCanceled();
+ expect(wrapper.vm.itemToBeDeleted).toEqual(null);
+ });
+ });
+
+ describe('when the list is empty', () => {
+ beforeEach(() => {
+ mountComponent({
+ packages: [],
+ slots: {
+ 'empty-state': EmptySlotStub,
+ },
+ });
+ });
+
+ it('show the empty slot', () => {
+ const emptySlot = findEmptySlot();
+ expect(emptySlot.exists()).toBe(true);
+ });
+ });
+
+ describe('pagination component', () => {
+ let pagination;
+ let modelEvent;
+
+ beforeEach(() => {
+ mountComponent();
+ pagination = findPackageListPagination();
+ // retrieve the event used by v-model, a more sturdy approach than hardcoding it
+ modelEvent = pagination.vm.$options.model.event;
+ });
+
+ it('emits page:changed events when the page changes', () => {
+ pagination.vm.$emit(modelEvent, 2);
+ expect(wrapper.emitted('page:changed')).toEqual([[2]]);
+ });
+ });
+
+ describe('tracking', () => {
+ let eventSpy;
+
+ beforeEach(() => {
+ mountComponent();
+ eventSpy = jest.spyOn(Tracking, 'event');
+ 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),
+ );
+ });
+ });
+});
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
new file mode 100644
index 00000000000..3fbfe1060dc
--- /dev/null
+++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/stores/actions_spec.js
@@ -0,0 +1,277 @@
+import axios from 'axios';
+import MockAdapter from 'axios-mock-adapter';
+import testAction from 'helpers/vuex_action_helper';
+import Api from '~/api';
+import createFlash from '~/flash';
+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('~/api.js');
+
+describe('Actions Package list store', () => {
+ const headers = 'bar';
+ let mock;
+
+ beforeEach(() => {
+ Api.projectPackages = jest.fn().mockResolvedValue({ data: 'foo', headers });
+ Api.groupPackages = jest.fn().mockResolvedValue({ data: 'baz', headers });
+ mock = new MockAdapter(axios);
+ });
+
+ afterEach(() => {
+ mock.restore();
+ });
+
+ describe('requestPackagesList', () => {
+ const sorting = {
+ sort: 'asc',
+ orderBy: 'version',
+ };
+
+ const filter = [];
+ it('should fetch the project packages list when isGroupPage is false', (done) => {
+ testAction(
+ actions.requestPackagesList,
+ undefined,
+ { config: { isGroupPage: false, resourceId: 1 }, sorting, filter },
+ [],
+ [
+ { type: 'setLoading', payload: true },
+ { type: 'receivePackagesListSuccess', payload: { data: 'foo', headers } },
+ { type: 'setLoading', payload: false },
+ ],
+ () => {
+ expect(Api.projectPackages).toHaveBeenCalledWith(1, {
+ params: { page: 1, per_page: 20, sort: sorting.sort, order_by: sorting.orderBy },
+ });
+ done();
+ },
+ );
+ });
+
+ it('should fetch the group packages list when isGroupPage is true', (done) => {
+ testAction(
+ actions.requestPackagesList,
+ undefined,
+ { config: { isGroupPage: true, resourceId: 2 }, sorting, filter },
+ [],
+ [
+ { type: 'setLoading', payload: true },
+ { type: 'receivePackagesListSuccess', payload: { data: 'baz', headers } },
+ { type: 'setLoading', payload: false },
+ ],
+ () => {
+ expect(Api.groupPackages).toHaveBeenCalledWith(2, {
+ params: { page: 1, per_page: 20, sort: sorting.sort, order_by: sorting.orderBy },
+ });
+ done();
+ },
+ );
+ });
+
+ it('should fetch packages of a certain type when a filter with a type is present', (done) => {
+ const packageType = 'maven';
+
+ testAction(
+ actions.requestPackagesList,
+ undefined,
+ {
+ config: { isGroupPage: false, resourceId: 1 },
+ sorting,
+ filter: [{ type: 'type', value: { data: 'maven' } }],
+ },
+ [],
+ [
+ { type: 'setLoading', payload: true },
+ { type: 'receivePackagesListSuccess', payload: { data: 'foo', headers } },
+ { type: 'setLoading', payload: false },
+ ],
+ () => {
+ expect(Api.projectPackages).toHaveBeenCalledWith(1, {
+ params: {
+ page: 1,
+ per_page: 20,
+ sort: sorting.sort,
+ order_by: sorting.orderBy,
+ package_type: packageType,
+ },
+ });
+ done();
+ },
+ );
+ });
+
+ it('should create flash on API error', (done) => {
+ Api.projectPackages = jest.fn().mockRejectedValue();
+ testAction(
+ actions.requestPackagesList,
+ undefined,
+ { config: { isGroupPage: false, resourceId: 2 }, sorting, filter },
+ [],
+ [
+ { type: 'setLoading', payload: true },
+ { type: 'setLoading', payload: false },
+ ],
+ () => {
+ expect(createFlash).toHaveBeenCalled();
+ done();
+ },
+ );
+ });
+
+ it('should force the terraform_module type when forceTerraform is true', (done) => {
+ testAction(
+ actions.requestPackagesList,
+ undefined,
+ { config: { isGroupPage: false, resourceId: 1, forceTerraform: true }, sorting, filter },
+ [],
+ [
+ { type: 'setLoading', payload: true },
+ { type: 'receivePackagesListSuccess', payload: { data: 'foo', headers } },
+ { type: 'setLoading', payload: false },
+ ],
+ () => {
+ expect(Api.projectPackages).toHaveBeenCalledWith(1, {
+ params: {
+ page: 1,
+ per_page: 20,
+ sort: sorting.sort,
+ order_by: sorting.orderBy,
+ package_type: 'terraform_module',
+ },
+ });
+ done();
+ },
+ );
+ });
+ });
+
+ describe('receivePackagesListSuccess', () => {
+ it('should set received packages', (done) => {
+ const data = 'foo';
+
+ testAction(
+ actions.receivePackagesListSuccess,
+ { data, headers },
+ null,
+ [
+ { type: types.SET_PACKAGE_LIST_SUCCESS, payload: data },
+ { type: types.SET_PAGINATION, payload: headers },
+ ],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('setInitialState', () => {
+ it('should commit setInitialState', (done) => {
+ testAction(
+ actions.setInitialState,
+ '1',
+ null,
+ [{ type: types.SET_INITIAL_STATE, payload: '1' }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('setLoading', () => {
+ it('should commit set main loading', (done) => {
+ testAction(
+ actions.setLoading,
+ true,
+ null,
+ [{ type: types.SET_MAIN_LOADING, payload: true }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('requestDeletePackage', () => {
+ const payload = {
+ _links: {
+ delete_api_path: 'foo',
+ },
+ };
+ it('should perform a delete operation on _links.delete_api_path', (done) => {
+ mock.onDelete(payload._links.delete_api_path).replyOnce(200);
+ Api.projectPackages = jest.fn().mockResolvedValue({ data: 'foo' });
+
+ testAction(
+ actions.requestDeletePackage,
+ payload,
+ { pagination: { page: 1 } },
+ [],
+ [
+ { type: 'setLoading', payload: true },
+ { type: 'requestPackagesList', payload: { page: 1 } },
+ ],
+ done,
+ );
+ });
+
+ it('should stop the loading and call create flash on api error', (done) => {
+ mock.onDelete(payload._links.delete_api_path).replyOnce(400);
+ testAction(
+ actions.requestDeletePackage,
+ payload,
+ null,
+ [],
+ [
+ { type: 'setLoading', payload: true },
+ { type: 'setLoading', payload: false },
+ ],
+ () => {
+ expect(createFlash).toHaveBeenCalled();
+ done();
+ },
+ );
+ });
+
+ it.each`
+ property | actionPayload
+ ${'_links'} | ${{}}
+ ${'delete_api_path'} | ${{ _links: {} }}
+ `('should reject and createFlash when $property is missing', ({ actionPayload }, done) => {
+ testAction(actions.requestDeletePackage, actionPayload, null, [], []).catch((e) => {
+ expect(e).toEqual(new Error(MISSING_DELETE_PATH_ERROR));
+ expect(createFlash).toHaveBeenCalledWith({
+ message: DELETE_PACKAGE_ERROR_MESSAGE,
+ });
+ done();
+ });
+ });
+ });
+
+ describe('setSorting', () => {
+ it('should commit SET_SORTING', (done) => {
+ testAction(
+ actions.setSorting,
+ 'foo',
+ null,
+ [{ type: types.SET_SORTING, payload: 'foo' }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('setFilter', () => {
+ it('should commit SET_FILTER', (done) => {
+ testAction(
+ actions.setFilter,
+ 'foo',
+ null,
+ [{ type: types.SET_FILTER, payload: 'foo' }],
+ [],
+ done,
+ );
+ });
+ });
+});
diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/list/stores/getters_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/stores/getters_spec.js
new file mode 100644
index 00000000000..f2d52ace34e
--- /dev/null
+++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/stores/getters_spec.js
@@ -0,0 +1,36 @@
+import getList from '~/packages_and_registries/infrastructure_registry/list/stores/getters';
+import { packageList } from '../../mock_data';
+
+describe('Getters registry list store', () => {
+ let state;
+
+ const setState = ({ isGroupPage = false } = {}) => {
+ state = {
+ packages: packageList,
+ config: {
+ isGroupPage,
+ },
+ };
+ };
+
+ beforeEach(() => setState());
+
+ afterEach(() => {
+ state = null;
+ });
+
+ describe('getList', () => {
+ it('returns a list of packages', () => {
+ const result = getList(state);
+
+ expect(result).toHaveLength(packageList.length);
+ expect(result[0].name).toBe('Test package');
+ });
+
+ it('adds projectPathName', () => {
+ const result = getList(state);
+
+ expect(result[0].projectPathName).toMatchInlineSnapshot(`"foo / bar / baz"`);
+ });
+ });
+});
diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/list/stores/mutations_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/stores/mutations_spec.js
new file mode 100644
index 00000000000..afd7a7e5439
--- /dev/null
+++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/stores/mutations_spec.js
@@ -0,0 +1,87 @@
+import * as commonUtils from '~/lib/utils/common_utils';
+import * as types from '~/packages_and_registries/infrastructure_registry/list/stores/mutation_types';
+import mutations from '~/packages_and_registries/infrastructure_registry/list/stores/mutations';
+import createState from '~/packages_and_registries/infrastructure_registry/list/stores/state';
+import { npmPackage, mavenPackage } from '../../mock_data';
+
+describe('Mutations Registry Store', () => {
+ let mockState;
+ beforeEach(() => {
+ mockState = createState();
+ });
+
+ describe('SET_INITIAL_STATE', () => {
+ it('should set the initial state', () => {
+ const config = {
+ resourceId: '1',
+ pageType: 'groups',
+ userCanDelete: '',
+ emptyListIllustration: 'foo',
+ emptyListHelpUrl: 'baz',
+ };
+
+ const expectedState = {
+ ...mockState,
+ config: {
+ ...config,
+ isGroupPage: true,
+ canDestroyPackage: true,
+ },
+ };
+ mutations[types.SET_INITIAL_STATE](mockState, config);
+
+ expect(mockState.projectId).toEqual(expectedState.projectId);
+ });
+ });
+
+ describe('SET_PACKAGE_LIST_SUCCESS', () => {
+ it('should set a packages list', () => {
+ const payload = [npmPackage, mavenPackage];
+ const expectedState = { ...mockState, packages: payload };
+ mutations[types.SET_PACKAGE_LIST_SUCCESS](mockState, payload);
+
+ expect(mockState.packages).toEqual(expectedState.packages);
+ });
+ });
+
+ describe('SET_MAIN_LOADING', () => {
+ it('should set main loading', () => {
+ mutations[types.SET_MAIN_LOADING](mockState, true);
+
+ expect(mockState.isLoading).toEqual(true);
+ });
+ });
+
+ describe('SET_PAGINATION', () => {
+ const mockPagination = { perPage: 10, page: 1 };
+ beforeEach(() => {
+ commonUtils.normalizeHeaders = jest.fn().mockReturnValue('baz');
+ commonUtils.parseIntPagination = jest.fn().mockReturnValue(mockPagination);
+ });
+ it('should set a parsed pagination', () => {
+ mutations[types.SET_PAGINATION](mockState, 'foo');
+ expect(commonUtils.normalizeHeaders).toHaveBeenCalledWith('foo');
+ expect(commonUtils.parseIntPagination).toHaveBeenCalledWith('baz');
+ expect(mockState.pagination).toEqual(mockPagination);
+ });
+ });
+
+ describe('SET_SORTING', () => {
+ it('should merge the sorting object with sort value', () => {
+ mutations[types.SET_SORTING](mockState, { sort: 'desc' });
+ expect(mockState.sorting).toEqual({ ...mockState.sorting, sort: 'desc' });
+ });
+
+ it('should merge the sorting object with order_by value', () => {
+ mutations[types.SET_SORTING](mockState, { orderBy: 'foo' });
+ expect(mockState.sorting).toEqual({ ...mockState.sorting, orderBy: 'foo' });
+ });
+ });
+
+ describe('SET_FILTER', () => {
+ it('should set the filter query', () => {
+ mutations[types.SET_FILTER](mockState, 'foo');
+ expect(mockState.filter).toEqual('foo');
+ });
+ });
+});
diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/list/utils_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/utils_spec.js
new file mode 100644
index 00000000000..a897fb90522
--- /dev/null
+++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/utils_spec.js
@@ -0,0 +1,51 @@
+import { SORT_FIELDS } from '~/packages_and_registries/infrastructure_registry/list/constants';
+import {
+ getNewPaginationPage,
+ sortableFields,
+} from '~/packages_and_registries/infrastructure_registry/list/utils';
+
+describe('Packages list utils', () => {
+ describe('sortableFields', () => {
+ it('returns the correct list when is a project page', () => {
+ expect(sortableFields()).toEqual(SORT_FIELDS.filter((f) => f.orderBy !== 'project_path'));
+ });
+ it('returns the full list on the group page', () => {
+ expect(sortableFields(true)).toEqual(SORT_FIELDS);
+ });
+ });
+ describe('packageTypeDisplay', () => {
+ it('returns the current page when total items exceeds pagniation', () => {
+ expect(getNewPaginationPage(2, 20, 21)).toBe(2);
+ });
+
+ it('returns the previous page when total items is lower than or equal to pagination', () => {
+ expect(getNewPaginationPage(2, 20, 20)).toBe(1);
+ });
+
+ it('returns the first page when totalItems is lower than or equal to perPage', () => {
+ expect(getNewPaginationPage(4, 20, 20)).toBe(1);
+ });
+
+ describe('works when a different perPage is used', () => {
+ it('returns the current page', () => {
+ expect(getNewPaginationPage(2, 10, 11)).toBe(2);
+ });
+
+ it('returns the previous page', () => {
+ expect(getNewPaginationPage(2, 10, 10)).toBe(1);
+ });
+ });
+
+ describe.each`
+ currentPage | totalItems | expectedResult
+ ${1} | ${20} | ${1}
+ ${2} | ${20} | ${1}
+ ${3} | ${40} | ${2}
+ ${4} | ${60} | ${3}
+ `(`works across numerious pages`, ({ currentPage, totalItems, expectedResult }) => {
+ it(`when currentPage is ${currentPage} return to the previous page ${expectedResult}`, () => {
+ expect(getNewPaginationPage(currentPage, 20, totalItems)).toBe(expectedResult);
+ });
+ });
+ });
+});
diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/mock_data.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/mock_data.js
new file mode 100644
index 00000000000..33b47cca68b
--- /dev/null
+++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/mock_data.js
@@ -0,0 +1,210 @@
+const _links = {
+ web_path: 'foo',
+ delete_api_path: 'bar',
+};
+
+export const mockPipelineInfo = {
+ id: 1,
+ ref: 'branch-name',
+ sha: 'sha-baz',
+ user: {
+ name: 'foo',
+ },
+ project: {
+ name: 'foo-project',
+ web_url: 'foo-project-link',
+ commit_url: 'foo-commit-link',
+ pipeline_url: 'foo-pipeline-link',
+ },
+ created_at: '2015-12-10',
+};
+
+export const mavenPackage = {
+ created_at: '2015-12-10',
+ id: 1,
+ maven_metadatum: {
+ app_group: 'com.test.app',
+ app_name: 'test-app',
+ app_version: '1.0-SNAPSHOT',
+ },
+ name: 'Test package',
+ package_type: 'maven',
+ project_path: 'foo/bar/baz',
+ projectPathName: 'foo/bar/baz',
+ project_id: 1,
+ updated_at: '2015-12-10',
+ version: '1.0.0',
+ _links,
+};
+
+export const mavenFiles = [
+ {
+ created_at: '2015-12-10',
+ file_name: 'File one',
+ id: 1,
+ size: 100,
+ download_path: '/-/package_files/1/download',
+ },
+ {
+ created_at: '2015-12-10',
+ file_name: 'File two',
+ id: 2,
+ size: 200,
+ download_path: '/-/package_files/2/download',
+ },
+];
+
+export const npmPackage = {
+ created_at: '2015-12-10',
+ id: 2,
+ name: '@Test/package',
+ package_type: 'npm',
+ project_path: 'foo/bar/baz',
+ projectPathName: 'foo/bar/baz',
+ project_id: 1,
+ updated_at: '2015-12-10',
+ version: '',
+ versions: [],
+ _links,
+ pipeline: mockPipelineInfo,
+};
+
+export const npmFiles = [
+ {
+ created_at: '2015-12-10',
+ file_name: '@test/test-package-1.0.0.tgz',
+ id: 2,
+ size: 200,
+ download_path: '/-/package_files/2/download',
+ pipelines: [
+ { id: 1, project: { commit_url: 'http://foo.bar' }, git_commit_message: 'foo bar baz?' },
+ ],
+ file_sha256: 'file_sha256',
+ file_md5: 'file_md5',
+ file_sha1: 'file_sha1',
+ },
+];
+
+export const conanPackage = {
+ conan_metadatum: {
+ package_channel: 'stable',
+ package_username: 'conan+conan-package',
+ },
+ conan_package_name: 'conan-package',
+ created_at: '2015-12-10',
+ id: 3,
+ name: 'conan-package/1.0.0@conan+conan-package/stable',
+ project_path: 'foo/bar/baz',
+ projectPathName: 'foo/bar/baz',
+ package_files: [],
+ package_type: 'conan',
+ project_id: 1,
+ updated_at: '2015-12-10',
+ version: '1.0.0',
+ _links,
+};
+
+export const dependencyLinks = {
+ withoutFramework: { name: 'Moqi', version_pattern: '2.5.6' },
+ withoutVersion: { name: 'Castle.Core', version_pattern: '' },
+ fullLink: {
+ name: 'Test.Dependency',
+ version_pattern: '2.3.7',
+ target_framework: '.NETStandard2.0',
+ },
+ anotherFullLink: {
+ name: 'Newtonsoft.Json',
+ version_pattern: '12.0.3',
+ target_framework: '.NETStandard2.0',
+ },
+};
+
+export const nugetPackage = {
+ created_at: '2015-12-10',
+ id: 4,
+ name: 'NugetPackage1',
+ package_files: [],
+ package_type: 'nuget',
+ project_id: 1,
+ tags: [],
+ updated_at: '2015-12-10',
+ version: '1.0.0',
+ dependency_links: Object.values(dependencyLinks),
+ nuget_metadatum: {
+ icon_url: 'fake-icon',
+ project_url: 'project-foo-url',
+ license_url: 'license-foo-url',
+ },
+};
+
+export const rubygemsPackage = {
+ created_at: '2015-12-10',
+ id: 4,
+ name: 'RubyGem1',
+ package_files: [],
+ package_type: 'rubygems',
+ project_id: 1,
+ tags: [],
+ updated_at: '2015-12-10',
+ version: '1.0.0',
+ rubygems_metadatum: {
+ author: 'Fake Name',
+ summary: 'My gem',
+ email: 'tanuki@fake.com',
+ },
+};
+
+export const pypiPackage = {
+ created_at: '2015-12-10',
+ id: 5,
+ name: 'PyPiPackage',
+ package_files: [],
+ package_type: 'pypi',
+ project_id: 1,
+ tags: [],
+ updated_at: '2015-12-10',
+ version: '1.0.0',
+};
+
+export const composerPackage = {
+ created_at: '2015-12-10',
+ id: 5,
+ name: 'ComposerPackage',
+ package_files: [],
+ package_type: 'composer',
+ project_id: 1,
+ tags: [],
+ updated_at: '2015-12-10',
+ version: '1.0.0',
+};
+
+export const terraformModule = {
+ created_at: '2015-12-10',
+ id: 2,
+ name: 'Test/system-22',
+ package_type: 'terraform_module',
+ project_path: 'foo/bar/baz',
+ projectPathName: 'foo/bar/baz',
+ project_id: 1,
+ updated_at: '2015-12-10',
+ version: '0.1',
+ versions: [],
+ _links,
+};
+
+export const mockTags = [
+ {
+ name: 'foo-1',
+ },
+ {
+ name: 'foo-2',
+ },
+ {
+ name: 'foo-3',
+ },
+ {
+ name: 'foo-4',
+ },
+];
+
+export const packageList = [mavenPackage, { ...npmPackage, tags: mockTags }, conanPackage];
diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/shared/__snapshots__/package_list_row_spec.js.snap b/spec/frontend/packages_and_registries/infrastructure_registry/components/shared/__snapshots__/package_list_row_spec.js.snap
new file mode 100644
index 00000000000..67c3b8b795a
--- /dev/null
+++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/shared/__snapshots__/package_list_row_spec.js.snap
@@ -0,0 +1,118 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`packages_list_row renders 1`] = `
+<div
+ class="gl-display-flex gl-flex-direction-column gl-border-b-solid gl-border-t-solid gl-border-t-1 gl-border-b-1 gl-border-t-transparent gl-border-b-gray-100"
+ data-qa-selector="package_row"
+>
+ <div
+ class="gl-display-flex gl-align-items-center gl-py-3"
+ >
+ <!---->
+
+ <div
+ class="gl-display-flex gl-xs-flex-direction-column gl-justify-content-space-between gl-align-items-stretch gl-flex-grow-1"
+ >
+ <div
+ class="gl-display-flex gl-flex-direction-column gl-xs-mb-3 gl-min-w-0 gl-flex-grow-1"
+ >
+ <div
+ class="gl-display-flex gl-align-items-center gl-text-body gl-font-weight-bold gl-min-h-6 gl-min-w-0"
+ >
+ <div
+ class="gl-display-flex gl-align-items-center gl-mr-3 gl-min-w-0"
+ >
+ <gl-link-stub
+ class="gl-text-body gl-min-w-0"
+ data-qa-selector="package_link"
+ href="foo"
+ >
+ <gl-truncate-stub
+ position="end"
+ text="Test package"
+ />
+ </gl-link-stub>
+
+ <!---->
+
+ <!---->
+ </div>
+
+ <!---->
+ </div>
+
+ <div
+ class="gl-display-flex gl-align-items-center gl-text-gray-500 gl-min-h-6 gl-min-w-0 gl-flex-grow-1"
+ >
+ <div
+ class="gl-display-flex"
+ >
+ <span>
+ 1.0.0
+ </span>
+
+ <!---->
+
+ <div />
+
+ <package-path-stub
+ path="foo/bar/baz"
+ />
+ </div>
+ </div>
+ </div>
+
+ <div
+ class="gl-display-flex gl-flex-direction-column gl-sm-align-items-flex-end gl-justify-content-space-between gl-text-gray-500 gl-flex-shrink-0"
+ >
+ <div
+ class="gl-display-flex gl-align-items-center gl-sm-text-body gl-sm-font-weight-bold gl-min-h-6"
+ >
+ <publish-method-stub
+ packageentity="[object Object]"
+ />
+ </div>
+
+ <div
+ class="gl-display-flex gl-align-items-center gl-min-h-6"
+ >
+ <span>
+ <gl-sprintf-stub
+ message="Created %{timestamp}"
+ />
+ </span>
+ </div>
+ </div>
+ </div>
+
+ <div
+ class="gl-w-9 gl-display-flex gl-justify-content-end gl-pr-1"
+ >
+ <gl-button-stub
+ aria-label="Remove package"
+ buttontextclasses=""
+ category="secondary"
+ data-testid="action-delete"
+ icon="remove"
+ size="medium"
+ title="Remove package"
+ variant="danger"
+ />
+ </div>
+ </div>
+
+ <div
+ class="gl-display-flex"
+ >
+ <div
+ class="gl-w-7"
+ />
+
+ <!---->
+
+ <div
+ class="gl-w-9"
+ />
+ </div>
+</div>
+`;
diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/infrastructure_icon_and_name_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/shared/infrastructure_icon_and_name_spec.js
index ef26c729691..abb0d23b6e4 100644
--- a/spec/frontend/packages_and_registries/infrastructure_registry/components/infrastructure_icon_and_name_spec.js
+++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/shared/infrastructure_icon_and_name_spec.js
@@ -1,6 +1,6 @@
import { GlIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import InfrastructureIconAndName from '~/packages_and_registries/infrastructure_registry/components/infrastructure_icon_and_name.vue';
+import InfrastructureIconAndName from '~/packages_and_registries/infrastructure_registry/shared/infrastructure_icon_and_name.vue';
describe('InfrastructureIconAndName', () => {
let wrapper;
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
new file mode 100644
index 00000000000..1052fdd1dda
--- /dev/null
+++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/shared/package_list_row_spec.js
@@ -0,0 +1,161 @@
+import { GlLink } from '@gitlab/ui';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
+
+import PackagesListRow from '~/packages_and_registries/infrastructure_registry/shared/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 { PACKAGE_ERROR_STATUS } from '~/packages_and_registries/shared/constants';
+
+import ListItem from '~/vue_shared/components/registry/list_item.vue';
+import { packageList } from '../mock_data';
+
+describe('packages_list_row', () => {
+ let wrapper;
+ let store;
+
+ const [packageWithoutTags, packageWithTags] = packageList;
+
+ const InfrastructureIconAndName = { name: 'InfrastructureIconAndName', template: '<div></div>' };
+
+ const findPackageTags = () => wrapper.findComponent(PackageTags);
+ const findPackagePath = () => wrapper.findComponent(PackagePath);
+ const findDeleteButton = () => wrapper.findByTestId('action-delete');
+ const findInfrastructureIconAndName = () => wrapper.findComponent(InfrastructureIconAndName);
+ const findListItem = () => wrapper.findComponent(ListItem);
+ const findPackageLink = () => wrapper.findComponent(GlLink);
+ const findWarningIcon = () => wrapper.findByTestId('warning-icon');
+
+ const mountComponent = ({
+ isGroup = false,
+ packageEntity = packageWithoutTags,
+ showPackageType = true,
+ disableDelete = false,
+ provide,
+ } = {}) => {
+ wrapper = shallowMountExtended(PackagesListRow, {
+ store,
+ provide,
+ stubs: {
+ ListItem,
+ InfrastructureIconAndName,
+ },
+ propsData: {
+ packageLink: 'foo',
+ packageEntity,
+ isGroup,
+ showPackageType,
+ disableDelete,
+ },
+ directives: {
+ GlTooltip: createMockDirective(),
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ it('renders', () => {
+ mountComponent();
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ describe('tags', () => {
+ it('renders package tags when a package has tags', () => {
+ mountComponent({ isGroup: false, packageEntity: packageWithTags });
+
+ expect(findPackageTags().exists()).toBe(true);
+ });
+
+ it('does not render when there are no tags', () => {
+ mountComponent();
+
+ expect(findPackageTags().exists()).toBe(false);
+ });
+ });
+
+ describe('when is is group', () => {
+ it('has a package path component', () => {
+ mountComponent({ isGroup: true });
+
+ expect(findPackagePath().exists()).toBe(true);
+ expect(findPackagePath().props()).toMatchObject({ path: 'foo/bar/baz' });
+ });
+ });
+
+ describe('showPackageType', () => {
+ it('shows the type when set', () => {
+ mountComponent();
+
+ expect(findInfrastructureIconAndName().exists()).toBe(true);
+ });
+
+ it('does not show the type when not set', () => {
+ mountComponent({ showPackageType: false });
+
+ expect(findInfrastructureIconAndName().exists()).toBe(false);
+ });
+ });
+
+ describe('deleteAvailable', () => {
+ it('does not show when not set', () => {
+ mountComponent({ disableDelete: true });
+
+ expect(findDeleteButton().exists()).toBe(false);
+ });
+ });
+
+ describe('delete button', () => {
+ it('exists and has the correct props', () => {
+ mountComponent({ packageEntity: packageWithoutTags });
+
+ expect(findDeleteButton().exists()).toBe(true);
+ expect(findDeleteButton().attributes()).toMatchObject({
+ icon: 'remove',
+ category: 'secondary',
+ variant: 'danger',
+ title: 'Remove package',
+ });
+ });
+
+ it('emits the packageToDelete event when the delete button is clicked', async () => {
+ mountComponent({ packageEntity: packageWithoutTags });
+
+ findDeleteButton().vm.$emit('click');
+
+ await wrapper.vm.$nextTick();
+ expect(wrapper.emitted('packageToDelete')).toBeTruthy();
+ expect(wrapper.emitted('packageToDelete')[0]).toEqual([packageWithoutTags]);
+ });
+ });
+
+ describe(`when the package is in ${PACKAGE_ERROR_STATUS} status`, () => {
+ beforeEach(() => {
+ mountComponent({ packageEntity: { ...packageWithoutTags, status: PACKAGE_ERROR_STATUS } });
+ });
+
+ it('list item has a disabled prop', () => {
+ expect(findListItem().props('disabled')).toBe(true);
+ });
+
+ it('details link is disabled', () => {
+ expect(findPackageLink().attributes('disabled')).toBe('true');
+ });
+
+ it('has a warning icon', () => {
+ const icon = findWarningIcon();
+ const tooltip = getBinding(icon.element, 'gl-tooltip');
+ expect(icon.props('icon')).toBe('warning');
+ expect(tooltip.value).toMatchObject({
+ title: 'Invalid Package: failed metadata extraction',
+ });
+ });
+
+ it('delete button is disabled', () => {
+ expect(findDeleteButton().props('disabled')).toBe(true);
+ });
+ });
+});
diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/version_row_spec.js.snap b/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/version_row_spec.js.snap
index c95538546c1..7aa42a1f1e5 100644
--- a/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/version_row_spec.js.snap
+++ b/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/version_row_spec.js.snap
@@ -5,7 +5,7 @@ exports[`VersionRow renders 1`] = `
class="gl-display-flex gl-flex-direction-column gl-border-b-solid gl-border-t-solid gl-border-t-1 gl-border-b-1 gl-border-t-transparent gl-border-b-gray-100"
>
<div
- class="gl-display-flex gl-align-items-center gl-py-3 gl-px-5"
+ class="gl-display-flex gl-align-items-center gl-py-3"
>
<!---->
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 d59c3184e4e..6ad6007c9da 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
@@ -2,7 +2,7 @@ import { GlIcon, GlSprintf } from '@gitlab/ui';
import { GlBreakpointInstance } from '@gitlab/ui/dist/utils';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
-import PackageTags from '~/packages/shared/components/package_tags.vue';
+import PackageTags from '~/packages_and_registries/shared/components/package_tags.vue';
import PackageTitle from '~/packages_and_registries/package_registry/components/details/package_title.vue';
import {
PACKAGE_TYPE_CONAN,
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 f7613949fe4..faeca76d746 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,8 +1,8 @@
import { GlLink, GlSprintf, GlTruncate } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
-import PackageTags from '~/packages/shared/components/package_tags.vue';
-import PublishMethod from '~/packages/shared/components/publish_method.vue';
+import PackageTags from '~/packages_and_registries/shared/components/package_tags.vue';
+import PublishMethod from '~/packages_and_registries/shared/components/publish_method.vue';
import VersionRow from '~/packages_and_registries/package_registry/components/details/version_row.vue';
import ListItem from '~/vue_shared/components/registry/list_item.vue';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
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 2f2be797251..165ee962417 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
@@ -6,7 +6,7 @@ exports[`packages_list_row renders 1`] = `
data-qa-selector="package_row"
>
<div
- class="gl-display-flex gl-align-items-center gl-py-3 gl-px-5"
+ class="gl-display-flex gl-align-items-center gl-py-3"
>
<!---->
@@ -77,7 +77,9 @@ exports[`packages_list_row renders 1`] = `
<div
class="gl-display-flex gl-align-items-center gl-min-h-6"
>
- <span>
+ <span
+ data-testid="created-date"
+ >
Created
<timeago-tooltip-stub
cssclass=""
@@ -90,7 +92,7 @@ exports[`packages_list_row renders 1`] = `
</div>
<div
- class="gl-w-9 gl-display-none gl-sm-display-flex gl-justify-content-end gl-pr-1"
+ class="gl-w-9 gl-display-flex gl-justify-content-end gl-pr-1"
>
<gl-button-stub
aria-label="Remove package"
diff --git a/spec/frontend/packages_and_registries/package_registry/components/list/__snapshots__/publish_method_spec.js.snap b/spec/frontend/packages_and_registries/package_registry/components/list/__snapshots__/publish_method_spec.js.snap
index 919dbe25ffe..4407c4a2003 100644
--- a/spec/frontend/packages_and_registries/package_registry/components/list/__snapshots__/publish_method_spec.js.snap
+++ b/spec/frontend/packages_and_registries/package_registry/components/list/__snapshots__/publish_method_spec.js.snap
@@ -37,6 +37,7 @@ exports[`publish_method renders 1`] = `
text="b83d6e391c22777fca1ed3012fce84f633d7fed0"
title="Copy commit SHA"
tooltipplacement="top"
+ variant="default"
/>
</div>
`;
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 a276db104d7..292667ec47c 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
@@ -3,9 +3,11 @@ import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import PackagesListRow from '~/packages_and_registries/package_registry/components/list/package_list_row.vue';
-import PackagePath from '~/packages/shared/components/package_path.vue';
-import PackageTags from '~/packages/shared/components/package_tags.vue';
-import PackageIconAndName from '~/packages/shared/components/package_icon_and_name.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';
import ListItem from '~/vue_shared/components/registry/list_item.vue';
@@ -29,6 +31,9 @@ describe('packages_list_row', () => {
const findPackageLink = () => wrapper.findComponent(GlLink);
const findWarningIcon = () => wrapper.findByTestId('warning-icon');
const findLeftSecondaryInfos = () => wrapper.findByTestId('left-secondary-infos');
+ const findPublishMethod = () => wrapper.findComponent(PublishMethod);
+ const findCreatedDateText = () => wrapper.findByTestId('created-date');
+ const findTimeAgoTooltip = () => wrapper.findComponent(TimeagoTooltip);
const mountComponent = ({
packageEntity = packageWithoutTags,
@@ -153,4 +158,23 @@ describe('packages_list_row', () => {
expect(findPackageIconAndName().text()).toBe(packageWithoutTags.packageType.toLowerCase());
});
});
+
+ describe('right info', () => {
+ it('has publish method component', () => {
+ mountComponent({
+ packageEntity: { ...packageWithoutTags, pipelines: { nodes: packagePipelines() } },
+ });
+
+ expect(findPublishMethod().props('pipeline')).toEqual(packagePipelines()[0]);
+ });
+
+ it('has the created date', () => {
+ mountComponent();
+
+ expect(findCreatedDateText().text()).toMatchInterpolatedText(PackagesListRow.i18n.createdAt);
+ expect(findTimeAgoTooltip().props()).toMatchObject({
+ time: packageData().createdAt,
+ });
+ });
+ });
});
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 de4e9c8ae5b..97978dee909 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
@@ -1,8 +1,8 @@
import { GlKeysetPagination, GlModal, GlSprintf } from '@gitlab/ui';
import { nextTick } from 'vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
-import PackagesListRow from '~/packages/shared/components/package_list_row.vue';
-import PackagesListLoader from '~/packages/shared/components/packages_list_loader.vue';
+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 {
DELETE_PACKAGE_TRACKING_ACTION,
REQUEST_DELETE_PACKAGE_TRACKING_ACTION,
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 bacc748db81..4c23b52b8a2 100644
--- a/spec/frontend/packages_and_registries/package_registry/mock_data.js
+++ b/spec/frontend/packages_and_registries/package_registry/mock_data.js
@@ -16,11 +16,13 @@ export const packagePipelines = (extend) => [
ref: 'master',
sha: 'b83d6e391c22777fca1ed3012fce84f633d7fed0',
project: {
+ id: '1',
name: 'project14',
webUrl: 'http://gdk.test:3000/namespace14/project14',
__typename: 'Project',
},
user: {
+ id: 'user-1',
name: 'Administrator',
},
...extend,
@@ -89,6 +91,7 @@ export const dependencyLinks = () => [
];
export const packageProject = () => ({
+ id: '1',
fullPath: 'gitlab-org/gitlab-test',
webUrl: 'http://gdk.test:3000/gitlab-org/gitlab-test',
__typename: 'Project',
@@ -127,6 +130,7 @@ export const packageData = (extend) => ({
});
export const conanMetadata = () => ({
+ id: 'conan-1',
packageChannel: 'stable',
packageUsername: 'gitlab-org+gitlab-test',
recipe: 'package-8/1.0.0@gitlab-org+gitlab-test/stable',
@@ -179,6 +183,7 @@ export const packageDetailsQuery = (extendPackage) => ({
...nugetMetadata(),
},
project: {
+ id: '1',
path: 'projectPath',
},
tags: {
@@ -270,6 +275,7 @@ export const packageDestroyFileMutationError = () => ({
export const packagesListQuery = ({ type = 'group', extend = {}, extendPagination = {} } = {}) => ({
data: {
[type]: {
+ id: '1',
packages: {
count: 2,
nodes: [
diff --git a/spec/frontend/packages_and_registries/package_registry/components/list/__snapshots__/app_spec.js.snap b/spec/frontend/packages_and_registries/package_registry/pages/__snapshots__/list_spec.js.snap
index 5af75868084..dbe3c70c3cb 100644
--- a/spec/frontend/packages_and_registries/package_registry/components/list/__snapshots__/app_spec.js.snap
+++ b/spec/frontend/packages_and_registries/package_registry/pages/__snapshots__/list_spec.js.snap
@@ -4,7 +4,7 @@ exports[`PackagesListApp renders 1`] = `
<div>
<package-title-stub
count="2"
- helpurl="packageHelpUrl"
+ helpurl="/help/user/packages/index"
/>
<package-search-stub />
@@ -35,17 +35,21 @@ exports[`PackagesListApp renders 1`] = `
class="text-content gl-mx-auto gl-my-0 gl-p-5"
>
<h1
- class="h4"
+ class="gl-font-size-h-display gl-line-height-36 h4"
>
- There are no packages yet
+
+ There are no packages yet
+
</h1>
- <p>
+ <p
+ class="gl-mt-3"
+ >
Learn how to
<b-link-stub
class="gl-link"
event="click"
- href="emptyListHelpUrl"
+ href="/help/user/packages/package_registry/index"
routertag="a"
target="_blank"
>
diff --git a/spec/frontend/packages_and_registries/package_registry/components/list/app_spec.js b/spec/frontend/packages_and_registries/package_registry/pages/list_spec.js
index ad848f367e0..2ac2a6455ef 100644
--- a/spec/frontend/packages_and_registries/package_registry/components/list/app_spec.js
+++ b/spec/frontend/packages_and_registries/package_registry/pages/list_spec.js
@@ -6,7 +6,7 @@ import { nextTick } from 'vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
-import PackageListApp from '~/packages_and_registries/package_registry/components/list/app.vue';
+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';
@@ -16,11 +16,13 @@ import {
PROJECT_RESOURCE_TYPE,
GROUP_RESOURCE_TYPE,
GRAPHQL_PAGE_SIZE,
+ EMPTY_LIST_HELP_URL,
+ PACKAGE_HELP_URL,
} from '~/packages_and_registries/package_registry/constants';
import getPackagesQuery from '~/packages_and_registries/package_registry/graphql/queries/get_packages.query.graphql';
-import { packagesListQuery, packageData, pagination } from '../../mock_data';
+import { packagesListQuery, packageData, pagination } from '../mock_data';
jest.mock('~/lib/utils/common_utils');
jest.mock('~/flash');
@@ -32,9 +34,7 @@ describe('PackagesListApp', () => {
let apolloProvider;
const defaultProvide = {
- packageHelpUrl: 'packageHelpUrl',
emptyListIllustration: 'emptyListIllustration',
- emptyListHelpUrl: 'emptyListHelpUrl',
isGroupPage: true,
fullPath: 'gitlab-org',
};
@@ -66,7 +66,7 @@ describe('PackagesListApp', () => {
const requestHandlers = [[getPackagesQuery, resolver]];
apolloProvider = createMockApollo(requestHandlers);
- wrapper = shallowMountExtended(PackageListApp, {
+ wrapper = shallowMountExtended(ListPage, {
localVue,
apolloProvider,
provide,
@@ -113,7 +113,10 @@ describe('PackagesListApp', () => {
await waitForFirstRequest();
expect(findPackageTitle().exists()).toBe(true);
- expect(findPackageTitle().props('count')).toBe(2);
+ expect(findPackageTitle().props()).toMatchObject({
+ count: 2,
+ helpUrl: PACKAGE_HELP_URL,
+ });
});
describe('search component', () => {
@@ -213,12 +216,12 @@ describe('PackagesListApp', () => {
it('generate the correct empty list link', () => {
const link = findListComponent().findComponent(GlLink);
- expect(link.attributes('href')).toBe(defaultProvide.emptyListHelpUrl);
+ expect(link.attributes('href')).toBe(EMPTY_LIST_HELP_URL);
expect(link.text()).toBe('publish and share your packages');
});
it('includes the right content on the default tab', () => {
- expect(findEmptyState().text()).toContain(PackageListApp.i18n.emptyPageTitle);
+ expect(findEmptyState().text()).toContain(ListPage.i18n.emptyPageTitle);
});
});
@@ -234,8 +237,8 @@ describe('PackagesListApp', () => {
});
it('should show specific empty message', () => {
- expect(findEmptyState().text()).toContain(PackageListApp.i18n.noResultsTitle);
- expect(findEmptyState().text()).toContain(PackageListApp.i18n.widenFilters);
+ expect(findEmptyState().text()).toContain(ListPage.i18n.noResultsTitle);
+ expect(findEmptyState().text()).toContain(ListPage.i18n.widenFilters);
});
});
diff --git a/spec/frontend/packages_and_registries/settings/group/components/__snapshots__/settings_titles_spec.js.snap b/spec/frontend/packages_and_registries/settings/group/components/__snapshots__/settings_titles_spec.js.snap
index f2087733d2b..5b56cb7f74e 100644
--- a/spec/frontend/packages_and_registries/settings/group/components/__snapshots__/settings_titles_spec.js.snap
+++ b/spec/frontend/packages_and_registries/settings/group/components/__snapshots__/settings_titles_spec.js.snap
@@ -3,7 +3,7 @@
exports[`settings_titles renders properly 1`] = `
<div>
<h5
- class="gl-border-b-solid gl-border-b-1 gl-border-gray-200"
+ class="gl-border-b-solid gl-border-b-1 gl-border-gray-200 gl-pb-3"
>
foo
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 d3a970e86eb..f6c1d212b51 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
@@ -1,6 +1,7 @@
-import { GlSprintf, GlLink, GlToggle } from '@gitlab/ui';
-import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { GlSprintf, GlToggle } from '@gitlab/ui';
+import { createLocalVue } from '@vue/test-utils';
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';
@@ -12,14 +13,21 @@ import {
} from '~/packages_and_registries/settings/group/constants';
import updateDependencyProxySettings from '~/packages_and_registries/settings/group/graphql/mutations/update_dependency_proxy_settings.mutation.graphql';
+import updateDependencyProxyImageTtlGroupPolicy from '~/packages_and_registries/settings/group/graphql/mutations/update_dependency_proxy_image_ttl_group_policy.mutation.graphql';
import getGroupPackagesSettingsQuery from '~/packages_and_registries/settings/group/graphql/queries/get_group_packages_settings.query.graphql';
import SettingsBlock from '~/vue_shared/components/settings/settings_block.vue';
-import { updateGroupDependencyProxySettingsOptimisticResponse } from '~/packages_and_registries/settings/group/graphql/utils/optimistic_responses';
+import SettingsTitles from '~/packages_and_registries/settings/group/components/settings_titles.vue';
import {
- dependencyProxySettings,
+ updateGroupDependencyProxySettingsOptimisticResponse,
+ updateDependencyProxyImageTtlGroupPolicyOptimisticResponse,
+} from '~/packages_and_registries/settings/group/graphql/utils/optimistic_responses';
+import {
+ dependencyProxySettings as dependencyProxySettingsMock,
+ dependencyProxyImageTtlPolicy as dependencyProxyImageTtlPolicyMock,
dependencyProxySettingMutationMock,
groupPackageSettingsMock,
- dependencyProxySettingMutationErrorMock,
+ mutationErrorMock,
+ dependencyProxyUpdateTllPolicyMutationMock,
} from '../mock_data';
jest.mock('~/flash');
@@ -30,46 +38,68 @@ const localVue = createLocalVue();
describe('DependencyProxySettings', () => {
let wrapper;
let apolloProvider;
+ let updateSettingsMutationResolver;
+ let updateTtlPoliciesMutationResolver;
const defaultProvide = {
defaultExpanded: false,
groupPath: 'foo_group_path',
+ groupDependencyProxyPath: 'group_dependency_proxy_path',
};
localVue.use(VueApollo);
const mountComponent = ({
provide = defaultProvide,
- mutationResolver = jest.fn().mockResolvedValue(dependencyProxySettingMutationMock()),
isLoading = false,
+ dependencyProxySettings = dependencyProxySettingsMock(),
+ dependencyProxyImageTtlPolicy = dependencyProxyImageTtlPolicyMock(),
} = {}) => {
- const requestHandlers = [[updateDependencyProxySettings, mutationResolver]];
+ const requestHandlers = [
+ [updateDependencyProxySettings, updateSettingsMutationResolver],
+ [updateDependencyProxyImageTtlGroupPolicy, updateTtlPoliciesMutationResolver],
+ ];
apolloProvider = createMockApollo(requestHandlers);
- wrapper = shallowMount(component, {
+ wrapper = shallowMountExtended(component, {
localVue,
apolloProvider,
provide,
propsData: {
- dependencyProxySettings: dependencyProxySettings(),
+ dependencyProxySettings,
+ dependencyProxyImageTtlPolicy,
isLoading,
},
stubs: {
GlSprintf,
+ GlToggle,
SettingsBlock,
},
});
};
+ beforeEach(() => {
+ updateSettingsMutationResolver = jest
+ .fn()
+ .mockResolvedValue(dependencyProxySettingMutationMock());
+ updateTtlPoliciesMutationResolver = jest
+ .fn()
+ .mockResolvedValue(dependencyProxyUpdateTllPolicyMutationMock());
+ });
+
afterEach(() => {
wrapper.destroy();
});
const findSettingsBlock = () => wrapper.findComponent(SettingsBlock);
- const findDescription = () => wrapper.find('[data-testid="description"');
- const findLink = () => wrapper.findComponent(GlLink);
- const findToggle = () => wrapper.findComponent(GlToggle);
+ const findSettingsTitles = () => wrapper.findComponent(SettingsTitles);
+ const findDescription = () => wrapper.findByTestId('description');
+ const findDescriptionLink = () => wrapper.findByTestId('description-link');
+ const findEnableProxyToggle = () => wrapper.findByTestId('dependency-proxy-setting-toggle');
+ const findEnableTtlPoliciesToggle = () =>
+ wrapper.findByTestId('dependency-proxy-ttl-policies-toggle');
+ const findToggleHelpLink = () => wrapper.findByTestId('toggle-help-link');
const fillApolloCache = () => {
apolloProvider.defaultClient.cache.writeQuery({
@@ -81,10 +111,6 @@ describe('DependencyProxySettings', () => {
});
};
- const emitSettingsUpdate = (value = false) => {
- findToggle().vm.$emit('change', value);
- };
-
it('renders a settings block', () => {
mountComponent();
@@ -112,19 +138,93 @@ describe('DependencyProxySettings', () => {
it('has the correct link', () => {
mountComponent();
- expect(findLink().attributes()).toMatchObject({
+ expect(findDescriptionLink().attributes()).toMatchObject({
href: DEPENDENCY_PROXY_DOCS_PATH,
});
- expect(findLink().text()).toBe('Learn more');
+ expect(findDescriptionLink().text()).toBe('Learn more');
+ });
+
+ describe('enable toggle', () => {
+ it('exists', () => {
+ mountComponent();
+
+ expect(findEnableProxyToggle().props()).toMatchObject({
+ label: component.i18n.enabledProxyLabel,
+ });
+ });
+
+ describe('when enabled', () => {
+ beforeEach(() => {
+ mountComponent();
+ });
+
+ it('has the help prop correctly set', () => {
+ expect(findEnableProxyToggle().props()).toMatchObject({
+ help: component.i18n.enabledProxyHelpText,
+ });
+ });
+
+ it('has help text with a link', () => {
+ expect(findEnableProxyToggle().text()).toContain(
+ 'To see the image prefix and what is in the cache, visit the Dependency Proxy',
+ );
+ expect(findToggleHelpLink().attributes()).toMatchObject({
+ href: defaultProvide.groupDependencyProxyPath,
+ });
+ });
+ });
+
+ describe('when disabled', () => {
+ beforeEach(() => {
+ mountComponent({
+ dependencyProxySettings: dependencyProxySettingsMock({ enabled: false }),
+ });
+ });
+
+ it('has the help prop set to empty', () => {
+ expect(findEnableProxyToggle().props()).toMatchObject({
+ help: '',
+ });
+ });
+
+ it('the help text is not visible', () => {
+ expect(findToggleHelpLink().exists()).toBe(false);
+ });
+ });
+ });
+
+ describe('storage settings', () => {
+ it('the component has the settings title', () => {
+ mountComponent();
+
+ expect(findSettingsTitles().props()).toMatchObject({
+ title: component.i18n.storageSettingsTitle,
+ });
+ });
+
+ describe('enable proxy ttl policies', () => {
+ it('exists', () => {
+ mountComponent();
+
+ expect(findEnableTtlPoliciesToggle().props()).toMatchObject({
+ label: component.i18n.ttlPolicyEnabledLabel,
+ help: component.i18n.ttlPolicyEnabledHelpText,
+ });
+ });
+ });
});
- describe('settings update', () => {
+ describe.each`
+ toggleName | toggleFinder | localErrorMock | optimisticResponse
+ ${'enable proxy'} | ${findEnableProxyToggle} | ${dependencyProxySettingMutationMock} | ${updateGroupDependencyProxySettingsOptimisticResponse}
+ ${'enable ttl policies'} | ${findEnableTtlPoliciesToggle} | ${dependencyProxyUpdateTllPolicyMutationMock} | ${updateDependencyProxyImageTtlGroupPolicyOptimisticResponse}
+ `('$toggleName settings update ', ({ optimisticResponse, toggleFinder, localErrorMock }) => {
describe('success state', () => {
it('emits a success event', async () => {
mountComponent();
fillApolloCache();
- emitSettingsUpdate();
+ toggleFinder().vm.$emit('change', false);
await waitForPromises();
@@ -136,26 +236,28 @@ describe('DependencyProxySettings', () => {
fillApolloCache();
- expect(findToggle().props('value')).toBe(true);
+ expect(toggleFinder().props('value')).toBe(true);
- emitSettingsUpdate();
+ toggleFinder().vm.$emit('change', false);
- expect(updateGroupDependencyProxySettingsOptimisticResponse).toHaveBeenCalledWith({
- enabled: false,
- });
+ expect(optimisticResponse).toHaveBeenCalledWith(
+ expect.objectContaining({
+ enabled: false,
+ }),
+ );
});
});
describe('errors', () => {
it('mutation payload with root level errors', async () => {
- const mutationResolver = jest
- .fn()
- .mockResolvedValue(dependencyProxySettingMutationErrorMock);
- mountComponent({ mutationResolver });
+ updateSettingsMutationResolver = jest.fn().mockResolvedValue(mutationErrorMock);
+ updateTtlPoliciesMutationResolver = jest.fn().mockResolvedValue(mutationErrorMock);
+
+ mountComponent();
fillApolloCache();
- emitSettingsUpdate();
+ toggleFinder().vm.$emit('change', false);
await waitForPromises();
@@ -163,14 +265,16 @@ describe('DependencyProxySettings', () => {
});
it.each`
- type | mutationResolver
- ${'local'} | ${jest.fn().mockResolvedValue(dependencyProxySettingMutationMock({ errors: ['foo'] }))}
+ type | mutationResolverMock
+ ${'local'} | ${jest.fn().mockResolvedValue(localErrorMock({ errors: ['foo'] }))}
${'network'} | ${jest.fn().mockRejectedValue()}
- `('mutation payload with $type error', async ({ mutationResolver }) => {
- mountComponent({ mutationResolver });
+ `('mutation payload with $type error', async ({ mutationResolverMock }) => {
+ updateSettingsMutationResolver = mutationResolverMock;
+ updateTtlPoliciesMutationResolver = mutationResolverMock;
+ mountComponent();
fillApolloCache();
- emitSettingsUpdate();
+ toggleFinder().vm.$emit('change', false);
await waitForPromises();
@@ -180,10 +284,16 @@ describe('DependencyProxySettings', () => {
});
describe('when isLoading is true', () => {
- it('disables enable toggle', () => {
+ it('disables enable proxy toggle', () => {
+ mountComponent({ isLoading: true });
+
+ expect(findEnableProxyToggle().props('disabled')).toBe(true);
+ });
+
+ it('disables enable ttl policies toggle', () => {
mountComponent({ isLoading: true });
- expect(findToggle().props('disabled')).toBe(true);
+ expect(findEnableTtlPoliciesToggle().props('disabled')).toBe(true);
});
});
});
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 e4d62bc6a6e..933dac7f5a8 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
@@ -10,7 +10,12 @@ import DependencyProxySettings from '~/packages_and_registries/settings/group/co
import component from '~/packages_and_registries/settings/group/components/group_settings_app.vue';
import getGroupPackagesSettingsQuery from '~/packages_and_registries/settings/group/graphql/queries/get_group_packages_settings.query.graphql';
-import { groupPackageSettingsMock, packageSettings, dependencyProxySettings } from '../mock_data';
+import {
+ groupPackageSettingsMock,
+ packageSettings,
+ dependencyProxySettings,
+ dependencyProxyImageTtlPolicy,
+} from '../mock_data';
jest.mock('~/flash');
@@ -66,11 +71,17 @@ describe('Group Settings App', () => {
await nextTick();
};
+ const packageSettingsProps = { packageSettings: packageSettings() };
+ const dependencyProxyProps = {
+ dependencyProxySettings: dependencyProxySettings(),
+ dependencyProxyImageTtlPolicy: dependencyProxyImageTtlPolicy(),
+ };
+
describe.each`
- finder | entityProp | entityValue | successMessage | errorMessage
- ${findPackageSettings} | ${'packageSettings'} | ${packageSettings()} | ${'Settings saved successfully'} | ${'An error occurred while saving the settings'}
- ${findDependencyProxySettings} | ${'dependencyProxySettings'} | ${dependencyProxySettings()} | ${'Setting saved successfully'} | ${'An error occurred while saving the setting'}
- `('settings blocks', ({ finder, entityProp, entityValue, successMessage, errorMessage }) => {
+ finder | entitySpecificProps | successMessage | errorMessage
+ ${findPackageSettings} | ${packageSettingsProps} | ${'Settings saved successfully'} | ${'An error occurred while saving the settings'}
+ ${findDependencyProxySettings} | ${dependencyProxyProps} | ${'Setting saved successfully'} | ${'An error occurred while saving the setting'}
+ `('settings blocks', ({ finder, entitySpecificProps, successMessage, errorMessage }) => {
beforeEach(() => {
mountComponent();
return waitForApolloQueryAndRender();
@@ -83,7 +94,7 @@ describe('Group Settings App', () => {
it('binds the correctProps', () => {
expect(finder().props()).toMatchObject({
isLoading: false,
- [entityProp]: entityValue,
+ ...entitySpecificProps,
});
});
diff --git a/spec/frontend/packages_and_registries/settings/group/components/settings_titles_spec.js b/spec/frontend/packages_and_registries/settings/group/components/settings_titles_spec.js
index a61edad8685..fcfad4b42b8 100644
--- a/spec/frontend/packages_and_registries/settings/group/components/settings_titles_spec.js
+++ b/spec/frontend/packages_and_registries/settings/group/components/settings_titles_spec.js
@@ -4,15 +4,19 @@ import SettingsTitles from '~/packages_and_registries/settings/group/components/
describe('settings_titles', () => {
let wrapper;
- const mountComponent = () => {
+ const defaultProps = {
+ title: 'foo',
+ subTitle: 'bar',
+ };
+
+ const mountComponent = (propsData = defaultProps) => {
wrapper = shallowMount(SettingsTitles, {
- propsData: {
- title: 'foo',
- subTitle: 'bar',
- },
+ propsData,
});
};
+ const findSubTitle = () => wrapper.find('p');
+
afterEach(() => {
wrapper.destroy();
});
@@ -22,4 +26,10 @@ describe('settings_titles', () => {
expect(wrapper.element).toMatchSnapshot();
});
+
+ it('does not render the subtitle paragraph when no subtitle is passed', () => {
+ mountComponent({ title: defaultProps.title });
+
+ expect(findSubTitle().exists()).toBe(false);
+ });
});
diff --git a/spec/frontend/packages_and_registries/settings/group/graphl/utils/cache_update_spec.js b/spec/frontend/packages_and_registries/settings/group/graphl/utils/cache_update_spec.js
index 9d8504a1124..a5b571a0241 100644
--- a/spec/frontend/packages_and_registries/settings/group/graphl/utils/cache_update_spec.js
+++ b/spec/frontend/packages_and_registries/settings/group/graphl/utils/cache_update_spec.js
@@ -17,6 +17,13 @@ describe('Package and Registries settings group cache updates', () => {
},
};
+ const updateDependencyProxyImageTtlGroupPolicyPayload = {
+ dependencyProxyImageTtlPolicy: {
+ enabled: false,
+ ttl: 45,
+ },
+ };
+
const cacheMock = {
group: {
packageSettings: {
@@ -26,6 +33,10 @@ describe('Package and Registries settings group cache updates', () => {
dependencyProxySetting: {
enabled: true,
},
+ dependencyProxyImageTtlPolicy: {
+ enabled: true,
+ ttl: 45,
+ },
},
};
@@ -42,15 +53,26 @@ describe('Package and Registries settings group cache updates', () => {
});
describe.each`
- updateNamespacePackageSettings | updateDependencyProxySettings
- ${updateNamespacePackageSettingsPayload} | ${updateDependencyProxySettingsPayload}
- ${undefined} | ${updateDependencyProxySettingsPayload}
- ${updateNamespacePackageSettingsPayload} | ${undefined}
- ${undefined} | ${undefined}
+ updateNamespacePackageSettings | updateDependencyProxySettings | updateDependencyProxyImageTtlGroupPolicy
+ ${updateNamespacePackageSettingsPayload} | ${updateDependencyProxySettingsPayload} | ${undefined}
+ ${undefined} | ${updateDependencyProxySettingsPayload} | ${undefined}
+ ${updateNamespacePackageSettingsPayload} | ${undefined} | ${undefined}
+ ${undefined} | ${undefined} | ${updateDependencyProxyImageTtlGroupPolicyPayload}
+ ${undefined} | ${undefined} | ${undefined}
`(
'updateGroupPackageSettings',
- ({ updateNamespacePackageSettings, updateDependencyProxySettings }) => {
- const payload = { data: { updateNamespacePackageSettings, updateDependencyProxySettings } };
+ ({
+ updateNamespacePackageSettings,
+ updateDependencyProxySettings,
+ updateDependencyProxyImageTtlGroupPolicy,
+ }) => {
+ const payload = {
+ data: {
+ updateNamespacePackageSettings,
+ updateDependencyProxySettings,
+ updateDependencyProxyImageTtlGroupPolicy,
+ },
+ };
it('calls readQuery', () => {
updateGroupPackageSettings('foo')(client, payload);
expect(client.readQuery).toHaveBeenCalledWith(queryAndVariables);
@@ -65,6 +87,7 @@ describe('Package and Registries settings group cache updates', () => {
...cacheMock.group,
...payload.data.updateNamespacePackageSettings,
...payload.data.updateDependencyProxySettings,
+ ...payload.data.updateDependencyProxyImageTtlGroupPolicy,
},
},
});
diff --git a/spec/frontend/packages_and_registries/settings/group/graphl/utils/optimistic_responses_spec.js b/spec/frontend/packages_and_registries/settings/group/graphl/utils/optimistic_responses_spec.js
index debeb9aa89c..b4efda3e7b2 100644
--- a/spec/frontend/packages_and_registries/settings/group/graphl/utils/optimistic_responses_spec.js
+++ b/spec/frontend/packages_and_registries/settings/group/graphl/utils/optimistic_responses_spec.js
@@ -1,6 +1,7 @@
import {
updateGroupPackagesSettingsOptimisticResponse,
updateGroupDependencyProxySettingsOptimisticResponse,
+ updateDependencyProxyImageTtlGroupPolicyOptimisticResponse,
} from '~/packages_and_registries/settings/group/graphql/utils/optimistic_responses';
describe('Optimistic responses', () => {
@@ -38,4 +39,22 @@ describe('Optimistic responses', () => {
`);
});
});
+
+ describe('updateDependencyProxyImageTtlGroupPolicyOptimisticResponse', () => {
+ it('returns the correct structure', () => {
+ expect(updateDependencyProxyImageTtlGroupPolicyOptimisticResponse({ foo: 'bar' }))
+ .toMatchInlineSnapshot(`
+ Object {
+ "__typename": "Mutation",
+ "updateDependencyProxyImageTtlGroupPolicy": Object {
+ "__typename": "UpdateDependencyProxyImageTtlGroupPolicyPayload",
+ "dependencyProxyImageTtlPolicy": Object {
+ "foo": "bar",
+ },
+ "errors": Array [],
+ },
+ }
+ `);
+ });
+ });
});
diff --git a/spec/frontend/packages_and_registries/settings/group/mock_data.js b/spec/frontend/packages_and_registries/settings/group/mock_data.js
index 81ba0795b7d..d53446de910 100644
--- a/spec/frontend/packages_and_registries/settings/group/mock_data.js
+++ b/spec/frontend/packages_and_registries/settings/group/mock_data.js
@@ -5,16 +5,25 @@ export const packageSettings = () => ({
genericDuplicateExceptionRegex: '',
});
-export const dependencyProxySettings = () => ({
+export const dependencyProxySettings = (extend) => ({
enabled: true,
+ ...extend,
+});
+
+export const dependencyProxyImageTtlPolicy = (extend) => ({
+ ttl: 90,
+ enabled: true,
+ ...extend,
});
export const groupPackageSettingsMock = {
data: {
group: {
+ id: '1',
fullPath: 'foo_group_path',
packageSettings: packageSettings(),
dependencyProxySetting: dependencyProxySettings(),
+ dependencyProxyImageTtlPolicy: dependencyProxyImageTtlPolicy(),
},
},
};
@@ -44,6 +53,16 @@ export const dependencyProxySettingMutationMock = (override) => ({
},
});
+export const dependencyProxyUpdateTllPolicyMutationMock = (override) => ({
+ data: {
+ updateDependencyProxyImageTtlGroupPolicy: {
+ dependencyProxyImageTtlPolicy: dependencyProxyImageTtlPolicy(),
+ errors: [],
+ ...override,
+ },
+ },
+});
+
export const groupPackageSettingsMutationErrorMock = {
errors: [
{
@@ -68,7 +87,8 @@ export const groupPackageSettingsMutationErrorMock = {
},
],
};
-export const dependencyProxySettingMutationErrorMock = {
+
+export const mutationErrorMock = {
errors: [
{
message: 'Some error',
diff --git a/spec/frontend/packages_and_registries/settings/project/settings/mock_data.js b/spec/frontend/packages_and_registries/settings/project/settings/mock_data.js
index 9778f409010..a56bb75f8ed 100644
--- a/spec/frontend/packages_and_registries/settings/project/settings/mock_data.js
+++ b/spec/frontend/packages_and_registries/settings/project/settings/mock_data.js
@@ -11,6 +11,7 @@ export const containerExpirationPolicyData = () => ({
export const expirationPolicyPayload = (override) => ({
data: {
project: {
+ id: '1',
containerExpirationPolicy: {
...containerExpirationPolicyData(),
...override,
diff --git a/spec/frontend/packages_and_registries/shared/__snapshots__/publish_method_spec.js.snap b/spec/frontend/packages_and_registries/shared/__snapshots__/publish_method_spec.js.snap
new file mode 100644
index 00000000000..5f243799bae
--- /dev/null
+++ b/spec/frontend/packages_and_registries/shared/__snapshots__/publish_method_spec.js.snap
@@ -0,0 +1,43 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`publish_method renders 1`] = `
+<div
+ class="gl-display-flex gl-align-items-center"
+>
+ <gl-icon-stub
+ class="gl-mr-2"
+ name="git-merge"
+ size="16"
+ />
+
+ <span
+ class="gl-mr-2"
+ data-testid="pipeline-ref"
+ >
+ branch-name
+ </span>
+
+ <gl-icon-stub
+ class="gl-mr-2"
+ name="commit"
+ size="16"
+ />
+
+ <gl-link-stub
+ class="gl-mr-2"
+ data-testid="pipeline-sha"
+ href="../commit/sha-baz"
+ >
+ sha-baz
+ </gl-link-stub>
+
+ <clipboard-button-stub
+ category="tertiary"
+ size="small"
+ text="sha-baz"
+ title="Copy commit SHA"
+ tooltipplacement="top"
+ variant="default"
+ />
+</div>
+`;
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
new file mode 100644
index 00000000000..aaca58d21bb
--- /dev/null
+++ b/spec/frontend/packages_and_registries/shared/components/registry_list_spec.js
@@ -0,0 +1,199 @@
+import { GlButton, GlFormCheckbox, GlKeysetPagination } from '@gitlab/ui';
+import { nextTick } from 'vue';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import component from '~/packages_and_registries/shared/components/registry_list.vue';
+
+describe('Registry List', () => {
+ let wrapper;
+
+ const items = [{ id: 'a' }, { id: 'b' }];
+ const defaultPropsData = {
+ title: 'test_title',
+ items,
+ };
+
+ const rowScopedSlot = `
+ <div data-testid="scoped-slot">
+ <button @click="props.selectItem(props.item)">Select</button>
+ <span>{{props.first}}</span>
+ <p>{{props.isSelected(props.item)}}</p>
+ </div>`;
+
+ const mountComponent = ({ propsData = defaultPropsData } = {}) => {
+ wrapper = shallowMountExtended(component, {
+ propsData,
+ scopedSlots: {
+ default: rowScopedSlot,
+ },
+ });
+ };
+
+ const findSelectAll = () => wrapper.findComponent(GlFormCheckbox);
+ const findDeleteSelected = () => wrapper.findComponent(GlButton);
+ const findPagination = () => wrapper.findComponent(GlKeysetPagination);
+ const findScopedSlots = () => wrapper.findAllByTestId('scoped-slot');
+ const findScopedSlotSelectButton = (index) => findScopedSlots().at(index).find('button');
+ 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();
+
+ expect(wrapper.text()).toContain(defaultPropsData.title);
+ });
+
+ describe('select all checkbox', () => {
+ beforeEach(() => {
+ mountComponent();
+ });
+
+ it('exists', () => {
+ expect(findSelectAll().exists()).toBe(true);
+ });
+
+ it('select and unselect all', async () => {
+ // no row is not selected
+ items.forEach((item, index) => {
+ expect(findScopedSlotIsSelectedValue(index).text()).toBe('');
+ });
+
+ // simulate selection
+ findSelectAll().vm.$emit('input', true);
+ await nextTick();
+
+ // all rows selected
+ items.forEach((item, index) => {
+ expect(findScopedSlotIsSelectedValue(index).text()).toBe('true');
+ });
+
+ // simulate de-selection
+ findSelectAll().vm.$emit('input', '');
+ await nextTick();
+
+ // no row is not selected
+ items.forEach((item, index) => {
+ expect(findScopedSlotIsSelectedValue(index).text()).toBe('');
+ });
+ });
+ });
+
+ describe('delete button', () => {
+ it('has the correct text', () => {
+ mountComponent();
+
+ expect(findDeleteSelected().text()).toBe(component.i18n.deleteSelected);
+ });
+
+ it('is hidden when hiddenDelete is true', () => {
+ mountComponent({ propsData: { ...defaultPropsData, hiddenDelete: true } });
+
+ expect(findDeleteSelected().exists()).toBe(false);
+ });
+
+ it('is disabled when isLoading is true', () => {
+ mountComponent({ propsData: { ...defaultPropsData, isLoading: true } });
+
+ expect(findDeleteSelected().props('disabled')).toBe(true);
+ });
+
+ it('is disabled when no row is selected', async () => {
+ mountComponent();
+
+ expect(findDeleteSelected().props('disabled')).toBe(true);
+
+ await findScopedSlotSelectButton(0).trigger('click');
+
+ expect(findDeleteSelected().props('disabled')).toBe(false);
+ });
+
+ it('on click emits the delete event with the selected rows', async () => {
+ mountComponent();
+
+ await findScopedSlotSelectButton(0).trigger('click');
+
+ findDeleteSelected().vm.$emit('click');
+
+ expect(wrapper.emitted('delete')).toEqual([[[items[0]]]]);
+ });
+ });
+ });
+
+ describe('main area', () => {
+ beforeEach(() => {
+ mountComponent();
+ });
+
+ it('renders scopedSlots based on the items props', () => {
+ expect(findScopedSlots()).toHaveLength(items.length);
+ });
+
+ it('populates the scope of the slot correctly', async () => {
+ expect(findScopedSlots().at(0).exists()).toBe(true);
+
+ // it's the first slot
+ expect(findScopedSlotFirstValue(0).text()).toBe('true');
+
+ // item is not selected, falsy is translated to empty string
+ expect(findScopedSlotIsSelectedValue(0).text()).toBe('');
+
+ // find the button with the bound function
+ await findScopedSlotSelectButton(0).trigger('click');
+
+ // the item is selected
+ expect(findScopedSlotIsSelectedValue(0).text()).toBe('true');
+ });
+ });
+
+ describe('footer', () => {
+ let pagination;
+
+ beforeEach(() => {
+ pagination = { hasPreviousPage: false, hasNextPage: true };
+ });
+
+ it('has a pagination', () => {
+ mountComponent({
+ propsData: { ...defaultPropsData, pagination },
+ });
+
+ expect(findPagination().props()).toMatchObject(pagination);
+ });
+
+ it.each`
+ hasPreviousPage | hasNextPage | visible
+ ${true} | ${true} | ${true}
+ ${true} | ${false} | ${true}
+ ${false} | ${true} | ${true}
+ ${false} | ${false} | ${false}
+ `(
+ 'when hasPreviousPage is $hasPreviousPage and hasNextPage is $hasNextPage is $visible that the pagination is shown',
+ ({ hasPreviousPage, hasNextPage, visible }) => {
+ pagination = { hasPreviousPage, hasNextPage };
+ mountComponent({
+ propsData: { ...defaultPropsData, pagination },
+ });
+
+ expect(findPagination().exists()).toBe(visible);
+ },
+ );
+
+ it('pagination emits the correct events', () => {
+ mountComponent({
+ propsData: { ...defaultPropsData, pagination },
+ });
+
+ findPagination().vm.$emit('prev');
+
+ expect(wrapper.emitted('prev-page')).toEqual([[]]);
+
+ findPagination().vm.$emit('next');
+
+ expect(wrapper.emitted('next-page')).toEqual([[]]);
+ });
+ });
+});
diff --git a/spec/frontend/packages_and_registries/shared/package_icon_and_name_spec.js b/spec/frontend/packages_and_registries/shared/package_icon_and_name_spec.js
new file mode 100644
index 00000000000..d6d1970cb12
--- /dev/null
+++ b/spec/frontend/packages_and_registries/shared/package_icon_and_name_spec.js
@@ -0,0 +1,32 @@
+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.find(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/package_path_spec.js b/spec/frontend/packages_and_registries/shared/package_path_spec.js
new file mode 100644
index 00000000000..93425d4f399
--- /dev/null
+++ b/spec/frontend/packages_and_registries/shared/package_path_spec.js
@@ -0,0 +1,104 @@
+import { shallowMount } from '@vue/test-utils';
+import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
+import PackagePath from '~/packages_and_registries/shared/components/package_path.vue';
+
+describe('PackagePath', () => {
+ let wrapper;
+
+ const mountComponent = (propsData = { path: 'foo' }) => {
+ wrapper = shallowMount(PackagePath, {
+ propsData,
+ directives: {
+ GlTooltip: createMockDirective(),
+ },
+ });
+ };
+
+ const BASE_ICON = 'base-icon';
+ const ROOT_LINK = 'root-link';
+ const ROOT_CHEVRON = 'root-chevron';
+ const ELLIPSIS_ICON = 'ellipsis-icon';
+ const ELLIPSIS_CHEVRON = 'ellipsis-chevron';
+ const LEAF_LINK = 'leaf-link';
+
+ 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]}
+ ${'foo/bar/baz'} | ${'/foo/bar'} | ${[ROOT_CHEVRON, LEAF_LINK]} | ${[ELLIPSIS_ICON, ELLIPSIS_CHEVRON]}
+ ${'foo/bar/baz/baz2'} | ${'/foo/bar'} | ${[ROOT_CHEVRON, LEAF_LINK, ELLIPSIS_ICON, ELLIPSIS_CHEVRON]} | ${[]}
+ ${'foo/bar/baz/baz2/bar2'} | ${'/foo/bar'} | ${[ROOT_CHEVRON, LEAF_LINK, ELLIPSIS_ICON, ELLIPSIS_CHEVRON]} | ${[]}
+ `('given path $path', ({ path, shouldExist, shouldNotExist, rootUrl }) => {
+ const pathPieces = path.split('/').slice(1);
+ const hasTooltip = shouldExist.includes(ELLIPSIS_ICON);
+
+ describe('not disabled component', () => {
+ beforeEach(() => {
+ mountComponent({ path });
+ });
+
+ it('should have a base icon', () => {
+ expect(findItem(BASE_ICON).exists()).toBe(true);
+ });
+
+ it('should have a root link', () => {
+ const root = findItem(ROOT_LINK);
+ expect(root.exists()).toBe(true);
+ expect(root.attributes('href')).toBe(rootUrl);
+ });
+
+ if (hasTooltip) {
+ it('should have a tooltip', () => {
+ const tooltip = findTooltip(findItem(ELLIPSIS_ICON));
+ expect(tooltip).toBeDefined();
+ expect(tooltip.value).toMatchObject({
+ title: path,
+ });
+ });
+ }
+
+ if (shouldExist.length) {
+ it.each(shouldExist)(`should have %s`, (element) => {
+ expect(findItem(element).exists()).toBe(true);
+ });
+ }
+
+ if (shouldNotExist.length) {
+ it.each(shouldNotExist)(`should not have %s`, (element) => {
+ expect(findItem(element).exists()).toBe(false);
+ });
+ }
+
+ if (shouldExist.includes(LEAF_LINK)) {
+ it('the last link should be the last piece of the path', () => {
+ const leaf = findItem(LEAF_LINK);
+ expect(leaf.attributes('href')).toBe(`/${path}`);
+ expect(leaf.text()).toBe(pathPieces[pathPieces.length - 1]);
+ });
+ }
+ });
+
+ describe('disabled component', () => {
+ beforeEach(() => {
+ mountComponent({ path, disabled: true });
+ });
+
+ it('root link is disabled', () => {
+ expect(findItem(ROOT_LINK).attributes('disabled')).toBe('true');
+ });
+
+ if (shouldExist.includes(LEAF_LINK)) {
+ it('the last link is disabled', () => {
+ expect(findItem(LEAF_LINK).attributes('disabled')).toBe('true');
+ });
+ }
+ });
+ });
+});
diff --git a/spec/frontend/packages_and_registries/shared/package_tags_spec.js b/spec/frontend/packages_and_registries/shared/package_tags_spec.js
new file mode 100644
index 00000000000..33e96c0775e
--- /dev/null
+++ b/spec/frontend/packages_and_registries/shared/package_tags_spec.js
@@ -0,0 +1,107 @@
+import { mount } from '@vue/test-utils';
+import PackageTags from '~/packages_and_registries/shared/components/package_tags.vue';
+import { mockTags } from 'jest/packages_and_registries/infrastructure_registry/components/mock_data';
+
+describe('PackageTags', () => {
+ let wrapper;
+
+ function createComponent(tags = [], props = {}) {
+ const propsData = {
+ tags,
+ ...props,
+ };
+
+ wrapper = mount(PackageTags, {
+ propsData,
+ });
+ }
+
+ const tagLabel = () => wrapper.find('[data-testid="tagLabel"]');
+ 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();
+
+ expect(tagLabel().exists()).toBe(true);
+ });
+
+ it('hides when hideLabel prop is set to true', () => {
+ createComponent(mockTags, { hideLabel: true });
+
+ expect(tagLabel().exists()).toBe(false);
+ });
+ });
+
+ it('renders the correct number of tags', () => {
+ createComponent(mockTags.slice(0, 2));
+
+ expect(tagBadges()).toHaveLength(2);
+ expect(moreBadge().exists()).toBe(false);
+ });
+
+ it('does not render more than the configured tagDisplayLimit', () => {
+ createComponent(mockTags);
+
+ expect(tagBadges()).toHaveLength(2);
+ });
+
+ it('renders the more tags badge if there are more than the configured limit', () => {
+ createComponent(mockTags);
+
+ expect(tagBadges()).toHaveLength(2);
+ expect(moreBadge().exists()).toBe(true);
+ expect(moreBadge().text()).toContain('2');
+ });
+
+ it('renders the configured tagDisplayLimit when set in props', () => {
+ createComponent(mockTags, { tagDisplayLimit: 1 });
+
+ expect(tagBadges()).toHaveLength(1);
+ expect(moreBadge().exists()).toBe(true);
+ expect(moreBadge().text()).toContain('3');
+ });
+
+ describe('tagBadgeStyle', () => {
+ const defaultStyle = ['badge', 'badge-info', 'gl-display-none'];
+
+ it('shows tag badge when there is only one', () => {
+ createComponent([mockTags[0]]);
+
+ const expectedStyle = [...defaultStyle, 'gl-display-flex', 'gl-ml-3'];
+
+ expect(tagBadges().at(0).classes()).toEqual(expect.arrayContaining(expectedStyle));
+ });
+
+ it('shows tag badge for medium or heigher resolutions', () => {
+ createComponent(mockTags);
+
+ const expectedStyle = [...defaultStyle, 'd-md-flex'];
+
+ expect(tagBadges().at(1).classes()).toEqual(expect.arrayContaining(expectedStyle));
+ });
+
+ it('correctly prepends left and appends right when there is more than one tag', () => {
+ createComponent(mockTags, {
+ tagDisplayLimit: 4,
+ });
+
+ const expectedStyleWithoutAppend = [...defaultStyle, 'd-md-flex'];
+ const expectedStyleWithAppend = [...expectedStyleWithoutAppend, 'gl-mr-2'];
+
+ const allBadges = tagBadges();
+
+ expect(allBadges.at(0).classes()).toEqual(
+ expect.arrayContaining([...expectedStyleWithAppend, 'gl-ml-3']),
+ );
+ expect(allBadges.at(1).classes()).toEqual(expect.arrayContaining(expectedStyleWithAppend));
+ expect(allBadges.at(2).classes()).toEqual(expect.arrayContaining(expectedStyleWithAppend));
+ expect(allBadges.at(3).classes()).toEqual(expect.arrayContaining(expectedStyleWithoutAppend));
+ });
+ });
+});
diff --git a/spec/frontend/packages_and_registries/shared/packages_list_loader_spec.js b/spec/frontend/packages_and_registries/shared/packages_list_loader_spec.js
new file mode 100644
index 00000000000..0005162e0bb
--- /dev/null
+++ b/spec/frontend/packages_and_registries/shared/packages_list_loader_spec.js
@@ -0,0 +1,51 @@
+import { mount } from '@vue/test-utils';
+import PackagesListLoader from '~/packages_and_registries/shared/components/packages_list_loader.vue';
+
+describe('PackagesListLoader', () => {
+ let wrapper;
+
+ const createComponent = (props = {}) => {
+ wrapper = mount(PackagesListLoader, {
+ propsData: {
+ ...props,
+ },
+ });
+ };
+
+ const findDesktopShapes = () => wrapper.find('[data-testid="desktop-loader"]');
+ const findMobileShapes = () => wrapper.find('[data-testid="mobile-loader"]');
+
+ beforeEach(createComponent);
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ describe('desktop loader', () => {
+ it('produces the right loader', () => {
+ expect(findDesktopShapes().findAll('rect[width="1000"]')).toHaveLength(20);
+ });
+
+ it('has the correct classes', () => {
+ expect(findDesktopShapes().classes()).toEqual([
+ 'gl-display-none',
+ 'gl-sm-display-flex',
+ 'gl-flex-direction-column',
+ ]);
+ });
+ });
+
+ describe('mobile loader', () => {
+ it('produces the right loader', () => {
+ expect(findMobileShapes().findAll('rect[height="170"]')).toHaveLength(5);
+ });
+
+ it('has the correct classes', () => {
+ expect(findMobileShapes().classes()).toEqual([
+ 'gl-flex-direction-column',
+ 'gl-sm-display-none',
+ ]);
+ });
+ });
+});
diff --git a/spec/frontend/packages_and_registries/shared/publish_method_spec.js b/spec/frontend/packages_and_registries/shared/publish_method_spec.js
new file mode 100644
index 00000000000..fa8f8f7641a
--- /dev/null
+++ b/spec/frontend/packages_and_registries/shared/publish_method_spec.js
@@ -0,0 +1,50 @@
+import { shallowMount } from '@vue/test-utils';
+import PublishMethod from '~/packages_and_registries/shared/components/publish_method.vue';
+import { packageList } from 'jest/packages_and_registries/infrastructure_registry/components/mock_data';
+
+describe('publish_method', () => {
+ let wrapper;
+
+ const [packageWithoutPipeline, packageWithPipeline] = packageList;
+
+ const findPipelineRef = () => wrapper.find('[data-testid="pipeline-ref"]');
+ const findPipelineSha = () => wrapper.find('[data-testid="pipeline-sha"]');
+ const findManualPublish = () => wrapper.find('[data-testid="manually-published"]');
+
+ const mountComponent = (packageEntity = {}, isGroup = false) => {
+ wrapper = shallowMount(PublishMethod, {
+ propsData: {
+ packageEntity,
+ isGroup,
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ it('renders', () => {
+ mountComponent(packageWithPipeline);
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ describe('pipeline information', () => {
+ it('displays branch and commit when pipeline info exists', () => {
+ mountComponent(packageWithPipeline);
+
+ expect(findPipelineRef().exists()).toBe(true);
+ expect(findPipelineSha().exists()).toBe(true);
+ });
+
+ it('does not show any pipeline details when no information exists', () => {
+ mountComponent(packageWithoutPipeline);
+
+ expect(findPipelineRef().exists()).toBe(false);
+ expect(findPipelineSha().exists()).toBe(false);
+ expect(findManualPublish().exists()).toBe(true);
+ expect(findManualPublish().text()).toBe('Manually Published');
+ });
+ });
+});
diff --git a/spec/frontend/packages_and_registries/shared/utils_spec.js b/spec/frontend/packages_and_registries/shared/utils_spec.js
index bbc8791ca21..962cb2257ce 100644
--- a/spec/frontend/packages_and_registries/shared/utils_spec.js
+++ b/spec/frontend/packages_and_registries/shared/utils_spec.js
@@ -4,8 +4,12 @@ import {
keyValueToFilterToken,
searchArrayToFilterTokens,
extractFilterAndSorting,
+ beautifyPath,
+ getCommitLink,
} from '~/packages_and_registries/shared/utils';
+import { packageList } from 'jest/packages_and_registries/infrastructure_registry/components/mock_data';
+
describe('Packages And Registries shared utils', () => {
describe('getQueryParams', () => {
it('returns an object from a query string, with arrays', () => {
@@ -56,4 +60,30 @@ describe('Packages And Registries shared utils', () => {
},
);
});
+
+ describe('beautifyPath', () => {
+ it('returns a string with spaces around /', () => {
+ expect(beautifyPath('foo/bar')).toBe('foo / bar');
+ });
+ it('does not fail for empty string', () => {
+ expect(beautifyPath()).toBe('');
+ });
+ });
+
+ describe('getCommitLink', () => {
+ it('returns a relative link when isGroup is false', () => {
+ const link = getCommitLink(packageList[0], false);
+
+ expect(link).toContain('../commit');
+ });
+
+ describe('when isGroup is true', () => {
+ it('returns an absolute link matching project path', () => {
+ const mavenPackage = packageList[0];
+ const link = getCommitLink(mavenPackage, true);
+
+ expect(link).toContain(`/${mavenPackage.project_path}/commit`);
+ });
+ });
+ });
});