summaryrefslogtreecommitdiff
path: root/spec/frontend/packages_and_registries/package_registry
diff options
context:
space:
mode:
Diffstat (limited to 'spec/frontend/packages_and_registries/package_registry')
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/package_title_spec.js.snap16
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/version_row_spec.js.snap1
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/details/app_spec.js53
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/details/installations_commands_spec.js14
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/functional/delete_package_spec.js160
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/list/__snapshots__/app_spec.js.snap57
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/list/app_spec.js168
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/list/packages_list_spec.js244
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/list/packages_search_spec.js2
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/list/packages_title_spec.js5
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/list/tokens/package_type_token_spec.js8
-rw-r--r--spec/frontend/packages_and_registries/package_registry/mock_data.js17
12 files changed, 527 insertions, 218 deletions
diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/package_title_spec.js.snap b/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/package_title_spec.js.snap
index 451cf743e35..519014bb9cf 100644
--- a/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/package_title_spec.js.snap
+++ b/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/package_title_spec.js.snap
@@ -19,15 +19,15 @@ exports[`PackageTitle renders with tags 1`] = `
<div
class="gl-display-flex gl-flex-direction-column"
>
- <h1
- class="gl-font-size-h1 gl-mt-3 gl-mb-2"
+ <h2
+ class="gl-font-size-h1 gl-mt-3 gl-mb-0"
data-testid="title"
>
@gitlab-org/package-15
- </h1>
+ </h2>
<div
- class="gl-display-flex gl-align-items-center gl-text-gray-500 gl-mt-1"
+ class="gl-display-flex gl-align-items-center gl-text-gray-500 gl-mt-3"
>
<gl-icon-stub
class="gl-mr-3"
@@ -117,15 +117,15 @@ exports[`PackageTitle renders without tags 1`] = `
<div
class="gl-display-flex gl-flex-direction-column"
>
- <h1
- class="gl-font-size-h1 gl-mt-3 gl-mb-2"
+ <h2
+ class="gl-font-size-h1 gl-mt-3 gl-mb-0"
data-testid="title"
>
@gitlab-org/package-15
- </h1>
+ </h2>
<div
- class="gl-display-flex gl-align-items-center gl-text-gray-500 gl-mt-1"
+ class="gl-display-flex gl-align-items-center gl-text-gray-500 gl-mt-3"
>
<gl-icon-stub
class="gl-mr-3"
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 8f69f943112..c95538546c1 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
@@ -27,6 +27,7 @@ exports[`VersionRow renders 1`] = `
>
<span
class="gl-truncate"
+ data-testid="truncate-end-container"
title="@gitlab-org/package-15"
>
<span
diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/app_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/app_spec.js
index 5119512564f..0bea84693f6 100644
--- a/spec/frontend/packages_and_registries/package_registry/components/details/app_spec.js
+++ b/spec/frontend/packages_and_registries/package_registry/components/details/app_spec.js
@@ -16,16 +16,15 @@ import PackageFiles from '~/packages_and_registries/package_registry/components/
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 {
FETCH_PACKAGE_DETAILS_ERROR_MESSAGE,
- DELETE_PACKAGE_ERROR_MESSAGE,
PACKAGE_TYPE_COMPOSER,
DELETE_PACKAGE_FILE_SUCCESS_MESSAGE,
DELETE_PACKAGE_FILE_ERROR_MESSAGE,
PACKAGE_TYPE_NUGET,
} from '~/packages_and_registries/package_registry/constants';
-import destroyPackageMutation from '~/packages_and_registries/package_registry/graphql/mutations/destroy_package.mutation.graphql';
import destroyPackageFileMutation from '~/packages_and_registries/package_registry/graphql/mutations/destroy_package_file.mutation.graphql';
import getPackageDetails from '~/packages_and_registries/package_registry/graphql/queries/get_package_details.query.graphql';
import {
@@ -34,8 +33,6 @@ import {
packageVersions,
dependencyLinks,
emptyPackageDetailsQuery,
- packageDestroyMutation,
- packageDestroyMutationError,
packageFiles,
packageDestroyFileMutation,
packageDestroyFileMutationError,
@@ -64,14 +61,12 @@ describe('PackagesApp', () => {
function createComponent({
resolver = jest.fn().mockResolvedValue(packageDetailsQuery()),
- mutationResolver = jest.fn().mockResolvedValue(packageDestroyMutation()),
fileDeleteMutationResolver = jest.fn().mockResolvedValue(packageDestroyFileMutation()),
} = {}) {
localVue.use(VueApollo);
const requestHandlers = [
[getPackageDetails, resolver],
- [destroyPackageMutation, mutationResolver],
[destroyPackageFileMutation, fileDeleteMutationResolver],
];
apolloProvider = createMockApollo(requestHandlers);
@@ -82,6 +77,7 @@ describe('PackagesApp', () => {
provide,
stubs: {
PackageTitle,
+ DeletePackage,
GlModal: {
template: '<div></div>',
methods: {
@@ -108,6 +104,7 @@ describe('PackagesApp', () => {
const findDependenciesCountBadge = () => wrapper.findComponent(GlBadge);
const findNoDependenciesMessage = () => wrapper.findByTestId('no-dependencies-message');
const findDependencyRows = () => wrapper.findAllComponents(DependencyRow);
+ const findDeletePackage = () => wrapper.findComponent(DeletePackage);
afterEach(() => {
wrapper.destroy();
@@ -187,14 +184,6 @@ describe('PackagesApp', () => {
});
};
- const performDeletePackage = async () => {
- await findDeleteButton().trigger('click');
-
- findDeleteModal().vm.$emit('primary');
-
- await waitForPromises();
- };
-
afterEach(() => {
Object.defineProperty(document, 'referrer', {
value: originalReferrer,
@@ -220,7 +209,7 @@ describe('PackagesApp', () => {
await waitForPromises();
- await performDeletePackage();
+ findDeletePackage().vm.$emit('end');
expect(window.location.replace).toHaveBeenCalledWith(
'projectListUrl?showSuccessDeleteAlert=true',
@@ -234,45 +223,13 @@ describe('PackagesApp', () => {
await waitForPromises();
- await performDeletePackage();
+ findDeletePackage().vm.$emit('end');
expect(window.location.replace).toHaveBeenCalledWith(
'groupListUrl?showSuccessDeleteAlert=true',
);
});
});
-
- describe('request failure', () => {
- it('on global failure it displays an alert', async () => {
- createComponent({ mutationResolver: jest.fn().mockRejectedValue() });
-
- await waitForPromises();
-
- await performDeletePackage();
-
- expect(createFlash).toHaveBeenCalledWith(
- expect.objectContaining({
- message: DELETE_PACKAGE_ERROR_MESSAGE,
- }),
- );
- });
-
- it('on payload with error it displays an alert', async () => {
- createComponent({
- mutationResolver: jest.fn().mockResolvedValue(packageDestroyMutationError()),
- });
-
- await waitForPromises();
-
- await performDeletePackage();
-
- expect(createFlash).toHaveBeenCalledWith(
- expect.objectContaining({
- message: DELETE_PACKAGE_ERROR_MESSAGE,
- }),
- );
- });
- });
});
describe('package files', () => {
diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/installations_commands_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/installations_commands_spec.js
index b24946c8638..8bb05b00e65 100644
--- a/spec/frontend/packages_and_registries/package_registry/components/details/installations_commands_spec.js
+++ b/spec/frontend/packages_and_registries/package_registry/components/details/installations_commands_spec.js
@@ -33,12 +33,12 @@ describe('InstallationCommands', () => {
});
}
- const npmInstallation = () => wrapper.find(NpmInstallation);
- const mavenInstallation = () => wrapper.find(MavenInstallation);
- const conanInstallation = () => wrapper.find(ConanInstallation);
- const nugetInstallation = () => wrapper.find(NugetInstallation);
- const pypiInstallation = () => wrapper.find(PypiInstallation);
- const composerInstallation = () => wrapper.find(ComposerInstallation);
+ const npmInstallation = () => wrapper.findComponent(NpmInstallation);
+ const mavenInstallation = () => wrapper.findComponent(MavenInstallation);
+ const conanInstallation = () => wrapper.findComponent(ConanInstallation);
+ const nugetInstallation = () => wrapper.findComponent(NugetInstallation);
+ const pypiInstallation = () => wrapper.findComponent(PypiInstallation);
+ const composerInstallation = () => wrapper.findComponent(ComposerInstallation);
afterEach(() => {
wrapper.destroy();
@@ -57,7 +57,7 @@ describe('InstallationCommands', () => {
it(`${packageEntity.packageType} instructions exist`, () => {
createComponent({ packageEntity });
- expect(selector()).toExist();
+ expect(selector().exists()).toBe(true);
});
});
});
diff --git a/spec/frontend/packages_and_registries/package_registry/components/functional/delete_package_spec.js b/spec/frontend/packages_and_registries/package_registry/components/functional/delete_package_spec.js
new file mode 100644
index 00000000000..5de30829fa5
--- /dev/null
+++ b/spec/frontend/packages_and_registries/package_registry/components/functional/delete_package_spec.js
@@ -0,0 +1,160 @@
+import { createLocalVue } from '@vue/test-utils';
+import VueApollo from 'vue-apollo';
+import waitForPromises from 'helpers/wait_for_promises';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import createFlash from '~/flash';
+import DeletePackage from '~/packages_and_registries/package_registry/components/functional/delete_package.vue';
+
+import destroyPackageMutation from '~/packages_and_registries/package_registry/graphql/mutations/destroy_package.mutation.graphql';
+import getPackagesQuery from '~/packages_and_registries/package_registry/graphql/queries/get_packages.query.graphql';
+import {
+ packageDestroyMutation,
+ packageDestroyMutationError,
+ packagesListQuery,
+} from '../../mock_data';
+
+jest.mock('~/flash');
+
+const localVue = createLocalVue();
+
+describe('DeletePackage', () => {
+ let wrapper;
+ let apolloProvider;
+ let resolver;
+ let mutationResolver;
+
+ const eventPayload = { id: '1' };
+
+ function createComponent(propsData = {}) {
+ localVue.use(VueApollo);
+
+ const requestHandlers = [
+ [getPackagesQuery, resolver],
+ [destroyPackageMutation, mutationResolver],
+ ];
+ apolloProvider = createMockApollo(requestHandlers);
+
+ wrapper = shallowMountExtended(DeletePackage, {
+ propsData,
+ localVue,
+ apolloProvider,
+ scopedSlots: {
+ default(props) {
+ return this.$createElement('button', {
+ attrs: {
+ 'data-testid': 'trigger-button',
+ },
+ on: {
+ click: props.deletePackage,
+ },
+ });
+ },
+ },
+ });
+ }
+
+ const findButton = () => wrapper.findByTestId('trigger-button');
+
+ const clickOnButtonAndWait = (payload) => {
+ findButton().trigger('click', payload);
+ return waitForPromises();
+ };
+
+ beforeEach(() => {
+ resolver = jest.fn().mockResolvedValue(packagesListQuery());
+ mutationResolver = jest.fn().mockResolvedValue(packageDestroyMutation());
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('binds deletePackage method to the default slot', () => {
+ createComponent();
+
+ findButton().trigger('click');
+
+ expect(wrapper.emitted('start')).toEqual([[]]);
+ });
+
+ it('calls apollo mutation', async () => {
+ createComponent();
+
+ await clickOnButtonAndWait(eventPayload);
+
+ expect(mutationResolver).toHaveBeenCalledWith(eventPayload);
+ });
+
+ it('passes refetchQueries to apollo mutate', async () => {
+ const variables = { isGroupPage: true };
+ createComponent({
+ refetchQueries: [{ query: getPackagesQuery, variables }],
+ });
+
+ await clickOnButtonAndWait(eventPayload);
+
+ expect(mutationResolver).toHaveBeenCalledWith(eventPayload);
+ expect(resolver).toHaveBeenCalledWith(variables);
+ });
+
+ describe('on mutation success', () => {
+ it('emits end event', async () => {
+ createComponent();
+
+ await clickOnButtonAndWait(eventPayload);
+
+ expect(wrapper.emitted('end')).toEqual([[]]);
+ });
+
+ it('does not call createFlash', async () => {
+ createComponent();
+
+ await clickOnButtonAndWait(eventPayload);
+
+ expect(createFlash).not.toHaveBeenCalled();
+ });
+
+ it('calls createFlash with the success message when showSuccessAlert is true', async () => {
+ createComponent({ showSuccessAlert: true });
+
+ await clickOnButtonAndWait(eventPayload);
+
+ expect(createFlash).toHaveBeenCalledWith({
+ message: DeletePackage.i18n.successMessage,
+ type: 'success',
+ });
+ });
+ });
+
+ describe.each`
+ errorType | mutationResolverResponse
+ ${'connectionError'} | ${jest.fn().mockRejectedValue()}
+ ${'localError'} | ${jest.fn().mockResolvedValue(packageDestroyMutationError())}
+ `('on mutation $errorType', ({ mutationResolverResponse }) => {
+ beforeEach(() => {
+ mutationResolver = mutationResolverResponse;
+ });
+
+ it('emits end event', async () => {
+ createComponent();
+
+ await clickOnButtonAndWait(eventPayload);
+
+ expect(wrapper.emitted('end')).toEqual([[]]);
+ });
+
+ it('calls createFlash with the error message', async () => {
+ createComponent({ showSuccessAlert: true });
+
+ await clickOnButtonAndWait(eventPayload);
+
+ expect(createFlash).toHaveBeenCalledWith({
+ message: DeletePackage.i18n.errorMessage,
+ type: 'warning',
+ captureError: true,
+ error: expect.any(Error),
+ });
+ });
+ });
+});
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/components/list/__snapshots__/app_spec.js.snap
index 1b556be5873..5af75868084 100644
--- a/spec/frontend/packages_and_registries/package_registry/components/list/__snapshots__/app_spec.js.snap
+++ b/spec/frontend/packages_and_registries/package_registry/components/list/__snapshots__/app_spec.js.snap
@@ -8,5 +8,62 @@ exports[`PackagesListApp renders 1`] = `
/>
<package-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="emptyListIllustration"
+ />
+ </div>
+ </div>
+
+ <div
+ class="col-12"
+ >
+ <div
+ class="text-content gl-mx-auto gl-my-0 gl-p-5"
+ >
+ <h1
+ class="h4"
+ >
+ There are no packages yet
+ </h1>
+
+ <p>
+ Learn how to
+ <b-link-stub
+ class="gl-link"
+ event="click"
+ href="emptyListHelpUrl"
+ 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/package_registry/components/list/app_spec.js b/spec/frontend/packages_and_registries/package_registry/components/list/app_spec.js
index 3958cdf21bb..ad848f367e0 100644
--- a/spec/frontend/packages_and_registries/package_registry/components/list/app_spec.js
+++ b/spec/frontend/packages_and_registries/package_registry/components/list/app_spec.js
@@ -2,22 +2,25 @@ 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,
- LIST_QUERY_DEBOUNCE_TIME,
+ 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 } from '../../mock_data';
+import { packagesListQuery, packageData, pagination } from '../../mock_data';
jest.mock('~/lib/utils/common_utils');
jest.mock('~/flash');
@@ -39,11 +42,20 @@ describe('PackagesListApp', () => {
const PackageList = {
name: 'package-list',
template: '<div><slot name="empty-state"></slot></div>',
+ props: OriginalPackageList.props,
};
const GlLoadingIcon = { name: 'gl-loading-icon', template: '<div>loading</div>' };
+ 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()),
@@ -61,9 +73,10 @@ describe('PackagesListApp', () => {
stubs: {
GlEmptyState,
GlLoadingIcon,
- PackageList,
GlSprintf,
GlLink,
+ PackageList,
+ DeletePackage,
},
});
};
@@ -72,15 +85,24 @@ describe('PackagesListApp', () => {
wrapper.destroy();
});
- const waitForDebouncedApollo = () => {
- jest.advanceTimersByTime(LIST_QUERY_DEBOUNCE_TIME);
+ 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 waitForDebouncedApollo();
+ await waitForFirstRequest();
expect(wrapper.element).toMatchSnapshot();
});
@@ -88,7 +110,7 @@ describe('PackagesListApp', () => {
it('has a package title', async () => {
mountComponent();
- await waitForDebouncedApollo();
+ await waitForFirstRequest();
expect(findPackageTitle().exists()).toBe(true);
expect(findPackageTitle().props('count')).toBe(2);
@@ -105,25 +127,54 @@ describe('PackagesListApp', () => {
const resolver = jest.fn().mockResolvedValue(packagesListQuery());
mountComponent({ resolver });
- const payload = {
- sort: 'VERSION_DESC',
- filters: { packageName: 'foo', packageType: 'CONAN' },
- };
-
- findSearch().vm.$emit('update', payload);
+ findSearch().vm.$emit('update', searchPayload);
- await waitForDebouncedApollo();
- jest.advanceTimersByTime(LIST_QUERY_DEBOUNCE_TIME);
+ await waitForPromises();
expect(resolver).toHaveBeenCalledWith(
expect.objectContaining({
- groupSort: payload.sort,
- ...payload.filters,
+ 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'}
@@ -136,9 +187,9 @@ describe('PackagesListApp', () => {
beforeEach(() => {
provide = { ...defaultProvide, isGroupPage };
- resolver = jest.fn().mockResolvedValue(packagesListQuery(type));
+ resolver = jest.fn().mockResolvedValue(packagesListQuery({ type }));
mountComponent({ provide, resolver });
- return waitForDebouncedApollo();
+ return waitForFirstRequest();
});
it('succeeds', () => {
@@ -147,8 +198,85 @@ describe('PackagesListApp', () => {
it('calls the resolver with the right parameters', () => {
expect(resolver).toHaveBeenCalledWith(
- expect.objectContaining({ isGroupPage, [sortType]: '' }),
+ 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);
+ });
+ });
});
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 b624e66482d..de4e9c8ae5b 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,93 +1,86 @@
-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 { packageList } from 'jest/packages/mock_data';
+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 { TrackingActions } from '~/packages/shared/constants';
-import * as SharedUtils from '~/packages/shared/utils';
+import {
+ DELETE_PACKAGE_TRACKING_ACTION,
+ REQUEST_DELETE_PACKAGE_TRACKING_ACTION,
+ CANCEL_DELETE_PACKAGE_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';
-
-const localVue = createLocalVue();
-localVue.use(Vuex);
+import { packageData } from '../../mock_data';
describe('packages_list', () => {
let wrapper;
- let store;
+
+ const firstPackage = packageData();
+ const secondPackage = {
+ ...packageData(),
+ id: 'gid://gitlab/Packages::Package/112',
+ name: 'second-package',
+ };
+
+ const defaultProps = {
+ list: [firstPackage, secondPackage],
+ isLoading: false,
+ pageInfo: {},
+ };
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.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,
+ const findPackagesListLoader = () => wrapper.findComponent(PackagesListLoader);
+ const findPackageListPagination = () => wrapper.findComponent(GlKeysetPagination);
+ const findPackageListDeleteModal = () => wrapper.findComponent(GlModalStub);
+ const findEmptySlot = () => wrapper.findComponent(EmptySlotStub);
+ const findPackagesListRow = () => wrapper.findComponent(PackagesListRow);
+
+ const mountComponent = (props) => {
+ wrapper = shallowMountExtended(PackagesList, {
+ propsData: {
+ ...defaultProps,
+ ...props,
},
- sorting: {
- orderBy: 'version',
- sort: 'desc',
+ stubs: {
+ GlModal: GlModalStub,
+ GlSprintf,
},
- };
- store = new Vuex.Store({
- state,
- getters: {
- getList: () => packages,
+ slots: {
+ 'empty-state': EmptySlotStub,
},
});
- 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,
- });
- };
+ beforeEach(() => {
+ GlModalStub.methods.show.mockReset();
+ });
afterEach(() => {
wrapper.destroy();
- wrapper = null;
});
describe('when is loading', () => {
beforeEach(() => {
- mountComponent({
- packages: [],
- isLoading: true,
- });
+ mountComponent({ isLoading: true });
});
- it('shows skeleton loader when loading', () => {
+ it('shows skeleton loader', () => {
expect(findPackagesListLoader().exists()).toBe(true);
});
+
+ it('does not show the rows', () => {
+ expect(findPackagesListRow().exists()).toBe(false);
+ });
+
+ it('does not show the pagination', () => {
+ expect(findPackageListPagination().exists()).toBe(false);
+ });
});
describe('when is not loading', () => {
@@ -95,74 +88,61 @@ describe('packages_list', () => {
mountComponent();
});
- it('does not show skeleton loader when not loading', () => {
+ it('does not show skeleton loader', () => {
expect(findPackagesListLoader().exists()).toBe(false);
});
- });
- describe('layout', () => {
- beforeEach(() => {
- mountComponent();
+ it('shows the rows', () => {
+ expect(findPackagesListRow().exists()).toBe(true);
});
+ });
+ describe('layout', () => {
it('contains a pagination component', () => {
- const sorting = findPackageListPagination();
- expect(sorting.exists()).toBe(true);
+ mountComponent({ pageInfo: { hasPreviousPage: true } });
+
+ expect(findPackageListPagination().exists()).toBe(true);
});
it('contains a modal component', () => {
- const sorting = findPackageListDeleteModal();
- expect(sorting.exists()).toBe(true);
+ mountComponent();
+
+ expect(findPackageListDeleteModal().exists()).toBe(true);
});
});
describe('when the user can destroy the package', () => {
beforeEach(() => {
mountComponent();
+ findPackagesListRow().vm.$emit('packageToDelete', firstPackage);
+ return nextTick();
});
- it('setItemToBeDeleted sets itemToBeDeleted and open the modal', () => {
- const mockModalShow = jest.spyOn(wrapper.vm.$refs.packageListDeleteModal, 'show');
- const item = last(wrapper.vm.list);
+ it('deleting a package opens the modal', () => {
+ expect(findPackageListDeleteModal().text()).toContain(firstPackage.name);
+ });
- findPackagesListRow().vm.$emit('packageToDelete', item);
+ it('confirming on the modal emits package:delete', async () => {
+ findPackageListDeleteModal().vm.$emit('ok');
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.vm.itemToBeDeleted).toEqual(item);
- expect(mockModalShow).toHaveBeenCalled();
- });
- });
+ await nextTick();
- it('deleteItemConfirmation resets itemToBeDeleted', () => {
- wrapper.setData({ itemToBeDeleted: 1 });
- wrapper.vm.deleteItemConfirmation();
- expect(wrapper.vm.itemToBeDeleted).toEqual(null);
+ expect(wrapper.emitted('package:delete')[0]).toEqual([firstPackage]);
});
- 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('closing the modal resets itemToBeDeleted', async () => {
+ // triggering the v-model
+ findPackageListDeleteModal().vm.$emit('input', false);
- it('deleteItemCanceled resets itemToBeDeleted', () => {
- wrapper.setData({ itemToBeDeleted: 1 });
- wrapper.vm.deleteItemCanceled();
- expect(wrapper.vm.itemToBeDeleted).toEqual(null);
+ await nextTick();
+
+ expect(findPackageListDeleteModal().text()).not.toContain(firstPackage.name);
});
});
describe('when the list is empty', () => {
beforeEach(() => {
- mountComponent({
- packages: [],
- slots: {
- 'empty-state': EmptySlotStub,
- },
- });
+ mountComponent({ list: [] });
});
it('show the empty slot', () => {
@@ -171,45 +151,59 @@ describe('packages_list', () => {
});
});
- describe('pagination component', () => {
- let pagination;
- let modelEvent;
-
+ describe('pagination ', () => {
beforeEach(() => {
- mountComponent();
- pagination = findPackageListPagination();
- // retrieve the event used by v-model, a more sturdy approach than hardcoding it
- modelEvent = pagination.vm.$options.model.event;
+ mountComponent({ pageInfo: { hasPreviousPage: true } });
});
- it('emits page:changed events when the page changes', () => {
- pagination.vm.$emit(modelEvent, 2);
- expect(wrapper.emitted('page:changed')).toEqual([[2]]);
+ it('emits prev-page events when the prev event is fired', () => {
+ findPackageListPagination().vm.$emit('prev');
+
+ expect(wrapper.emitted('prev-page')).toEqual([[]]);
+ });
+
+ it('emits next-page events when the next event is fired', () => {
+ findPackageListPagination().vm.$emit('next');
+
+ expect(wrapper.emitted('next-page')).toEqual([[]]);
});
});
describe('tracking', () => {
let eventSpy;
- let utilSpy;
- const category = 'foo';
+ const category = 'UI::NpmPackages';
beforeEach(() => {
- mountComponent();
eventSpy = jest.spyOn(Tracking, 'event');
- utilSpy = jest.spyOn(SharedUtils, 'packageTypeToTrackCategory').mockReturnValue(category);
- wrapper.setData({ itemToBeDeleted: { package_type: 'conan' } });
+ mountComponent();
+ findPackagesListRow().vm.$emit('packageToDelete', firstPackage);
+ return nextTick();
});
- it('tracking category calls packageTypeToTrackCategory', () => {
- expect(wrapper.vm.tracking.category).toBe(category);
- expect(utilSpy).toHaveBeenCalledWith('conan');
+ it('requesting the delete tracks the right action', () => {
+ expect(eventSpy).toHaveBeenCalledWith(
+ category,
+ REQUEST_DELETE_PACKAGE_TRACKING_ACTION,
+ expect.any(Object),
+ );
+ });
+
+ it('confirming delete tracks the right action', () => {
+ findPackageListDeleteModal().vm.$emit('ok');
+
+ expect(eventSpy).toHaveBeenCalledWith(
+ category,
+ DELETE_PACKAGE_TRACKING_ACTION,
+ expect.any(Object),
+ );
});
- it('deleteItemConfirmation calls event', () => {
- wrapper.vm.deleteItemConfirmation();
+ it('canceling delete tracks the right action', () => {
+ findPackageListDeleteModal().vm.$emit('cancel');
+
expect(eventSpy).toHaveBeenCalledWith(
category,
- TrackingActions.DELETE_PACKAGE,
+ CANCEL_DELETE_PACKAGE_TRACKING_ACTION,
expect.any(Object),
);
});
diff --git a/spec/frontend/packages_and_registries/package_registry/components/list/packages_search_spec.js b/spec/frontend/packages_and_registries/package_registry/components/list/packages_search_spec.js
index e65b2a6f320..bed7a07ff36 100644
--- a/spec/frontend/packages_and_registries/package_registry/components/list/packages_search_spec.js
+++ b/spec/frontend/packages_and_registries/package_registry/components/list/packages_search_spec.js
@@ -1,6 +1,6 @@
import { nextTick } from 'vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
-import { sortableFields } from '~/packages/list/utils';
+import { sortableFields } from '~/packages_and_registries/package_registry/utils';
import component from '~/packages_and_registries/package_registry/components/list/package_search.vue';
import PackageTypeToken from '~/packages_and_registries/package_registry/components/list/tokens/package_type_token.vue';
import RegistrySearch from '~/vue_shared/components/registry/registry_search.vue';
diff --git a/spec/frontend/packages_and_registries/package_registry/components/list/packages_title_spec.js b/spec/frontend/packages_and_registries/package_registry/components/list/packages_title_spec.js
index 3fa96ce1d29..e992ba12faa 100644
--- a/spec/frontend/packages_and_registries/package_registry/components/list/packages_title_spec.js
+++ b/spec/frontend/packages_and_registries/package_registry/components/list/packages_title_spec.js
@@ -1,5 +1,4 @@
import { shallowMount } from '@vue/test-utils';
-import { LIST_INTRO_TEXT, LIST_TITLE_TEXT } from '~/packages/list/constants';
import PackageTitle from '~/packages_and_registries/package_registry/components/list/package_title.vue';
import MetadataItem from '~/vue_shared/components/registry/metadata_item.vue';
import TitleArea from '~/vue_shared/components/registry/title_area.vue';
@@ -37,8 +36,8 @@ describe('PackageTitle', () => {
mountComponent();
expect(findTitleArea().props()).toMatchObject({
- title: LIST_TITLE_TEXT,
- infoMessages: [{ text: LIST_INTRO_TEXT, link: 'foo' }],
+ title: PackageTitle.i18n.LIST_TITLE_TEXT,
+ infoMessages: [{ text: PackageTitle.i18n.LIST_INTRO_TEXT, link: 'foo' }],
});
});
});
diff --git a/spec/frontend/packages_and_registries/package_registry/components/list/tokens/package_type_token_spec.js b/spec/frontend/packages_and_registries/package_registry/components/list/tokens/package_type_token_spec.js
index b0cbe34f0b9..26b2f3b359f 100644
--- a/spec/frontend/packages_and_registries/package_registry/components/list/tokens/package_type_token_spec.js
+++ b/spec/frontend/packages_and_registries/package_registry/components/list/tokens/package_type_token_spec.js
@@ -1,7 +1,7 @@
import { GlFilteredSearchToken, GlFilteredSearchSuggestion } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import component from '~/packages/list/components/tokens/package_type_token.vue';
-import { PACKAGE_TYPES } from '~/packages/list/constants';
+import component from '~/packages_and_registries/package_registry/components/list/tokens/package_type_token.vue';
+import { PACKAGE_TYPES } from '~/packages_and_registries/package_registry/constants';
describe('packages_filter', () => {
let wrapper;
@@ -41,8 +41,8 @@ describe('packages_filter', () => {
(packageType, index) => {
mountComponent();
const item = findFilteredSearchSuggestions().at(index);
- expect(item.text()).toBe(packageType.title);
- expect(item.props('value')).toBe(packageType.type);
+ expect(item.text()).toBe(packageType);
+ expect(item.props('value')).toBe(packageType);
},
);
});
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 70fc096fa44..bacc748db81 100644
--- a/spec/frontend/packages_and_registries/package_registry/mock_data.js
+++ b/spec/frontend/packages_and_registries/package_registry/mock_data.js
@@ -1,3 +1,5 @@
+import capitalize from 'lodash/capitalize';
+
export const packageTags = () => [
{ id: 'gid://gitlab/Packages::Tag/87', name: 'bananas_9', __typename: 'PackageTag' },
{ id: 'gid://gitlab/Packages::Tag/86', name: 'bananas_8', __typename: 'PackageTag' },
@@ -156,6 +158,15 @@ export const nugetMetadata = () => ({
projectUrl: 'projectUrl',
});
+export const pagination = (extend) => ({
+ endCursor: 'eyJpZCI6IjIwNSIsIm5hbWUiOiJteS9jb21wYW55L2FwcC9teS1hcHAifQ',
+ hasNextPage: true,
+ hasPreviousPage: true,
+ startCursor: 'eyJpZCI6IjI0NyIsIm5hbWUiOiJ2ZXJzaW9uX3Rlc3QxIn0',
+ __typename: 'PageInfo',
+ ...extend,
+});
+
export const packageDetailsQuery = (extendPackage) => ({
data: {
package: {
@@ -256,7 +267,7 @@ export const packageDestroyFileMutationError = () => ({
],
});
-export const packagesListQuery = (type = 'group') => ({
+export const packagesListQuery = ({ type = 'group', extend = {}, extendPagination = {} } = {}) => ({
data: {
[type]: {
packages: {
@@ -277,9 +288,11 @@ export const packagesListQuery = (type = 'group') => ({
pipelines: { nodes: [] },
},
],
+ pageInfo: pagination(extendPagination),
__typename: 'PackageConnection',
},
- __typename: 'Group',
+ ...extend,
+ __typename: capitalize(type),
},
},
});