summaryrefslogtreecommitdiff
path: root/spec/frontend/members
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-05-19 15:44:42 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2021-05-19 15:44:42 +0000
commit4555e1b21c365ed8303ffb7a3325d773c9b8bf31 (patch)
tree5423a1c7516cffe36384133ade12572cf709398d /spec/frontend/members
parente570267f2f6b326480d284e0164a6464ba4081bc (diff)
downloadgitlab-ce-4555e1b21c365ed8303ffb7a3325d773c9b8bf31.tar.gz
Add latest changes from gitlab-org/gitlab@13-12-stable-eev13.12.0-rc42
Diffstat (limited to 'spec/frontend/members')
-rw-r--r--spec/frontend/members/components/filter_sort/members_filtered_search_bar_spec.js31
-rw-r--r--spec/frontend/members/components/members_tabs_spec.js194
-rw-r--r--spec/frontend/members/components/table/members_table_spec.js98
-rw-r--r--spec/frontend/members/index_spec.js13
-rw-r--r--spec/frontend/members/mock_data.js25
-rw-r--r--spec/frontend/members/utils_spec.js13
6 files changed, 359 insertions, 15 deletions
diff --git a/spec/frontend/members/components/filter_sort/members_filtered_search_bar_spec.js b/spec/frontend/members/components/filter_sort/members_filtered_search_bar_spec.js
index af5434f7068..5e04e20801a 100644
--- a/spec/frontend/members/components/filter_sort/members_filtered_search_bar_spec.js
+++ b/spec/frontend/members/components/filter_sort/members_filtered_search_bar_spec.js
@@ -3,6 +3,7 @@ import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import MembersFilteredSearchBar from '~/members/components/filter_sort/members_filtered_search_bar.vue';
import { MEMBER_TYPES } from '~/members/constants';
+import { OPERATOR_IS_ONLY } from '~/vue_shared/components/filtered_search_bar/constants';
import FilteredSearchBar from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
const localVue = createLocalVue();
@@ -65,7 +66,7 @@ describe('MembersFilteredSearchBar', () => {
title: '2FA',
token: GlFilteredSearchToken,
unique: true,
- operators: [{ value: '=', description: 'is' }],
+ operators: OPERATOR_IS_ONLY,
options: [
{ value: 'enabled', title: 'Enabled' },
{ value: 'disabled', title: 'Disabled' },
@@ -99,7 +100,7 @@ describe('MembersFilteredSearchBar', () => {
title: 'Membership',
token: GlFilteredSearchToken,
unique: true,
- operators: [{ value: '=', description: 'is' }],
+ operators: OPERATOR_IS_ONLY,
options: [
{ value: 'exclude', title: 'Direct' },
{ value: 'only', title: 'Inherited' },
@@ -146,6 +147,21 @@ describe('MembersFilteredSearchBar', () => {
},
]);
});
+
+ it('parses and passes search param with multiple words to `FilteredSearchBar` component as `initialFilterValue` prop', () => {
+ window.location.search = '?search=foo+bar+baz';
+
+ createComponent();
+
+ expect(findFilteredSearchBar().props('initialFilterValue')).toEqual([
+ {
+ type: 'filtered-search-term',
+ value: {
+ data: 'foo bar baz',
+ },
+ },
+ ]);
+ });
});
describe('when filter bar is submitted', () => {
@@ -175,6 +191,17 @@ describe('MembersFilteredSearchBar', () => {
expect(window.location.href).toBe('https://localhost/?two_factor=enabled&search=foobar');
});
+ it('adds search query param with multiple words', () => {
+ createComponent();
+
+ findFilteredSearchBar().vm.$emit('onFilter', [
+ { type: 'two_factor', value: { data: 'enabled', operator: '=' } },
+ { type: 'filtered-search-term', value: { data: 'foo bar baz' } },
+ ]);
+
+ expect(window.location.href).toBe('https://localhost/?two_factor=enabled&search=foo+bar+baz');
+ });
+
it('adds sort query param', () => {
window.location.search = '?sort=name_asc';
diff --git a/spec/frontend/members/components/members_tabs_spec.js b/spec/frontend/members/components/members_tabs_spec.js
new file mode 100644
index 00000000000..28614b52706
--- /dev/null
+++ b/spec/frontend/members/components/members_tabs_spec.js
@@ -0,0 +1,194 @@
+import Vue, { nextTick } from 'vue';
+import Vuex from 'vuex';
+import { mountExtended } from 'helpers/vue_test_utils_helper';
+import MembersApp from '~/members/components/app.vue';
+import MembersTabs from '~/members/components/members_tabs.vue';
+import { MEMBER_TYPES } from '~/members/constants';
+import { pagination } from '../mock_data';
+
+describe('MembersApp', () => {
+ Vue.use(Vuex);
+
+ let wrapper;
+
+ const createComponent = ({ totalItems = 10, options = {} } = {}) => {
+ const store = new Vuex.Store({
+ modules: {
+ [MEMBER_TYPES.user]: {
+ namespaced: true,
+ state: {
+ pagination: {
+ ...pagination,
+ totalItems,
+ },
+ filteredSearchBar: {
+ searchParam: 'search',
+ },
+ },
+ },
+ [MEMBER_TYPES.group]: {
+ namespaced: true,
+ state: {
+ pagination: {
+ ...pagination,
+ totalItems,
+ paramName: 'groups_page',
+ },
+ filteredSearchBar: {
+ searchParam: 'search_groups',
+ },
+ },
+ },
+ [MEMBER_TYPES.invite]: {
+ namespaced: true,
+ state: {
+ pagination: {
+ ...pagination,
+ totalItems,
+ paramName: 'invited_page',
+ },
+ filteredSearchBar: {
+ searchParam: 'search_invited',
+ },
+ },
+ },
+ [MEMBER_TYPES.accessRequest]: {
+ namespaced: true,
+ state: {
+ pagination: {
+ ...pagination,
+ totalItems,
+ paramName: 'access_requests_page',
+ },
+ filteredSearchBar: {
+ searchParam: 'search_access_requests',
+ },
+ },
+ },
+ },
+ });
+
+ wrapper = mountExtended(MembersTabs, {
+ store,
+ stubs: ['members-app'],
+ provide: {
+ canManageMembers: true,
+ },
+ ...options,
+ });
+
+ return nextTick();
+ };
+
+ const findTabs = () => wrapper.findAllByRole('tab').wrappers;
+ const findTabByText = (text) => findTabs().find((tab) => tab.text().includes(text));
+ const findActiveTab = () => wrapper.findByRole('tab', { selected: true });
+
+ beforeEach(() => {
+ delete window.location;
+ window.location = new URL('https://localhost');
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('when tabs have a count', () => {
+ it('renders tabs with count', async () => {
+ await createComponent();
+
+ const tabs = findTabs();
+
+ expect(tabs[0].text()).toBe('Members 10');
+ expect(tabs[1].text()).toBe('Groups 10');
+ expect(tabs[2].text()).toBe('Invited 10');
+ expect(tabs[3].text()).toBe('Access requests 10');
+ expect(findActiveTab().text()).toContain('Members');
+ });
+
+ it('renders `MembersApp` and passes `namespace` prop', async () => {
+ await createComponent();
+
+ const membersApps = wrapper.findAllComponents(MembersApp).wrappers;
+
+ expect(membersApps[0].attributes('namespace')).toBe(MEMBER_TYPES.user);
+ expect(membersApps[1].attributes('namespace')).toBe(MEMBER_TYPES.group);
+ expect(membersApps[2].attributes('namespace')).toBe(MEMBER_TYPES.invite);
+ expect(membersApps[3].attributes('namespace')).toBe(MEMBER_TYPES.accessRequest);
+ });
+ });
+
+ describe('when tabs do not have a count', () => {
+ it('only renders `Members` tab', async () => {
+ await createComponent({ totalItems: 0 });
+
+ expect(findTabByText('Members')).not.toBeUndefined();
+ expect(findTabByText('Groups')).toBeUndefined();
+ expect(findTabByText('Invited')).toBeUndefined();
+ expect(findTabByText('Access requests')).toBeUndefined();
+ });
+ });
+
+ describe('when url param matches `filteredSearchBar.searchParam`', () => {
+ beforeEach(() => {
+ window.location.search = '?search_groups=foo+bar';
+ });
+
+ const expectGroupsTabActive = () => {
+ expect(findActiveTab().text()).toContain('Groups');
+ };
+
+ describe('when tab has a count', () => {
+ it('sets tab that corresponds to search param as active tab', async () => {
+ await createComponent();
+
+ expectGroupsTabActive();
+ });
+ });
+
+ describe('when tab does not have a count', () => {
+ it('sets tab that corresponds to search param as active tab', async () => {
+ await createComponent({ totalItems: 0 });
+
+ expectGroupsTabActive();
+ });
+ });
+ });
+
+ describe('when url param matches `pagination.paramName`', () => {
+ beforeEach(() => {
+ window.location.search = '?invited_page=2';
+ });
+
+ const expectInvitedTabActive = () => {
+ expect(findActiveTab().text()).toContain('Invited');
+ };
+
+ describe('when tab has a count', () => {
+ it('sets tab that corresponds to pagination param as active tab', async () => {
+ await createComponent();
+
+ expectInvitedTabActive();
+ });
+ });
+
+ describe('when tab does not have a count', () => {
+ it('sets tab that corresponds to pagination param as active tab', async () => {
+ await createComponent({ totalItems: 0 });
+
+ expectInvitedTabActive();
+ });
+ });
+ });
+
+ describe('when `canManageMembers` is `false`', () => {
+ it('shows all tabs except `Invited` and `Access requests`', async () => {
+ await createComponent({ options: { provide: { canManageMembers: false } } });
+
+ expect(findTabByText('Members')).not.toBeUndefined();
+ expect(findTabByText('Groups')).not.toBeUndefined();
+ expect(findTabByText('Invited')).toBeUndefined();
+ expect(findTabByText('Access requests')).toBeUndefined();
+ });
+ });
+});
diff --git a/spec/frontend/members/components/table/members_table_spec.js b/spec/frontend/members/components/table/members_table_spec.js
index 5cf1f40a8f4..5308d7651a3 100644
--- a/spec/frontend/members/components/table/members_table_spec.js
+++ b/spec/frontend/members/components/table/members_table_spec.js
@@ -1,4 +1,4 @@
-import { GlBadge, GlTable } from '@gitlab/ui';
+import { GlBadge, GlPagination, GlTable } from '@gitlab/ui';
import {
getByText as getByTextHelper,
getByTestId as getByTestIdHelper,
@@ -6,6 +6,7 @@ import {
} from '@testing-library/dom';
import { mount, createLocalVue, createWrapper } from '@vue/test-utils';
import Vuex from 'vuex';
+import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import CreatedAt from '~/members/components/table/created_at.vue';
import ExpirationDatepicker from '~/members/components/table/expiration_datepicker.vue';
import ExpiresAt from '~/members/components/table/expires_at.vue';
@@ -16,7 +17,13 @@ import MembersTable from '~/members/components/table/members_table.vue';
import RoleDropdown from '~/members/components/table/role_dropdown.vue';
import { MEMBER_TYPES } from '~/members/constants';
import * as initUserPopovers from '~/user_popovers';
-import { member as memberMock, directMember, invite, accessRequest } from '../../mock_data';
+import {
+ member as memberMock,
+ directMember,
+ invite,
+ accessRequest,
+ pagination,
+} from '../../mock_data';
const localVue = createLocalVue();
localVue.use(Vuex);
@@ -36,6 +43,7 @@ describe('MembersTable', () => {
table: { 'data-qa-selector': 'members_list' },
tr: { 'data-qa-selector': 'member_row' },
},
+ pagination,
...state,
},
},
@@ -66,6 +74,8 @@ describe('MembersTable', () => {
});
};
+ const url = 'https://localhost/foo-bar/-/project_members';
+
const getByText = (text, options) =>
createWrapper(getByTextHelper(wrapper.element, text, options));
@@ -78,6 +88,14 @@ describe('MembersTable', () => {
`[data-label="${tableCellLabel}"][role="cell"]`,
);
+ const findPagination = () => extendedWrapper(wrapper.find(GlPagination));
+
+ const expectCorrectLinkToPage2 = () => {
+ expect(findPagination().findByText('2', { selector: 'a' }).attributes('href')).toBe(
+ `${url}?page=2`,
+ );
+ };
+
afterEach(() => {
wrapper.destroy();
wrapper = null;
@@ -219,4 +237,80 @@ describe('MembersTable', () => {
expect(findTable().find('tbody tr').attributes('data-qa-selector')).toBe('member_row');
});
+
+ describe('when required pagination data is provided', () => {
+ beforeEach(() => {
+ delete window.location;
+ });
+
+ it('renders `gl-pagination` component with correct props', () => {
+ window.location = new URL(url);
+
+ createComponent();
+
+ const glPagination = findPagination();
+
+ expect(glPagination.exists()).toBe(true);
+ expect(glPagination.props()).toMatchObject({
+ value: pagination.currentPage,
+ perPage: pagination.perPage,
+ totalItems: pagination.totalItems,
+ prevText: 'Prev',
+ nextText: 'Next',
+ labelNextPage: 'Go to next page',
+ labelPrevPage: 'Go to previous page',
+ align: 'center',
+ });
+ });
+
+ it('uses `pagination.paramName` to generate the pagination links', () => {
+ window.location = new URL(url);
+
+ createComponent({
+ pagination: {
+ currentPage: 1,
+ perPage: 5,
+ totalItems: 10,
+ paramName: 'page',
+ },
+ });
+
+ expectCorrectLinkToPage2();
+ });
+
+ it('removes any url params defined as `null` in the `params` attribute', () => {
+ window.location = new URL(`${url}?search_groups=foo`);
+
+ createComponent({
+ pagination: {
+ currentPage: 1,
+ perPage: 5,
+ totalItems: 10,
+ paramName: 'page',
+ params: { search_groups: null },
+ },
+ });
+
+ expectCorrectLinkToPage2();
+ });
+ });
+
+ describe.each`
+ attribute | value
+ ${'paramName'} | ${null}
+ ${'currentPage'} | ${null}
+ ${'perPage'} | ${null}
+ ${'totalItems'} | ${0}
+ `('when pagination.$attribute is $value', ({ attribute, value }) => {
+ it('does not render `gl-pagination`', () => {
+ createComponent({
+ pagination: {
+ ...pagination,
+ [attribute]: value,
+ },
+ });
+
+ expect(findPagination().exists()).toBe(false);
+ });
+ });
});
diff --git a/spec/frontend/members/index_spec.js b/spec/frontend/members/index_spec.js
index 8b645d9b059..b07534ae4ed 100644
--- a/spec/frontend/members/index_spec.js
+++ b/spec/frontend/members/index_spec.js
@@ -2,7 +2,7 @@ import { createWrapper } from '@vue/test-utils';
import MembersApp from '~/members/components/app.vue';
import { MEMBER_TYPES } from '~/members/constants';
import { initMembersApp } from '~/members/index';
-import { membersJsonString, members } from './mock_data';
+import { members, pagination, dataAttribute } from './mock_data';
describe('initMembersApp', () => {
let el;
@@ -23,10 +23,7 @@ describe('initMembersApp', () => {
beforeEach(() => {
el = document.createElement('div');
- el.setAttribute('data-members', membersJsonString);
- el.setAttribute('data-source-id', '234');
- el.setAttribute('data-can-manage-members', 'true');
- el.setAttribute('data-member-path', '/groups/foo-bar/-/group_members/:id');
+ el.setAttribute('data-members-data', dataAttribute);
window.gon = { current_user_id: 123 };
});
@@ -50,6 +47,12 @@ describe('initMembersApp', () => {
expect(vm.$store.state[MEMBER_TYPES.user].members).toEqual(members);
});
+ it('parses and sets `pagination` in Vuex store', () => {
+ setup();
+
+ expect(vm.$store.state[MEMBER_TYPES.user].pagination).toEqual(pagination);
+ });
+
it('sets `tableFields` in Vuex store', () => {
setup();
diff --git a/spec/frontend/members/mock_data.js b/spec/frontend/members/mock_data.js
index a47b7ab2118..d0a7c36349b 100644
--- a/spec/frontend/members/mock_data.js
+++ b/spec/frontend/members/mock_data.js
@@ -79,3 +79,28 @@ export const directMember = { ...member, isDirectMember: true };
export const inheritedMember = { ...member, isDirectMember: false };
export const member2faEnabled = { ...member, user: { ...member.user, twoFactorEnabled: true } };
+
+export const paginationData = {
+ current_page: 1,
+ per_page: 5,
+ total_items: 10,
+ param_name: 'page',
+ params: { search_groups: null },
+};
+
+export const pagination = {
+ currentPage: 1,
+ perPage: 5,
+ totalItems: 10,
+ paramName: 'page',
+ params: { search_groups: null },
+};
+
+export const dataAttribute = JSON.stringify({
+ members,
+ pagination: paginationData,
+ source_id: 234,
+ can_manage_members: true,
+ member_path: '/groups/foo-bar/-/group_members/:id',
+ ldap_override_path: '/groups/ldap-group/-/group_members/:id/override',
+});
diff --git a/spec/frontend/members/utils_spec.js b/spec/frontend/members/utils_spec.js
index bfb5a4bc7d3..72696979722 100644
--- a/spec/frontend/members/utils_spec.js
+++ b/spec/frontend/members/utils_spec.js
@@ -20,8 +20,9 @@ import {
member2faEnabled,
group,
invite,
- membersJsonString,
members,
+ pagination,
+ dataAttribute,
} from './mock_data';
const IS_CURRENT_USER_ID = 123;
@@ -258,20 +259,20 @@ describe('Members Utils', () => {
beforeEach(() => {
el = document.createElement('div');
- el.setAttribute('data-members', membersJsonString);
- el.setAttribute('data-source-id', '234');
- el.setAttribute('data-can-manage-members', 'true');
+ el.setAttribute('data-members-data', dataAttribute);
});
afterEach(() => {
el = null;
});
- it('correctly parses the data attributes', () => {
- expect(parseDataAttributes(el)).toEqual({
+ it('correctly parses the data attribute', () => {
+ expect(parseDataAttributes(el)).toMatchObject({
members,
+ pagination,
sourceId: 234,
canManageMembers: true,
+ memberPath: '/groups/foo-bar/-/group_members/:id',
});
});
});