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

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'spec/frontend/packages_and_registries')
-rw-r--r--spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/image_list_row_spec.js60
-rw-r--r--spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/registry_header_spec.js6
-rw-r--r--spec/frontend/packages_and_registries/container_registry/explorer/pages/details_spec.js24
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/packages_list_spec.js57
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/delete_modal_spec.js71
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/details/package_versions_list_spec.js152
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/list/__snapshots__/package_list_row_spec.js.snap9
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/list/package_list_row_spec.js43
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/list/packages_list_spec.js104
-rw-r--r--spec/frontend/packages_and_registries/package_registry/mock_data.js34
-rw-r--r--spec/frontend/packages_and_registries/package_registry/pages/__snapshots__/list_spec.js.snap54
-rw-r--r--spec/frontend/packages_and_registries/package_registry/pages/details_spec.js33
-rw-r--r--spec/frontend/packages_and_registries/package_registry/pages/list_spec.js158
-rw-r--r--spec/frontend/packages_and_registries/settings/group/components/forwarding_settings_spec.js78
-rw-r--r--spec/frontend/packages_and_registries/settings/group/components/group_settings_app_spec.js17
-rw-r--r--spec/frontend/packages_and_registries/settings/group/components/package_settings_spec.js14
-rw-r--r--spec/frontend/packages_and_registries/settings/group/components/packages_forwarding_settings_spec.js280
-rw-r--r--spec/frontend/packages_and_registries/settings/group/mock_data.js75
-rw-r--r--spec/frontend/packages_and_registries/shared/components/delete_package_modal_spec.js82
19 files changed, 1176 insertions, 175 deletions
diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/image_list_row_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/image_list_row_spec.js
index 0b59fe2d8ce..7da91c4af96 100644
--- a/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/image_list_row_spec.js
+++ b/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/image_list_row_spec.js
@@ -33,7 +33,7 @@ describe('Image List Row', () => {
const findListItemComponent = () => wrapper.findComponent(ListItem);
const findShowFullPathButton = () => wrapper.findComponent(GlButton);
- const mountComponent = (props, features = {}) => {
+ const mountComponent = (props) => {
wrapper = shallowMount(Component, {
stubs: {
RouterLink,
@@ -47,9 +47,6 @@ describe('Image List Row', () => {
},
provide: {
config: {},
- glFeatures: {
- ...features,
- },
},
directives: {
GlTooltip: createMockDirective(),
@@ -88,23 +85,43 @@ describe('Image List Row', () => {
});
describe('image title and path', () => {
- it('contains a link to the details page', () => {
+ it('renders shortened name of image and contains a link to the details page', () => {
mountComponent();
const link = findDetailsLink();
- expect(link.text()).toBe(item.path);
- expect(findDetailsLink().props('to')).toMatchObject({
+ expect(link.text()).toBe('gitlab-test/rails-12009');
+
+ expect(link.props('to')).toMatchObject({
name: 'details',
params: {
id: getIdFromGraphQLId(item.id),
},
});
+
+ expect(findShowFullPathButton().exists()).toBe(true);
});
it('when the image has no name lists the path', () => {
mountComponent({ item: { ...item, name: '' } });
+ expect(findDetailsLink().text()).toBe('gitlab-test');
+ });
+
+ it('clicking on shortened name of image hides the button & shows full path', async () => {
+ mountComponent();
+
+ const trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
+ const mockFocusFn = jest.fn();
+ wrapper.vm.$refs.imageName.$el.focus = mockFocusFn;
+
+ await findShowFullPathButton().trigger('click');
+
+ expect(findShowFullPathButton().exists()).toBe(false);
expect(findDetailsLink().text()).toBe(item.path);
+ expect(mockFocusFn).toHaveBeenCalled();
+ expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_show_full_path', {
+ label: 'registry_image_list',
+ });
});
it('contains a clipboard button', () => {
@@ -149,35 +166,6 @@ describe('Image List Row', () => {
expect(findClipboardButton().attributes('disabled')).toBe('true');
});
});
-
- describe('when containerRegistryShowShortenedPath feature enabled', () => {
- let trackingSpy;
-
- beforeEach(() => {
- mountComponent({}, { containerRegistryShowShortenedPath: true });
- trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
- });
-
- it('renders shortened name of image', () => {
- expect(findShowFullPathButton().exists()).toBe(true);
- expect(findDetailsLink().text()).toBe('gitlab-test/rails-12009');
- });
-
- it('clicking on shortened name of image hides the button & shows full path', async () => {
- const btn = findShowFullPathButton();
- const mockFocusFn = jest.fn();
- wrapper.vm.$refs.imageName.$el.focus = mockFocusFn;
-
- await btn.trigger('click');
-
- expect(findShowFullPathButton().exists()).toBe(false);
- expect(findDetailsLink().text()).toBe(item.path);
- expect(mockFocusFn).toHaveBeenCalled();
- expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_show_full_path', {
- label: 'registry_image_list',
- });
- });
- });
});
describe('delete button', () => {
diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/registry_header_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/registry_header_spec.js
index e6d81d4a28f..bcc8e41fce8 100644
--- a/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/registry_header_spec.js
+++ b/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/registry_header_spec.js
@@ -58,6 +58,12 @@ describe('registry_header', () => {
describe('sub header parts', () => {
describe('images count', () => {
+ it('does not exist', async () => {
+ await mountComponent({ imagesCount: 0 });
+
+ expect(findImagesCountSubHeader().exists()).toBe(false);
+ });
+
it('exists', async () => {
await mountComponent({ imagesCount: 1 });
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 ee6470a9df8..310398b01cf 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
@@ -26,6 +26,7 @@ import {
import deleteContainerRepositoryTagsMutation from '~/packages_and_registries/container_registry/explorer/graphql/mutations/delete_container_repository_tags.mutation.graphql';
import getContainerRepositoryDetailsQuery from '~/packages_and_registries/container_registry/explorer/graphql/queries/get_container_repository_details.query.graphql';
import getContainerRepositoryTagsQuery from '~/packages_and_registries/container_registry/explorer/graphql/queries/get_container_repository_tags.query.graphql';
+import getContainerRepositoriesDetails from '~/packages_and_registries/container_registry/explorer/graphql/queries/get_container_repositories_details.query.graphql';
import component from '~/packages_and_registries/container_registry/explorer/pages/details.vue';
import Tracking from '~/tracking';
@@ -34,6 +35,7 @@ import {
graphQLImageDetailsMock,
graphQLDeleteImageRepositoryTagsMock,
graphQLDeleteImageRepositoryTagImportingErrorMock,
+ graphQLProjectImageRepositoriesDetailsMock,
containerRepositoryMock,
graphQLEmptyImageDetailsMock,
tagsMock,
@@ -64,6 +66,9 @@ describe('Details Page', () => {
const defaultConfig = {
noContainersImage: 'noContainersImage',
+ projectListUrl: 'projectListUrl',
+ groupListUrl: 'groupListUrl',
+ isGroupPage: false,
};
const cleanTags = tagsMock.map((t) => {
@@ -81,7 +86,8 @@ describe('Details Page', () => {
const mountComponent = ({
resolver = jest.fn().mockResolvedValue(graphQLImageDetailsMock()),
mutationResolver = jest.fn().mockResolvedValue(graphQLDeleteImageRepositoryTagsMock),
- tagsResolver = jest.fn().mockResolvedValue(graphQLImageDetailsMock(imageTagsMock)),
+ tagsResolver = jest.fn().mockResolvedValue(graphQLImageDetailsMock(imageTagsMock())),
+ detailsResolver = jest.fn().mockResolvedValue(graphQLProjectImageRepositoriesDetailsMock),
options,
config = defaultConfig,
} = {}) => {
@@ -91,6 +97,7 @@ describe('Details Page', () => {
[getContainerRepositoryDetailsQuery, resolver],
[deleteContainerRepositoryTagsMutation, mutationResolver],
[getContainerRepositoryTagsQuery, tagsResolver],
+ [getContainerRepositoriesDetails, detailsResolver],
];
apolloProvider = createMockApollo(requestHandlers);
@@ -256,11 +263,13 @@ describe('Details Page', () => {
describe('confirmDelete event', () => {
let mutationResolver;
let tagsResolver;
+ let detailsResolver;
beforeEach(() => {
mutationResolver = jest.fn().mockResolvedValue(graphQLDeleteImageRepositoryTagsMock);
- tagsResolver = jest.fn().mockResolvedValue(graphQLImageDetailsMock(imageTagsMock));
- mountComponent({ mutationResolver, tagsResolver });
+ tagsResolver = jest.fn().mockResolvedValue(graphQLImageDetailsMock(imageTagsMock()));
+ detailsResolver = jest.fn().mockResolvedValue(graphQLProjectImageRepositoriesDetailsMock);
+ mountComponent({ mutationResolver, tagsResolver, detailsResolver });
return waitForApolloRequestRender();
});
@@ -280,6 +289,7 @@ describe('Details Page', () => {
await waitForPromises();
expect(tagsResolver).toHaveBeenCalled();
+ expect(detailsResolver).toHaveBeenCalled();
});
});
@@ -298,6 +308,7 @@ describe('Details Page', () => {
await waitForPromises();
expect(tagsResolver).toHaveBeenCalled();
+ expect(detailsResolver).toHaveBeenCalled();
});
});
});
@@ -359,14 +370,16 @@ describe('Details Page', () => {
describe('importing repository error', () => {
let mutationResolver;
let tagsResolver;
+ let detailsResolver;
beforeEach(async () => {
mutationResolver = jest
.fn()
.mockResolvedValue(graphQLDeleteImageRepositoryTagImportingErrorMock);
- tagsResolver = jest.fn().mockResolvedValue(graphQLImageDetailsMock(imageTagsMock));
+ tagsResolver = jest.fn().mockResolvedValue(graphQLImageDetailsMock(imageTagsMock()));
+ detailsResolver = jest.fn().mockResolvedValue(graphQLProjectImageRepositoriesDetailsMock);
- mountComponent({ mutationResolver, tagsResolver });
+ mountComponent({ mutationResolver, tagsResolver, detailsResolver });
await waitForApolloRequestRender();
});
@@ -378,6 +391,7 @@ describe('Details Page', () => {
await waitForPromises();
expect(tagsResolver).toHaveBeenCalled();
+ expect(detailsResolver).toHaveBeenCalled();
const deleteAlert = findDeleteAlert();
expect(deleteAlert.exists()).toBe(true);
diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/packages_list_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/packages_list_spec.js
index fb5ee4e6884..0164d92ce34 100644
--- a/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/packages_list_spec.js
+++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/packages_list_spec.js
@@ -1,12 +1,13 @@
-import { GlTable, GlPagination, GlModal } from '@gitlab/ui';
+import { GlTable, GlPagination } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
-import Vue, { nextTick } from 'vue';
+import Vue from 'vue';
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 DeletePackageModal from '~/packages_and_registries/shared/components/delete_package_modal.vue';
import { TRACKING_ACTIONS } from '~/packages_and_registries/shared/constants';
import { TRACK_CATEGORY } from '~/packages_and_registries/infrastructure_registry/shared/constants';
import Tracking from '~/tracking';
@@ -22,7 +23,7 @@ describe('packages_list', () => {
const findPackagesListLoader = () => wrapper.findComponent(PackagesListLoader);
const findPackageListPagination = () => wrapper.findComponent(GlPagination);
- const findPackageListDeleteModal = () => wrapper.findComponent(GlModal);
+ const findPackageListDeleteModal = () => wrapper.findComponent(DeletePackageModal);
const findEmptySlot = () => wrapper.findComponent(EmptySlotStub);
const findPackagesListRow = () => wrapper.findComponent(PackagesListRow);
@@ -65,7 +66,7 @@ describe('packages_list', () => {
stubs: {
...stubChildren(PackagesList),
GlTable,
- GlModal,
+ DeletePackageModal,
},
...options,
});
@@ -109,52 +110,38 @@ describe('packages_list', () => {
expect(sorting.exists()).toBe(true);
});
- it('contains a modal component', () => {
- const sorting = findPackageListDeleteModal();
- expect(sorting.exists()).toBe(true);
+ it("doesn't contain a modal component", () => {
+ expect(findPackageListDeleteModal().props('itemToBeDeleted')).toBeNull();
});
});
describe('when the user can destroy the package', () => {
- beforeEach(() => {
+ let itemToBeDeleted;
+
+ beforeEach(async () => {
mountComponent();
+ itemToBeDeleted = last(packageList);
+ await findPackagesListRow().vm.$emit('packageToDelete', itemToBeDeleted);
});
- it('setItemToBeDeleted sets itemToBeDeleted and open the modal', async () => {
- const mockModalShow = jest.spyOn(wrapper.vm.$refs.packageListDeleteModal, 'show');
- const item = last(wrapper.vm.list);
-
- findPackagesListRow().vm.$emit('packageToDelete', item);
-
- await nextTick();
- expect(wrapper.vm.itemToBeDeleted).toEqual(item);
- expect(mockModalShow).toHaveBeenCalled();
+ afterEach(() => {
+ itemToBeDeleted = null;
});
- it('deleteItemConfirmation resets itemToBeDeleted', () => {
- // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
- // eslint-disable-next-line no-restricted-syntax
- wrapper.setData({ itemToBeDeleted: 1 });
- wrapper.vm.deleteItemConfirmation();
- expect(wrapper.vm.itemToBeDeleted).toEqual(null);
+ it('passes itemToBeDeleted to the modal', () => {
+ expect(findPackageListDeleteModal().props('itemToBeDeleted')).toStrictEqual(itemToBeDeleted);
});
it('deleteItemConfirmation emit package:delete', async () => {
- const itemToBeDeleted = { id: 2 };
- // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
- // eslint-disable-next-line no-restricted-syntax
- wrapper.setData({ itemToBeDeleted });
- wrapper.vm.deleteItemConfirmation();
- await nextTick();
+ await findPackageListDeleteModal().vm.$emit('ok');
+
expect(wrapper.emitted('package:delete')[0]).toEqual([itemToBeDeleted]);
});
- it('deleteItemCanceled resets itemToBeDeleted', () => {
- // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
- // eslint-disable-next-line no-restricted-syntax
- wrapper.setData({ itemToBeDeleted: 1 });
- wrapper.vm.deleteItemCanceled();
- expect(wrapper.vm.itemToBeDeleted).toEqual(null);
+ it.each(['ok', 'cancel'])('resets itemToBeDeleted when modal emits %s', async (event) => {
+ await findPackageListDeleteModal().vm.$emit(event);
+
+ expect(findPackageListDeleteModal().props('itemToBeDeleted')).toBeNull();
});
});
diff --git a/spec/frontend/packages_and_registries/package_registry/components/delete_modal_spec.js b/spec/frontend/packages_and_registries/package_registry/components/delete_modal_spec.js
new file mode 100644
index 00000000000..e0e26434680
--- /dev/null
+++ b/spec/frontend/packages_and_registries/package_registry/components/delete_modal_spec.js
@@ -0,0 +1,71 @@
+import { GlModal as RealGlModal } from '@gitlab/ui';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import { stubComponent } from 'helpers/stub_component';
+import DeleteModal from '~/packages_and_registries/package_registry/components/delete_modal.vue';
+
+const GlModal = stubComponent(RealGlModal, {
+ methods: {
+ show: jest.fn(),
+ },
+});
+
+describe('DeleteModal', () => {
+ let wrapper;
+
+ const defaultItemsToBeDeleted = [
+ {
+ name: 'package 01',
+ },
+ {
+ name: 'package 02',
+ },
+ ];
+
+ const findModal = () => wrapper.findComponent(GlModal);
+
+ const mountComponent = ({ itemsToBeDeleted = defaultItemsToBeDeleted } = {}) => {
+ wrapper = shallowMountExtended(DeleteModal, {
+ propsData: {
+ itemsToBeDeleted,
+ },
+ stubs: {
+ GlModal,
+ },
+ });
+ };
+
+ beforeEach(() => {
+ mountComponent();
+ });
+
+ it('passes title prop', () => {
+ expect(findModal().props('title')).toMatchInterpolatedText('Delete packages');
+ });
+
+ it('passes actionPrimary prop', () => {
+ expect(findModal().props('actionPrimary')).toStrictEqual({
+ text: 'Permanently delete',
+ attributes: [{ variant: 'danger' }, { category: 'primary' }],
+ });
+ });
+
+ it('renders description', () => {
+ expect(findModal().text()).toContain(
+ 'You are about to delete 2 packages. This operation is irreversible.',
+ );
+ });
+
+ it('emits confirm when primary event is emitted', () => {
+ expect(wrapper.emitted('confirm')).toBeUndefined();
+
+ findModal().vm.$emit('primary');
+
+ expect(wrapper.emitted('confirm')).toHaveLength(1);
+ });
+
+ it('show calls gl-modal show', () => {
+ findModal().vm.show();
+
+ expect(GlModal.methods.show).toHaveBeenCalled();
+ });
+});
diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/package_versions_list_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/package_versions_list_spec.js
new file mode 100644
index 00000000000..f0fa9592419
--- /dev/null
+++ b/spec/frontend/packages_and_registries/package_registry/components/details/package_versions_list_spec.js
@@ -0,0 +1,152 @@
+import { GlKeysetPagination } from '@gitlab/ui';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import PackageVersionsList from '~/packages_and_registries/package_registry/components/details/package_versions_list.vue';
+import PackagesListLoader from '~/packages_and_registries/shared/components/packages_list_loader.vue';
+import VersionRow from '~/packages_and_registries/package_registry/components/details/version_row.vue';
+import { packageData } from '../../mock_data';
+
+describe('PackageVersionsList', () => {
+ let wrapper;
+
+ const EmptySlotStub = { name: 'empty-slot-stub', template: '<div>empty message</div>' };
+ const packageList = [
+ packageData({
+ name: 'version 1',
+ }),
+ packageData({
+ id: `gid://gitlab/Packages::Package/112`,
+ name: 'version 2',
+ }),
+ ];
+
+ const uiElements = {
+ findLoader: () => wrapper.findComponent(PackagesListLoader),
+ findListPagination: () => wrapper.findComponent(GlKeysetPagination),
+ findEmptySlot: () => wrapper.findComponent(EmptySlotStub),
+ findListRow: () => wrapper.findAllComponents(VersionRow),
+ };
+ const mountComponent = (props) => {
+ wrapper = shallowMountExtended(PackageVersionsList, {
+ propsData: {
+ versions: packageList,
+ pageInfo: {},
+ isLoading: false,
+ ...props,
+ },
+ slots: {
+ 'empty-state': EmptySlotStub,
+ },
+ });
+ };
+
+ describe('when list is loading', () => {
+ beforeEach(() => {
+ mountComponent({ isLoading: true, versions: [] });
+ });
+ it('displays loader', () => {
+ expect(uiElements.findLoader().exists()).toBe(true);
+ });
+
+ it('does not display rows', () => {
+ expect(uiElements.findListRow().exists()).toBe(false);
+ });
+
+ it('does not display empty slot message', () => {
+ expect(uiElements.findEmptySlot().exists()).toBe(false);
+ });
+
+ it('does not display pagination', () => {
+ expect(uiElements.findListPagination().exists()).toBe(false);
+ });
+ });
+
+ describe('when list is loaded and has no data', () => {
+ beforeEach(() => {
+ mountComponent({ isLoading: false, versions: [] });
+ });
+
+ it('displays empty slot message', () => {
+ expect(uiElements.findEmptySlot().exists()).toBe(true);
+ });
+
+ it('does not display loader', () => {
+ expect(uiElements.findLoader().exists()).toBe(false);
+ });
+
+ it('does not display rows', () => {
+ expect(uiElements.findListRow().exists()).toBe(false);
+ });
+
+ it('does not display pagination', () => {
+ expect(uiElements.findListPagination().exists()).toBe(false);
+ });
+ });
+
+ describe('when list is loaded with data', () => {
+ beforeEach(() => {
+ mountComponent();
+ });
+
+ it('displays package version rows', () => {
+ expect(uiElements.findListRow().exists()).toEqual(true);
+ expect(uiElements.findListRow()).toHaveLength(packageList.length);
+ });
+
+ it('binds the correct props', () => {
+ expect(uiElements.findListRow().at(0).props()).toMatchObject({
+ packageEntity: expect.objectContaining(packageList[0]),
+ });
+
+ expect(uiElements.findListRow().at(1).props()).toMatchObject({
+ packageEntity: expect.objectContaining(packageList[1]),
+ });
+ });
+
+ describe('pagination display', () => {
+ it('does not display pagination if there is no previous or next page', () => {
+ expect(uiElements.findListPagination().exists()).toBe(false);
+ });
+
+ it('displays pagination if pageInfo.hasNextPage is true', async () => {
+ await wrapper.setProps({ pageInfo: { hasNextPage: true } });
+ expect(uiElements.findListPagination().exists()).toBe(true);
+ });
+
+ it('displays pagination if pageInfo.hasPreviousPage is true', async () => {
+ await wrapper.setProps({ pageInfo: { hasPreviousPage: true } });
+ expect(uiElements.findListPagination().exists()).toBe(true);
+ });
+
+ it('displays pagination if both pageInfo.hasNextPage and pageInfo.hasPreviousPage are true', async () => {
+ await wrapper.setProps({ pageInfo: { hasNextPage: true, hasPreviousPage: true } });
+ expect(uiElements.findListPagination().exists()).toBe(true);
+ });
+ });
+
+ it('does not display loader', () => {
+ expect(uiElements.findLoader().exists()).toBe(false);
+ });
+
+ it('does not display empty slot message', () => {
+ expect(uiElements.findEmptySlot().exists()).toBe(false);
+ });
+ });
+
+ describe('when user interacts with pagination', () => {
+ beforeEach(() => {
+ mountComponent({ pageInfo: { hasNextPage: true } });
+ });
+
+ it('emits prev-page event when paginator emits prev event', () => {
+ uiElements.findListPagination().vm.$emit('prev');
+
+ expect(wrapper.emitted('prev-page')).toHaveLength(1);
+ });
+
+ it('emits next-page when paginator emits next event', () => {
+ uiElements.findListPagination().vm.$emit('next');
+
+ expect(wrapper.emitted('next-page')).toHaveLength(1);
+ });
+ });
+});
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 5be05ddf629..a7de751aadd 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
@@ -8,7 +8,14 @@ exports[`packages_list_row renders 1`] = `
<div
class="gl-display-flex gl-align-items-center gl-py-3"
>
- <!---->
+ <div
+ class="gl-w-7 gl-display-flex gl-justify-content-start gl-pl-2"
+ >
+ <gl-form-checkbox-stub
+ class="gl-m-0"
+ id="2"
+ />
+ </div>
<div
class="gl-display-flex gl-xs-flex-direction-column gl-justify-content-space-between gl-align-items-stretch gl-flex-grow-1"
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 b5a512b8806..913b4f5926f 100644
--- a/spec/frontend/packages_and_registries/package_registry/components/list/package_list_row_spec.js
+++ b/spec/frontend/packages_and_registries/package_registry/components/list/package_list_row_spec.js
@@ -1,4 +1,4 @@
-import { GlSprintf } from '@gitlab/ui';
+import { GlFormCheckbox, GlSprintf } from '@gitlab/ui';
import Vue, { nextTick } from 'vue';
import VueRouter from 'vue-router';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
@@ -40,9 +40,11 @@ describe('packages_list_row', () => {
const findPublishMethod = () => wrapper.findComponent(PublishMethod);
const findCreatedDateText = () => wrapper.findByTestId('created-date');
const findTimeAgoTooltip = () => wrapper.findComponent(TimeagoTooltip);
+ const findBulkDeleteAction = () => wrapper.findComponent(GlFormCheckbox);
const mountComponent = ({
packageEntity = packageWithoutTags,
+ selected = false,
provide = defaultProvide,
} = {}) => {
wrapper = shallowMountExtended(PackagesListRow, {
@@ -53,6 +55,7 @@ describe('packages_list_row', () => {
},
propsData: {
packageEntity,
+ selected,
},
directives: {
GlTooltip: createMockDirective(),
@@ -117,14 +120,13 @@ describe('packages_list_row', () => {
});
});
- it('emits the packageToDelete event when the delete button is clicked', async () => {
+ it('emits the delete event when the delete button is clicked', async () => {
mountComponent({ packageEntity: packageWithoutTags });
findDeleteDropdown().vm.$emit('click');
await nextTick();
- expect(wrapper.emitted('packageToDelete')).toHaveLength(1);
- expect(wrapper.emitted('packageToDelete')[0]).toEqual([packageWithoutTags]);
+ expect(wrapper.emitted('delete')).toHaveLength(1);
});
});
@@ -151,6 +153,39 @@ describe('packages_list_row', () => {
});
});
+ describe('left action template', () => {
+ it('does not render checkbox if not permitted', () => {
+ mountComponent({
+ packageEntity: { ...packageWithoutTags, canDestroy: false },
+ });
+
+ expect(findBulkDeleteAction().exists()).toBe(false);
+ });
+
+ it('renders checkbox', () => {
+ mountComponent();
+
+ expect(findBulkDeleteAction().exists()).toBe(true);
+ expect(findBulkDeleteAction().attributes('checked')).toBeUndefined();
+ });
+
+ it('emits select when checked', () => {
+ mountComponent();
+
+ findBulkDeleteAction().vm.$emit('change');
+
+ expect(wrapper.emitted('select')).toHaveLength(1);
+ });
+
+ it('renders checkbox in selected state if selected', () => {
+ mountComponent({
+ selected: true,
+ });
+
+ expect(findBulkDeleteAction().attributes('checked')).toBe('true');
+ });
+ });
+
describe('secondary left info', () => {
it('has the package version', () => {
mountComponent();
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 3e3607a361c..7cc5bea0f7a 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,10 @@
-import { GlAlert, GlKeysetPagination, GlModal, GlSprintf } from '@gitlab/ui';
+import { GlAlert, GlSprintf } from '@gitlab/ui';
import { nextTick } from 'vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import PackagesListRow from '~/packages_and_registries/package_registry/components/list/package_list_row.vue';
import PackagesListLoader from '~/packages_and_registries/shared/components/packages_list_loader.vue';
+import DeletePackageModal from '~/packages_and_registries/shared/components/delete_package_modal.vue';
+import RegistryList from '~/packages_and_registries/shared/components/registry_list.vue';
import {
DELETE_PACKAGE_TRACKING_ACTION,
REQUEST_DELETE_PACKAGE_TRACKING_ACTION,
@@ -35,16 +37,11 @@ describe('packages_list', () => {
};
const EmptySlotStub = { name: 'empty-slot-stub', template: '<div>bar</div>' };
- const GlModalStub = {
- name: GlModal.name,
- template: '<div><slot></slot></div>',
- methods: { show: jest.fn() },
- };
const findPackagesListLoader = () => wrapper.findComponent(PackagesListLoader);
- const findPackageListPagination = () => wrapper.findComponent(GlKeysetPagination);
- const findPackageListDeleteModal = () => wrapper.findComponent(GlModalStub);
+ const findPackageListDeleteModal = () => wrapper.findComponent(DeletePackageModal);
const findEmptySlot = () => wrapper.findComponent(EmptySlotStub);
+ const findRegistryList = () => wrapper.findComponent(RegistryList);
const findPackagesListRow = () => wrapper.findComponent(PackagesListRow);
const findErrorPackageAlert = () => wrapper.findComponent(GlAlert);
@@ -55,8 +52,9 @@ describe('packages_list', () => {
...props,
},
stubs: {
- GlModal: GlModalStub,
+ DeletePackageModal,
GlSprintf,
+ RegistryList,
},
slots: {
'empty-state': EmptySlotStub,
@@ -64,10 +62,6 @@ describe('packages_list', () => {
});
};
- beforeEach(() => {
- GlModalStub.methods.show.mockReset();
- });
-
afterEach(() => {
wrapper.destroy();
});
@@ -81,12 +75,12 @@ describe('packages_list', () => {
expect(findPackagesListLoader().exists()).toBe(true);
});
- it('does not show the rows', () => {
- expect(findPackagesListRow().exists()).toBe(false);
+ it('does not show the registry list', () => {
+ expect(findRegistryList().exists()).toBe(false);
});
- it('does not show the pagination', () => {
- expect(findPackageListPagination().exists()).toBe(false);
+ it('does not show the rows', () => {
+ expect(findPackagesListRow().exists()).toBe(false);
});
});
@@ -99,22 +93,29 @@ describe('packages_list', () => {
expect(findPackagesListLoader().exists()).toBe(false);
});
+ it('shows the registry list', () => {
+ expect(findRegistryList().exists()).toBe(true);
+ });
+
+ it('shows the registry list with the right props', () => {
+ expect(findRegistryList().props()).toMatchObject({
+ title: '2 packages',
+ items: defaultProps.list,
+ pagination: defaultProps.pageInfo,
+ isLoading: false,
+ });
+ });
+
it('shows the rows', () => {
expect(findPackagesListRow().exists()).toBe(true);
});
});
describe('layout', () => {
- it('contains a pagination component', () => {
- mountComponent({ pageInfo: { hasPreviousPage: true } });
-
- expect(findPackageListPagination().exists()).toBe(true);
- });
-
- it('contains a modal component', () => {
+ it("doesn't contain a visible modal component", () => {
mountComponent();
- expect(findPackageListDeleteModal().exists()).toBe(true);
+ expect(findPackageListDeleteModal().props('itemToBeDeleted')).toBeNull();
});
it('does not have an error alert displayed', () => {
@@ -125,31 +126,46 @@ describe('packages_list', () => {
});
describe('when the user can destroy the package', () => {
- beforeEach(() => {
+ beforeEach(async () => {
mountComponent();
- findPackagesListRow().vm.$emit('packageToDelete', firstPackage);
- return nextTick();
+ await findPackagesListRow().vm.$emit('delete', firstPackage);
});
- it('deleting a package opens the modal', () => {
- expect(findPackageListDeleteModal().text()).toContain(firstPackage.name);
+ it('passes itemToBeDeleted to the modal', () => {
+ expect(findPackageListDeleteModal().props('itemToBeDeleted')).toStrictEqual(firstPackage);
});
- it('confirming on the modal emits package:delete', async () => {
- findPackageListDeleteModal().vm.$emit('ok');
-
- await nextTick();
+ it('emits package:delete when modal confirms', async () => {
+ await findPackageListDeleteModal().vm.$emit('ok');
expect(wrapper.emitted('package:delete')[0]).toEqual([firstPackage]);
});
- it('closing the modal resets itemToBeDeleted', async () => {
- // triggering the v-model
- findPackageListDeleteModal().vm.$emit('input', false);
+ it.each(['ok', 'cancel'])('resets itemToBeDeleted when modal emits %s', async (event) => {
+ await findPackageListDeleteModal().vm.$emit(event);
- await nextTick();
+ expect(findPackageListDeleteModal().props('itemToBeDeleted')).toBeNull();
+ });
+ });
+
+ describe('when the user can bulk destroy packages', () => {
+ beforeEach(() => {
+ mountComponent();
+ });
+
+ it('passes itemToBeDeleted to the modal when there is only one package', async () => {
+ await findRegistryList().vm.$emit('delete', [firstPackage]);
+
+ expect(findPackageListDeleteModal().props('itemToBeDeleted')).toStrictEqual(firstPackage);
+ expect(wrapper.emitted('delete')).toBeUndefined();
+ });
+
+ it('emits delete when there is more than one package', () => {
+ const items = [firstPackage, secondPackage];
+ findRegistryList().vm.$emit('delete', items);
- expect(findPackageListDeleteModal().text()).not.toContain(firstPackage.name);
+ expect(wrapper.emitted('delete')).toHaveLength(1);
+ expect(wrapper.emitted('delete')[0]).toEqual([items]);
});
});
@@ -196,15 +212,15 @@ describe('packages_list', () => {
});
it('emits prev-page events when the prev event is fired', () => {
- findPackageListPagination().vm.$emit('prev');
+ findRegistryList().vm.$emit('prev-page');
- expect(wrapper.emitted('prev-page')).toEqual([[]]);
+ expect(wrapper.emitted('prev-page')).toHaveLength(1);
});
it('emits next-page events when the next event is fired', () => {
- findPackageListPagination().vm.$emit('next');
+ findRegistryList().vm.$emit('next-page');
- expect(wrapper.emitted('next-page')).toEqual([[]]);
+ expect(wrapper.emitted('next-page')).toHaveLength(1);
});
});
@@ -215,7 +231,7 @@ describe('packages_list', () => {
beforeEach(() => {
eventSpy = jest.spyOn(Tracking, 'event');
mountComponent();
- findPackagesListRow().vm.$emit('packageToDelete', firstPackage);
+ findPackagesListRow().vm.$emit('delete', firstPackage);
return nextTick();
});
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 c2b6fb734d6..f36c5923532 100644
--- a/spec/frontend/packages_and_registries/package_registry/mock_data.js
+++ b/spec/frontend/packages_and_registries/package_registry/mock_data.js
@@ -233,6 +233,12 @@ export const packageDetailsQuery = (extendPackage) => ({
},
versions: {
nodes: packageVersions(),
+ pageInfo: {
+ hasNextPage: true,
+ hasPreviousPage: false,
+ endCursor: 'endCursor',
+ startCursor: 'startCursor',
+ },
__typename: 'PackageConnection',
},
dependencyLinks: {
@@ -288,6 +294,33 @@ export const packageDestroyMutation = () => ({
},
});
+export const packagesDestroyMutation = () => ({
+ data: {
+ destroyPackages: {
+ errors: [],
+ },
+ },
+});
+
+export const packagesDestroyMutationError = () => ({
+ data: {
+ destroyPackages: null,
+ },
+ errors: [
+ {
+ message:
+ "The resource that you are attempting to access does not exist or you don't have permission to perform this action",
+ locations: [
+ {
+ line: 2,
+ column: 3,
+ },
+ ],
+ path: ['destroyPackages'],
+ },
+ ],
+});
+
export const packageDestroyMutationError = () => ({
data: {
destroyPackage: null,
@@ -314,6 +347,7 @@ export const packageDestroyFilesMutation = () => ({
},
},
});
+
export const packageDestroyFilesMutationError = () => ({
data: {
destroyPackageFiles: null,
diff --git a/spec/frontend/packages_and_registries/package_registry/pages/__snapshots__/list_spec.js.snap b/spec/frontend/packages_and_registries/package_registry/pages/__snapshots__/list_spec.js.snap
index 17905a8db2d..c2fecf87428 100644
--- a/spec/frontend/packages_and_registries/package_registry/pages/__snapshots__/list_spec.js.snap
+++ b/spec/frontend/packages_and_registries/package_registry/pages/__snapshots__/list_spec.js.snap
@@ -2,12 +2,62 @@
exports[`PackagesListApp renders 1`] = `
<div>
+ <!---->
+
+ <gl-card-stub
+ bodyclass="gl-display-flex gl-p-0!"
+ class="gl-px-8 gl-py-6 gl-line-height-20 gl-mt-3"
+ footerclass=""
+ headerclass=""
+ >
+ <!---->
+
+ <div
+ class="gl-banner-content"
+ >
+ <h2
+ class="gl-banner-title"
+ >
+ Help us learn about your registry migration needs
+ </h2>
+
+ <p>
+ If you are interested in migrating packages from your private registry to the GitLab Package Registry, take our survey and tell us more about your needs.
+ </p>
+
+ <gl-button-stub
+ buttontextclasses=""
+ category="primary"
+ data-testid="gl-banner-primary-button"
+ href="https://gitlab.fra1.qualtrics.com/jfe/form/SV_cHomH9FPzOaiDTU"
+ icon=""
+ size="medium"
+ variant="confirm"
+ >
+ Take survey
+ </gl-button-stub>
+
+ </div>
+
+ <gl-button-stub
+ aria-label="Close banner"
+ buttontextclasses=""
+ category="tertiary"
+ class="gl-banner-close"
+ icon="close"
+ size="small"
+ variant="default"
+ />
+ </gl-card-stub>
+
<package-title-stub
count="2"
helpurl="/help/user/packages/index"
/>
- <package-search-stub />
+ <package-search-stub
+ class="gl-mb-5"
+ />
<div>
<section
@@ -69,5 +119,7 @@ exports[`PackagesListApp renders 1`] = `
</div>
</section>
</div>
+
+ <div />
</div>
`;
diff --git a/spec/frontend/packages_and_registries/package_registry/pages/details_spec.js b/spec/frontend/packages_and_registries/package_registry/pages/details_spec.js
index a32e76a132e..f942a334f40 100644
--- a/spec/frontend/packages_and_registries/package_registry/pages/details_spec.js
+++ b/spec/frontend/packages_and_registries/package_registry/pages/details_spec.js
@@ -15,8 +15,8 @@ import InstallationCommands from '~/packages_and_registries/package_registry/com
import PackageFiles from '~/packages_and_registries/package_registry/components/details/package_files.vue';
import PackageHistory from '~/packages_and_registries/package_registry/components/details/package_history.vue';
import PackageTitle from '~/packages_and_registries/package_registry/components/details/package_title.vue';
-import VersionRow from '~/packages_and_registries/package_registry/components/details/version_row.vue';
import DeletePackage from '~/packages_and_registries/package_registry/components/functional/delete_package.vue';
+import PackageVersionsList from '~/packages_and_registries/package_registry/components/details/package_versions_list.vue';
import {
FETCH_PACKAGE_DETAILS_ERROR_MESSAGE,
PACKAGE_TYPE_COMPOSER,
@@ -99,6 +99,7 @@ describe('PackagesApp', () => {
GlSprintf,
GlTabs,
GlTab,
+ PackageVersionsList,
},
mocks: {
$route: {
@@ -120,8 +121,7 @@ describe('PackagesApp', () => {
const findPackageFiles = () => wrapper.findComponent(PackageFiles);
const findDeleteFileModal = () => wrapper.findByTestId('delete-file-modal');
const findDeleteFilesModal = () => wrapper.findByTestId('delete-files-modal');
- const findVersionRows = () => wrapper.findAllComponents(VersionRow);
- const noVersionsMessage = () => wrapper.findByTestId('no-versions-message');
+ const findVersionsList = () => wrapper.findComponent(PackageVersionsList);
const findDependenciesCountBadge = () => wrapper.findComponent(GlBadge);
const findNoDependenciesMessage = () => wrapper.findByTestId('no-dependencies-message');
const findDependencyRows = () => wrapper.findAllComponents(DependencyRow);
@@ -558,38 +558,23 @@ describe('PackagesApp', () => {
});
describe('versions', () => {
- it('displays the correct version count when the package has versions', async () => {
+ it('displays versions list when the package has versions', async () => {
createComponent();
await waitForPromises();
- expect(findVersionRows()).toHaveLength(packageVersions().length);
+ expect(findVersionsList()).toBeDefined();
});
it('binds the correct props', async () => {
- const [versionPackage] = packageVersions();
- // eslint-disable-next-line no-underscore-dangle
- delete versionPackage.__typename;
- delete versionPackage.tags;
-
- createComponent();
-
+ const versionNodes = packageVersions();
+ createComponent({ packageEntity: { versions: { nodes: versionNodes } } });
await waitForPromises();
- expect(findVersionRows().at(0).props()).toMatchObject({
- packageEntity: expect.objectContaining(versionPackage),
+ expect(findVersionsList().props()).toMatchObject({
+ versions: expect.arrayContaining(versionNodes),
});
});
-
- it('displays the no versions message when there are none', async () => {
- createComponent({
- resolver: jest.fn().mockResolvedValue(packageDetailsQuery({ versions: { nodes: [] } })),
- });
-
- await waitForPromises();
-
- expect(noVersionsMessage().exists()).toBe(true);
- });
});
describe('dependency links', () => {
it('does not show the dependency links for a non nuget package', async () => {
diff --git a/spec/frontend/packages_and_registries/package_registry/pages/list_spec.js b/spec/frontend/packages_and_registries/package_registry/pages/list_spec.js
index 0e74fbbc6d9..abdb875e839 100644
--- a/spec/frontend/packages_and_registries/package_registry/pages/list_spec.js
+++ b/spec/frontend/packages_and_registries/package_registry/pages/list_spec.js
@@ -1,30 +1,39 @@
-import { GlEmptyState, GlSprintf, GlLink } from '@gitlab/ui';
+import { GlAlert, GlBanner, GlEmptyState, GlSprintf, GlLink } from '@gitlab/ui';
import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
-
+import * as utils from '~/lib/utils/common_utils';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
+import { stubComponent } from 'helpers/stub_component';
import ListPage from '~/packages_and_registries/package_registry/pages/list.vue';
import PackageTitle from '~/packages_and_registries/package_registry/components/list/package_title.vue';
import PackageSearch from '~/packages_and_registries/package_registry/components/list/package_search.vue';
import OriginalPackageList from '~/packages_and_registries/package_registry/components/list/packages_list.vue';
import DeletePackage from '~/packages_and_registries/package_registry/components/functional/delete_package.vue';
-
+import DeleteModal from '~/packages_and_registries/package_registry/components/delete_modal.vue';
import {
PROJECT_RESOURCE_TYPE,
GROUP_RESOURCE_TYPE,
GRAPHQL_PAGE_SIZE,
+ HIDE_PACKAGE_MIGRATION_SURVEY_COOKIE,
EMPTY_LIST_HELP_URL,
PACKAGE_HELP_URL,
+ DELETE_PACKAGES_ERROR_MESSAGE,
+ DELETE_PACKAGES_SUCCESS_MESSAGE,
} from '~/packages_and_registries/package_registry/constants';
import getPackagesQuery from '~/packages_and_registries/package_registry/graphql/queries/get_packages.query.graphql';
+import destroyPackagesMutation from '~/packages_and_registries/package_registry/graphql/mutations/destroy_packages.mutation.graphql';
+import {
+ packagesListQuery,
+ packageData,
+ pagination,
+ packagesDestroyMutation,
+ packagesDestroyMutationError,
+} from '../mock_data';
-import { packagesListQuery, packageData, pagination } from '../mock_data';
-
-jest.mock('~/lib/utils/common_utils');
jest.mock('~/flash');
describe('PackagesListApp', () => {
@@ -49,31 +58,44 @@ describe('PackagesListApp', () => {
filters: { packageName: 'foo', packageType: 'CONAN' },
};
+ const findAlert = () => wrapper.findComponent(GlAlert);
+ const findBanner = () => wrapper.findComponent(GlBanner);
const findPackageTitle = () => wrapper.findComponent(PackageTitle);
const findSearch = () => wrapper.findComponent(PackageSearch);
const findListComponent = () => wrapper.findComponent(PackageList);
const findEmptyState = () => wrapper.findComponent(GlEmptyState);
const findDeletePackage = () => wrapper.findComponent(DeletePackage);
+ const findDeletePackagesModal = () => wrapper.findComponent(DeleteModal);
const mountComponent = ({
resolver = jest.fn().mockResolvedValue(packagesListQuery()),
+ mutationResolver,
provide = defaultProvide,
} = {}) => {
Vue.use(VueApollo);
- const requestHandlers = [[getPackagesQuery, resolver]];
+ const requestHandlers = [
+ [getPackagesQuery, resolver],
+ [destroyPackagesMutation, mutationResolver],
+ ];
apolloProvider = createMockApollo(requestHandlers);
wrapper = shallowMountExtended(ListPage, {
apolloProvider,
provide,
stubs: {
+ GlBanner,
GlEmptyState,
GlLoadingIcon,
GlSprintf,
GlLink,
PackageList,
DeletePackage,
+ DeleteModal: stubComponent(DeleteModal, {
+ methods: {
+ show: jest.fn(),
+ },
+ }),
},
});
};
@@ -116,6 +138,70 @@ describe('PackagesListApp', () => {
});
});
+ describe('package migration survey banner', () => {
+ describe('with no cookie set', () => {
+ beforeEach(() => {
+ utils.setCookie = jest.fn();
+
+ mountComponent();
+ });
+
+ it('displays the banner', () => {
+ expect(findBanner().exists()).toBe(true);
+ });
+
+ it('does not call setCookie', () => {
+ expect(utils.setCookie).not.toHaveBeenCalled();
+ });
+
+ describe('when the close button is clicked', () => {
+ beforeEach(() => {
+ findBanner().vm.$emit('close');
+ });
+
+ it('sets the dismissed cookie', () => {
+ expect(utils.setCookie).toHaveBeenCalledWith(
+ HIDE_PACKAGE_MIGRATION_SURVEY_COOKIE,
+ 'true',
+ );
+ });
+
+ it('does not display the banner', () => {
+ expect(findBanner().exists()).toBe(false);
+ });
+ });
+
+ describe('when the primary button is clicked', () => {
+ beforeEach(() => {
+ findBanner().vm.$emit('primary');
+ });
+
+ it('sets the dismissed cookie', () => {
+ expect(utils.setCookie).toHaveBeenCalledWith(
+ HIDE_PACKAGE_MIGRATION_SURVEY_COOKIE,
+ 'true',
+ );
+ });
+
+ it('does not display the banner', () => {
+ expect(findBanner().exists()).toBe(false);
+ });
+ });
+ });
+
+ describe('with the dismissed cookie set', () => {
+ beforeEach(() => {
+ jest.spyOn(utils, 'getCookie').mockReturnValue('true');
+
+ mountComponent();
+ });
+
+ it('does not display the banner', () => {
+ expect(findBanner().exists()).toBe(false);
+ });
+ });
+ });
+
describe('search component', () => {
it('exists', () => {
mountComponent();
@@ -282,4 +368,62 @@ describe('PackagesListApp', () => {
expect(findListComponent().props('isLoading')).toBe(false);
});
});
+
+ describe('bulk delete package', () => {
+ const items = [{ id: '1' }, { id: '2' }];
+
+ it('deletePackage is bound to package-list package:delete event', async () => {
+ mountComponent();
+
+ await waitForFirstRequest();
+
+ findListComponent().vm.$emit('delete', [{ id: '1' }, { id: '2' }]);
+
+ await waitForPromises();
+
+ expect(findDeletePackagesModal().props('itemsToBeDeleted')).toEqual(items);
+ });
+
+ it('calls mutation with the right values and shows success alert', async () => {
+ const mutationResolver = jest.fn().mockResolvedValue(packagesDestroyMutation());
+ mountComponent({
+ mutationResolver,
+ });
+
+ await waitForFirstRequest();
+
+ findListComponent().vm.$emit('delete', items);
+
+ findDeletePackagesModal().vm.$emit('confirm');
+
+ expect(mutationResolver).toHaveBeenCalledWith({
+ ids: items.map((item) => item.id),
+ });
+
+ await waitForPromises();
+
+ expect(findAlert().exists()).toBe(true);
+ expect(findAlert().props('variant')).toEqual('success');
+ expect(findAlert().text()).toMatchInterpolatedText(DELETE_PACKAGES_SUCCESS_MESSAGE);
+ });
+
+ it('on error shows danger alert', async () => {
+ const mutationResolver = jest.fn().mockResolvedValue(packagesDestroyMutationError());
+ mountComponent({
+ mutationResolver,
+ });
+
+ await waitForFirstRequest();
+
+ findListComponent().vm.$emit('delete', items);
+
+ findDeletePackagesModal().vm.$emit('confirm');
+
+ await waitForPromises();
+
+ expect(findAlert().exists()).toBe(true);
+ expect(findAlert().props('variant')).toEqual('danger');
+ expect(findAlert().text()).toMatchInterpolatedText(DELETE_PACKAGES_ERROR_MESSAGE);
+ });
+ });
});
diff --git a/spec/frontend/packages_and_registries/settings/group/components/forwarding_settings_spec.js b/spec/frontend/packages_and_registries/settings/group/components/forwarding_settings_spec.js
new file mode 100644
index 00000000000..8f229182fe5
--- /dev/null
+++ b/spec/frontend/packages_and_registries/settings/group/components/forwarding_settings_spec.js
@@ -0,0 +1,78 @@
+import { GlFormGroup, GlSprintf } from '@gitlab/ui';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import component from '~/packages_and_registries/settings/group/components/forwarding_settings.vue';
+
+describe('Forwarding Settings', () => {
+ let wrapper;
+
+ const defaultProps = {
+ disabled: false,
+ forwarding: false,
+ label: 'label',
+ lockForwarding: false,
+ modelNames: {
+ forwarding: 'forwardField',
+ lockForwarding: 'lockForwardingField',
+ isLocked: 'lockedField',
+ },
+ };
+
+ const mountComponent = (propsData = defaultProps) => {
+ wrapper = shallowMountExtended(component, {
+ propsData,
+ stubs: {
+ GlSprintf,
+ },
+ });
+ };
+
+ const findFormGroup = () => wrapper.findComponent(GlFormGroup);
+ const findForwardingCheckbox = () => wrapper.findByTestId('forwarding-checkbox');
+ const findLockForwardingCheckbox = () => wrapper.findByTestId('lock-forwarding-checkbox');
+
+ it('has a form group', () => {
+ mountComponent();
+
+ expect(findFormGroup().exists()).toBe(true);
+ expect(findFormGroup().attributes()).toMatchObject({
+ label: defaultProps.label,
+ });
+ });
+
+ describe.each`
+ name | finder | label | extraProps | field
+ ${'forwarding'} | ${findForwardingCheckbox} | ${'Forward label package requests'} | ${{ forwarding: true }} | ${defaultProps.modelNames.forwarding}
+ ${'lock forwarding'} | ${findLockForwardingCheckbox} | ${'Enforce label setting for all subgroups'} | ${{ lockForwarding: true }} | ${defaultProps.modelNames.lockForwarding}
+ `('$name checkbox', ({ name, finder, label, extraProps, field }) => {
+ it('is rendered', () => {
+ mountComponent();
+ expect(finder().exists()).toBe(true);
+ expect(finder().text()).toMatchInterpolatedText(label);
+ expect(finder().attributes('disabled')).toBeUndefined();
+ expect(finder().attributes('checked')).toBeUndefined();
+ });
+
+ it(`is checked when ${name} set`, () => {
+ mountComponent({ ...defaultProps, ...extraProps });
+
+ expect(finder().attributes('checked')).toBe('true');
+ });
+
+ it(`emits an update event with field ${field} set`, () => {
+ mountComponent();
+
+ finder().vm.$emit('change', true);
+
+ expect(wrapper.emitted('update')).toStrictEqual([[field, true]]);
+ });
+ });
+
+ describe('disabled', () => {
+ it('disables both checkboxes', () => {
+ mountComponent({ ...defaultProps, disabled: true });
+
+ expect(findForwardingCheckbox().attributes('disabled')).toEqual('true');
+ expect(findLockForwardingCheckbox().attributes('disabled')).toEqual('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 31fc3ad419c..7edc321867c 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
@@ -7,6 +7,7 @@ import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import PackagesSettings from '~/packages_and_registries/settings/group/components/packages_settings.vue';
import DependencyProxySettings from '~/packages_and_registries/settings/group/components/dependency_proxy_settings.vue';
+import PackagesForwardingSettings from '~/packages_and_registries/settings/group/components/packages_forwarding_settings.vue';
import component from '~/packages_and_registries/settings/group/components/group_settings_app.vue';
@@ -60,6 +61,7 @@ describe('Group Settings App', () => {
const findAlert = () => wrapper.findComponent(GlAlert);
const findPackageSettings = () => wrapper.findComponent(PackagesSettings);
+ const findPackageForwardingSettings = () => wrapper.findComponent(PackagesForwardingSettings);
const findDependencyProxySettings = () => wrapper.findComponent(DependencyProxySettings);
const waitForApolloQueryAndRender = async () => {
@@ -67,16 +69,18 @@ describe('Group Settings App', () => {
await nextTick();
};
- const packageSettingsProps = { packageSettings: packageSettings() };
+ const packageSettingsProps = { packageSettings };
+ const packageForwardingSettingsProps = { forwardSettings: { ...packageSettings } };
const dependencyProxyProps = {
dependencyProxySettings: dependencyProxySettings(),
dependencyProxyImageTtlPolicy: dependencyProxyImageTtlPolicy(),
};
describe.each`
- 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'}
+ finder | entitySpecificProps | successMessage | errorMessage
+ ${findPackageSettings} | ${packageSettingsProps} | ${'Settings saved successfully'} | ${'An error occurred while saving the settings'}
+ ${findPackageForwardingSettings} | ${packageForwardingSettingsProps} | ${'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();
@@ -88,10 +92,7 @@ describe('Group Settings App', () => {
});
it('binds the correctProps', () => {
- expect(finder().props()).toMatchObject({
- isLoading: false,
- ...entitySpecificProps,
- });
+ expect(finder().props()).toMatchObject(entitySpecificProps);
});
describe('success event', () => {
diff --git a/spec/frontend/packages_and_registries/settings/group/components/package_settings_spec.js b/spec/frontend/packages_and_registries/settings/group/components/package_settings_spec.js
index 13eba39ec8c..807f332f4d3 100644
--- a/spec/frontend/packages_and_registries/settings/group/components/package_settings_spec.js
+++ b/spec/frontend/packages_and_registries/settings/group/components/package_settings_spec.js
@@ -48,7 +48,7 @@ describe('Packages Settings', () => {
apolloProvider,
provide: defaultProvide,
propsData: {
- packageSettings: packageSettings(),
+ packageSettings,
},
stubs: {
SettingsBlock,
@@ -83,7 +83,7 @@ describe('Packages Settings', () => {
};
const emitMavenSettingsUpdate = (override) => {
- findGenericDuplicatedSettingsExceptionsInput().vm.$emit('update', {
+ findMavenDuplicatedSettingsExceptionsInput().vm.$emit('update', {
mavenDuplicateExceptionRegex: ')',
...override,
});
@@ -117,7 +117,7 @@ describe('Packages Settings', () => {
it('renders toggle', () => {
mountComponent({ mountFn: mountExtended });
- const { mavenDuplicatesAllowed } = packageSettings();
+ const { mavenDuplicatesAllowed } = packageSettings;
expect(findMavenDuplicatedSettingsToggle().exists()).toBe(true);
@@ -132,7 +132,7 @@ describe('Packages Settings', () => {
it('renders ExceptionsInput and assigns duplication allowness and exception props', () => {
mountComponent({ mountFn: mountExtended });
- const { mavenDuplicatesAllowed, mavenDuplicateExceptionRegex } = packageSettings();
+ const { mavenDuplicatesAllowed, mavenDuplicateExceptionRegex } = packageSettings;
expect(findMavenDuplicatedSettingsExceptionsInput().exists()).toBe(true);
@@ -170,7 +170,7 @@ describe('Packages Settings', () => {
it('renders toggle', () => {
mountComponent({ mountFn: mountExtended });
- const { genericDuplicatesAllowed } = packageSettings();
+ const { genericDuplicatesAllowed } = packageSettings;
expect(findGenericDuplicatedSettingsToggle().exists()).toBe(true);
expect(findGenericDuplicatedSettingsToggle().props()).toMatchObject({
@@ -184,7 +184,7 @@ describe('Packages Settings', () => {
it('renders ExceptionsInput and assigns duplication allowness and exception props', async () => {
mountComponent({ mountFn: mountExtended });
- const { genericDuplicatesAllowed, genericDuplicateExceptionRegex } = packageSettings();
+ const { genericDuplicatesAllowed, genericDuplicateExceptionRegex } = packageSettings;
expect(findGenericDuplicatedSettingsExceptionsInput().props()).toMatchObject({
duplicatesAllowed: genericDuplicatesAllowed,
@@ -239,7 +239,7 @@ describe('Packages Settings', () => {
emitMavenSettingsUpdate({ mavenDuplicateExceptionRegex });
expect(updateGroupPackagesSettingsOptimisticResponse).toHaveBeenCalledWith({
- ...packageSettings(),
+ ...packageSettings,
mavenDuplicateExceptionRegex,
});
});
diff --git a/spec/frontend/packages_and_registries/settings/group/components/packages_forwarding_settings_spec.js b/spec/frontend/packages_and_registries/settings/group/components/packages_forwarding_settings_spec.js
new file mode 100644
index 00000000000..a0b257a9496
--- /dev/null
+++ b/spec/frontend/packages_and_registries/settings/group/components/packages_forwarding_settings_spec.js
@@ -0,0 +1,280 @@
+import Vue from 'vue';
+import { GlButton } from '@gitlab/ui';
+import VueApollo from 'vue-apollo';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import component from '~/packages_and_registries/settings/group/components/packages_forwarding_settings.vue';
+import {
+ PACKAGE_FORWARDING_SETTINGS_DESCRIPTION,
+ PACKAGE_FORWARDING_SETTINGS_HEADER,
+} from '~/packages_and_registries/settings/group/constants';
+
+import updateNamespacePackageSettings from '~/packages_and_registries/settings/group/graphql/mutations/update_group_packages_settings.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 { updateGroupPackagesSettingsOptimisticResponse } from '~/packages_and_registries/settings/group/graphql/utils/optimistic_responses';
+import {
+ packageSettings,
+ packageForwardingSettings,
+ groupPackageSettingsMock,
+ groupPackageForwardSettingsMutationMock,
+ mutationErrorMock,
+ npmProps,
+ pypiProps,
+ mavenProps,
+} from '../mock_data';
+
+jest.mock('~/flash');
+jest.mock('~/packages_and_registries/settings/group/graphql/utils/optimistic_responses');
+
+describe('Packages Forwarding Settings', () => {
+ let wrapper;
+ let apolloProvider;
+ const mutationResolverFn = jest.fn().mockResolvedValue(groupPackageForwardSettingsMutationMock());
+
+ const defaultProvide = {
+ groupPath: 'foo_group_path',
+ };
+
+ const mountComponent = ({
+ forwardSettings = { ...packageSettings },
+ features = {},
+ mutationResolver = mutationResolverFn,
+ } = {}) => {
+ Vue.use(VueApollo);
+
+ const requestHandlers = [[updateNamespacePackageSettings, mutationResolver]];
+
+ apolloProvider = createMockApollo(requestHandlers);
+
+ wrapper = shallowMountExtended(component, {
+ apolloProvider,
+ provide: {
+ ...defaultProvide,
+ glFeatures: {
+ ...features,
+ },
+ },
+ propsData: {
+ forwardSettings,
+ },
+ stubs: {
+ SettingsBlock,
+ },
+ });
+ };
+
+ const findSettingsBlock = () => wrapper.findComponent(SettingsBlock);
+ const findForm = () => wrapper.find('form');
+ const findSubmitButton = () => findForm().findComponent(GlButton);
+ const findDescription = () => wrapper.findByTestId('description');
+ const findMavenForwardingSettings = () => wrapper.findByTestId('maven');
+ const findNpmForwardingSettings = () => wrapper.findByTestId('npm');
+ const findPyPiForwardingSettings = () => wrapper.findByTestId('pypi');
+
+ const fillApolloCache = () => {
+ apolloProvider.defaultClient.cache.writeQuery({
+ query: getGroupPackagesSettingsQuery,
+ variables: {
+ fullPath: defaultProvide.groupPath,
+ },
+ ...groupPackageSettingsMock,
+ });
+ };
+
+ const updateNpmSettings = () => {
+ findNpmForwardingSettings().vm.$emit('update', 'npmPackageRequestsForwarding', false);
+ };
+
+ const submitForm = () => {
+ findForm().trigger('submit');
+ return waitForPromises();
+ };
+
+ afterEach(() => {
+ apolloProvider = null;
+ });
+
+ it('renders a settings block', () => {
+ mountComponent();
+
+ expect(findSettingsBlock().exists()).toBe(true);
+ });
+
+ it('has the correct header text', () => {
+ mountComponent();
+
+ expect(wrapper.text()).toContain(PACKAGE_FORWARDING_SETTINGS_HEADER);
+ });
+
+ it('has the correct description text', () => {
+ mountComponent();
+
+ expect(findDescription().text()).toMatchInterpolatedText(
+ PACKAGE_FORWARDING_SETTINGS_DESCRIPTION,
+ );
+ });
+
+ it('watches changes to props', async () => {
+ mountComponent();
+
+ expect(findNpmForwardingSettings().props()).toMatchObject(npmProps);
+
+ await wrapper.setProps({
+ forwardSettings: {
+ ...packageSettings,
+ npmPackageRequestsForwardingLocked: true,
+ },
+ });
+
+ expect(findNpmForwardingSettings().props()).toMatchObject({ ...npmProps, disabled: true });
+ });
+
+ it('submit button is disabled', () => {
+ mountComponent();
+
+ expect(findSubmitButton().props('disabled')).toBe(true);
+ });
+
+ describe.each`
+ type | finder | props | field
+ ${'npm'} | ${findNpmForwardingSettings} | ${npmProps} | ${'npmPackageRequestsForwarding'}
+ ${'pypi'} | ${findPyPiForwardingSettings} | ${pypiProps} | ${'pypiPackageRequestsForwarding'}
+ ${'maven'} | ${findMavenForwardingSettings} | ${mavenProps} | ${'mavenPackageRequestsForwarding'}
+ `('$type settings', ({ finder, props, field }) => {
+ beforeEach(() => {
+ mountComponent({ features: { mavenCentralRequestForwarding: true } });
+ });
+
+ it('assigns forwarding settings props', () => {
+ expect(finder().props()).toMatchObject(props);
+ });
+
+ it('on update event enables submit button', async () => {
+ finder().vm.$emit('update', field, false);
+
+ await waitForPromises();
+
+ expect(findSubmitButton().props('disabled')).toBe(false);
+ });
+ });
+
+ describe('maven settings', () => {
+ describe('with feature turned off', () => {
+ it('does not exist', () => {
+ mountComponent();
+
+ expect(findMavenForwardingSettings().exists()).toBe(false);
+ });
+ });
+ });
+
+ describe('settings update', () => {
+ describe('success state', () => {
+ it('calls the mutation with the right variables', async () => {
+ const {
+ mavenPackageRequestsForwardingLocked,
+ npmPackageRequestsForwardingLocked,
+ pypiPackageRequestsForwardingLocked,
+ ...packageSettingsInput
+ } = packageForwardingSettings;
+
+ mountComponent();
+
+ fillApolloCache();
+ updateNpmSettings();
+
+ await submitForm();
+
+ expect(mutationResolverFn).toHaveBeenCalledWith({
+ input: {
+ namespacePath: defaultProvide.groupPath,
+ ...packageSettingsInput,
+ npmPackageRequestsForwarding: false,
+ },
+ });
+ });
+
+ it('when field are locked calls the mutation with the right variables', async () => {
+ mountComponent({
+ forwardSettings: {
+ ...packageSettings,
+ mavenPackageRequestsForwardingLocked: true,
+ pypiPackageRequestsForwardingLocked: true,
+ },
+ });
+
+ fillApolloCache();
+ updateNpmSettings();
+
+ await submitForm();
+
+ expect(mutationResolverFn).toHaveBeenCalledWith({
+ input: {
+ namespacePath: defaultProvide.groupPath,
+ lockNpmPackageRequestsForwarding: false,
+ npmPackageRequestsForwarding: false,
+ },
+ });
+ });
+
+ it('emits a success event', async () => {
+ mountComponent();
+ fillApolloCache();
+ updateNpmSettings();
+
+ await submitForm();
+
+ expect(wrapper.emitted('success')).toHaveLength(1);
+ });
+
+ it('has an optimistic response', async () => {
+ const npmPackageRequestsForwarding = false;
+ mountComponent();
+
+ fillApolloCache();
+
+ expect(findNpmForwardingSettings().props('forwarding')).toBe(true);
+
+ updateNpmSettings();
+ await submitForm();
+
+ expect(updateGroupPackagesSettingsOptimisticResponse).toHaveBeenCalledWith({
+ ...packageSettings,
+ npmPackageRequestsForwarding,
+ });
+ expect(findNpmForwardingSettings().props('forwarding')).toBe(npmPackageRequestsForwarding);
+ });
+ });
+
+ describe('errors', () => {
+ it('mutation payload with root level errors', async () => {
+ const mutationResolver = jest.fn().mockResolvedValue(mutationErrorMock);
+ mountComponent({ mutationResolver });
+
+ fillApolloCache();
+
+ updateNpmSettings();
+ await submitForm();
+
+ expect(wrapper.emitted('error')).toHaveLength(1);
+ });
+
+ it.each`
+ type | mutationResolver
+ ${'local'} | ${jest.fn().mockResolvedValue(groupPackageForwardSettingsMutationMock({ errors: ['foo'] }))}
+ ${'network'} | ${jest.fn().mockRejectedValue()}
+ `('mutation payload with $type error', async ({ mutationResolver }) => {
+ mountComponent({ mutationResolver });
+
+ fillApolloCache();
+
+ updateNpmSettings();
+ await submitForm();
+
+ expect(wrapper.emitted('error')).toHaveLength(1);
+ });
+ });
+ });
+});
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 d53446de910..1ca9dc6daeb 100644
--- a/spec/frontend/packages_and_registries/settings/group/mock_data.js
+++ b/spec/frontend/packages_and_registries/settings/group/mock_data.js
@@ -1,9 +1,26 @@
-export const packageSettings = () => ({
+const packageDuplicateSettings = {
mavenDuplicatesAllowed: true,
mavenDuplicateExceptionRegex: '',
genericDuplicatesAllowed: true,
genericDuplicateExceptionRegex: '',
-});
+};
+
+export const packageForwardingSettings = {
+ mavenPackageRequestsForwarding: true,
+ lockMavenPackageRequestsForwarding: false,
+ npmPackageRequestsForwarding: true,
+ lockNpmPackageRequestsForwarding: false,
+ pypiPackageRequestsForwarding: true,
+ lockPypiPackageRequestsForwarding: false,
+ mavenPackageRequestsForwardingLocked: false,
+ npmPackageRequestsForwardingLocked: false,
+ pypiPackageRequestsForwardingLocked: false,
+};
+
+export const packageSettings = {
+ ...packageDuplicateSettings,
+ ...packageForwardingSettings,
+};
export const dependencyProxySettings = (extend) => ({
enabled: true,
@@ -21,13 +38,52 @@ export const groupPackageSettingsMock = {
group: {
id: '1',
fullPath: 'foo_group_path',
- packageSettings: packageSettings(),
+ packageSettings: {
+ ...packageSettings,
+ __typename: 'PackageSettings',
+ },
dependencyProxySetting: dependencyProxySettings(),
dependencyProxyImageTtlPolicy: dependencyProxyImageTtlPolicy(),
},
},
};
+export const npmProps = {
+ forwarding: packageForwardingSettings.npmPackageRequestsForwarding,
+ lockForwarding: packageForwardingSettings.lockNpmPackageRequestsForwarding,
+ label: 'npm',
+ disabled: false,
+ modelNames: {
+ forwarding: 'npmPackageRequestsForwarding',
+ lockForwarding: 'lockNpmPackageRequestsForwarding',
+ isLocked: 'npmPackageRequestsForwardingLocked',
+ },
+};
+
+export const pypiProps = {
+ forwarding: packageForwardingSettings.pypiPackageRequestsForwarding,
+ lockForwarding: packageForwardingSettings.lockPypiPackageRequestsForwarding,
+ label: 'PyPI',
+ disabled: false,
+ modelNames: {
+ forwarding: 'pypiPackageRequestsForwarding',
+ lockForwarding: 'lockPypiPackageRequestsForwarding',
+ isLocked: 'pypiPackageRequestsForwardingLocked',
+ },
+};
+
+export const mavenProps = {
+ forwarding: packageForwardingSettings.mavenPackageRequestsForwarding,
+ lockForwarding: packageForwardingSettings.lockMavenPackageRequestsForwarding,
+ label: 'Maven',
+ disabled: false,
+ modelNames: {
+ forwarding: 'mavenPackageRequestsForwarding',
+ lockForwarding: 'lockMavenPackageRequestsForwarding',
+ isLocked: 'mavenPackageRequestsForwardingLocked',
+ },
+};
+
export const groupPackageSettingsMutationMock = (override) => ({
data: {
updateNamespacePackageSettings: {
@@ -43,6 +99,19 @@ export const groupPackageSettingsMutationMock = (override) => ({
},
});
+export const groupPackageForwardSettingsMutationMock = (override) => ({
+ data: {
+ updateNamespacePackageSettings: {
+ packageSettings: {
+ npmPackageRequestsForwarding: true,
+ lockNpmPackageRequestsForwarding: false,
+ },
+ errors: [],
+ ...override,
+ },
+ },
+});
+
export const dependencyProxySettingMutationMock = (override) => ({
data: {
updateDependencyProxySettings: {
diff --git a/spec/frontend/packages_and_registries/shared/components/delete_package_modal_spec.js b/spec/frontend/packages_and_registries/shared/components/delete_package_modal_spec.js
new file mode 100644
index 00000000000..357dab593e8
--- /dev/null
+++ b/spec/frontend/packages_and_registries/shared/components/delete_package_modal_spec.js
@@ -0,0 +1,82 @@
+import { GlSprintf, GlModal } from '@gitlab/ui';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import DeletePackageModal from '~/packages_and_registries/shared/components/delete_package_modal.vue';
+
+describe('DeletePackageModal', () => {
+ let wrapper;
+
+ const defaultItemToBeDeleted = {
+ name: 'package 01',
+ };
+
+ const findModal = () => wrapper.findComponent(GlModal);
+
+ const mountComponent = ({ itemToBeDeleted = defaultItemToBeDeleted } = {}) => {
+ wrapper = shallowMountExtended(DeletePackageModal, {
+ propsData: {
+ itemToBeDeleted,
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ describe('when itemToBeDeleted prop is defined', () => {
+ beforeEach(() => {
+ mountComponent();
+ });
+
+ it('displays modal', () => {
+ expect(findModal().props('visible')).toBe(true);
+ });
+
+ it('passes title prop', () => {
+ expect(findModal().props('title')).toBe(wrapper.vm.$options.i18n.modalTitle);
+ });
+
+ it('passes actionPrimary prop', () => {
+ expect(findModal().props('actionPrimary')).toStrictEqual({
+ text: wrapper.vm.$options.i18n.modalAction,
+ attributes: {
+ variant: 'danger',
+ },
+ });
+ });
+
+ it('displays description', () => {
+ const descriptionEl = findModal().findComponent(GlSprintf);
+
+ expect(descriptionEl.exists()).toBe(true);
+ expect(descriptionEl.attributes('message')).toBe(wrapper.vm.$options.i18n.modalDescription);
+ });
+
+ it('emits ok when modal is validate', () => {
+ expect(wrapper.emitted().ok).toBeUndefined();
+
+ findModal().vm.$emit('ok');
+
+ expect(wrapper.emitted().ok).toHaveLength(1);
+ });
+
+ it('emits cancel when modal close', () => {
+ expect(wrapper.emitted().cancel).toBeUndefined();
+
+ findModal().vm.$emit('change', false);
+
+ expect(wrapper.emitted().cancel).toHaveLength(1);
+ });
+ });
+
+ describe('when itemToBeDeleted prop is null', () => {
+ beforeEach(() => {
+ mountComponent({ itemToBeDeleted: null });
+ });
+
+ it("doesn't display modal", () => {
+ expect(findModal().props('visible')).toBe(false);
+ });
+ });
+});