diff --git a/app/javascript/widget/components/ChatMessage.vue b/app/javascript/widget/components/ChatMessage.vue
index f80933b83..9a23f1458 100755
--- a/app/javascript/widget/components/ChatMessage.vue
+++ b/app/javascript/widget/components/ChatMessage.vue
@@ -1,5 +1,9 @@
-
+
diff --git a/app/javascript/widget/components/UserMessage.vue b/app/javascript/widget/components/UserMessage.vue
index 81ae2edad..e6cebc8e2 100755
--- a/app/javascript/widget/components/UserMessage.vue
+++ b/app/javascript/widget/components/UserMessage.vue
@@ -1,7 +1,7 @@
@@ -15,8 +15,12 @@ export default {
UserMessageBubble,
},
props: {
- message: String,
avatarUrl: String,
+ message: String,
+ status: {
+ type: String,
+ default: '',
+ },
},
};
diff --git a/app/javascript/widget/components/UserMessageBubble.vue b/app/javascript/widget/components/UserMessageBubble.vue
index 977d78eff..1517f2995 100755
--- a/app/javascript/widget/components/UserMessageBubble.vue
+++ b/app/javascript/widget/components/UserMessageBubble.vue
@@ -1,7 +1,7 @@
@@ -16,10 +16,17 @@ export default {
...mapGetters({
widgetColor: 'appConfig/getWidgetColor',
}),
+ backgroundColor() {
+ return this.status !== 'in_progress' ? this.widgetColor : '#c0ccda';
+ },
},
mixins: [messageFormatterMixin],
props: {
message: String,
+ status: {
+ type: String,
+ default: '',
+ },
},
};
diff --git a/app/javascript/widget/helpers/uuid.js b/app/javascript/widget/helpers/uuid.js
new file mode 100644
index 000000000..740d1bc3a
--- /dev/null
+++ b/app/javascript/widget/helpers/uuid.js
@@ -0,0 +1,10 @@
+const getUuid = () =>
+ 'xxxxxxxx4xxx'.replace(/[xy]/g, c => {
+ // eslint-disable-next-line
+ const r = (Math.random() * 16) | 0;
+ // eslint-disable-next-line
+ const v = c === 'x' ? r : (r & 0x3) | 0x8;
+ return v.toString(16);
+ });
+
+export default getUuid;
diff --git a/app/javascript/widget/store/modules/conversation.js b/app/javascript/widget/store/modules/conversation.js
index bff606001..6f1f25953 100755
--- a/app/javascript/widget/store/modules/conversation.js
+++ b/app/javascript/widget/store/modules/conversation.js
@@ -1,6 +1,24 @@
/* eslint-disable no-param-reassign */
import Vue from 'vue';
import { sendMessageAPI, getConversationAPI } from 'widget/api/conversation';
+import { MESSAGE_TYPE } from 'widget/helpers/constants';
+import getUuid from '../../helpers/uuid';
+
+export const createTemporaryMessage = content => {
+ const timestamp = new Date().getTime();
+ return {
+ id: getUuid(),
+ content,
+ status: 'in_progress',
+ created_at: timestamp,
+ message_type: MESSAGE_TYPE.INCOMING,
+ };
+};
+
+export const findUndeliveredMessage = (messageInbox, { content }) =>
+ Object.values(messageInbox).filter(
+ message => message.content === content && message.status === 'in_progress'
+ );
export const DEFAULT_CONVERSATION = 'default';
const state = {
@@ -13,8 +31,9 @@ const getters = {
};
const actions = {
- sendMessage: async (_, params) => {
+ sendMessage: async ({ commit }, params) => {
const { content } = params;
+ commit('pushMessageToConversations', createTemporaryMessage(content));
await sendMessageAPI(content);
},
@@ -38,9 +57,27 @@ const mutations = {
},
pushMessageToConversations($state, message) {
- const { id } = message;
+ const { id, status, message_type: type } = message;
const messagesInbox = $state.conversations;
- Vue.set(messagesInbox, id, message);
+ const isMessageIncoming = type === MESSAGE_TYPE.INCOMING;
+ const isTemporaryMessage = status === 'in_progress';
+
+ if (!isMessageIncoming || isTemporaryMessage) {
+ Vue.set(messagesInbox, id, message);
+ return;
+ }
+
+ const [messageInConversation] = findUndeliveredMessage(
+ messagesInbox,
+ message
+ );
+
+ if (!messageInConversation) {
+ Vue.set(messagesInbox, id, message);
+ } else {
+ Vue.delete(messagesInbox, messageInConversation.id);
+ Vue.set(messagesInbox, id, message);
+ }
},
initMessagesInConversation(_state, payload) {
diff --git a/app/javascript/widget/store/modules/specs/conversation.spec.js b/app/javascript/widget/store/modules/specs/conversation.spec.js
new file mode 100644
index 000000000..03a97618b
--- /dev/null
+++ b/app/javascript/widget/store/modules/specs/conversation.spec.js
@@ -0,0 +1,37 @@
+import {
+ findUndeliveredMessage,
+ createTemporaryMessage,
+} from '../conversation';
+
+describe('#findUndeliveredMessage', () => {
+ it('returns message objects if exist', () => {
+ const conversation = {
+ 1: {
+ id: 1,
+ content: 'Hello',
+ status: 'in_progress',
+ },
+ 2: {
+ id: 2,
+ content: 'Hello',
+ status: 'sent',
+ },
+ 3: {
+ id: 3,
+ content: 'How may I help you',
+ status: 'sent',
+ },
+ };
+ expect(
+ findUndeliveredMessage(conversation, { content: 'Hello' })
+ ).toStrictEqual([{ id: 1, content: 'Hello', status: 'in_progress' }]);
+ });
+});
+
+describe('#createTemporaryMessage', () => {
+ it('returns message object', () => {
+ const message = createTemporaryMessage('hello');
+ expect(message.content).toBe('hello');
+ expect(message.status).toBe('in_progress');
+ });
+});
diff --git a/jest.config.js b/jest.config.js
index 45f9a258e..1986065f4 100644
--- a/jest.config.js
+++ b/jest.config.js
@@ -2,15 +2,15 @@ process.env.VUE_CLI_BABEL_TARGET_NODE = true;
process.env.VUE_CLI_BABEL_TRANSPILE_MODULES = true;
module.exports = {
- moduleDirectories: ['node_modules', 'app/javascript/app'],
- moduleFileExtensions: ['js', 'jsx', 'json', 'vue', 'ts', 'tsx', 'vue'],
+ moduleDirectories: ['node_modules', 'app/javascript'],
+ moduleFileExtensions: ['js', 'jsx', 'json', 'vue', 'ts', 'tsx'],
automock: false,
resetMocks: true,
transform: {
'^.+\\.vue$': 'vue-jest',
- '.+\\.(css|styl|less|sass|scss|png|jpg|ttf|woff|woff2|svg)$':
+ '.+\\.(css|styl|less|sass|scss|png|jpg|ttf|woff|woff2)$':
'jest-transform-stub',
- '^.+\\.jsx?$': 'babel-jest',
+ '^.+\\.(js|jsx)?$': 'babel-jest',
},
cacheDirectory: '/.jest-cache',
collectCoverage: false,