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/components/list')
-rw-r--r--spec/frontend/ci/catalog/components/list/catalog_header_spec.js86
-rw-r--r--spec/frontend/ci/catalog/components/list/catalog_list_skeleton_loader_spec.js22
-rw-r--r--spec/frontend/ci/catalog/components/list/ci_resources_list_item_spec.js198
-rw-r--r--spec/frontend/ci/catalog/components/list/ci_resources_list_spec.js143
-rw-r--r--spec/frontend/ci/catalog/components/list/empty_state_spec.js27
5 files changed, 476 insertions, 0 deletions
diff --git a/spec/frontend/ci/catalog/components/list/catalog_header_spec.js b/spec/frontend/ci/catalog/components/list/catalog_header_spec.js
new file mode 100644
index 00000000000..912fd9e1a93
--- /dev/null
+++ b/spec/frontend/ci/catalog/components/list/catalog_header_spec.js
@@ -0,0 +1,86 @@
+import { GlBanner, GlButton } from '@gitlab/ui';
+import { useLocalStorageSpy } from 'helpers/local_storage_helper';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import CatalogHeader from '~/ci/catalog/components/list/catalog_header.vue';
+import { CATALOG_FEEDBACK_DISMISSED_KEY } from '~/ci/catalog/constants';
+
+describe('CatalogHeader', () => {
+ useLocalStorageSpy();
+
+ let wrapper;
+
+ const defaultProps = {};
+ const defaultProvide = {
+ pageTitle: 'Catalog page',
+ pageDescription: 'This is a nice catalog page',
+ };
+
+ const findBanner = () => wrapper.findComponent(GlBanner);
+ const findFeedbackButton = () => findBanner().findComponent(GlButton);
+ const findTitle = () => wrapper.findByText(defaultProvide.pageTitle);
+ const findDescription = () => wrapper.findByText(defaultProvide.pageDescription);
+
+ const createComponent = ({ props = {}, stubs = {} } = {}) => {
+ wrapper = shallowMountExtended(CatalogHeader, {
+ propsData: {
+ ...defaultProps,
+ ...props,
+ },
+ provide: defaultProvide,
+ stubs: {
+ ...stubs,
+ },
+ });
+ };
+
+ it('renders the Catalog title and description', () => {
+ createComponent();
+
+ expect(findTitle().exists()).toBe(true);
+ expect(findDescription().exists()).toBe(true);
+ });
+
+ describe('Feedback banner', () => {
+ describe('when user has never dismissed', () => {
+ beforeEach(() => {
+ createComponent({ stubs: { GlBanner } });
+ });
+
+ it('is visible', () => {
+ expect(findBanner().exists()).toBe(true);
+ });
+
+ it('has link to feedback issue', () => {
+ expect(findFeedbackButton().attributes().href).toBe(
+ 'https://gitlab.com/gitlab-org/gitlab/-/issues/407556',
+ );
+ });
+ });
+
+ describe('when user dismisses it', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('sets the local storage and removes the banner', async () => {
+ expect(findBanner().exists()).toBe(true);
+
+ await findBanner().vm.$emit('close');
+
+ expect(localStorage.setItem).toHaveBeenCalledWith(CATALOG_FEEDBACK_DISMISSED_KEY, 'true');
+ expect(findBanner().exists()).toBe(false);
+ });
+ });
+
+ describe('when user has dismissed it before', () => {
+ beforeEach(() => {
+ localStorage.setItem(CATALOG_FEEDBACK_DISMISSED_KEY, 'true');
+ createComponent();
+ });
+
+ it('does not show the banner', () => {
+ expect(findBanner().exists()).toBe(false);
+ });
+ });
+ });
+});
diff --git a/spec/frontend/ci/catalog/components/list/catalog_list_skeleton_loader_spec.js b/spec/frontend/ci/catalog/components/list/catalog_list_skeleton_loader_spec.js
new file mode 100644
index 00000000000..d21fd56eb2e
--- /dev/null
+++ b/spec/frontend/ci/catalog/components/list/catalog_list_skeleton_loader_spec.js
@@ -0,0 +1,22 @@
+import { shallowMount } from '@vue/test-utils';
+import CatalogListSkeletonLoader from '~/ci/catalog/components/list/catalog_list_skeleton_loader.vue';
+
+describe('CatalogListSkeletonLoader', () => {
+ let wrapper;
+
+ const findSkeletonLoader = () => wrapper.findComponent(CatalogListSkeletonLoader);
+
+ const createComponent = () => {
+ wrapper = shallowMount(CatalogListSkeletonLoader, {});
+ };
+
+ describe('when mounted', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('renders skeleton item', () => {
+ expect(findSkeletonLoader().exists()).toBe(true);
+ });
+ });
+});
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
new file mode 100644
index 00000000000..7f446064366
--- /dev/null
+++ b/spec/frontend/ci/catalog/components/list/ci_resources_list_item_spec.js
@@ -0,0 +1,198 @@
+import Vue from 'vue';
+import VueRouter from 'vue-router';
+import { GlAvatar, GlBadge, GlButton, GlSprintf } from '@gitlab/ui';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+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;
+
+describe('CiResourcesListItem', () => {
+ let wrapper;
+
+ const resource = catalogSinglePageResponse.data.ciCatalogResources.nodes[0];
+ const release = {
+ author: { name: 'author', webUrl: '/user/1' },
+ releasedAt: Date.now(),
+ tagName: '1.0.0',
+ };
+ const defaultProps = {
+ resource,
+ };
+
+ const createComponent = ({ props = {} } = {}) => {
+ wrapper = shallowMountExtended(CiResourcesListItem, {
+ router,
+ propsData: {
+ ...defaultProps,
+ ...props,
+ },
+ stubs: {
+ GlSprintf,
+ RouterLink: true,
+ RouterView: true,
+ },
+ });
+ };
+
+ const findAvatar = () => wrapper.findComponent(GlAvatar);
+ const findBadge = () => wrapper.findComponent(GlBadge);
+ const findResourceName = () => wrapper.findComponent(GlButton);
+ const findResourceDescription = () => wrapper.findByText(defaultProps.resource.description);
+ const findUserLink = () => wrapper.findByTestId('user-link');
+ const findTimeAgoMessage = () => wrapper.findComponent(GlSprintf);
+ const findFavorites = () => wrapper.findByTestId('stats-favorites');
+ const findForks = () => wrapper.findByTestId('stats-forks');
+
+ beforeEach(() => {
+ router = createRouter();
+ routerPush = jest.spyOn(router, 'push').mockImplementation(() => {});
+ });
+
+ describe('when mounted', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('renders the resource avatar and passes the right props', () => {
+ const { icon, id, name } = defaultProps.resource;
+
+ expect(findAvatar().exists()).toBe(true);
+ expect(findAvatar().props()).toMatchObject({
+ entityId: getIdFromGraphQLId(id),
+ entityName: name,
+ src: icon,
+ });
+ });
+
+ it('renders the resource name button', () => {
+ expect(findResourceName().exists()).toBe(true);
+ });
+
+ it('renders the resource version badge', () => {
+ expect(findBadge().exists()).toBe(true);
+ });
+
+ 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 } } });
+ });
+
+ 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');
+ });
+ });
+
+ 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 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);
+ });
+ });
+ });
+ });
+
+ describe('when clicking on an item title', () => {
+ beforeEach(async () => {
+ createComponent();
+
+ await findResourceName().vm.$emit('click');
+ });
+
+ it('navigates to the details page', () => {
+ expect(routerPush).toHaveBeenCalledWith({
+ name: CI_RESOURCE_DETAILS_PAGE_NAME,
+ params: {
+ id: getIdFromGraphQLId(resource.id),
+ },
+ });
+ });
+ });
+
+ describe('when clicking on an item avatar', () => {
+ beforeEach(async () => {
+ createComponent();
+
+ await findAvatar().vm.$emit('click');
+ });
+
+ it('navigates to the details page', () => {
+ expect(routerPush).toHaveBeenCalledWith({
+ name: CI_RESOURCE_DETAILS_PAGE_NAME,
+ params: {
+ id: getIdFromGraphQLId(resource.id),
+ },
+ });
+ });
+ });
+
+ describe('statistics', () => {
+ describe('when there are no statistics', () => {
+ beforeEach(() => {
+ createComponent({
+ props: {
+ resource: {
+ forksCount: 0,
+ starCount: 0,
+ },
+ },
+ });
+ });
+
+ it('render favorites as 0', () => {
+ expect(findFavorites().exists()).toBe(true);
+ expect(findFavorites().text()).toBe('0');
+ });
+
+ it('render forks as 0', () => {
+ expect(findForks().exists()).toBe(true);
+ expect(findForks().text()).toBe('0');
+ });
+ });
+
+ describe('where there are statistics', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('render favorites', () => {
+ expect(findFavorites().exists()).toBe(true);
+ expect(findFavorites().text()).toBe(String(defaultProps.resource.starCount));
+ });
+
+ it('render forks', () => {
+ expect(findForks().exists()).toBe(true);
+ expect(findForks().text()).toBe(String(defaultProps.resource.forksCount));
+ });
+ });
+ });
+});
diff --git a/spec/frontend/ci/catalog/components/list/ci_resources_list_spec.js b/spec/frontend/ci/catalog/components/list/ci_resources_list_spec.js
new file mode 100644
index 00000000000..aca20a83979
--- /dev/null
+++ b/spec/frontend/ci/catalog/components/list/ci_resources_list_spec.js
@@ -0,0 +1,143 @@
+import { GlKeysetPagination } from '@gitlab/ui';
+
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import CiResourcesList from '~/ci/catalog/components/list/ci_resources_list.vue';
+import CiResourcesListItem from '~/ci/catalog/components/list/ci_resources_list_item.vue';
+import { ciCatalogResourcesItemsCount } from '~/ci/catalog/graphql/settings';
+import { catalogResponseBody, catalogSinglePageResponse } from '../../mock';
+
+describe('CiResourcesList', () => {
+ let wrapper;
+
+ const createComponent = ({ props = {} } = {}) => {
+ const { nodes, pageInfo, count } = catalogResponseBody.data.ciCatalogResources;
+
+ const defaultProps = {
+ currentPage: 1,
+ resources: nodes,
+ pageInfo,
+ totalCount: count,
+ };
+
+ wrapper = shallowMountExtended(CiResourcesList, {
+ propsData: {
+ ...defaultProps,
+ ...props,
+ },
+ stubs: {
+ GlKeysetPagination,
+ },
+ });
+ };
+
+ const findPageCount = () => wrapper.findByTestId('pageCount');
+ const findResourcesListItems = () => wrapper.findAllComponents(CiResourcesListItem);
+ const findPrevBtn = () => wrapper.findByTestId('prevButton');
+ const findNextBtn = () => wrapper.findByTestId('nextButton');
+
+ describe('contains only one page', () => {
+ const { nodes, pageInfo, count } = catalogSinglePageResponse.data.ciCatalogResources;
+
+ beforeEach(async () => {
+ await createComponent({
+ props: { currentPage: 1, resources: nodes, pageInfo, totalCount: count },
+ });
+ });
+
+ it('shows the right number of items', () => {
+ expect(findResourcesListItems()).toHaveLength(nodes.length);
+ });
+
+ it('hides the keyset control for previous page', () => {
+ expect(findPrevBtn().exists()).toBe(false);
+ });
+
+ it('hides the keyset control for next page', () => {
+ expect(findNextBtn().exists()).toBe(false);
+ });
+
+ it('shows the correct count of current page', () => {
+ expect(findPageCount().text()).toContain('1 of 1');
+ });
+ });
+
+ describe.each`
+ hasPreviousPage | hasNextPage | pageText | expectedTotal | currentPage
+ ${false} | ${true} | ${'1 of 3'} | ${ciCatalogResourcesItemsCount} | ${1}
+ ${true} | ${true} | ${'2 of 3'} | ${ciCatalogResourcesItemsCount} | ${2}
+ ${true} | ${false} | ${'3 of 3'} | ${ciCatalogResourcesItemsCount} | ${3}
+ `(
+ 'when on page $pageText',
+ ({ currentPage, expectedTotal, pageText, hasPreviousPage, hasNextPage }) => {
+ const { nodes, pageInfo, count } = catalogResponseBody.data.ciCatalogResources;
+
+ const previousPageState = hasPreviousPage ? 'enabled' : 'disabled';
+ const nextPageState = hasNextPage ? 'enabled' : 'disabled';
+
+ beforeEach(async () => {
+ await createComponent({
+ props: {
+ currentPage,
+ resources: nodes,
+ pageInfo: { ...pageInfo, hasPreviousPage, hasNextPage },
+ totalCount: count,
+ },
+ });
+ });
+
+ it('shows the right number of items', () => {
+ expect(findResourcesListItems()).toHaveLength(expectedTotal);
+ });
+
+ it(`shows the keyset control for previous page as ${previousPageState}`, () => {
+ const disableAttr = findPrevBtn().attributes('disabled');
+
+ if (previousPageState === 'disabled') {
+ expect(disableAttr).toBeDefined();
+ } else {
+ expect(disableAttr).toBeUndefined();
+ }
+ });
+
+ it(`shows the keyset control for next page as ${nextPageState}`, () => {
+ const disableAttr = findNextBtn().attributes('disabled');
+
+ if (nextPageState === 'disabled') {
+ expect(disableAttr).toBeDefined();
+ } else {
+ expect(disableAttr).toBeUndefined();
+ }
+ });
+
+ it('shows the correct count of current page', () => {
+ expect(findPageCount().text()).toContain(pageText);
+ });
+ },
+ );
+
+ describe('when there is an error getting the page count', () => {
+ beforeEach(() => {
+ createComponent({ props: { totalCount: 0 } });
+ });
+
+ it('hides the page count', () => {
+ expect(findPageCount().exists()).toBe(false);
+ });
+ });
+
+ describe('emitted events', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it.each`
+ btnText | elFinder | eventName
+ ${'previous'} | ${findPrevBtn} | ${'onPrevPage'}
+ ${'next'} | ${findNextBtn} | ${'onNextPage'}
+ `('emits $eventName when clicking on the $btnText button', async ({ elFinder, eventName }) => {
+ await elFinder().vm.$emit('click');
+
+ expect(wrapper.emitted(eventName)).toHaveLength(1);
+ });
+ });
+});
diff --git a/spec/frontend/ci/catalog/components/list/empty_state_spec.js b/spec/frontend/ci/catalog/components/list/empty_state_spec.js
new file mode 100644
index 00000000000..f589ad96a9d
--- /dev/null
+++ b/spec/frontend/ci/catalog/components/list/empty_state_spec.js
@@ -0,0 +1,27 @@
+import { GlEmptyState } from '@gitlab/ui';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import EmptyState from '~/ci/catalog/components/list/empty_state.vue';
+
+describe('EmptyState', () => {
+ let wrapper;
+
+ const findEmptyState = () => wrapper.findComponent(GlEmptyState);
+
+ const createComponent = ({ props = {} } = {}) => {
+ wrapper = shallowMountExtended(EmptyState, {
+ propsData: {
+ ...props,
+ },
+ });
+ };
+
+ describe('when mounted', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('renders the empty state', () => {
+ expect(findEmptyState().exists()).toBe(true);
+ });
+ });
+});