Commit 32ef9221 authored by Paul Slaughter's avatar Paul Slaughter

Move isLoading and invalid state to parents of invite_modal_base

- This cleans up adding callbacks to event args in
  the Vue component which is complex and a breaks
  1-way state flow a bit.
- This also sets up fixing the module duplication
  by simplifying the `submit` handling
parent 3c489e08
...@@ -4,6 +4,7 @@ import Api from '~/api'; ...@@ -4,6 +4,7 @@ import Api from '~/api';
import { BV_SHOW_MODAL, BV_HIDE_MODAL } from '~/lib/utils/constants'; import { BV_SHOW_MODAL, BV_HIDE_MODAL } from '~/lib/utils/constants';
import { GROUP_FILTERS, GROUP_MODAL_LABELS } from '../constants'; import { GROUP_FILTERS, GROUP_MODAL_LABELS } from '../constants';
import eventHub from '../event_hub'; import eventHub from '../event_hub';
import { getInvalidFeedbackMessage } from '../utils/get_invalid_feedback_message';
import GroupSelect from './group_select.vue'; import GroupSelect from './group_select.vue';
import InviteModalBase from './invite_modal_base.vue'; import InviteModalBase from './invite_modal_base.vue';
...@@ -55,6 +56,8 @@ export default { ...@@ -55,6 +56,8 @@ export default {
}, },
data() { data() {
return { return {
invalidFeedbackMessage: '',
isLoading: false,
modalId: uniqueId('invite-groups-modal-'), modalId: uniqueId('invite-groups-modal-'),
groupToBeSharedWith: {}, groupToBeSharedWith: {},
}; };
...@@ -83,13 +86,19 @@ export default { ...@@ -83,13 +86,19 @@ export default {
}); });
}, },
methods: { methods: {
showInvalidFeedbackMessage(response) {
this.invalidFeedbackMessage = getInvalidFeedbackMessage(response);
},
openModal() { openModal() {
this.$root.$emit(BV_SHOW_MODAL, this.modalId); this.$root.$emit(BV_SHOW_MODAL, this.modalId);
}, },
closeModal() { closeModal() {
this.$root.$emit(BV_HIDE_MODAL, this.modalId); this.$root.$emit(BV_HIDE_MODAL, this.modalId);
}, },
sendInvite({ onError, onSuccess, data: { accessLevel, expiresAt } }) { sendInvite({ accessLevel, expiresAt }) {
this.invalidFeedbackMessage = '';
this.isLoading = true;
const apiShareWithGroup = this.isProject const apiShareWithGroup = this.isProject
? Api.projectShareWithGroup.bind(Api) ? Api.projectShareWithGroup.bind(Api)
: Api.groupShareWithGroup.bind(Api); : Api.groupShareWithGroup.bind(Api);
...@@ -101,18 +110,27 @@ export default { ...@@ -101,18 +110,27 @@ export default {
expires_at: expiresAt, expires_at: expiresAt,
}) })
.then(() => { .then(() => {
onSuccess();
this.showSuccessMessage(); this.showSuccessMessage();
}) })
.catch(onError); .catch((e) => {
this.showInvalidFeedbackMessage(e);
})
.finally(() => {
this.isLoading = false;
});
}, },
resetFields() { resetFields() {
this.invalidFeedbackMessage = '';
this.isLoading = false;
this.groupToBeSharedWith = {}; this.groupToBeSharedWith = {};
}, },
showSuccessMessage() { showSuccessMessage() {
this.$toast.show(this.$options.labels.toastMessageSuccessful, this.toastOptions); this.$toast.show(this.$options.labels.toastMessageSuccessful, this.toastOptions);
this.closeModal(); this.closeModal();
}, },
clearValidation() {
this.invalidFeedbackMessage = '';
},
}, },
labels: GROUP_MODAL_LABELS, labels: GROUP_MODAL_LABELS,
}; };
...@@ -129,10 +147,12 @@ export default { ...@@ -129,10 +147,12 @@ export default {
:label-intro-text="labelIntroText" :label-intro-text="labelIntroText"
:label-search-field="$options.labels.searchField" :label-search-field="$options.labels.searchField"
:submit-disabled="inviteDisabled" :submit-disabled="inviteDisabled"
:invalid-feedback-message="invalidFeedbackMessage"
:is-loading="isLoading"
@reset="resetFields" @reset="resetFields"
@submit="sendInvite" @submit="sendInvite"
> >
<template #select="{ clearValidation }"> <template #select>
<group-select <group-select
v-model="groupToBeSharedWith" v-model="groupToBeSharedWith"
:access-levels="accessLevels" :access-levels="accessLevels"
......
...@@ -21,6 +21,7 @@ import { ...@@ -21,6 +21,7 @@ import {
} from '../constants'; } from '../constants';
import eventHub from '../event_hub'; import eventHub from '../event_hub';
import { responseMessageFromSuccess } from '../utils/response_message_parser'; import { responseMessageFromSuccess } from '../utils/response_message_parser';
import { getInvalidFeedbackMessage } from '../utils/get_invalid_feedback_message';
import ModalConfetti from './confetti.vue'; import ModalConfetti from './confetti.vue';
import MembersTokenSelect from './members_token_select.vue'; import MembersTokenSelect from './members_token_select.vue';
...@@ -84,6 +85,8 @@ export default { ...@@ -84,6 +85,8 @@ export default {
}, },
data() { data() {
return { return {
invalidFeedbackMessage: '',
isLoading: false,
modalId: uniqueId('invite-members-modal-'), modalId: uniqueId('invite-members-modal-'),
newUsersToInvite: [], newUsersToInvite: [],
selectedTasksToBeDone: [], selectedTasksToBeDone: [],
...@@ -152,6 +155,9 @@ export default { ...@@ -152,6 +155,9 @@ export default {
} }
}, },
methods: { methods: {
showInvalidFeedbackMessage(response) {
this.invalidFeedbackMessage = getInvalidFeedbackMessage(response);
},
partitionNewUsersToInvite() { partitionNewUsersToInvite() {
const [usersToInviteByEmail, usersToAddById] = partition( const [usersToInviteByEmail, usersToAddById] = partition(
this.newUsersToInvite, this.newUsersToInvite,
...@@ -176,7 +182,10 @@ export default { ...@@ -176,7 +182,10 @@ export default {
const tracking = new ExperimentTracking(experimentName); const tracking = new ExperimentTracking(experimentName);
tracking.event(eventName); tracking.event(eventName);
}, },
sendInvite({ onError, onSuccess, data: { accessLevel, expiresAt } }) { sendInvite({ accessLevel, expiresAt }) {
this.isLoading = true;
this.invalidFeedbackMessage = '';
const [usersToInviteByEmail, usersToAddById] = this.partitionNewUsersToInvite(); const [usersToInviteByEmail, usersToAddById] = this.partitionNewUsersToInvite();
const promises = []; const promises = [];
const baseData = { const baseData = {
...@@ -220,19 +229,17 @@ export default { ...@@ -220,19 +229,17 @@ export default {
const message = responseMessageFromSuccess(responses); const message = responseMessageFromSuccess(responses);
if (message) { if (message) {
onError({ this.showInvalidFeedbackMessage({
response: { response: { data: { message } },
data: {
message,
},
},
}); });
} else { } else {
onSuccess();
this.showSuccessMessage(); this.showSuccessMessage();
} }
}) })
.catch(onError); .catch((e) => this.showInvalidFeedbackMessage(e))
.finally(() => {
this.isLoading = false;
});
}, },
trackinviteMembersForTask() { trackinviteMembersForTask() {
const label = 'selected_tasks_to_be_done'; const label = 'selected_tasks_to_be_done';
...@@ -241,6 +248,8 @@ export default { ...@@ -241,6 +248,8 @@ export default {
tracking.event(INVITE_MEMBERS_FOR_TASK.submit); tracking.event(INVITE_MEMBERS_FOR_TASK.submit);
}, },
resetFields() { resetFields() {
this.isLoading = false;
this.invalidFeedbackMessage = '';
this.newUsersToInvite = []; this.newUsersToInvite = [];
this.selectedTasksToBeDone = []; this.selectedTasksToBeDone = [];
[this.selectedTaskProject] = this.projects; [this.selectedTaskProject] = this.projects;
...@@ -260,6 +269,9 @@ export default { ...@@ -260,6 +269,9 @@ export default {
onAccessLevelUpdate(val) { onAccessLevelUpdate(val) {
this.selectedAccessLevel = val; this.selectedAccessLevel = val;
}, },
clearValidation() {
this.invalidFeedbackMessage = '';
},
}, },
labels: MEMBER_MODAL_LABELS, labels: MEMBER_MODAL_LABELS,
}; };
...@@ -276,6 +288,8 @@ export default { ...@@ -276,6 +288,8 @@ export default {
:label-search-field="$options.labels.searchField" :label-search-field="$options.labels.searchField"
:form-group-description="$options.labels.placeHolder" :form-group-description="$options.labels.placeHolder"
:submit-disabled="inviteDisabled" :submit-disabled="inviteDisabled"
:invalid-feedback-message="invalidFeedbackMessage"
:is-loading="isLoading"
@reset="resetFields" @reset="resetFields"
@submit="sendInvite" @submit="sendInvite"
@access-level="onAccessLevelUpdate" @access-level="onAccessLevelUpdate"
...@@ -288,7 +302,7 @@ export default { ...@@ -288,7 +302,7 @@ export default {
<span v-if="isCelebration">{{ $options.labels.modal.celebrate.intro }} </span> <span v-if="isCelebration">{{ $options.labels.modal.celebrate.intro }} </span>
<modal-confetti v-if="isCelebration" /> <modal-confetti v-if="isCelebration" />
</template> </template>
<template #select="{ clearValidation, validationState, labelId }"> <template #select="{ validationState, labelId }">
<members-token-select <members-token-select
v-model="newUsersToInvite" v-model="newUsersToInvite"
class="gl-mb-2" class="gl-mb-2"
......
...@@ -10,19 +10,15 @@ import { ...@@ -10,19 +10,15 @@ import {
GlButton, GlButton,
GlFormInput, GlFormInput,
} from '@gitlab/ui'; } from '@gitlab/ui';
import { unescape } from 'lodash';
import { sanitize } from '~/lib/dompurify';
import { sprintf } from '~/locale'; import { sprintf } from '~/locale';
import { import {
ACCESS_LEVEL, ACCESS_LEVEL,
ACCESS_EXPIRE_DATE, ACCESS_EXPIRE_DATE,
INVALID_FEEDBACK_MESSAGE_DEFAULT,
READ_MORE_TEXT, READ_MORE_TEXT,
INVITE_BUTTON_TEXT, INVITE_BUTTON_TEXT,
CANCEL_BUTTON_TEXT, CANCEL_BUTTON_TEXT,
HEADER_CLOSE_LABEL, HEADER_CLOSE_LABEL,
} from '../constants'; } from '../constants';
import { responseMessageFromError } from '../utils/response_message_parser';
export default { export default {
components: { components: {
...@@ -80,14 +76,22 @@ export default { ...@@ -80,14 +76,22 @@ export default {
required: false, required: false,
default: false, default: false,
}, },
isLoading: {
type: Boolean,
required: false,
default: false,
},
invalidFeedbackMessage: {
type: String,
required: false,
default: '',
},
}, },
data() { data() {
// Be sure to check out reset! // Be sure to check out reset!
return { return {
invalidFeedbackMessage: '',
selectedAccessLevel: this.defaultAccessLevel, selectedAccessLevel: this.defaultAccessLevel,
selectedDate: undefined, selectedDate: undefined,
isLoading: false,
minDate: new Date(), minDate: new Date(),
}; };
}, },
...@@ -116,16 +120,9 @@ export default { ...@@ -116,16 +120,9 @@ export default {
}, },
}, },
methods: { methods: {
showInvalidFeedbackMessage(response) {
const message = this.unescapeMsg(responseMessageFromError(response));
this.invalidFeedbackMessage = message || INVALID_FEEDBACK_MESSAGE_DEFAULT;
},
reset() { reset() {
// This component isn't necessarily disposed, // This component isn't necessarily disposed,
// so we might need to reset it's state. // so we might need to reset it's state.
this.isLoading = false;
this.invalidFeedbackMessage = '';
this.selectedAccessLevel = this.defaultAccessLevel; this.selectedAccessLevel = this.defaultAccessLevel;
this.selectedDate = undefined; this.selectedDate = undefined;
...@@ -135,33 +132,15 @@ export default { ...@@ -135,33 +132,15 @@ export default {
this.reset(); this.reset();
this.$refs.modal.hide(); this.$refs.modal.hide();
}, },
clearValidation() {
this.invalidFeedbackMessage = '';
},
changeSelectedItem(item) { changeSelectedItem(item) {
this.selectedAccessLevel = item; this.selectedAccessLevel = item;
}, },
submit() { submit() {
this.isLoading = true;
this.invalidFeedbackMessage = '';
this.$emit('submit', { this.$emit('submit', {
onSuccess: () => { accessLevel: this.selectedAccessLevel,
this.isLoading = false; expiresAt: this.selectedDate,
},
onError: (...args) => {
this.isLoading = false;
this.showInvalidFeedbackMessage(...args);
},
data: {
accessLevel: this.selectedAccessLevel,
expiresAt: this.selectedDate,
},
}); });
}, },
unescapeMsg(message) {
return unescape(sanitize(message, { ALLOWED_TAGS: [] }));
},
}, },
HEADER_CLOSE_LABEL, HEADER_CLOSE_LABEL,
ACCESS_EXPIRE_DATE, ACCESS_EXPIRE_DATE,
...@@ -204,10 +183,7 @@ export default { ...@@ -204,10 +183,7 @@ export default {
data-testid="members-form-group" data-testid="members-form-group"
> >
<label :id="selectLabelId" class="col-form-label">{{ labelSearchField }}</label> <label :id="selectLabelId" class="col-form-label">{{ labelSearchField }}</label>
<slot <slot name="select" v-bind="{ validationState, labelId: selectLabelId }"></slot>
name="select"
v-bind="{ clearValidation, validationState, labelId: selectLabelId }"
></slot>
</gl-form-group> </gl-form-group>
<label class="gl-font-weight-bold">{{ $options.ACCESS_LEVEL }}</label> <label class="gl-font-weight-bold">{{ $options.ACCESS_LEVEL }}</label>
......
import { unescape } from 'lodash';
import { sanitize } from '~/lib/dompurify';
import { INVALID_FEEDBACK_MESSAGE_DEFAULT } from '../constants';
import { responseMessageFromError } from './response_message_parser';
const unescapeMsg = (message) => unescape(sanitize(message, { ALLOWED_TAGS: [] }));
export const getInvalidFeedbackMessage = (response) => {
const message = unescapeMsg(responseMessageFromError(response));
return message || INVALID_FEEDBACK_MESSAGE_DEFAULT;
};
...@@ -10,20 +10,16 @@ import { ...@@ -10,20 +10,16 @@ import {
GlButton, GlButton,
GlFormInput, GlFormInput,
} from '@gitlab/ui'; } from '@gitlab/ui';
import { unescape } from 'lodash';
import { sanitize } from '~/lib/dompurify';
import { sprintf } from '~/locale'; import { sprintf } from '~/locale';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { import {
ACCESS_LEVEL, ACCESS_LEVEL,
ACCESS_EXPIRE_DATE, ACCESS_EXPIRE_DATE,
INVALID_FEEDBACK_MESSAGE_DEFAULT,
READ_MORE_TEXT, READ_MORE_TEXT,
INVITE_BUTTON_TEXT, INVITE_BUTTON_TEXT,
CANCEL_BUTTON_TEXT, CANCEL_BUTTON_TEXT,
HEADER_CLOSE_LABEL, HEADER_CLOSE_LABEL,
} from '~/invite_members/constants'; } from '~/invite_members/constants';
import { responseMessageFromError } from '~/invite_members/utils/response_message_parser';
import { import {
OVERAGE_MODAL_LINK, OVERAGE_MODAL_LINK,
OVERAGE_MODAL_TITLE, OVERAGE_MODAL_TITLE,
...@@ -91,6 +87,16 @@ export default { ...@@ -91,6 +87,16 @@ export default {
required: false, required: false,
default: false, default: false,
}, },
isLoading: {
type: Boolean,
required: false,
default: false,
},
invalidFeedbackMessage: {
type: String,
required: false,
default: '',
},
subscriptionSeats: { subscriptionSeats: {
type: Number, type: Number,
required: false, required: false,
...@@ -100,10 +106,8 @@ export default { ...@@ -100,10 +106,8 @@ export default {
data() { data() {
// Be sure to check out reset! // Be sure to check out reset!
return { return {
invalidFeedbackMessage: '',
selectedAccessLevel: this.defaultAccessLevel, selectedAccessLevel: this.defaultAccessLevel,
selectedDate: undefined, selectedDate: undefined,
isLoading: false,
minDate: new Date(), minDate: new Date(),
hasOverage: false, hasOverage: false,
totalUserCount: null, totalUserCount: null,
...@@ -152,16 +156,9 @@ export default { ...@@ -152,16 +156,9 @@ export default {
}, },
}, },
methods: { methods: {
showInvalidFeedbackMessage(response) {
const message = this.unescapeMsg(responseMessageFromError(response));
this.invalidFeedbackMessage = message || INVALID_FEEDBACK_MESSAGE_DEFAULT;
},
reset() { reset() {
// This component isn't necessarily disposed, // This component isn't necessarily disposed,
// so we might need to reset it's state. // so we might need to reset it's state.
this.isLoading = false;
this.invalidFeedbackMessage = '';
this.selectedAccessLevel = this.defaultAccessLevel; this.selectedAccessLevel = this.defaultAccessLevel;
this.selectedDate = undefined; this.selectedDate = undefined;
...@@ -174,33 +171,15 @@ export default { ...@@ -174,33 +171,15 @@ export default {
this.reset(); this.reset();
this.$refs.modal.hide(); this.$refs.modal.hide();
}, },
clearValidation() {
this.invalidFeedbackMessage = '';
},
changeSelectedItem(item) { changeSelectedItem(item) {
this.selectedAccessLevel = item; this.selectedAccessLevel = item;
}, },
submit() { submit() {
this.isLoading = true;
this.invalidFeedbackMessage = '';
this.$emit('submit', { this.$emit('submit', {
onSuccess: () => { accessLevel: this.selectedAccessLevel,
this.isLoading = false; expiresAt: this.selectedDate,
},
onError: (...args) => {
this.isLoading = false;
this.showInvalidFeedbackMessage(...args);
},
data: {
accessLevel: this.selectedAccessLevel,
expiresAt: this.selectedDate,
},
}); });
}, },
unescapeMsg(message) {
return unescape(sanitize(message, { ALLOWED_TAGS: [] }));
},
checkOverage() { checkOverage() {
// add a more complex check in https://gitlab.com/gitlab-org/gitlab/-/merge_requests/78287 // add a more complex check in https://gitlab.com/gitlab-org/gitlab/-/merge_requests/78287
// totalUserCount should be calculated there // totalUserCount should be calculated there
...@@ -272,10 +251,7 @@ export default { ...@@ -272,10 +251,7 @@ export default {
data-testid="members-form-group" data-testid="members-form-group"
> >
<label :id="selectLabelId" class="col-form-label">{{ labelSearchField }}</label> <label :id="selectLabelId" class="col-form-label">{{ labelSearchField }}</label>
<slot <slot name="select" v-bind="{ validationState, labelId: selectLabelId }"></slot>
name="select"
v-bind="{ clearValidation, validationState, labelId: selectLabelId }"
></slot>
</gl-form-group> </gl-form-group>
<label class="gl-font-weight-bold">{{ $options.i18n.ACCESS_LEVEL }}</label> <label class="gl-font-weight-bold">{{ $options.i18n.ACCESS_LEVEL }}</label>
......
...@@ -21,7 +21,7 @@ import { propsData } from 'jest/invite_members/mock_data/modal_base'; ...@@ -21,7 +21,7 @@ import { propsData } from 'jest/invite_members/mock_data/modal_base';
describe('InviteModalBase', () => { describe('InviteModalBase', () => {
let wrapper; let wrapper;
const createComponent = (data = {}, props = {}, glFeatures = {}) => { const createComponent = (props = {}, glFeatures = {}) => {
wrapper = shallowMountExtended(InviteModalBase, { wrapper = shallowMountExtended(InviteModalBase, {
propsData: { propsData: {
...propsData, ...propsData,
...@@ -30,9 +30,6 @@ describe('InviteModalBase', () => { ...@@ -30,9 +30,6 @@ describe('InviteModalBase', () => {
provide: { provide: {
...glFeatures, ...glFeatures,
}, },
data() {
return data;
},
stubs: { stubs: {
GlModal: stubComponent(GlModal, { GlModal: stubComponent(GlModal, {
template: template:
...@@ -64,6 +61,7 @@ describe('InviteModalBase', () => { ...@@ -64,6 +61,7 @@ describe('InviteModalBase', () => {
const findOverageInviteButton = () => wrapper.findByTestId('invite-with-overage-button'); const findOverageInviteButton = () => wrapper.findByTestId('invite-with-overage-button');
const findInitialModalContent = () => wrapper.findByTestId('invite-modal-initial-content'); const findInitialModalContent = () => wrapper.findByTestId('invite-modal-initial-content');
const findOverageModalContent = () => wrapper.findByTestId('invite-modal-overage-content'); const findOverageModalContent = () => wrapper.findByTestId('invite-modal-overage-content');
const findMembersFormGroup = () => wrapper.findByTestId('members-form-group');
const clickInviteButton = () => findInviteButton().vm.$emit('click'); const clickInviteButton = () => findInviteButton().vm.$emit('click');
const clickBackButton = () => findBackButton().vm.$emit('click'); const clickBackButton = () => findBackButton().vm.$emit('click');
...@@ -114,11 +112,39 @@ describe('InviteModalBase', () => { ...@@ -114,11 +112,39 @@ describe('InviteModalBase', () => {
it("doesn't show the overage content", () => { it("doesn't show the overage content", () => {
expect(findOverageModalContent().isVisible()).toBe(false); expect(findOverageModalContent().isVisible()).toBe(false);
}); });
it('renders the members form group', () => {
expect(findMembersFormGroup().props()).toEqual({
description: propsData.formGroupDescription,
invalidFeedback: '',
state: null,
});
});
});
it('with isLoading, shows loading for invite button', () => {
createComponent({
isLoading: true,
});
expect(findInviteButton().props('loading')).toBe(true);
});
it('with invalidFeedbackMessage, set members form group validation state', () => {
createComponent({
invalidFeedbackMessage: 'invalid message!',
});
expect(findMembersFormGroup().props()).toEqual({
description: propsData.formGroupDescription,
invalidFeedback: 'invalid message!',
state: false,
});
}); });
describe('displays overage modal', () => { describe('displays overage modal', () => {
beforeEach(() => { beforeEach(() => {
createComponent({}, {}, { glFeatures: { overageMembersModal: true } }); createComponent({}, { glFeatures: { overageMembersModal: true } });
clickInviteButton(); clickInviteButton();
}); });
......
...@@ -50,6 +50,8 @@ describe('InviteGroupsModal', () => { ...@@ -50,6 +50,8 @@ describe('InviteGroupsModal', () => {
const clickInviteButton = () => findInviteButton().vm.$emit('click'); const clickInviteButton = () => findInviteButton().vm.$emit('click');
const clickCancelButton = () => findCancelButton().vm.$emit('click'); const clickCancelButton = () => findCancelButton().vm.$emit('click');
const triggerGroupSelect = (val) => findGroupSelect().vm.$emit('input', val); const triggerGroupSelect = (val) => findGroupSelect().vm.$emit('input', val);
const findBase = () => wrapper.findComponent(InviteModalBase);
const hideModal = () => wrapper.findComponent(GlModal).vm.$emit('hide');
describe('displaying the correct introText and form group description', () => { describe('displaying the correct introText and form group description', () => {
describe('when inviting to a project', () => { describe('when inviting to a project', () => {
...@@ -70,26 +72,50 @@ describe('InviteGroupsModal', () => { ...@@ -70,26 +72,50 @@ describe('InviteGroupsModal', () => {
}); });
describe('submitting the invite form', () => { describe('submitting the invite form', () => {
describe('when sharing the group is successful', () => { let apiResolve;
const groupPostData = { let apiReject;
group_id: sharedGroup.id, const groupPostData = {
group_access: propsData.defaultAccessLevel, group_id: sharedGroup.id,
expires_at: undefined, group_access: propsData.defaultAccessLevel,
format: 'json', expires_at: undefined,
}; format: 'json',
};
beforeEach(() => {
createComponent();
triggerGroupSelect(sharedGroup);
wrapper.vm.$toast = { show: jest.fn() };
jest.spyOn(Api, 'groupShareWithGroup').mockImplementation(
() =>
new Promise((resolve, reject) => {
apiResolve = resolve;
apiReject = reject;
}),
);
clickInviteButton();
});
beforeEach(() => { it('shows loading', () => {
createComponent(); expect(findBase().props('isLoading')).toBe(true);
triggerGroupSelect(sharedGroup); });
it('calls Api groupShareWithGroup with the correct params', () => {
expect(Api.groupShareWithGroup).toHaveBeenCalledWith(propsData.id, groupPostData);
});
wrapper.vm.$toast = { show: jest.fn() }; describe('when succeeds', () => {
jest.spyOn(Api, 'groupShareWithGroup').mockResolvedValue({ data: groupPostData }); beforeEach(() => {
apiResolve({ data: groupPostData });
});
clickInviteButton(); it('hides loading', () => {
expect(findBase().props('isLoading')).toBe(false);
}); });
it('calls Api groupShareWithGroup with the correct params', () => { it('has no error message', () => {
expect(Api.groupShareWithGroup).toHaveBeenCalledWith(propsData.id, groupPostData); expect(findBase().props('invalidFeedbackMessage')).toBe('');
}); });
it('displays the successful toastMessage', () => { it('displays the successful toastMessage', () => {
...@@ -99,18 +125,9 @@ describe('InviteGroupsModal', () => { ...@@ -99,18 +125,9 @@ describe('InviteGroupsModal', () => {
}); });
}); });
describe('when sharing the group fails', () => { describe('when fails', () => {
beforeEach(() => { beforeEach(() => {
createInviteGroupToGroupWrapper(); apiReject({ response: { data: { success: false } } });
triggerGroupSelect(sharedGroup);
wrapper.vm.$toast = { show: jest.fn() };
jest
.spyOn(Api, 'groupShareWithGroup')
.mockRejectedValue({ response: { data: { success: false } } });
clickInviteButton();
}); });
it('does not show the toast message on failure', () => { it('does not show the toast message on failure', () => {
...@@ -121,22 +138,18 @@ describe('InviteGroupsModal', () => { ...@@ -121,22 +138,18 @@ describe('InviteGroupsModal', () => {
expect(membersFormGroupInvalidFeedback()).toBe('Something went wrong'); expect(membersFormGroupInvalidFeedback()).toBe('Something went wrong');
}); });
describe('clearing the invalid state and message', () => { it.each`
it('clears the error when the cancel button is clicked', async () => { desc | act
clickCancelButton(); ${'when the cancel button is clicked'} | ${clickCancelButton}
${'when the modal is hidden'} | ${hideModal}
await nextTick(); ${'when invite button is clicked'} | ${clickInviteButton}
${'when group input changes'} | ${() => triggerGroupSelect(sharedGroup)}
`('clears the error, $desc', async ({ act }) => {
act();
expect(membersFormGroupInvalidFeedback()).toBe(''); await nextTick();
});
it('clears the error when the modal is hidden', async () => {
wrapper.findComponent(GlModal).vm.$emit('hide');
await nextTick(); expect(membersFormGroupInvalidFeedback()).toBe('');
expect(membersFormGroupInvalidFeedback()).toBe('');
});
}); });
}); });
}); });
......
...@@ -16,15 +16,12 @@ import { propsData } from '../mock_data/modal_base'; ...@@ -16,15 +16,12 @@ import { propsData } from '../mock_data/modal_base';
describe('InviteModalBase', () => { describe('InviteModalBase', () => {
let wrapper; let wrapper;
const createComponent = (data = {}, props = {}) => { const createComponent = (props = {}) => {
wrapper = shallowMountExtended(InviteModalBase, { wrapper = shallowMountExtended(InviteModalBase, {
propsData: { propsData: {
...propsData, ...propsData,
...props, ...props,
}, },
data() {
return data;
},
stubs: { stubs: {
GlModal: stubComponent(GlModal, { GlModal: stubComponent(GlModal, {
template: template:
...@@ -52,6 +49,7 @@ describe('InviteModalBase', () => { ...@@ -52,6 +49,7 @@ describe('InviteModalBase', () => {
const findIntroText = () => wrapper.findByTestId('modal-base-intro-text').text(); const findIntroText = () => wrapper.findByTestId('modal-base-intro-text').text();
const findCancelButton = () => wrapper.findByTestId('cancel-button'); const findCancelButton = () => wrapper.findByTestId('cancel-button');
const findInviteButton = () => wrapper.findByTestId('invite-button'); const findInviteButton = () => wrapper.findByTestId('invite-button');
const findMembersFormGroup = () => wrapper.findByTestId('members-form-group');
describe('rendering the modal', () => { describe('rendering the modal', () => {
beforeEach(() => { beforeEach(() => {
...@@ -99,5 +97,33 @@ describe('InviteModalBase', () => { ...@@ -99,5 +97,33 @@ describe('InviteModalBase', () => {
expect(findDatepicker().exists()).toBe(true); expect(findDatepicker().exists()).toBe(true);
}); });
}); });
it('renders the members form group', () => {
expect(findMembersFormGroup().props()).toEqual({
description: propsData.formGroupDescription,
invalidFeedback: '',
state: null,
});
});
});
it('with isLoading, shows loading for invite button', () => {
createComponent({
isLoading: true,
});
expect(findInviteButton().props('loading')).toBe(true);
});
it('with invalidFeedbackMessage, set members form group validation state', () => {
createComponent({
invalidFeedbackMessage: 'invalid message!',
});
expect(findMembersFormGroup().props()).toEqual({
description: propsData.formGroupDescription,
invalidFeedback: 'invalid message!',
state: false,
});
}); });
}); });
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