feat: Create modal to merge two contacts (#2457)

This commit is contained in:
Nithin David Thomas 2021-10-13 18:35:13 +05:30 committed by GitHub
parent 6c3e2a0bd3
commit b33701a666
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 437 additions and 110 deletions

View file

@ -42,7 +42,7 @@ class ContactMergeAction
end end
def merge_and_remove_mergee_contact def merge_and_remove_mergee_contact
mergable_attribute_keys = %w[identifier name email phone_number custom_attributes] mergable_attribute_keys = %w[identifier name email phone_number additional_attributes custom_attributes]
base_contact_attributes = base_contact.attributes.slice(*mergable_attribute_keys).compact_blank base_contact_attributes = base_contact.attributes.slice(*mergable_attribute_keys).compact_blank
mergee_contact_attributes = mergee_contact.attributes.slice(*mergable_attribute_keys).compact_blank mergee_contact_attributes = mergee_contact.attributes.slice(*mergable_attribute_keys).compact_blank

View file

@ -0,0 +1,18 @@
/* global axios */
import ApiClient from './ApiClient';
class AccountActions extends ApiClient {
constructor() {
super('actions', { accountScoped: true });
}
merge(parentId, childId) {
return axios.post(`${this.url}/contact_merge`, {
base_contact_id: parentId,
mergee_contact_id: childId,
});
}
}
export default new AccountActions();

View file

@ -0,0 +1,23 @@
import accountActionsAPI from '../accountActions';
import ApiClient from '../ApiClient';
import describeWithAPIMock from './apiSpecHelper';
describe('#ContactsAPI', () => {
it('creates correct instance', () => {
expect(accountActionsAPI).toBeInstanceOf(ApiClient);
expect(accountActionsAPI).toHaveProperty('merge');
});
describeWithAPIMock('API calls', context => {
it('#merge', () => {
accountActionsAPI.merge(1, 2);
expect(context.axiosMock.post).toHaveBeenCalledWith(
'/api/v1/actions/contact_merge',
{
base_contact_id: 1,
mergee_contact_id: 2,
}
);
});
});
});

View file

@ -15,6 +15,10 @@
.multiselect { .multiselect {
margin-bottom: var(--space-normal); margin-bottom: var(--space-normal);
&.multiselect--disabled {
opacity: .8;
}
.multiselect--active { .multiselect--active {
>.multiselect__tags { >.multiselect__tags {
border-color: $color-woot; border-color: $color-woot;
@ -209,3 +213,53 @@
flex-shrink: 0; flex-shrink: 0;
} }
} }
.multiselect-wrap--medium {
$multiselect-height: 4.8rem;
.multiselect__tags,
.multiselect__input {
align-items: center;
display: flex;
}
.multiselect__tags,
.multiselect__input,
.multiselect {
background: var(--white);
font-size: var(--font-size-small);
height: $multiselect-height;
min-height: $multiselect-height;
}
.multiselect__input {
height: $multiselect-height - $space-micro;
min-height: $multiselect-height - $space-micro;
}
.multiselect__single {
align-items: center;
display: flex;
font-size: var(--font-size-small);
margin: 0;
padding: var(--space-smaller) var(--space-micro);
}
.multiselect__placeholder {
margin: 0;
padding: var(--space-smaller) var(--space-micro);
}
.multiselect__select {
min-height: $multiselect-height;
}
.multiselect--disabled .multiselect__current,
.multiselect--disabled .multiselect__select {
background: transparent;
}
.multiselect__tags-wrap {
flex-shrink: 0;
}
}

View file

@ -32,6 +32,8 @@
"NO_RESULT": "No labels found" "NO_RESULT": "No labels found"
} }
}, },
"MERGE_CONTACT": "Merge contact",
"CONTACT_ACTIONS": "Contact actions",
"MUTE_CONTACT": "Mute Conversation", "MUTE_CONTACT": "Mute Conversation",
"UNMUTE_CONTACT": "Unmute Conversation", "UNMUTE_CONTACT": "Unmute Conversation",
"MUTED_SUCCESS": "This conversation is muted for 6 hours", "MUTED_SUCCESS": "This conversation is muted for 6 hours",
@ -242,17 +244,19 @@
}, },
"MERGE_CONTACTS": { "MERGE_CONTACTS": {
"TITLE": "Merge contacts", "TITLE": "Merge contacts",
"DESCRIPTION": "Merge contact is helpful when you have duplicated entries of the same contact. Merging action takes a primary contact and a child contact. After merging, all details in the primary contact will remain the same. If the primary contact doesn't have a field, then the value from the child contact will be used after merging. If a conflict happens, fields in primary contact will remain unaffected, but fields from secondary will be copied to the custom attributes in the primary contact.", "DESCRIPTION": "Merge contacts to combine two profiles into one, including all attributes and conversations. In case of conflict, the Primary contact s attributes will take precedence.",
"PRIMARY": { "PRIMARY": {
"TITLE": "Primary contact" "TITLE": "Primary contact",
"HELP_LABEL": "To be kept"
}, },
"CHILD": { "CHILD": {
"TITLE": "Contact to merge", "TITLE": "Contact to merge",
"PLACEHOLDER": "Choose a contact" "PLACEHOLDER": "Search for a contact",
"HELP_LABEL": "To be deleted"
}, },
"SUMMARY": { "SUMMARY": {
"TITLE": "Summary", "TITLE": "Summary",
"DELETE_WARNING": "Contact of <strong>%{childContactName}</strong>will be deleted.", "DELETE_WARNING": "Contact of <strong>%{childContactName}</strong> will be deleted.",
"ATTRIBUTE_WARNING": "Contact details of <strong>%{childContactName}</strong> will be copied to <strong>%{primaryContactName}</strong>." "ATTRIBUTE_WARNING": "Contact details of <strong>%{childContactName}</strong> will be copied to <strong>%{primaryContactName}</strong>."
}, },
"SEARCH": { "SEARCH": {
@ -265,7 +269,7 @@
"ERROR": "Select a child contact to merge" "ERROR": "Select a child contact to merge"
}, },
"SUCCESS_MESSAGE": "Contact merged successfully", "SUCCESS_MESSAGE": "Contact merged successfully",
"ERROR_MESSAGE": "Could not merge contcts, try again!" "ERROR_MESSAGE": "Could not merge contacts, try again!"
} }
} }
} }

View file

@ -0,0 +1,89 @@
<template>
<woot-modal :show.sync="show" :on-close="onClose">
<woot-modal-header
:header-title="$t('MERGE_CONTACTS.TITLE')"
:header-content="$t('MERGE_CONTACTS.DESCRIPTION')"
/>
<merge-contact
:primary-contact="primaryContact"
:is-searching="isSearching"
:is-merging="uiFlags.isMerging"
:search-results="searchResults"
@search="onContactSearch"
@cancel="onClose"
@submit="onMergeContacts"
/>
</woot-modal>
</template>
<script>
import alertMixin from 'shared/mixins/alertMixin';
import MergeContact from 'dashboard/modules/contact/components/MergeContact';
import ContactAPI from 'dashboard/api/contacts';
import { mapGetters } from 'vuex';
export default {
components: { MergeContact },
mixins: [alertMixin],
props: {
primaryContact: {
type: Object,
required: true,
},
show: {
type: Boolean,
default: false,
},
},
data() {
return {
isSearching: false,
searchResults: [],
};
},
computed: {
...mapGetters({
uiFlags: 'contacts/getUIFlags',
}),
},
methods: {
onClose() {
this.$emit('close');
},
async onContactSearch(query) {
this.isSearching = true;
this.searchResults = [];
try {
const {
data: { payload },
} = await ContactAPI.search(query);
this.searchResults = payload.filter(
contact => contact.id !== this.primaryContact.id
);
} catch (error) {
this.showAlert(this.$t('MERGE_CONTACTS.SEARCH.ERROR_MESSAGE'));
} finally {
this.isSearching = false;
}
},
async onMergeContacts(childContactId) {
try {
await this.$store.dispatch('contacts/merge', {
childId: childContactId,
parentId: this.primaryContact.id,
});
this.showAlert(this.$t('MERGE_CONTACTS.FORM.SUCCESS_MESSAGE'));
this.onClose();
} catch (error) {
this.showAlert(this.$t('MERGE_CONTACTS.FORM.ERROR_MESSAGE'));
}
},
},
};
</script>
<style lang="scss" scoped></style>

View file

@ -1,9 +1,24 @@
<template> <template>
<div class="option-item--user"> <div class="option-item--user">
<thumbnail :src="thumbnail" size="24px" :username="name" /> <thumbnail :src="thumbnail" size="28px" :username="name" />
<span class="option__title"> <div class="option__user-data">
{{ name }} <h5 class="option__title">
</span> {{ name }}
<span v-if="identifier" class="user-identifier">
( id: {{ identifier }} )
</span>
</h5>
<p class="option__body">
<span v-if="email" class="email-icon-wrap">
<i class="icon ion-email" />{{ email }}
</span>
<span v-if="phoneNumber" class="phone-icon-wrap">
<i class="icon ion-ios-telephone" />
{{ phoneNumber }}
</span>
<span v-if="!phoneNumber && !email">{{ '---' }}</span>
</p>
</div>
</div> </div>
</template> </template>
@ -23,6 +38,18 @@ export default {
type: String, type: String,
default: '', default: '',
}, },
email: {
type: String,
default: '',
},
phoneNumber: {
type: String,
default: '',
},
identifier: {
type: String,
default: '',
},
}, },
}; };
</script> </script>
@ -30,5 +57,49 @@ export default {
<style lang="scss" scoped> <style lang="scss" scoped>
.option-item--user { .option-item--user {
display: flex; display: flex;
align-items: center;
}
.user-identifier {
font-size: var(--font-size-mini);
margin-left: var(--space-micro);
color: var(--s-700);
}
.option__user-data {
display: flex;
flex-direction: column;
flex-grow: 1;
margin-left: var(--space-small);
}
.option__body,
.option__title {
display: flex;
align-items: center;
justify-content: flex-start;
line-height: 1.2;
font-size: var(--font-size-small);
}
.option__body .icon {
position: relative;
top: 1px;
margin-right: var(--space-micro);
}
.option__title {
font-weight: var(--font-weight-medium);
margin-bottom: var(--space-micro);
}
.option__body {
font-size: var(--font-size-mini);
color: var(--s-700);
}
.email-icon-wrap {
margin-right: var(--space-normal);
}
.option__user-data .option__body {
> .phone-icon-wrap,
> .email-icon-wrap {
width: auto;
}
} }
</style> </style>

View file

@ -1,6 +1,6 @@
<template> <template>
<div class="contact-fields"> <div class="contact-fields">
<h3 class="block-title title">Contact fields</h3> <h3 class="block-title title">{{ $t('CONTACTS_PAGE.FIELDS') }}</h3>
<attribute <attribute
:label="$t('CONTACT_PANEL.EMAIL_ADDRESS')" :label="$t('CONTACT_PANEL.EMAIL_ADDRESS')"
icon="ion-email" icon="ion-email"

View file

@ -1,9 +1,15 @@
<template> <template>
<form @submit.prevent="onSubmit"> <form @submit.prevent="onSubmit">
<div class="merge-contacts"> <div class="merge-contacts">
<div class="multiselect-wrap--small"> <div class="multiselect-wrap--medium">
<label class="multiselect__label"> <label class="multiselect__label">
{{ $t('MERGE_CONTACTS.PRIMARY.TITLE') }} {{ $t('MERGE_CONTACTS.PRIMARY.TITLE') }}
<woot-label
:title="$t('MERGE_CONTACTS.PRIMARY.HELP_LABEL')"
color-scheme="success"
small
class="label--merge-warning"
></woot-label>
</label> </label>
<multiselect <multiselect
:value="primaryContact" :value="primaryContact"
@ -17,6 +23,9 @@
<contact-dropdown-item <contact-dropdown-item
:thumbnail="props.option.thumbnail" :thumbnail="props.option.thumbnail"
:name="props.option.name" :name="props.option.name"
:identifier="props.option.id"
:email="props.option.email"
:phone-number="props.option.phoneNumber"
/> />
</template> </template>
</multiselect> </multiselect>
@ -27,11 +36,17 @@
<i class="ion-ios-arrow-up up" /> <i class="ion-ios-arrow-up up" />
</div> </div>
<div <div
class="child-contact multiselect-wrap--small" class="child-contact multiselect-wrap--medium"
:class="{ error: $v.childContact.$error }" :class="{ error: $v.childContact.$error }"
> >
<label class="multiselect__label"> <label class="multiselect__label">
{{ $t('MERGE_CONTACTS.CHILD.TITLE') }} {{ $t('MERGE_CONTACTS.CHILD.TITLE')
}}<woot-label
:title="$t('MERGE_CONTACTS.CHILD.HELP_LABEL')"
color-scheme="alert"
small
class="label--merge-warning"
></woot-label>
</label> </label>
<multiselect <multiselect
v-model="childContact" v-model="childContact"
@ -51,7 +66,19 @@
<template slot="singleLabel" slot-scope="props"> <template slot="singleLabel" slot-scope="props">
<contact-dropdown-item <contact-dropdown-item
:thumbnail="props.option.thumbnail" :thumbnail="props.option.thumbnail"
:identifier="props.option.id"
:name="props.option.name" :name="props.option.name"
:email="props.option.email"
:phone-number="props.option.phone_number"
/>
</template>
<template slot="option" slot-scope="props">
<contact-dropdown-item
:thumbnail="props.option.thumbnail"
:identifier="props.option.id"
:name="props.option.name"
:email="props.option.email"
:phone-number="props.option.phone_number"
/> />
</template> </template>
<span slot="noResult"> <span slot="noResult">
@ -190,12 +217,6 @@ export default {
left: var(--space-normal); left: var(--space-normal);
} }
::v-deep .multiselect__tags .option__title {
display: inline-flex;
align-items: center;
margin-left: var(--space-small);
}
.footer { .footer {
margin-top: var(--space-medium); margin-top: var(--space-medium);
display: flex; display: flex;
@ -206,4 +227,8 @@ export default {
.error .message { .error .message {
margin-top: 0; margin-top: 0;
} }
.label--merge-warning {
margin-left: var(--space-small);
}
</style> </style>

View file

@ -5,7 +5,7 @@
</h5> </h5>
<ul class="summary-items"> <ul class="summary-items">
<li> <li>
<span></span> <span class="bullet"></span>
<span <span
v-html=" v-html="
$t('MERGE_CONTACTS.SUMMARY.DELETE_WARNING', { $t('MERGE_CONTACTS.SUMMARY.DELETE_WARNING', {
@ -15,7 +15,7 @@
/> />
</li> </li>
<li> <li>
<span></span> <span class="bullet"></span>
<span <span
v-html=" v-html="
$t('MERGE_CONTACTS.SUMMARY.ATTRIBUTE_WARNING', { $t('MERGE_CONTACTS.SUMMARY.ATTRIBUTE_WARNING', {
@ -59,4 +59,9 @@ export default {
margin-bottom: var(--space-smaller); margin-bottom: var(--space-smaller);
} }
} }
.bullet {
display: inline-block;
margin-right: var(--space-smaller);
}
</style> </style>

View file

@ -91,9 +91,9 @@ export default {
.close-button { .close-button {
position: absolute; position: absolute;
right: var(--space-normal); right: var(--space-normal);
top: var(--space-slab); top: 3.6rem;
font-size: var(--font-size-big); font-size: var(--font-size-big);
color: var(--color-heading); color: var(--s-500);
.close-icon { .close-icon {
margin-right: var(--space-smaller); margin-right: var(--space-smaller);

View file

@ -222,8 +222,9 @@ export default {
return this.additionalAttributes.initiated_at; return this.additionalAttributes.initiated_at;
}, },
browserName() { browserName() {
return `${this.browser.browser_name || ''} ${this.browser return `${this.browser.browser_name || ''} ${
.browser_version || ''}`; this.browser.browser_version || ''
}`;
}, },
contactAdditionalAttributes() { contactAdditionalAttributes() {
return this.contact.additional_attributes || {}; return this.contact.additional_attributes || {};
@ -247,10 +248,8 @@ export default {
return `${cityAndCountry} ${countryFlag}`; return `${cityAndCountry} ${countryFlag}`;
}, },
platformName() { platformName() {
const { const { platform_name: platformName, platform_version: platformVersion } =
platform_name: platformName, this.browser;
platform_version: platformVersion,
} = this.browser;
return `${platformName || ''} ${platformVersion || ''}`; return `${platformName || ''} ${platformVersion || ''}`;
}, },
channelType() { channelType() {
@ -421,10 +420,11 @@ export default {
.close-button { .close-button {
position: absolute; position: absolute;
right: $space-normal; right: $space-two;
top: $space-slab; top: $space-slab + $space-two;
font-size: $font-size-default; font-size: $font-size-default;
color: $color-heading; color: $color-heading;
z-index: 9999;
} }
.conversation--labels { .conversation--labels {

View file

@ -48,59 +48,49 @@
/> />
</div> </div>
</div> </div>
<div v-if="!showNewMessage"> <div class="contact-actions">
<div> <woot-button
<woot-button v-if="showNewMessage"
class="edit-contact" v-tooltip="$t('CONTACT_PANEL.NEW_MESSAGE')"
variant="link" title="$t('CONTACT_PANEL.NEW_MESSAGE')"
size="small" class="new-message"
@click="toggleEditModal" icon="ion-chatboxes"
> size="small expanded"
{{ $t('EDIT_CONTACT.BUTTON_LABEL') }} @click="toggleConversationModal"
</woot-button> />
</div> <woot-button
<div v-if="isAdmin"> v-tooltip="$t('EDIT_CONTACT.BUTTON_LABEL')"
<woot-button title="$t('EDIT_CONTACT.BUTTON_LABEL')"
class="delete-contact" class="edit-contact"
variant="link" icon="ion-edit"
size="small" variant="smooth"
color-scheme="alert" size="small expanded"
@click="toggleDeleteModal" @click="toggleEditModal"
:disabled="uiFlags.isDeleting" />
> <woot-button
{{ $t('DELETE_CONTACT.BUTTON_LABEL') }} v-if="isAdmin"
</woot-button> v-tooltip="$t('CONTACT_PANEL.MERGE_CONTACT')"
</div> title="$t('CONTACT_PANEL.MERGE_CONTACT')"
</div> class="merge-contact"
<div v-else> icon="ion-merge"
<div class="contact-actions"> variant="smooth"
<woot-button size="small expanded"
v-tooltip="$t('CONTACT_PANEL.NEW_MESSAGE')" color-scheme="secondary"
class="new-message" :disabled="uiFlags.isMerging"
icon="ion-chatboxes" @click="openMergeModal"
size="small expanded" />
@click="toggleConversationModal" <woot-button
/> v-if="isAdmin"
<woot-button v-tooltip="$t('DELETE_CONTACT.BUTTON_LABEL')"
v-tooltip="$t('EDIT_CONTACT.BUTTON_LABEL')" title="$t('DELETE_CONTACT.BUTTON_LABEL')"
class="edit-contact" class="delete-contact"
icon="ion-edit" icon="ion-trash-a"
variant="smooth" variant="smooth"
size="small expanded" size="small expanded"
@click="toggleEditModal" color-scheme="alert"
/> :disabled="uiFlags.isDeleting"
<woot-button @click="toggleDeleteModal"
v-if="isAdmin" />
v-tooltip="$t('DELETE_CONTACT.BUTTON_LABEL')"
class="delete-contact"
icon="ion-trash-a"
variant="hollow"
size="small expanded"
color-scheme="alert"
@click="toggleDeleteModal"
:disabled="uiFlags.isDeleting"
/>
</div>
</div> </div>
<edit-contact <edit-contact
v-if="showEditModal" v-if="showEditModal"
@ -114,6 +104,12 @@
:contact="contact" :contact="contact"
@cancel="toggleConversationModal" @cancel="toggleConversationModal"
/> />
<contact-merge-modal
v-if="showMergeModal"
:primary-contact="contact"
:show="showMergeModal"
@close="toggleMergeModal"
/>
</div> </div>
<woot-confirm-delete-modal <woot-confirm-delete-modal
v-if="showDeleteModal" v-if="showDeleteModal"
@ -130,11 +126,14 @@
</div> </div>
</template> </template>
<script> <script>
import { mixin as clickaway } from 'vue-clickaway';
import ContactInfoRow from './ContactInfoRow'; import ContactInfoRow from './ContactInfoRow';
import Thumbnail from 'dashboard/components/widgets/Thumbnail.vue'; import Thumbnail from 'dashboard/components/widgets/Thumbnail.vue';
import SocialIcons from './SocialIcons'; import SocialIcons from './SocialIcons';
import EditContact from './EditContact'; import EditContact from './EditContact';
import NewConversation from './NewConversation'; import NewConversation from './NewConversation';
import ContactMergeModal from 'dashboard/modules/contact/ContactMergeModal';
import alertMixin from 'shared/mixins/alertMixin'; import alertMixin from 'shared/mixins/alertMixin';
import adminMixin from '../../../../mixins/isAdmin'; import adminMixin from '../../../../mixins/isAdmin';
import { mapGetters } from 'vuex'; import { mapGetters } from 'vuex';
@ -146,8 +145,9 @@ export default {
Thumbnail, Thumbnail,
SocialIcons, SocialIcons,
NewConversation, NewConversation,
ContactMergeModal,
}, },
mixins: [alertMixin, adminMixin], mixins: [alertMixin, adminMixin, clickaway],
props: { props: {
contact: { contact: {
type: Object, type: Object,
@ -166,6 +166,7 @@ export default {
return { return {
showEditModal: false, showEditModal: false,
showConversationModal: false, showConversationModal: false,
showMergeModal: false,
showDeleteModal: false, showDeleteModal: false,
}; };
}, },
@ -201,6 +202,9 @@ export default {
}, },
}, },
methods: { methods: {
toggleMergeModal() {
this.showMergeModal = !this.showMergeModal;
},
toggleEditModal() { toggleEditModal() {
this.showEditModal = !this.showEditModal; this.showEditModal = !this.showEditModal;
}, },
@ -232,24 +236,26 @@ export default {
); );
} }
}, },
openMergeModal() {
this.toggleMergeModal();
},
}, },
}; };
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import '~dashboard/assets/scss/variables';
@import '~dashboard/assets/scss/mixins';
.contact--profile { .contact--profile {
position: relative;
align-items: flex-start; align-items: flex-start;
padding: var(--space-normal); padding: var(--space-normal);
.user-thumbnail-box { .user-thumbnail-box {
margin-right: $space-normal; margin-right: var(--space-normal);
} }
} }
.contact--details { .contact--details {
margin-top: $space-small; margin-top: var(--space-small);
width: 100%; width: 100%;
} }
@ -270,31 +276,25 @@ export default {
margin-top: var(--space-small); margin-top: var(--space-small);
} }
.edit-contact {
margin-left: var(--space-medium);
}
.delete-contact {
margin-left: var(--space-medium);
}
.contact-actions { .contact-actions {
display: flex; display: flex;
align-items: center; align-items: center;
width: 100%; width: 100%;
.new-message { .new-message,
font-size: var(--font-size-medium); .edit-contact,
} .merge-contact,
.edit-contact {
margin-left: var(--space-small);
font-size: var(--font-size-medium);
}
.delete-contact { .delete-contact {
margin-left: var(--space-small); margin-right: var(--space-small);
font-size: var(--font-size-medium);
} }
} }
.merege-summary--card {
padding: var(--space-normal);
}
.button--contact-menu {
position: absolute;
right: var(--space-normal);
top: 0;
}
</style> </style>

View file

@ -4,6 +4,7 @@ import {
} from 'shared/helpers/CustomErrors'; } from 'shared/helpers/CustomErrors';
import types from '../../mutation-types'; import types from '../../mutation-types';
import ContactAPI from '../../../api/contacts'; import ContactAPI from '../../../api/contacts';
import AccountActionsAPI from '../../../api/accountActions';
export const actions = { export const actions = {
search: async ({ commit }, { search, page, sortAttr, label }) => { search: async ({ commit }, { search, page, sortAttr, label }) => {
@ -137,6 +138,18 @@ export const actions = {
commit(types.SET_CONTACT_ITEM, data); commit(types.SET_CONTACT_ITEM, data);
}, },
merge: async ({ commit }, { childId, parentId }) => {
commit(types.SET_CONTACT_UI_FLAG, { isMerging: true });
try {
const response = await AccountActionsAPI.merge(parentId, childId);
commit(types.SET_CONTACT_ITEM, response.data);
} catch (error) {
throw new Error(error);
} finally {
commit(types.SET_CONTACT_UI_FLAG, { isMerging: false });
}
},
deleteContactThroughConversations: ({ commit }, id) => { deleteContactThroughConversations: ({ commit }, id) => {
commit(types.DELETE_CONTACT, id); commit(types.DELETE_CONTACT, id);
commit(types.CLEAR_CONTACT_CONVERSATIONS, id, { root: true }); commit(types.CLEAR_CONTACT_CONVERSATIONS, id, { root: true });

View file

@ -13,6 +13,7 @@ const state = {
isFetchingItem: false, isFetchingItem: false,
isFetchingInboxes: false, isFetchingInboxes: false,
isUpdating: false, isUpdating: false,
isMerging: false,
isDeleting: false, isDeleting: false,
}, },
sortOrder: [], sortOrder: [],

View file

@ -168,6 +168,30 @@ describe('#actions', () => {
}); });
}); });
describe('#merge', () => {
it('sends correct mutations if API is success', async () => {
axios.post.mockResolvedValue({
data: contactList[0],
});
await actions.merge({ commit }, { childId: 0, parentId: 1 });
expect(commit.mock.calls).toEqual([
[types.SET_CONTACT_UI_FLAG, { isMerging: true }],
[types.SET_CONTACT_ITEM, contactList[0]],
[types.SET_CONTACT_UI_FLAG, { isMerging: false }],
]);
});
it('sends correct actions if API is error', async () => {
axios.post.mockRejectedValue({ message: 'Incorrect header' });
await expect(
actions.merge({ commit }, { childId: 0, parentId: 1 })
).rejects.toThrow(Error);
expect(commit.mock.calls).toEqual([
[types.SET_CONTACT_UI_FLAG, { isMerging: true }],
[types.SET_CONTACT_UI_FLAG, { isMerging: false }],
]);
});
});
describe('#deleteContactThroughConversations', () => { describe('#deleteContactThroughConversations', () => {
it('returns correct mutations', () => { it('returns correct mutations', () => {
actions.deleteContactThroughConversations({ commit }, contactList[0].id); actions.deleteContactThroughConversations({ commit }, contactList[0].id);