import { GlEmptyState, GlSprintf, GlLink } from '@gitlab/ui';
import { createLocalVue } from '@vue/test-utils';
import VueApollo from 'vue-apollo';
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 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 {
PROJECT_RESOURCE_TYPE,
GROUP_RESOURCE_TYPE,
GRAPHQL_PAGE_SIZE,
} 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';
jest.mock('~/lib/utils/common_utils');
jest.mock('~/flash');
const localVue = createLocalVue();
describe('PackagesListApp', () => {
let wrapper;
let apolloProvider;
const defaultProvide = {
packageHelpUrl: 'packageHelpUrl',
emptyListIllustration: 'emptyListIllustration',
emptyListHelpUrl: 'emptyListHelpUrl',
isGroupPage: true,
fullPath: 'gitlab-org',
};
const PackageList = {
name: 'package-list',
template: '
',
props: OriginalPackageList.props,
};
const GlLoadingIcon = { name: 'gl-loading-icon', template: 'loading
' };
const searchPayload = {
sort: 'VERSION_DESC',
filters: { packageName: 'foo', packageType: 'CONAN' },
};
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 mountComponent = ({
resolver = jest.fn().mockResolvedValue(packagesListQuery()),
provide = defaultProvide,
} = {}) => {
localVue.use(VueApollo);
const requestHandlers = [[getPackagesQuery, resolver]];
apolloProvider = createMockApollo(requestHandlers);
wrapper = shallowMountExtended(PackageListApp, {
localVue,
apolloProvider,
provide,
stubs: {
GlEmptyState,
GlLoadingIcon,
GlSprintf,
GlLink,
PackageList,
DeletePackage,
},
});
};
afterEach(() => {
wrapper.destroy();
});
const waitForFirstRequest = () => {
// emit a search update so the query is executed
findSearch().vm.$emit('update', { sort: 'NAME_DESC', filters: [] });
return waitForPromises();
};
it('does not execute the query without sort being set', () => {
const resolver = jest.fn().mockResolvedValue(packagesListQuery());
mountComponent({ resolver });
expect(resolver).not.toHaveBeenCalled();
});
it('renders', async () => {
mountComponent();
await waitForFirstRequest();
expect(wrapper.element).toMatchSnapshot();
});
it('has a package title', async () => {
mountComponent();
await waitForFirstRequest();
expect(findPackageTitle().exists()).toBe(true);
expect(findPackageTitle().props('count')).toBe(2);
});
describe('search component', () => {
it('exists', () => {
mountComponent();
expect(findSearch().exists()).toBe(true);
});
it('on update triggers a new query with updated values', async () => {
const resolver = jest.fn().mockResolvedValue(packagesListQuery());
mountComponent({ resolver });
findSearch().vm.$emit('update', searchPayload);
await waitForPromises();
expect(resolver).toHaveBeenCalledWith(
expect.objectContaining({
groupSort: searchPayload.sort,
...searchPayload.filters,
}),
);
});
});
describe('list component', () => {
let resolver;
beforeEach(() => {
resolver = jest.fn().mockResolvedValue(packagesListQuery());
mountComponent({ resolver });
return waitForFirstRequest();
});
it('exists and has the right props', () => {
expect(findListComponent().props()).toMatchObject({
list: expect.arrayContaining([expect.objectContaining({ id: packageData().id })]),
isLoading: false,
pageInfo: expect.objectContaining({ endCursor: pagination().endCursor }),
});
});
it('when list emits next-page fetches the next set of records', () => {
findListComponent().vm.$emit('next-page');
expect(resolver).toHaveBeenCalledWith(
expect.objectContaining({ after: pagination().endCursor, first: GRAPHQL_PAGE_SIZE }),
);
});
it('when list emits prev-page fetches the prev set of records', () => {
findListComponent().vm.$emit('prev-page');
expect(resolver).toHaveBeenCalledWith(
expect.objectContaining({ before: pagination().startCursor, last: GRAPHQL_PAGE_SIZE }),
);
});
});
describe.each`
type | sortType
${PROJECT_RESOURCE_TYPE} | ${'sort'}
${GROUP_RESOURCE_TYPE} | ${'groupSort'}
`('$type query', ({ type, sortType }) => {
let provide;
let resolver;
const isGroupPage = type === GROUP_RESOURCE_TYPE;
beforeEach(() => {
provide = { ...defaultProvide, isGroupPage };
resolver = jest.fn().mockResolvedValue(packagesListQuery({ type }));
mountComponent({ provide, resolver });
return waitForFirstRequest();
});
it('succeeds', () => {
expect(findPackageTitle().props('count')).toBe(2);
});
it('calls the resolver with the right parameters', () => {
expect(resolver).toHaveBeenCalledWith(
expect.objectContaining({ isGroupPage, [sortType]: 'NAME_DESC' }),
);
});
});
describe('empty state', () => {
beforeEach(() => {
const resolver = jest.fn().mockResolvedValue(packagesListQuery({ extend: { nodes: [] } }));
mountComponent({ resolver });
return waitForFirstRequest();
});
it('generate the correct empty list link', () => {
const link = findListComponent().findComponent(GlLink);
expect(link.attributes('href')).toBe(defaultProvide.emptyListHelpUrl);
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);
});
});
describe('filter without results', () => {
beforeEach(async () => {
mountComponent();
await waitForFirstRequest();
findSearch().vm.$emit('update', searchPayload);
return nextTick();
});
it('should show specific empty message', () => {
expect(findEmptyState().text()).toContain(PackageListApp.i18n.noResultsTitle);
expect(findEmptyState().text()).toContain(PackageListApp.i18n.widenFilters);
});
});
describe('delete package', () => {
it('exists and has the correct props', async () => {
mountComponent();
await waitForFirstRequest();
expect(findDeletePackage().props()).toMatchObject({
refetchQueries: [{ query: getPackagesQuery, variables: {} }],
showSuccessAlert: true,
});
});
it('deletePackage is bound to package-list package:delete event', async () => {
mountComponent();
await waitForFirstRequest();
findListComponent().vm.$emit('package:delete', { id: 1 });
expect(findDeletePackage().emitted('start')).toEqual([[]]);
});
it('start and end event set loading correctly', async () => {
mountComponent();
await waitForFirstRequest();
findDeletePackage().vm.$emit('start');
await nextTick();
expect(findListComponent().props('isLoading')).toBe(true);
findDeletePackage().vm.$emit('end');
await nextTick();
expect(findListComponent().props('isLoading')).toBe(false);
});
});
});