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/ci/catalog')
-rw-r--r--spec/frontend/ci/catalog/components/details/ci_resource_components_spec.js16
-rw-r--r--spec/frontend/ci/catalog/components/details/ci_resource_details_spec.js2
-rw-r--r--spec/frontend/ci/catalog/components/details/ci_resource_header_spec.js8
-rw-r--r--spec/frontend/ci/catalog/components/details/ci_resource_readme_spec.js3
-rw-r--r--spec/frontend/ci/catalog/components/list/catalog_header_spec.js18
-rw-r--r--spec/frontend/ci/catalog/components/list/catalog_search_spec.js103
-rw-r--r--spec/frontend/ci/catalog/components/list/ci_resources_list_item_spec.js118
-rw-r--r--spec/frontend/ci/catalog/components/list/empty_state_spec.js64
-rw-r--r--spec/frontend/ci/catalog/components/pages/ci_resource_details_page_spec.js8
-rw-r--r--spec/frontend/ci/catalog/components/pages/ci_resources_page_spec.js118
-rw-r--r--spec/frontend/ci/catalog/mock.js194
11 files changed, 413 insertions, 239 deletions
diff --git a/spec/frontend/ci/catalog/components/details/ci_resource_components_spec.js b/spec/frontend/ci/catalog/components/details/ci_resource_components_spec.js
index 382f8e46203..330163e9f39 100644
--- a/spec/frontend/ci/catalog/components/details/ci_resource_components_spec.js
+++ b/spec/frontend/ci/catalog/components/details/ci_resource_components_spec.js
@@ -2,7 +2,6 @@ import Vue from 'vue';
import VueApollo from 'vue-apollo';
import { GlEmptyState, GlLoadingIcon } from '@gitlab/ui';
import { mountExtended } from 'helpers/vue_test_utils_helper';
-import { resolvers } from '~/ci/catalog/graphql/settings';
import CiResourceComponents from '~/ci/catalog/components/details/ci_resource_components.vue';
import getCiCatalogcomponentComponents from '~/ci/catalog/graphql/queries/get_ci_catalog_resource_components.query.graphql';
import createMockApollo from 'helpers/mock_apollo_helper';
@@ -17,15 +16,15 @@ describe('CiResourceComponents', () => {
let wrapper;
let mockComponentsResponse;
- const components = mockComponents.data.ciCatalogResource.components.nodes;
+ const components = mockComponents.data.ciCatalogResource.latestVersion.components.nodes;
- const resourceId = 'gid://gitlab/Ci::Catalog::Resource/1';
+ const resourcePath = 'twitter/project-1';
- const defaultProps = { resourceId };
+ const defaultProps = { resourcePath };
const createComponent = async () => {
const handlers = [[getCiCatalogcomponentComponents, mockComponentsResponse]];
- const mockApollo = createMockApollo(handlers, resolvers);
+ const mockApollo = createMockApollo(handlers);
wrapper = mountExtended(CiResourceComponents, {
propsData: {
@@ -113,10 +112,9 @@ describe('CiResourceComponents', () => {
expect(findComponents()).toHaveLength(components.length);
});
- it('renders the component name, description and snippet', () => {
+ it('renders the component name and snippet', () => {
components.forEach((component) => {
expect(wrapper.text()).toContain(component.name);
- expect(wrapper.text()).toContain(component.description);
expect(wrapper.text()).toContain(component.path);
});
});
@@ -134,9 +132,9 @@ describe('CiResourceComponents', () => {
it('renders the component parameter attributes', () => {
const [firstComponent] = components;
- firstComponent.inputs.nodes.forEach((input) => {
+ firstComponent.inputs.forEach((input) => {
expect(findComponents().at(0).text()).toContain(input.name);
- expect(findComponents().at(0).text()).toContain(input.defaultValue);
+ expect(findComponents().at(0).text()).toContain(input.default);
expect(findComponents().at(0).text()).toContain('Yes');
});
});
diff --git a/spec/frontend/ci/catalog/components/details/ci_resource_details_spec.js b/spec/frontend/ci/catalog/components/details/ci_resource_details_spec.js
index 1f7dcf9d4e5..e4b6c1cd046 100644
--- a/spec/frontend/ci/catalog/components/details/ci_resource_details_spec.js
+++ b/spec/frontend/ci/catalog/components/details/ci_resource_details_spec.js
@@ -8,7 +8,7 @@ describe('CiResourceDetails', () => {
let wrapper;
const defaultProps = {
- resourceId: 'gid://gitlab/Ci::Catalog::Resource/1',
+ resourcePath: 'twitter/project-1',
};
const defaultProvide = {
glFeatures: { ciCatalogComponentsTab: true },
diff --git a/spec/frontend/ci/catalog/components/details/ci_resource_header_spec.js b/spec/frontend/ci/catalog/components/details/ci_resource_header_spec.js
index c061332ba13..6af9daabea0 100644
--- a/spec/frontend/ci/catalog/components/details/ci_resource_header_spec.js
+++ b/spec/frontend/ci/catalog/components/details/ci_resource_header_spec.js
@@ -3,7 +3,7 @@ import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import CiResourceHeader from '~/ci/catalog/components/details/ci_resource_header.vue';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import CiResourceAbout from '~/ci/catalog/components/details/ci_resource_about.vue';
-import CiIcon from '~/vue_shared/components/ci_icon.vue';
+import CiIcon from '~/vue_shared/components/ci_icon/ci_icon.vue';
import { catalogSharedDataMock, catalogAdditionalDetailsMock } from '../../mock';
describe('CiResourceHeader', () => {
@@ -45,9 +45,9 @@ describe('CiResourceHeader', () => {
expect(wrapper.html()).toContain(resource.description);
});
- it('renders the namespace and project path', () => {
- expect(wrapper.html()).toContain(resource.rootNamespace.fullPath);
- expect(wrapper.html()).toContain(resource.rootNamespace.name);
+ it('renders the project path and name', () => {
+ expect(wrapper.html()).toContain(resource.webPath);
+ expect(wrapper.html()).toContain(resource.name);
});
it('renders the avatar', () => {
diff --git a/spec/frontend/ci/catalog/components/details/ci_resource_readme_spec.js b/spec/frontend/ci/catalog/components/details/ci_resource_readme_spec.js
index 0dadac236a8..ad76b47db57 100644
--- a/spec/frontend/ci/catalog/components/details/ci_resource_readme_spec.js
+++ b/spec/frontend/ci/catalog/components/details/ci_resource_readme_spec.js
@@ -23,12 +23,13 @@ describe('CiResourceReadme', () => {
data: {
ciCatalogResource: {
id: resourceId,
+ webPath: 'twitter/project-1',
readmeHtml,
},
},
};
- const defaultProps = { resourceId };
+ const defaultProps = { resourcePath: readmeMockData.data.ciCatalogResource.webPath };
const createComponent = ({ props = {} } = {}) => {
const handlers = [[getCiCatalogResourceReadme, mockReadmeResponse]];
diff --git a/spec/frontend/ci/catalog/components/list/catalog_header_spec.js b/spec/frontend/ci/catalog/components/list/catalog_header_spec.js
index 2a5c24d0515..e9d2e68c1a3 100644
--- a/spec/frontend/ci/catalog/components/list/catalog_header_spec.js
+++ b/spec/frontend/ci/catalog/components/list/catalog_header_spec.js
@@ -1,6 +1,7 @@
import { GlBanner, GlButton } from '@gitlab/ui';
import { useLocalStorageSpy } from 'helpers/local_storage_helper';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import BetaBadge from '~/vue_shared/components/badges/beta_badge.vue';
import CatalogHeader from '~/ci/catalog/components/list/catalog_header.vue';
import { CATALOG_FEEDBACK_DISMISSED_KEY } from '~/ci/catalog/constants';
@@ -16,9 +17,10 @@ describe('CatalogHeader', () => {
};
const findBanner = () => wrapper.findComponent(GlBanner);
+ const findBetaBadge = () => wrapper.findComponent(BetaBadge);
const findFeedbackButton = () => findBanner().findComponent(GlButton);
const findTitle = () => wrapper.find('h1');
- const findDescription = () => wrapper.findByTestId('description');
+ const findDescription = () => wrapper.findByTestId('page-description');
const createComponent = ({ props = {}, provide = {}, stubs = {} } = {}) => {
wrapper = shallowMountExtended(CatalogHeader, {
@@ -33,6 +35,16 @@ describe('CatalogHeader', () => {
});
};
+ describe('Default view', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('renders a Beta Badge', () => {
+ expect(findBetaBadge().exists()).toBe(true);
+ });
+ });
+
describe('title and description', () => {
describe('when there are no values provided', () => {
beforeEach(() => {
@@ -42,10 +54,11 @@ describe('CatalogHeader', () => {
it('renders the default values', () => {
expect(findTitle().text()).toBe('CI/CD Catalog');
expect(findDescription().text()).toBe(
- 'Discover CI configuration resources for a seamless CI/CD experience.',
+ 'Discover CI/CD components that can improve your pipeline with additional functionality.',
);
});
});
+
describe('when custom values are provided', () => {
beforeEach(() => {
createComponent({ provide: customProvide });
@@ -57,6 +70,7 @@ describe('CatalogHeader', () => {
});
});
});
+
describe('Feedback banner', () => {
describe('when user has never dismissed', () => {
beforeEach(() => {
diff --git a/spec/frontend/ci/catalog/components/list/catalog_search_spec.js b/spec/frontend/ci/catalog/components/list/catalog_search_spec.js
new file mode 100644
index 00000000000..c6f8498f2fd
--- /dev/null
+++ b/spec/frontend/ci/catalog/components/list/catalog_search_spec.js
@@ -0,0 +1,103 @@
+import { GlSearchBoxByClick, GlSorting, GlSortingItem } from '@gitlab/ui';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import CatalogSearch from '~/ci/catalog/components/list/catalog_search.vue';
+import { SORT_ASC, SORT_DESC, SORT_OPTION_CREATED } from '~/ci/catalog/constants';
+
+describe('CatalogSearch', () => {
+ let wrapper;
+
+ const findSearchBar = () => wrapper.findComponent(GlSearchBoxByClick);
+ const findSorting = () => wrapper.findComponent(GlSorting);
+ const findAllSortingItems = () => wrapper.findAllComponents(GlSortingItem);
+
+ const createComponent = () => {
+ wrapper = shallowMountExtended(CatalogSearch, {});
+ };
+
+ beforeEach(() => {
+ createComponent();
+ });
+
+ describe('default UI', () => {
+ it('renders the search bar', () => {
+ expect(findSearchBar().exists()).toBe(true);
+ });
+
+ it('renders the sorting options', () => {
+ expect(findSorting().exists()).toBe(true);
+ expect(findAllSortingItems()).toHaveLength(1);
+ });
+
+ it('renders the `Created at` option as the default', () => {
+ expect(findAllSortingItems().at(0).text()).toBe('Created at');
+ });
+ });
+
+ describe('search', () => {
+ it('passes down the search value to the search component', async () => {
+ const newSearchTerm = 'cat';
+
+ expect(findSearchBar().props().value).toBe('');
+
+ await findSearchBar().vm.$emit('input', newSearchTerm);
+
+ expect(findSearchBar().props().value).toBe(newSearchTerm);
+ });
+
+ it('does not submit only when typing', async () => {
+ expect(wrapper.emitted('update-search-term')).toBeUndefined();
+
+ await findSearchBar().vm.$emit('input', 'new');
+
+ expect(wrapper.emitted('update-search-term')).toBeUndefined();
+ });
+
+ describe('when submitting the search', () => {
+ const newSearchTerm = 'dog';
+
+ beforeEach(async () => {
+ await findSearchBar().vm.$emit('input', newSearchTerm);
+ await findSearchBar().vm.$emit('submit');
+ });
+
+ it('emits the event up with the new payload', () => {
+ expect(wrapper.emitted('update-search-term')).toEqual([[newSearchTerm]]);
+ });
+ });
+
+ describe('when clearing the search', () => {
+ beforeEach(async () => {
+ await findSearchBar().vm.$emit('input', 'new');
+ await findSearchBar().vm.$emit('clear');
+ });
+
+ it('emits an update event with an empty string payload', () => {
+ expect(wrapper.emitted('update-search-term')).toEqual([['']]);
+ });
+ });
+ });
+
+ describe('sort', () => {
+ describe('when changing sort order', () => {
+ it('changes the `isAscending` prop to the sorting component', async () => {
+ expect(findSorting().props().isAscending).toBe(false);
+
+ await findSorting().vm.$emit('sortDirectionChange');
+
+ expect(findSorting().props().isAscending).toBe(true);
+ });
+
+ it('emits an `update-sorting` event with the new direction', async () => {
+ expect(wrapper.emitted('update-sorting')).toBeUndefined();
+
+ await findSorting().vm.$emit('sortDirectionChange');
+ await findSorting().vm.$emit('sortDirectionChange');
+
+ expect(wrapper.emitted('update-sorting')).toEqual([
+ [`${SORT_OPTION_CREATED}_${SORT_ASC}`],
+ [`${SORT_OPTION_CREATED}_${SORT_DESC}`],
+ ]);
+ });
+ });
+ });
+});
diff --git a/spec/frontend/ci/catalog/components/list/ci_resources_list_item_spec.js b/spec/frontend/ci/catalog/components/list/ci_resources_list_item_spec.js
index 3862195d8c7..d74b133f386 100644
--- a/spec/frontend/ci/catalog/components/list/ci_resources_list_item_spec.js
+++ b/spec/frontend/ci/catalog/components/list/ci_resources_list_item_spec.js
@@ -1,21 +1,22 @@
import Vue from 'vue';
import VueRouter from 'vue-router';
-import { GlAvatar, GlBadge, GlButton, GlSprintf } from '@gitlab/ui';
+import { GlAvatar, GlBadge, GlSprintf } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import { cleanLeadingSeparator } from '~/lib/utils/url_utility';
import { createRouter } from '~/ci/catalog/router/index';
import CiResourcesListItem from '~/ci/catalog/components/list/ci_resources_list_item.vue';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
-import { CI_RESOURCE_DETAILS_PAGE_NAME } from '~/ci/catalog/router/constants';
import { catalogSinglePageResponse } from '../../mock';
Vue.use(VueRouter);
-let router;
-let routerPush;
+const defaultEvent = { preventDefault: jest.fn, ctrlKey: false, metaKey: false };
describe('CiResourcesListItem', () => {
let wrapper;
+ let routerPush;
+ const router = createRouter();
const resource = catalogSinglePageResponse.data.ciCatalogResources.nodes[0];
const release = {
author: { name: 'author', webUrl: '/user/1' },
@@ -35,22 +36,19 @@ describe('CiResourcesListItem', () => {
},
stubs: {
GlSprintf,
- RouterLink: true,
- RouterView: true,
},
});
};
const findAvatar = () => wrapper.findComponent(GlAvatar);
const findBadge = () => wrapper.findComponent(GlBadge);
- const findResourceName = () => wrapper.findComponent(GlButton);
+ const findResourceName = () => wrapper.findByTestId('ci-resource-link');
const findResourceDescription = () => wrapper.findByText(defaultProps.resource.description);
const findUserLink = () => wrapper.findByTestId('user-link');
const findTimeAgoMessage = () => wrapper.findComponent(GlSprintf);
const findFavorites = () => wrapper.findByTestId('stats-favorites');
beforeEach(() => {
- router = createRouter();
routerPush = jest.spyOn(router, 'push').mockImplementation(() => {});
});
@@ -70,8 +68,9 @@ describe('CiResourcesListItem', () => {
});
});
- it('renders the resource name button', () => {
+ it('renders the resource name and link', () => {
expect(findResourceName().exists()).toBe(true);
+ expect(findResourceName().attributes().href).toBe(defaultProps.resource.webPath);
});
it('renders the resource version badge', () => {
@@ -81,58 +80,69 @@ describe('CiResourcesListItem', () => {
it('renders the resource description', () => {
expect(findResourceDescription().exists()).toBe(true);
});
+ });
- describe('release time', () => {
- describe('when there is no release data', () => {
- beforeEach(() => {
- createComponent({ props: { resource: { ...resource, latestVersion: null } } });
- });
+ describe('release time', () => {
+ describe('when there is no release data', () => {
+ beforeEach(() => {
+ createComponent({ props: { resource: { ...resource, latestVersion: null } } });
+ });
- it('does not render the release', () => {
- expect(findTimeAgoMessage().exists()).toBe(false);
- });
+ it('does not render the release', () => {
+ expect(findTimeAgoMessage().exists()).toBe(false);
+ });
- it('renders the generic `unreleased` badge', () => {
- expect(findBadge().exists()).toBe(true);
- expect(findBadge().text()).toBe('Unreleased');
- });
+ it('renders the generic `unreleased` badge', () => {
+ expect(findBadge().exists()).toBe(true);
+ expect(findBadge().text()).toBe('Unreleased');
});
+ });
- describe('when there is release data', () => {
- beforeEach(() => {
- createComponent({ props: { resource: { ...resource, latestVersion: { ...release } } } });
- });
+ describe('when there is release data', () => {
+ beforeEach(() => {
+ createComponent({ props: { resource: { ...resource, latestVersion: { ...release } } } });
+ });
- it('renders the user link', () => {
- expect(findUserLink().exists()).toBe(true);
- expect(findUserLink().attributes('href')).toBe(release.author.webUrl);
- });
+ it('renders the user link', () => {
+ expect(findUserLink().exists()).toBe(true);
+ expect(findUserLink().attributes('href')).toBe(release.author.webUrl);
+ });
- it('renders the time since the resource was released', () => {
- expect(findTimeAgoMessage().exists()).toBe(true);
- });
+ it('renders the time since the resource was released', () => {
+ expect(findTimeAgoMessage().exists()).toBe(true);
+ });
- it('renders the version badge', () => {
- expect(findBadge().exists()).toBe(true);
- expect(findBadge().text()).toBe(release.tagName);
- });
+ it('renders the version badge', () => {
+ expect(findBadge().exists()).toBe(true);
+ expect(findBadge().text()).toBe(release.tagName);
});
});
});
describe('when clicking on an item title', () => {
- beforeEach(async () => {
- createComponent();
+ describe('without holding down a modifier key', () => {
+ it('navigates to the details page in the same tab', async () => {
+ createComponent();
+ await findResourceName().vm.$emit('click', defaultEvent);
- await findResourceName().vm.$emit('click');
+ expect(routerPush).toHaveBeenCalledWith({
+ path: cleanLeadingSeparator(resource.webPath),
+ });
+ });
});
- it('navigates to the details page', () => {
- expect(routerPush).toHaveBeenCalledWith({
- name: CI_RESOURCE_DETAILS_PAGE_NAME,
- params: {
- id: getIdFromGraphQLId(resource.id),
- },
+ describe.each`
+ keyName
+ ${'ctrlKey'}
+ ${'metaKey'}
+ `('when $keyName is being held down', ({ keyName }) => {
+ beforeEach(async () => {
+ createComponent();
+ await findResourceName().vm.$emit('click', { ...defaultEvent, [keyName]: true });
+ });
+
+ it('does not call VueRouter push', () => {
+ expect(routerPush).not.toHaveBeenCalled();
});
});
});
@@ -141,43 +151,35 @@ describe('CiResourcesListItem', () => {
beforeEach(async () => {
createComponent();
- await findAvatar().vm.$emit('click');
+ await findAvatar().vm.$emit('click', defaultEvent);
});
it('navigates to the details page', () => {
- expect(routerPush).toHaveBeenCalledWith({
- name: CI_RESOURCE_DETAILS_PAGE_NAME,
- params: {
- id: getIdFromGraphQLId(resource.id),
- },
- });
+ expect(routerPush).toHaveBeenCalledWith({ path: cleanLeadingSeparator(resource.webPath) });
});
});
describe('statistics', () => {
describe('when there are no statistics', () => {
- beforeEach(() => {
+ it('render favorites as 0', () => {
createComponent({
props: {
resource: {
+ ...resource,
starCount: 0,
},
},
});
- });
- it('render favorites as 0', () => {
expect(findFavorites().exists()).toBe(true);
expect(findFavorites().text()).toBe('0');
});
});
describe('where there are statistics', () => {
- beforeEach(() => {
+ it('render favorites', () => {
createComponent();
- });
- it('render favorites', () => {
expect(findFavorites().exists()).toBe(true);
expect(findFavorites().text()).toBe(String(defaultProps.resource.starCount));
});
diff --git a/spec/frontend/ci/catalog/components/list/empty_state_spec.js b/spec/frontend/ci/catalog/components/list/empty_state_spec.js
index f589ad96a9d..5db0c61371d 100644
--- a/spec/frontend/ci/catalog/components/list/empty_state_spec.js
+++ b/spec/frontend/ci/catalog/components/list/empty_state_spec.js
@@ -1,27 +1,83 @@
-import { GlEmptyState } from '@gitlab/ui';
+import { GlEmptyState, GlLink, GlSprintf } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import EmptyState from '~/ci/catalog/components/list/empty_state.vue';
+import { COMPONENTS_DOCS_URL } from '~/ci/catalog/constants';
describe('EmptyState', () => {
let wrapper;
const findEmptyState = () => wrapper.findComponent(GlEmptyState);
+ const findComponentsDocLink = () => wrapper.findComponent(GlLink);
const createComponent = ({ props = {} } = {}) => {
wrapper = shallowMountExtended(EmptyState, {
propsData: {
...props,
},
+ stubs: {
+ GlEmptyState,
+ GlSprintf,
+ },
});
};
- describe('when mounted', () => {
+ describe('default', () => {
beforeEach(() => {
createComponent();
});
- it('renders the empty state', () => {
- expect(findEmptyState().exists()).toBe(true);
+ it('renders the default empty state', () => {
+ const emptyState = findEmptyState();
+
+ expect(emptyState.exists()).toBe(true);
+ expect(emptyState.props().title).toBe('Get started with the CI/CD Catalog');
+ expect(emptyState.props().description).toBe(
+ 'Create a pipeline component repository and make reusing pipeline configurations faster and easier.',
+ );
+ });
+ });
+
+ describe('when there is a search query', () => {
+ beforeEach(() => {
+ createComponent({
+ props: { searchTerm: 'a' },
+ });
+ });
+
+ it('renders the search description', () => {
+ expect(findEmptyState().text()).toContain(
+ 'Edit your search and try again. Or learn to create a component repository.',
+ );
+ });
+
+ it('renders the link to the components documentation', () => {
+ const docsLink = findComponentsDocLink();
+ expect(docsLink.exists()).toBe(true);
+ expect(docsLink.attributes().href).toBe(COMPONENTS_DOCS_URL);
+ });
+
+ describe('and it is less than 3 characters', () => {
+ beforeEach(() => {
+ createComponent({
+ props: { searchTerm: 'a' },
+ });
+ });
+
+ it('render the too few chars empty state title', () => {
+ expect(findEmptyState().props().title).toBe('Search must be at least 3 characters');
+ });
+ });
+
+ describe('and it has more than 3 characters', () => {
+ beforeEach(() => {
+ createComponent({
+ props: { searchTerm: 'my component' },
+ });
+ });
+
+ it('renders the search empty state title', () => {
+ expect(findEmptyState().props().title).toBe('No result found');
+ });
});
});
});
diff --git a/spec/frontend/ci/catalog/components/pages/ci_resource_details_page_spec.js b/spec/frontend/ci/catalog/components/pages/ci_resource_details_page_spec.js
index 40f243ed891..015c6504fa5 100644
--- a/spec/frontend/ci/catalog/components/pages/ci_resource_details_page_spec.js
+++ b/spec/frontend/ci/catalog/components/pages/ci_resource_details_page_spec.js
@@ -5,7 +5,8 @@ import { GlEmptyState } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
-import { CI_CATALOG_RESOURCE_TYPE, cacheConfig } from '~/ci/catalog/graphql/settings';
+import { cacheConfig } from '~/ci/catalog/graphql/settings';
+import { cleanLeadingSeparator } from '~/lib/utils/url_utility';
import getCiCatalogResourceSharedData from '~/ci/catalog/graphql/queries/get_ci_catalog_resource_shared_data.query.graphql';
import getCiCatalogResourceDetails from '~/ci/catalog/graphql/queries/get_ci_catalog_resource_details.query.graphql';
@@ -17,7 +18,6 @@ import CiResourceHeaderSkeletonLoader from '~/ci/catalog/components/details/ci_r
import { createRouter } from '~/ci/catalog/router/index';
import { CI_RESOURCE_DETAILS_PAGE_NAME } from '~/ci/catalog/router/constants';
-import { convertToGraphQLId } from '~/graphql_shared/utils';
import { catalogSharedDataMock, catalogAdditionalDetailsMock } from '../../mock';
Vue.use(VueApollo);
@@ -75,7 +75,7 @@ describe('CiResourceDetailsPage', () => {
router = createRouter();
await router.push({
name: CI_RESOURCE_DETAILS_PAGE_NAME,
- params: { id: defaultSharedData.id },
+ params: { id: defaultSharedData.webPath },
});
});
@@ -178,7 +178,7 @@ describe('CiResourceDetailsPage', () => {
it('passes expected props', () => {
expect(findDetailsComponent().props()).toEqual({
- resourceId: convertToGraphQLId(CI_CATALOG_RESOURCE_TYPE, defaultAdditionalData.id),
+ resourcePath: cleanLeadingSeparator(defaultSharedData.webPath),
});
});
});
diff --git a/spec/frontend/ci/catalog/components/pages/ci_resources_page_spec.js b/spec/frontend/ci/catalog/components/pages/ci_resources_page_spec.js
index e18b418b155..e6fbd63f307 100644
--- a/spec/frontend/ci/catalog/components/pages/ci_resources_page_spec.js
+++ b/spec/frontend/ci/catalog/components/pages/ci_resources_page_spec.js
@@ -7,10 +7,12 @@ import createMockApollo from 'helpers/mock_apollo_helper';
import { createAlert } from '~/alert';
import CatalogHeader from '~/ci/catalog/components/list/catalog_header.vue';
+import CatalogSearch from '~/ci/catalog/components/list/catalog_search.vue';
import CiResourcesList from '~/ci/catalog/components/list/ci_resources_list.vue';
import CatalogListSkeletonLoader from '~/ci/catalog/components/list/catalog_list_skeleton_loader.vue';
import EmptyState from '~/ci/catalog/components/list/empty_state.vue';
-import { cacheConfig } from '~/ci/catalog/graphql/settings';
+import { cacheConfig, resolvers } from '~/ci/catalog/graphql/settings';
+import typeDefs from '~/ci/catalog/graphql/typedefs.graphql';
import ciResourcesPage from '~/ci/catalog/components/pages/ci_resources_page.vue';
import getCatalogResources from '~/ci/catalog/graphql/queries/get_ci_catalog_resources.query.graphql';
@@ -24,9 +26,11 @@ describe('CiResourcesPage', () => {
let wrapper;
let catalogResourcesResponse;
+ const defaultQueryVariables = { first: 20 };
+
const createComponent = () => {
const handlers = [[getCatalogResources, catalogResourcesResponse]];
- const mockApollo = createMockApollo(handlers, {}, cacheConfig);
+ const mockApollo = createMockApollo(handlers, resolvers, { cacheConfig, typeDefs });
wrapper = shallowMountExtended(ciResourcesPage, {
apolloProvider: mockApollo,
@@ -36,6 +40,7 @@ describe('CiResourcesPage', () => {
};
const findCatalogHeader = () => wrapper.findComponent(CatalogHeader);
+ const findCatalogSearch = () => wrapper.findComponent(CatalogSearch);
const findCiResourcesList = () => wrapper.findComponent(CiResourcesList);
const findLoadingState = () => wrapper.findComponent(CatalogListSkeletonLoader);
const findEmptyState = () => wrapper.findComponent(EmptyState);
@@ -71,8 +76,14 @@ describe('CiResourcesPage', () => {
});
it('renders the empty state', () => {
- expect(findLoadingState().exists()).toBe(false);
expect(findEmptyState().exists()).toBe(true);
+ });
+
+ it('renders the search', () => {
+ expect(findCatalogSearch().exists()).toBe(true);
+ });
+
+ it('does not render the list', () => {
expect(findCiResourcesList().exists()).toBe(false);
});
});
@@ -99,6 +110,10 @@ describe('CiResourcesPage', () => {
totalCount: count,
});
});
+
+ it('renders the search and sort', () => {
+ expect(findCatalogSearch().exists()).toBe(true);
+ });
});
});
@@ -121,11 +136,12 @@ describe('CiResourcesPage', () => {
if (eventName === 'onNextPage') {
expect(catalogResourcesResponse.mock.calls[1][0]).toEqual({
+ ...defaultQueryVariables,
after: pageInfo.endCursor,
- first: 20,
});
} else {
expect(catalogResourcesResponse.mock.calls[1][0]).toEqual({
+ ...defaultQueryVariables,
before: pageInfo.startCursor,
last: 20,
first: null,
@@ -134,8 +150,75 @@ describe('CiResourcesPage', () => {
});
});
+ describe('search and sort', () => {
+ describe('on initial load', () => {
+ beforeEach(async () => {
+ catalogResourcesResponse.mockResolvedValue(catalogResponseBody);
+ await createComponent();
+ });
+
+ it('calls the query without search or sort', () => {
+ expect(catalogResourcesResponse).toHaveBeenCalledTimes(1);
+ expect(catalogResourcesResponse.mock.calls[0][0]).toEqual({
+ ...defaultQueryVariables,
+ });
+ });
+ });
+
+ describe('when sorting changes', () => {
+ const newSort = 'MOST_AWESOME_ASC';
+
+ beforeEach(async () => {
+ catalogResourcesResponse.mockResolvedValue(catalogResponseBody);
+ await createComponent();
+ await findCatalogSearch().vm.$emit('update-sorting', newSort);
+ });
+
+ it('passes it to the graphql query', () => {
+ expect(catalogResourcesResponse).toHaveBeenCalledTimes(2);
+ expect(catalogResourcesResponse.mock.calls[1][0]).toEqual({
+ ...defaultQueryVariables,
+ sortValue: newSort,
+ });
+ });
+ });
+
+ describe('when search component emits a new search term', () => {
+ const newSearch = 'sloths';
+
+ describe('and there are no results', () => {
+ beforeEach(async () => {
+ catalogResourcesResponse.mockResolvedValue(emptyCatalogResponseBody);
+ await createComponent();
+ await findCatalogSearch().vm.$emit('update-search-term', newSearch);
+ });
+
+ it('renders the empty state and passes down the search query', () => {
+ expect(findEmptyState().exists()).toBe(true);
+ expect(findEmptyState().props().searchTerm).toBe(newSearch);
+ });
+ });
+
+ describe('and there are results', () => {
+ beforeEach(async () => {
+ catalogResourcesResponse.mockResolvedValue(catalogResponseBody);
+ await createComponent();
+ await findCatalogSearch().vm.$emit('update-search-term', newSearch);
+ });
+
+ it('passes it to the graphql query', () => {
+ expect(catalogResourcesResponse).toHaveBeenCalledTimes(2);
+ expect(catalogResourcesResponse.mock.calls[1][0]).toEqual({
+ ...defaultQueryVariables,
+ searchTerm: newSearch,
+ });
+ });
+ });
+ });
+ });
+
describe('pages count', () => {
- describe('when the fetchMore call suceeds', () => {
+ describe('when the fetchMore call succeeds', () => {
beforeEach(async () => {
catalogResourcesResponse.mockResolvedValue(catalogResponseBody);
@@ -157,6 +240,31 @@ describe('CiResourcesPage', () => {
});
});
+ describe.each`
+ event | payload
+ ${'update-search-term'} | ${'cat'}
+ ${'update-sorting'} | ${'CREATED_ASC'}
+ `('when $event event is emitted', ({ event, payload }) => {
+ beforeEach(async () => {
+ catalogResourcesResponse.mockResolvedValue(catalogResponseBody);
+ await createComponent();
+ });
+
+ it('resets the page count', async () => {
+ expect(findCiResourcesList().props().currentPage).toBe(1);
+
+ findCiResourcesList().vm.$emit('onNextPage');
+ await waitForPromises();
+
+ expect(findCiResourcesList().props().currentPage).toBe(2);
+
+ await findCatalogSearch().vm.$emit(event, payload);
+ await waitForPromises();
+
+ expect(findCiResourcesList().props().currentPage).toBe(1);
+ });
+ });
+
describe('when the fetchMore call fails', () => {
const errorMessage = 'there was an error';
diff --git a/spec/frontend/ci/catalog/mock.js b/spec/frontend/ci/catalog/mock.js
index 125f003224c..e370ac5054f 100644
--- a/spec/frontend/ci/catalog/mock.js
+++ b/spec/frontend/ci/catalog/mock.js
@@ -1,5 +1,3 @@
-import { componentsMockData } from '~/ci/catalog/constants';
-
export const emptyCatalogResponseBody = {
data: {
ciCatalogResources: {
@@ -39,12 +37,6 @@ export const catalogResponseBody = {
description: 'A simple component',
starCount: 0,
latestVersion: null,
- rootNamespace: {
- id: 'gid://gitlab/Group/185',
- fullPath: 'frontend-fixtures',
- name: 'frontend-fixtures',
- __typename: 'Namespace',
- },
webPath: '/frontend-fixtures/project-42',
__typename: 'CiCatalogResource',
},
@@ -55,12 +47,6 @@ export const catalogResponseBody = {
description: 'A simple component',
starCount: 0,
latestVersion: null,
- rootNamespace: {
- id: 'gid://gitlab/Group/185',
- fullPath: 'frontend-fixtures',
- name: 'frontend-fixtures',
- __typename: 'Namespace',
- },
webPath: '/frontend-fixtures/project-41',
__typename: 'CiCatalogResource',
},
@@ -71,12 +57,6 @@ export const catalogResponseBody = {
description: 'A simple component',
starCount: 0,
latestVersion: null,
- rootNamespace: {
- id: 'gid://gitlab/Group/185',
- fullPath: 'frontend-fixtures',
- name: 'frontend-fixtures',
- __typename: 'Namespace',
- },
webPath: '/frontend-fixtures/project-40',
__typename: 'CiCatalogResource',
},
@@ -87,12 +67,6 @@ export const catalogResponseBody = {
description: 'A simple component',
starCount: 0,
latestVersion: null,
- rootNamespace: {
- id: 'gid://gitlab/Group/185',
- fullPath: 'frontend-fixtures',
- name: 'frontend-fixtures',
- __typename: 'Namespace',
- },
webPath: '/frontend-fixtures/project-39',
__typename: 'CiCatalogResource',
},
@@ -103,12 +77,6 @@ export const catalogResponseBody = {
description: 'A simple component',
starCount: 0,
latestVersion: null,
- rootNamespace: {
- id: 'gid://gitlab/Group/185',
- fullPath: 'frontend-fixtures',
- name: 'frontend-fixtures',
- __typename: 'Namespace',
- },
webPath: '/frontend-fixtures/project-38',
__typename: 'CiCatalogResource',
},
@@ -119,12 +87,6 @@ export const catalogResponseBody = {
description: 'A simple component',
starCount: 0,
latestVersion: null,
- rootNamespace: {
- id: 'gid://gitlab/Group/185',
- fullPath: 'frontend-fixtures',
- name: 'frontend-fixtures',
- __typename: 'Namespace',
- },
webPath: '/frontend-fixtures/project-37',
__typename: 'CiCatalogResource',
},
@@ -135,12 +97,6 @@ export const catalogResponseBody = {
description: 'A simple component',
starCount: 0,
latestVersion: null,
- rootNamespace: {
- id: 'gid://gitlab/Group/185',
- fullPath: 'frontend-fixtures',
- name: 'frontend-fixtures',
- __typename: 'Namespace',
- },
webPath: '/frontend-fixtures/project-36',
__typename: 'CiCatalogResource',
},
@@ -151,12 +107,6 @@ export const catalogResponseBody = {
description: 'A simple component',
starCount: 0,
latestVersion: null,
- rootNamespace: {
- id: 'gid://gitlab/Group/185',
- fullPath: 'frontend-fixtures',
- name: 'frontend-fixtures',
- __typename: 'Namespace',
- },
webPath: '/frontend-fixtures/project-35',
__typename: 'CiCatalogResource',
},
@@ -167,12 +117,6 @@ export const catalogResponseBody = {
description: 'A simple component',
starCount: 0,
latestVersion: null,
- rootNamespace: {
- id: 'gid://gitlab/Group/185',
- fullPath: 'frontend-fixtures',
- name: 'frontend-fixtures',
- __typename: 'Namespace',
- },
webPath: '/frontend-fixtures/project-34',
__typename: 'CiCatalogResource',
},
@@ -183,12 +127,6 @@ export const catalogResponseBody = {
description: 'A simple component',
starCount: 0,
latestVersion: null,
- rootNamespace: {
- id: 'gid://gitlab/Group/185',
- fullPath: 'frontend-fixtures',
- name: 'frontend-fixtures',
- __typename: 'Namespace',
- },
webPath: '/frontend-fixtures/project-33',
__typename: 'CiCatalogResource',
},
@@ -199,12 +137,6 @@ export const catalogResponseBody = {
description: 'A simple component',
starCount: 0,
latestVersion: null,
- rootNamespace: {
- id: 'gid://gitlab/Group/185',
- fullPath: 'frontend-fixtures',
- name: 'frontend-fixtures',
- __typename: 'Namespace',
- },
webPath: '/frontend-fixtures/project-32',
__typename: 'CiCatalogResource',
},
@@ -215,12 +147,6 @@ export const catalogResponseBody = {
description: 'A simple component',
starCount: 0,
latestVersion: null,
- rootNamespace: {
- id: 'gid://gitlab/Group/185',
- fullPath: 'frontend-fixtures',
- name: 'frontend-fixtures',
- __typename: 'Namespace',
- },
webPath: '/frontend-fixtures/project-31',
__typename: 'CiCatalogResource',
},
@@ -231,12 +157,6 @@ export const catalogResponseBody = {
description: 'A simple component',
starCount: 0,
latestVersion: null,
- rootNamespace: {
- id: 'gid://gitlab/Group/185',
- fullPath: 'frontend-fixtures',
- name: 'frontend-fixtures',
- __typename: 'Namespace',
- },
webPath: '/frontend-fixtures/project-30',
__typename: 'CiCatalogResource',
},
@@ -247,12 +167,6 @@ export const catalogResponseBody = {
description: 'A simple component',
starCount: 0,
latestVersion: null,
- rootNamespace: {
- id: 'gid://gitlab/Group/185',
- fullPath: 'frontend-fixtures',
- name: 'frontend-fixtures',
- __typename: 'Namespace',
- },
webPath: '/frontend-fixtures/project-29',
__typename: 'CiCatalogResource',
},
@@ -263,12 +177,6 @@ export const catalogResponseBody = {
description: 'A simple component',
starCount: 0,
latestVersion: null,
- rootNamespace: {
- id: 'gid://gitlab/Group/185',
- fullPath: 'frontend-fixtures',
- name: 'frontend-fixtures',
- __typename: 'Namespace',
- },
webPath: '/frontend-fixtures/project-28',
__typename: 'CiCatalogResource',
},
@@ -279,12 +187,6 @@ export const catalogResponseBody = {
description: 'A simple component',
starCount: 0,
latestVersion: null,
- rootNamespace: {
- id: 'gid://gitlab/Group/185',
- fullPath: 'frontend-fixtures',
- name: 'frontend-fixtures',
- __typename: 'Namespace',
- },
webPath: '/frontend-fixtures/project-27',
__typename: 'CiCatalogResource',
},
@@ -295,12 +197,6 @@ export const catalogResponseBody = {
description: 'A simple component',
starCount: 0,
latestVersion: null,
- rootNamespace: {
- id: 'gid://gitlab/Group/185',
- fullPath: 'frontend-fixtures',
- name: 'frontend-fixtures',
- __typename: 'Namespace',
- },
webPath: '/frontend-fixtures/project-26',
__typename: 'CiCatalogResource',
},
@@ -311,12 +207,6 @@ export const catalogResponseBody = {
description: 'A simple component',
starCount: 0,
latestVersion: null,
- rootNamespace: {
- id: 'gid://gitlab/Group/185',
- fullPath: 'frontend-fixtures',
- name: 'frontend-fixtures',
- __typename: 'Namespace',
- },
webPath: '/frontend-fixtures/project-25',
__typename: 'CiCatalogResource',
},
@@ -327,12 +217,6 @@ export const catalogResponseBody = {
description: 'A simple component',
starCount: 0,
latestVersion: null,
- rootNamespace: {
- id: 'gid://gitlab/Group/185',
- fullPath: 'frontend-fixtures',
- name: 'frontend-fixtures',
- __typename: 'Namespace',
- },
webPath: '/frontend-fixtures/project-24',
__typename: 'CiCatalogResource',
},
@@ -343,12 +227,6 @@ export const catalogResponseBody = {
description: 'A simple component',
starCount: 0,
latestVersion: null,
- rootNamespace: {
- id: 'gid://gitlab/Group/185',
- fullPath: 'frontend-fixtures',
- name: 'frontend-fixtures',
- __typename: 'Namespace',
- },
webPath: '/frontend-fixtures/project-23',
__typename: 'CiCatalogResource',
},
@@ -379,12 +257,6 @@ export const catalogSinglePageResponse = {
description: 'A simple component',
starCount: 0,
latestVersion: null,
- rootNamespace: {
- id: 'gid://gitlab/Group/185',
- fullPath: 'frontend-fixtures',
- name: 'frontend-fixtures',
- __typename: 'Namespace',
- },
webPath: '/frontend-fixtures/project-45',
__typename: 'CiCatalogResource',
},
@@ -395,12 +267,6 @@ export const catalogSinglePageResponse = {
description: 'A simple component',
starCount: 0,
latestVersion: null,
- rootNamespace: {
- id: 'gid://gitlab/Group/185',
- fullPath: 'frontend-fixtures',
- name: 'frontend-fixtures',
- __typename: 'Namespace',
- },
webPath: '/frontend-fixtures/project-44',
__typename: 'CiCatalogResource',
},
@@ -411,12 +277,6 @@ export const catalogSinglePageResponse = {
description: 'A simple component',
starCount: 0,
latestVersion: null,
- rootNamespace: {
- id: 'gid://gitlab/Group/185',
- fullPath: 'frontend-fixtures',
- name: 'frontend-fixtures',
- __typename: 'Namespace',
- },
webPath: '/frontend-fixtures/project-43',
__typename: 'CiCatalogResource',
},
@@ -434,7 +294,6 @@ export const catalogSharedDataMock = {
icon: null,
description: 'This is the description of the repo',
name: 'Ruby',
- rootNamespace: { id: 1, fullPath: '/group/project', name: 'my-dumb-project' },
starCount: 1,
latestVersion: {
__typename: 'Release',
@@ -444,7 +303,7 @@ export const catalogSharedDataMock = {
releasedAt: Date.now(),
author: { id: 1, webUrl: 'profile/1', name: 'username' },
},
- webPath: 'path/to/project',
+ webPath: '/path/to/project',
},
},
};
@@ -454,6 +313,7 @@ export const catalogAdditionalDetailsMock = {
ciCatalogResource: {
__typename: 'CiCatalogResource',
id: `gid://gitlab/CiCatalogResource/1`,
+ webPath: '/twitter/project',
openIssuesCount: 4,
openMergeRequestsCount: 10,
readmeHtml: '<h1>Hello world</h1>',
@@ -502,12 +362,6 @@ const generateResourcesNodes = (count = 20, startId = 0) => {
description: `This is a component that does a bunch of stuff and is really just a number: ${i}`,
icon: 'my-icon',
name: `My component #${i}`,
- rootNamespace: {
- id: 1,
- __typename: 'Namespace',
- name: 'namespaceName',
- path: 'namespacePath',
- },
starCount: 10,
latestVersion: {
__typename: 'Release',
@@ -526,13 +380,47 @@ const generateResourcesNodes = (count = 20, startId = 0) => {
export const mockCatalogResourceItem = generateResourcesNodes(1)[0];
+const componentsMockData = {
+ __typename: 'CiComponentConnection',
+ nodes: [
+ {
+ id: 'gid://gitlab/Ci::Component/1',
+ name: 'Ruby gal',
+ description: 'This is a pretty amazing component that does EVERYTHING ruby.',
+ path: 'gitlab.com/gitlab-org/ruby-gal@~latest',
+ inputs: [{ name: 'version', default: '1.0.0', required: true }],
+ },
+ {
+ id: 'gid://gitlab/Ci::Component/2',
+ name: 'Javascript madness',
+ description: 'Adds some spice to your life.',
+ path: 'gitlab.com/gitlab-org/javascript-madness@~latest',
+ inputs: [
+ { name: 'isFun', default: 'true', required: true },
+ { name: 'RandomNumber', default: '10', required: false },
+ ],
+ },
+ {
+ id: 'gid://gitlab/Ci::Component/3',
+ name: 'Go go go',
+ description: 'When you write Go, you gotta go go go.',
+ path: 'gitlab.com/gitlab-org/go-go-go@~latest',
+ inputs: [{ name: 'version', default: '1.0.0', required: true }],
+ },
+ ],
+};
+
export const mockComponents = {
data: {
ciCatalogResource: {
__typename: 'CiCatalogResource',
id: `gid://gitlab/CiCatalogResource/1`,
- components: {
- ...componentsMockData,
+ webPath: '/twitter/project-1',
+ latestVersion: {
+ id: 'gid://gitlab/Version/1',
+ components: {
+ ...componentsMockData,
+ },
},
},
},
@@ -543,7 +431,11 @@ export const mockComponentsEmpty = {
ciCatalogResource: {
__typename: 'CiCatalogResource',
id: `gid://gitlab/CiCatalogResource/1`,
- components: [],
+ webPath: '/twitter/project-1',
+ latestVersion: {
+ id: 'gid://gitlab/Version/1',
+ components: [],
+ },
},
},
};