Commit 27b24dba authored by Jackie Fraser's avatar Jackie Fraser Committed by Illya Klymov

Add invite by email to modal

parent 5a791545
...@@ -13,6 +13,7 @@ const Api = { ...@@ -13,6 +13,7 @@ const Api = {
groupMilestonesPath: '/api/:version/groups/:id/milestones', groupMilestonesPath: '/api/:version/groups/:id/milestones',
subgroupsPath: '/api/:version/groups/:id/subgroups', subgroupsPath: '/api/:version/groups/:id/subgroups',
namespacesPath: '/api/:version/namespaces.json', namespacesPath: '/api/:version/namespaces.json',
groupInvitationsPath: '/api/:version/groups/:id/invitations',
groupPackagesPath: '/api/:version/groups/:id/packages', groupPackagesPath: '/api/:version/groups/:id/packages',
projectPackagesPath: '/api/:version/projects/:id/packages', projectPackagesPath: '/api/:version/projects/:id/packages',
projectPackagePath: '/api/:version/projects/:id/packages/:package_id', projectPackagePath: '/api/:version/projects/:id/packages/:package_id',
...@@ -23,6 +24,7 @@ const Api = { ...@@ -23,6 +24,7 @@ const Api = {
projectLabelsPath: '/:namespace_path/:project_path/-/labels', projectLabelsPath: '/:namespace_path/:project_path/-/labels',
projectFileSchemaPath: '/:namespace_path/:project_path/-/schema/:ref/:filename', projectFileSchemaPath: '/:namespace_path/:project_path/-/schema/:ref/:filename',
projectUsersPath: '/api/:version/projects/:id/users', projectUsersPath: '/api/:version/projects/:id/users',
projectInvitationsPath: '/api/:version/projects/:id/invitations',
projectMembersPath: '/api/:version/projects/:id/members', projectMembersPath: '/api/:version/projects/:id/members',
projectMergeRequestsPath: '/api/:version/projects/:id/merge_requests', projectMergeRequestsPath: '/api/:version/projects/:id/merge_requests',
projectMergeRequestPath: '/api/:version/projects/:id/merge_requests/:mrid', projectMergeRequestPath: '/api/:version/projects/:id/merge_requests/:mrid',
...@@ -127,12 +129,18 @@ const Api = { ...@@ -127,12 +129,18 @@ const Api = {
}); });
}, },
inviteGroupMember(id, data) { addGroupMembersByUserId(id, data) {
const url = Api.buildUrl(this.groupMembersPath).replace(':id', encodeURIComponent(id)); const url = Api.buildUrl(this.groupMembersPath).replace(':id', encodeURIComponent(id));
return axios.post(url, data); return axios.post(url, data);
}, },
inviteGroupMembersByEmail(id, data) {
const url = Api.buildUrl(this.groupInvitationsPath).replace(':id', encodeURIComponent(id));
return axios.post(url, data);
},
groupMilestones(id, options) { groupMilestones(id, options) {
const url = Api.buildUrl(this.groupMilestonesPath).replace(':id', encodeURIComponent(id)); const url = Api.buildUrl(this.groupMilestonesPath).replace(':id', encodeURIComponent(id));
...@@ -217,12 +225,18 @@ const Api = { ...@@ -217,12 +225,18 @@ const Api = {
.then(({ data }) => data); .then(({ data }) => data);
}, },
inviteProjectMembers(id, data) { addProjectMembersByUserId(id, data) {
const url = Api.buildUrl(this.projectMembersPath).replace(':id', encodeURIComponent(id)); const url = Api.buildUrl(this.projectMembersPath).replace(':id', encodeURIComponent(id));
return axios.post(url, data); return axios.post(url, data);
}, },
inviteProjectMembersByEmail(id, data) {
const url = Api.buildUrl(this.projectInvitationsPath).replace(':id', encodeURIComponent(id));
return axios.post(url, data);
},
// Return single project // Return single project
project(projectPath) { project(projectPath) {
const url = Api.buildUrl(Api.projectPath).replace(':id', encodeURIComponent(projectPath)); const url = Api.buildUrl(Api.projectPath).replace(':id', encodeURIComponent(projectPath));
......
...@@ -9,6 +9,7 @@ import { ...@@ -9,6 +9,7 @@ import {
GlButton, GlButton,
GlFormInput, GlFormInput,
} from '@gitlab/ui'; } from '@gitlab/ui';
import { partition, isString } from 'lodash';
import eventHub from '../event_hub'; import eventHub from '../event_hub';
import { s__, __, sprintf } from '~/locale'; import { s__, __, sprintf } from '~/locale';
import Api from '~/api'; import Api from '~/api';
...@@ -58,7 +59,7 @@ export default { ...@@ -58,7 +59,7 @@ export default {
visible: true, visible: true,
modalId: 'invite-members-modal', modalId: 'invite-members-modal',
selectedAccessLevel: this.defaultAccessLevel, selectedAccessLevel: this.defaultAccessLevel,
newUsersToInvite: '', newUsersToInvite: [],
selectedDate: undefined, selectedDate: undefined,
}; };
}, },
...@@ -79,13 +80,12 @@ export default { ...@@ -79,13 +80,12 @@ export default {
return { return {
onComplete: () => { onComplete: () => {
this.selectedAccessLevel = this.defaultAccessLevel; this.selectedAccessLevel = this.defaultAccessLevel;
this.newUsersToInvite = ''; this.newUsersToInvite = [];
}, },
}; };
}, },
postData() { basePostData() {
return { return {
user_id: this.newUsersToInvite,
access_level: this.selectedAccessLevel, access_level: this.selectedAccessLevel,
expires_at: this.selectedDate, expires_at: this.selectedDate,
format: 'json', format: 'json',
...@@ -101,6 +101,17 @@ export default { ...@@ -101,6 +101,17 @@ export default {
eventHub.$on('openModal', this.openModal); eventHub.$on('openModal', this.openModal);
}, },
methods: { methods: {
partitionNewUsersToInvite() {
const [usersToInviteByEmail, usersToAddById] = partition(
this.newUsersToInvite,
(user) => isString(user.id) && user.id.includes('user-defined-token'),
);
return [
usersToInviteByEmail.map((user) => user.name).join(','),
usersToAddById.map((user) => user.id).join(','),
];
},
openModal() { openModal() {
this.$root.$emit('bv::show::modal', this.modalId); this.$root.$emit('bv::show::modal', this.modalId);
}, },
...@@ -108,7 +119,7 @@ export default { ...@@ -108,7 +119,7 @@ export default {
this.$root.$emit('bv::hide::modal', this.modalId); this.$root.$emit('bv::hide::modal', this.modalId);
}, },
sendInvite() { sendInvite() {
this.submitForm(this.postData); this.submitForm();
this.closeModal(); this.closeModal();
}, },
cancelInvite() { cancelInvite() {
...@@ -120,15 +131,33 @@ export default { ...@@ -120,15 +131,33 @@ export default {
changeSelectedItem(item) { changeSelectedItem(item) {
this.selectedAccessLevel = item; this.selectedAccessLevel = item;
}, },
submitForm(formData) { submitForm() {
if (this.isProject) { const [usersToInviteByEmail, usersToAddById] = this.partitionNewUsersToInvite();
return Api.inviteProjectMembers(this.id, formData) const promises = [];
.then(this.showToastMessageSuccess)
.catch(this.showToastMessageError); if (usersToInviteByEmail !== '') {
const apiInviteByEmail = this.isProject
? Api.inviteProjectMembersByEmail.bind(Api)
: Api.inviteGroupMembersByEmail.bind(Api);
promises.push(apiInviteByEmail(this.id, this.inviteByEmailPostData(usersToInviteByEmail)));
} }
return Api.inviteGroupMember(this.id, formData)
.then(this.showToastMessageSuccess) if (usersToAddById !== '') {
.catch(this.showToastMessageError); const apiAddByUserId = this.isProject
? Api.addProjectMembersByUserId.bind(Api)
: Api.addGroupMembersByUserId.bind(Api);
promises.push(apiAddByUserId(this.id, this.addByUserIdPostData(usersToAddById)));
}
Promise.all(promises).then(this.showToastMessageSuccess).catch(this.showToastMessageError);
},
inviteByEmailPostData(usersToInviteByEmail) {
return { ...this.basePostData, email: usersToInviteByEmail };
},
addByUserIdPostData(usersToAddById) {
return { ...this.basePostData, user_id: usersToAddById };
}, },
showToastMessageSuccess() { showToastMessageSuccess() {
this.$toast.show(this.$options.labels.toastMessageSuccessful, this.toastOptions); this.$toast.show(this.$options.labels.toastMessageSuccessful, this.toastOptions);
......
<script> <script>
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import { GlTokenSelector, GlAvatar, GlAvatarLabeled } from '@gitlab/ui'; import { GlTokenSelector, GlAvatar, GlAvatarLabeled, GlSprintf } from '@gitlab/ui';
import { __ } from '~/locale';
import { USER_SEARCH_DELAY } from '../constants'; import { USER_SEARCH_DELAY } from '../constants';
import Api from '~/api'; import Api from '~/api';
...@@ -9,6 +10,7 @@ export default { ...@@ -9,6 +10,7 @@ export default {
GlTokenSelector, GlTokenSelector,
GlAvatar, GlAvatar,
GlAvatarLabeled, GlAvatarLabeled,
GlSprintf,
}, },
props: { props: {
placeholder: { placeholder: {
...@@ -32,12 +34,10 @@ export default { ...@@ -32,12 +34,10 @@ export default {
}; };
}, },
computed: { computed: {
newUsersToInvite() { emailIsValid() {
return this.selectedTokens const regex = /.+@/;
.map((obj) => {
return obj.id; return this.query.match(regex) !== null;
})
.join(',');
}, },
placeholderText() { placeholderText() {
if (this.selectedTokens.length === 0) { if (this.selectedTokens.length === 0) {
...@@ -69,7 +69,7 @@ export default { ...@@ -69,7 +69,7 @@ export default {
}); });
}, USER_SEARCH_DELAY), }, USER_SEARCH_DELAY),
handleInput() { handleInput() {
this.$emit('input', this.newUsersToInvite); this.$emit('input', this.selectedTokens);
}, },
handleBlur() { handleBlur() {
this.hideDropdownWithNoItems = false; this.hideDropdownWithNoItems = false;
...@@ -86,6 +86,9 @@ export default { ...@@ -86,6 +86,9 @@ export default {
}, },
}, },
queryOptions: { exclude_internal: true, active: true }, queryOptions: { exclude_internal: true, active: true },
i18n: {
inviteTextMessage: __('Invite "%{email}" by email'),
},
}; };
</script> </script>
...@@ -94,7 +97,7 @@ export default { ...@@ -94,7 +97,7 @@ export default {
v-model="selectedTokens" v-model="selectedTokens"
:dropdown-items="users" :dropdown-items="users"
:loading="loading" :loading="loading"
:allow-user-defined-tokens="false" :allow-user-defined-tokens="emailIsValid"
:hide-dropdown-with-no-items="hideDropdownWithNoItems" :hide-dropdown-with-no-items="hideDropdownWithNoItems"
:placeholder="placeholderText" :placeholder="placeholderText"
:aria-labelledby="ariaLabelledby" :aria-labelledby="ariaLabelledby"
...@@ -116,5 +119,13 @@ export default { ...@@ -116,5 +119,13 @@ export default {
:sub-label="dropdownItem.username" :sub-label="dropdownItem.username"
/> />
</template> </template>
<template #user-defined-token-content="{ inputText: email }">
<gl-sprintf :message="$options.i18n.inviteTextMessage">
<template #email>
<span>{{ email }}</span>
</template>
</gl-sprintf>
</template>
</gl-token-selector> </gl-token-selector>
</template> </template>
import Vue from 'vue'; import Vue from 'vue';
import { GlToast } from '@gitlab/ui'; import { GlToast } from '@gitlab/ui';
import { parseBoolean } from '~/lib/utils/common_utils';
import InviteMembersModal from '~/invite_members/components/invite_members_modal.vue'; import InviteMembersModal from '~/invite_members/components/invite_members_modal.vue';
Vue.use(GlToast); Vue.use(GlToast);
...@@ -17,6 +18,7 @@ export default function initInviteMembersModal() { ...@@ -17,6 +18,7 @@ export default function initInviteMembersModal() {
createElement(InviteMembersModal, { createElement(InviteMembersModal, {
props: { props: {
...el.dataset, ...el.dataset,
isProject: parseBoolean(el.dataset.isProject),
accessLevels: JSON.parse(el.dataset.accessLevels), accessLevels: JSON.parse(el.dataset.accessLevels),
}, },
}), }),
......
- if invite_members_allowed?(group) - if invite_members_allowed?(group)
.js-invite-members-modal{ data: { id: group.id, .js-invite-members-modal{ data: { id: group.id,
name: group.name, name: group.name,
is_project: false, is_project: 'false',
access_levels: GroupMember.access_level_roles.to_json, access_levels: GroupMember.access_level_roles.to_json,
default_access_level: Gitlab::Access::GUEST, default_access_level: Gitlab::Access::GUEST,
help_link: help_page_url('user/permissions') } } help_link: help_page_url('user/permissions') } }
- if invite_members_allowed?(project.group) - if invite_members_allowed?(project.group)
.js-invite-members-modal{ data: { id: project.id, .js-invite-members-modal{ data: { id: project.id,
name: project.name, name: project.name,
is_project: true, is_project: 'true',
access_levels: GroupMember.access_level_roles.to_json, access_levels: GroupMember.access_level_roles.to_json,
default_access_level: Gitlab::Access::GUEST, default_access_level: Gitlab::Access::GUEST,
help_link: help_page_url('user/permissions') } } help_link: help_page_url('user/permissions') } }
...@@ -15342,6 +15342,9 @@ msgstr "" ...@@ -15342,6 +15342,9 @@ msgstr ""
msgid "Invite" msgid "Invite"
msgstr "" msgstr ""
msgid "Invite \"%{email}\" by email"
msgstr ""
msgid "Invite \"%{trimmed}\" by email" msgid "Invite \"%{trimmed}\" by email"
msgstr "" msgstr ""
......
...@@ -167,6 +167,50 @@ describe('Api', () => { ...@@ -167,6 +167,50 @@ describe('Api', () => {
}); });
}); });
describe('addGroupMembersByUserId', () => {
it('adds an existing User as a new Group Member by User ID', () => {
const groupId = 1;
const expectedUserId = 2;
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/groups/1/members`;
const params = {
user_id: expectedUserId,
access_level: 10,
expires_at: undefined,
};
mock.onPost(expectedUrl).reply(200, {
id: expectedUserId,
state: 'active',
});
return Api.addGroupMembersByUserId(groupId, params).then(({ data }) => {
expect(data.id).toBe(expectedUserId);
expect(data.state).toBe('active');
});
});
});
describe('inviteGroupMembersByEmail', () => {
it('invites a new email address to create a new User and become a Group Member', () => {
const groupId = 1;
const email = 'email@example.com';
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/groups/1/invitations`;
const params = {
email,
access_level: 10,
expires_at: undefined,
};
mock.onPost(expectedUrl).reply(200, {
status: 'success',
});
return Api.inviteGroupMembersByEmail(groupId, params).then(({ data }) => {
expect(data.status).toBe('success');
});
});
});
describe('groupMilestones', () => { describe('groupMilestones', () => {
it('fetches group milestones', (done) => { it('fetches group milestones', (done) => {
const groupId = '16'; const groupId = '16';
...@@ -458,6 +502,50 @@ describe('Api', () => { ...@@ -458,6 +502,50 @@ describe('Api', () => {
}); });
}); });
describe('addProjectMembersByUserId', () => {
it('adds an existing User as a new Project Member by User ID', () => {
const projectId = 1;
const expectedUserId = 2;
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/1/members`;
const params = {
user_id: expectedUserId,
access_level: 10,
expires_at: undefined,
};
mock.onPost(expectedUrl).reply(200, {
id: expectedUserId,
state: 'active',
});
return Api.addProjectMembersByUserId(projectId, params).then(({ data }) => {
expect(data.id).toBe(expectedUserId);
expect(data.state).toBe('active');
});
});
});
describe('inviteProjectMembersByEmail', () => {
it('invites a new email address to create a new User and become a Project Member', () => {
const projectId = 1;
const expectedEmail = 'email@example.com';
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/1/invitations`;
const params = {
email: expectedEmail,
access_level: 10,
expires_at: undefined,
};
mock.onPost(expectedUrl).reply(200, {
status: 'success',
});
return Api.inviteProjectMembersByEmail(projectId, params).then(({ data }) => {
expect(data.status).toBe('success');
});
});
});
describe('newLabel', () => { describe('newLabel', () => {
it('creates a new label', (done) => { it('creates a new label', (done) => {
const namespace = 'some namespace'; const namespace = 'some namespace';
......
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import { GlDropdown, GlDropdownItem, GlDatepicker, GlSprintf, GlLink, GlModal } from '@gitlab/ui'; import { GlDropdown, GlDropdownItem, GlDatepicker, GlSprintf, GlLink, GlModal } from '@gitlab/ui';
import { stubComponent } from 'helpers/stub_component'; import { stubComponent } from 'helpers/stub_component';
import waitForPromises from 'helpers/wait_for_promises';
import Api from '~/api'; import Api from '~/api';
import InviteMembersModal from '~/invite_members/components/invite_members_modal.vue'; import InviteMembersModal from '~/invite_members/components/invite_members_modal.vue';
...@@ -11,6 +12,15 @@ const accessLevels = { Guest: 10, Reporter: 20, Developer: 30, Maintainer: 40, O ...@@ -11,6 +12,15 @@ const accessLevels = { Guest: 10, Reporter: 20, Developer: 30, Maintainer: 40, O
const defaultAccessLevel = '10'; const defaultAccessLevel = '10';
const helpLink = 'https://example.com'; const helpLink = 'https://example.com';
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 createComponent = (data = {}) => { const createComponent = (data = {}) => {
return shallowMount(InviteMembersModal, { return shallowMount(InviteMembersModal, {
propsData: { propsData: {
...@@ -50,6 +60,7 @@ describe('InviteMembersModal', () => { ...@@ -50,6 +60,7 @@ describe('InviteMembersModal', () => {
const findLink = () => wrapper.find(GlLink); const findLink = () => wrapper.find(GlLink);
const findCancelButton = () => wrapper.find({ ref: 'cancelButton' }); const findCancelButton = () => wrapper.find({ ref: 'cancelButton' });
const findInviteButton = () => wrapper.find({ ref: 'inviteButton' }); const findInviteButton = () => wrapper.find({ ref: 'inviteButton' });
const clickInviteButton = () => findInviteButton().vm.$emit('click');
describe('rendering the modal', () => { describe('rendering the modal', () => {
beforeEach(() => { beforeEach(() => {
...@@ -92,78 +103,184 @@ describe('InviteMembersModal', () => { ...@@ -92,78 +103,184 @@ describe('InviteMembersModal', () => {
}); });
describe('submitting the invite form', () => { describe('submitting the invite form', () => {
const postData = { const apiErrorMessage = 'Member already exists';
user_id: '1',
access_level: '10', describe('when inviting an existing user to group by user ID', () => {
expires_at: new Date(), const postData = {
format: 'json', user_id: '1',
}; access_level: '10',
expires_at: undefined,
format: 'json',
};
describe('when invites are sent successfully', () => {
beforeEach(() => {
wrapper = createComponent({ newUsersToInvite: [user1] });
describe('when the invite was sent successfully', () => { wrapper.vm.$toast = { show: jest.fn() };
beforeEach(() => { jest.spyOn(Api, 'addGroupMembersByUserId').mockResolvedValue({ data: postData });
wrapper = createComponent(); jest.spyOn(wrapper.vm, 'showToastMessageSuccess');
wrapper.vm.$toast = { show: jest.fn() }; clickInviteButton();
jest.spyOn(Api, 'inviteGroupMember').mockResolvedValue({ data: postData }); });
wrapper.vm.submitForm(postData); it('calls Api addGroupMembersByUserId with the correct params', () => {
expect(Api.addGroupMembersByUserId).toHaveBeenCalledWith(id, postData);
});
it('displays the successful toastMessage', () => {
expect(wrapper.vm.showToastMessageSuccess).toHaveBeenCalled();
});
}); });
it('displays the successful toastMessage', () => { describe('when the invite received an api error message', () => {
const toastMessageSuccessful = 'Members were successfully added'; beforeEach(() => {
wrapper = createComponent({ newUsersToInvite: [user1] });
wrapper.vm.$toast = { show: jest.fn() };
jest
.spyOn(Api, 'addGroupMembersByUserId')
.mockRejectedValue({ response: { data: { message: apiErrorMessage } } });
jest.spyOn(wrapper.vm, 'showToastMessageError');
clickInviteButton();
});
expect(wrapper.vm.$toast.show).toHaveBeenCalledWith( it('displays the apiErrorMessage in the toastMessage', async () => {
toastMessageSuccessful, await waitForPromises();
wrapper.vm.toastOptions,
); expect(wrapper.vm.showToastMessageError).toHaveBeenCalledWith({
response: { data: { message: apiErrorMessage } },
});
});
}); });
it('calls Api inviteGroupMember with the correct params', () => { describe('when any invite failed for any other reason', () => {
expect(Api.inviteGroupMember).toHaveBeenCalledWith(id, postData); beforeEach(() => {
wrapper = createComponent({ newUsersToInvite: [user1, user2] });
wrapper.vm.$toast = { show: jest.fn() };
jest
.spyOn(Api, 'addGroupMembersByUserId')
.mockRejectedValue({ response: { data: { success: false } } });
jest.spyOn(wrapper.vm, 'showToastMessageError');
clickInviteButton();
});
it('displays the generic error toastMessage', async () => {
await waitForPromises();
expect(wrapper.vm.showToastMessageError).toHaveBeenCalled();
});
}); });
}); });
describe('when sending the invite for a single member returned an api error', () => { describe('when inviting a new user by email address', () => {
const apiErrorMessage = 'Members already exists'; const postData = {
access_level: '10',
expires_at: undefined,
email: 'email@example.com',
format: 'json',
};
describe('when invites are sent successfully', () => {
beforeEach(() => {
wrapper = createComponent({ newUsersToInvite: [user3] });
wrapper.vm.$toast = { show: jest.fn() };
jest.spyOn(Api, 'inviteGroupMembersByEmail').mockResolvedValue({ data: postData });
jest.spyOn(wrapper.vm, 'showToastMessageSuccess');
beforeEach(() => { clickInviteButton();
wrapper = createComponent({ newUsersToInvite: '123' }); });
wrapper.vm.$toast = { show: jest.fn() }; it('calls Api inviteGroupMembersByEmail with the correct params', () => {
jest expect(Api.inviteGroupMembersByEmail).toHaveBeenCalledWith(id, postData);
.spyOn(Api, 'inviteGroupMember') });
.mockRejectedValue({ response: { data: { message: apiErrorMessage } } });
findInviteButton().vm.$emit('click'); it('displays the successful toastMessage', () => {
expect(wrapper.vm.showToastMessageSuccess).toHaveBeenCalled();
});
}); });
it('displays the api error message for the toastMessage', () => { describe('when any invite failed for any reason', () => {
expect(wrapper.vm.$toast.show).toHaveBeenCalledWith( beforeEach(() => {
apiErrorMessage, wrapper = createComponent({ newUsersToInvite: [user1, user2] });
wrapper.vm.toastOptions,
); wrapper.vm.$toast = { show: jest.fn() };
jest
.spyOn(Api, 'addGroupMembersByUserId')
.mockRejectedValue({ response: { data: { success: false } } });
jest.spyOn(wrapper.vm, 'showToastMessageError');
clickInviteButton();
});
it('displays the generic error toastMessage', async () => {
await waitForPromises();
expect(wrapper.vm.showToastMessageError).toHaveBeenCalled();
});
}); });
}); });
describe('when sending the invite for multiple members returned any error', () => { describe('when inviting members and non-members in same click', () => {
const genericErrorMessage = 'Some of the members could not be added'; const postData = {
access_level: '10',
expires_at: undefined,
format: 'json',
};
const emailPostData = { ...postData, email: 'email@example.com' };
const idPostData = { ...postData, user_id: '1' };
describe('when invites are sent successfully', () => {
beforeEach(() => {
wrapper = 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');
beforeEach(() => { clickInviteButton();
wrapper = createComponent({ newUsersToInvite: '123' }); });
wrapper.vm.$toast = { show: jest.fn() }; it('calls Api inviteGroupMembersByEmail with the correct params', () => {
jest expect(Api.inviteGroupMembersByEmail).toHaveBeenCalledWith(id, emailPostData);
.spyOn(Api, 'inviteGroupMember') });
.mockRejectedValue({ response: { data: { success: false } } });
findInviteButton().vm.$emit('click'); 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('displays the expected toastMessage', () => { describe('when any invite failed for any reason', () => {
expect(wrapper.vm.$toast.show).toHaveBeenCalledWith( beforeEach(() => {
genericErrorMessage, wrapper = createComponent({ newUsersToInvite: [user1, user3] });
wrapper.vm.toastOptions,
); wrapper.vm.$toast = { show: jest.fn() };
jest
.spyOn(Api, 'inviteGroupMembersByEmail')
.mockRejectedValue({ response: { data: { success: false } } });
jest.spyOn(Api, 'addGroupMembersByUserId').mockResolvedValue({ data: postData });
jest.spyOn(wrapper.vm, 'showToastMessageError');
clickInviteButton();
});
it('displays the generic error toastMessage', async () => {
await waitForPromises();
expect(wrapper.vm.showToastMessageError).toHaveBeenCalled();
});
}); });
}); });
}); });
......
...@@ -8,8 +8,8 @@ import MembersTokenSelect from '~/invite_members/components/members_token_select ...@@ -8,8 +8,8 @@ import MembersTokenSelect from '~/invite_members/components/members_token_select
const label = 'testgroup'; const label = 'testgroup';
const placeholder = 'Search for a member'; const placeholder = 'Search for a member';
const user1 = { id: 1, name: 'Name One', username: 'one_1', avatar_url: '' }; const user1 = { id: 1, name: 'John Smith', username: 'one_1', avatar_url: '' };
const user2 = { id: 2, name: 'Name Two', username: 'two_2', avatar_url: '' }; const user2 = { id: 2, name: 'Jane Doe', username: 'two_2', avatar_url: '' };
const allUsers = [user1, user2]; const allUsers = [user1, user2];
const createComponent = () => { const createComponent = () => {
...@@ -77,9 +77,14 @@ describe('MembersTokenSelect', () => { ...@@ -77,9 +77,14 @@ describe('MembersTokenSelect', () => {
}); });
describe('when text input is typed in', () => { describe('when text input is typed in', () => {
let tokenSelector;
beforeEach(() => {
tokenSelector = findTokenSelector();
});
it('calls the API with search parameter', async () => { it('calls the API with search parameter', async () => {
const searchParam = 'One'; const searchParam = 'One';
const tokenSelector = findTokenSelector();
tokenSelector.vm.$emit('text-input', searchParam); tokenSelector.vm.$emit('text-input', searchParam);
...@@ -88,16 +93,23 @@ describe('MembersTokenSelect', () => { ...@@ -88,16 +93,23 @@ describe('MembersTokenSelect', () => {
expect(Api.users).toHaveBeenCalledWith(searchParam, wrapper.vm.$options.queryOptions); expect(Api.users).toHaveBeenCalledWith(searchParam, wrapper.vm.$options.queryOptions);
expect(tokenSelector.props('hideDropdownWithNoItems')).toBe(false); expect(tokenSelector.props('hideDropdownWithNoItems')).toBe(false);
}); });
describe('when input text is an email', () => {
it('allows user defined tokens', async () => {
tokenSelector.vm.$emit('text-input', 'foo@bar.com');
await nextTick();
expect(tokenSelector.props('allowUserDefinedTokens')).toBe(true);
});
});
}); });
describe('when user is selected', () => { describe('when user is selected', () => {
it('emits `input` event with selected users', () => { it('emits `input` event with selected users', () => {
findTokenSelector().vm.$emit('input', [ findTokenSelector().vm.$emit('input', [user1, user2]);
{ id: 1, name: 'John Smith' },
{ id: 2, name: 'Jane Doe' },
]);
expect(wrapper.emitted().input[0][0]).toBe('1,2'); expect(wrapper.emitted().input[0][0]).toEqual([user1, user2]);
}); });
}); });
}); });
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment