From 859a6fb938bb9ee2a317c46dfa4fcc1af49608f0 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Thu, 18 Feb 2021 10:34:06 +0000 Subject: Add latest changes from gitlab-org/gitlab@13-9-stable-ee --- .../access_request_action_buttons_spec.js | 2 +- .../approve_access_request_button_spec.js | 2 +- .../components/action_buttons/leave_button_spec.js | 2 +- .../remove_group_link_button_spec.js | 2 +- .../action_buttons/resend_invite_button_spec.js | 2 +- .../action_buttons/user_action_buttons_spec.js | 4 +- spec/frontend/members/components/app_spec.js | 95 +++++++++++++++++ .../components/avatars/group_avatar_spec.js | 6 +- .../components/avatars/invite_avatar_spec.js | 4 +- .../members/components/avatars/user_avatar_spec.js | 6 +- .../members_filtered_search_bar_spec.js | 2 +- .../components/filter_sort/sort_dropdown_spec.js | 4 +- .../members/components/modals/leave_modal_spec.js | 4 +- .../modals/remove_group_link_modal_spec.js | 4 +- .../members/components/table/created_at_spec.js | 2 +- .../components/table/expiration_datepicker_spec.js | 4 +- .../members/components/table/expires_at_spec.js | 2 +- .../components/table/member_action_buttons_spec.js | 10 +- .../members/components/table/member_avatar_spec.js | 8 +- .../members/components/table/member_source_spec.js | 2 +- .../components/table/members_table_cell_spec.js | 24 ++--- .../members/components/table/members_table_spec.js | 25 ++--- .../members/components/table/role_dropdown_spec.js | 11 +- spec/frontend/members/index_spec.js | 113 +++++++++++++++++++++ spec/frontend/members/mock_data.js | 6 ++ spec/frontend/members/store/actions_spec.js | 20 ++-- spec/frontend/members/store/mutations_spec.js | 66 +++++++++--- spec/frontend/members/utils_spec.js | 100 +++++++++++------- 28 files changed, 409 insertions(+), 123 deletions(-) create mode 100644 spec/frontend/members/components/app_spec.js create mode 100644 spec/frontend/members/index_spec.js (limited to 'spec/frontend/members') diff --git a/spec/frontend/members/components/action_buttons/access_request_action_buttons_spec.js b/spec/frontend/members/components/action_buttons/access_request_action_buttons_spec.js index 30166e2d5ae..f86237dc160 100644 --- a/spec/frontend/members/components/action_buttons/access_request_action_buttons_spec.js +++ b/spec/frontend/members/components/action_buttons/access_request_action_buttons_spec.js @@ -1,7 +1,7 @@ import { shallowMount } from '@vue/test-utils'; import AccessRequestActionButtons from '~/members/components/action_buttons/access_request_action_buttons.vue'; -import RemoveMemberButton from '~/members/components/action_buttons/remove_member_button.vue'; import ApproveAccessRequestButton from '~/members/components/action_buttons/approve_access_request_button.vue'; +import RemoveMemberButton from '~/members/components/action_buttons/remove_member_button.vue'; import { accessRequest as member } from '../../mock_data'; describe('AccessRequestActionButtons', () => { diff --git a/spec/frontend/members/components/action_buttons/approve_access_request_button_spec.js b/spec/frontend/members/components/action_buttons/approve_access_request_button_spec.js index 7ce2c633bb3..f77d41a642e 100644 --- a/spec/frontend/members/components/action_buttons/approve_access_request_button_spec.js +++ b/spec/frontend/members/components/action_buttons/approve_access_request_button_spec.js @@ -1,6 +1,6 @@ +import { GlButton, GlForm } from '@gitlab/ui'; import { shallowMount, createLocalVue } from '@vue/test-utils'; import Vuex from 'vuex'; -import { GlButton, GlForm } from '@gitlab/ui'; import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; import ApproveAccessRequestButton from '~/members/components/action_buttons/approve_access_request_button.vue'; diff --git a/spec/frontend/members/components/action_buttons/leave_button_spec.js b/spec/frontend/members/components/action_buttons/leave_button_spec.js index 2afe112c74b..4859d033464 100644 --- a/spec/frontend/members/components/action_buttons/leave_button_spec.js +++ b/spec/frontend/members/components/action_buttons/leave_button_spec.js @@ -1,5 +1,5 @@ -import { shallowMount } from '@vue/test-utils'; import { GlButton } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; import LeaveButton from '~/members/components/action_buttons/leave_button.vue'; import LeaveModal from '~/members/components/modals/leave_modal.vue'; diff --git a/spec/frontend/members/components/action_buttons/remove_group_link_button_spec.js b/spec/frontend/members/components/action_buttons/remove_group_link_button_spec.js index 45283788676..f6e342898cb 100644 --- a/spec/frontend/members/components/action_buttons/remove_group_link_button_spec.js +++ b/spec/frontend/members/components/action_buttons/remove_group_link_button_spec.js @@ -1,6 +1,6 @@ +import { GlButton } from '@gitlab/ui'; import { mount, createLocalVue } from '@vue/test-utils'; import Vuex from 'vuex'; -import { GlButton } from '@gitlab/ui'; import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; import RemoveGroupLinkButton from '~/members/components/action_buttons/remove_group_link_button.vue'; import { group } from '../../mock_data'; diff --git a/spec/frontend/members/components/action_buttons/resend_invite_button_spec.js b/spec/frontend/members/components/action_buttons/resend_invite_button_spec.js index 05ea0dc2886..49b6979f954 100644 --- a/spec/frontend/members/components/action_buttons/resend_invite_button_spec.js +++ b/spec/frontend/members/components/action_buttons/resend_invite_button_spec.js @@ -1,6 +1,6 @@ +import { GlButton } from '@gitlab/ui'; import { shallowMount, createLocalVue } from '@vue/test-utils'; import Vuex from 'vuex'; -import { GlButton } from '@gitlab/ui'; import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; import ResendInviteButton from '~/members/components/action_buttons/resend_invite_button.vue'; diff --git a/spec/frontend/members/components/action_buttons/user_action_buttons_spec.js b/spec/frontend/members/components/action_buttons/user_action_buttons_spec.js index f28e5040006..1d7ea5b3109 100644 --- a/spec/frontend/members/components/action_buttons/user_action_buttons_spec.js +++ b/spec/frontend/members/components/action_buttons/user_action_buttons_spec.js @@ -1,7 +1,7 @@ import { shallowMount } from '@vue/test-utils'; -import UserActionButtons from '~/members/components/action_buttons/user_action_buttons.vue'; -import RemoveMemberButton from '~/members/components/action_buttons/remove_member_button.vue'; import LeaveButton from '~/members/components/action_buttons/leave_button.vue'; +import RemoveMemberButton from '~/members/components/action_buttons/remove_member_button.vue'; +import UserActionButtons from '~/members/components/action_buttons/user_action_buttons.vue'; import { member, orphanedMember } from '../../mock_data'; describe('UserActionButtons', () => { diff --git a/spec/frontend/members/components/app_spec.js b/spec/frontend/members/components/app_spec.js new file mode 100644 index 00000000000..a1329c3ee9f --- /dev/null +++ b/spec/frontend/members/components/app_spec.js @@ -0,0 +1,95 @@ +import { GlAlert } from '@gitlab/ui'; +import { shallowMount, createLocalVue } from '@vue/test-utils'; +import { nextTick } from 'vue'; +import Vuex from 'vuex'; +import * as commonUtils from '~/lib/utils/common_utils'; +import MembersApp from '~/members/components/app.vue'; +import FilterSortContainer from '~/members/components/filter_sort/filter_sort_container.vue'; +import { RECEIVE_MEMBER_ROLE_ERROR, HIDE_ERROR } from '~/members/store/mutation_types'; +import mutations from '~/members/store/mutations'; + +describe('MembersApp', () => { + const localVue = createLocalVue(); + localVue.use(Vuex); + + let wrapper; + let store; + + const createComponent = (state = {}, options = {}) => { + store = new Vuex.Store({ + state: { + showError: true, + errorMessage: 'Something went wrong, please try again.', + ...state, + }, + mutations, + }); + + wrapper = shallowMount(MembersApp, { + localVue, + store, + ...options, + }); + }; + + const findAlert = () => wrapper.find(GlAlert); + const findFilterSortContainer = () => wrapper.find(FilterSortContainer); + + beforeEach(() => { + commonUtils.scrollToElement = jest.fn(); + }); + + afterEach(() => { + wrapper.destroy(); + store = null; + }); + + describe('when `showError` is changed to `true`', () => { + it('renders and scrolls to error alert', async () => { + createComponent({ showError: false, errorMessage: '' }); + + store.commit(RECEIVE_MEMBER_ROLE_ERROR, { error: new Error('Network Error') }); + + await nextTick(); + + const alert = findAlert(); + + expect(alert.exists()).toBe(true); + expect(alert.text()).toBe( + "An error occurred while updating the member's role, please try again.", + ); + expect(commonUtils.scrollToElement).toHaveBeenCalledWith(alert.element); + }); + }); + + describe('when `showError` is changed to `false`', () => { + it('does not render and scroll to error alert', async () => { + createComponent(); + + store.commit(HIDE_ERROR); + + await nextTick(); + + expect(findAlert().exists()).toBe(false); + expect(commonUtils.scrollToElement).not.toHaveBeenCalled(); + }); + }); + + describe('when alert is dismissed', () => { + it('hides alert', async () => { + createComponent(); + + findAlert().vm.$emit('dismiss'); + + await nextTick(); + + expect(findAlert().exists()).toBe(false); + }); + }); + + it('renders `FilterSortContainer`', () => { + createComponent(); + + expect(findFilterSortContainer().exists()).toBe(true); + }); +}); diff --git a/spec/frontend/members/components/avatars/group_avatar_spec.js b/spec/frontend/members/components/avatars/group_avatar_spec.js index 658bb9462b0..9c1574a84ee 100644 --- a/spec/frontend/members/components/avatars/group_avatar_spec.js +++ b/spec/frontend/members/components/avatars/group_avatar_spec.js @@ -1,8 +1,8 @@ -import { mount, createWrapper } from '@vue/test-utils'; -import { getByText as getByTextHelper } from '@testing-library/dom'; import { GlAvatarLink } from '@gitlab/ui'; -import { group as member } from '../../mock_data'; +import { getByText as getByTextHelper } from '@testing-library/dom'; +import { mount, createWrapper } from '@vue/test-utils'; import GroupAvatar from '~/members/components/avatars/group_avatar.vue'; +import { group as member } from '../../mock_data'; describe('MemberList', () => { let wrapper; diff --git a/spec/frontend/members/components/avatars/invite_avatar_spec.js b/spec/frontend/members/components/avatars/invite_avatar_spec.js index 13ee727528b..b197a46c0d1 100644 --- a/spec/frontend/members/components/avatars/invite_avatar_spec.js +++ b/spec/frontend/members/components/avatars/invite_avatar_spec.js @@ -1,7 +1,7 @@ -import { mount, createWrapper } from '@vue/test-utils'; import { getByText as getByTextHelper } from '@testing-library/dom'; -import { invite as member } from '../../mock_data'; +import { mount, createWrapper } from '@vue/test-utils'; import InviteAvatar from '~/members/components/avatars/invite_avatar.vue'; +import { invite as member } from '../../mock_data'; describe('MemberList', () => { let wrapper; diff --git a/spec/frontend/members/components/avatars/user_avatar_spec.js b/spec/frontend/members/components/avatars/user_avatar_spec.js index 411ec1a54de..303c82582a3 100644 --- a/spec/frontend/members/components/avatars/user_avatar_spec.js +++ b/spec/frontend/members/components/avatars/user_avatar_spec.js @@ -1,8 +1,8 @@ -import { mount, createWrapper } from '@vue/test-utils'; -import { within } from '@testing-library/dom'; import { GlAvatarLink, GlBadge } from '@gitlab/ui'; -import { member as memberMock, orphanedMember } from '../../mock_data'; +import { within } from '@testing-library/dom'; +import { mount, createWrapper } from '@vue/test-utils'; import UserAvatar from '~/members/components/avatars/user_avatar.vue'; +import { member as memberMock, orphanedMember } from '../../mock_data'; describe('UserAvatar', () => { let wrapper; 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 2bed1e803ca..14b437a8c4e 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,6 +1,6 @@ +import { GlFilteredSearchToken } from '@gitlab/ui'; import { shallowMount, createLocalVue } from '@vue/test-utils'; import Vuex from 'vuex'; -import { GlFilteredSearchToken } from '@gitlab/ui'; import MembersFilteredSearchBar from '~/members/components/filter_sort/members_filtered_search_bar.vue'; import FilteredSearchBar from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue'; 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 d98c9116512..357fad741e9 100644 --- a/spec/frontend/members/components/filter_sort/sort_dropdown_spec.js +++ b/spec/frontend/members/components/filter_sort/sort_dropdown_spec.js @@ -1,8 +1,8 @@ +import { GlSorting, GlSortingItem } from '@gitlab/ui'; import { mount, createLocalVue } from '@vue/test-utils'; import Vuex from 'vuex'; -import { GlSorting, GlSortingItem } from '@gitlab/ui'; -import SortDropdown from '~/members/components/filter_sort/sort_dropdown.vue'; import * as urlUtilities from '~/lib/utils/url_utility'; +import SortDropdown from '~/members/components/filter_sort/sort_dropdown.vue'; const localVue = createLocalVue(); localVue.use(Vuex); diff --git a/spec/frontend/members/components/modals/leave_modal_spec.js b/spec/frontend/members/components/modals/leave_modal_spec.js index dca47d1f6af..2d52911572f 100644 --- a/spec/frontend/members/components/modals/leave_modal_spec.js +++ b/spec/frontend/members/components/modals/leave_modal_spec.js @@ -1,7 +1,7 @@ -import { mount, createLocalVue, createWrapper } from '@vue/test-utils'; import { GlModal, GlForm } from '@gitlab/ui'; -import { nextTick } from 'vue'; import { within } from '@testing-library/dom'; +import { mount, createLocalVue, createWrapper } from '@vue/test-utils'; +import { nextTick } from 'vue'; import Vuex from 'vuex'; import LeaveModal from '~/members/components/modals/leave_modal.vue'; import { LEAVE_MODAL_ID } from '~/members/constants'; diff --git a/spec/frontend/members/components/modals/remove_group_link_modal_spec.js b/spec/frontend/members/components/modals/remove_group_link_modal_spec.js index 234857419b6..62df912c1a2 100644 --- a/spec/frontend/members/components/modals/remove_group_link_modal_spec.js +++ b/spec/frontend/members/components/modals/remove_group_link_modal_spec.js @@ -1,7 +1,7 @@ -import { mount, createLocalVue, createWrapper } from '@vue/test-utils'; import { GlModal, GlForm } from '@gitlab/ui'; -import { nextTick } from 'vue'; import { within } from '@testing-library/dom'; +import { mount, createLocalVue, createWrapper } from '@vue/test-utils'; +import { nextTick } from 'vue'; import Vuex from 'vuex'; import RemoveGroupLinkModal from '~/members/components/modals/remove_group_link_modal.vue'; import { REMOVE_GROUP_LINK_MODAL_ID } from '~/members/constants'; diff --git a/spec/frontend/members/components/table/created_at_spec.js b/spec/frontend/members/components/table/created_at_spec.js index dc1f62722ab..74b71e22893 100644 --- a/spec/frontend/members/components/table/created_at_spec.js +++ b/spec/frontend/members/components/table/created_at_spec.js @@ -1,5 +1,5 @@ -import { mount, createWrapper } from '@vue/test-utils'; import { within } from '@testing-library/dom'; +import { mount, createWrapper } from '@vue/test-utils'; import { useFakeDate } from 'helpers/fake_date'; import CreatedAt from '~/members/components/table/created_at.vue'; import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; diff --git a/spec/frontend/members/components/table/expiration_datepicker_spec.js b/spec/frontend/members/components/table/expiration_datepicker_spec.js index 0caaafb8d7b..d26172b4ed1 100644 --- a/spec/frontend/members/components/table/expiration_datepicker_spec.js +++ b/spec/frontend/members/components/table/expiration_datepicker_spec.js @@ -1,7 +1,7 @@ +import { GlDatepicker } from '@gitlab/ui'; import { mount, createLocalVue } from '@vue/test-utils'; -import Vuex from 'vuex'; import { nextTick } from 'vue'; -import { GlDatepicker } from '@gitlab/ui'; +import Vuex from 'vuex'; import { useFakeDate } from 'helpers/fake_date'; import waitForPromises from 'helpers/wait_for_promises'; import ExpirationDatepicker from '~/members/components/table/expiration_datepicker.vue'; diff --git a/spec/frontend/members/components/table/expires_at_spec.js b/spec/frontend/members/components/table/expires_at_spec.js index 321008727cd..02fe3c6d684 100644 --- a/spec/frontend/members/components/table/expires_at_spec.js +++ b/spec/frontend/members/components/table/expires_at_spec.js @@ -1,5 +1,5 @@ -import { mount, createWrapper } from '@vue/test-utils'; import { within } from '@testing-library/dom'; +import { mount, createWrapper } from '@vue/test-utils'; import { useFakeDate } from 'helpers/fake_date'; import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; import ExpiresAt from '~/members/components/table/expires_at.vue'; diff --git a/spec/frontend/members/components/table/member_action_buttons_spec.js b/spec/frontend/members/components/table/member_action_buttons_spec.js index b7a6df3d054..546d09732d6 100644 --- a/spec/frontend/members/components/table/member_action_buttons_spec.js +++ b/spec/frontend/members/components/table/member_action_buttons_spec.js @@ -1,11 +1,11 @@ import { shallowMount } from '@vue/test-utils'; -import { MEMBER_TYPES } from '~/members/constants'; -import { member as memberMock, group, invite, accessRequest } from '../../mock_data'; -import MemberActionButtons from '~/members/components/table/member_action_buttons.vue'; -import UserActionButtons from '~/members/components/action_buttons/user_action_buttons.vue'; +import AccessRequestActionButtons from '~/members/components/action_buttons/access_request_action_buttons.vue'; import GroupActionButtons from '~/members/components/action_buttons/group_action_buttons.vue'; import InviteActionButtons from '~/members/components/action_buttons/invite_action_buttons.vue'; -import AccessRequestActionButtons from '~/members/components/action_buttons/access_request_action_buttons.vue'; +import UserActionButtons from '~/members/components/action_buttons/user_action_buttons.vue'; +import MemberActionButtons from '~/members/components/table/member_action_buttons.vue'; +import { MEMBER_TYPES } from '~/members/constants'; +import { member as memberMock, group, invite, accessRequest } from '../../mock_data'; describe('MemberActionButtons', () => { let wrapper; diff --git a/spec/frontend/members/components/table/member_avatar_spec.js b/spec/frontend/members/components/table/member_avatar_spec.js index 4341dfbbaf9..3cce64effbc 100644 --- a/spec/frontend/members/components/table/member_avatar_spec.js +++ b/spec/frontend/members/components/table/member_avatar_spec.js @@ -1,10 +1,10 @@ import { shallowMount } from '@vue/test-utils'; -import { MEMBER_TYPES } from '~/members/constants'; -import { member as memberMock, group, invite, accessRequest } from '../../mock_data'; -import MemberAvatar from '~/members/components/table/member_avatar.vue'; -import UserAvatar from '~/members/components/avatars/user_avatar.vue'; import GroupAvatar from '~/members/components/avatars/group_avatar.vue'; import InviteAvatar from '~/members/components/avatars/invite_avatar.vue'; +import UserAvatar from '~/members/components/avatars/user_avatar.vue'; +import MemberAvatar from '~/members/components/table/member_avatar.vue'; +import { MEMBER_TYPES } from '~/members/constants'; +import { member as memberMock, group, invite, accessRequest } from '../../mock_data'; describe('MemberList', () => { let wrapper; diff --git a/spec/frontend/members/components/table/member_source_spec.js b/spec/frontend/members/components/table/member_source_spec.js index 95547090aed..2cd888207b1 100644 --- a/spec/frontend/members/components/table/member_source_spec.js +++ b/spec/frontend/members/components/table/member_source_spec.js @@ -1,5 +1,5 @@ -import { mount, createWrapper } from '@vue/test-utils'; import { getByText as getByTextHelper } from '@testing-library/dom'; +import { mount, createWrapper } from '@vue/test-utils'; import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; import MemberSource from '~/members/components/table/member_source.vue'; diff --git a/spec/frontend/members/components/table/members_table_cell_spec.js b/spec/frontend/members/components/table/members_table_cell_spec.js index 117c9255c00..b7dcd2a9fae 100644 --- a/spec/frontend/members/components/table/members_table_cell_spec.js +++ b/spec/frontend/members/components/table/members_table_cell_spec.js @@ -1,8 +1,15 @@ import { mount, createLocalVue } from '@vue/test-utils'; import Vuex from 'vuex'; -import { MEMBER_TYPES } from '~/members/constants'; -import { member as memberMock, group, invite, accessRequest } from '../../mock_data'; import MembersTableCell from '~/members/components/table/members_table_cell.vue'; +import { MEMBER_TYPES } from '~/members/constants'; +import { + member as memberMock, + directMember, + inheritedMember, + group, + invite, + accessRequest, +} from '../../mock_data'; describe('MembersTableCell', () => { const WrappedComponent = { @@ -31,7 +38,7 @@ describe('MembersTableCell', () => { const localVue = createLocalVue(); localVue.use(Vuex); - localVue.component('wrapped-component', WrappedComponent); + localVue.component('WrappedComponent', WrappedComponent); const createStore = (state = {}) => { return new Vuex.Store({ @@ -75,19 +82,12 @@ describe('MembersTableCell', () => { const createComponentWithDirectMember = (member = {}) => { createComponent({ - member: { - ...memberMock, - source: { - ...memberMock.source, - id: 1, - }, - ...member, - }, + member: { ...directMember, ...member }, }); }; const createComponentWithInheritedMember = (member = {}) => { createComponent({ - member: { ...memberMock, ...member }, + member: { ...inheritedMember, ...member }, }); }; diff --git a/spec/frontend/members/components/table/members_table_spec.js b/spec/frontend/members/components/table/members_table_spec.js index dbaccde069c..cf5811e72e7 100644 --- a/spec/frontend/members/components/table/members_table_spec.js +++ b/spec/frontend/members/components/table/members_table_spec.js @@ -1,21 +1,21 @@ -import { mount, createLocalVue, createWrapper } from '@vue/test-utils'; -import Vuex from 'vuex'; +import { GlBadge, GlTable } from '@gitlab/ui'; import { getByText as getByTextHelper, getByTestId as getByTestIdHelper, within, } from '@testing-library/dom'; -import { GlBadge, GlTable } from '@gitlab/ui'; -import MembersTable from '~/members/components/table/members_table.vue'; -import MemberAvatar from '~/members/components/table/member_avatar.vue'; -import MemberSource from '~/members/components/table/member_source.vue'; -import ExpiresAt from '~/members/components/table/expires_at.vue'; +import { mount, createLocalVue, createWrapper } from '@vue/test-utils'; +import Vuex from 'vuex'; import CreatedAt from '~/members/components/table/created_at.vue'; -import RoleDropdown from '~/members/components/table/role_dropdown.vue'; import ExpirationDatepicker from '~/members/components/table/expiration_datepicker.vue'; +import ExpiresAt from '~/members/components/table/expires_at.vue'; import MemberActionButtons from '~/members/components/table/member_action_buttons.vue'; +import MemberAvatar from '~/members/components/table/member_avatar.vue'; +import MemberSource from '~/members/components/table/member_source.vue'; +import MembersTable from '~/members/components/table/members_table.vue'; +import RoleDropdown from '~/members/components/table/role_dropdown.vue'; import * as initUserPopovers from '~/user_popovers'; -import { member as memberMock, invite, accessRequest } from '../../mock_data'; +import { member as memberMock, directMember, invite, accessRequest } from '../../mock_data'; const localVue = createLocalVue(); localVue.use(Vuex); @@ -74,11 +74,6 @@ describe('MembersTable', () => { }); describe('fields', () => { - const directMember = { - ...memberMock, - source: { ...memberMock.source, id: 1 }, - }; - const memberCanUpdate = { ...directMember, canUpdate: true, @@ -154,7 +149,7 @@ describe('MembersTable', () => { expect(findTableCellByMemberId('Actions', members[0].id).classes()).toStrictEqual([ 'col-actions', 'gl-display-none!', - 'gl-display-lg-table-cell!', + 'gl-lg-display-table-cell!', ]); expect(findTableCellByMemberId('Actions', members[1].id).classes()).toStrictEqual([ 'col-actions', diff --git a/spec/frontend/members/components/table/role_dropdown_spec.js b/spec/frontend/members/components/table/role_dropdown_spec.js index 96a388614f3..aa280599061 100644 --- a/spec/frontend/members/components/table/role_dropdown_spec.js +++ b/spec/frontend/members/components/table/role_dropdown_spec.js @@ -1,10 +1,11 @@ -import { mount, createWrapper, createLocalVue } from '@vue/test-utils'; -import Vuex from 'vuex'; -import { nextTick } from 'vue'; -import { within } from '@testing-library/dom'; import { GlDropdown, GlDropdownItem } from '@gitlab/ui'; import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils'; +import { within } from '@testing-library/dom'; +import { mount, createWrapper, createLocalVue } from '@vue/test-utils'; +import { nextTick } from 'vue'; +import Vuex from 'vuex'; import waitForPromises from 'helpers/wait_for_promises'; +import { BV_DROPDOWN_SHOW } from '~/lib/utils/constants'; import RoleDropdown from '~/members/components/table/role_dropdown.vue'; import { member } from '../../mock_data'; @@ -67,7 +68,7 @@ describe('RoleDropdown', () => { createComponent(); findDropdownToggle().trigger('click'); - wrapper.vm.$root.$on('bv::dropdown::shown', () => { + wrapper.vm.$root.$on(BV_DROPDOWN_SHOW, () => { done(); }); }); diff --git a/spec/frontend/members/index_spec.js b/spec/frontend/members/index_spec.js new file mode 100644 index 00000000000..dd3b9ddd912 --- /dev/null +++ b/spec/frontend/members/index_spec.js @@ -0,0 +1,113 @@ +import { createWrapper } from '@vue/test-utils'; +import MembersApp from '~/members/components/app.vue'; +import { initMembersApp } from '~/members/index'; +import { membersJsonString, members } from './mock_data'; + +describe('initMembersApp', () => { + let el; + let vm; + let wrapper; + + const setup = () => { + vm = initMembersApp(el, { + tableFields: ['account'], + tableAttrs: { table: { 'data-qa-selector': 'members_list' } }, + tableSortableFields: ['account'], + requestFormatter: () => ({}), + filteredSearchBar: { show: false }, + }); + wrapper = createWrapper(vm); + }; + + 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'); + + window.gon = { current_user_id: 123 }; + }); + + afterEach(() => { + el = null; + + wrapper.destroy(); + wrapper = null; + }); + + it('renders `MembersApp`', () => { + setup(); + + expect(wrapper.find(MembersApp).exists()).toBe(true); + }); + + it('sets `currentUserId` in Vuex store', () => { + setup(); + + expect(vm.$store.state.currentUserId).toBe(123); + }); + + describe('when `gon.current_user_id` is not set (user is not logged in)', () => { + it('sets `currentUserId` as `null` in Vuex store', () => { + window.gon = {}; + setup(); + + expect(vm.$store.state.currentUserId).toBeNull(); + }); + }); + + it('parses and sets `data-source-id` as `sourceId` in Vuex store', () => { + setup(); + + expect(vm.$store.state.sourceId).toBe(234); + }); + + it('parses and sets `data-can-manage-members` as `canManageMembers` in Vuex store', () => { + setup(); + + expect(vm.$store.state.canManageMembers).toBe(true); + }); + + it('parses and sets `members` in Vuex store', () => { + setup(); + + expect(vm.$store.state.members).toEqual(members); + }); + + it('sets `tableFields` in Vuex store', () => { + setup(); + + expect(vm.$store.state.tableFields).toEqual(['account']); + }); + + it('sets `tableAttrs` in Vuex store', () => { + setup(); + + expect(vm.$store.state.tableAttrs).toEqual({ table: { 'data-qa-selector': 'members_list' } }); + }); + + it('sets `tableSortableFields` in Vuex store', () => { + setup(); + + expect(vm.$store.state.tableSortableFields).toEqual(['account']); + }); + + it('sets `requestFormatter` in Vuex store', () => { + setup(); + + expect(vm.$store.state.requestFormatter()).toEqual({}); + }); + + it('sets `filteredSearchBar` in Vuex store', () => { + setup(); + + expect(vm.$store.state.filteredSearchBar).toEqual({ show: false }); + }); + + it('sets `memberPath` in Vuex store', () => { + setup(); + + expect(vm.$store.state.memberPath).toBe('/groups/foo-bar/-/group_members/:id'); + }); +}); diff --git a/spec/frontend/members/mock_data.js b/spec/frontend/members/mock_data.js index e668f2a1998..fa324ce1cf9 100644 --- a/spec/frontend/members/mock_data.js +++ b/spec/frontend/members/mock_data.js @@ -4,6 +4,7 @@ export const member = { canRemove: false, canOverride: false, isOverridden: false, + isDirectMember: false, accessLevel: { integerValue: 50, stringValue: 'Owner' }, source: { id: 178, @@ -69,3 +70,8 @@ export const accessRequest = { }; export const members = [member]; + +export const membersJsonString = JSON.stringify(members); + +export const directMember = { ...member, isDirectMember: true }; +export const inheritedMember = { ...member, isDirectMember: false }; diff --git a/spec/frontend/members/store/actions_spec.js b/spec/frontend/members/store/actions_spec.js index 5424fee0750..d913c5c56df 100644 --- a/spec/frontend/members/store/actions_spec.js +++ b/spec/frontend/members/store/actions_spec.js @@ -1,17 +1,17 @@ -import { noop } from 'lodash'; import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; -import { members, group } from 'jest/members/mock_data'; -import testAction from 'helpers/vuex_action_helper'; +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 httpStatusCodes from '~/lib/utils/http_status'; -import * as types from '~/members/store/mutation_types'; import { updateMemberRole, showRemoveGroupLinkModal, hideRemoveGroupLinkModal, updateMemberExpiration, } from '~/members/store/actions'; +import * as types from '~/members/store/mutation_types'; describe('Vuex members actions', () => { describe('update member actions', () => { @@ -57,15 +57,17 @@ describe('Vuex members actions', () => { describe('unsuccessful request', () => { it(`commits ${types.RECEIVE_MEMBER_ROLE_ERROR} mutation and throws error`, async () => { - mock.onPut().networkError(); + const error = new Error('Network Error'); + mock.onPut().reply(() => Promise.reject(error)); await expect( testAction(updateMemberRole, payload, state, [ { type: types.RECEIVE_MEMBER_ROLE_ERROR, + payload: { error }, }, ]), - ).rejects.toThrowError(new Error('Network Error')); + ).rejects.toThrowError(error); }); }); }); @@ -108,15 +110,17 @@ describe('Vuex members actions', () => { describe('unsuccessful request', () => { it(`commits ${types.RECEIVE_MEMBER_EXPIRATION_ERROR} mutation and throws error`, async () => { - mock.onPut().networkError(); + const error = new Error('Network Error'); + mock.onPut().reply(() => Promise.reject(error)); await expect( testAction(updateMemberExpiration, { memberId, expiresAt }, state, [ { type: types.RECEIVE_MEMBER_EXPIRATION_ERROR, + payload: { error }, }, ]), - ).rejects.toThrowError(new Error('Network Error')); + ).rejects.toThrowError(error); }); }); }); diff --git a/spec/frontend/members/store/mutations_spec.js b/spec/frontend/members/store/mutations_spec.js index 488bfdf15fd..7ad7034eb6d 100644 --- a/spec/frontend/members/store/mutations_spec.js +++ b/spec/frontend/members/store/mutations_spec.js @@ -1,6 +1,6 @@ import { members, group } from 'jest/members/mock_data'; -import mutations from '~/members/store/mutations'; import * as types from '~/members/store/mutation_types'; +import mutations from '~/members/store/mutations'; describe('Vuex members mutations', () => { describe('update member mutations', () => { @@ -28,13 +28,33 @@ describe('Vuex members mutations', () => { }); describe(types.RECEIVE_MEMBER_ROLE_ERROR, () => { - it('shows error message', () => { - mutations[types.RECEIVE_MEMBER_ROLE_ERROR](state); + describe('when error does not have a message', () => { + it('shows default error message', () => { + mutations[types.RECEIVE_MEMBER_ROLE_ERROR](state, { + error: new Error('Network Error'), + }); + + expect(state.showError).toBe(true); + expect(state.errorMessage).toBe( + "An error occurred while updating the member's role, please try again.", + ); + }); + }); + + describe('when error has a message', () => { + it('shows error message', () => { + const error = new Error('Request failed with status code 422'); + const message = + 'User email "john.smith@gmail.com" does not match the allowed domain of example.com'; + + error.response = { + data: { message }, + }; + mutations[types.RECEIVE_MEMBER_ROLE_ERROR](state, { error }); - expect(state.showError).toBe(true); - expect(state.errorMessage).toBe( - "An error occurred while updating the member's role, please try again.", - ); + expect(state.showError).toBe(true); + expect(state.errorMessage).toBe(message); + }); }); }); @@ -52,13 +72,33 @@ describe('Vuex members mutations', () => { }); describe(types.RECEIVE_MEMBER_EXPIRATION_ERROR, () => { - it('shows error message', () => { - mutations[types.RECEIVE_MEMBER_EXPIRATION_ERROR](state); + describe('when error does not have a message', () => { + it('shows default error message', () => { + mutations[types.RECEIVE_MEMBER_EXPIRATION_ERROR](state, { + error: new Error('Network Error'), + }); + + expect(state.showError).toBe(true); + expect(state.errorMessage).toBe( + "An error occurred while updating the member's expiration date, please try again.", + ); + }); + }); - expect(state.showError).toBe(true); - expect(state.errorMessage).toBe( - "An error occurred while updating the member's expiration date, please try again.", - ); + describe('when error has a message', () => { + it('shows error message', () => { + const error = new Error('Request failed with status code 422'); + const message = + 'User email "john.smith@gmail.com" does not match the allowed domain of example.com'; + + error.response = { + data: { message }, + }; + mutations[types.RECEIVE_MEMBER_EXPIRATION_ERROR](state, { error }); + + expect(state.showError).toBe(true); + expect(state.errorMessage).toBe(message); + }); }); }); }); diff --git a/spec/frontend/members/utils_spec.js b/spec/frontend/members/utils_spec.js index 7cd4e735b55..f447a4c4ee9 100644 --- a/spec/frontend/members/utils_spec.js +++ b/spec/frontend/members/utils_spec.js @@ -1,3 +1,4 @@ +import { DEFAULT_SORT } from '~/members/constants'; import { generateBadges, isGroup, @@ -9,12 +10,19 @@ import { canOverride, parseSortParam, buildSortHref, + parseDataAttributes, + groupLinkRequestFormatter, } from '~/members/utils'; -import { DEFAULT_SORT } from '~/members/constants'; -import { member as memberMock, group, invite } from './mock_data'; +import { + member as memberMock, + directMember, + inheritedMember, + group, + invite, + membersJsonString, + members, +} from './mock_data'; -const DIRECT_MEMBER_ID = 178; -const INHERITED_MEMBER_ID = 179; const IS_CURRENT_USER_ID = 123; const IS_NOT_CURRENT_USER_ID = 124; const URL_HOST = 'https://localhost/'; @@ -57,11 +65,11 @@ describe('Members Utils', () => { describe('isDirectMember', () => { test.each` - sourceId | expected - ${DIRECT_MEMBER_ID} | ${true} - ${INHERITED_MEMBER_ID} | ${false} - `('returns $expected', ({ sourceId, expected }) => { - expect(isDirectMember(memberMock, sourceId)).toBe(expected); + member | expected + ${directMember} | ${true} + ${inheritedMember} | ${false} + `('returns $expected', ({ member, expected }) => { + expect(isDirectMember(member)).toBe(expected); }); }); @@ -76,18 +84,13 @@ describe('Members Utils', () => { }); describe('canRemove', () => { - const memberCanRemove = { - ...memberMock, - canRemove: true, - }; - test.each` - member | sourceId | expected - ${memberCanRemove} | ${DIRECT_MEMBER_ID} | ${true} - ${memberCanRemove} | ${INHERITED_MEMBER_ID} | ${false} - ${memberMock} | ${INHERITED_MEMBER_ID} | ${false} - `('returns $expected', ({ member, sourceId, expected }) => { - expect(canRemove(member, sourceId)).toBe(expected); + member | expected + ${{ ...directMember, canRemove: true }} | ${true} + ${{ ...inheritedMember, canRemove: true }} | ${false} + ${{ ...memberMock, canRemove: false }} | ${false} + `('returns $expected', ({ member, expected }) => { + expect(canRemove(member)).toBe(expected); }); }); @@ -96,25 +99,20 @@ describe('Members Utils', () => { member | expected ${invite} | ${true} ${{ ...invite, invite: { ...invite.invite, canResend: false } }} | ${false} - `('returns $expected', ({ member, sourceId, expected }) => { - expect(canResend(member, sourceId)).toBe(expected); + `('returns $expected', ({ member, expected }) => { + expect(canResend(member)).toBe(expected); }); }); describe('canUpdate', () => { - const memberCanUpdate = { - ...memberMock, - canUpdate: true, - }; - test.each` - member | currentUserId | sourceId | expected - ${memberCanUpdate} | ${IS_NOT_CURRENT_USER_ID} | ${DIRECT_MEMBER_ID} | ${true} - ${memberCanUpdate} | ${IS_CURRENT_USER_ID} | ${DIRECT_MEMBER_ID} | ${false} - ${memberCanUpdate} | ${IS_CURRENT_USER_ID} | ${INHERITED_MEMBER_ID} | ${false} - ${memberMock} | ${IS_NOT_CURRENT_USER_ID} | ${DIRECT_MEMBER_ID} | ${false} - `('returns $expected', ({ member, currentUserId, sourceId, expected }) => { - expect(canUpdate(member, currentUserId, sourceId)).toBe(expected); + member | currentUserId | expected + ${{ ...directMember, canUpdate: true }} | ${IS_NOT_CURRENT_USER_ID} | ${true} + ${{ ...directMember, canUpdate: true }} | ${IS_CURRENT_USER_ID} | ${false} + ${{ ...inheritedMember, canUpdate: true }} | ${IS_CURRENT_USER_ID} | ${false} + ${{ ...directMember, canUpdate: false }} | ${IS_NOT_CURRENT_USER_ID} | ${false} + `('returns $expected', ({ member, currentUserId, expected }) => { + expect(canUpdate(member, currentUserId)).toBe(expected); }); }); @@ -229,4 +227,38 @@ describe('Members Utils', () => { }); }); }); + + describe('parseDataAttributes', () => { + let el; + + beforeEach(() => { + el = document.createElement('div'); + el.setAttribute('data-members', membersJsonString); + el.setAttribute('data-source-id', '234'); + el.setAttribute('data-can-manage-members', 'true'); + }); + + afterEach(() => { + el = null; + }); + + it('correctly parses the data attributes', () => { + expect(parseDataAttributes(el)).toEqual({ + members, + sourceId: 234, + canManageMembers: true, + }); + }); + }); + + describe('groupLinkRequestFormatter', () => { + it('returns expected format', () => { + expect( + groupLinkRequestFormatter({ + accessLevel: 50, + expires_at: '2020-10-16', + }), + ).toEqual({ group_link: { group_access: 50, expires_at: '2020-10-16' } }); + }); + }); }); -- cgit v1.2.3