import { GlAlert, GlSprintf } from '@gitlab/ui';
import { nextTick } from 'vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { stubComponent } from 'helpers/stub_component';
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 DeleteModal from '~/packages_and_registries/package_registry/components/delete_modal.vue';
import RegistryList from '~/packages_and_registries/shared/components/registry_list.vue';
import {
DELETE_PACKAGE_TRACKING_ACTION,
DELETE_PACKAGES_TRACKING_ACTION,
REQUEST_DELETE_PACKAGE_TRACKING_ACTION,
REQUEST_DELETE_PACKAGES_TRACKING_ACTION,
CANCEL_DELETE_PACKAGE_TRACKING_ACTION,
CANCEL_DELETE_PACKAGES_TRACKING_ACTION,
} from '~/packages_and_registries/package_registry/constants';
import PackagesList from '~/packages_and_registries/package_registry/components/list/packages_list.vue';
import Tracking from '~/tracking';
import { defaultPackageGroupSettings, packageData } from '../../mock_data';
describe('packages_list', () => {
let wrapper;
const firstPackage = packageData();
const secondPackage = {
...packageData(),
id: 'gid://gitlab/Packages::Package/112',
name: 'second-package',
};
const errorPackage = {
...packageData(),
id: 'gid://gitlab/Packages::Package/121',
status: 'ERROR',
name: 'error package',
};
const defaultProps = {
list: [firstPackage, secondPackage],
isLoading: false,
pageInfo: {},
groupSettings: defaultPackageGroupSettings,
};
const defaultProvide = {
canDeletePackages: true,
};
const EmptySlotStub = { name: 'empty-slot-stub', template: '
bar
' };
const findPackagesListLoader = () => wrapper.findComponent(PackagesListLoader);
const findEmptySlot = () => wrapper.findComponent(EmptySlotStub);
const findRegistryList = () => wrapper.findComponent(RegistryList);
const findPackagesListRow = () => wrapper.findComponent(PackagesListRow);
const findErrorPackageAlert = () => wrapper.findComponent(GlAlert);
const findDeletePackagesModal = () => wrapper.findComponent(DeleteModal);
const showMock = jest.fn();
const mountComponent = ({ props = {}, provide = defaultProvide } = {}) => {
wrapper = shallowMountExtended(PackagesList, {
provide,
propsData: {
...defaultProps,
...props,
},
stubs: {
DeleteModal: stubComponent(DeleteModal, {
methods: {
show: showMock,
},
}),
GlSprintf,
RegistryList,
},
slots: {
'empty-state': EmptySlotStub,
},
});
};
describe('when is loading', () => {
beforeEach(() => {
mountComponent({ props: { isLoading: true } });
});
it('shows skeleton loader', () => {
expect(findPackagesListLoader().exists()).toBe(true);
});
it('does not show the registry list', () => {
expect(findRegistryList().exists()).toBe(false);
});
it('does not show the rows', () => {
expect(findPackagesListRow().exists()).toBe(false);
});
});
describe('when is not loading', () => {
beforeEach(() => {
mountComponent();
});
it('does not show skeleton loader', () => {
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,
hiddenDelete: false,
isLoading: false,
});
});
it('shows the rows', () => {
expect(findPackagesListRow().exists()).toBe(true);
});
});
describe('layout', () => {
beforeEach(() => {
mountComponent();
});
it('modal component is not shown', () => {
expect(showMock).not.toHaveBeenCalled();
});
it('modal component props is empty', () => {
expect(findDeletePackagesModal().props('itemsToBeDeleted')).toEqual([]);
expect(findDeletePackagesModal().props('showRequestForwardingContent')).toBe(false);
});
it('does not have an error alert displayed', () => {
expect(findErrorPackageAlert().exists()).toBe(false);
});
});
describe('when the user does not have permission to destroy packages', () => {
beforeEach(() => {
mountComponent({ provide: { canDeletePackages: false } });
});
it('sets the hidden delete prop of registry list to true', () => {
expect(findRegistryList().props('hiddenDelete')).toBe(true);
});
});
describe.each`
description | finderFunction | deletePayload
${'when the user can destroy the package'} | ${findPackagesListRow} | ${firstPackage}
${'when the user can bulk destroy packages and deletes only one package'} | ${findRegistryList} | ${[firstPackage]}
`('$description', ({ finderFunction, deletePayload }) => {
let eventSpy;
const category = 'UI::NpmPackages';
beforeEach(() => {
eventSpy = jest.spyOn(Tracking, 'event');
mountComponent();
finderFunction().vm.$emit('delete', deletePayload);
});
it('passes itemsToBeDeleted to the modal', () => {
expect(findDeletePackagesModal().props('itemsToBeDeleted')).toStrictEqual([firstPackage]);
});
it('requesting delete tracks the right action', () => {
expect(eventSpy).toHaveBeenCalledWith(
category,
REQUEST_DELETE_PACKAGE_TRACKING_ACTION,
expect.any(Object),
);
});
it('modal component is shown', () => {
expect(showMock).toHaveBeenCalledTimes(1);
});
describe('when modal confirms', () => {
beforeEach(() => {
findDeletePackagesModal().vm.$emit('confirm');
});
it('emits delete when modal confirms', () => {
expect(wrapper.emitted('delete')[0][0]).toEqual([firstPackage]);
});
it('tracks the right action', () => {
expect(eventSpy).toHaveBeenCalledWith(
category,
DELETE_PACKAGE_TRACKING_ACTION,
expect.any(Object),
);
});
});
it.each(['confirm', 'cancel'])('resets itemsToBeDeleted when modal emits %s', async (event) => {
await findDeletePackagesModal().vm.$emit(event);
expect(findDeletePackagesModal().props('itemsToBeDeleted')).toEqual([]);
});
it('canceling delete tracks the right action', () => {
findDeletePackagesModal().vm.$emit('cancel');
expect(eventSpy).toHaveBeenCalledWith(
category,
CANCEL_DELETE_PACKAGE_TRACKING_ACTION,
expect.any(Object),
);
});
});
describe('when the user can bulk destroy packages', () => {
let eventSpy;
const items = [firstPackage, secondPackage];
beforeEach(() => {
eventSpy = jest.spyOn(Tracking, 'event');
mountComponent();
findRegistryList().vm.$emit('delete', items);
});
it('passes itemsToBeDeleted to the modal', () => {
expect(findDeletePackagesModal().props('itemsToBeDeleted')).toStrictEqual(items);
expect(wrapper.emitted('delete')).toBeUndefined();
});
it('requesting delete tracks the right action', () => {
expect(eventSpy).toHaveBeenCalledWith(
undefined,
REQUEST_DELETE_PACKAGES_TRACKING_ACTION,
expect.any(Object),
);
});
describe('when modal confirms', () => {
beforeEach(() => {
findDeletePackagesModal().vm.$emit('confirm');
});
it('emits delete event', () => {
expect(wrapper.emitted('delete')[0]).toEqual([items]);
});
it('tracks the right action', () => {
expect(eventSpy).toHaveBeenCalledWith(
undefined,
DELETE_PACKAGES_TRACKING_ACTION,
expect.any(Object),
);
});
});
it.each(['confirm', 'cancel'])('resets itemsToBeDeleted when modal emits %s', async (event) => {
await findDeletePackagesModal().vm.$emit(event);
expect(findDeletePackagesModal().props('itemsToBeDeleted')).toEqual([]);
});
it('canceling delete tracks the right action', () => {
findDeletePackagesModal().vm.$emit('cancel');
expect(eventSpy).toHaveBeenCalledWith(
undefined,
CANCEL_DELETE_PACKAGES_TRACKING_ACTION,
expect.any(Object),
);
});
});
describe('when an error package is present', () => {
beforeEach(() => {
mountComponent({ props: { list: [firstPackage, errorPackage] } });
return nextTick();
});
it('should display an alert', () => {
expect(findErrorPackageAlert().exists()).toBe(true);
expect(findErrorPackageAlert().props('title')).toBe(
'There was an error publishing a error package package',
);
expect(findErrorPackageAlert().text()).toBe(
'There was a timeout and the package was not published. Delete this package and try again.',
);
});
it('should display the deletion modal when clicked on the confirm button', async () => {
findErrorPackageAlert().vm.$emit('primaryAction');
await nextTick();
expect(showMock).toHaveBeenCalledTimes(1);
expect(findDeletePackagesModal().props('itemsToBeDeleted')).toStrictEqual([errorPackage]);
});
});
describe('when the list is empty', () => {
beforeEach(() => {
mountComponent({ props: { list: [] } });
});
it('show the empty slot', () => {
const emptySlot = findEmptySlot();
expect(emptySlot.exists()).toBe(true);
});
});
describe('pagination', () => {
beforeEach(() => {
mountComponent({ props: { pageInfo: { hasPreviousPage: true } } });
});
it('emits prev-page events when the prev event is fired', () => {
findRegistryList().vm.$emit('prev-page');
expect(wrapper.emitted('prev-page')).toHaveLength(1);
});
it('emits next-page events when the next event is fired', () => {
findRegistryList().vm.$emit('next-page');
expect(wrapper.emitted('next-page')).toHaveLength(1);
});
});
});