summaryrefslogtreecommitdiff
path: root/addons/im_livechat/static/src
diff options
context:
space:
mode:
authorSébastien Theys <seb@odoo.com>2020-08-24 10:37:47 +0000
committerSébastien Theys <seb@odoo.com>2020-08-24 13:21:45 +0000
commit72b9335334c076d8deaa36e404feaa3f4d30c351 (patch)
treeaea8c816b33d89b409fedbc75bcca3d2d6597717 /addons/im_livechat/static/src
parent0834d241624330ab3fd3925366a3217fe8145dfc (diff)
[FIX] mail, im_livechat: make livechat work properly
- fix traceback on receiving first livechat message due to bad computation of correspondent - fix missing [0] in return of country name_get in channel_info - fix visitor/correspondent computation to work when chatting with oneself - reintroduce livechat ordered by last message in discuss sidebar - improve mock server to make these fixes possible to test - introduce corresponding tests PR: #56243 task-2323670 closes odoo/odoo#56414 X-original-commit: f623bfd2c8819604c709bc723153c071dca6e3cc Signed-off-by: Alexandre Kühn (aku) <aku@odoo.com>
Diffstat (limited to 'addons/im_livechat/static/src')
-rw-r--r--addons/im_livechat/static/src/components/discuss/discuss_tests.js356
-rw-r--r--addons/im_livechat/static/src/components/discuss_sidebar/discuss_sidebar.js27
-rw-r--r--addons/im_livechat/static/src/components/thread_icon/thread_icon_tests.js11
-rw-r--r--addons/im_livechat/static/src/components/thread_textual_typing_status/thread_textual_typing_status_tests.js11
-rw-r--r--addons/im_livechat/static/src/models/thread/thread.js28
5 files changed, 406 insertions, 27 deletions
diff --git a/addons/im_livechat/static/src/components/discuss/discuss_tests.js b/addons/im_livechat/static/src/components/discuss/discuss_tests.js
index 22f4c9a235c..166e4fd679f 100644
--- a/addons/im_livechat/static/src/components/discuss/discuss_tests.js
+++ b/addons/im_livechat/static/src/components/discuss/discuss_tests.js
@@ -1,7 +1,13 @@
odoo.define('im_livechat/static/src/components/discuss/discuss_tests.js', function (require) {
'use strict';
-const { afterEach, beforeEach, start } = require('mail/static/src/utils/test_utils.js');
+const {
+ afterEach,
+ afterNextRender,
+ beforeEach,
+ nextAnimationFrame,
+ start,
+} = require('mail/static/src/utils/test_utils.js');
QUnit.module('im_livechat', {}, function () {
QUnit.module('components', {}, function () {
@@ -28,13 +34,12 @@ QUnit.module('discuss_tests.js', {
QUnit.test('livechat in the sidebar: basic rendering', async function (assert) {
assert.expect(5);
- // channel that is expected to be found in the sidebar
this.data['mail.channel'].records.push({
- channel_type: 'livechat', // channel is expected to be livechat
- id: 11, // random unique id, will be referenced in the test
- livechat_visitor: {
- name: "Visitor 11", // random name, will be asserted during the test
- },
+ anonymous_name: "Visitor 11",
+ channel_type: 'livechat',
+ id: 11,
+ livechat_operator_id: this.data.currentPartnerId,
+ members: [this.data.currentPartnerId, this.data.publicPartnerId],
});
await this.start();
assert.containsOnce(document.body, '.o_Discuss_sidebar',
@@ -69,6 +74,343 @@ QUnit.test('livechat in the sidebar: basic rendering', async function (assert) {
);
});
+QUnit.test('livechat in the sidebar: existing user with country', async function (assert) {
+ assert.expect(3);
+
+ this.data['res.country'].records.push({
+ code: 'be',
+ id: 10,
+ name: "Belgium",
+ });
+ this.data['res.partner'].records.push({
+ country_id: 10,
+ id: 10,
+ name: "Jean",
+ });
+ this.data['mail.channel'].records.push({
+ channel_type: 'livechat',
+ id: 11,
+ livechat_operator_id: this.data.currentPartnerId,
+ members: [this.data.currentPartnerId, 10],
+ });
+ await this.start();
+ assert.containsOnce(
+ document.body,
+ '.o_DiscussSidebar_groupLivechat',
+ "should have a channel group livechat in the side bar"
+ );
+ const livechat = document.querySelector('.o_DiscussSidebar_groupLivechat .o_DiscussSidebarItem');
+ assert.ok(
+ livechat,
+ "should have a livechat in sidebar"
+ );
+ assert.strictEqual(
+ livechat.textContent,
+ "Jean (Belgium)",
+ "should have user name and country as livechat name"
+ );
+});
+
+QUnit.test('do not add livechat in the sidebar on visitor opening his chat', async function (assert) {
+ assert.expect(2);
+
+ const currentUser = this.data['res.users'].records.find(user =>
+ user.id === this.data.currentUserId
+ );
+ currentUser.im_status = 'online';
+ this.data['im_livechat.channel'].records.push({
+ id: 10,
+ user_ids: [this.data.currentUserId],
+ });
+ await this.start();
+ assert.containsNone(
+ document.body,
+ '.o_DiscussSidebar_groupLivechat',
+ "should not have any livechat in the sidebar initially"
+ );
+
+ // simulate livechat visitor opening his chat
+ await this.env.services.rpc({
+ route: '/im_livechat/get_session',
+ params: {
+ context: {
+ mockedUserId: false,
+ },
+ channel_id: 10,
+ },
+ });
+ await nextAnimationFrame();
+ assert.containsNone(
+ document.body,
+ '.o_DiscussSidebar_groupLivechat',
+ "should still not have any livechat in the sidebar after visitor opened his chat"
+ );
+});
+
+QUnit.test('do not add livechat in the sidebar on visitor typing', async function (assert) {
+ assert.expect(2);
+
+ const currentUser = this.data['res.users'].records.find(user =>
+ user.id === this.data.currentUserId
+ );
+ currentUser.im_status = 'online';
+ this.data['im_livechat.channel'].records.push({
+ id: 10,
+ user_ids: [this.data.currentUserId],
+ });
+ this.data['mail.channel'].records.push({
+ channel_type: 'livechat',
+ id: 10,
+ is_pinned: false,
+ livechat_channel_id: 10,
+ livechat_operator_id: this.data.currentPartnerId,
+ members: [this.data.publicPartnerId, this.data.currentPartnerId],
+ });
+ await this.start();
+ assert.containsNone(
+ document.body,
+ '.o_DiscussSidebar_groupLivechat',
+ "should not have any livechat in the sidebar initially"
+ );
+
+ // simulate livechat visitor typing
+ const channel = this.data['mail.channel'].records.find(channel => channel.id === 10);
+ await this.env.services.rpc({
+ route: '/im_livechat/notify_typing',
+ params: {
+ context: {
+ mockedPartnerId: this.publicPartnerId,
+ },
+ is_typing: true,
+ uuid: channel.uuid,
+ },
+ });
+ await nextAnimationFrame();
+ assert.containsNone(
+ document.body,
+ '.o_DiscussSidebar_groupLivechat',
+ "should still not have any livechat in the sidebar after visitor started typing"
+ );
+});
+
+QUnit.test('add livechat in the sidebar on visitor sending first message', async function (assert) {
+ assert.expect(4);
+
+ const currentUser = this.data['res.users'].records.find(user =>
+ user.id === this.data.currentUserId
+ );
+ currentUser.im_status = 'online';
+ this.data['res.country'].records.push({
+ code: 'be',
+ id: 10,
+ name: "Belgium",
+ });
+ this.data['im_livechat.channel'].records.push({
+ id: 10,
+ user_ids: [this.data.currentUserId],
+ });
+ this.data['mail.channel'].records.push({
+ anonymous_name: "Visitor (Belgium)",
+ channel_type: 'livechat',
+ country_id: 10,
+ id: 10,
+ is_pinned: false,
+ livechat_channel_id: 10,
+ livechat_operator_id: this.data.currentPartnerId,
+ members: [this.data.publicPartnerId, this.data.currentPartnerId],
+ });
+ await this.start();
+ assert.containsNone(
+ document.body,
+ '.o_DiscussSidebar_groupLivechat',
+ "should not have any livechat in the sidebar initially"
+ );
+
+ // simulate livechat visitor sending a message
+ const channel = this.data['mail.channel'].records.find(channel => channel.id === 10);
+ await afterNextRender(async () => this.env.services.rpc({
+ route: '/mail/chat_post',
+ params: {
+ context: {
+ mockedUserId: false,
+ },
+ uuid: channel.uuid,
+ message_content: "new message",
+ },
+ }));
+ assert.containsOnce(
+ document.body,
+ '.o_DiscussSidebar_groupLivechat',
+ "should have a channel group livechat in the side bar after receiving first message"
+ );
+ assert.containsOnce(
+ document.body,
+ '.o_DiscussSidebar_groupLivechat .o_DiscussSidebar_item',
+ "should have a livechat in the sidebar after receiving first message"
+ );
+ assert.strictEqual(
+ document.querySelector('.o_DiscussSidebar_groupLivechat .o_DiscussSidebar_item').textContent,
+ "Visitor (Belgium)",
+ "should have visitor name and country as livechat name"
+ );
+});
+
+QUnit.test('livechats are sorted by last message date in the sidebar: most recent at the top', async function (assert) {
+ /**
+ * For simplicity the code that is covered in this test is considering
+ * messages to be more/less recent than others based on their ids instead of
+ * their actual creation date.
+ */
+ assert.expect(7);
+
+ this.data['mail.message'].records.push(
+ { id: 11, channel_ids: [11] }, // least recent message due to smaller id
+ { id: 12, channel_ids: [12] }, // most recent message due to higher id
+ );
+ this.data['mail.channel'].records.push(
+ {
+ anonymous_name: "Visitor 11",
+ channel_type: 'livechat',
+ id: 11,
+ livechat_operator_id: this.data.currentPartnerId,
+ members: [this.data.currentPartnerId, this.data.publicPartnerId],
+ },
+ {
+ anonymous_name: "Visitor 12",
+ channel_type: 'livechat',
+ id: 12,
+ livechat_operator_id: this.data.currentPartnerId,
+ members: [this.data.currentPartnerId, this.data.publicPartnerId],
+ },
+ );
+ await this.start();
+ const livechat11 = this.env.models['mail.thread'].findFromIdentifyingData({
+ id: 11,
+ model: 'mail.channel',
+ });
+ const livechat12 = this.env.models['mail.thread'].findFromIdentifyingData({
+ id: 12,
+ model: 'mail.channel',
+ });
+ assert.containsOnce(
+ document.body,
+ '.o_DiscussSidebar_groupLivechat',
+ "should have a channel group livechat"
+ );
+ const initialLivechats = document.querySelectorAll('.o_DiscussSidebar_groupLivechat .o_DiscussSidebarItem');
+ assert.strictEqual(
+ initialLivechats.length,
+ 2,
+ "should have 2 livechats in the sidebar"
+ );
+ assert.strictEqual(
+ initialLivechats[0].dataset.threadLocalId,
+ livechat12.localId,
+ "first livechat should be the one with the most recent message"
+ );
+ assert.strictEqual(
+ initialLivechats[1].dataset.threadLocalId,
+ livechat11.localId,
+ "second livechat should be the one with the least recent message"
+ );
+
+ // post a new message on the last channel
+ await afterNextRender(() => initialLivechats[1].click());
+ await afterNextRender(() => document.execCommand('insertText', false, "Blabla"));
+ await afterNextRender(() => document.querySelector('.o_Composer_buttonSend').click());
+ const livechats = document.querySelectorAll('.o_DiscussSidebar_groupLivechat .o_DiscussSidebarItem');
+ assert.strictEqual(
+ livechats.length,
+ 2,
+ "should still have 2 livechats in the sidebar after posting a new message"
+ );
+ assert.strictEqual(
+ livechats[0].dataset.threadLocalId,
+ livechat11.localId,
+ "first livechat should now be the one on which the new message was posted"
+ );
+ assert.strictEqual(
+ livechats[1].dataset.threadLocalId,
+ livechat12.localId,
+ "second livechat should now be the one on which the message was not posted"
+ );
+});
+
+QUnit.test('livechats with no messages are sorted by creation date in the sidebar: most recent at the top', async function (assert) {
+ /**
+ * For simplicity the code that is covered in this test is considering
+ * channels to be more/less recent than others based on their ids instead of
+ * their actual creation date.
+ */
+ assert.expect(5);
+
+ this.data['mail.message'].records.push(
+ { id: 13, channel_ids: [13] },
+ );
+ this.data['mail.channel'].records.push(
+ {
+ anonymous_name: "Visitor 11",
+ channel_type: 'livechat',
+ id: 11, // least recent channel due to smallest id
+ livechat_operator_id: this.data.currentPartnerId,
+ members: [this.data.currentPartnerId, this.data.publicPartnerId],
+ },
+ {
+ anonymous_name: "Visitor 12",
+ channel_type: 'livechat',
+ id: 12, // most recent channel that does not have a message
+ livechat_operator_id: this.data.currentPartnerId,
+ members: [this.data.currentPartnerId, this.data.publicPartnerId],
+ },
+ {
+ anonymous_name: "Visitor 13",
+ channel_type: 'livechat',
+ id: 13, // most recent channel (but it has a message)
+ livechat_operator_id: this.data.currentPartnerId,
+ members: [this.data.currentPartnerId, this.data.publicPartnerId],
+ },
+ );
+ await this.start();
+ const livechat11 = this.env.models['mail.thread'].findFromIdentifyingData({
+ id: 11,
+ model: 'mail.channel',
+ });
+ const livechat12 = this.env.models['mail.thread'].findFromIdentifyingData({
+ id: 12,
+ model: 'mail.channel',
+ });
+ const livechat13 = this.env.models['mail.thread'].findFromIdentifyingData({
+ id: 13,
+ model: 'mail.channel',
+ });
+ assert.containsOnce(
+ document.body,
+ '.o_DiscussSidebar_groupLivechat',
+ "should have a channel group livechat"
+ );
+ const initialLivechats = document.querySelectorAll('.o_DiscussSidebar_groupLivechat .o_DiscussSidebarItem');
+ assert.strictEqual(
+ initialLivechats.length,
+ 3,
+ "should have 3 livechats in the sidebar"
+ );
+ assert.strictEqual(
+ initialLivechats[0].dataset.threadLocalId,
+ livechat12.localId,
+ "first livechat should be the most recent channel without message"
+ );
+ assert.strictEqual(
+ initialLivechats[1].dataset.threadLocalId,
+ livechat11.localId,
+ "second livechat should be the second most recent channel without message"
+ );
+ assert.strictEqual(
+ initialLivechats[2].dataset.threadLocalId,
+ livechat13.localId,
+ "third livechat should be the channel with a message"
+ );
+});
+
});
});
});
diff --git a/addons/im_livechat/static/src/components/discuss_sidebar/discuss_sidebar.js b/addons/im_livechat/static/src/components/discuss_sidebar/discuss_sidebar.js
index 36656c877c7..2dafeed4c79 100644
--- a/addons/im_livechat/static/src/components/discuss_sidebar/discuss_sidebar.js
+++ b/addons/im_livechat/static/src/components/discuss_sidebar/discuss_sidebar.js
@@ -19,11 +19,27 @@ patch(components.DiscussSidebar, 'im_livechat/static/src/components/discuss_side
* @returns {mail.thread[]}
*/
quickSearchOrderedAndPinnedLivechatList() {
- const allOrderedAndPinnedLivechats = this.env.models['mail.thread'].all(thread =>
- thread.channel_type === 'livechat' &&
- thread.isPinned &&
- thread.model === 'mail.channel'
- );
+ const allOrderedAndPinnedLivechats = this.env.models['mail.thread']
+ .all(thread =>
+ thread.channel_type === 'livechat' &&
+ thread.isPinned &&
+ thread.model === 'mail.channel'
+ ).sort((c1, c2) => {
+ // sort by: last message id (desc), id (desc)
+ if (c1.lastMessage && c2.lastMessage) {
+ return c2.lastMessage.id - c1.lastMessage.id;
+ }
+ // a channel without a last message is assumed to be a new
+ // channel just created with the intent of posting a new
+ // message on it, in which case it should be moved up.
+ if (!c1.lastMessage) {
+ return -1;
+ }
+ if (!c2.lastMessage) {
+ return 1;
+ }
+ return c2.id - c1.id;
+ });
if (!this.discuss.sidebarQuickSearchValue) {
return allOrderedAndPinnedLivechats;
}
@@ -59,7 +75,6 @@ patch(components.DiscussSidebar, 'im_livechat/static/src/components/discuss_side
thread.isPinned &&
thread.model === 'mail.channel'
)
- .sort((c1, c2) => c1.displayName < c2.displayName ? -1 : 1)
.map(livechat => livechat.__state),
}
);
diff --git a/addons/im_livechat/static/src/components/thread_icon/thread_icon_tests.js b/addons/im_livechat/static/src/components/thread_icon/thread_icon_tests.js
index 32fb4583108..6ff7767af25 100644
--- a/addons/im_livechat/static/src/components/thread_icon/thread_icon_tests.js
+++ b/addons/im_livechat/static/src/components/thread_icon/thread_icon_tests.js
@@ -42,13 +42,12 @@ QUnit.module('thread_icon_tests.js', {
QUnit.test('livechat: public website visitor is typing', async function (assert) {
assert.expect(4);
- // channel that will be used for testing the typing feature
this.data['mail.channel'].records.push({
- channel_type: 'livechat', // channel is expected to be livechat
- id: 20, // random unique id, will be referenced in the test
- livechat_visitor: {
- name: "Visitor 20", // random name, will be asserted during the test
- },
+ anonymous_name: "Visitor 20",
+ channel_type: 'livechat',
+ id: 20,
+ livechat_operator_id: this.data.currentPartnerId,
+ members: [this.data.currentPartnerId, this.data.publicPartnerId],
});
await this.start();
const thread = this.env.models['mail.thread'].find(thread =>
diff --git a/addons/im_livechat/static/src/components/thread_textual_typing_status/thread_textual_typing_status_tests.js b/addons/im_livechat/static/src/components/thread_textual_typing_status/thread_textual_typing_status_tests.js
index e085c03b8d7..b3cbe96e277 100644
--- a/addons/im_livechat/static/src/components/thread_textual_typing_status/thread_textual_typing_status_tests.js
+++ b/addons/im_livechat/static/src/components/thread_textual_typing_status/thread_textual_typing_status_tests.js
@@ -42,15 +42,12 @@ QUnit.module('thread_textual_typing_status_tests.js', {
QUnit.test('receive visitor typing status "is typing"', async function (assert) {
assert.expect(2);
- // channel that will be used for testing the typing feature
this.data['mail.channel'].records.push({
- // channel is expected to be livechat, but channel_type set only for
- // consistency, not actually useful in the scope of this test
+ anonymous_name: "Visitor 20",
channel_type: 'livechat',
- id: 20, // random unique id, will be referenced in the test
- livechat_visitor: {
- name: "Visitor 20", // random name, will be asserted during the test
- },
+ id: 20,
+ livechat_operator_id: this.data.currentPartnerId,
+ members: [this.data.currentPartnerId, this.data.publicPartnerId],
});
await this.start();
const thread = this.env.models['mail.thread'].find(thread =>
diff --git a/addons/im_livechat/static/src/models/thread/thread.js b/addons/im_livechat/static/src/models/thread/thread.js
index 148e7b7c5ee..190a2088066 100644
--- a/addons/im_livechat/static/src/models/thread/thread.js
+++ b/addons/im_livechat/static/src/models/thread/thread.js
@@ -17,12 +17,35 @@ registerClassPatchModel('mail.thread', 'im_livechat/static/src/models/thread/thr
*/
convertData(data) {
const data2 = this._super(data);
+ if ('last_message_id' in data) {
+ if (!data2.messagesAsServerChannel) {
+ data2.messagesAsServerChannel = [];
+ }
+ data2.messagesAsServerChannel.push(['insert', { id: data.last_message_id }]);
+ }
if ('livechat_visitor' in data && data.livechat_visitor) {
if (!data2.members) {
data2.members = [];
}
+ // `livechat_visitor` without `id` is the anonymous visitor.
if (!data.livechat_visitor.id) {
- // Create partner derived from public partner.
+ /**
+ * Create partner derived from public partner and replace the
+ * public partner.
+ *
+ * Indeed the anonymous visitor is registered as a member of the
+ * channel as the public partner in the database to avoid
+ * polluting the contact list with many temporary partners.
+ *
+ * But the issue with public partner is that it is the same
+ * record for every livechat, whereas every correspondent should
+ * actually have its own visitor name, typing status, etc.
+ *
+ * Due to JS being temporary by nature there is no such notion
+ * of polluting the database, it is therefore acceptable and
+ * easier to handle one temporary partner per channel.
+ */
+ data2.members.push(['unlink', this.env.messaging.publicPartner]);
const partner = this.env.models['mail.partner'].create(
Object.assign(
this.env.models['mail.partner'].convertData(data.livechat_visitor),
@@ -50,6 +73,9 @@ registerInstancePatchModel('mail.thread', 'im_livechat/static/src/models/thread/
*/
_computeDisplayName() {
if (this.channel_type === 'livechat' && this.correspondent) {
+ if (this.correspondent.country) {
+ return `${this.correspondent.nameOrDisplayName} (${this.correspondent.country.name})`;
+ }
return this.correspondent.nameOrDisplayName;
}
return this._super();