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/groups/components')
-rw-r--r--spec/frontend/groups/components/app_spec.js88
-rw-r--r--spec/frontend/groups/components/group_item_spec.js11
-rw-r--r--spec/frontend/groups/components/groups_spec.js9
-rw-r--r--spec/frontend/groups/components/new_top_level_group_alert_spec.js75
-rw-r--r--spec/frontend/groups/components/overview_tabs_spec.js162
-rw-r--r--spec/frontend/groups/components/transfer_group_form_spec.js2
6 files changed, 295 insertions, 52 deletions
diff --git a/spec/frontend/groups/components/app_spec.js b/spec/frontend/groups/components/app_spec.js
index a4a7530184d..091ec17d58e 100644
--- a/spec/frontend/groups/components/app_spec.js
+++ b/spec/frontend/groups/components/app_spec.js
@@ -3,7 +3,7 @@ import { shallowMount } from '@vue/test-utils';
import AxiosMockAdapter from 'axios-mock-adapter';
import Vue, { nextTick } from 'vue';
import waitForPromises from 'helpers/wait_for_promises';
-import createFlash from '~/flash';
+import { createAlert } from '~/flash';
import appComponent from '~/groups/components/app.vue';
import groupFolderComponent from '~/groups/components/group_folder.vue';
import groupItemComponent from '~/groups/components/group_item.vue';
@@ -11,6 +11,7 @@ import eventHub from '~/groups/event_hub';
import GroupsService from '~/groups/service/groups_service';
import GroupsStore from '~/groups/store/groups_store';
import EmptyState from '~/groups/components/empty_state.vue';
+import GroupsComponent from '~/groups/components/groups.vue';
import axios from '~/lib/utils/axios_utils';
import * as urlUtilities from '~/lib/utils/url_utility';
import setWindowLocation from 'helpers/set_window_location_helper';
@@ -115,7 +116,7 @@ describe('AppComponent', () => {
return vm.fetchGroups({}).then(() => {
expect(vm.isLoading).toBe(false);
expect(window.scrollTo).toHaveBeenCalledWith({ behavior: 'smooth', top: 0 });
- expect(createFlash).toHaveBeenCalledWith({
+ expect(createAlert).toHaveBeenCalledWith({
message: 'An error occurred. Please try again.',
});
});
@@ -326,7 +327,7 @@ describe('AppComponent', () => {
expect(vm.service.leaveGroup).toHaveBeenCalledWith(childGroupItem.leavePath);
return waitForPromises().then(() => {
expect(vm.store.removeGroup).not.toHaveBeenCalled();
- expect(createFlash).toHaveBeenCalledWith({ message });
+ expect(createAlert).toHaveBeenCalledWith({ message });
expect(vm.targetGroup.isBeingRemoved).toBe(false);
});
});
@@ -341,7 +342,7 @@ describe('AppComponent', () => {
expect(vm.service.leaveGroup).toHaveBeenCalledWith(childGroupItem.leavePath);
return waitForPromises().then(() => {
expect(vm.store.removeGroup).not.toHaveBeenCalled();
- expect(createFlash).toHaveBeenCalledWith({ message });
+ expect(createAlert).toHaveBeenCalledWith({ message });
expect(vm.targetGroup.isBeingRemoved).toBe(false);
});
});
@@ -388,24 +389,27 @@ describe('AppComponent', () => {
});
describe.each`
- action | groups | fromSearch | renderEmptyState | expected
- ${'subgroups_and_projects'} | ${[]} | ${false} | ${true} | ${true}
- ${''} | ${[]} | ${false} | ${true} | ${false}
- ${'subgroups_and_projects'} | ${mockGroups} | ${false} | ${true} | ${false}
- ${'subgroups_and_projects'} | ${[]} | ${true} | ${true} | ${false}
+ action | groups | fromSearch | shouldRenderEmptyState | searchEmpty
+ ${'subgroups_and_projects'} | ${[]} | ${false} | ${true} | ${false}
+ ${''} | ${[]} | ${false} | ${false} | ${false}
+ ${'subgroups_and_projects'} | ${mockGroups} | ${false} | ${false} | ${false}
+ ${'subgroups_and_projects'} | ${[]} | ${true} | ${false} | ${true}
`(
- 'when `action` is $action, `groups` is $groups, `fromSearch` is $fromSearch, and `renderEmptyState` is $renderEmptyState',
- ({ action, groups, fromSearch, renderEmptyState, expected }) => {
- it(expected ? 'renders empty state' : 'does not render empty state', async () => {
+ 'when `action` is $action, `groups` is $groups, and `fromSearch` is $fromSearch',
+ ({ action, groups, fromSearch, shouldRenderEmptyState, searchEmpty }) => {
+ it(`${shouldRenderEmptyState ? 'renders' : 'does not render'} empty state`, async () => {
createShallowComponent({
- propsData: { action, renderEmptyState },
+ propsData: { action, renderEmptyState: true },
});
+ await waitForPromises();
+
vm.updateGroups(groups, fromSearch);
await nextTick();
- expect(wrapper.findComponent(EmptyState).exists()).toBe(expected);
+ expect(wrapper.findComponent(EmptyState).exists()).toBe(shouldRenderEmptyState);
+ expect(wrapper.findComponent(GroupsComponent).props('searchEmpty')).toBe(searchEmpty);
});
},
);
@@ -440,18 +444,10 @@ describe('AppComponent', () => {
expect(eventHub.$on).toHaveBeenCalledWith('showLeaveGroupModal', expect.any(Function));
expect(eventHub.$on).toHaveBeenCalledWith('updatePagination', expect.any(Function));
expect(eventHub.$on).toHaveBeenCalledWith('updateGroups', expect.any(Function));
- });
-
- it('should initialize `searchEmptyMessage` prop with correct string when `hideProjects` is `false`', async () => {
- createShallowComponent();
- await nextTick();
- expect(vm.searchEmptyMessage).toBe('No groups or projects matched your search');
- });
-
- it('should initialize `searchEmptyMessage` prop with correct string when `hideProjects` is `true`', async () => {
- createShallowComponent({ propsData: { hideProjects: true } });
- await nextTick();
- expect(vm.searchEmptyMessage).toBe('No groups matched your search');
+ expect(eventHub.$on).toHaveBeenCalledWith(
+ 'fetchFilteredAndSortedGroups',
+ expect.any(Function),
+ );
});
});
@@ -468,6 +464,46 @@ describe('AppComponent', () => {
expect(eventHub.$off).toHaveBeenCalledWith('showLeaveGroupModal', expect.any(Function));
expect(eventHub.$off).toHaveBeenCalledWith('updatePagination', expect.any(Function));
expect(eventHub.$off).toHaveBeenCalledWith('updateGroups', expect.any(Function));
+ expect(eventHub.$off).toHaveBeenCalledWith(
+ 'fetchFilteredAndSortedGroups',
+ expect.any(Function),
+ );
+ });
+ });
+
+ describe('when `fetchFilteredAndSortedGroups` event is emitted', () => {
+ const search = 'Foo bar';
+ const sort = 'created_asc';
+ const emitFetchFilteredAndSortedGroups = () => {
+ eventHub.$emit('fetchFilteredAndSortedGroups', {
+ filterGroupsBy: search,
+ sortBy: sort,
+ });
+ };
+ let setPaginationInfoSpy;
+
+ beforeEach(() => {
+ setPaginationInfoSpy = jest.spyOn(GroupsStore.prototype, 'setPaginationInfo');
+ createShallowComponent();
+ });
+
+ it('renders loading icon', async () => {
+ emitFetchFilteredAndSortedGroups();
+ await nextTick();
+
+ expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true);
+ });
+
+ it('calls API with expected params', () => {
+ emitFetchFilteredAndSortedGroups();
+
+ expect(getGroupsSpy).toHaveBeenCalledWith(undefined, undefined, search, sort, undefined);
+ });
+
+ it('updates pagination', () => {
+ emitFetchFilteredAndSortedGroups();
+
+ expect(setPaginationInfoSpy).toHaveBeenCalled();
});
});
diff --git a/spec/frontend/groups/components/group_item_spec.js b/spec/frontend/groups/components/group_item_spec.js
index 3aa66644c19..4570aa33a6c 100644
--- a/spec/frontend/groups/components/group_item_spec.js
+++ b/spec/frontend/groups/components/group_item_spec.js
@@ -245,19 +245,14 @@ describe('GroupItemComponent', () => {
expect(vm.$el.querySelector('.group-list-tree')).toBeDefined();
});
});
+
describe('schema.org props', () => {
describe('when showSchemaMarkup is disabled on the group', () => {
- it.each(['itemprop', 'itemtype', 'itemscope'], 'it does not set %s', (attr) => {
+ it.each(['itemprop', 'itemtype', 'itemscope'])('does not set %s', (attr) => {
expect(wrapper.attributes(attr)).toBeUndefined();
});
- it.each(
- ['.js-group-avatar', '.js-group-name', '.js-group-description'],
- 'it does not set `itemprop` on sub-nodes',
- (selector) => {
- expect(wrapper.find(selector).attributes('itemprop')).toBeUndefined();
- },
- );
});
+
describe('when group has microdata', () => {
beforeEach(() => {
const group = withMicrodata({
diff --git a/spec/frontend/groups/components/groups_spec.js b/spec/frontend/groups/components/groups_spec.js
index 866868eff36..0cbb6cc8309 100644
--- a/spec/frontend/groups/components/groups_spec.js
+++ b/spec/frontend/groups/components/groups_spec.js
@@ -1,4 +1,5 @@
import Vue from 'vue';
+import { GlEmptyState } from '@gitlab/ui';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import GroupFolderComponent from '~/groups/components/group_folder.vue';
@@ -15,7 +16,6 @@ describe('GroupsComponent', () => {
const defaultPropsData = {
groups: mockGroups,
pageInfo: mockPageInfo,
- searchEmptyMessage: 'No matching results',
searchEmpty: false,
};
@@ -67,13 +67,16 @@ describe('GroupsComponent', () => {
expect(wrapper.findComponent(GroupFolderComponent).exists()).toBe(true);
expect(findPaginationLinks().exists()).toBe(true);
- expect(wrapper.findByText(defaultPropsData.searchEmptyMessage).exists()).toBe(false);
+ expect(wrapper.findComponent(GlEmptyState).exists()).toBe(false);
});
it('should render empty search message when `searchEmpty` is `true`', () => {
createComponent({ propsData: { searchEmpty: true } });
- expect(wrapper.findByText(defaultPropsData.searchEmptyMessage).exists()).toBe(true);
+ expect(wrapper.findComponent(GlEmptyState).props()).toMatchObject({
+ title: GroupsComponent.i18n.emptyStateTitle,
+ description: GroupsComponent.i18n.emptyStateDescription,
+ });
});
});
});
diff --git a/spec/frontend/groups/components/new_top_level_group_alert_spec.js b/spec/frontend/groups/components/new_top_level_group_alert_spec.js
new file mode 100644
index 00000000000..db9a5c7b16b
--- /dev/null
+++ b/spec/frontend/groups/components/new_top_level_group_alert_spec.js
@@ -0,0 +1,75 @@
+import { shallowMount } from '@vue/test-utils';
+import NewTopLevelGroupAlert from '~/groups/components/new_top_level_group_alert.vue';
+import { makeMockUserCalloutDismisser } from 'helpers/mock_user_callout_dismisser';
+import { helpPagePath } from '~/helpers/help_page_helper';
+
+describe('NewTopLevelGroupAlert', () => {
+ let wrapper;
+ let userCalloutDismissSpy;
+
+ const findAlert = () => wrapper.findComponent({ ref: 'newTopLevelAlert' });
+ const createSubGroupPath = '/groups/new?parent_id=1#create-group-pane';
+
+ const createComponent = ({ shouldShowCallout = true } = {}) => {
+ userCalloutDismissSpy = jest.fn();
+
+ wrapper = shallowMount(NewTopLevelGroupAlert, {
+ provide: {
+ createSubGroupPath,
+ },
+ stubs: {
+ UserCalloutDismisser: makeMockUserCalloutDismisser({
+ dismiss: userCalloutDismissSpy,
+ shouldShowCallout,
+ }),
+ },
+ });
+ };
+
+ beforeEach(() => {
+ createComponent();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('when the component is created', () => {
+ beforeEach(() => {
+ createComponent({
+ shouldShowCallout: true,
+ });
+ });
+
+ it('renders a button with a link to create a new sub-group', () => {
+ expect(findAlert().props('primaryButtonText')).toBe(
+ NewTopLevelGroupAlert.i18n.primaryBtnText,
+ );
+ expect(findAlert().props('primaryButtonLink')).toBe(
+ helpPagePath('user/group/subgroups/index'),
+ );
+ });
+ });
+
+ describe('dismissing the alert', () => {
+ beforeEach(() => {
+ findAlert().vm.$emit('dismiss');
+ });
+
+ it('calls the dismiss callback', () => {
+ expect(userCalloutDismissSpy).toHaveBeenCalled();
+ });
+ });
+
+ describe('when the alert has been dismissed', () => {
+ beforeEach(() => {
+ createComponent({
+ shouldShowCallout: false,
+ });
+ });
+
+ it('does not show the alert', () => {
+ expect(findAlert().exists()).toBe(false);
+ });
+ });
+});
diff --git a/spec/frontend/groups/components/overview_tabs_spec.js b/spec/frontend/groups/components/overview_tabs_spec.js
index 352bf25b84f..93e087e10f2 100644
--- a/spec/frontend/groups/components/overview_tabs_spec.js
+++ b/spec/frontend/groups/components/overview_tabs_spec.js
@@ -1,28 +1,46 @@
-import { GlTab } from '@gitlab/ui';
+import { GlSorting, GlSortingItem, GlTab } from '@gitlab/ui';
import { nextTick } from 'vue';
+import { createLocalVue } from '@vue/test-utils';
import AxiosMockAdapter from 'axios-mock-adapter';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import OverviewTabs from '~/groups/components/overview_tabs.vue';
import GroupsApp from '~/groups/components/app.vue';
+import GroupFolderComponent from '~/groups/components/group_folder.vue';
import GroupsStore from '~/groups/store/groups_store';
import GroupsService from '~/groups/service/groups_service';
import { createRouter } from '~/groups/init_overview_tabs';
+import eventHub from '~/groups/event_hub';
import {
ACTIVE_TAB_SUBGROUPS_AND_PROJECTS,
ACTIVE_TAB_SHARED,
ACTIVE_TAB_ARCHIVED,
+ OVERVIEW_TABS_SORTING_ITEMS,
} from '~/groups/constants';
import axios from '~/lib/utils/axios_utils';
+const localVue = createLocalVue();
+localVue.component('GroupFolder', GroupFolderComponent);
const router = createRouter();
+const [SORTING_ITEM_NAME, , SORTING_ITEM_UPDATED] = OVERVIEW_TABS_SORTING_ITEMS;
describe('OverviewTabs', () => {
let wrapper;
+ let axiosMock;
- const endpoints = {
- subgroups_and_projects: '/groups/foobar/-/children.json',
- shared: '/groups/foobar/-/shared_projects.json',
- archived: '/groups/foobar/-/children.json?archived=only',
+ const defaultProvide = {
+ endpoints: {
+ subgroups_and_projects: '/groups/foobar/-/children.json',
+ shared: '/groups/foobar/-/shared_projects.json',
+ archived: '/groups/foobar/-/children.json?archived=only',
+ },
+ newSubgroupPath: '/groups/new',
+ newProjectPath: 'projects/new',
+ newSubgroupIllustration: '',
+ newProjectIllustration: '',
+ emptySubgroupIllustration: '',
+ canCreateSubgroups: false,
+ canCreateProjects: false,
+ initialSort: 'name_asc',
};
const routerMock = {
@@ -31,12 +49,15 @@ describe('OverviewTabs', () => {
const createComponent = async ({
route = { name: ACTIVE_TAB_SUBGROUPS_AND_PROJECTS, params: { group: 'foo/bar/baz' } },
+ provide = {},
} = {}) => {
wrapper = mountExtended(OverviewTabs, {
router,
provide: {
- endpoints,
+ ...defaultProvide,
+ ...provide,
},
+ localVue,
mocks: { $route: route, $router: routerMock },
});
@@ -47,13 +68,13 @@ describe('OverviewTabs', () => {
const findTab = (name) => wrapper.findByRole('tab', { name });
const findSelectedTab = () => wrapper.findByRole('tab', { selected: true });
- afterEach(() => {
- wrapper.destroy();
+ beforeEach(() => {
+ axiosMock = new AxiosMockAdapter(axios);
});
- beforeEach(async () => {
- // eslint-disable-next-line no-new
- new AxiosMockAdapter(axios);
+ afterEach(() => {
+ wrapper.destroy();
+ axiosMock.restore();
});
it('renders `Subgroups and projects` tab with `GroupsApp` component', async () => {
@@ -68,7 +89,7 @@ describe('OverviewTabs', () => {
expect(tabPanel.findComponent(GroupsApp).props()).toMatchObject({
action: ACTIVE_TAB_SUBGROUPS_AND_PROJECTS,
store: new GroupsStore({ showSchemaMarkup: true }),
- service: new GroupsService(endpoints[ACTIVE_TAB_SUBGROUPS_AND_PROJECTS]),
+ service: new GroupsService(defaultProvide.endpoints[ACTIVE_TAB_SUBGROUPS_AND_PROJECTS]),
hideProjects: false,
renderEmptyState: true,
});
@@ -89,7 +110,7 @@ describe('OverviewTabs', () => {
expect(tabPanel.findComponent(GroupsApp).props()).toMatchObject({
action: ACTIVE_TAB_SHARED,
store: new GroupsStore(),
- service: new GroupsService(endpoints[ACTIVE_TAB_SHARED]),
+ service: new GroupsService(defaultProvide.endpoints[ACTIVE_TAB_SHARED]),
hideProjects: false,
renderEmptyState: false,
});
@@ -112,7 +133,7 @@ describe('OverviewTabs', () => {
expect(tabPanel.findComponent(GroupsApp).props()).toMatchObject({
action: ACTIVE_TAB_ARCHIVED,
store: new GroupsStore(),
- service: new GroupsService(endpoints[ACTIVE_TAB_ARCHIVED]),
+ service: new GroupsService(defaultProvide.endpoints[ACTIVE_TAB_ARCHIVED]),
hideProjects: false,
renderEmptyState: false,
});
@@ -120,6 +141,14 @@ describe('OverviewTabs', () => {
expect(tabPanel.vm.$attrs.lazy).toBe(false);
});
+ it('sets `lazy` prop to `false` for initially active tab and `true` for all other tabs', async () => {
+ await createComponent({ route: { name: ACTIVE_TAB_SHARED, params: { group: 'foo/bar' } } });
+
+ expect(findTabPanels().at(0).vm.$attrs.lazy).toBe(true);
+ expect(findTabPanels().at(1).vm.$attrs.lazy).toBe(false);
+ expect(findTabPanels().at(2).vm.$attrs.lazy).toBe(true);
+ });
+
describe.each([
[
{ name: ACTIVE_TAB_SUBGROUPS_AND_PROJECTS, params: { group: 'foo/bar/baz' } },
@@ -184,4 +213,109 @@ describe('OverviewTabs', () => {
expect(routerMock.push).toHaveBeenCalledWith(expectedRoute);
});
});
+
+ describe('searching and sorting', () => {
+ const setup = async () => {
+ jest.spyOn(eventHub, '$emit');
+ await createComponent();
+
+ // Click through tabs so they are all loaded
+ await findTab(OverviewTabs.i18n[ACTIVE_TAB_SHARED]).trigger('click');
+ await findTab(OverviewTabs.i18n[ACTIVE_TAB_ARCHIVED]).trigger('click');
+ await findTab(OverviewTabs.i18n[ACTIVE_TAB_SUBGROUPS_AND_PROJECTS]).trigger('click');
+ };
+
+ const sharedAssertions = ({ search, sort }) => {
+ it('sets `lazy` prop to `true` for all of the non-active tabs so they are reloaded after sort or search is applied', () => {
+ expect(findTabPanels().at(0).vm.$attrs.lazy).toBe(false);
+ expect(findTabPanels().at(1).vm.$attrs.lazy).toBe(true);
+ expect(findTabPanels().at(2).vm.$attrs.lazy).toBe(true);
+ });
+
+ it('emits `fetchFilteredAndSortedGroups` event from `eventHub`', () => {
+ expect(eventHub.$emit).toHaveBeenCalledWith(
+ `${ACTIVE_TAB_SUBGROUPS_AND_PROJECTS}fetchFilteredAndSortedGroups`,
+ {
+ filterGroupsBy: search,
+ sortBy: sort,
+ },
+ );
+ });
+ };
+
+ describe('when search is typed in', () => {
+ const search = 'Foo bar';
+
+ beforeEach(async () => {
+ await setup();
+ await wrapper.findByPlaceholderText(OverviewTabs.i18n.searchPlaceholder).setValue(search);
+ });
+
+ it('updates query string with `filter` key', () => {
+ expect(routerMock.push).toHaveBeenCalledWith({ query: { filter: search } });
+ });
+
+ sharedAssertions({ search, sort: defaultProvide.initialSort });
+ });
+
+ describe('when sort is changed', () => {
+ beforeEach(async () => {
+ await setup();
+ wrapper.findAllComponents(GlSortingItem).at(2).vm.$emit('click');
+ await nextTick();
+ });
+
+ it('updates query string with `sort` key', () => {
+ expect(routerMock.push).toHaveBeenCalledWith({
+ query: { sort: SORTING_ITEM_UPDATED.asc },
+ });
+ });
+
+ sharedAssertions({ search: '', sort: SORTING_ITEM_UPDATED.asc });
+ });
+
+ describe('when sort direction is changed', () => {
+ beforeEach(async () => {
+ await setup();
+ await wrapper
+ .findByRole('button', { name: 'Sorting Direction: Ascending' })
+ .trigger('click');
+ });
+
+ it('updates query string with `sort` key', () => {
+ expect(routerMock.push).toHaveBeenCalledWith({
+ query: { sort: SORTING_ITEM_NAME.desc },
+ });
+ });
+
+ sharedAssertions({ search: '', sort: SORTING_ITEM_NAME.desc });
+ });
+
+ describe('when `filter` and `sort` query strings are set', () => {
+ beforeEach(async () => {
+ await createComponent({
+ route: {
+ name: ACTIVE_TAB_SUBGROUPS_AND_PROJECTS,
+ params: { group: 'foo/bar/baz' },
+ query: { filter: 'Foo bar', sort: SORTING_ITEM_UPDATED.desc },
+ },
+ });
+ });
+
+ it('sets value of search input', () => {
+ expect(
+ wrapper.findByPlaceholderText(OverviewTabs.i18n.searchPlaceholder).element.value,
+ ).toBe('Foo bar');
+ });
+
+ it('sets sort dropdown', () => {
+ expect(wrapper.findComponent(GlSorting).props()).toMatchObject({
+ text: SORTING_ITEM_UPDATED.label,
+ isAscending: false,
+ });
+
+ expect(wrapper.findAllComponents(GlSortingItem).at(2).vm.$attrs.active).toBe(true);
+ });
+ });
+ });
});
diff --git a/spec/frontend/groups/components/transfer_group_form_spec.js b/spec/frontend/groups/components/transfer_group_form_spec.js
index 8cfe8ce8e18..7cbe6e5bbab 100644
--- a/spec/frontend/groups/components/transfer_group_form_spec.js
+++ b/spec/frontend/groups/components/transfer_group_form_spec.js
@@ -2,7 +2,7 @@ import { GlAlert, GlSprintf } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import Component from '~/groups/components/transfer_group_form.vue';
import ConfirmDanger from '~/vue_shared/components/confirm_danger/confirm_danger.vue';
-import NamespaceSelect from '~/vue_shared/components/namespace_select/namespace_select.vue';
+import NamespaceSelect from '~/vue_shared/components/namespace_select/namespace_select_deprecated.vue';
describe('Transfer group form', () => {
let wrapper;