import { GlDropdown, GlDropdownItem, GlDatepicker, GlFormGroup, GlSprintf, GlLink, GlModal, GlFormCheckboxGroup, } from '@gitlab/ui'; import MockAdapter from 'axios-mock-adapter'; import { stubComponent } from 'helpers/stub_component'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import waitForPromises from 'helpers/wait_for_promises'; 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, MEMBER_AREAS_OF_FOCUS, INVITE_MEMBERS_FOR_TASK, } 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 { getParameterValues } from '~/lib/utils/url_utility'; import { apiPaths, membersApiResponse, invitationsApiResponse } from '../mock_data/api_responses'; let wrapper; let mock; jest.mock('~/experimentation/experiment_tracking'); jest.mock('~/lib/utils/url_utility', () => ({ ...jest.requireActual('~/lib/utils/url_utility'), getParameterValues: jest.fn(() => []), })); const id = '1'; const name = 'test name'; const isProject = false; 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 tasksToBeDoneOptions = [ { text: 'First task', value: 'first' }, { text: 'Second task', value: 'second' }, ]; const newProjectPath = 'projects/new'; const projects = [ { text: 'First project', value: '1' }, { text: 'Second project', value: '2' }, ]; const user1 = { id: 1, name: 'Name One', username: 'one_1', avatar_url: '' }; const user2 = { id: 2, name: 'Name Two', username: 'one_2', avatar_url: '' }; const user3 = { id: 'user-defined-token', name: 'email@example.com', username: 'one_2', avatar_url: '', }; const user4 = { id: 'user-defined-token', name: 'email4@example.com', username: 'one_4', avatar_url: '', }; const sharedGroup = { id: '981' }; const createComponent = (data = {}, props = {}) => { wrapper = shallowMountExtended(InviteMembersModal, { provide: { newProjectPath, }, propsData: { id, name, isProject, inviteeType, accessLevels, areasOfFocusOptions, defaultAccessLevel, noSelectionAreasOfFocus, tasksToBeDoneOptions, projects, helpLink, ...props, }, data() { return data; }, stubs: { GlModal: stubComponent(GlModal, { template: '
', }), GlDropdown: true, GlDropdownItem: true, GlSprintf, GlFormGroup: stubComponent(GlFormGroup, { props: ['state', 'invalidFeedback', 'description'], }), }, }); }; const createInviteMembersToProjectWrapper = () => { createComponent({ inviteeType: 'members' }, { isProject: true }); }; const createInviteMembersToGroupWrapper = () => { createComponent({ inviteeType: 'members' }, { isProject: false }); }; const createInviteGroupToProjectWrapper = () => { createComponent({ inviteeType: 'group' }, { isProject: true }); }; const createInviteGroupToGroupWrapper = () => { createComponent({ inviteeType: 'group' }, { isProject: false }); }; beforeEach(() => { gon.api_version = 'v4'; mock = new MockAdapter(axios); }); afterEach(() => { wrapper.destroy(); wrapper = null; mock.restore(); }); describe('InviteMembersModal', () => { const findDropdown = () => wrapper.findComponent(GlDropdown); const findDropdownItems = () => findDropdown().findAllComponents(GlDropdownItem); const findDatepicker = () => wrapper.findComponent(GlDatepicker); const findLink = () => wrapper.findComponent(GlLink); const findIntroText = () => wrapper.find({ ref: 'introText' }).text(); 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); const findTasksToBeDone = () => wrapper.findByTestId('invite-members-modal-tasks-to-be-done'); const findTasks = () => wrapper.findByTestId('invite-members-modal-tasks'); const findProjectSelect = () => wrapper.findByTestId('invite-members-modal-project-select'); const findNoProjectsAlert = () => wrapper.findByTestId('invite-members-modal-no-projects-alert'); describe('rendering the modal', () => { beforeEach(() => { createComponent(); }); it('renders the modal with the correct title', () => { expect(wrapper.findComponent(GlModal).props('title')).toBe('Invite members'); }); it('renders the Cancel button text correctly', () => { expect(findCancelButton().text()).toBe('Cancel'); }); it('renders the Invite button text correctly', () => { 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'); }); it('renders dropdown items for each accessLevel', () => { expect(findDropdownItems()).toHaveLength(5); }); }); describe('rendering the help link', () => { it('renders the correct link', () => { expect(findLink().attributes('href')).toBe(helpLink); }); }); describe('rendering the access expiration date field', () => { it('renders the datepicker', () => { expect(findDatepicker()).toExist(); }); }); }); 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('rendering the tasks to be done', () => { const setupComponent = ( extraData = {}, props = {}, urlParameter = ['invite_members_for_task'], ) => { const data = { selectedAccessLevel: 30, selectedTasksToBeDone: ['ci', 'code'], ...extraData, }; getParameterValues.mockImplementation(() => urlParameter); createComponent(data, props); }; afterAll(() => { getParameterValues.mockImplementation(() => []); }); it('renders the tasks to be done', () => { setupComponent(); expect(findTasksToBeDone().exists()).toBe(true); }); describe('when the selected access level is lower than 30', () => { it('does not render the tasks to be done', () => { setupComponent({ selectedAccessLevel: 20 }); expect(findTasksToBeDone().exists()).toBe(false); }); }); describe('when the url does not contain the parameter `open_modal=invite_members_for_task`', () => { it('does not render the tasks to be done', () => { setupComponent({}, {}, []); expect(findTasksToBeDone().exists()).toBe(false); }); }); describe('rendering the tasks', () => { it('renders the tasks', () => { setupComponent(); expect(findTasks().exists()).toBe(true); }); it('does not render an alert', () => { setupComponent(); expect(findNoProjectsAlert().exists()).toBe(false); }); describe('when there are no projects passed in the data', () => { it('does not render the tasks', () => { setupComponent({}, { projects: [] }); expect(findTasks().exists()).toBe(false); }); it('renders an alert with a link to the new projects path', () => { setupComponent({}, { projects: [] }); expect(findNoProjectsAlert().exists()).toBe(true); expect(findNoProjectsAlert().findComponent(GlLink).attributes('href')).toBe( newProjectPath, ); }); }); }); describe('rendering the project dropdown', () => { it('renders the project select', () => { setupComponent(); expect(findProjectSelect().exists()).toBe(true); }); describe('when the modal is shown for a project', () => { it('does not render the project select', () => { setupComponent({}, { isProject: true }); expect(findProjectSelect().exists()).toBe(false); }); }); describe('when no tasks are selected', () => { it('does not render the project select', () => { setupComponent({ selectedTasksToBeDone: [] }); expect(findProjectSelect().exists()).toBe(false); }); }); }); describe('tracking events', () => { it('tracks the view for invite_members_for_task', () => { setupComponent(); expect(ExperimentTracking).toHaveBeenCalledWith(INVITE_MEMBERS_FOR_TASK.name); expect(ExperimentTracking.prototype.event).toHaveBeenCalledWith( INVITE_MEMBERS_FOR_TASK.view, ); }); it('tracks the submit for invite_members_for_task', () => { setupComponent(); clickInviteButton(); expect(ExperimentTracking).toHaveBeenCalledWith(INVITE_MEMBERS_FOR_TASK.name, { label: 'selected_tasks_to_be_done', property: 'ci,code', }); expect(ExperimentTracking.prototype.event).toHaveBeenCalledWith( INVITE_MEMBERS_FOR_TASK.submit, ); }); }); }); 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'); }); }); describe('when sharing with a group', () => { it('includes the correct invitee, type, and formatted name', () => { createInviteGroupToProjectWrapper(); expect(findIntroText()).toBe("You're inviting a group to the test name project."); expect(membersFormGroupDescription()).toBe(''); }); }); }); describe('when inviting to a group', () => { describe('when inviting members', () => { it('includes the correct invitee, type, and formatted name', () => { createInviteMembersToGroupWrapper(); expect(findIntroText()).toBe("You're inviting members to the test name group."); expect(membersFormGroupDescription()).toBe('Select members or type email addresses'); }); }); describe('when sharing with a group', () => { it('includes the correct invitee, type, and formatted name', () => { createInviteGroupToGroupWrapper(); expect(findIntroText()).toBe("You're inviting a group to the test name group."); expect(membersFormGroupDescription()).toBe(''); }); }); }); }); describe('submitting the invite form', () => { const mockMembersApi = (code, data) => { mock.onPost(apiPaths.GROUPS_MEMBERS).reply(code, data); }; const mockInvitationsApi = (code, data) => { mock.onPost(apiPaths.GROUPS_INVITATIONS).reply(code, data); }; const expectedEmailRestrictedError = "The member's email address is not allowed for this project. Go to the Admin area > Sign-up restrictions, and check Allowed domains for sign-ups."; 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', access_level: defaultAccessLevel, expires_at: undefined, invite_source: inviteSource, format: 'json', areas_of_focus: noSelectionAreasOfFocus, tasks_to_be_done: [], tasks_project_id: '', }; describe('when member is added successfully', () => { beforeEach(() => { createComponent({ newUsersToInvite: [user1, user2] }); 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); }); describe('when triggered from regular mounting', () => { beforeEach(() => { clickInviteButton(); }); it('sets isLoading on the Invite button when it is clicked', () => { expect(findInviteButton().props('loading')).toBe(true); }); it('calls Api addGroupMembersByUserId with the correct params', () => { expect(Api.addGroupMembersByUserId).toHaveBeenCalledWith(id, postData); }); it('displays the successful toastMessage', () => { expect(wrapper.vm.showToastMessageSuccess).toHaveBeenCalled(); }); }); }); describe('when member is not added successfully', () => { beforeEach(() => { createInviteMembersToGroupWrapper(); wrapper.setData({ newUsersToInvite: [user1] }); }); it('displays "Member already exists" api message for http status conflict', async () => { mockMembersApi(httpStatus.CONFLICT, membersApiResponse.MEMBER_ALREADY_EXISTS); clickInviteButton(); await waitForPromises(); 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 () => { mockMembersApi(httpStatus.CONFLICT, membersApiResponse.MEMBER_ALREADY_EXISTS); clickInviteButton(); await waitForPromises(); 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'); await waitForPromises(); 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 () => { mockMembersApi(httpStatus.INTERNAL_SERVER_ERROR, 'Request failed with status code 500'); clickInviteButton(); await waitForPromises(); expect(membersFormGroupInvalidFeedback()).toBe('Something went wrong'); }); it('displays the restricted user api message for response with bad request', async () => { mockMembersApi(httpStatus.BAD_REQUEST, membersApiResponse.SINGLE_USER_RESTRICTED); clickInviteButton(); await waitForPromises(); expect(membersFormGroupInvalidFeedback()).toBe(expectedEmailRestrictedError); }); it('displays the first part of the error when multiple existing users are restricted by email', async () => { mockMembersApi(httpStatus.CREATED, membersApiResponse.MULTIPLE_USERS_RESTRICTED); clickInviteButton(); await waitForPromises(); expect(membersFormGroupInvalidFeedback()).toBe( "The member's email address is not allowed for this project. Go to the Admin area > Sign-up restrictions, and check Allowed domains for sign-ups.", ); expect(findMembersSelect().props('validationState')).toBe(false); }); it('displays an access_level error message received for the existing user', async () => { mockMembersApi(httpStatus.BAD_REQUEST, membersApiResponse.SINGLE_USER_ACCESS_LEVEL); clickInviteButton(); await waitForPromises(); expect(membersFormGroupInvalidFeedback()).toBe( 'should be greater than or equal to Owner inherited membership from group Gitlab Org', ); expect(findMembersSelect().props('validationState')).toBe(false); }); }); }); describe('when inviting a new user by email address', () => { const postData = { access_level: defaultAccessLevel, expires_at: undefined, email: 'email@example.com', invite_source: inviteSource, areas_of_focus: noSelectionAreasOfFocus, tasks_to_be_done: [], tasks_project_id: '', format: 'json', }; describe('when invites are sent successfully', () => { beforeEach(() => { createComponent({ newUsersToInvite: [user3] }); 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(); expect(Api.inviteGroupMembersByEmail).toHaveBeenCalledWith(id, updatedPostData); }); 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(); }); }); }); describe('when invites are not sent successfully', () => { beforeEach(() => { createInviteMembersToGroupWrapper(); wrapper.setData({ newUsersToInvite: [user3] }); }); it('displays the api error for invalid email syntax', async () => { mockInvitationsApi(httpStatus.BAD_REQUEST, invitationsApiResponse.EMAIL_INVALID); clickInviteButton(); await waitForPromises(); 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 () => { mockInvitationsApi(httpStatus.CREATED, invitationsApiResponse.EMAIL_RESTRICTED); clickInviteButton(); await waitForPromises(); 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 () => { mockInvitationsApi(httpStatus.CREATED, invitationsApiResponse.EMAIL_TAKEN); wrapper.vm.$toast = { show: jest.fn() }; jest.spyOn(wrapper.vm, 'showToastMessageSuccess'); clickInviteButton(); await waitForPromises(); expect(wrapper.vm.showToastMessageSuccess).toHaveBeenCalled(); expect(findMembersSelect().props('validationState')).toBe(null); }); it('displays the first error message when multiple emails return a restricted error message', async () => { mockInvitationsApi(httpStatus.CREATED, invitationsApiResponse.MULTIPLE_EMAIL_RESTRICTED); clickInviteButton(); await waitForPromises(); expect(membersFormGroupInvalidFeedback()).toContain(expectedEmailRestrictedError); expect(findMembersSelect().props('validationState')).toBe(false); }); it('displays the invalid syntax error for bad request', async () => { mockInvitationsApi(httpStatus.BAD_REQUEST, invitationsApiResponse.ERROR_EMAIL_INVALID); clickInviteButton(); await waitForPromises(); expect(membersFormGroupInvalidFeedback()).toBe(expectedSyntaxError); expect(findMembersSelect().props('validationState')).toBe(false); }); }); describe('when multiple emails are invited at the same time', () => { it('displays the invalid syntax error if one of the emails is invalid', async () => { createInviteMembersToGroupWrapper(); wrapper.setData({ newUsersToInvite: [user3, user4] }); mockInvitationsApi(httpStatus.CREATED, invitationsApiResponse.ERROR_EMAIL_INVALID); clickInviteButton(); await waitForPromises(); expect(membersFormGroupInvalidFeedback()).toBe(expectedSyntaxError); expect(findMembersSelect().props('validationState')).toBe(false); }); }); }); describe('when inviting members and non-members in same click', () => { const postData = { access_level: defaultAccessLevel, expires_at: undefined, invite_source: inviteSource, areas_of_focus: noSelectionAreasOfFocus, format: 'json', tasks_to_be_done: [], tasks_project_id: '', }; const emailPostData = { ...postData, email: 'email@example.com' }; const idPostData = { ...postData, user_id: '1' }; describe('when invites are sent successfully', () => { beforeEach(() => { createComponent({ newUsersToInvite: [user1, user3] }); wrapper.vm.$toast = { show: jest.fn() }; jest.spyOn(Api, 'inviteGroupMembersByEmail').mockResolvedValue({ data: postData }); jest.spyOn(Api, 'addGroupMembersByUserId').mockResolvedValue({ data: postData }); jest.spyOn(wrapper.vm, 'showToastMessageSuccess'); jest.spyOn(wrapper.vm, 'trackInvite'); }); describe('when triggered from regular mounting', () => { beforeEach(() => { clickInviteButton(); }); it('calls Api inviteGroupMembersByEmail with the correct params', () => { expect(Api.inviteGroupMembersByEmail).toHaveBeenCalledWith(id, emailPostData); }); it('calls Api addGroupMembersByUserId with the correct params', () => { expect(Api.addGroupMembersByUserId).toHaveBeenCalledWith(id, idPostData); }); it('displays the successful toastMessage', () => { expect(wrapper.vm.showToastMessageSuccess).toHaveBeenCalled(); }); }); it('calls Apis with the invite source passed through to openModal', () => { eventHub.$emit('openModal', { inviteeType: 'members', source: '_invite_source_' }); clickInviteButton(); expect(Api.inviteGroupMembersByEmail).toHaveBeenCalledWith(id, { ...emailPostData, invite_source: '_invite_source_', }); expect(Api.addGroupMembersByUserId).toHaveBeenCalledWith(id, { ...idPostData, invite_source: '_invite_source_', }); }); }); describe('when any invite failed for any reason', () => { beforeEach(() => { createInviteMembersToGroupWrapper(); wrapper.setData({ newUsersToInvite: [user1, user3] }); mockInvitationsApi(httpStatus.BAD_REQUEST, invitationsApiResponse.EMAIL_INVALID); mockMembersApi(httpStatus.OK, '200 OK'); clickInviteButton(); }); it('displays the first error message', async () => { await waitForPromises(); expect(membersFormGroupInvalidFeedback()).toBe(expectedSyntaxError); }); }); }); describe('when inviting a group to share', () => { describe('when sharing the group is successful', () => { const groupPostData = { group_id: sharedGroup.id, group_access: defaultAccessLevel, expires_at: undefined, format: 'json', }; beforeEach(() => { createComponent({ groupToBeSharedWith: sharedGroup }); wrapper.setData({ inviteeType: 'group' }); wrapper.vm.$toast = { show: jest.fn() }; jest.spyOn(Api, 'groupShareWithGroup').mockResolvedValue({ data: groupPostData }); jest.spyOn(wrapper.vm, 'showToastMessageSuccess'); clickInviteButton(); }); it('calls Api groupShareWithGroup with the correct params', () => { expect(Api.groupShareWithGroup).toHaveBeenCalledWith(id, groupPostData); }); it('displays the successful toastMessage', () => { expect(wrapper.vm.showToastMessageSuccess).toHaveBeenCalled(); }); }); describe('when sharing the group fails', () => { beforeEach(() => { createInviteGroupToGroupWrapper(); wrapper.setData({ groupToBeSharedWith: sharedGroup }); wrapper.vm.$toast = { show: jest.fn() }; jest .spyOn(Api, 'groupShareWithGroup') .mockRejectedValue({ response: { data: { success: false } } }); clickInviteButton(); }); it('displays the generic error message', () => { expect(membersFormGroupInvalidFeedback()).toBe('Something went wrong'); expect(membersFormGroupDescription()).toBe(''); }); }); }); describe('tracking', () => { beforeEach(() => { createComponent({ newUsersToInvite: [user3] }); wrapper.vm.$toast = { show: jest.fn() }; jest.spyOn(Api, 'inviteGroupMembersByEmail').mockResolvedValue({}); }); it('tracks the invite', () => { eventHub.$emit('openModal', { inviteeType: 'members', source: INVITE_MEMBERS_IN_COMMENT }); clickInviteButton(); expect(ExperimentTracking).toHaveBeenCalledWith(INVITE_MEMBERS_IN_COMMENT); expect(ExperimentTracking.prototype.event).toHaveBeenCalledWith('comment_invite_success'); }); it('does not track invite for unknown source', () => { eventHub.$emit('openModal', { inviteeType: 'members', source: 'unknown' }); clickInviteButton(); expect(ExperimentTracking).not.toHaveBeenCalledWith(INVITE_MEMBERS_IN_COMMENT); }); it('does not track invite undefined source', () => { 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).toHaveBeenCalledWith(MEMBER_AREAS_OF_FOCUS.name); expect(ExperimentTracking.prototype.event).toHaveBeenCalledWith( MEMBER_AREAS_OF_FOCUS.submit, ); }); }); }); });