diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-05-19 15:44:42 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-05-19 15:44:42 +0000 |
commit | 4555e1b21c365ed8303ffb7a3325d773c9b8bf31 (patch) | |
tree | 5423a1c7516cffe36384133ade12572cf709398d /spec/frontend/members | |
parent | e570267f2f6b326480d284e0164a6464ba4081bc (diff) | |
download | gitlab-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.js | 31 | ||||
-rw-r--r-- | spec/frontend/members/components/members_tabs_spec.js | 194 | ||||
-rw-r--r-- | spec/frontend/members/components/table/members_table_spec.js | 98 | ||||
-rw-r--r-- | spec/frontend/members/index_spec.js | 13 | ||||
-rw-r--r-- | spec/frontend/members/mock_data.js | 25 | ||||
-rw-r--r-- | spec/frontend/members/utils_spec.js | 13 |
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', }); }); }); |