diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-08-19 09:08:42 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-08-19 09:08:42 +0000 |
commit | b76ae638462ab0f673e5915986070518dd3f9ad3 (patch) | |
tree | bdab0533383b52873be0ec0eb4d3c66598ff8b91 /spec/frontend/invite_members | |
parent | 434373eabe7b4be9593d18a585fb763f1e5f1a6f (diff) | |
download | gitlab-ce-b76ae638462ab0f673e5915986070518dd3f9ad3.tar.gz |
Add latest changes from gitlab-org/gitlab@14-2-stable-eev14.2.0-rc42
Diffstat (limited to 'spec/frontend/invite_members')
-rw-r--r-- | spec/frontend/invite_members/components/invite_members_modal_spec.js | 198 | ||||
-rw-r--r-- | spec/frontend/invite_members/components/members_token_select_spec.js | 54 |
2 files changed, 216 insertions, 36 deletions
diff --git a/spec/frontend/invite_members/components/invite_members_modal_spec.js b/spec/frontend/invite_members/components/invite_members_modal_spec.js index b828b5d8a04..95b1c55b82d 100644 --- a/spec/frontend/invite_members/components/invite_members_modal_spec.js +++ b/spec/frontend/invite_members/components/invite_members_modal_spec.js @@ -6,6 +6,7 @@ import { GlSprintf, GlLink, GlModal, + GlFormCheckboxGroup, } from '@gitlab/ui'; import MockAdapter from 'axios-mock-adapter'; import { stubComponent } from 'helpers/stub_component'; @@ -15,7 +16,8 @@ import Api from '~/api'; import ExperimentTracking from '~/experimentation/experiment_tracking'; import InviteMembersModal from '~/invite_members/components/invite_members_modal.vue'; import MembersTokenSelect from '~/invite_members/components/members_token_select.vue'; -import { INVITE_MEMBERS_IN_COMMENT } from '~/invite_members/constants'; +import { INVITE_MEMBERS_IN_COMMENT, MEMBER_AREAS_OF_FOCUS } from '~/invite_members/constants'; +import eventHub from '~/invite_members/event_hub'; import axios from '~/lib/utils/axios_utils'; import httpStatus from '~/lib/utils/http_status'; import { apiPaths, membersApiResponse, invitationsApiResponse } from '../mock_data/api_responses'; @@ -32,7 +34,12 @@ const inviteeType = 'members'; const accessLevels = { Guest: 10, Reporter: 20, Developer: 30, Maintainer: 40, Owner: 50 }; const defaultAccessLevel = 10; const inviteSource = 'unknown'; +const noSelectionAreasOfFocus = ['no_selection']; const helpLink = 'https://example.com'; +const areasOfFocusOptions = [ + { text: 'area1', value: 'area1' }, + { text: 'area2', value: 'area2' }, +]; const user1 = { id: 1, name: 'Name One', username: 'one_1', avatar_url: '' }; const user2 = { id: 2, name: 'Name Two', username: 'one_2', avatar_url: '' }; @@ -58,7 +65,9 @@ const createComponent = (data = {}, props = {}) => { isProject, inviteeType, accessLevels, + areasOfFocusOptions, defaultAccessLevel, + noSelectionAreasOfFocus, helpLink, ...props, }, @@ -74,7 +83,7 @@ const createComponent = (data = {}, props = {}) => { GlDropdownItem: true, GlSprintf, GlFormGroup: stubComponent(GlFormGroup, { - props: ['state', 'invalidFeedback'], + props: ['state', 'invalidFeedback', 'description'], }), }, }); @@ -116,9 +125,12 @@ describe('InviteMembersModal', () => { const findCancelButton = () => wrapper.findByTestId('cancel-button'); const findInviteButton = () => wrapper.findByTestId('invite-button'); const clickInviteButton = () => findInviteButton().vm.$emit('click'); + const clickCancelButton = () => findCancelButton().vm.$emit('click'); const findMembersFormGroup = () => wrapper.findByTestId('members-form-group'); const membersFormGroupInvalidFeedback = () => findMembersFormGroup().props('invalidFeedback'); + const membersFormGroupDescription = () => findMembersFormGroup().props('description'); const findMembersSelect = () => wrapper.findComponent(MembersTokenSelect); + const findAreaofFocusCheckBoxGroup = () => wrapper.findComponent(GlFormCheckboxGroup); describe('rendering the modal', () => { beforeEach(() => { @@ -137,6 +149,10 @@ describe('InviteMembersModal', () => { expect(findInviteButton().text()).toBe('Invite'); }); + it('renders the Invite button modal without isLoading', () => { + expect(findInviteButton().props('loading')).toBe(false); + }); + describe('rendering the access levels dropdown', () => { it('sets the default dropdown text to the default access level name', () => { expect(findDropdown().attributes('text')).toBe('Guest'); @@ -160,13 +176,29 @@ describe('InviteMembersModal', () => { }); }); - describe('displaying the correct introText', () => { + describe('rendering the areas_of_focus', () => { + it('renders the areas_of_focus checkboxes', () => { + createComponent(); + + expect(findAreaofFocusCheckBoxGroup().props('options')).toBe(areasOfFocusOptions); + expect(findAreaofFocusCheckBoxGroup().exists()).toBe(true); + }); + + it('does not render the areas_of_focus checkboxes', () => { + createComponent({}, { areasOfFocusOptions: [] }); + + expect(findAreaofFocusCheckBoxGroup().exists()).toBe(false); + }); + }); + + describe('displaying the correct introText and form group description', () => { describe('when inviting to a project', () => { describe('when inviting members', () => { it('includes the correct invitee, type, and formatted name', () => { createInviteMembersToProjectWrapper(); expect(findIntroText()).toBe("You're inviting members to the test name project."); + expect(membersFormGroupDescription()).toBe('Select members or type email addresses'); }); }); @@ -175,6 +207,7 @@ describe('InviteMembersModal', () => { createInviteGroupToProjectWrapper(); expect(findIntroText()).toBe("You're inviting a group to the test name project."); + expect(membersFormGroupDescription()).toBe(''); }); }); }); @@ -185,6 +218,7 @@ describe('InviteMembersModal', () => { createInviteMembersToGroupWrapper(); expect(findIntroText()).toBe("You're inviting members to the test name group."); + expect(membersFormGroupDescription()).toBe('Select members or type email addresses'); }); }); @@ -193,6 +227,7 @@ describe('InviteMembersModal', () => { createInviteGroupToGroupWrapper(); expect(findIntroText()).toBe("You're inviting a group to the test name group."); + expect(membersFormGroupDescription()).toBe(''); }); }); }); @@ -210,6 +245,20 @@ describe('InviteMembersModal', () => { "email 'email@example.com' does not match the allowed domains: example1.org"; const expectedSyntaxError = 'email contains an invalid email address'; + it('calls the API with the expected focus data when an areas_of_focus checkbox is clicked', () => { + const spy = jest.spyOn(Api, 'addGroupMembersByUserId'); + const expectedFocus = [areasOfFocusOptions[0].value]; + createComponent({ newUsersToInvite: [user1] }); + + findAreaofFocusCheckBoxGroup().vm.$emit('input', expectedFocus); + clickInviteButton(); + + expect(spy).toHaveBeenCalledWith( + user1.id.toString(), + expect.objectContaining({ areas_of_focus: expectedFocus }), + ); + }); + describe('when inviting an existing user to group by user ID', () => { const postData = { user_id: '1,2', @@ -217,6 +266,7 @@ describe('InviteMembersModal', () => { expires_at: undefined, invite_source: inviteSource, format: 'json', + areas_of_focus: noSelectionAreasOfFocus, }; describe('when member is added successfully', () => { @@ -226,20 +276,34 @@ describe('InviteMembersModal', () => { wrapper.vm.$toast = { show: jest.fn() }; jest.spyOn(Api, 'addGroupMembersByUserId').mockResolvedValue({ data: postData }); jest.spyOn(wrapper.vm, 'showToastMessageSuccess'); + }); + + it('includes the non-default selected areas of focus', () => { + const focus = ['abc']; + const updatedPostData = { ...postData, areas_of_focus: focus }; + wrapper.setData({ selectedAreasOfFocus: focus }); clickInviteButton(); + + expect(Api.addGroupMembersByUserId).toHaveBeenCalledWith(id, updatedPostData); }); - it('calls Api addGroupMembersByUserId with the correct params', async () => { - await waitForPromises; + describe('when triggered from regular mounting', () => { + beforeEach(() => { + clickInviteButton(); + }); - expect(Api.addGroupMembersByUserId).toHaveBeenCalledWith(id, postData); - }); + it('sets isLoading on the Invite button when it is clicked', () => { + expect(findInviteButton().props('loading')).toBe(true); + }); - it('displays the successful toastMessage', async () => { - await waitForPromises; + it('calls Api addGroupMembersByUserId with the correct params', () => { + expect(Api.addGroupMembersByUserId).toHaveBeenCalledWith(id, postData); + }); - expect(wrapper.vm.showToastMessageSuccess).toHaveBeenCalled(); + it('displays the successful toastMessage', () => { + expect(wrapper.vm.showToastMessageSuccess).toHaveBeenCalled(); + }); }); }); @@ -260,6 +324,51 @@ describe('InviteMembersModal', () => { expect(membersFormGroupInvalidFeedback()).toBe('Member already exists'); expect(findMembersFormGroup().props('state')).toBe(false); expect(findMembersSelect().props('validationState')).toBe(false); + expect(findInviteButton().props('loading')).toBe(false); + }); + + describe('clearing the invalid state and message', () => { + beforeEach(async () => { + mockMembersApi(httpStatus.CONFLICT, membersApiResponse.MEMBER_ALREADY_EXISTS); + + clickInviteButton(); + + await waitForPromises(); + }); + + it('clears the error when the list of members to invite is cleared', async () => { + expect(membersFormGroupInvalidFeedback()).toBe('Member already exists'); + expect(findMembersFormGroup().props('state')).toBe(false); + expect(findMembersSelect().props('validationState')).toBe(false); + + findMembersSelect().vm.$emit('clear'); + + await wrapper.vm.$nextTick(); + + expect(membersFormGroupInvalidFeedback()).toBe(''); + expect(findMembersFormGroup().props('state')).not.toBe(false); + expect(findMembersSelect().props('validationState')).not.toBe(false); + }); + + it('clears the error when the cancel button is clicked', async () => { + clickCancelButton(); + + await wrapper.vm.$nextTick(); + + expect(membersFormGroupInvalidFeedback()).toBe(''); + expect(findMembersFormGroup().props('state')).not.toBe(false); + expect(findMembersSelect().props('validationState')).not.toBe(false); + }); + + it('clears the error when the modal is hidden', async () => { + wrapper.findComponent(GlModal).vm.$emit('hide'); + + await wrapper.vm.$nextTick(); + + expect(membersFormGroupInvalidFeedback()).toBe(''); + expect(findMembersFormGroup().props('state')).not.toBe(false); + expect(findMembersSelect().props('validationState')).not.toBe(false); + }); }); it('clears the invalid state and message once the list of members to invite is cleared', async () => { @@ -272,6 +381,7 @@ describe('InviteMembersModal', () => { expect(membersFormGroupInvalidFeedback()).toBe('Member already exists'); expect(findMembersFormGroup().props('state')).toBe(false); expect(findMembersSelect().props('validationState')).toBe(false); + expect(findInviteButton().props('loading')).toBe(false); findMembersSelect().vm.$emit('clear'); @@ -280,6 +390,7 @@ describe('InviteMembersModal', () => { expect(membersFormGroupInvalidFeedback()).toBe(''); expect(findMembersFormGroup().props('state')).not.toBe(false); expect(findMembersSelect().props('validationState')).not.toBe(false); + expect(findInviteButton().props('loading')).toBe(false); }); it('displays the generic error for http server error', async () => { @@ -336,6 +447,7 @@ describe('InviteMembersModal', () => { expires_at: undefined, email: 'email@example.com', invite_source: inviteSource, + areas_of_focus: noSelectionAreasOfFocus, format: 'json', }; @@ -346,16 +458,30 @@ describe('InviteMembersModal', () => { wrapper.vm.$toast = { show: jest.fn() }; jest.spyOn(Api, 'inviteGroupMembersByEmail').mockResolvedValue({ data: postData }); jest.spyOn(wrapper.vm, 'showToastMessageSuccess'); + }); + + it('includes the non-default selected areas of focus', () => { + const focus = ['abc']; + const updatedPostData = { ...postData, areas_of_focus: focus }; + wrapper.setData({ selectedAreasOfFocus: focus }); clickInviteButton(); - }); - it('calls Api inviteGroupMembersByEmail with the correct params', () => { - expect(Api.inviteGroupMembersByEmail).toHaveBeenCalledWith(id, postData); + expect(Api.inviteGroupMembersByEmail).toHaveBeenCalledWith(id, updatedPostData); }); - it('displays the successful toastMessage', () => { - expect(wrapper.vm.showToastMessageSuccess).toHaveBeenCalled(); + describe('when triggered from regular mounting', () => { + beforeEach(() => { + clickInviteButton(); + }); + + it('calls Api inviteGroupMembersByEmail with the correct params', () => { + expect(Api.inviteGroupMembersByEmail).toHaveBeenCalledWith(id, postData); + }); + + it('displays the successful toastMessage', () => { + expect(wrapper.vm.showToastMessageSuccess).toHaveBeenCalled(); + }); }); }); @@ -375,6 +501,7 @@ describe('InviteMembersModal', () => { expect(membersFormGroupInvalidFeedback()).toBe(expectedSyntaxError); expect(findMembersSelect().props('validationState')).toBe(false); + expect(findInviteButton().props('loading')).toBe(false); }); it('displays the restricted email error when restricted email is invited', async () => { @@ -386,6 +513,7 @@ describe('InviteMembersModal', () => { expect(membersFormGroupInvalidFeedback()).toContain(expectedEmailRestrictedError); expect(findMembersSelect().props('validationState')).toBe(false); + expect(findInviteButton().props('loading')).toBe(false); }); it('displays the successful toast message when email has already been invited', async () => { @@ -446,6 +574,7 @@ describe('InviteMembersModal', () => { access_level: defaultAccessLevel, expires_at: undefined, invite_source: inviteSource, + areas_of_focus: noSelectionAreasOfFocus, format: 'json', }; @@ -482,7 +611,7 @@ describe('InviteMembersModal', () => { }); it('calls Apis with the invite source passed through to openModal', () => { - wrapper.vm.openModal({ inviteeType: 'members', source: '_invite_source_' }); + eventHub.$emit('openModal', { inviteeType: 'members', source: '_invite_source_' }); clickInviteButton(); @@ -548,9 +677,9 @@ describe('InviteMembersModal', () => { describe('when sharing the group fails', () => { beforeEach(() => { - createComponent({ groupToBeSharedWith: sharedGroup }); + createInviteGroupToGroupWrapper(); - wrapper.setData({ inviteeType: 'group' }); + wrapper.setData({ groupToBeSharedWith: sharedGroup }); wrapper.vm.$toast = { show: jest.fn() }; jest @@ -560,10 +689,9 @@ describe('InviteMembersModal', () => { clickInviteButton(); }); - it('displays the generic error message', async () => { - await waitForPromises(); - + it('displays the generic error message', () => { expect(membersFormGroupInvalidFeedback()).toBe('Something went wrong'); + expect(membersFormGroupDescription()).toBe(''); }); }); }); @@ -577,7 +705,7 @@ describe('InviteMembersModal', () => { }); it('tracks the invite', () => { - wrapper.vm.openModal({ inviteeType: 'members', source: INVITE_MEMBERS_IN_COMMENT }); + eventHub.$emit('openModal', { inviteeType: 'members', source: INVITE_MEMBERS_IN_COMMENT }); clickInviteButton(); @@ -586,19 +714,37 @@ describe('InviteMembersModal', () => { }); it('does not track invite for unknown source', () => { - wrapper.vm.openModal({ inviteeType: 'members', source: 'unknown' }); + eventHub.$emit('openModal', { inviteeType: 'members', source: 'unknown' }); clickInviteButton(); - expect(ExperimentTracking).not.toHaveBeenCalled(); + expect(ExperimentTracking).not.toHaveBeenCalledWith(INVITE_MEMBERS_IN_COMMENT); }); it('does not track invite undefined source', () => { - wrapper.vm.openModal({ inviteeType: 'members' }); + eventHub.$emit('openModal', { inviteeType: 'members' }); + + clickInviteButton(); + + expect(ExperimentTracking).not.toHaveBeenCalledWith(INVITE_MEMBERS_IN_COMMENT); + }); + + it('tracks the view for areas_of_focus', () => { + eventHub.$emit('openModal', { inviteeType: 'members' }); + + expect(ExperimentTracking).toHaveBeenCalledWith(MEMBER_AREAS_OF_FOCUS.name); + expect(ExperimentTracking.prototype.event).toHaveBeenCalledWith(MEMBER_AREAS_OF_FOCUS.view); + }); + + it('tracks the invite for areas_of_focus', () => { + eventHub.$emit('openModal', { inviteeType: 'members' }); clickInviteButton(); - expect(ExperimentTracking).not.toHaveBeenCalled(); + expect(ExperimentTracking).toHaveBeenCalledWith(MEMBER_AREAS_OF_FOCUS.name); + expect(ExperimentTracking.prototype.event).toHaveBeenCalledWith( + MEMBER_AREAS_OF_FOCUS.submit, + ); }); }); }); diff --git a/spec/frontend/invite_members/components/members_token_select_spec.js b/spec/frontend/invite_members/components/members_token_select_spec.js index 12db7e42464..196a716d08c 100644 --- a/spec/frontend/invite_members/components/members_token_select_spec.js +++ b/spec/frontend/invite_members/components/members_token_select_spec.js @@ -12,11 +12,12 @@ const user1 = { id: 1, name: 'John Smith', username: 'one_1', avatar_url: '' }; const user2 = { id: 2, name: 'Jane Doe', username: 'two_2', avatar_url: '' }; const allUsers = [user1, user2]; -const createComponent = () => { +const createComponent = (props) => { return shallowMount(MembersTokenSelect, { propsData: { ariaLabelledby: label, placeholder, + ...props, }, stubs: { GlTokenSelector: stubComponent(GlTokenSelector), @@ -27,11 +28,6 @@ const createComponent = () => { describe('MembersTokenSelect', () => { let wrapper; - beforeEach(() => { - jest.spyOn(UserApi, 'getUsers').mockResolvedValue({ data: allUsers }); - wrapper = createComponent(); - }); - afterEach(() => { wrapper.destroy(); wrapper = null; @@ -41,6 +37,8 @@ describe('MembersTokenSelect', () => { describe('rendering the token-selector component', () => { it('renders with the correct props', () => { + wrapper = createComponent(); + const expectedProps = { ariaLabelledby: label, placeholder, @@ -51,6 +49,11 @@ describe('MembersTokenSelect', () => { }); describe('users', () => { + beforeEach(() => { + jest.spyOn(UserApi, 'getUsers').mockResolvedValue({ data: allUsers }); + wrapper = createComponent(); + }); + describe('when input is focused for the first time (modal auto-focus)', () => { it('does not call the API', async () => { findTokenSelector().vm.$emit('focus'); @@ -90,10 +93,10 @@ describe('MembersTokenSelect', () => { await waitForPromises(); - expect(UserApi.getUsers).toHaveBeenCalledWith( - searchParam, - wrapper.vm.$options.queryOptions, - ); + expect(UserApi.getUsers).toHaveBeenCalledWith(searchParam, { + active: true, + exclude_internal: true, + }); expect(tokenSelector.props('hideDropdownWithNoItems')).toBe(false); }); @@ -134,6 +137,8 @@ describe('MembersTokenSelect', () => { describe('when text input is blurred', () => { it('clears text input', async () => { + wrapper = createComponent(); + const tokenSelector = findTokenSelector(); tokenSelector.vm.$emit('blur'); @@ -143,4 +148,33 @@ describe('MembersTokenSelect', () => { expect(tokenSelector.props('hideDropdownWithNoItems')).toBe(false); }); }); + + describe('when component is mounted for a group using a saml provider', () => { + const searchParam = 'name'; + const samlProviderId = 123; + let resolveApiRequest; + + beforeEach(() => { + jest.spyOn(UserApi, 'getUsers').mockImplementation( + () => + new Promise((resolve) => { + resolveApiRequest = resolve; + }), + ); + + wrapper = createComponent({ filterId: samlProviderId, usersFilter: 'saml_provider_id' }); + + findTokenSelector().vm.$emit('text-input', searchParam); + }); + + it('calls the API with the saml provider ID param', () => { + resolveApiRequest({ data: allUsers }); + + expect(UserApi.getUsers).toHaveBeenCalledWith(searchParam, { + active: true, + exclude_internal: true, + saml_provider_id: samlProviderId, + }); + }); + }); }); |