Commit 7f166104 authored by Natalia Tepluhina's avatar Natalia Tepluhina

Merge branch '330846-convert-package-list-page-to-use-apollo-graphql' into 'master'

Connect list app to apollo, enable search

See merge request gitlab-org/gitlab!71280
parents 77299e52 7595ec3c
...@@ -10,10 +10,14 @@ import { historyReplaceState } from '~/lib/utils/common_utils'; ...@@ -10,10 +10,14 @@ import { historyReplaceState } from '~/lib/utils/common_utils';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import { DELETE_PACKAGE_SUCCESS_MESSAGE } from '~/packages/list/constants'; import { DELETE_PACKAGE_SUCCESS_MESSAGE } from '~/packages/list/constants';
import { SHOW_DELETE_SUCCESS_ALERT } from '~/packages/shared/constants'; import { SHOW_DELETE_SUCCESS_ALERT } from '~/packages/shared/constants';
import { FILTERED_SEARCH_TERM } from '~/packages_and_registries/shared/constants'; import getPackagesQuery from '~/packages_and_registries/package_registry/graphql/queries/get_packages.query.graphql';
import { getQueryParams, extractFilterAndSorting } from '~/packages_and_registries/shared/utils'; import {
PROJECT_RESOURCE_TYPE,
GROUP_RESOURCE_TYPE,
LIST_QUERY_DEBOUNCE_TIME,
} from '~/packages_and_registries/package_registry/constants';
import PackageTitle from './package_title.vue'; import PackageTitle from './package_title.vue';
// import PackageSearch from './package_search.vue'; import PackageSearch from './package_search.vue';
// import PackageList from './packages_list.vue'; // import PackageList from './packages_list.vue';
export default { export default {
...@@ -23,28 +27,53 @@ export default { ...@@ -23,28 +27,53 @@ export default {
// GlSprintf, // GlSprintf,
// PackageList, // PackageList,
PackageTitle, PackageTitle,
// PackageSearch, PackageSearch,
}, },
inject: ['packageHelpUrl', 'emptyListIllustration', 'emptyListHelpUrl'], inject: [
'packageHelpUrl',
'emptyListIllustration',
'emptyListHelpUrl',
'isGroupPage',
'fullPath',
],
data() { data() {
return { return {
filter: [], packages: {},
sorting: { sort: '',
sort: 'desc', filters: {},
orderBy: 'created_at',
},
selectedType: '',
pagination: {},
}; };
}, },
apollo: {
packages: {
query: getPackagesQuery,
variables() {
return this.queryVariables;
},
update(data) {
return data[this.graphqlResource].packages;
},
debounce: LIST_QUERY_DEBOUNCE_TIME,
},
},
computed: { computed: {
queryVariables() {
return {
isGroupPage: this.isGroupPage,
fullPath: this.fullPath,
sort: this.isGroupPage ? undefined : this.sort,
groupSort: this.isGroupPage ? this.sort : undefined,
packageName: this.filters?.packageName,
packageType: this.filters?.packageType,
};
},
graphqlResource() {
return this.isGroupPage ? GROUP_RESOURCE_TYPE : PROJECT_RESOURCE_TYPE;
},
packagesCount() { packagesCount() {
return 0; return this.packages?.count;
}, },
emptySearch() { hasFilters() {
return ( return this.filters.packageName && this.filters.packageType;
this.filter.filter((f) => f.type !== FILTERED_SEARCH_TERM || f.value?.data).length === 0
);
}, },
emptyStateTitle() { emptyStateTitle() {
return this.emptySearch return this.emptySearch
...@@ -53,19 +82,9 @@ export default { ...@@ -53,19 +82,9 @@ export default {
}, },
}, },
mounted() { mounted() {
const queryParams = getQueryParams(window.document.location.search);
const { sorting, filters } = extractFilterAndSorting(queryParams);
this.sorting = { ...sorting };
this.filter = [...filters];
this.checkDeleteAlert(); this.checkDeleteAlert();
}, },
methods: { methods: {
onPageChanged(page) {
return this.requestPackagesList({ page });
},
onPackageDeleteRequest(item) {
return this.requestDeletePackage(item);
},
checkDeleteAlert() { checkDeleteAlert() {
const urlParams = new URLSearchParams(window.location.search); const urlParams = new URLSearchParams(window.location.search);
const showAlert = urlParams.get(SHOW_DELETE_SUCCESS_ALERT); const showAlert = urlParams.get(SHOW_DELETE_SUCCESS_ALERT);
...@@ -76,6 +95,10 @@ export default { ...@@ -76,6 +95,10 @@ export default {
historyReplaceState(cleanUrl); historyReplaceState(cleanUrl);
} }
}, },
handleSearchUpdate({ sort, filters }) {
this.sort = sort;
this.filters = { ...filters };
},
}, },
i18n: { i18n: {
widenFilters: s__('PackageRegistry|To widen your search, change or remove the filters above.'), widenFilters: s__('PackageRegistry|To widen your search, change or remove the filters above.'),
...@@ -91,13 +114,13 @@ export default { ...@@ -91,13 +114,13 @@ export default {
<template> <template>
<div> <div>
<package-title :help-url="packageHelpUrl" :count="packagesCount" /> <package-title :help-url="packageHelpUrl" :count="packagesCount" />
<!-- <package-search @update="requestPackagesList" /> <package-search @update="handleSearchUpdate" />
<package-list @page:changed="onPageChanged" @package:delete="onPackageDeleteRequest"> <!-- <package-list @page:changed="onPageChanged" @package:delete="onPackageDeleteRequest">
<template #empty-state> <template #empty-state>
<gl-empty-state :title="emptyStateTitle" :svg-path="emptyListIllustration"> <gl-empty-state :title="emptyStateTitle" :svg-path="emptyListIllustration">
<template #description> <template #description>
<gl-sprintf v-if="!emptySearch" :message="$options.i18n.widenFilters" /> <gl-sprintf v-if="hasFilters" :message="$options.i18n.widenFilters" />
<gl-sprintf v-else :message="$options.i18n.noResultsText"> <gl-sprintf v-else :message="$options.i18n.noResultsText">
<template #noPackagesLink="{ content }"> <template #noPackagesLink="{ content }">
<gl-link :href="emptyListHelpUrl" target="_blank">{{ content }}</gl-link> <gl-link :href="emptyListHelpUrl" target="_blank">{{ content }}</gl-link>
......
<script> <script>
import { mapState, mapActions } from 'vuex';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import { sortableFields } from '~/packages/list/utils'; import { sortableFields } from '~/packages/list/utils';
import { OPERATOR_IS_ONLY } from '~/vue_shared/components/filtered_search_bar/constants'; import { OPERATOR_IS_ONLY } from '~/vue_shared/components/filtered_search_bar/constants';
import RegistrySearch from '~/vue_shared/components/registry/registry_search.vue'; import RegistrySearch from '~/vue_shared/components/registry/registry_search.vue';
import UrlSync from '~/vue_shared/components/url_sync.vue'; import UrlSync from '~/vue_shared/components/url_sync.vue';
import { getQueryParams, extractFilterAndSorting } from '~/packages_and_registries/shared/utils';
import {
FILTERED_SEARCH_TERM,
FILTERED_SEARCH_TYPE,
} from '~/packages_and_registries/shared/constants';
import PackageTypeToken from './tokens/package_type_token.vue'; import PackageTypeToken from './tokens/package_type_token.vue';
export default { export default {
...@@ -19,21 +23,71 @@ export default { ...@@ -19,21 +23,71 @@ export default {
}, },
], ],
components: { RegistrySearch, UrlSync }, components: { RegistrySearch, UrlSync },
inject: ['isGroupPage'],
data() {
return {
filters: [],
sorting: {
orderBy: 'name',
sort: 'desc',
},
mountRegistrySearch: false,
};
},
computed: { computed: {
...mapState({
isGroupPage: (state) => state.config.isGroupPage,
sorting: (state) => state.sorting,
filter: (state) => state.filter,
}),
sortableFields() { sortableFields() {
return sortableFields(this.isGroupPage); return sortableFields(this.isGroupPage);
}, },
parsedSorting() {
const cleanOrderBy = this.sorting?.orderBy.replace('_at', '');
return `${cleanOrderBy}_${this.sorting?.sort}`.toUpperCase();
},
parsedFilters() {
const parsed = {
packageName: '',
packageType: undefined,
};
return this.filters.reduce((acc, filter) => {
if (filter.type === FILTERED_SEARCH_TYPE && filter.value?.data) {
return {
...acc,
packageType: filter.value.data.toUpperCase(),
};
}
if (filter.type === FILTERED_SEARCH_TERM) {
return {
...acc,
packageName: `${acc.packageName} ${filter.value.data}`.trim(),
};
}
return acc;
}, parsed);
},
},
mounted() {
const queryParams = getQueryParams(window.document.location.search);
const { sorting, filters } = extractFilterAndSorting(queryParams);
this.updateSorting(sorting);
this.updateFilters(filters);
this.mountRegistrySearch = true;
this.emitUpdate();
}, },
methods: { methods: {
...mapActions(['setSorting', 'setFilter']), updateFilters(newValue) {
this.filters = newValue;
},
updateSorting(newValue) { updateSorting(newValue) {
this.setSorting(newValue); this.sorting = { ...this.sorting, ...newValue };
this.$emit('update'); },
updateSortingAndEmitUpdate(newValue) {
this.updateSorting(newValue);
this.emitUpdate();
},
emitUpdate() {
this.$emit('update', { sort: this.parsedSorting, filters: this.parsedFilters });
}, },
}, },
}; };
...@@ -43,13 +97,14 @@ export default { ...@@ -43,13 +97,14 @@ export default {
<url-sync> <url-sync>
<template #default="{ updateQuery }"> <template #default="{ updateQuery }">
<registry-search <registry-search
:filter="filter" v-if="mountRegistrySearch"
:filter="filters"
:sorting="sorting" :sorting="sorting"
:tokens="$options.tokens" :tokens="$options.tokens"
:sortable-fields="sortableFields" :sortable-fields="sortableFields"
@sorting:changed="updateSorting" @sorting:changed="updateSortingAndEmitUpdate"
@filter:changed="setFilter" @filter:changed="updateFilters"
@filter:submit="$emit('update')" @filter:submit="emitUpdate"
@query:changed="updateQuery" @query:changed="updateQuery"
/> />
</template> </template>
......
...@@ -89,3 +89,7 @@ export const YARN_PACKAGE_MANAGER = 'yarn'; ...@@ -89,3 +89,7 @@ export const YARN_PACKAGE_MANAGER = 'yarn';
export const PROJECT_PACKAGE_ENDPOINT_TYPE = 'project'; export const PROJECT_PACKAGE_ENDPOINT_TYPE = 'project';
export const INSTANCE_PACKAGE_ENDPOINT_TYPE = 'instance'; export const INSTANCE_PACKAGE_ENDPOINT_TYPE = 'instance';
export const PROJECT_RESOURCE_TYPE = 'project';
export const GROUP_RESOURCE_TYPE = 'group';
export const LIST_QUERY_DEBOUNCE_TIME = 50;
#import "~/packages_and_registries/package_registry/graphql/fragments/package_data.fragment.graphql"
query getPackages(
$fullPath: ID!
$isGroupPage: Boolean!
$sort: PackageSort
$groupSort: PackageGroupSort
$packageName: String
$packageType: PackageTypeEnum
) {
project(fullPath: $fullPath) @skip(if: $isGroupPage) {
packages(sort: $sort, packageName: $packageName, packageType: $packageType) {
count
nodes {
...PackageData
}
}
}
group(fullPath: $fullPath) @include(if: $isGroupPage) {
packages(sort: $groupSort, packageName: $packageName, packageType: $packageType) {
count
nodes {
...PackageData
}
}
}
}
import Vue from 'vue'; import Vue from 'vue';
import Translate from '~/vue_shared/translate'; import Translate from '~/vue_shared/translate';
import { apolloProvider } from '~/packages_and_registries/package_registry/graphql/index';
import PackagesListApp from '../components/list/app.vue'; import PackagesListApp from '../components/list/app.vue';
Vue.use(Translate); Vue.use(Translate);
...@@ -7,10 +8,14 @@ Vue.use(Translate); ...@@ -7,10 +8,14 @@ Vue.use(Translate);
export default () => { export default () => {
const el = document.getElementById('js-vue-packages-list'); const el = document.getElementById('js-vue-packages-list');
const isGroupPage = el.dataset.pageType === 'groups';
return new Vue({ return new Vue({
el, el,
apolloProvider,
provide: { provide: {
...el.dataset, ...el.dataset,
isGroupPage,
}, },
render(createElement) { render(createElement) {
return createElement(PackagesListApp); return createElement(PackagesListApp);
......
export const FILTERED_SEARCH_TERM = 'filtered-search-term'; export const FILTERED_SEARCH_TERM = 'filtered-search-term';
export const FILTERED_SEARCH_TYPE = 'type';
...@@ -41,6 +41,7 @@ module PackagesHelper ...@@ -41,6 +41,7 @@ module PackagesHelper
def packages_list_data(type, resource) def packages_list_data(type, resource)
{ {
resource_id: resource.id, resource_id: resource.id,
full_path: resource.full_path,
page_type: type, page_type: type,
empty_list_help_url: help_page_path('user/packages/package_registry/index'), empty_list_help_url: help_page_path('user/packages/package_registry/index'),
empty_list_illustration: image_path('illustrations/no-packages.svg'), empty_list_illustration: image_path('illustrations/no-packages.svg'),
......
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`packages_list_app renders 1`] = ` exports[`PackagesListApp renders 1`] = `
<div> <div>
<package-title-stub <package-title-stub
count="0" count="2"
helpurl="packageHelpUrl" helpurl="packageHelpUrl"
/> />
<package-search-stub />
</div> </div>
`; `;
import { GlEmptyState, GlSprintf, GlLink } from '@gitlab/ui'; import { GlEmptyState, GlSprintf, GlLink } from '@gitlab/ui';
import { createLocalVue } from '@vue/test-utils';
import VueApollo from 'vue-apollo';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; 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 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 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 {
PROJECT_RESOURCE_TYPE,
GROUP_RESOURCE_TYPE,
LIST_QUERY_DEBOUNCE_TIME,
} from '~/packages_and_registries/package_registry/constants';
import getPackagesQuery from '~/packages_and_registries/package_registry/graphql/queries/get_packages.query.graphql';
import * as packageUtils from '~/packages_and_registries/shared/utils'; import { packagesListQuery } from '../../mock_data';
jest.mock('~/lib/utils/common_utils'); jest.mock('~/lib/utils/common_utils');
jest.mock('~/flash'); jest.mock('~/flash');
describe('packages_list_app', () => { const localVue = createLocalVue();
describe('PackagesListApp', () => {
let wrapper; let wrapper;
let apolloProvider;
const defaultProvide = {
packageHelpUrl: 'packageHelpUrl',
emptyListIllustration: 'emptyListIllustration',
emptyListHelpUrl: 'emptyListHelpUrl',
isGroupPage: true,
fullPath: 'gitlab-org',
};
const PackageList = { const PackageList = {
name: 'package-list', name: 'package-list',
...@@ -18,9 +43,21 @@ describe('packages_list_app', () => { ...@@ -18,9 +43,21 @@ describe('packages_list_app', () => {
const GlLoadingIcon = { name: 'gl-loading-icon', template: '<div>loading</div>' }; const GlLoadingIcon = { name: 'gl-loading-icon', template: '<div>loading</div>' };
const findPackageTitle = () => wrapper.findComponent(PackageTitle); const findPackageTitle = () => wrapper.findComponent(PackageTitle);
const findSearch = () => wrapper.findComponent(PackageSearch);
const mountComponent = ({
resolver = jest.fn().mockResolvedValue(packagesListQuery()),
provide = defaultProvide,
} = {}) => {
localVue.use(VueApollo);
const requestHandlers = [[getPackagesQuery, resolver]];
apolloProvider = createMockApollo(requestHandlers);
const mountComponent = () => {
wrapper = shallowMountExtended(PackageListApp, { wrapper = shallowMountExtended(PackageListApp, {
localVue,
apolloProvider,
provide,
stubs: { stubs: {
GlEmptyState, GlEmptyState,
GlLoadingIcon, GlLoadingIcon,
...@@ -28,30 +65,90 @@ describe('packages_list_app', () => { ...@@ -28,30 +65,90 @@ describe('packages_list_app', () => {
GlSprintf, GlSprintf,
GlLink, GlLink,
}, },
provide: {
packageHelpUrl: 'packageHelpUrl',
emptyListIllustration: 'emptyListIllustration',
emptyListHelpUrl: 'emptyListHelpUrl',
},
}); });
}; };
beforeEach(() => {
jest.spyOn(packageUtils, 'getQueryParams').mockReturnValue({});
});
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
}); });
it('renders', () => { const waitForDebouncedApollo = () => {
jest.advanceTimersByTime(LIST_QUERY_DEBOUNCE_TIME);
return waitForPromises();
};
it('renders', async () => {
mountComponent(); mountComponent();
await waitForDebouncedApollo();
expect(wrapper.element).toMatchSnapshot(); expect(wrapper.element).toMatchSnapshot();
}); });
it('has a package title', () => { it('has a package title', async () => {
mountComponent(); mountComponent();
await waitForDebouncedApollo();
expect(findPackageTitle().exists()).toBe(true); expect(findPackageTitle().exists()).toBe(true);
expect(findPackageTitle().props('count')).toBe(2);
});
describe('search component', () => {
it('exists', () => {
mountComponent();
expect(findSearch().exists()).toBe(true);
});
it('on update triggers a new query with updated values', async () => {
const resolver = jest.fn().mockResolvedValue(packagesListQuery());
mountComponent({ resolver });
const payload = {
sort: 'VERSION_DESC',
filters: { packageName: 'foo', packageType: 'CONAN' },
};
findSearch().vm.$emit('update', payload);
await waitForDebouncedApollo();
jest.advanceTimersByTime(LIST_QUERY_DEBOUNCE_TIME);
expect(resolver).toHaveBeenCalledWith(
expect.objectContaining({
groupSort: payload.sort,
...payload.filters,
}),
);
});
});
describe.each`
type | sortType
${PROJECT_RESOURCE_TYPE} | ${'sort'}
${GROUP_RESOURCE_TYPE} | ${'groupSort'}
`('$type query', ({ type, sortType }) => {
let provide;
let resolver;
const isGroupPage = type === GROUP_RESOURCE_TYPE;
beforeEach(() => {
provide = { ...defaultProvide, isGroupPage };
resolver = jest.fn().mockResolvedValue(packagesListQuery(type));
mountComponent({ provide, resolver });
return waitForDebouncedApollo();
});
it('succeeds', () => {
expect(findPackageTitle().props('count')).toBe(2);
});
it('calls the resolver with the right parameters', () => {
expect(resolver).toHaveBeenCalledWith(
expect.objectContaining({ isGroupPage, [sortType]: '' }),
);
});
}); });
}); });
import { shallowMount, createLocalVue } from '@vue/test-utils'; import { nextTick } from 'vue';
import Vuex from 'vuex'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { sortableFields } from '~/packages/list/utils'; import { sortableFields } from '~/packages/list/utils';
import component from '~/packages_and_registries/package_registry/components/list/package_search.vue'; 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 PackageTypeToken from '~/packages_and_registries/package_registry/components/list/tokens/package_type_token.vue';
import RegistrySearch from '~/vue_shared/components/registry/registry_search.vue'; import RegistrySearch from '~/vue_shared/components/registry/registry_search.vue';
import UrlSync from '~/vue_shared/components/url_sync.vue'; import UrlSync from '~/vue_shared/components/url_sync.vue';
import { useMockLocationHelper } from 'helpers/mock_window_location_helper';
import { getQueryParams, extractFilterAndSorting } from '~/packages_and_registries/shared/utils';
const localVue = createLocalVue(); jest.mock('~/packages_and_registries/shared/utils');
localVue.use(Vuex);
useMockLocationHelper();
describe('Package Search', () => { describe('Package Search', () => {
let wrapper; let wrapper;
let store;
const defaultQueryParamsMock = {
filters: ['foo'],
sorting: { sort: 'desc' },
};
const findRegistrySearch = () => wrapper.findComponent(RegistrySearch); const findRegistrySearch = () => wrapper.findComponent(RegistrySearch);
const findUrlSync = () => wrapper.findComponent(UrlSync); const findUrlSync = () => wrapper.findComponent(UrlSync);
const createStore = (isGroupPage) => {
const state = {
config: {
isGroupPage,
},
sorting: {
orderBy: 'version',
sort: 'desc',
},
filter: [],
};
store = new Vuex.Store({
state,
});
store.dispatch = jest.fn();
};
const mountComponent = (isGroupPage = false) => { const mountComponent = (isGroupPage = false) => {
createStore(isGroupPage); wrapper = shallowMountExtended(component, {
provide() {
wrapper = shallowMount(component, { return {
localVue, isGroupPage,
store, };
},
stubs: { stubs: {
UrlSync, UrlSync,
}, },
}); });
}; };
beforeEach(() => {
extractFilterAndSorting.mockReturnValue(defaultQueryParamsMock);
});
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
wrapper = null;
}); });
it('has a registry search component', () => { it('has a registry search component', async () => {
mountComponent(); mountComponent();
await nextTick();
expect(findRegistrySearch().exists()).toBe(true); expect(findRegistrySearch().exists()).toBe(true);
expect(findRegistrySearch().props()).toMatchObject({ });
filter: store.state.filter,
sorting: store.state.sorting, it('registry search is mounted after mount', async () => {
tokens: expect.arrayContaining([ mountComponent();
expect.objectContaining({ token: PackageTypeToken, type: 'type', icon: 'package' }),
]), expect(findRegistrySearch().exists()).toBe(false);
sortableFields: sortableFields(), });
});
it('has a UrlSync component', () => {
mountComponent();
expect(findUrlSync().exists()).toBe(true);
}); });
it.each` it.each`
isGroupPage | page isGroupPage | page
${false} | ${'project'} ${false} | ${'project'}
${true} | ${'group'} ${true} | ${'group'}
`('in a $page page binds the right props', ({ isGroupPage }) => { `('in a $page page binds the right props', async ({ isGroupPage }) => {
mountComponent(isGroupPage); mountComponent(isGroupPage);
await nextTick();
expect(findRegistrySearch().props()).toMatchObject({ expect(findRegistrySearch().props()).toMatchObject({
filter: store.state.filter,
sorting: store.state.sorting,
tokens: expect.arrayContaining([ tokens: expect.arrayContaining([
expect.objectContaining({ token: PackageTypeToken, type: 'type', icon: 'package' }), expect.objectContaining({ token: PackageTypeToken, type: 'type', icon: 'package' }),
]), ]),
...@@ -81,48 +81,85 @@ describe('Package Search', () => { ...@@ -81,48 +81,85 @@ describe('Package Search', () => {
}); });
}); });
it('on sorting:changed emits update event and calls vuex setSorting', () => { it('on sorting:changed emits update event and update internal sort', async () => {
const payload = { sort: 'foo' }; const payload = { sort: 'foo' };
mountComponent(); mountComponent();
await nextTick();
findRegistrySearch().vm.$emit('sorting:changed', payload); findRegistrySearch().vm.$emit('sorting:changed', payload);
expect(store.dispatch).toHaveBeenCalledWith('setSorting', payload); await nextTick();
expect(wrapper.emitted('update')).toEqual([[]]);
expect(findRegistrySearch().props('sorting')).toEqual({ sort: 'foo', orderBy: 'name' });
// there is always a first call on mounted that emits up default values
expect(wrapper.emitted('update')[1]).toEqual([
{
filters: {
packageName: '',
packageType: undefined,
},
sort: 'NAME_FOO',
},
]);
}); });
it('on filter:changed calls vuex setFilter', () => { it('on filter:changed updates the filters', async () => {
const payload = ['foo']; const payload = ['foo'];
mountComponent(); mountComponent();
await nextTick();
findRegistrySearch().vm.$emit('filter:changed', payload); findRegistrySearch().vm.$emit('filter:changed', payload);
expect(store.dispatch).toHaveBeenCalledWith('setFilter', payload); await nextTick();
expect(findRegistrySearch().props('filter')).toEqual(['foo']);
}); });
it('on filter:submit emits update event', () => { it('on filter:submit emits update event', async () => {
mountComponent(); mountComponent();
findRegistrySearch().vm.$emit('filter:submit'); await nextTick();
expect(wrapper.emitted('update')).toEqual([[]]);
});
it('has a UrlSync component', () => { findRegistrySearch().vm.$emit('filter:submit');
mountComponent();
expect(findUrlSync().exists()).toBe(true); expect(wrapper.emitted('update')[1]).toEqual([
{
filters: {
packageName: '',
packageType: undefined,
},
sort: 'NAME_DESC',
},
]);
}); });
it('on query:changed calls updateQuery from UrlSync', () => { it('on query:changed calls updateQuery from UrlSync', async () => {
jest.spyOn(UrlSync.methods, 'updateQuery').mockImplementation(() => {}); jest.spyOn(UrlSync.methods, 'updateQuery').mockImplementation(() => {});
mountComponent(); mountComponent();
await nextTick();
findRegistrySearch().vm.$emit('query:changed'); findRegistrySearch().vm.$emit('query:changed');
expect(UrlSync.methods.updateQuery).toHaveBeenCalled(); expect(UrlSync.methods.updateQuery).toHaveBeenCalled();
}); });
it('sets the component sorting and filtering based on the querystring', async () => {
mountComponent();
await nextTick();
expect(getQueryParams).toHaveBeenCalled();
expect(findRegistrySearch().props()).toMatchObject({
filter: defaultQueryParamsMock.filters,
sorting: defaultQueryParamsMock.sorting,
});
});
}); });
...@@ -249,3 +249,27 @@ export const packageDestroyFileMutationError = () => ({ ...@@ -249,3 +249,27 @@ export const packageDestroyFileMutationError = () => ({
}, },
], ],
}); });
export const packagesListQuery = (type = 'group') => ({
data: {
[type]: {
packages: {
count: 2,
nodes: [
{
__typename: 'Package',
id: 'gid://gitlab/Packages::Package/247',
name: 'version_test1',
},
{
__typename: 'Package',
id: 'gid://gitlab/Packages::Package/246',
name: 'version_test1',
},
],
__typename: 'PackageConnection',
},
__typename: 'Group',
},
},
});
...@@ -260,4 +260,34 @@ RSpec.describe PackagesHelper do ...@@ -260,4 +260,34 @@ RSpec.describe PackagesHelper do
end end
end end
end end
describe '#packages_list_data' do
let_it_be(:resource) { project }
let_it_be(:type) { 'project' }
let(:expected_result) do
{
resource_id: resource.id,
full_path: resource.full_path,
page_type: type
}
end
subject(:result) { helper.packages_list_data(type, resource) }
context 'at a project level' do
it 'populates presenter data' do
expect(result).to match(hash_including(expected_result))
end
end
context 'at a group level' do
let_it_be(:resource) { create(:group) }
let_it_be(:type) { 'group' }
it 'populates presenter data' do
expect(result).to match(hash_including(expected_result))
end
end
end
end end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment