diff options
| author | Sébastien Theys <seb@odoo.com> | 2020-08-24 10:37:47 +0000 |
|---|---|---|
| committer | Sébastien Theys <seb@odoo.com> | 2020-08-24 13:21:45 +0000 |
| commit | 72b9335334c076d8deaa36e404feaa3f4d30c351 (patch) | |
| tree | aea8c816b33d89b409fedbc75bcca3d2d6597717 /addons/im_livechat/static/src | |
| parent | 0834d241624330ab3fd3925366a3217fe8145dfc (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')
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(); |
