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/infrastructure_registry')
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/app_spec.js55
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/details_title_spec.js2
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/package_files_spec.js2
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/package_history_spec.js2
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/terraform_installation_spec.js2
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/details/store/actions_spec.js4
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/details/store/getters_spec.js2
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/details/store/mutations_spec.js2
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/__snapshots__/packages_list_app_spec.js.snap72
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/infrastructure_search_spec.js (renamed from spec/frontend/packages_and_registries/infrastructure_registry/components/infrastructure_search_spec.js)2
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/infrastructure_title_spec.js (renamed from spec/frontend/packages_and_registries/infrastructure_registry/components/infrastructure_title_spec.js)2
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/packages_list_app_spec.js239
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/packages_list_spec.js209
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/list/stores/actions_spec.js277
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/list/stores/getters_spec.js36
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/list/stores/mutations_spec.js87
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/list/utils_spec.js51
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/mock_data.js210
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/shared/__snapshots__/package_list_row_spec.js.snap118
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/shared/infrastructure_icon_and_name_spec.js (renamed from spec/frontend/packages_and_registries/infrastructure_registry/components/infrastructure_icon_and_name_spec.js)2
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/shared/package_list_row_spec.js161
21 files changed, 1494 insertions, 43 deletions
diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/app_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/app_spec.js
index c7c10cef504..2868af84181 100644
--- a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/app_spec.js
+++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/app_spec.js
@@ -9,15 +9,15 @@ import PackagesApp from '~/packages_and_registries/infrastructure_registry/detai
import PackageFiles from '~/packages_and_registries/infrastructure_registry/details/components/package_files.vue';
import PackageHistory from '~/packages_and_registries/infrastructure_registry/details/components/package_history.vue';
import * as getters from '~/packages_and_registries/infrastructure_registry/details/store/getters';
-import PackageListRow from '~/packages/shared/components/package_list_row.vue';
-import PackagesListLoader from '~/packages/shared/components/packages_list_loader.vue';
-import { TrackingActions } from '~/packages/shared/constants';
-import * as SharedUtils from '~/packages/shared/utils';
+import PackageListRow from '~/packages_and_registries/infrastructure_registry/shared/package_list_row.vue';
+import PackagesListLoader from '~/packages_and_registries/shared/components/packages_list_loader.vue';
+import { TRACKING_ACTIONS } from '~/packages_and_registries/shared/constants';
+import { TRACK_CATEGORY } from '~/packages_and_registries/infrastructure_registry/shared/constants';
import TerraformTitle from '~/packages_and_registries/infrastructure_registry/details/components/details_title.vue';
import TerraformInstallation from '~/packages_and_registries/infrastructure_registry/details/components/terraform_installation.vue';
import Tracking from '~/tracking';
-import { mavenPackage, mavenFiles, npmPackage } from 'jest/packages/mock_data';
+import { mavenPackage, mavenFiles, npmPackage } from '../../mock_data';
const localVue = createLocalVue();
localVue.use(Vuex);
@@ -232,87 +232,78 @@ describe('PackagesApp', () => {
describe('tracking', () => {
let eventSpy;
- let utilSpy;
- const category = 'foo';
beforeEach(() => {
eventSpy = jest.spyOn(Tracking, 'event');
- utilSpy = jest.spyOn(SharedUtils, 'packageTypeToTrackCategory').mockReturnValue(category);
});
- it('tracking category calls packageTypeToTrackCategory', () => {
- createComponent({ packageEntity: npmPackage });
- expect(wrapper.vm.tracking.category).toBe(category);
- expect(utilSpy).toHaveBeenCalledWith('npm');
- });
-
- it(`delete button on delete modal call event with ${TrackingActions.DELETE_PACKAGE}`, () => {
+ it(`delete button on delete modal call event with ${TRACKING_ACTIONS.DELETE_PACKAGE}`, () => {
createComponent({ packageEntity: npmPackage });
findDeleteModal().vm.$emit('primary');
expect(eventSpy).toHaveBeenCalledWith(
- category,
- TrackingActions.DELETE_PACKAGE,
+ TRACK_CATEGORY,
+ TRACKING_ACTIONS.DELETE_PACKAGE,
expect.any(Object),
);
});
- it(`canceling a package deletion tracks ${TrackingActions.CANCEL_DELETE_PACKAGE}`, () => {
+ it(`canceling a package deletion tracks ${TRACKING_ACTIONS.CANCEL_DELETE_PACKAGE}`, () => {
createComponent({ packageEntity: npmPackage });
findDeleteModal().vm.$emit('canceled');
expect(eventSpy).toHaveBeenCalledWith(
- category,
- TrackingActions.CANCEL_DELETE_PACKAGE,
+ TRACK_CATEGORY,
+ TRACKING_ACTIONS.CANCEL_DELETE_PACKAGE,
expect.any(Object),
);
});
- it(`request a file deletion tracks ${TrackingActions.REQUEST_DELETE_PACKAGE_FILE}`, () => {
+ it(`request a file deletion tracks ${TRACKING_ACTIONS.REQUEST_DELETE_PACKAGE_FILE}`, () => {
createComponent({ packageEntity: npmPackage });
findPackageFiles().vm.$emit('delete-file', mavenFiles[0]);
expect(eventSpy).toHaveBeenCalledWith(
- category,
- TrackingActions.REQUEST_DELETE_PACKAGE_FILE,
+ TRACK_CATEGORY,
+ TRACKING_ACTIONS.REQUEST_DELETE_PACKAGE_FILE,
expect.any(Object),
);
});
- it(`confirming a file deletion tracks ${TrackingActions.DELETE_PACKAGE_FILE}`, () => {
+ it(`confirming a file deletion tracks ${TRACKING_ACTIONS.DELETE_PACKAGE_FILE}`, () => {
createComponent({ packageEntity: npmPackage });
findPackageFiles().vm.$emit('delete-file', npmPackage);
findDeleteFileModal().vm.$emit('primary');
expect(eventSpy).toHaveBeenCalledWith(
- category,
- TrackingActions.REQUEST_DELETE_PACKAGE_FILE,
+ TRACK_CATEGORY,
+ TRACKING_ACTIONS.REQUEST_DELETE_PACKAGE_FILE,
expect.any(Object),
);
});
- it(`canceling a file deletion tracks ${TrackingActions.CANCEL_DELETE_PACKAGE_FILE}`, () => {
+ it(`canceling a file deletion tracks ${TRACKING_ACTIONS.CANCEL_DELETE_PACKAGE_FILE}`, () => {
createComponent({ packageEntity: npmPackage });
findPackageFiles().vm.$emit('delete-file', npmPackage);
findDeleteFileModal().vm.$emit('canceled');
expect(eventSpy).toHaveBeenCalledWith(
- category,
- TrackingActions.CANCEL_DELETE_PACKAGE_FILE,
+ TRACK_CATEGORY,
+ TRACKING_ACTIONS.CANCEL_DELETE_PACKAGE_FILE,
expect.any(Object),
);
});
- it(`file download link call event with ${TrackingActions.PULL_PACKAGE}`, () => {
+ it(`file download link call event with ${TRACKING_ACTIONS.PULL_PACKAGE}`, () => {
createComponent({ packageEntity: npmPackage });
findPackageFiles().vm.$emit('download-file');
expect(eventSpy).toHaveBeenCalledWith(
- category,
- TrackingActions.PULL_PACKAGE,
+ TRACK_CATEGORY,
+ TRACKING_ACTIONS.PULL_PACKAGE,
expect.any(Object),
);
});
diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/details_title_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/details_title_spec.js
index a012ec4ab05..24bd80ba80c 100644
--- a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/details_title_spec.js
+++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/details_title_spec.js
@@ -1,8 +1,8 @@
import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
-import { terraformModule, mavenFiles, npmPackage } from 'jest/packages/mock_data';
import component from '~/packages_and_registries/infrastructure_registry/details/components/details_title.vue';
import TitleArea from '~/vue_shared/components/registry/title_area.vue';
+import { terraformModule, mavenFiles, npmPackage } from '../../mock_data';
const localVue = createLocalVue();
localVue.use(Vuex);
diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/package_files_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/package_files_spec.js
index 0c5aa30223b..6b6c33b7561 100644
--- a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/package_files_spec.js
+++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/package_files_spec.js
@@ -6,7 +6,7 @@ import component from '~/packages_and_registries/infrastructure_registry/details
import FileIcon from '~/vue_shared/components/file_icon.vue';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
-import { npmFiles, mavenFiles } from 'jest/packages/mock_data';
+import { npmFiles, mavenFiles } from '../../mock_data';
describe('Package Files', () => {
let wrapper;
diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/package_history_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/package_history_spec.js
index 4987af9f5b0..f10f05f4a0d 100644
--- a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/package_history_spec.js
+++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/package_history_spec.js
@@ -6,7 +6,7 @@ import { HISTORY_PIPELINES_LIMIT } from '~/packages_and_registries/shared/consta
import HistoryItem from '~/vue_shared/components/registry/history_item.vue';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
-import { mavenPackage, mockPipelineInfo } from 'jest/packages/mock_data';
+import { mavenPackage, mockPipelineInfo } from '../../mock_data';
describe('Package History', () => {
let wrapper;
diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/terraform_installation_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/terraform_installation_spec.js
index c26784a4b75..6ff4a4c51ef 100644
--- a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/terraform_installation_spec.js
+++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/terraform_installation_spec.js
@@ -1,8 +1,8 @@
import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
-import { terraformModule as packageEntity } from 'jest/packages/mock_data';
import TerraformInstallation from '~/packages_and_registries/infrastructure_registry/details/components/terraform_installation.vue';
import CodeInstructions from '~/vue_shared/components/registry/code_instruction.vue';
+import { terraformModule as packageEntity } from '../../mock_data';
const localVue = createLocalVue();
localVue.use(Vuex);
diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/store/actions_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/store/actions_spec.js
index 61fa69c2f7a..b9383d6c38c 100644
--- a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/store/actions_spec.js
+++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/store/actions_spec.js
@@ -12,8 +12,8 @@ import {
DELETE_PACKAGE_ERROR_MESSAGE,
DELETE_PACKAGE_FILE_ERROR_MESSAGE,
DELETE_PACKAGE_FILE_SUCCESS_MESSAGE,
-} from '~/packages/shared/constants';
-import { npmPackage as packageEntity } from '../../../../../packages/mock_data';
+} from '~/packages_and_registries/shared/constants';
+import { npmPackage as packageEntity } from '../../mock_data';
jest.mock('~/flash.js');
jest.mock('~/api.js');
diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/store/getters_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/store/getters_spec.js
index 8740691a8ee..b14aaa93e1f 100644
--- a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/store/getters_spec.js
+++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/store/getters_spec.js
@@ -3,7 +3,7 @@ import {
npmPackage,
mockPipelineInfo,
mavenPackage as packageWithoutBuildInfo,
-} from 'jest/packages/mock_data';
+} from '../../mock_data';
describe('Getters PackageDetails Store', () => {
let state;
diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/store/mutations_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/store/mutations_spec.js
index 6efefea4a14..0f0c84af7da 100644
--- a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/store/mutations_spec.js
+++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/store/mutations_spec.js
@@ -1,6 +1,6 @@
import * as types from '~/packages_and_registries/infrastructure_registry/details/store/mutation_types';
import mutations from '~/packages_and_registries/infrastructure_registry/details/store/mutations';
-import { npmPackage as packageEntity } from 'jest/packages/mock_data';
+import { npmPackage as packageEntity } from '../../mock_data';
describe('Mutations package details Store', () => {
let mockState;
diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/__snapshots__/packages_list_app_spec.js.snap b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/__snapshots__/packages_list_app_spec.js.snap
new file mode 100644
index 00000000000..99a7b8e427a
--- /dev/null
+++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/__snapshots__/packages_list_app_spec.js.snap
@@ -0,0 +1,72 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`packages_list_app renders 1`] = `
+<div>
+ <infrastructure-title-stub
+ helpurl="foo"
+ />
+
+ <infrastructure-search-stub />
+
+ <div>
+ <section
+ class="row empty-state text-center"
+ >
+ <div
+ class="col-12"
+ >
+ <div
+ class="svg-250 svg-content"
+ >
+ <img
+ alt=""
+ class="gl-max-w-full"
+ role="img"
+ src="helpSvg"
+ />
+ </div>
+ </div>
+
+ <div
+ class="col-12"
+ >
+ <div
+ class="text-content gl-mx-auto gl-my-0 gl-p-5"
+ >
+ <h1
+ class="gl-font-size-h-display gl-line-height-36 h4"
+ >
+
+ There are no packages yet
+
+ </h1>
+
+ <p
+ class="gl-mt-3"
+ >
+ Learn how to
+ <b-link-stub
+ class="gl-link"
+ event="click"
+ href="helpUrl"
+ routertag="a"
+ target="_blank"
+ >
+ publish and share your packages
+ </b-link-stub>
+ with GitLab.
+ </p>
+
+ <div
+ class="gl-display-flex gl-flex-wrap gl-justify-content-center"
+ >
+ <!---->
+
+ <!---->
+ </div>
+ </div>
+ </div>
+ </section>
+ </div>
+</div>
+`;
diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/infrastructure_search_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/infrastructure_search_spec.js
index 119b678cc37..b519ab00d06 100644
--- a/spec/frontend/packages_and_registries/infrastructure_registry/components/infrastructure_search_spec.js
+++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/infrastructure_search_spec.js
@@ -1,6 +1,6 @@
import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
-import component from '~/packages_and_registries/infrastructure_registry/components/infrastructure_search.vue';
+import component from '~/packages_and_registries/infrastructure_registry/list/components/infrastructure_search.vue';
import RegistrySearch from '~/vue_shared/components/registry/registry_search.vue';
import UrlSync from '~/vue_shared/components/url_sync.vue';
diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/infrastructure_title_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/infrastructure_title_spec.js
index db6e175b054..b0e586f189a 100644
--- a/spec/frontend/packages_and_registries/infrastructure_registry/components/infrastructure_title_spec.js
+++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/infrastructure_title_spec.js
@@ -1,5 +1,5 @@
import { shallowMount } from '@vue/test-utils';
-import component from '~/packages_and_registries/infrastructure_registry/components/infrastructure_title.vue';
+import component from '~/packages_and_registries/infrastructure_registry/list/components/infrastructure_title.vue';
import MetadataItem from '~/vue_shared/components/registry/metadata_item.vue';
import TitleArea from '~/vue_shared/components/registry/title_area.vue';
diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/packages_list_app_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/packages_list_app_spec.js
new file mode 100644
index 00000000000..cad75d2a858
--- /dev/null
+++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/packages_list_app_spec.js
@@ -0,0 +1,239 @@
+import { GlEmptyState, GlSprintf, GlLink } from '@gitlab/ui';
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import Vuex from 'vuex';
+import setWindowLocation from 'helpers/set_window_location_helper';
+import createFlash from '~/flash';
+import * as commonUtils from '~/lib/utils/common_utils';
+import PackageListApp from '~/packages_and_registries/infrastructure_registry/list/components/packages_list_app.vue';
+import { DELETE_PACKAGE_SUCCESS_MESSAGE } from '~/packages_and_registries/infrastructure_registry/list/constants';
+import {
+ SHOW_DELETE_SUCCESS_ALERT,
+ FILTERED_SEARCH_TERM,
+} from '~/packages_and_registries/shared/constants';
+
+import * as packageUtils from '~/packages_and_registries/shared/utils';
+import InfrastructureSearch from '~/packages_and_registries/infrastructure_registry/list/components/infrastructure_search.vue';
+
+jest.mock('~/lib/utils/common_utils');
+jest.mock('~/flash');
+
+const localVue = createLocalVue();
+localVue.use(Vuex);
+
+describe('packages_list_app', () => {
+ let wrapper;
+ let store;
+
+ const PackageList = {
+ name: 'package-list',
+ template: '<div><slot name="empty-state"></slot></div>',
+ };
+ const GlLoadingIcon = { name: 'gl-loading-icon', template: '<div>loading</div>' };
+
+ const emptyListHelpUrl = 'helpUrl';
+ const findEmptyState = () => wrapper.find(GlEmptyState);
+ const findListComponent = () => wrapper.find(PackageList);
+ const findInfrastructureSearch = () => wrapper.find(InfrastructureSearch);
+
+ const createStore = (filter = []) => {
+ store = new Vuex.Store({
+ state: {
+ isLoading: false,
+ config: {
+ resourceId: 'project_id',
+ emptyListIllustration: 'helpSvg',
+ emptyListHelpUrl,
+ packageHelpUrl: 'foo',
+ },
+ filter,
+ },
+ });
+ store.dispatch = jest.fn();
+ };
+
+ const mountComponent = (provide) => {
+ wrapper = shallowMount(PackageListApp, {
+ localVue,
+ store,
+ stubs: {
+ GlEmptyState,
+ GlLoadingIcon,
+ PackageList,
+ GlSprintf,
+ GlLink,
+ },
+ provide,
+ });
+ };
+
+ beforeEach(() => {
+ createStore();
+ jest.spyOn(packageUtils, 'getQueryParams').mockReturnValue({});
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('renders', () => {
+ mountComponent();
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ it('call requestPackagesList on page:changed', () => {
+ mountComponent();
+ store.dispatch.mockClear();
+
+ const list = findListComponent();
+ list.vm.$emit('page:changed', 1);
+ expect(store.dispatch).toHaveBeenCalledWith('requestPackagesList', { page: 1 });
+ });
+
+ it('call requestDeletePackage on package:delete', () => {
+ mountComponent();
+
+ const list = findListComponent();
+ list.vm.$emit('package:delete', 'foo');
+ expect(store.dispatch).toHaveBeenCalledWith('requestDeletePackage', 'foo');
+ });
+
+ it('does call requestPackagesList only one time on render', () => {
+ mountComponent();
+
+ expect(store.dispatch).toHaveBeenCalledTimes(3);
+ expect(store.dispatch).toHaveBeenNthCalledWith(1, 'setSorting', expect.any(Object));
+ expect(store.dispatch).toHaveBeenNthCalledWith(2, 'setFilter', expect.any(Array));
+ expect(store.dispatch).toHaveBeenNthCalledWith(3, 'requestPackagesList');
+ });
+
+ describe('url query string handling', () => {
+ const defaultQueryParamsMock = {
+ search: [1, 2],
+ type: 'npm',
+ sort: 'asc',
+ orderBy: 'created',
+ };
+
+ it('calls setSorting with the query string based sorting', () => {
+ jest.spyOn(packageUtils, 'getQueryParams').mockReturnValue(defaultQueryParamsMock);
+
+ mountComponent();
+
+ expect(store.dispatch).toHaveBeenNthCalledWith(1, 'setSorting', {
+ orderBy: defaultQueryParamsMock.orderBy,
+ sort: defaultQueryParamsMock.sort,
+ });
+ });
+
+ it('calls setFilter with the query string based filters', () => {
+ jest.spyOn(packageUtils, 'getQueryParams').mockReturnValue(defaultQueryParamsMock);
+
+ mountComponent();
+
+ expect(store.dispatch).toHaveBeenNthCalledWith(2, 'setFilter', [
+ { type: 'type', value: { data: defaultQueryParamsMock.type } },
+ { type: FILTERED_SEARCH_TERM, value: { data: defaultQueryParamsMock.search[0] } },
+ { type: FILTERED_SEARCH_TERM, value: { data: defaultQueryParamsMock.search[1] } },
+ ]);
+ });
+
+ it('calls setSorting and setFilters with the results of extractFilterAndSorting', () => {
+ jest
+ .spyOn(packageUtils, 'extractFilterAndSorting')
+ .mockReturnValue({ filters: ['foo'], sorting: { sort: 'desc' } });
+
+ mountComponent();
+
+ expect(store.dispatch).toHaveBeenNthCalledWith(1, 'setSorting', { sort: 'desc' });
+ expect(store.dispatch).toHaveBeenNthCalledWith(2, 'setFilter', ['foo']);
+ });
+ });
+
+ describe('empty state', () => {
+ it('generate the correct empty list link', () => {
+ mountComponent();
+
+ const link = findListComponent().find(GlLink);
+
+ expect(link.attributes('href')).toBe(emptyListHelpUrl);
+ expect(link.text()).toBe('publish and share your packages');
+ });
+
+ it('includes the right content on the default tab', () => {
+ mountComponent();
+
+ const heading = findEmptyState().find('h1');
+
+ expect(heading.text()).toBe('There are no packages yet');
+ });
+ });
+
+ describe('filter without results', () => {
+ beforeEach(() => {
+ createStore([{ type: 'something' }]);
+ mountComponent();
+ });
+
+ it('should show specific empty message', () => {
+ expect(findEmptyState().text()).toContain('Sorry, your filter produced no results');
+ expect(findEmptyState().text()).toContain(
+ 'To widen your search, change or remove the filters above',
+ );
+ });
+ });
+
+ describe('Search', () => {
+ it('exists', () => {
+ mountComponent();
+
+ expect(findInfrastructureSearch().exists()).toBe(true);
+ });
+
+ it('on update fetches data from the store', () => {
+ mountComponent();
+ store.dispatch.mockClear();
+
+ findInfrastructureSearch().vm.$emit('update');
+
+ expect(store.dispatch).toHaveBeenCalledWith('requestPackagesList');
+ });
+ });
+
+ describe('delete alert handling', () => {
+ const originalLocation = window.location.href;
+ const search = `?${SHOW_DELETE_SUCCESS_ALERT}=true`;
+
+ beforeEach(() => {
+ createStore();
+ jest.spyOn(commonUtils, 'historyReplaceState').mockImplementation(() => {});
+ setWindowLocation(search);
+ });
+
+ afterEach(() => {
+ setWindowLocation(originalLocation);
+ });
+
+ it(`creates a flash if the query string contains ${SHOW_DELETE_SUCCESS_ALERT}`, () => {
+ mountComponent();
+
+ expect(createFlash).toHaveBeenCalledWith({
+ message: DELETE_PACKAGE_SUCCESS_MESSAGE,
+ type: 'notice',
+ });
+ });
+
+ it('calls historyReplaceState with a clean url', () => {
+ mountComponent();
+
+ expect(commonUtils.historyReplaceState).toHaveBeenCalledWith(originalLocation);
+ });
+
+ it(`does nothing if the query string does not contain ${SHOW_DELETE_SUCCESS_ALERT}`, () => {
+ setWindowLocation('?');
+ mountComponent();
+
+ expect(createFlash).not.toHaveBeenCalled();
+ expect(commonUtils.historyReplaceState).not.toHaveBeenCalled();
+ });
+ });
+});
diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/packages_list_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/packages_list_spec.js
new file mode 100644
index 00000000000..2fb76b98925
--- /dev/null
+++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/packages_list_spec.js
@@ -0,0 +1,209 @@
+import { GlTable, GlPagination, GlModal } from '@gitlab/ui';
+import { mount, createLocalVue } from '@vue/test-utils';
+import { last } from 'lodash';
+import Vuex from 'vuex';
+import stubChildren from 'helpers/stub_children';
+import PackagesList from '~/packages_and_registries/infrastructure_registry/list/components/packages_list.vue';
+import PackagesListRow from '~/packages_and_registries/infrastructure_registry/shared/package_list_row.vue';
+import PackagesListLoader from '~/packages_and_registries/shared/components/packages_list_loader.vue';
+import { TRACKING_ACTIONS } from '~/packages_and_registries/shared/constants';
+import { TRACK_CATEGORY } from '~/packages_and_registries/infrastructure_registry/shared/constants';
+import Tracking from '~/tracking';
+import { packageList } from '../../mock_data';
+
+const localVue = createLocalVue();
+localVue.use(Vuex);
+
+describe('packages_list', () => {
+ let wrapper;
+ let store;
+
+ const EmptySlotStub = { name: 'empty-slot-stub', template: '<div>bar</div>' };
+
+ const findPackagesListLoader = () => wrapper.find(PackagesListLoader);
+ const findPackageListPagination = () => wrapper.find(GlPagination);
+ const findPackageListDeleteModal = () => wrapper.find(GlModal);
+ const findEmptySlot = () => wrapper.find(EmptySlotStub);
+ const findPackagesListRow = () => wrapper.find(PackagesListRow);
+
+ const createStore = (isGroupPage, packages, isLoading) => {
+ const state = {
+ isLoading,
+ packages,
+ pagination: {
+ perPage: 1,
+ total: 1,
+ page: 1,
+ },
+ config: {
+ isGroupPage,
+ },
+ sorting: {
+ orderBy: 'version',
+ sort: 'desc',
+ },
+ };
+ store = new Vuex.Store({
+ state,
+ getters: {
+ getList: () => packages,
+ },
+ });
+ store.dispatch = jest.fn();
+ };
+
+ const mountComponent = ({
+ isGroupPage = false,
+ packages = packageList,
+ isLoading = false,
+ ...options
+ } = {}) => {
+ createStore(isGroupPage, packages, isLoading);
+
+ wrapper = mount(PackagesList, {
+ localVue,
+ store,
+ stubs: {
+ ...stubChildren(PackagesList),
+ GlTable,
+ GlModal,
+ },
+ ...options,
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ describe('when is loading', () => {
+ beforeEach(() => {
+ mountComponent({
+ packages: [],
+ isLoading: true,
+ });
+ });
+
+ it('shows skeleton loader when loading', () => {
+ expect(findPackagesListLoader().exists()).toBe(true);
+ });
+ });
+
+ describe('when is not loading', () => {
+ beforeEach(() => {
+ mountComponent();
+ });
+
+ it('does not show skeleton loader when not loading', () => {
+ expect(findPackagesListLoader().exists()).toBe(false);
+ });
+ });
+
+ describe('layout', () => {
+ beforeEach(() => {
+ mountComponent();
+ });
+
+ it('contains a pagination component', () => {
+ const sorting = findPackageListPagination();
+ expect(sorting.exists()).toBe(true);
+ });
+
+ it('contains a modal component', () => {
+ const sorting = findPackageListDeleteModal();
+ expect(sorting.exists()).toBe(true);
+ });
+ });
+
+ describe('when the user can destroy the package', () => {
+ beforeEach(() => {
+ mountComponent();
+ });
+
+ it('setItemToBeDeleted sets itemToBeDeleted and open the modal', () => {
+ const mockModalShow = jest.spyOn(wrapper.vm.$refs.packageListDeleteModal, 'show');
+ const item = last(wrapper.vm.list);
+
+ findPackagesListRow().vm.$emit('packageToDelete', item);
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.vm.itemToBeDeleted).toEqual(item);
+ expect(mockModalShow).toHaveBeenCalled();
+ });
+ });
+
+ it('deleteItemConfirmation resets itemToBeDeleted', () => {
+ wrapper.setData({ itemToBeDeleted: 1 });
+ wrapper.vm.deleteItemConfirmation();
+ expect(wrapper.vm.itemToBeDeleted).toEqual(null);
+ });
+
+ it('deleteItemConfirmation emit package:delete', () => {
+ const itemToBeDeleted = { id: 2 };
+ wrapper.setData({ itemToBeDeleted });
+ wrapper.vm.deleteItemConfirmation();
+ return wrapper.vm.$nextTick(() => {
+ expect(wrapper.emitted('package:delete')[0]).toEqual([itemToBeDeleted]);
+ });
+ });
+
+ it('deleteItemCanceled resets itemToBeDeleted', () => {
+ wrapper.setData({ itemToBeDeleted: 1 });
+ wrapper.vm.deleteItemCanceled();
+ expect(wrapper.vm.itemToBeDeleted).toEqual(null);
+ });
+ });
+
+ describe('when the list is empty', () => {
+ beforeEach(() => {
+ mountComponent({
+ packages: [],
+ slots: {
+ 'empty-state': EmptySlotStub,
+ },
+ });
+ });
+
+ it('show the empty slot', () => {
+ const emptySlot = findEmptySlot();
+ expect(emptySlot.exists()).toBe(true);
+ });
+ });
+
+ describe('pagination component', () => {
+ let pagination;
+ let modelEvent;
+
+ beforeEach(() => {
+ mountComponent();
+ pagination = findPackageListPagination();
+ // retrieve the event used by v-model, a more sturdy approach than hardcoding it
+ modelEvent = pagination.vm.$options.model.event;
+ });
+
+ it('emits page:changed events when the page changes', () => {
+ pagination.vm.$emit(modelEvent, 2);
+ expect(wrapper.emitted('page:changed')).toEqual([[2]]);
+ });
+ });
+
+ describe('tracking', () => {
+ let eventSpy;
+
+ beforeEach(() => {
+ mountComponent();
+ eventSpy = jest.spyOn(Tracking, 'event');
+ wrapper.setData({ itemToBeDeleted: { package_type: 'conan' } });
+ });
+
+ it('deleteItemConfirmation calls event', () => {
+ wrapper.vm.deleteItemConfirmation();
+ expect(eventSpy).toHaveBeenCalledWith(
+ TRACK_CATEGORY,
+ TRACKING_ACTIONS.DELETE_PACKAGE,
+ expect.any(Object),
+ );
+ });
+ });
+});
diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/list/stores/actions_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/stores/actions_spec.js
new file mode 100644
index 00000000000..3fbfe1060dc
--- /dev/null
+++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/stores/actions_spec.js
@@ -0,0 +1,277 @@
+import axios from 'axios';
+import MockAdapter from 'axios-mock-adapter';
+import testAction from 'helpers/vuex_action_helper';
+import Api from '~/api';
+import createFlash from '~/flash';
+import { MISSING_DELETE_PATH_ERROR } from '~/packages_and_registries/infrastructure_registry/list/constants';
+import * as actions from '~/packages_and_registries/infrastructure_registry/list/stores/actions';
+import * as types from '~/packages_and_registries/infrastructure_registry/list/stores/mutation_types';
+import { DELETE_PACKAGE_ERROR_MESSAGE } from '~/packages_and_registries/shared/constants';
+
+jest.mock('~/flash.js');
+jest.mock('~/api.js');
+
+describe('Actions Package list store', () => {
+ const headers = 'bar';
+ let mock;
+
+ beforeEach(() => {
+ Api.projectPackages = jest.fn().mockResolvedValue({ data: 'foo', headers });
+ Api.groupPackages = jest.fn().mockResolvedValue({ data: 'baz', headers });
+ mock = new MockAdapter(axios);
+ });
+
+ afterEach(() => {
+ mock.restore();
+ });
+
+ describe('requestPackagesList', () => {
+ const sorting = {
+ sort: 'asc',
+ orderBy: 'version',
+ };
+
+ const filter = [];
+ it('should fetch the project packages list when isGroupPage is false', (done) => {
+ testAction(
+ actions.requestPackagesList,
+ undefined,
+ { config: { isGroupPage: false, resourceId: 1 }, sorting, filter },
+ [],
+ [
+ { type: 'setLoading', payload: true },
+ { type: 'receivePackagesListSuccess', payload: { data: 'foo', headers } },
+ { type: 'setLoading', payload: false },
+ ],
+ () => {
+ expect(Api.projectPackages).toHaveBeenCalledWith(1, {
+ params: { page: 1, per_page: 20, sort: sorting.sort, order_by: sorting.orderBy },
+ });
+ done();
+ },
+ );
+ });
+
+ it('should fetch the group packages list when isGroupPage is true', (done) => {
+ testAction(
+ actions.requestPackagesList,
+ undefined,
+ { config: { isGroupPage: true, resourceId: 2 }, sorting, filter },
+ [],
+ [
+ { type: 'setLoading', payload: true },
+ { type: 'receivePackagesListSuccess', payload: { data: 'baz', headers } },
+ { type: 'setLoading', payload: false },
+ ],
+ () => {
+ expect(Api.groupPackages).toHaveBeenCalledWith(2, {
+ params: { page: 1, per_page: 20, sort: sorting.sort, order_by: sorting.orderBy },
+ });
+ done();
+ },
+ );
+ });
+
+ it('should fetch packages of a certain type when a filter with a type is present', (done) => {
+ const packageType = 'maven';
+
+ testAction(
+ actions.requestPackagesList,
+ undefined,
+ {
+ config: { isGroupPage: false, resourceId: 1 },
+ sorting,
+ filter: [{ type: 'type', value: { data: 'maven' } }],
+ },
+ [],
+ [
+ { type: 'setLoading', payload: true },
+ { type: 'receivePackagesListSuccess', payload: { data: 'foo', headers } },
+ { type: 'setLoading', payload: false },
+ ],
+ () => {
+ expect(Api.projectPackages).toHaveBeenCalledWith(1, {
+ params: {
+ page: 1,
+ per_page: 20,
+ sort: sorting.sort,
+ order_by: sorting.orderBy,
+ package_type: packageType,
+ },
+ });
+ done();
+ },
+ );
+ });
+
+ it('should create flash on API error', (done) => {
+ Api.projectPackages = jest.fn().mockRejectedValue();
+ testAction(
+ actions.requestPackagesList,
+ undefined,
+ { config: { isGroupPage: false, resourceId: 2 }, sorting, filter },
+ [],
+ [
+ { type: 'setLoading', payload: true },
+ { type: 'setLoading', payload: false },
+ ],
+ () => {
+ expect(createFlash).toHaveBeenCalled();
+ done();
+ },
+ );
+ });
+
+ it('should force the terraform_module type when forceTerraform is true', (done) => {
+ testAction(
+ actions.requestPackagesList,
+ undefined,
+ { config: { isGroupPage: false, resourceId: 1, forceTerraform: true }, sorting, filter },
+ [],
+ [
+ { type: 'setLoading', payload: true },
+ { type: 'receivePackagesListSuccess', payload: { data: 'foo', headers } },
+ { type: 'setLoading', payload: false },
+ ],
+ () => {
+ expect(Api.projectPackages).toHaveBeenCalledWith(1, {
+ params: {
+ page: 1,
+ per_page: 20,
+ sort: sorting.sort,
+ order_by: sorting.orderBy,
+ package_type: 'terraform_module',
+ },
+ });
+ done();
+ },
+ );
+ });
+ });
+
+ describe('receivePackagesListSuccess', () => {
+ it('should set received packages', (done) => {
+ const data = 'foo';
+
+ testAction(
+ actions.receivePackagesListSuccess,
+ { data, headers },
+ null,
+ [
+ { type: types.SET_PACKAGE_LIST_SUCCESS, payload: data },
+ { type: types.SET_PAGINATION, payload: headers },
+ ],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('setInitialState', () => {
+ it('should commit setInitialState', (done) => {
+ testAction(
+ actions.setInitialState,
+ '1',
+ null,
+ [{ type: types.SET_INITIAL_STATE, payload: '1' }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('setLoading', () => {
+ it('should commit set main loading', (done) => {
+ testAction(
+ actions.setLoading,
+ true,
+ null,
+ [{ type: types.SET_MAIN_LOADING, payload: true }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('requestDeletePackage', () => {
+ const payload = {
+ _links: {
+ delete_api_path: 'foo',
+ },
+ };
+ it('should perform a delete operation on _links.delete_api_path', (done) => {
+ mock.onDelete(payload._links.delete_api_path).replyOnce(200);
+ Api.projectPackages = jest.fn().mockResolvedValue({ data: 'foo' });
+
+ testAction(
+ actions.requestDeletePackage,
+ payload,
+ { pagination: { page: 1 } },
+ [],
+ [
+ { type: 'setLoading', payload: true },
+ { type: 'requestPackagesList', payload: { page: 1 } },
+ ],
+ done,
+ );
+ });
+
+ it('should stop the loading and call create flash on api error', (done) => {
+ mock.onDelete(payload._links.delete_api_path).replyOnce(400);
+ testAction(
+ actions.requestDeletePackage,
+ payload,
+ null,
+ [],
+ [
+ { type: 'setLoading', payload: true },
+ { type: 'setLoading', payload: false },
+ ],
+ () => {
+ expect(createFlash).toHaveBeenCalled();
+ done();
+ },
+ );
+ });
+
+ it.each`
+ property | actionPayload
+ ${'_links'} | ${{}}
+ ${'delete_api_path'} | ${{ _links: {} }}
+ `('should reject and createFlash when $property is missing', ({ actionPayload }, done) => {
+ testAction(actions.requestDeletePackage, actionPayload, null, [], []).catch((e) => {
+ expect(e).toEqual(new Error(MISSING_DELETE_PATH_ERROR));
+ expect(createFlash).toHaveBeenCalledWith({
+ message: DELETE_PACKAGE_ERROR_MESSAGE,
+ });
+ done();
+ });
+ });
+ });
+
+ describe('setSorting', () => {
+ it('should commit SET_SORTING', (done) => {
+ testAction(
+ actions.setSorting,
+ 'foo',
+ null,
+ [{ type: types.SET_SORTING, payload: 'foo' }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('setFilter', () => {
+ it('should commit SET_FILTER', (done) => {
+ testAction(
+ actions.setFilter,
+ 'foo',
+ null,
+ [{ type: types.SET_FILTER, payload: 'foo' }],
+ [],
+ done,
+ );
+ });
+ });
+});
diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/list/stores/getters_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/stores/getters_spec.js
new file mode 100644
index 00000000000..f2d52ace34e
--- /dev/null
+++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/stores/getters_spec.js
@@ -0,0 +1,36 @@
+import getList from '~/packages_and_registries/infrastructure_registry/list/stores/getters';
+import { packageList } from '../../mock_data';
+
+describe('Getters registry list store', () => {
+ let state;
+
+ const setState = ({ isGroupPage = false } = {}) => {
+ state = {
+ packages: packageList,
+ config: {
+ isGroupPage,
+ },
+ };
+ };
+
+ beforeEach(() => setState());
+
+ afterEach(() => {
+ state = null;
+ });
+
+ describe('getList', () => {
+ it('returns a list of packages', () => {
+ const result = getList(state);
+
+ expect(result).toHaveLength(packageList.length);
+ expect(result[0].name).toBe('Test package');
+ });
+
+ it('adds projectPathName', () => {
+ const result = getList(state);
+
+ expect(result[0].projectPathName).toMatchInlineSnapshot(`"foo / bar / baz"`);
+ });
+ });
+});
diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/list/stores/mutations_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/stores/mutations_spec.js
new file mode 100644
index 00000000000..afd7a7e5439
--- /dev/null
+++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/stores/mutations_spec.js
@@ -0,0 +1,87 @@
+import * as commonUtils from '~/lib/utils/common_utils';
+import * as types from '~/packages_and_registries/infrastructure_registry/list/stores/mutation_types';
+import mutations from '~/packages_and_registries/infrastructure_registry/list/stores/mutations';
+import createState from '~/packages_and_registries/infrastructure_registry/list/stores/state';
+import { npmPackage, mavenPackage } from '../../mock_data';
+
+describe('Mutations Registry Store', () => {
+ let mockState;
+ beforeEach(() => {
+ mockState = createState();
+ });
+
+ describe('SET_INITIAL_STATE', () => {
+ it('should set the initial state', () => {
+ const config = {
+ resourceId: '1',
+ pageType: 'groups',
+ userCanDelete: '',
+ emptyListIllustration: 'foo',
+ emptyListHelpUrl: 'baz',
+ };
+
+ const expectedState = {
+ ...mockState,
+ config: {
+ ...config,
+ isGroupPage: true,
+ canDestroyPackage: true,
+ },
+ };
+ mutations[types.SET_INITIAL_STATE](mockState, config);
+
+ expect(mockState.projectId).toEqual(expectedState.projectId);
+ });
+ });
+
+ describe('SET_PACKAGE_LIST_SUCCESS', () => {
+ it('should set a packages list', () => {
+ const payload = [npmPackage, mavenPackage];
+ const expectedState = { ...mockState, packages: payload };
+ mutations[types.SET_PACKAGE_LIST_SUCCESS](mockState, payload);
+
+ expect(mockState.packages).toEqual(expectedState.packages);
+ });
+ });
+
+ describe('SET_MAIN_LOADING', () => {
+ it('should set main loading', () => {
+ mutations[types.SET_MAIN_LOADING](mockState, true);
+
+ expect(mockState.isLoading).toEqual(true);
+ });
+ });
+
+ describe('SET_PAGINATION', () => {
+ const mockPagination = { perPage: 10, page: 1 };
+ beforeEach(() => {
+ commonUtils.normalizeHeaders = jest.fn().mockReturnValue('baz');
+ commonUtils.parseIntPagination = jest.fn().mockReturnValue(mockPagination);
+ });
+ it('should set a parsed pagination', () => {
+ mutations[types.SET_PAGINATION](mockState, 'foo');
+ expect(commonUtils.normalizeHeaders).toHaveBeenCalledWith('foo');
+ expect(commonUtils.parseIntPagination).toHaveBeenCalledWith('baz');
+ expect(mockState.pagination).toEqual(mockPagination);
+ });
+ });
+
+ describe('SET_SORTING', () => {
+ it('should merge the sorting object with sort value', () => {
+ mutations[types.SET_SORTING](mockState, { sort: 'desc' });
+ expect(mockState.sorting).toEqual({ ...mockState.sorting, sort: 'desc' });
+ });
+
+ it('should merge the sorting object with order_by value', () => {
+ mutations[types.SET_SORTING](mockState, { orderBy: 'foo' });
+ expect(mockState.sorting).toEqual({ ...mockState.sorting, orderBy: 'foo' });
+ });
+ });
+
+ describe('SET_FILTER', () => {
+ it('should set the filter query', () => {
+ mutations[types.SET_FILTER](mockState, 'foo');
+ expect(mockState.filter).toEqual('foo');
+ });
+ });
+});
diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/list/utils_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/utils_spec.js
new file mode 100644
index 00000000000..a897fb90522
--- /dev/null
+++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/utils_spec.js
@@ -0,0 +1,51 @@
+import { SORT_FIELDS } from '~/packages_and_registries/infrastructure_registry/list/constants';
+import {
+ getNewPaginationPage,
+ sortableFields,
+} from '~/packages_and_registries/infrastructure_registry/list/utils';
+
+describe('Packages list utils', () => {
+ describe('sortableFields', () => {
+ it('returns the correct list when is a project page', () => {
+ expect(sortableFields()).toEqual(SORT_FIELDS.filter((f) => f.orderBy !== 'project_path'));
+ });
+ it('returns the full list on the group page', () => {
+ expect(sortableFields(true)).toEqual(SORT_FIELDS);
+ });
+ });
+ describe('packageTypeDisplay', () => {
+ it('returns the current page when total items exceeds pagniation', () => {
+ expect(getNewPaginationPage(2, 20, 21)).toBe(2);
+ });
+
+ it('returns the previous page when total items is lower than or equal to pagination', () => {
+ expect(getNewPaginationPage(2, 20, 20)).toBe(1);
+ });
+
+ it('returns the first page when totalItems is lower than or equal to perPage', () => {
+ expect(getNewPaginationPage(4, 20, 20)).toBe(1);
+ });
+
+ describe('works when a different perPage is used', () => {
+ it('returns the current page', () => {
+ expect(getNewPaginationPage(2, 10, 11)).toBe(2);
+ });
+
+ it('returns the previous page', () => {
+ expect(getNewPaginationPage(2, 10, 10)).toBe(1);
+ });
+ });
+
+ describe.each`
+ currentPage | totalItems | expectedResult
+ ${1} | ${20} | ${1}
+ ${2} | ${20} | ${1}
+ ${3} | ${40} | ${2}
+ ${4} | ${60} | ${3}
+ `(`works across numerious pages`, ({ currentPage, totalItems, expectedResult }) => {
+ it(`when currentPage is ${currentPage} return to the previous page ${expectedResult}`, () => {
+ expect(getNewPaginationPage(currentPage, 20, totalItems)).toBe(expectedResult);
+ });
+ });
+ });
+});
diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/mock_data.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/mock_data.js
new file mode 100644
index 00000000000..33b47cca68b
--- /dev/null
+++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/mock_data.js
@@ -0,0 +1,210 @@
+const _links = {
+ web_path: 'foo',
+ delete_api_path: 'bar',
+};
+
+export const mockPipelineInfo = {
+ id: 1,
+ ref: 'branch-name',
+ sha: 'sha-baz',
+ user: {
+ name: 'foo',
+ },
+ project: {
+ name: 'foo-project',
+ web_url: 'foo-project-link',
+ commit_url: 'foo-commit-link',
+ pipeline_url: 'foo-pipeline-link',
+ },
+ created_at: '2015-12-10',
+};
+
+export const mavenPackage = {
+ created_at: '2015-12-10',
+ id: 1,
+ maven_metadatum: {
+ app_group: 'com.test.app',
+ app_name: 'test-app',
+ app_version: '1.0-SNAPSHOT',
+ },
+ name: 'Test package',
+ package_type: 'maven',
+ project_path: 'foo/bar/baz',
+ projectPathName: 'foo/bar/baz',
+ project_id: 1,
+ updated_at: '2015-12-10',
+ version: '1.0.0',
+ _links,
+};
+
+export const mavenFiles = [
+ {
+ created_at: '2015-12-10',
+ file_name: 'File one',
+ id: 1,
+ size: 100,
+ download_path: '/-/package_files/1/download',
+ },
+ {
+ created_at: '2015-12-10',
+ file_name: 'File two',
+ id: 2,
+ size: 200,
+ download_path: '/-/package_files/2/download',
+ },
+];
+
+export const npmPackage = {
+ created_at: '2015-12-10',
+ id: 2,
+ name: '@Test/package',
+ package_type: 'npm',
+ project_path: 'foo/bar/baz',
+ projectPathName: 'foo/bar/baz',
+ project_id: 1,
+ updated_at: '2015-12-10',
+ version: '',
+ versions: [],
+ _links,
+ pipeline: mockPipelineInfo,
+};
+
+export const npmFiles = [
+ {
+ created_at: '2015-12-10',
+ file_name: '@test/test-package-1.0.0.tgz',
+ id: 2,
+ size: 200,
+ download_path: '/-/package_files/2/download',
+ pipelines: [
+ { id: 1, project: { commit_url: 'http://foo.bar' }, git_commit_message: 'foo bar baz?' },
+ ],
+ file_sha256: 'file_sha256',
+ file_md5: 'file_md5',
+ file_sha1: 'file_sha1',
+ },
+];
+
+export const conanPackage = {
+ conan_metadatum: {
+ package_channel: 'stable',
+ package_username: 'conan+conan-package',
+ },
+ conan_package_name: 'conan-package',
+ created_at: '2015-12-10',
+ id: 3,
+ name: 'conan-package/1.0.0@conan+conan-package/stable',
+ project_path: 'foo/bar/baz',
+ projectPathName: 'foo/bar/baz',
+ package_files: [],
+ package_type: 'conan',
+ project_id: 1,
+ updated_at: '2015-12-10',
+ version: '1.0.0',
+ _links,
+};
+
+export const dependencyLinks = {
+ withoutFramework: { name: 'Moqi', version_pattern: '2.5.6' },
+ withoutVersion: { name: 'Castle.Core', version_pattern: '' },
+ fullLink: {
+ name: 'Test.Dependency',
+ version_pattern: '2.3.7',
+ target_framework: '.NETStandard2.0',
+ },
+ anotherFullLink: {
+ name: 'Newtonsoft.Json',
+ version_pattern: '12.0.3',
+ target_framework: '.NETStandard2.0',
+ },
+};
+
+export const nugetPackage = {
+ created_at: '2015-12-10',
+ id: 4,
+ name: 'NugetPackage1',
+ package_files: [],
+ package_type: 'nuget',
+ project_id: 1,
+ tags: [],
+ updated_at: '2015-12-10',
+ version: '1.0.0',
+ dependency_links: Object.values(dependencyLinks),
+ nuget_metadatum: {
+ icon_url: 'fake-icon',
+ project_url: 'project-foo-url',
+ license_url: 'license-foo-url',
+ },
+};
+
+export const rubygemsPackage = {
+ created_at: '2015-12-10',
+ id: 4,
+ name: 'RubyGem1',
+ package_files: [],
+ package_type: 'rubygems',
+ project_id: 1,
+ tags: [],
+ updated_at: '2015-12-10',
+ version: '1.0.0',
+ rubygems_metadatum: {
+ author: 'Fake Name',
+ summary: 'My gem',
+ email: 'tanuki@fake.com',
+ },
+};
+
+export const pypiPackage = {
+ created_at: '2015-12-10',
+ id: 5,
+ name: 'PyPiPackage',
+ package_files: [],
+ package_type: 'pypi',
+ project_id: 1,
+ tags: [],
+ updated_at: '2015-12-10',
+ version: '1.0.0',
+};
+
+export const composerPackage = {
+ created_at: '2015-12-10',
+ id: 5,
+ name: 'ComposerPackage',
+ package_files: [],
+ package_type: 'composer',
+ project_id: 1,
+ tags: [],
+ updated_at: '2015-12-10',
+ version: '1.0.0',
+};
+
+export const terraformModule = {
+ created_at: '2015-12-10',
+ id: 2,
+ name: 'Test/system-22',
+ package_type: 'terraform_module',
+ project_path: 'foo/bar/baz',
+ projectPathName: 'foo/bar/baz',
+ project_id: 1,
+ updated_at: '2015-12-10',
+ version: '0.1',
+ versions: [],
+ _links,
+};
+
+export const mockTags = [
+ {
+ name: 'foo-1',
+ },
+ {
+ name: 'foo-2',
+ },
+ {
+ name: 'foo-3',
+ },
+ {
+ name: 'foo-4',
+ },
+];
+
+export const packageList = [mavenPackage, { ...npmPackage, tags: mockTags }, conanPackage];
diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/shared/__snapshots__/package_list_row_spec.js.snap b/spec/frontend/packages_and_registries/infrastructure_registry/components/shared/__snapshots__/package_list_row_spec.js.snap
new file mode 100644
index 00000000000..67c3b8b795a
--- /dev/null
+++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/shared/__snapshots__/package_list_row_spec.js.snap
@@ -0,0 +1,118 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`packages_list_row renders 1`] = `
+<div
+ class="gl-display-flex gl-flex-direction-column gl-border-b-solid gl-border-t-solid gl-border-t-1 gl-border-b-1 gl-border-t-transparent gl-border-b-gray-100"
+ data-qa-selector="package_row"
+>
+ <div
+ class="gl-display-flex gl-align-items-center gl-py-3"
+ >
+ <!---->
+
+ <div
+ class="gl-display-flex gl-xs-flex-direction-column gl-justify-content-space-between gl-align-items-stretch gl-flex-grow-1"
+ >
+ <div
+ class="gl-display-flex gl-flex-direction-column gl-xs-mb-3 gl-min-w-0 gl-flex-grow-1"
+ >
+ <div
+ class="gl-display-flex gl-align-items-center gl-text-body gl-font-weight-bold gl-min-h-6 gl-min-w-0"
+ >
+ <div
+ class="gl-display-flex gl-align-items-center gl-mr-3 gl-min-w-0"
+ >
+ <gl-link-stub
+ class="gl-text-body gl-min-w-0"
+ data-qa-selector="package_link"
+ href="foo"
+ >
+ <gl-truncate-stub
+ position="end"
+ text="Test package"
+ />
+ </gl-link-stub>
+
+ <!---->
+
+ <!---->
+ </div>
+
+ <!---->
+ </div>
+
+ <div
+ class="gl-display-flex gl-align-items-center gl-text-gray-500 gl-min-h-6 gl-min-w-0 gl-flex-grow-1"
+ >
+ <div
+ class="gl-display-flex"
+ >
+ <span>
+ 1.0.0
+ </span>
+
+ <!---->
+
+ <div />
+
+ <package-path-stub
+ path="foo/bar/baz"
+ />
+ </div>
+ </div>
+ </div>
+
+ <div
+ class="gl-display-flex gl-flex-direction-column gl-sm-align-items-flex-end gl-justify-content-space-between gl-text-gray-500 gl-flex-shrink-0"
+ >
+ <div
+ class="gl-display-flex gl-align-items-center gl-sm-text-body gl-sm-font-weight-bold gl-min-h-6"
+ >
+ <publish-method-stub
+ packageentity="[object Object]"
+ />
+ </div>
+
+ <div
+ class="gl-display-flex gl-align-items-center gl-min-h-6"
+ >
+ <span>
+ <gl-sprintf-stub
+ message="Created %{timestamp}"
+ />
+ </span>
+ </div>
+ </div>
+ </div>
+
+ <div
+ class="gl-w-9 gl-display-flex gl-justify-content-end gl-pr-1"
+ >
+ <gl-button-stub
+ aria-label="Remove package"
+ buttontextclasses=""
+ category="secondary"
+ data-testid="action-delete"
+ icon="remove"
+ size="medium"
+ title="Remove package"
+ variant="danger"
+ />
+ </div>
+ </div>
+
+ <div
+ class="gl-display-flex"
+ >
+ <div
+ class="gl-w-7"
+ />
+
+ <!---->
+
+ <div
+ class="gl-w-9"
+ />
+ </div>
+</div>
+`;
diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/infrastructure_icon_and_name_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/shared/infrastructure_icon_and_name_spec.js
index ef26c729691..abb0d23b6e4 100644
--- a/spec/frontend/packages_and_registries/infrastructure_registry/components/infrastructure_icon_and_name_spec.js
+++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/shared/infrastructure_icon_and_name_spec.js
@@ -1,6 +1,6 @@
import { GlIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import InfrastructureIconAndName from '~/packages_and_registries/infrastructure_registry/components/infrastructure_icon_and_name.vue';
+import InfrastructureIconAndName from '~/packages_and_registries/infrastructure_registry/shared/infrastructure_icon_and_name.vue';
describe('InfrastructureIconAndName', () => {
let wrapper;
diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/shared/package_list_row_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/shared/package_list_row_spec.js
new file mode 100644
index 00000000000..1052fdd1dda
--- /dev/null
+++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/shared/package_list_row_spec.js
@@ -0,0 +1,161 @@
+import { GlLink } from '@gitlab/ui';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
+
+import PackagesListRow from '~/packages_and_registries/infrastructure_registry/shared/package_list_row.vue';
+import PackagePath from '~/packages_and_registries/shared/components/package_path.vue';
+import PackageTags from '~/packages_and_registries/shared/components/package_tags.vue';
+import { PACKAGE_ERROR_STATUS } from '~/packages_and_registries/shared/constants';
+
+import ListItem from '~/vue_shared/components/registry/list_item.vue';
+import { packageList } from '../mock_data';
+
+describe('packages_list_row', () => {
+ let wrapper;
+ let store;
+
+ const [packageWithoutTags, packageWithTags] = packageList;
+
+ const InfrastructureIconAndName = { name: 'InfrastructureIconAndName', template: '<div></div>' };
+
+ const findPackageTags = () => wrapper.findComponent(PackageTags);
+ const findPackagePath = () => wrapper.findComponent(PackagePath);
+ const findDeleteButton = () => wrapper.findByTestId('action-delete');
+ const findInfrastructureIconAndName = () => wrapper.findComponent(InfrastructureIconAndName);
+ const findListItem = () => wrapper.findComponent(ListItem);
+ const findPackageLink = () => wrapper.findComponent(GlLink);
+ const findWarningIcon = () => wrapper.findByTestId('warning-icon');
+
+ const mountComponent = ({
+ isGroup = false,
+ packageEntity = packageWithoutTags,
+ showPackageType = true,
+ disableDelete = false,
+ provide,
+ } = {}) => {
+ wrapper = shallowMountExtended(PackagesListRow, {
+ store,
+ provide,
+ stubs: {
+ ListItem,
+ InfrastructureIconAndName,
+ },
+ propsData: {
+ packageLink: 'foo',
+ packageEntity,
+ isGroup,
+ showPackageType,
+ disableDelete,
+ },
+ directives: {
+ GlTooltip: createMockDirective(),
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ it('renders', () => {
+ mountComponent();
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ describe('tags', () => {
+ it('renders package tags when a package has tags', () => {
+ mountComponent({ isGroup: false, packageEntity: packageWithTags });
+
+ expect(findPackageTags().exists()).toBe(true);
+ });
+
+ it('does not render when there are no tags', () => {
+ mountComponent();
+
+ expect(findPackageTags().exists()).toBe(false);
+ });
+ });
+
+ describe('when is is group', () => {
+ it('has a package path component', () => {
+ mountComponent({ isGroup: true });
+
+ expect(findPackagePath().exists()).toBe(true);
+ expect(findPackagePath().props()).toMatchObject({ path: 'foo/bar/baz' });
+ });
+ });
+
+ describe('showPackageType', () => {
+ it('shows the type when set', () => {
+ mountComponent();
+
+ expect(findInfrastructureIconAndName().exists()).toBe(true);
+ });
+
+ it('does not show the type when not set', () => {
+ mountComponent({ showPackageType: false });
+
+ expect(findInfrastructureIconAndName().exists()).toBe(false);
+ });
+ });
+
+ describe('deleteAvailable', () => {
+ it('does not show when not set', () => {
+ mountComponent({ disableDelete: true });
+
+ expect(findDeleteButton().exists()).toBe(false);
+ });
+ });
+
+ describe('delete button', () => {
+ it('exists and has the correct props', () => {
+ mountComponent({ packageEntity: packageWithoutTags });
+
+ expect(findDeleteButton().exists()).toBe(true);
+ expect(findDeleteButton().attributes()).toMatchObject({
+ icon: 'remove',
+ category: 'secondary',
+ variant: 'danger',
+ title: 'Remove package',
+ });
+ });
+
+ it('emits the packageToDelete event when the delete button is clicked', async () => {
+ mountComponent({ packageEntity: packageWithoutTags });
+
+ findDeleteButton().vm.$emit('click');
+
+ await wrapper.vm.$nextTick();
+ expect(wrapper.emitted('packageToDelete')).toBeTruthy();
+ expect(wrapper.emitted('packageToDelete')[0]).toEqual([packageWithoutTags]);
+ });
+ });
+
+ describe(`when the package is in ${PACKAGE_ERROR_STATUS} status`, () => {
+ beforeEach(() => {
+ mountComponent({ packageEntity: { ...packageWithoutTags, status: PACKAGE_ERROR_STATUS } });
+ });
+
+ it('list item has a disabled prop', () => {
+ expect(findListItem().props('disabled')).toBe(true);
+ });
+
+ it('details link is disabled', () => {
+ expect(findPackageLink().attributes('disabled')).toBe('true');
+ });
+
+ it('has a warning icon', () => {
+ const icon = findWarningIcon();
+ const tooltip = getBinding(icon.element, 'gl-tooltip');
+ expect(icon.props('icon')).toBe('warning');
+ expect(tooltip.value).toMatchObject({
+ title: 'Invalid Package: failed metadata extraction',
+ });
+ });
+
+ it('delete button is disabled', () => {
+ expect(findDeleteButton().props('disabled')).toBe(true);
+ });
+ });
+});