diff options
Diffstat (limited to 'spec/frontend/members')
11 files changed, 294 insertions, 73 deletions
diff --git a/spec/frontend/members/components/action_buttons/invite_action_buttons_spec.js b/spec/frontend/members/components/action_buttons/invite_action_buttons_spec.js index e7a99a96da6..79252456f67 100644 --- a/spec/frontend/members/components/action_buttons/invite_action_buttons_spec.js +++ b/spec/frontend/members/components/action_buttons/invite_action_buttons_spec.js @@ -37,7 +37,7 @@ describe('InviteActionButtons', () => { }); it('sets props correctly', () => { - expect(findRemoveMemberButton().props()).toEqual({ + expect(findRemoveMemberButton().props()).toMatchObject({ memberId: member.id, memberType: null, message: `Are you sure you want to revoke the invitation for ${member.invite.email} to join "${member.source.fullName}"`, diff --git a/spec/frontend/members/components/action_buttons/remove_member_button_spec.js b/spec/frontend/members/components/action_buttons/remove_member_button_spec.js index 4ff12f7fa97..d8453d453e7 100644 --- a/spec/frontend/members/components/action_buttons/remove_member_button_spec.js +++ b/spec/frontend/members/components/action_buttons/remove_member_button_spec.js @@ -1,6 +1,8 @@ +import { GlButton } from '@gitlab/ui'; import { shallowMount, createLocalVue } from '@vue/test-utils'; import Vuex from 'vuex'; import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; +import { modalData } from 'jest/members/mock_data'; import RemoveMemberButton from '~/members/components/action_buttons/remove_member_button.vue'; import { MEMBER_TYPES } from '~/members/constants'; @@ -10,6 +12,10 @@ localVue.use(Vuex); describe('RemoveMemberButton', () => { let wrapper; + const actions = { + showRemoveMemberModal: jest.fn(), + }; + const createStore = (state = {}) => { return new Vuex.Store({ modules: { @@ -19,6 +25,7 @@ describe('RemoveMemberButton', () => { memberPath: '/groups/foo-bar/-/group_members/:id', ...state, }, + actions, }, }, }); @@ -47,20 +54,16 @@ describe('RemoveMemberButton', () => { }); }; + beforeEach(() => { + createComponent(); + }); + afterEach(() => { wrapper.destroy(); }); it('sets attributes on button', () => { - createComponent(); - expect(wrapper.attributes()).toMatchObject({ - 'data-member-path': '/groups/foo-bar/-/group_members/1', - 'data-member-type': 'GroupMember', - 'data-message': 'Are you sure you want to remove John Smith?', - 'data-is-access-request': 'true', - 'data-is-invite': 'true', - 'data-oncall-schedules': '{"name":"user","schedules":[]}', 'aria-label': 'Remove member', title: 'Remove member', icon: 'remove', @@ -68,14 +71,12 @@ describe('RemoveMemberButton', () => { }); it('displays `title` prop as a tooltip', () => { - createComponent(); - expect(getBinding(wrapper.element, 'gl-tooltip')).not.toBeUndefined(); }); - it('has CSS class used by `remove_member_modal.vue`', () => { - createComponent(); + it('calls Vuex action to show `remove member` modal when clicked', () => { + wrapper.findComponent(GlButton).vm.$emit('click'); - expect(wrapper.classes()).toContain('js-remove-member-button'); + expect(actions.showRemoveMemberModal).toHaveBeenCalledWith(expect.any(Object), modalData); }); }); 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 a3b91cb20bb..3f47fa024bc 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 @@ -1,11 +1,23 @@ import { GlFilteredSearchToken } from '@gitlab/ui'; import { shallowMount, createLocalVue } from '@vue/test-utils'; import Vuex from 'vuex'; +import setWindowLocation from 'helpers/set_window_location_helper'; +import { redirectTo } from '~/lib/utils/url_utility'; 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'; +jest.mock('~/lib/utils/url_utility', () => { + const urlUtility = jest.requireActual('~/lib/utils/url_utility'); + + return { + __esModule: true, + ...urlUtility, + redirectTo: jest.fn(), + }; +}); + const localVue = createLocalVue(); localVue.use(Vuex); @@ -113,12 +125,11 @@ describe('MembersFilteredSearchBar', () => { describe('when filters are set via query params', () => { beforeEach(() => { - delete window.location; - window.location = new URL('https://localhost'); + setWindowLocation('https://localhost'); }); it('parses and passes tokens to `FilteredSearchBar` component as `initialFilterValue` prop', () => { - window.location.search = '?two_factor=enabled&token_not_available=foobar'; + setWindowLocation('?two_factor=enabled&token_not_available=foobar'); createComponent(); @@ -134,7 +145,7 @@ describe('MembersFilteredSearchBar', () => { }); it('parses and passes search param to `FilteredSearchBar` component as `initialFilterValue` prop', () => { - window.location.search = '?search=foobar'; + setWindowLocation('?search=foobar'); createComponent(); @@ -149,7 +160,7 @@ describe('MembersFilteredSearchBar', () => { }); it('parses and passes search param with multiple words to `FilteredSearchBar` component as `initialFilterValue` prop', () => { - window.location.search = '?search=foo+bar+baz'; + setWindowLocation('?search=foo+bar+baz'); createComponent(); @@ -166,8 +177,7 @@ describe('MembersFilteredSearchBar', () => { describe('when filter bar is submitted', () => { beforeEach(() => { - delete window.location; - window.location = new URL('https://localhost'); + setWindowLocation('https://localhost'); }); it('adds correct filter query params', () => { @@ -177,7 +187,7 @@ describe('MembersFilteredSearchBar', () => { { type: 'two_factor', value: { data: 'enabled', operator: '=' } }, ]); - expect(window.location.href).toBe('https://localhost/?two_factor=enabled'); + expect(redirectTo).toHaveBeenCalledWith('https://localhost/?two_factor=enabled'); }); it('adds search query param', () => { @@ -188,7 +198,9 @@ describe('MembersFilteredSearchBar', () => { { type: 'filtered-search-term', value: { data: 'foobar' } }, ]); - expect(window.location.href).toBe('https://localhost/?two_factor=enabled&search=foobar'); + expect(redirectTo).toHaveBeenCalledWith( + 'https://localhost/?two_factor=enabled&search=foobar', + ); }); it('adds search query param with multiple words', () => { @@ -199,11 +211,13 @@ describe('MembersFilteredSearchBar', () => { { type: 'filtered-search-term', value: { data: 'foo bar baz' } }, ]); - expect(window.location.href).toBe('https://localhost/?two_factor=enabled&search=foo+bar+baz'); + expect(redirectTo).toHaveBeenCalledWith( + 'https://localhost/?two_factor=enabled&search=foo+bar+baz', + ); }); it('adds sort query param', () => { - window.location.search = '?sort=name_asc'; + setWindowLocation('?sort=name_asc'); createComponent(); @@ -212,13 +226,13 @@ describe('MembersFilteredSearchBar', () => { { type: 'filtered-search-term', value: { data: 'foobar' } }, ]); - expect(window.location.href).toBe( + expect(redirectTo).toHaveBeenCalledWith( 'https://localhost/?two_factor=enabled&search=foobar&sort=name_asc', ); }); it('adds active tab query param', () => { - window.location.search = '?tab=invited'; + setWindowLocation('?tab=invited'); createComponent(); @@ -226,7 +240,7 @@ describe('MembersFilteredSearchBar', () => { { type: 'filtered-search-term', value: { data: 'foobar' } }, ]); - expect(window.location.href).toBe('https://localhost/?search=foobar&tab=invited'); + expect(redirectTo).toHaveBeenCalledWith('https://localhost/?search=foobar&tab=invited'); }); }); }); diff --git a/spec/frontend/members/components/filter_sort/sort_dropdown_spec.js b/spec/frontend/members/components/filter_sort/sort_dropdown_spec.js index 4b335755980..d0684acd487 100644 --- a/spec/frontend/members/components/filter_sort/sort_dropdown_spec.js +++ b/spec/frontend/members/components/filter_sort/sort_dropdown_spec.js @@ -1,6 +1,7 @@ import { GlSorting, GlSortingItem } from '@gitlab/ui'; import { mount, createLocalVue } from '@vue/test-utils'; import Vuex from 'vuex'; +import setWindowLocation from 'helpers/set_window_location_helper'; import * as urlUtilities from '~/lib/utils/url_utility'; import SortDropdown from '~/members/components/filter_sort/sort_dropdown.vue'; import { MEMBER_TYPES } from '~/members/constants'; @@ -52,17 +53,16 @@ describe('SortDropdown', () => { .findAll(GlSortingItem) .wrappers.find((dropdownItemWrapper) => dropdownItemWrapper.text() === text); - describe('dropdown options', () => { - beforeEach(() => { - delete window.location; - window.location = new URL(URL_HOST); - }); + beforeEach(() => { + setWindowLocation(URL_HOST); + }); + describe('dropdown options', () => { it('adds dropdown items for all the sortable fields', () => { const URL_FILTER_PARAMS = '?two_factor=enabled&search=foobar'; const EXPECTED_BASE_URL = `${URL_HOST}${URL_FILTER_PARAMS}&sort=`; - window.location.search = URL_FILTER_PARAMS; + setWindowLocation(URL_FILTER_PARAMS); const expectedDropdownItems = [ { @@ -94,7 +94,7 @@ describe('SortDropdown', () => { }); it('checks selected sort option', () => { - window.location.search = '?sort=access_level_asc'; + setWindowLocation('?sort=access_level_asc'); createComponent(); @@ -103,11 +103,6 @@ describe('SortDropdown', () => { }); describe('dropdown toggle', () => { - beforeEach(() => { - delete window.location; - window.location = new URL(URL_HOST); - }); - it('defaults to sorting by "Account" in ascending order', () => { createComponent(); @@ -116,7 +111,7 @@ describe('SortDropdown', () => { }); it('sets text as selected sort option', () => { - window.location.search = '?sort=access_level_asc'; + setWindowLocation('?sort=access_level_asc'); createComponent(); @@ -126,15 +121,12 @@ describe('SortDropdown', () => { describe('sort direction toggle', () => { beforeEach(() => { - delete window.location; - window.location = new URL(URL_HOST); - - jest.spyOn(urlUtilities, 'visitUrl'); + jest.spyOn(urlUtilities, 'visitUrl').mockImplementation(); }); describe('when current sort direction is ascending', () => { beforeEach(() => { - window.location.search = '?sort=access_level_asc'; + setWindowLocation('?sort=access_level_asc'); createComponent(); }); @@ -152,7 +144,7 @@ describe('SortDropdown', () => { describe('when current sort direction is descending', () => { beforeEach(() => { - window.location.search = '?sort=access_level_desc'; + setWindowLocation('?sort=access_level_desc'); createComponent(); }); diff --git a/spec/frontend/members/components/members_tabs_spec.js b/spec/frontend/members/components/members_tabs_spec.js index 33d8eebf7eb..1d882e5ef09 100644 --- a/spec/frontend/members/components/members_tabs_spec.js +++ b/spec/frontend/members/components/members_tabs_spec.js @@ -1,6 +1,7 @@ -import { GlTabs } from '@gitlab/ui'; +import { GlTabs, GlButton } from '@gitlab/ui'; import Vue, { nextTick } from 'vue'; import Vuex from 'vuex'; +import setWindowLocation from 'helpers/set_window_location_helper'; import { mountExtended } from 'helpers/vue_test_utils_helper'; import MembersApp from '~/members/components/app.vue'; import MembersTabs from '~/members/components/members_tabs.vue'; @@ -16,7 +17,7 @@ describe('MembersTabs', () => { let wrapper; - const createComponent = ({ totalItems = 10, options = {} } = {}) => { + const createComponent = ({ totalItems = 10, provide = {} } = {}) => { const store = new Vuex.Store({ modules: { [MEMBER_TYPES.user]: { @@ -78,8 +79,10 @@ describe('MembersTabs', () => { stubs: ['members-app'], provide: { canManageMembers: true, + canExportMembers: true, + exportCsvPath: '', + ...provide, }, - ...options, }); return nextTick(); @@ -88,10 +91,10 @@ describe('MembersTabs', () => { const findTabs = () => wrapper.findAllByRole('tab').wrappers; const findTabByText = (text) => findTabs().find((tab) => tab.text().includes(text)); const findActiveTab = () => wrapper.findByRole('tab', { selected: true }); + const findExportButton = () => wrapper.findComponent(GlButton); beforeEach(() => { - delete window.location; - window.location = new URL('https://localhost'); + setWindowLocation('https://localhost'); }); afterEach(() => { @@ -151,7 +154,7 @@ describe('MembersTabs', () => { describe('when url param matches `filteredSearchBar.searchParam`', () => { beforeEach(() => { - window.location.search = '?search_groups=foo+bar'; + setWindowLocation('?search_groups=foo+bar'); }); it('shows tab that corresponds to search param', async () => { @@ -164,7 +167,7 @@ describe('MembersTabs', () => { describe('when `canManageMembers` is `false`', () => { it('shows all tabs except `Invited` and `Access requests`', async () => { - await createComponent({ options: { provide: { canManageMembers: false } } }); + await createComponent({ provide: { canManageMembers: false } }); expect(findTabByText('Members')).not.toBeUndefined(); expect(findTabByText('Groups')).not.toBeUndefined(); @@ -172,4 +175,20 @@ describe('MembersTabs', () => { expect(findTabByText('Access requests')).toBeUndefined(); }); }); + + describe('when `canExportMembers` is true', () => { + it('shows the CSV export button with export path', async () => { + await createComponent({ provide: { canExportMembers: true, exportCsvPath: 'foo' } }); + + expect(findExportButton().attributes('href')).toBe('foo'); + }); + }); + + describe('when `canExportMembers` is false', () => { + it('does not show the CSV export button', async () => { + await createComponent({ provide: { canExportMembers: false } }); + + expect(findExportButton().exists()).toBe(false); + }); + }); }); diff --git a/spec/frontend/members/components/modals/remove_member_modal_spec.js b/spec/frontend/members/components/modals/remove_member_modal_spec.js new file mode 100644 index 00000000000..1dc41582c12 --- /dev/null +++ b/spec/frontend/members/components/modals/remove_member_modal_spec.js @@ -0,0 +1,133 @@ +import { GlModal } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; +import Vue from 'vue'; +import Vuex from 'vuex'; +import RemoveMemberModal from '~/members/components/modals/remove_member_modal.vue'; +import { MEMBER_TYPES } from '~/members/constants'; +import OncallSchedulesList from '~/vue_shared/components/oncall_schedules_list.vue'; + +Vue.use(Vuex); + +describe('RemoveMemberModal', () => { + const memberPath = '/gitlab-org/gitlab-test/-/project_members/90'; + const mockSchedules = { + name: 'User1', + schedules: [{ id: 1, name: 'Schedule 1' }], + }; + let wrapper; + + const actions = { + hideRemoveMemberModal: jest.fn(), + }; + + const createStore = (removeMemberModalData) => + new Vuex.Store({ + modules: { + [MEMBER_TYPES.user]: { + namespaced: true, + state: { + removeMemberModalData, + }, + actions, + }, + }, + }); + + const createComponent = (state) => { + wrapper = shallowMount(RemoveMemberModal, { + store: createStore(state), + provide: { + namespace: MEMBER_TYPES.user, + }, + }); + }; + + const findForm = () => wrapper.find({ ref: 'form' }); + const findGlModal = () => wrapper.findComponent(GlModal); + const findOnCallSchedulesList = () => wrapper.findComponent(OncallSchedulesList); + + afterEach(() => { + wrapper.destroy(); + }); + + describe.each` + state | memberType | isAccessRequest | isInvite | actionText | removeSubMembershipsCheckboxExpected | unassignIssuablesCheckboxExpected | message | onCallSchedules + ${'removing a group member'} | ${'GroupMember'} | ${false} | ${false} | ${'Remove member'} | ${true} | ${true} | ${'Are you sure you want to remove Jane Doe from the Gitlab Org / Gitlab Test project?'} | ${{}} + ${'removing a project member'} | ${'ProjectMember'} | ${false} | ${false} | ${'Remove member'} | ${false} | ${true} | ${'Are you sure you want to remove Jane Doe from the Gitlab Org / Gitlab Test project?'} | ${mockSchedules} + ${'denying an access request'} | ${'ProjectMember'} | ${true} | ${false} | ${'Deny access request'} | ${false} | ${false} | ${"Are you sure you want to deny Jane Doe's request to join the Gitlab Org / Gitlab Test project?"} | ${{}} + ${'revoking invite'} | ${'ProjectMember'} | ${false} | ${true} | ${'Revoke invite'} | ${false} | ${false} | ${'Are you sure you want to revoke the invitation for foo@bar.com to join the Gitlab Org / Gitlab Test project?'} | ${mockSchedules} + `( + 'when $state', + ({ + actionText, + memberType, + isAccessRequest, + isInvite, + message, + removeSubMembershipsCheckboxExpected, + unassignIssuablesCheckboxExpected, + onCallSchedules, + }) => { + beforeEach(() => { + createComponent({ + isAccessRequest, + isInvite, + message, + memberPath, + memberType, + onCallSchedules, + }); + }); + + const isPartOfOncallSchedules = Boolean(isAccessRequest && onCallSchedules.schedules?.length); + + it(`has the title ${actionText}`, () => { + expect(findGlModal().attributes('title')).toBe(actionText); + }); + + it('contains a form action', () => { + expect(findForm().attributes('action')).toBe(memberPath); + }); + + it('displays a message to the user', () => { + expect(wrapper.find('p').text()).toBe(message); + }); + + it(`shows ${ + removeSubMembershipsCheckboxExpected ? 'a' : 'no' + } checkbox to remove direct memberships of subgroups/projects`, () => { + expect(wrapper.find('[name=remove_sub_memberships]').exists()).toBe( + removeSubMembershipsCheckboxExpected, + ); + }); + + it(`shows ${ + unassignIssuablesCheckboxExpected ? 'a' : 'no' + } checkbox to allow removal from related issues and MRs`, () => { + expect(wrapper.find('[name=unassign_issuables]').exists()).toBe( + unassignIssuablesCheckboxExpected, + ); + }); + + it(`shows ${isPartOfOncallSchedules ? 'all' : 'no'} related on-call schedules`, () => { + expect(findOnCallSchedulesList().exists()).toBe(isPartOfOncallSchedules); + }); + + it('submits the form when the modal is submitted', () => { + const spy = jest.spyOn(findForm().element, 'submit'); + + findGlModal().vm.$emit('primary'); + + expect(spy).toHaveBeenCalled(); + + spy.mockRestore(); + }); + + it('calls Vuex action to hide the modal when `GlModal` emits `hide` event', () => { + findGlModal().vm.$emit('hide'); + + expect(actions.hideRemoveMemberModal).toHaveBeenCalled(); + }); + }, + ); +}); diff --git a/spec/frontend/members/components/table/members_table_spec.js b/spec/frontend/members/components/table/members_table_spec.js index 3a17d78bd17..6885da53b26 100644 --- a/spec/frontend/members/components/table/members_table_spec.js +++ b/spec/frontend/members/components/table/members_table_spec.js @@ -6,6 +6,7 @@ import { } from '@testing-library/dom'; import { mount, createLocalVue, createWrapper } from '@vue/test-utils'; import Vuex from 'vuex'; +import setWindowLocation from 'helpers/set_window_location_helper'; 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'; @@ -72,6 +73,7 @@ describe('MembersTable', () => { 'member-action-buttons', 'role-dropdown', 'remove-group-link-modal', + 'remove-member-modal', 'expiration-datepicker', ], }); @@ -242,12 +244,8 @@ describe('MembersTable', () => { }); describe('when required pagination data is provided', () => { - beforeEach(() => { - delete window.location; - }); - it('renders `gl-pagination` component with correct props', () => { - window.location = new URL(url); + setWindowLocation(url); createComponent(); @@ -267,7 +265,7 @@ describe('MembersTable', () => { }); it('uses `pagination.paramName` to generate the pagination links', () => { - window.location = new URL(url); + setWindowLocation(url); createComponent({ pagination: { @@ -282,7 +280,7 @@ describe('MembersTable', () => { }); it('removes any url params defined as `null` in the `params` attribute', () => { - window.location = new URL(`${url}&search_groups=foo`); + setWindowLocation(`${url}&search_groups=foo`); createComponent({ pagination: { diff --git a/spec/frontend/members/mock_data.js b/spec/frontend/members/mock_data.js index 4275db5fa9f..eb9f905fea2 100644 --- a/spec/frontend/members/mock_data.js +++ b/spec/frontend/members/mock_data.js @@ -57,6 +57,15 @@ export const group = { validRoles: { Guest: 10, Reporter: 20, Developer: 30, Maintainer: 40, Owner: 50 }, }; +export const modalData = { + isAccessRequest: true, + isInvite: true, + memberPath: '/groups/foo-bar/-/group_members/1', + memberType: 'GroupMember', + message: 'Are you sure you want to remove John Smith?', + oncallSchedules: { name: 'user', schedules: [] }, +}; + const { user, ...memberNoUser } = member; export const invite = { ...memberNoUser, diff --git a/spec/frontend/members/store/actions_spec.js b/spec/frontend/members/store/actions_spec.js index d913c5c56df..d37e6871387 100644 --- a/spec/frontend/members/store/actions_spec.js +++ b/spec/frontend/members/store/actions_spec.js @@ -3,12 +3,14 @@ import MockAdapter from 'axios-mock-adapter'; import { noop } from 'lodash'; import { useFakeDate } from 'helpers/fake_date'; import testAction from 'helpers/vuex_action_helper'; -import { members, group } from 'jest/members/mock_data'; +import { members, group, modalData } from 'jest/members/mock_data'; import httpStatusCodes from '~/lib/utils/http_status'; import { updateMemberRole, showRemoveGroupLinkModal, hideRemoveGroupLinkModal, + showRemoveMemberModal, + hideRemoveMemberModal, updateMemberExpiration, } from '~/members/store/actions'; import * as types from '~/members/store/mutation_types'; @@ -153,4 +155,32 @@ describe('Vuex members actions', () => { }); }); }); + + describe('Remove member modal', () => { + const state = { + removeMemberModalVisible: false, + removeMemberModalData: {}, + }; + + describe('showRemoveMemberModal', () => { + it(`commits ${types.SHOW_REMOVE_MEMBER_MODAL} mutation`, () => { + testAction(showRemoveMemberModal, modalData, state, [ + { + type: types.SHOW_REMOVE_MEMBER_MODAL, + payload: modalData, + }, + ]); + }); + }); + + describe('hideRemoveMemberModal', () => { + it(`commits ${types.HIDE_REMOVE_MEMBER_MODAL} mutation`, () => { + testAction(hideRemoveMemberModal, undefined, state, [ + { + type: types.HIDE_REMOVE_MEMBER_MODAL, + }, + ]); + }); + }); + }); }); diff --git a/spec/frontend/members/store/mutations_spec.js b/spec/frontend/members/store/mutations_spec.js index 78bbad394a0..8160cc373d8 100644 --- a/spec/frontend/members/store/mutations_spec.js +++ b/spec/frontend/members/store/mutations_spec.js @@ -1,4 +1,4 @@ -import { members, group } from 'jest/members/mock_data'; +import { members, group, modalData } from 'jest/members/mock_data'; import * as types from '~/members/store/mutation_types'; import mutations from '~/members/store/mutations'; @@ -152,4 +152,32 @@ describe('Vuex members mutations', () => { expect(state.removeGroupLinkModalVisible).toBe(false); }); }); + + describe(types.SHOW_REMOVE_MEMBER_MODAL, () => { + it('sets `removeMemberModalVisible` and `removeMemberModalData`', () => { + const state = { + removeMemberModalVisible: false, + removeMemberModalData: {}, + }; + + mutations[types.SHOW_REMOVE_MEMBER_MODAL](state, modalData); + + expect(state).toEqual({ + removeMemberModalVisible: true, + removeMemberModalData: modalData, + }); + }); + }); + + describe(types.HIDE_REMOVE_MEMBER_MODAL, () => { + it('sets `removeMemberModalVisible` to `false`', () => { + const state = { + removeMemberModalVisible: true, + }; + + mutations[types.HIDE_REMOVE_MEMBER_MODAL](state); + + expect(state.removeMemberModalVisible).toBe(false); + }); + }); }); diff --git a/spec/frontend/members/utils_spec.js b/spec/frontend/members/utils_spec.js index 9740e1c2edb..a157cfa1c1d 100644 --- a/spec/frontend/members/utils_spec.js +++ b/spec/frontend/members/utils_spec.js @@ -1,3 +1,4 @@ +import setWindowLocation from 'helpers/set_window_location_helper'; import { DEFAULT_SORT, MEMBER_TYPES } from '~/members/constants'; import { generateBadges, @@ -150,21 +151,18 @@ describe('Members Utils', () => { describe('parseSortParam', () => { beforeEach(() => { - delete window.location; - window.location = new URL(URL_HOST); + setWindowLocation(URL_HOST); }); describe('when `sort` param is not present', () => { it('returns default sort options', () => { - window.location.search = ''; - expect(parseSortParam(['account'])).toEqual(DEFAULT_SORT); }); }); describe('when field passed in `sortableFields` argument does not have `sort` key defined', () => { it('returns default sort options', () => { - window.location.search = '?sort=source_asc'; + setWindowLocation('?sort=source_asc'); expect(parseSortParam(['source'])).toEqual(DEFAULT_SORT); }); @@ -182,7 +180,7 @@ describe('Members Utils', () => { ${'oldest_sign_in'} | ${{ sortByKey: 'lastSignIn', sortDesc: true }} `('when `sort` query string param is `$sortParam`', ({ sortParam, expected }) => { it(`returns ${JSON.stringify(expected)}`, async () => { - window.location.search = `?sort=${sortParam}`; + setWindowLocation(`?sort=${sortParam}`); expect(parseSortParam(['account', 'granted', 'expires', 'maxRole', 'lastSignIn'])).toEqual( expected, @@ -193,8 +191,7 @@ describe('Members Utils', () => { describe('buildSortHref', () => { beforeEach(() => { - delete window.location; - window.location = new URL(URL_HOST); + setWindowLocation(URL_HOST); }); describe('when field passed in `sortBy` argument does not have `sort` key defined', () => { @@ -225,7 +222,7 @@ describe('Members Utils', () => { describe('when filter params are set', () => { it('merges the `sort` param with the filter params', () => { - window.location.search = '?two_factor=enabled&with_inherited_permissions=exclude'; + setWindowLocation('?two_factor=enabled&with_inherited_permissions=exclude'); expect( buildSortHref({ @@ -240,7 +237,7 @@ describe('Members Utils', () => { describe('when search param is set', () => { it('merges the `sort` param with the search param', () => { - window.location.search = '?search=foobar'; + setWindowLocation('?search=foobar'); expect( buildSortHref({ |