From 463af6f61bb6e4398260a240942be8b9ae43e95e Mon Sep 17 00:00:00 2001 From: "Qiuyu (QHO)" Date: Wed, 25 Aug 2021 15:24:59 +0000 Subject: [PATCH] [IMP] mail, im_livechat: improve Discuss sidebar MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The commit is to refactor the discuss sidebar - threads are now organized in categories based on the thread type e.g. chat, channel - categories can be folded or unfolded by clicking the category title - for active thread, even if the category is folded, it remains under the category title - for channel category, a new cog button is added to view all channels - the active indicator bar is removed. The active item now is highlighted with a different background color - thread avatar is used for livechat, chat and channel - for livechat and chat, threads are now sorted by last activity time (pin or message exchange) closes odoo/odoo#70986 Task-id: 2440073 Signed-off-by: Sébastien Theys (seb) --- addons/im_livechat/__manifest__.py | 6 +- addons/im_livechat/models/__init__.py | 1 + .../im_livechat/models/res_users_settings.py | 10 + .../src/components/discuss/discuss_tests.js | 148 +--- .../discuss_sidebar/discuss_sidebar.js | 51 -- .../discuss_sidebar/discuss_sidebar.xml | 25 +- .../tests/discuss_sidebar_category_tests.js | 490 ++++++++++ .../discuss_sidebar_category_item_tests.js | 100 +++ .../discuss_sidebar_item.js | 23 - .../static/src/models/discuss/discuss.js | 26 + .../discuss_sidebar_category_item.js | 54 ++ .../messaging_initializer.js | 22 +- .../messaging_notification_handler.js | 14 + .../static/tests/helpers/mock_models.js | 3 + addons/mail/__manifest__.py | 1 + addons/mail/models/__init__.py | 1 + addons/mail/models/mail_channel.py | 26 +- addons/mail/models/mail_channel_partner.py | 1 + addons/mail/models/res_users.py | 2 + addons/mail/models/res_users_settings.py | 40 + addons/mail/security/ir.model.access.csv | 2 + addons/mail/security/mail_security.xml | 22 + .../static/src/components/discuss/discuss.xml | 2 +- .../discuss/tests/discuss_pinned_tests.js | 20 +- .../discuss/tests/discuss_sidebar_tests.js | 18 +- .../components/discuss/tests/discuss_tests.js | 497 +++++------ .../discuss_sidebar/discuss_sidebar.js | 193 +--- .../discuss_sidebar/discuss_sidebar.scss | 72 +- .../discuss_sidebar/discuss_sidebar.xml | 91 +- .../discuss_sidebar_category.js | 28 + .../discuss_sidebar_category.scss | 101 +++ .../discuss_sidebar_category.xml | 58 ++ .../tests/discuss_sidebar_category_tests.js | 834 ++++++++++++++++++ .../discuss_sidebar_category_item.js | 28 + .../discuss_sidebar_category_item.scss | 125 +++ .../discuss_sidebar_category_item.xml | 57 ++ .../discuss_sidebar_category_item_tests.js | 159 ++++ .../discuss_sidebar_item.js | 183 ---- .../discuss_sidebar_item.scss | 109 --- .../discuss_sidebar_item.xml | 60 -- .../discuss_sidebar_mailbox.js | 29 + .../discuss_sidebar_mailbox.scss | 57 ++ .../discuss_sidebar_mailbox.xml | 22 + addons/mail/static/src/js/tours/mail.js | 14 +- .../mail/static/src/models/discuss/discuss.js | 24 + .../discuss_sidebar_category.js | 411 +++++++++ .../discuss_sidebar_category_item.js | 347 ++++++++ .../messaging_initializer.js | 42 +- .../messaging_notification_handler.js | 43 + .../mail/static/src/models/thread/thread.js | 26 + addons/mail/static/src/models/user/user.js | 4 + .../src/scss/variables/primary_variables.scss | 4 + .../mail/static/tests/helpers/mock_models.js | 14 + .../mail/static/tests/helpers/mock_server.js | 79 +- addons/mail/tests/__init__.py | 1 + addons/mail/tests/test_mail_channel.py | 40 +- addons/mail/tests/test_res_users_settings.py | 55 ++ addons/mail/views/mail_menus.xml | 6 + .../mail/views/res_users_settings_views.xml | 22 + .../tests/test_performance.py | 67 +- 60 files changed, 3803 insertions(+), 1207 deletions(-) create mode 100644 addons/im_livechat/models/res_users_settings.py delete mode 100644 addons/im_livechat/static/src/components/discuss_sidebar/discuss_sidebar.js create mode 100644 addons/im_livechat/static/src/components/discuss_sidebar_category/tests/discuss_sidebar_category_tests.js create mode 100644 addons/im_livechat/static/src/components/discuss_sidebar_category_item/tests/discuss_sidebar_category_item_tests.js delete mode 100644 addons/im_livechat/static/src/components/discuss_sidebar_item/discuss_sidebar_item.js create mode 100644 addons/im_livechat/static/src/models/discuss/discuss.js create mode 100644 addons/im_livechat/static/src/models/discuss_sidebar_category_item/discuss_sidebar_category_item.js create mode 100644 addons/mail/models/res_users_settings.py create mode 100644 addons/mail/static/src/components/discuss_sidebar_category/discuss_sidebar_category.js create mode 100644 addons/mail/static/src/components/discuss_sidebar_category/discuss_sidebar_category.scss create mode 100644 addons/mail/static/src/components/discuss_sidebar_category/discuss_sidebar_category.xml create mode 100644 addons/mail/static/src/components/discuss_sidebar_category/tests/discuss_sidebar_category_tests.js create mode 100644 addons/mail/static/src/components/discuss_sidebar_category_item/discuss_sidebar_category_item.js create mode 100644 addons/mail/static/src/components/discuss_sidebar_category_item/discuss_sidebar_category_item.scss create mode 100644 addons/mail/static/src/components/discuss_sidebar_category_item/discuss_sidebar_category_item.xml create mode 100644 addons/mail/static/src/components/discuss_sidebar_category_item/tests/discuss_sidebar_category_item_tests.js delete mode 100644 addons/mail/static/src/components/discuss_sidebar_item/discuss_sidebar_item.js delete mode 100644 addons/mail/static/src/components/discuss_sidebar_item/discuss_sidebar_item.scss delete mode 100644 addons/mail/static/src/components/discuss_sidebar_item/discuss_sidebar_item.xml create mode 100644 addons/mail/static/src/components/discuss_sidebar_mailbox/discuss_sidebar_mailbox.js create mode 100644 addons/mail/static/src/components/discuss_sidebar_mailbox/discuss_sidebar_mailbox.scss create mode 100644 addons/mail/static/src/components/discuss_sidebar_mailbox/discuss_sidebar_mailbox.xml create mode 100644 addons/mail/static/src/models/discuss_sidebar_category/discuss_sidebar_category.js create mode 100644 addons/mail/static/src/models/discuss_sidebar_category_item/discuss_sidebar_category_item.js create mode 100644 addons/mail/tests/test_res_users_settings.py create mode 100644 addons/mail/views/res_users_settings_views.xml diff --git a/addons/im_livechat/__manifest__.py b/addons/im_livechat/__manifest__.py index 2cd4c10a2a5..fcd5441fb60 100644 --- a/addons/im_livechat/__manifest__.py +++ b/addons/im_livechat/__manifest__.py @@ -47,11 +47,11 @@ Help your customers with this chat, and analyse their feedback. 'im_livechat/static/src/js/im_livechat_channel_form_view.js', 'im_livechat/static/src/js/im_livechat_channel_form_controller.js', 'im_livechat/static/src/components/discuss/discuss.js', - 'im_livechat/static/src/components/discuss_sidebar/discuss_sidebar.js', - 'im_livechat/static/src/components/discuss_sidebar_item/discuss_sidebar_item.js', 'im_livechat/static/src/components/notification_list/notification_list.js', 'im_livechat/static/src/components/thread_needaction_preview/thread_needaction_preview.js', 'im_livechat/static/src/components/thread_preview/thread_preview.js', + 'im_livechat/static/src/models/discuss/discuss.js', + 'im_livechat/static/src/models/discuss_sidebar_category_item/discuss_sidebar_category_item.js', 'im_livechat/static/src/models/chat_window/chat_window.js', 'im_livechat/static/src/models/messaging_initializer/messaging_initializer.js', 'im_livechat/static/src/models/messaging_notification_handler/messaging_notification_handler.js', @@ -61,6 +61,8 @@ Help your customers with this chat, and analyse their feedback. 'im_livechat/static/src/scss/im_livechat_form.scss', ], 'web.qunit_suite_tests': [ + 'im_livechat/static/src/components/discuss_sidebar_category/tests/discuss_sidebar_category_tests.js', + 'im_livechat/static/src/components/discuss_sidebar_category_item/tests/discuss_sidebar_category_item_tests.js', 'im_livechat/static/src/components/chat_window_manager/chat_window_manager_tests.js', 'im_livechat/static/src/components/composer/composer_tests.js', 'im_livechat/static/src/components/discuss/discuss_tests.js', diff --git a/addons/im_livechat/models/__init__.py b/addons/im_livechat/models/__init__.py index a22a22519cb..65478d25ced 100644 --- a/addons/im_livechat/models/__init__.py +++ b/addons/im_livechat/models/__init__.py @@ -5,5 +5,6 @@ from . import res_partner from . import im_livechat_channel from . import mail_channel from . import mail_channel_partner +from . import res_users_settings from . import rating from . import digest diff --git a/addons/im_livechat/models/res_users_settings.py b/addons/im_livechat/models/res_users_settings.py new file mode 100644 index 00000000000..3a26e1c6d6a --- /dev/null +++ b/addons/im_livechat/models/res_users_settings.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import fields, models + + +class ResUsersSettings(models.Model): + _inherit = 'res.users.settings' + + is_discuss_sidebar_category_livechat_open = fields.Boolean("Is category livechat open", default=True) 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 3214e7728d8..cd452a41898 100644 --- a/addons/im_livechat/static/src/components/discuss/discuss_tests.js +++ b/addons/im_livechat/static/src/components/discuss/discuss_tests.js @@ -8,6 +8,8 @@ import { start, } from '@mail/utils/test_utils'; +import { datetime_to_str } from 'web.time'; + QUnit.module('im_livechat', {}, function () { QUnit.module('components', {}, function () { QUnit.module('discuss', {}, function () { @@ -44,18 +46,18 @@ QUnit.test('livechat in the sidebar: basic rendering', async function (assert) { assert.containsOnce(document.body, '.o_Discuss_sidebar', "should have a sidebar section" ); - const groupLivechat = document.querySelector('.o_DiscussSidebar_groupLivechat'); + const groupLivechat = document.querySelector('.o_DiscussSidebar_categoryLivechat'); assert.ok(groupLivechat, "should have a channel group livechat" ); - const grouptitle = groupLivechat.querySelector('.o_DiscussSidebar_groupTitle'); + const titleText = groupLivechat.querySelector('.o_DiscussSidebarCategory_titleText'); assert.strictEqual( - grouptitle.textContent.trim(), + titleText.textContent.trim(), "Livechat", "should have a channel group named 'Livechat'" ); const livechat = groupLivechat.querySelector(` - .o_DiscussSidebarItem[data-thread-local-id="${ + .o_DiscussSidebarCategoryItem[data-thread-local-id="${ this.messaging.models['mail.thread'].findFromIdentifyingData({ id: 11, model: 'mail.channel', @@ -95,10 +97,10 @@ QUnit.test('livechat in the sidebar: existing user with country', async function await this.start(); assert.containsOnce( document.body, - '.o_DiscussSidebar_groupLivechat', + '.o_DiscussSidebar_categoryLivechat', "should have a channel group livechat in the side bar" ); - const livechat = document.querySelector('.o_DiscussSidebar_groupLivechat .o_DiscussSidebarItem'); + const livechat = document.querySelector('.o_DiscussSidebar_categoryLivechat .o_DiscussSidebarCategoryItem'); assert.ok( livechat, "should have a livechat in sidebar" @@ -124,7 +126,7 @@ QUnit.test('do not add livechat in the sidebar on visitor opening his chat', asy await this.start(); assert.containsNone( document.body, - '.o_DiscussSidebar_groupLivechat', + '.o_DiscussSidebar_categoryLivechat', "should not have any livechat in the sidebar initially" ); @@ -141,7 +143,7 @@ QUnit.test('do not add livechat in the sidebar on visitor opening his chat', asy await nextAnimationFrame(); assert.containsNone( document.body, - '.o_DiscussSidebar_groupLivechat', + '.o_DiscussSidebar_categoryLivechat', "should still not have any livechat in the sidebar after visitor opened his chat" ); }); @@ -168,7 +170,7 @@ QUnit.test('do not add livechat in the sidebar on visitor typing', async functio await this.start(); assert.containsNone( document.body, - '.o_DiscussSidebar_groupLivechat', + '.o_DiscussSidebar_categoryLivechat', "should not have any livechat in the sidebar initially" ); @@ -187,7 +189,7 @@ QUnit.test('do not add livechat in the sidebar on visitor typing', async functio await nextAnimationFrame(); assert.containsNone( document.body, - '.o_DiscussSidebar_groupLivechat', + '.o_DiscussSidebar_categoryLivechat', "should still not have any livechat in the sidebar after visitor started typing" ); }); @@ -221,7 +223,7 @@ QUnit.test('add livechat in the sidebar on visitor sending first message', async await this.start(); assert.containsNone( document.body, - '.o_DiscussSidebar_groupLivechat', + '.o_DiscussSidebar_categoryLivechat', "should not have any livechat in the sidebar initially" ); @@ -239,33 +241,24 @@ QUnit.test('add livechat in the sidebar on visitor sending first message', async })); assert.containsOnce( document.body, - '.o_DiscussSidebar_groupLivechat', + '.o_DiscussSidebar_categoryLivechat', "should have a channel group livechat in the side bar after receiving first message" ); assert.containsOnce( document.body, - '.o_DiscussSidebar_groupLivechat .o_DiscussSidebar_item', + '.o_DiscussSidebar_categoryLivechat .o_DiscussSidebarCategoryItem', "should have a livechat in the sidebar after receiving first message" ); assert.strictEqual( - document.querySelector('.o_DiscussSidebar_groupLivechat .o_DiscussSidebar_item').textContent, + document.querySelector('.o_DiscussSidebar_categoryLivechat .o_DiscussSidebarCategoryItem .o_DiscussSidebarCategoryItem_name').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); +QUnit.test('livechats are sorted by last activity time in the sidebar: most recent at the top', async function (assert) { + assert.expect(6); - this.data['mail.message'].records.push( - { id: 11, model: "mail.channel", res_id: 11 }, // least recent message due to smaller id - { id: 12, model: "mail.channel", res_id: 12 }, // most recent message due to higher id - ); this.data['mail.channel'].records.push( { anonymous_name: "Visitor 11", @@ -273,6 +266,7 @@ QUnit.test('livechats are sorted by last message date in the sidebar: most recen id: 11, livechat_operator_id: this.data.currentPartnerId, members: [this.data.currentPartnerId, this.data.publicPartnerId], + last_interest_dt: datetime_to_str(new Date(2021, 0, 1)), // less recent one }, { anonymous_name: "Visitor 12", @@ -280,6 +274,7 @@ QUnit.test('livechats are sorted by last message date in the sidebar: most recen id: 12, livechat_operator_id: this.data.currentPartnerId, members: [this.data.currentPartnerId, this.data.publicPartnerId], + last_interest_dt: datetime_to_str(new Date(2021, 0, 2)), // more recent one }, ); await this.start(); @@ -291,122 +286,43 @@ QUnit.test('livechats are sorted by last message date in the sidebar: most recen 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'); + const initialLivechats = document.querySelectorAll('.o_DiscussSidebar_categoryLivechat .o_DiscussSidebarCategoryItem'); assert.strictEqual( initialLivechats.length, 2, - "should have 2 livechats in the sidebar" + "should have 2 livechat items" ); assert.strictEqual( initialLivechats[0].dataset.threadLocalId, livechat12.localId, - "first livechat should be the one with the most recent message" + "first livechat should be the one with the more recent last activity time" ); assert.strictEqual( initialLivechats[1].dataset.threadLocalId, livechat11.localId, - "second livechat should be the one with the least recent message" + "second livechat should be the one with the less recent last activity time" ); // 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'); + + const newLivechats = document.querySelectorAll('.o_DiscussSidebar_categoryLivechat .o_DiscussSidebarCategoryItem'); assert.strictEqual( - livechats.length, + newLivechats.length, 2, - "should still have 2 livechats in the sidebar after posting a new message" + "should have 2 livechat items" ); assert.strictEqual( - livechats[0].dataset.threadLocalId, + newLivechats[0].dataset.threadLocalId, livechat11.localId, - "first livechat should now be the one on which the new message was posted" + "first livechat should be the one with the more recent last activity time" ); assert.strictEqual( - livechats[1].dataset.threadLocalId, + newLivechats[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, model: "mail.channel", res_id: 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.messaging.models['mail.thread'].findFromIdentifyingData({ - id: 11, - model: 'mail.channel', - }); - const livechat12 = this.messaging.models['mail.thread'].findFromIdentifyingData({ - id: 12, - model: 'mail.channel', - }); - const livechat13 = this.messaging.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" + "second livechat should be the one with the less recent last activity time" ); }); 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 deleted file mode 100644 index 20ca571ab60..00000000000 --- a/addons/im_livechat/static/src/components/discuss_sidebar/discuss_sidebar.js +++ /dev/null @@ -1,51 +0,0 @@ -/** @odoo-module **/ - -import { DiscussSidebar } from '@mail/components/discuss_sidebar/discuss_sidebar'; - -import { patch } from 'web.utils'; - -const components = { DiscussSidebar }; - -patch(components.DiscussSidebar.prototype, 'im_livechat/static/src/components/discuss_sidebar/discuss_sidebar.js', { - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - /** - * Return the list of livechats that match the quick search value input. - * - * @returns {mail.thread[]} - */ - quickSearchOrderedAndPinnedLivechatList() { - const allOrderedAndPinnedLivechats = this.messaging.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; - } - const qsVal = this.discuss.sidebarQuickSearchValue.toLowerCase(); - return allOrderedAndPinnedLivechats.filter(livechat => { - const nameVal = livechat.displayName.toLowerCase(); - return nameVal.includes(qsVal); - }); - }, -}); diff --git a/addons/im_livechat/static/src/components/discuss_sidebar/discuss_sidebar.xml b/addons/im_livechat/static/src/components/discuss_sidebar/discuss_sidebar.xml index 15c6fc35e4d..a1f0716e117 100644 --- a/addons/im_livechat/static/src/components/discuss_sidebar/discuss_sidebar.xml +++ b/addons/im_livechat/static/src/components/discuss_sidebar/discuss_sidebar.xml @@ -1,24 +1,13 @@ - - - -
-
-
- Livechat -
-
-
- - - -
-
+ + + +
diff --git a/addons/im_livechat/static/src/components/discuss_sidebar_category/tests/discuss_sidebar_category_tests.js b/addons/im_livechat/static/src/components/discuss_sidebar_category/tests/discuss_sidebar_category_tests.js new file mode 100644 index 00000000000..2474fdc8256 --- /dev/null +++ b/addons/im_livechat/static/src/components/discuss_sidebar_category/tests/discuss_sidebar_category_tests.js @@ -0,0 +1,490 @@ +/** @odoo-module **/ + +import { + afterEach, + afterNextRender, + beforeEach, + start, +} from '@mail/utils/test_utils'; + +QUnit.module('im_livechat', {}, function () { +QUnit.module('components', {}, function () { +QUnit.module('discuss_sidebar_category', {}, function () { +QUnit.module('discuss_sidebar_category_tests.js', { + beforeEach() { + beforeEach(this); + + this.start = async params => { + const { env, widget } = await start(Object.assign({}, params, { + autoOpenDiscuss: true, + data: this.data, + hasDiscuss: true, + })); + this.env = env; + this.widget = widget; + }; + }, + afterEach() { + afterEach(this); + }, +}); + +QUnit.test('livechat - counter: should not have a counter if the category is unfolded and without unread messages', async function (assert) { + assert.expect(1); + + 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], + }); + await this.start(); + assert.containsNone( + document.body, + `.o_DiscussSidebar_categoryLivechat .o_DiscussSidebarCategory_counter`, + "should not have a counter if the category is unfolded and without unread messages", + ); +}); + +QUnit.test('livechat - counter: should not have a counter if the category is unfolded and with unread messages', async function (assert) { + assert.expect(1); + + 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], + message_unread_counter: 10, + }); + await this.start(); + assert.containsNone( + document.body, + `.o_DiscussSidebar_categoryLivechat .o_DiscussSidebarCategory_counter`, + "should not have a counter if the category is unfolded and with unread messages", + ); +}); + +QUnit.test('livechat - counter: should not have a counter if category is folded and without unread messages', async function (assert) { + assert.expect(1); + + 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], + }); + this.data['res.users.settings'].records.push({ + user_id: this.data.currentUserId, + is_discuss_sidebar_category_livechat_open: false, + }); + await this.start(); + + assert.containsNone( + document.body, + `.o_DiscussSidebar_categoryLivechat .o_DiscussSidebarCategory_counter`, + "should not have a counter if the category is folded and without unread messages" + ); +}); + +QUnit.test('livechat - counter: should have correct value of unread threads if category is folded and with unread messages', async function (assert) { + assert.expect(1); + + 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], + message_unread_counter: 10, + }); + this.data['res.users.settings'].records.push({ + user_id: this.data.currentUserId, + is_discuss_sidebar_category_livechat_open: false, + }); + await this.start(); + + assert.strictEqual( + document.querySelector(`.o_DiscussSidebar_categoryLivechat .o_DiscussSidebarCategory_counter`).textContent, + "1", + "should have correct value of unread threads if category is folded and with unread messages" + ); +}); + +QUnit.test('livechat - states: close manually by clicking the title', async function (assert) { + assert.expect(2); + + 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], + }); + this.data['res.users.settings'].records.push({ + user_id: this.data.currentUserId, + is_discuss_sidebar_category_livechat_open: true, + }); + await this.start(); + + assert.containsOnce( + document.body, + `.o_DiscussSidebarCategoryItem[data-thread-local-id="${ + this.messaging.models['mail.thread'].findFromIdentifyingData({ + id: 11, + model: 'mail.channel', + }).localId + }"]` + ); + + // fold the livechat category + await afterNextRender(() => + document.querySelector(`.o_DiscussSidebarCategory[data-category-local-id="${ + this.messaging.discuss.categoryLivechat.localId}"] + .o_DiscussSidebarCategory_title + `).click() + ); + assert.containsNone( + document.body, + `.o_DiscussSidebarCategoryItem[data-thread-local-id="${ + this.messaging.models['mail.thread'].findFromIdentifyingData({ + id: 11, + model: 'mail.channel', + }).localId + }"]`, + "Category livechat should be closed and the content should be invisible" + ); +}); + +QUnit.test('livechat - states: open manually by clicking the title', async function (assert) { + assert.expect(2); + + 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], + }); + this.data['res.users.settings'].records.push({ + user_id: this.data.currentUserId, + is_discuss_sidebar_category_livechat_open: false, + }); + await this.start(); + + assert.containsNone( + document.body, + `.o_DiscussSidebarCategoryItem[data-thread-local-id="${ + this.messaging.models['mail.thread'].findFromIdentifyingData({ + id: 11, + model: 'mail.channel', + }).localId + }"]` + ); + + // open the livechat category + await afterNextRender(() => + document.querySelector(`.o_DiscussSidebarCategory[data-category-local-id="${ + this.messaging.discuss.categoryLivechat.localId}"] + .o_DiscussSidebarCategory_title + `).click() + ); + assert.containsOnce( + document.body, + `.o_DiscussSidebarCategoryItem[data-thread-local-id="${ + this.messaging.models['mail.thread'].findFromIdentifyingData({ + id: 11, + model: 'mail.channel', + }).localId + }"]`, + "Category livechat should be closed and the content should be invisible" + ); +}); + +QUnit.test('livechat - states: close should update the value on the server', async function (assert) { + assert.expect(2); + + 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], + }); + this.data['res.users.settings'].records.push({ + user_id: this.data.currentUserId, + is_discuss_sidebar_category_livechat_open: true, + }); + const currentUserId = this.data.currentUserId; + await this.start(); + + const initalSettings = await this.env.services.rpc({ + model: 'res.users.settings', + method: '_find_or_create_for_user', + args: [[currentUserId]], + }); + assert.strictEqual( + initalSettings.is_discuss_sidebar_category_livechat_open, + true, + "the value in server side should be true" + ); + + await afterNextRender(() => + document.querySelector(`.o_DiscussSidebarCategory[data-category-local-id="${ + this.messaging.discuss.categoryLivechat.localId}"] + .o_DiscussSidebarCategory_title + `).click() + ); + const newSettings = await this.env.services.rpc({ + model: 'res.users.settings', + method: '_find_or_create_for_user', + args: [[currentUserId]], + }); + assert.strictEqual( + newSettings.is_discuss_sidebar_category_livechat_open, + false, + "the value in server side should be false" + ); +}); + +QUnit.test('livechat - states: open should update the value on the server', async function (assert) { + assert.expect(2); + + 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], + }); + this.data['res.users.settings'].records.push({ + user_id: this.data.currentUserId, + is_discuss_sidebar_category_livechat_open: false, + }); + const currentUserId = this.data.currentUserId; + await this.start(); + + const initalSettings = await this.env.services.rpc({ + model: 'res.users.settings', + method: '_find_or_create_for_user', + args: [[currentUserId]], + }); + assert.strictEqual( + initalSettings.is_discuss_sidebar_category_livechat_open, + false, + "the value in server side should be false" + ); + + await afterNextRender(() => + document.querySelector(`.o_DiscussSidebarCategory[data-category-local-id="${ + this.messaging.discuss.categoryLivechat.localId}"] + .o_DiscussSidebarCategory_title + `).click() + ); + const newSettings = await this.env.services.rpc({ + model: 'res.users.settings', + method: '_find_or_create_for_user', + args: [[currentUserId]], + }); + assert.strictEqual( + newSettings.is_discuss_sidebar_category_livechat_open, + true, + "the value in server side should be true" + ); +}); + +QUnit.test('livechat - states: close from the bus', async function (assert) { + assert.expect(2); + + 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], + }); + await this.start(); + + assert.containsOnce( + document.body, + `.o_DiscussSidebarCategoryItem[data-thread-local-id="${ + this.messaging.models['mail.thread'].findFromIdentifyingData({ + id: 11, + model: 'mail.channel', + }).localId + }"]` + ); + + await afterNextRender(() => { + const notif = [ + ["dbName", "res.partner", this.messaging.currentPartner.id], + { + type: "res.users_settings_changed", + payload: { + is_discuss_sidebar_category_livechat_open: false, + }, + }, + ]; + this.env.services.bus_service.trigger('notification', [notif]); + }); + assert.containsNone( + document.body, + `.o_DiscussSidebarCategoryItem[data-thread-local-id="${ + this.messaging.models['mail.thread'].findFromIdentifyingData({ + id: 11, + model: 'mail.channel', + }).localId + }"]`, + "Category livechat should be closed and the content should be invisible" + ); +}); + +QUnit.test('livechat - states: open from the bus', async function (assert) { + assert.expect(2); + + 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], + }); + this.data['res.users.settings'].records.push({ + user_id: this.data.currentUserId, + is_discuss_sidebar_category_livechat_open: false, + }); + await this.start(); + + assert.containsNone( + document.body, + `.o_DiscussSidebarCategoryItem[data-thread-local-id="${ + this.messaging.models['mail.thread'].findFromIdentifyingData({ + id: 11, + model: 'mail.channel', + }).localId + }"]` + ); + + await afterNextRender(() => { + const notif = [ + ["dbName", "res.partner", this.messaging.currentPartner.id], + { + type: "res.users_settings_changed", + payload: { + is_discuss_sidebar_category_livechat_open: true, + }, + }, + ]; + this.env.services.bus_service.trigger('notification', [notif]); + }); + assert.containsOnce( + document.body, + `.o_DiscussSidebarCategoryItem[data-thread-local-id="${ + this.messaging.models['mail.thread'].findFromIdentifyingData({ + id: 11, + model: 'mail.channel', + }).localId + }"]`, + "Category livechat should be closed and the content should be invisible" + ); +}); + + +QUnit.test('livechat - states: category item should be invisible if the catgory is closed', async function (assert) { + assert.expect(2); + + 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], + }); + await this.start(); + + assert.containsOnce( + document.body, + `.o_DiscussSidebarCategoryItem[data-thread-local-id="${ + this.messaging.models['mail.thread'].findFromIdentifyingData({ + id: 11, + model: 'mail.channel', + }).localId + }"]` + ); + + await afterNextRender(() => + document.querySelector(`.o_DiscussSidebarCategory[data-category-local-id="${ + this.messaging.discuss.categoryLivechat.localId}"] + .o_DiscussSidebarCategory_title + `).click() + ); + + assert.containsNone( + document.body, + `.o_DiscussSidebarCategoryItem[data-thread-local-id="${ + this.messaging.models['mail.thread'].findFromIdentifyingData({ + id: 11, + model: 'mail.channel', + }).localId + }"]`, + "inactive item should be invisible if the category is folded" + ); +}); + +QUnit.test('livechat - states: the active category item should be visble even if the category is closed', async function (assert) { + assert.expect(3); + + 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], + }); + await this.start(); + + assert.containsOnce( + document.body, + `.o_DiscussSidebarCategoryItem[data-thread-local-id="${ + this.messaging.models['mail.thread'].findFromIdentifyingData({ + id: 11, + model: 'mail.channel', + }).localId + }"]` + ); + + const livechat = document.querySelector(`.o_DiscussSidebarCategoryItem[data-thread-local-id="${ + this.messaging.models['mail.thread'].findFromIdentifyingData({ + id: 11, + model: 'mail.channel', + }).localId + }"]`); + await afterNextRender(() => { + livechat.click(); + }); + assert.ok(livechat.classList.contains('o-active')); + + await afterNextRender(() => + document.querySelector(`.o_DiscussSidebarCategory[data-category-local-id="${ + this.messaging.discuss.categoryLivechat.localId}"] + .o_DiscussSidebarCategory_title + `).click() + ); + + assert.containsOnce( + document.body, + `.o_DiscussSidebarCategoryItem[data-thread-local-id="${ + this.messaging.models['mail.thread'].findFromIdentifyingData({ + id: 11, + model: 'mail.channel', + }).localId + }"]`, + 'the active livechat item should remain even if the category is folded' + ); +}); + +}); +}); +}); diff --git a/addons/im_livechat/static/src/components/discuss_sidebar_category_item/tests/discuss_sidebar_category_item_tests.js b/addons/im_livechat/static/src/components/discuss_sidebar_category_item/tests/discuss_sidebar_category_item_tests.js new file mode 100644 index 00000000000..7f51844051d --- /dev/null +++ b/addons/im_livechat/static/src/components/discuss_sidebar_category_item/tests/discuss_sidebar_category_item_tests.js @@ -0,0 +1,100 @@ +/** @odoo-module **/ + +import { + afterEach, + beforeEach, + start, +} from '@mail/utils/test_utils'; + +QUnit.module('im_livechat', {}, function () { +QUnit.module('components', {}, function () { +QUnit.module('discuss_sidebar_category_item', {}, function () { +QUnit.module('discuss_sidebar_category_item_tests.js', { + beforeEach() { + beforeEach(this); + + this.start = async params => { + const { env, widget } = await start(Object.assign({}, params, { + autoOpenDiscuss: true, + data: this.data, + hasDiscuss: true, + })); + this.env = env; + this.widget = widget; + }; + }, + afterEach() { + afterEach(this); + }, +}); + +QUnit.test('livechat - avatar: should have a smiley face avatar for an anonymous livechat item', async function (assert) { + assert.expect(2); + + 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.currentPartnerId], + }); + await this.start(); + + const livechatItem = document.querySelector(` + .o_DiscussSidebarCategoryItem[data-thread-local-id="${ + this.messaging.models['mail.thread'].findFromIdentifyingData({ + id: 11, + model: 'mail.channel', + }).localId + }"] + `); + assert.containsOnce( + livechatItem, + `.o_DiscussSidebarCategoryItem_image`, + "should have an avatar" + ); + assert.strictEqual( + livechatItem.querySelector(`:scope .o_DiscussSidebarCategoryItem_image`).dataset.src, + '/mail/static/src/img/smiley/avatar.jpg', + 'should have the smiley face as the avatar for anonymous users' + ); +}); + +QUnit.test('livechat - avatar: should have a partner profile picture for a livechat item linked with a partner', async function (assert) { + assert.expect(2); + + this.data['res.partner'].records.push({ + 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(); + + const livechatItem = document.querySelector(` + .o_DiscussSidebarCategoryItem[data-thread-local-id="${ + this.messaging.models['mail.thread'].findFromIdentifyingData({ + id: 11, + model: 'mail.channel', + }).localId + }"] + `); + assert.containsOnce( + livechatItem, + `.o_DiscussSidebarCategoryItem_image`, + "should have an avatar" + ); + assert.strictEqual( + livechatItem.querySelector(`:scope .o_DiscussSidebarCategoryItem_image`).dataset.src, + '/web/image/res.partner/10/avatar_128', + 'should have the partner profile picture as the avatar for partners' + ); +}); + +}); +}); +}); diff --git a/addons/im_livechat/static/src/components/discuss_sidebar_item/discuss_sidebar_item.js b/addons/im_livechat/static/src/components/discuss_sidebar_item/discuss_sidebar_item.js deleted file mode 100644 index 9cc8dbde071..00000000000 --- a/addons/im_livechat/static/src/components/discuss_sidebar_item/discuss_sidebar_item.js +++ /dev/null @@ -1,23 +0,0 @@ -/** @odoo-module **/ - -import { DiscussSidebarItem } from '@mail/components/discuss_sidebar_item/discuss_sidebar_item'; - -import { patch } from 'web.utils'; - -const components = { DiscussSidebarItem }; - -patch(components.DiscussSidebarItem.prototype, 'im_livechat/static/src/components/discuss_sidebar_item/discuss_sidebar_item.js', { - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - /** - * @override - */ - hasUnpin(...args) { - const res = this._super(...args); - return res || this.thread.channel_type === 'livechat'; - } - -}); diff --git a/addons/im_livechat/static/src/models/discuss/discuss.js b/addons/im_livechat/static/src/models/discuss/discuss.js new file mode 100644 index 00000000000..40a33e809f1 --- /dev/null +++ b/addons/im_livechat/static/src/models/discuss/discuss.js @@ -0,0 +1,26 @@ +/** @odoo-module **/ + +import { registerFieldPatchModel, registerInstancePatchModel } from '@mail/model/model_core'; +import { one2one } from '@mail/model/model_field'; + +registerInstancePatchModel('mail.discuss', 'im_livechat/static/src/models/discuss/discuss.js', { + + /** + * @override + */ + onInputQuickSearch(value) { + if (!this.sidebarQuickSearchValue) { + this.categoryLivechat.open(); + } + return this._super(value); + }, +}); + +registerFieldPatchModel('mail.discuss', 'im_livechat/static/src/models/discuss/discuss.js', { + /** + * Discuss sidebar category for `livechat` channel threads. + */ + categoryLivechat: one2one('mail.discuss_sidebar_category', { + isCausal: true, + }), +}); diff --git a/addons/im_livechat/static/src/models/discuss_sidebar_category_item/discuss_sidebar_category_item.js b/addons/im_livechat/static/src/models/discuss_sidebar_category_item/discuss_sidebar_category_item.js new file mode 100644 index 00000000000..dc4a8834c58 --- /dev/null +++ b/addons/im_livechat/static/src/models/discuss_sidebar_category_item/discuss_sidebar_category_item.js @@ -0,0 +1,54 @@ +/** @odoo-module **/ + +import { registerInstancePatchModel } from '@mail/model/model_core'; + +registerInstancePatchModel('mail.discuss_sidebar_category_item', 'im_livechat/static/src/models/discuss_sidebar_category_item/discuss_sidebar_category_item.js', { + + //---------------------------------------------------------------------- + // Private + //---------------------------------------------------------------------- + + /** + * @override + */ + _computeAvatarUrl() { + if (this.channelType === 'livechat') { + if (this.channel.correspondent && this.channel.correspondent.id > 0) { + return this.channel.correspondent.avatarUrl; + } + return '/mail/static/src/img/smiley/avatar.jpg'; + } + return this._super(); + }, + + /** + * @override + */ + _computeCounter() { + if (this.channelType === 'livechat') { + return this.channel.localMessageUnreadCounter; + } + return this._super(); + }, + + /** + * @override + */ + _computeHasUnpinCommand() { + if (this.channelType === 'livechat') { + return !this.channel.localMessageUnreadCounter; + } + return this._super(); + }, + + /** + * @override + */ + _computeHasThreadIcon() { + if (this.channelType === 'livechat') { + return false; + } + return this._super(); + }, + +}); diff --git a/addons/im_livechat/static/src/models/messaging_initializer/messaging_initializer.js b/addons/im_livechat/static/src/models/messaging_initializer/messaging_initializer.js index 27ef526720f..0b53a208090 100644 --- a/addons/im_livechat/static/src/models/messaging_initializer/messaging_initializer.js +++ b/addons/im_livechat/static/src/models/messaging_initializer/messaging_initializer.js @@ -1,11 +1,31 @@ /** @odoo-module **/ import { registerInstancePatchModel } from '@mail/model/model_core'; -import { insert } from '@mail/model/model_field_command'; +import { create, insert } from '@mail/model/model_field_command'; registerInstancePatchModel('mail.messaging_initializer', 'im_livechat/static/src/models/messaging_initializer/messaging_initializer.js', { /** * @override + * @param {Object} resUsersSettings + * @param {boolean} resUsersSettings.is_discuss_sidebar_category_livechat_open + */ + _initResUsersSettings({ is_discuss_sidebar_category_livechat_open }) { + this.messaging.discuss.update({ + categoryLivechat: create({ + counterComputeMethod: 'unread', + isServerOpen: is_discuss_sidebar_category_livechat_open, + name: this.env._t("Livechat"), + serverStateKey: 'is_discuss_sidebar_category_livechat_open', + sortComputeMethod: 'last_action', + supportedChannelTypes: ['livechat'], + }), + }); + this._super(...arguments); + }, + + /** + * @override + * @param {Object[]} [param0.channel_livechat=[]] */ _initCommands() { this._super(); diff --git a/addons/im_livechat/static/src/models/messaging_notification_handler/messaging_notification_handler.js b/addons/im_livechat/static/src/models/messaging_notification_handler/messaging_notification_handler.js index e3c90320bec..42b31397c2e 100644 --- a/addons/im_livechat/static/src/models/messaging_notification_handler/messaging_notification_handler.js +++ b/addons/im_livechat/static/src/models/messaging_notification_handler/messaging_notification_handler.js @@ -8,6 +8,20 @@ registerInstancePatchModel('mail.messaging_notification_handler', 'im_livechat/s // Private //---------------------------------------------------------------------- + /** + * @override + * @param {object} settings + * @param {boolean} [settings.is_discuss_sidebar_category_livechat_open] + */ + _handleNotificationResUsersSettings(settings) { + if ('is_discuss_sidebar_category_livechat_open' in settings) { + this.messaging.discuss.categoryLivechat.update({ + isServerOpen: settings.is_discuss_sidebar_category_livechat_open, + }); + } + this._super(settings); + }, + /** * @override */ diff --git a/addons/im_livechat/static/tests/helpers/mock_models.js b/addons/im_livechat/static/tests/helpers/mock_models.js index 6aa3cdfcb9b..7bb24c5f2cd 100644 --- a/addons/im_livechat/static/tests/helpers/mock_models.js +++ b/addons/im_livechat/static/tests/helpers/mock_models.js @@ -29,6 +29,9 @@ patch(MockModels, 'im_livechat/static/tests/helpers/mock_models.js', { livechat_channel_id: { string: "Channel", type: 'many2one', relation: 'im_livechat.channel' }, livechat_operator_id: { string: "Operator", type: 'many2one', relation: 'res.partner' }, }); + Object.assign(data['res.users.settings'].fields, { + is_discuss_sidebar_category_livechat_open: { string: "Is Discuss Sidebar Category Livechat Open?", type: 'boolean', default: true }, + }); Object.assign(data['res.users'].fields, { livechat_username: { string: 'Livechat Username', type: 'string' }, }); diff --git a/addons/mail/__manifest__.py b/addons/mail/__manifest__.py index 912f26d37f4..3a23aee1a4b 100644 --- a/addons/mail/__manifest__.py +++ b/addons/mail/__manifest__.py @@ -37,6 +37,7 @@ 'security/ir.model.access.csv', 'views/mail_alias_views.xml', 'views/res_users_views.xml', + 'views/res_users_settings_views.xml', 'views/mail_template_views.xml', 'views/ir_actions_server_views.xml', 'views/ir_model_views.xml', diff --git a/addons/mail/models/__init__.py b/addons/mail/models/__init__.py index 17e4a8346d9..e17d42bcaf6 100644 --- a/addons/mail/models/__init__.py +++ b/addons/mail/models/__init__.py @@ -30,6 +30,7 @@ from . import mail_template from . import mail_channel_partner from . import mail_channel from . import mail_shortcode +from . import res_users_settings # odoo models from . import ir_action_act_window diff --git a/addons/mail/models/mail_channel.py b/addons/mail/models/mail_channel.py index f67766d040d..e124b1c875b 100644 --- a/addons/mail/models/mail_channel.py +++ b/addons/mail/models/mail_channel.py @@ -8,6 +8,7 @@ from uuid import uuid4 from odoo import _, api, fields, models, modules, tools, Command from odoo.exceptions import UserError, ValidationError from odoo.osv import expression +from odoo.tools.misc import DEFAULT_SERVER_DATETIME_FORMAT _logger = logging.getLogger(__name__) @@ -421,6 +422,20 @@ class Channel(models.Model): message_format_values = message.message_format()[0] bus_notifications = self._channel_message_notifications(message, message_format_values) self.env['bus.bus'].sudo().sendmany(bus_notifications) + # Last interest is updated for a chat when posting a message. + # So a notification is needed to update UI. + if self.is_chat: + notifications = [] + for channel_partners in self.channel_last_seen_partner_ids: + notif = { + 'type': 'mail.channel_last_interest_dt_changed', + 'payload': { + 'id': self.id, + 'last_interest_dt': channel_partners.last_interest_dt, + } + } + notifications.append([(self._cr.dbname, 'res.partner', channel_partners.partner_id.id), notif]) + self.env['bus.bus'].sendmany(notifications) return rdata def _message_receive_bounce(self, email, partner): @@ -432,7 +447,10 @@ class Channel(models.Model): @api.returns('mail.message', lambda value: value.id) def message_post(self, *, message_type='notification', **kwargs): - self.filtered(lambda channel: channel.is_chat).mapped('channel_last_seen_partner_ids').sudo().write({'is_pinned': True}) + self.filtered(lambda channel: channel.is_chat).mapped('channel_last_seen_partner_ids').sudo().write({ + 'is_pinned': True, + 'last_interest_dt': fields.Datetime.now(), + }) # mail_post_autofollow=False is necessary to prevent adding followers # when using mentions in channels. Followers should not be added to @@ -549,6 +567,7 @@ class Channel(models.Model): info['seen_message_id'] = partner_channel.seen_message_id.id info['custom_channel_name'] = partner_channel.custom_channel_name info['is_pinned'] = partner_channel.is_pinned + info['last_interest_dt'] = partner_channel.last_interest_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT) # add members infos if channel.channel_type != 'channel': # avoid sending potentially a lot of members for big channels @@ -616,7 +635,10 @@ class Channel(models.Model): channel = self.browse(result[0].get('channel_id')) # pin up the channel for the current partner if pin: - self.env['mail.channel.partner'].search([('partner_id', '=', self.env.user.partner_id.id), ('channel_id', '=', channel.id)]).write({'is_pinned': True}) + self.env['mail.channel.partner'].search([('partner_id', '=', self.env.user.partner_id.id), ('channel_id', '=', channel.id)]).write({ + 'is_pinned': True, + 'last_interest_dt': fields.Datetime.now(), + }) channel._broadcast(self.env.user.partner_id.ids) else: # create a new one diff --git a/addons/mail/models/mail_channel_partner.py b/addons/mail/models/mail_channel_partner.py index 27bf14cf7fc..cba802513e8 100644 --- a/addons/mail/models/mail_channel_partner.py +++ b/addons/mail/models/mail_channel_partner.py @@ -23,6 +23,7 @@ class ChannelPartner(models.Model): fold_state = fields.Selection([('open', 'Open'), ('folded', 'Folded'), ('closed', 'Closed')], string='Conversation Fold State', default='open') is_minimized = fields.Boolean("Conversation is minimized") is_pinned = fields.Boolean("Is pinned on the interface", default=True) + last_interest_dt = fields.Datetime("Last Interest", default=fields.Datetime.now, help="Contains the date and time of the last interesting event that happened in this channel for this partner. This includes: creating, joining, pinning, and new message posted.") @api.model_create_multi def create(self, vals_list): diff --git a/addons/mail/models/res_users.py b/addons/mail/models/res_users.py index d91cf339801..c8119d52cf8 100644 --- a/addons/mail/models/res_users.py +++ b/addons/mail/models/res_users.py @@ -25,6 +25,7 @@ class Users(models.Model): help="Policy on how to handle Chatter notifications:\n" "- Handle by Emails: notifications are sent to your email address\n" "- Handle in Odoo: notifications appear in your Odoo Inbox") + res_users_settings_ids = fields.One2many('res.users.settings', 'user_id') # ------------------------------------------------------------ # CRUD @@ -95,6 +96,7 @@ class Users(models.Model): 'channels': self.partner_id._get_channels_as_member().channel_info(), 'current_partner': self.partner_id.mail_partner_format().get(self.partner_id), 'current_user_id': self.id, + 'current_user_settings': self.env['res.users.settings']._find_or_create_for_user(self)._res_users_settings_format(), 'mail_failures': self.partner_id._message_fetch_failed(), 'menu_id': self.env['ir.model.data']._xmlid_to_res_id('mail.menu_root_discuss'), 'needaction_inbox_counter': self.partner_id._get_needaction_count(), diff --git a/addons/mail/models/res_users_settings.py b/addons/mail/models/res_users_settings.py new file mode 100644 index 00000000000..e219397859c --- /dev/null +++ b/addons/mail/models/res_users_settings.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import api, fields, models + + +class ResUsersSettings(models.Model): + _name = 'res.users.settings' + _description = 'User Settings' + + user_id = fields.Many2one('res.users', string="User", required=True, readonly=True, ondelete='cascade') + is_discuss_sidebar_category_channel_open = fields.Boolean(string="Is discuss sidebar category channel open?", default=True) + is_discuss_sidebar_category_chat_open = fields.Boolean(string="Is discuss sidebar category chat open?", default=True) + + _sql_constraints = [ + ('unique_user_id', 'UNIQUE(user_id)', 'One user should only have one mail user settings.') + ] + + @api.model + def _find_or_create_for_user(self, user): + settings = user.res_users_settings_ids + if not settings: + settings = self.create({'user_id': user.id}) + return settings + + def _res_users_settings_format(self): + self.ensure_one() + return self._read_format(fnames=[name for name, field in self._fields.items() if name == 'id' or not field.automatic])[0] + + def set_res_users_settings(self, new_settings): + self.ensure_one() + changed_settings = {} + for setting in new_settings.keys(): + if setting in self._fields and new_settings[setting] != self[setting]: + changed_settings[setting] = new_settings[setting] + self.write(changed_settings) + self.env['bus.bus'].sendone((self._cr.dbname, 'res.partner', self.user_id.partner_id.id), { + 'type': 'res.users_settings_changed', + 'payload': changed_settings, + }) diff --git a/addons/mail/security/ir.model.access.csv b/addons/mail/security/ir.model.access.csv index b357cd858c4..4708e0c2a0f 100644 --- a/addons/mail/security/ir.model.access.csv +++ b/addons/mail/security/ir.model.access.csv @@ -45,4 +45,6 @@ access_mail_resend_message,access.mail.resend.message,model_mail_resend_message, access_mail_resend_partner,access.mail.resend.partner,model_mail_resend_partner,base.group_user,1,1,1,0 access_mail_template_preview,access.mail.template.preview,model_mail_template_preview,base.group_user,1,1,1,0 access_mail_blacklist_remove_system,acesss.mail.blacklist.remove.system,model_mail_blacklist_remove,base.group_system,1,1,1,1 +access_res_users_settings_all,res.users.settings,model_res_users_settings,,0,0,0,0 +access_res_users_settings_user,res.users.settings,model_res_users_settings,base.group_user,1,1,1,1 ir_actions_report_access_user,ir.actions.report.access.user,base.model_ir_actions_report,base.group_user,1,0,0,0 diff --git a/addons/mail/security/mail_security.xml b/addons/mail/security/mail_security.xml index cdbfe1230a8..d48bbbf5f2d 100644 --- a/addons/mail/security/mail_security.xml +++ b/addons/mail/security/mail_security.xml @@ -65,4 +65,26 @@ + + Administrators can access all User Settings. + + + [(1, '=', 1)] + + + + + + + + res.users.settings: access their own entries + + + [('user_id', '=', user.id)] + + + + + + diff --git a/addons/mail/static/src/components/discuss/discuss.xml b/addons/mail/static/src/components/discuss/discuss.xml index efcb8c39619..acf473db4f8 100644 --- a/addons/mail/static/src/components/discuss/discuss.xml +++ b/addons/mail/static/src/components/discuss/discuss.xml @@ -13,7 +13,7 @@
- + diff --git a/addons/mail/static/src/components/discuss/tests/discuss_pinned_tests.js b/addons/mail/static/src/components/discuss/tests/discuss_pinned_tests.js index c23c073f259..b315cf2ccb9 100644 --- a/addons/mail/static/src/components/discuss/tests/discuss_pinned_tests.js +++ b/addons/mail/static/src/components/discuss/tests/discuss_pinned_tests.js @@ -43,7 +43,7 @@ QUnit.test('sidebar: pinned channel 1: init with one pinned channel', async func ); assert.containsOnce( document.body, - `.o_DiscussSidebarItem[data-thread-local-id="${ + `.o_DiscussSidebarCategoryItem[data-thread-local-id="${ this.messaging.models['mail.thread'].findFromIdentifyingData({ id: 20, model: 'mail.channel', @@ -66,7 +66,7 @@ QUnit.test('sidebar: pinned channel 2: open pinned channel', async function (ass model: 'mail.channel', }); await afterNextRender(() => - document.querySelector(`.o_DiscussSidebarItem[data-thread-local-id="${ + document.querySelector(`.o_DiscussSidebarCategoryItem[data-thread-local-id="${ threadGeneral.localId }"]`).click() ); @@ -107,13 +107,13 @@ QUnit.test('sidebar: pinned channel 3: open pinned channel and unpin it', async model: 'mail.channel', }); await afterNextRender(() => - document.querySelector(`.o_DiscussSidebarItem[data-thread-local-id="${ + document.querySelector(`.o_DiscussSidebarCategoryItem[data-thread-local-id="${ threadGeneral.localId }"]`).click() ); assert.verifySteps([], "neither channel_fold nor action_unfollow are called yet"); await afterNextRender(() => - document.querySelector('.o_DiscussSidebarItem_commandLeave').click() + document.querySelector('.o_DiscussSidebarCategoryItem_commandLeave').click() ); assert.verifySteps( [ @@ -124,7 +124,7 @@ QUnit.test('sidebar: pinned channel 3: open pinned channel and unpin it', async ); assert.containsNone( document.body, - `.o_DiscussSidebarItem[data-thread-local-id="${threadGeneral.localId}"]`, + `.o_DiscussSidebarCategoryItem[data-thread-local-id="${threadGeneral.localId}"]`, "The channel must have been removed from discuss sidebar" ); assert.containsOnce( @@ -153,12 +153,12 @@ QUnit.test('sidebar: unpin channel from bus', async function (assert) { ); assert.containsOnce( document.body, - `.o_DiscussSidebarItem[data-thread-local-id="${threadGeneral.localId}"]`, + `.o_DiscussSidebarCategoryItem[data-thread-local-id="${threadGeneral.localId}"]`, "1 channel is present in discuss sidebar and it is 'general'" ); await afterNextRender(() => - document.querySelector(`.o_DiscussSidebarItem[data-thread-local-id="${ + document.querySelector(`.o_DiscussSidebarCategoryItem[data-thread-local-id="${ threadGeneral.localId }"]`).click() ); @@ -191,7 +191,7 @@ QUnit.test('sidebar: unpin channel from bus', async function (assert) { ); assert.containsNone( document.body, - `.o_DiscussSidebarItem[data-thread-local-id="${threadGeneral.localId}"]`, + `.o_DiscussSidebarCategoryItem[data-thread-local-id="${threadGeneral.localId}"]`, "The channel must have been removed from discuss sidebar" ); }); @@ -217,12 +217,12 @@ QUnit.test('[technical] sidebar: channel group_based_subscription: mandatorily p }); assert.containsOnce( document.body, - `.o_DiscussSidebarItem[data-thread-local-id="${threadGeneral.localId}"]`, + `.o_DiscussSidebarCategoryItem[data-thread-local-id="${threadGeneral.localId}"]`, "The channel #General is in discuss sidebar" ); assert.containsNone( document.body, - 'o_DiscussSidebarItem_commandLeave', + 'o_DiscussSidebarCategoryItem_commandLeave', "The group_based_subscription channel is not unpinnable" ); }); diff --git a/addons/mail/static/src/components/discuss/tests/discuss_sidebar_tests.js b/addons/mail/static/src/components/discuss/tests/discuss_sidebar_tests.js index a0041b874dd..717747d6385 100644 --- a/addons/mail/static/src/components/discuss/tests/discuss_sidebar_tests.js +++ b/addons/mail/static/src/components/discuss/tests/discuss_sidebar_tests.js @@ -52,13 +52,13 @@ QUnit.test('sidebar find shows channels matching search term', async function (a }, }); await afterNextRender(() => - document.querySelector(`.o_DiscussSidebar_groupHeaderItemAdd`).click() + document.querySelector(`.o_DiscussSidebarCategory_commandAdd`).click() ); - document.querySelector(`.o_DiscussSidebar_itemNew`).focus(); + document.querySelector(`.o_DiscussSidebarCategory_newItem`).focus(); document.execCommand('insertText', false, "test"); - document.querySelector(`.o_DiscussSidebar_itemNew`) + document.querySelector(`.o_DiscussSidebarCategory_newItem`) .dispatchEvent(new window.KeyboardEvent('keydown')); - document.querySelector(`.o_DiscussSidebar_itemNew`) + document.querySelector(`.o_DiscussSidebarCategory_newItem`) .dispatchEvent(new window.KeyboardEvent('keyup')); await searchReadDef; @@ -104,13 +104,13 @@ QUnit.test('sidebar find shows channels matching search term even when user is m }, }); await afterNextRender(() => - document.querySelector(`.o_DiscussSidebar_groupHeaderItemAdd`).click() + document.querySelector(`.o_DiscussSidebarCategory_commandAdd`).click() ); - document.querySelector(`.o_DiscussSidebar_itemNew`).focus(); + document.querySelector(`.o_DiscussSidebarCategory_newItem`).focus(); document.execCommand('insertText', false, "test"); - document.querySelector(`.o_DiscussSidebar_itemNew`) + document.querySelector(`.o_DiscussSidebarCategory_newItem`) .dispatchEvent(new window.KeyboardEvent('keydown')); - document.querySelector(`.o_DiscussSidebar_itemNew`) + document.querySelector(`.o_DiscussSidebarCategory_newItem`) .dispatchEvent(new window.KeyboardEvent('keyup')); await searchReadDef; @@ -145,7 +145,7 @@ QUnit.test('sidebar channels should be ordered case insensitive alphabetically', { id: 22, name: "Xyz" } ); await this.start(); - const results = document.querySelectorAll('.o_DiscussSidebar_groupChannel .o_DiscussSidebarItem_name'); + const results = document.querySelectorAll('.o_DiscussSidebar_categoryChannel .o_DiscussSidebarCategoryItem_name'); assert.deepEqual( [results[0].textContent, results[1].textContent, results[2].textContent, results[3].textContent], ["abc", "Abc", "Xyz", "Xyz"], diff --git a/addons/mail/static/src/components/discuss/tests/discuss_tests.js b/addons/mail/static/src/components/discuss/tests/discuss_tests.js index 99fd938b506..245d05c2847 100644 --- a/addons/mail/static/src/components/discuss/tests/discuss_tests.js +++ b/addons/mail/static/src/components/discuss/tests/discuss_tests.js @@ -125,37 +125,30 @@ QUnit.test('basic rendering', async function (assert) { }); QUnit.test('basic rendering: sidebar', async function (assert) { - assert.expect(20); + assert.expect(18); await this.start(); assert.strictEqual( - document.querySelectorAll(`.o_DiscussSidebar_group`).length, + document.querySelectorAll(`.o_DiscussSidebar_category`).length, 3, "should have 3 groups in sidebar" ); assert.strictEqual( - document.querySelectorAll(`.o_DiscussSidebar_groupMailbox`).length, + document.querySelectorAll(`.o_DiscussSidebar_categoryMailbox`).length, 1, "should have group 'Mailbox' in sidebar" ); assert.strictEqual( document.querySelectorAll(` - .o_DiscussSidebar_groupMailbox .o_DiscussSidebar_groupHeader - `).length, - 0, - "mailbox category should not have any header" - ); - assert.strictEqual( - document.querySelectorAll(` - .o_DiscussSidebar_groupMailbox .o_DiscussSidebar_item + .o_DiscussSidebar_categoryMailbox .o_DiscussSidebarMailbox `).length, 3, "should have 3 mailbox items" ); assert.strictEqual( document.querySelectorAll(` - .o_DiscussSidebar_groupMailbox - .o_DiscussSidebar_item[data-thread-local-id="${ + .o_DiscussSidebar_categoryMailbox + .o_DiscussSidebarMailbox[data-thread-local-id="${ this.messaging.inbox.localId }"] `).length, @@ -164,8 +157,8 @@ QUnit.test('basic rendering: sidebar', async function (assert) { ); assert.strictEqual( document.querySelectorAll(` - .o_DiscussSidebar_groupMailbox - .o_DiscussSidebar_item[data-thread-local-id="${ + .o_DiscussSidebar_categoryMailbox + .o_DiscussSidebarMailbox[data-thread-local-id="${ this.messaging.starred.localId }"] `).length, @@ -174,8 +167,8 @@ QUnit.test('basic rendering: sidebar', async function (assert) { ); assert.strictEqual( document.querySelectorAll(` - .o_DiscussSidebar_groupMailbox - .o_DiscussSidebar_item[data-thread-local-id="${ + .o_DiscussSidebar_categoryMailbox + .o_DiscussSidebarMailbox[data-thread-local-id="${ this.messaging.history.localId }"] `).length, @@ -188,88 +181,76 @@ QUnit.test('basic rendering: sidebar', async function (assert) { "should have separator (between mailboxes and channels, but that's not tested)" ); assert.strictEqual( - document.querySelectorAll(`.o_DiscussSidebar_groupChannel`).length, + document.querySelectorAll(`.o_DiscussSidebar_categoryChannel`).length, 1, "should have group 'Channel' in sidebar" ); assert.strictEqual( document.querySelectorAll(` - .o_DiscussSidebar_groupChannel .o_DiscussSidebar_groupHeader + .o_DiscussSidebar_categoryChannel .o_DiscussSidebarCategory_header `).length, 1, - "channel category should have a header" - ); - assert.strictEqual( - document.querySelectorAll(` - .o_DiscussSidebar_groupChannel .o_DiscussSidebar_groupTitle - `).length, - 1, - "should have title in channel header" + "should have header in channel group" ); assert.strictEqual( document.querySelector(` - .o_DiscussSidebar_groupChannel .o_DiscussSidebar_groupTitle + .o_DiscussSidebar_categoryChannel .o_DiscussSidebarCategory_header .o_DiscussSidebarCategory_titleText `).textContent.trim(), "Channels" ); assert.strictEqual( - document.querySelectorAll(`.o_DiscussSidebar_groupChannel .o_DiscussSidebar_list`).length, + document.querySelectorAll(`.o_DiscussSidebar_categoryChannel .o_DiscussSidebarCategory_content`).length, 1, "channel category should list items" ); assert.strictEqual( - document.querySelectorAll(`.o_DiscussSidebar_groupChannel .o_DiscussSidebar_item`).length, + document.querySelectorAll(`.o_DiscussSidebar_categoryChannel .o_DiscussSidebarCategory_item`).length, 0, "channel category should have no item by default" ); assert.strictEqual( - document.querySelectorAll(`.o_DiscussSidebar_groupChat`).length, + document.querySelectorAll(`.o_DiscussSidebar_categoryChat`).length, 1, "should have group 'Chat' in sidebar" ); assert.strictEqual( - document.querySelectorAll(`.o_DiscussSidebar_groupChat .o_DiscussSidebar_groupHeader`).length, + document.querySelectorAll(`.o_DiscussSidebar_categoryChat .o_DiscussSidebarCategory_header`).length, 1, "channel category should have a header" ); assert.strictEqual( - document.querySelectorAll(`.o_DiscussSidebar_groupChat .o_DiscussSidebar_groupTitle`).length, + document.querySelectorAll(`.o_DiscussSidebar_categoryChat .o_DiscussSidebarCategory_title`).length, 1, "should have title in chat header" ); assert.strictEqual( document.querySelector(` - .o_DiscussSidebar_groupChat .o_DiscussSidebar_groupTitle + .o_DiscussSidebar_categoryChat .o_DiscussSidebarCategory_title .o_DiscussSidebarCategory_titleText `).textContent.trim(), "Direct Messages" ); assert.strictEqual( - document.querySelectorAll(`.o_DiscussSidebar_groupChat .o_DiscussSidebar_list`).length, + document.querySelectorAll(`.o_DiscussSidebar_categoryChat .o_DiscussSidebarCategory_content`).length, 1, "chat category should list items" ); assert.strictEqual( - document.querySelectorAll(`.o_DiscussSidebar_groupChat .o_DiscussSidebar_item`).length, + document.querySelectorAll(`.o_DiscussSidebar_categoryChat .o_DiscussSidebarCategory_item`).length, 0, "chat category should have no item by default" ); }); QUnit.test('sidebar: basic mailbox rendering', async function (assert) { - assert.expect(6); + assert.expect(5); await this.start(); const inbox = document.querySelector(` - .o_DiscussSidebar_groupMailbox - .o_DiscussSidebar_item[data-thread-local-id="${ + .o_DiscussSidebar_categoryMailbox + .o_DiscussSidebarMailbox[data-thread-local-id="${ this.messaging.inbox.localId }"] `); - assert.strictEqual( - inbox.querySelectorAll(`:scope .o_DiscussSidebarItem_activeIndicator`).length, - 1, - "mailbox should have active indicator" - ); assert.strictEqual( inbox.querySelectorAll(`:scope .o_ThreadIcon`).length, 1, @@ -281,21 +262,21 @@ QUnit.test('sidebar: basic mailbox rendering', async function (assert) { "inbox should have 'inbox' icon" ); assert.strictEqual( - inbox.querySelectorAll(`:scope .o_DiscussSidebarItem_name`).length, + inbox.querySelectorAll(`:scope .o_DiscussSidebarMailbox_name`).length, 1, "mailbox should have a name" ); assert.strictEqual( - inbox.querySelector(`:scope .o_DiscussSidebarItem_name`).textContent, + inbox.querySelector(`:scope .o_DiscussSidebarMailbox_name`).textContent, "Inbox", "inbox should have name 'Inbox'" ); assert.strictEqual( document.querySelectorAll(` - .o_DiscussSidebar_item[data-thread-local-id="${ + .o_DiscussSidebarMailbox[data-thread-local-id="${ this.messaging.inbox.localId }"] - .o_DiscussSidebarItem_counter + .o_DiscussSidebarMailbox_counter `).length, 0, "should have no counter when equal to 0 (default value)" @@ -307,15 +288,13 @@ QUnit.test('sidebar: default active inbox', async function (assert) { await this.start(); const inbox = document.querySelector(` - .o_DiscussSidebar_groupMailbox - .o_DiscussSidebar_item[data-thread-local-id="${ + .o_DiscussSidebar_categoryMailbox + .o_DiscussSidebarMailbox[data-thread-local-id="${ this.messaging.inbox.localId }"] `); assert.ok( - inbox.querySelector(` - :scope .o_DiscussSidebarItem_activeIndicator - `).classList.contains('o-item-active'), + inbox.classList.contains('o-active'), "inbox should be active by default" ); }); @@ -326,46 +305,42 @@ QUnit.test('sidebar: change item', async function (assert) { await this.start(); assert.ok( document.querySelector(` - .o_DiscussSidebar_item[data-thread-local-id="${ + .o_DiscussSidebarMailbox[data-thread-local-id="${ this.messaging.inbox.localId }"] - .o_DiscussSidebarItem_activeIndicator - `).classList.contains('o-item-active'), + `).classList.contains('o-active'), "inbox should be active by default" ); assert.notOk( document.querySelector(` - .o_DiscussSidebar_item[data-thread-local-id="${ + .o_DiscussSidebarMailbox[data-thread-local-id="${ this.messaging.starred.localId }"] - .o_DiscussSidebarItem_activeIndicator - `).classList.contains('o-item-active'), + `).classList.contains('o-active'), "starred should be inactive by default" ); await afterNextRender(() => document.querySelector(` - .o_DiscussSidebar_item[data-thread-local-id="${ + .o_DiscussSidebarMailbox[data-thread-local-id="${ this.messaging.starred.localId }"] `).click() ); assert.notOk( document.querySelector(` - .o_DiscussSidebar_item[data-thread-local-id="${ + .o_DiscussSidebarMailbox[data-thread-local-id="${ this.messaging.inbox.localId }"] - .o_DiscussSidebarItem_activeIndicator - `).classList.contains('o-item-active'), + `).classList.contains('o-active'), "inbox mailbox should become inactive" ); assert.ok( document.querySelector(` - .o_DiscussSidebar_item[data-thread-local-id="${ + .o_DiscussSidebarMailbox[data-thread-local-id="${ this.messaging.starred.localId }"] - .o_DiscussSidebarItem_activeIndicator - `).classList.contains('o-item-active'), + `).classList.contains('o-active'), "starred mailbox should become active"); }); @@ -377,20 +352,20 @@ QUnit.test('sidebar: inbox with counter', async function (assert) { await this.start(); assert.strictEqual( document.querySelectorAll(` - .o_DiscussSidebar_item[data-thread-local-id="${ + .o_DiscussSidebarMailbox[data-thread-local-id="${ this.messaging.inbox.localId }"] - .o_DiscussSidebarItem_counter + .o_DiscussSidebarMailbox_counter `).length, 1, "should display a counter (= have a counter when different from 0)" ); assert.strictEqual( document.querySelector(` - .o_DiscussSidebar_item[data-thread-local-id="${ + .o_DiscussSidebarMailbox[data-thread-local-id="${ this.messaging.inbox.localId }"] - .o_DiscussSidebarItem_counter + .o_DiscussSidebarMailbox_counter `).textContent, "1", "should have counter value" @@ -403,45 +378,45 @@ QUnit.test('sidebar: add channel', async function (assert) { await this.start(); assert.strictEqual( document.querySelectorAll(` - .o_DiscussSidebar_groupChannel - .o_DiscussSidebar_groupHeaderItemAdd + .o_DiscussSidebar_categoryChannel + .o_DiscussSidebarCategory_commandAdd `).length, 1, "should be able to add channel from header" ); assert.strictEqual( document.querySelector(` - .o_DiscussSidebar_groupChannel - .o_DiscussSidebar_groupHeaderItemAdd + .o_DiscussSidebar_categoryChannel + .o_DiscussSidebarCategory_commandAdd `).title, "Add or join a channel"); await afterNextRender(() => document.querySelector(` - .o_DiscussSidebar_groupChannel .o_DiscussSidebar_groupHeaderItemAdd + .o_DiscussSidebar_categoryChannel .o_DiscussSidebarCategory_commandAdd `).click() ); assert.strictEqual( - document.querySelectorAll(`.o_DiscussSidebar_groupChannel .o_DiscussSidebar_itemNew`).length, + document.querySelectorAll(`.o_DiscussSidebar_categoryChannel .o_DiscussSidebarCategory_itemNewInput`).length, 1, "should have item to add a new channel" ); }); QUnit.test('sidebar: basic channel rendering', async function (assert) { - assert.expect(14); + assert.expect(13); // channel expected to be found in the sidebar, // with a random unique id and name that will be referenced in the test this.data['mail.channel'].records.push({ id: 20, name: "General" }); await this.start(); assert.strictEqual( - document.querySelectorAll(`.o_DiscussSidebar_groupChannel .o_DiscussSidebar_item`).length, + document.querySelectorAll(`.o_DiscussSidebar_categoryChannel .o_DiscussSidebarCategory_item`).length, 1, "should have one channel item"); let channel = document.querySelector(` - .o_DiscussSidebar_groupChannel - .o_DiscussSidebar_item + .o_DiscussSidebar_categoryChannel + .o_DiscussSidebarCategory_item `); assert.strictEqual( channel.dataset.threadLocalId, @@ -451,14 +426,8 @@ QUnit.test('sidebar: basic channel rendering', async function (assert) { }).localId, "should have channel with Id 20" ); - assert.strictEqual( - channel.querySelectorAll(`:scope .o_DiscussSidebarItem_activeIndicator`).length, - 1, - "should have active indicator" - ); - assert.strictEqual( - channel.querySelectorAll(`:scope .o_DiscussSidebarItem_activeIndicator.o-item-active`).length, - 0, + assert.notOk( + channel.classList.contains('o-active'), "should not be active by default" ); assert.strictEqual( @@ -467,48 +436,48 @@ QUnit.test('sidebar: basic channel rendering', async function (assert) { "should have an icon" ); assert.strictEqual( - channel.querySelectorAll(`:scope .o_DiscussSidebarItem_name`).length, + channel.querySelectorAll(`:scope .o_DiscussSidebarCategoryItem_name`).length, 1, "should have a name" ); assert.strictEqual( - channel.querySelector(`:scope .o_DiscussSidebarItem_name`).textContent, + channel.querySelector(`:scope .o_DiscussSidebarCategoryItem_name`).textContent, "General", "should have name value" ); assert.strictEqual( - channel.querySelectorAll(`:scope .o_DiscussSidebarItem_commands`).length, + channel.querySelectorAll(`:scope .o_DiscussSidebarCategoryItem_commands`).length, 1, "should have commands" ); assert.strictEqual( - channel.querySelectorAll(`:scope .o_DiscussSidebarItem_command`).length, + channel.querySelectorAll(`:scope .o_DiscussSidebarCategoryItem_command`).length, 2, "should have 2 commands" ); assert.strictEqual( - channel.querySelectorAll(`:scope .o_DiscussSidebarItem_commandSettings`).length, + channel.querySelectorAll(`:scope .o_DiscussSidebarCategoryItem_commandSettings`).length, 1, "should have 'settings' command" ); assert.strictEqual( - channel.querySelectorAll(`:scope .o_DiscussSidebarItem_commandLeave`).length, + channel.querySelectorAll(`:scope .o_DiscussSidebarCategoryItem_commandLeave`).length, 1, "should have 'leave' command" ); assert.strictEqual( - channel.querySelectorAll(`:scope .o_DiscussSidebarItem_counter`).length, + channel.querySelectorAll(`:scope .o_DiscussSidebarCategoryItem_counter`).length, 0, "should have a counter when equals 0 (default value)" ); await afterNextRender(() => - document.querySelector(`.o_DiscussSidebar_groupChannel .o_DiscussSidebar_item`).click() + document.querySelector(`.o_DiscussSidebar_categoryChannel .o_DiscussSidebarCategory_item`).click() ); - channel = document.querySelector(`.o_DiscussSidebar_groupChannel .o_DiscussSidebar_item`); - assert.strictEqual( - channel.querySelectorAll(`:scope .o_DiscussSidebarItem_activeIndicator.o-item-active`).length, - 1, + channel = document.querySelector(`.o_DiscussSidebar_categoryChannel .o_DiscussSidebarCategory_item`); + assert.hasClass( + channel, + 'o-active', "channel should become active" ); assert.strictEqual( @@ -537,35 +506,34 @@ QUnit.test('sidebar: channel rendering with needaction counter', async function res_partner_id: this.data.currentPartnerId, // must be for current partner }); await this.start(); - const channel = document.querySelector(`.o_DiscussSidebar_groupChannel .o_DiscussSidebar_item`); + const channel = document.querySelector(`.o_DiscussSidebar_categoryChannel .o_DiscussSidebarCategory_item`); assert.strictEqual( - channel.querySelectorAll(`:scope .o_DiscussSidebarItem_counter`).length, + channel.querySelectorAll(`:scope .o_DiscussSidebarCategoryItem_counter`).length, 1, "should have a counter when different from 0" ); assert.strictEqual( - channel.querySelector(`:scope .o_DiscussSidebarItem_counter`).textContent, + channel.querySelector(`:scope .o_DiscussSidebarCategoryItem_counter`).textContent, "1", "should have counter value" ); assert.strictEqual( - channel.querySelectorAll(`:scope .o_DiscussSidebarItem_command`).length, + channel.querySelectorAll(`:scope .o_DiscussSidebarCategoryItem_command`).length, 1, "should have single command" ); assert.strictEqual( - channel.querySelectorAll(`:scope .o_DiscussSidebarItem_commandSettings`).length, + channel.querySelectorAll(`:scope .o_DiscussSidebarCategoryItem_commandSettings`).length, 1, "should have 'settings' command" ); assert.strictEqual( - channel.querySelectorAll(`:scope .o_DiscussSidebarItem_commandLeave`).length, + channel.querySelectorAll(`:scope .o_DiscussSidebarCategoryItem_commandLeave`).length, 0, "should not have 'leave' command" ); }); - QUnit.test('sidebar: public/private channel rendering', async function (assert) { assert.expect(5); @@ -577,14 +545,14 @@ QUnit.test('sidebar: public/private channel rendering', async function (assert) ); await this.start(); assert.strictEqual( - document.querySelectorAll(`.o_DiscussSidebar_groupChannel .o_DiscussSidebar_item`).length, + document.querySelectorAll(`.o_DiscussSidebar_categoryChannel .o_DiscussSidebarCategory_item`).length, 2, "should have 2 channel items" ); assert.strictEqual( document.querySelectorAll(` - .o_DiscussSidebar_groupChannel - .o_DiscussSidebar_item[data-thread-local-id="${ + .o_DiscussSidebar_categoryChannel + .o_DiscussSidebarCategory_item[data-thread-local-id="${ this.messaging.models['mail.thread'].findFromIdentifyingData({ id: 100, model: 'mail.channel', @@ -596,8 +564,8 @@ QUnit.test('sidebar: public/private channel rendering', async function (assert) ); assert.strictEqual( document.querySelectorAll(` - .o_DiscussSidebar_groupChannel - .o_DiscussSidebar_item[data-thread-local-id="${ + .o_DiscussSidebar_categoryChannel + .o_DiscussSidebarCategory_item[data-thread-local-id="${ this.messaging.models['mail.thread'].findFromIdentifyingData({ id: 101, model: 'mail.channel' @@ -608,8 +576,8 @@ QUnit.test('sidebar: public/private channel rendering', async function (assert) "should have channel2 (Id 101)" ); const channel1 = document.querySelector(` - .o_DiscussSidebar_groupChannel - .o_DiscussSidebar_item[data-thread-local-id="${ + .o_DiscussSidebar_categoryChannel + .o_DiscussSidebarCategory_item[data-thread-local-id="${ this.messaging.models['mail.thread'].findFromIdentifyingData({ id: 100, model: 'mail.channel' @@ -617,8 +585,8 @@ QUnit.test('sidebar: public/private channel rendering', async function (assert) }"] `); const channel2 = document.querySelector(` - .o_DiscussSidebar_groupChannel - .o_DiscussSidebar_item[data-thread-local-id="${ + .o_DiscussSidebar_categoryChannel + .o_DiscussSidebarCategory_item[data-thread-local-id="${ this.messaging.models['mail.thread'].findFromIdentifyingData({ id: 101, model: 'mail.channel' @@ -638,7 +606,7 @@ QUnit.test('sidebar: public/private channel rendering', async function (assert) }); QUnit.test('sidebar: basic chat rendering', async function (assert) { - assert.expect(11); + assert.expect(10); // expected correspondent, with a random unique id that will be used to link // partner to chat and a random name that will be asserted in the test @@ -652,11 +620,11 @@ QUnit.test('sidebar: basic chat rendering', async function (assert) { }); await this.start(); assert.strictEqual( - document.querySelectorAll(`.o_DiscussSidebar_groupChat .o_DiscussSidebar_item`).length, + document.querySelectorAll(`.o_DiscussSidebar_categoryChat .o_DiscussSidebarCategory_item`).length, 1, "should have one chat item" ); - const chat = document.querySelector(`.o_DiscussSidebar_groupChat .o_DiscussSidebar_item`); + const chat = document.querySelector(`.o_DiscussSidebar_categoryChat .o_DiscussSidebarCategory_item`); assert.strictEqual( chat.dataset.threadLocalId, this.messaging.models['mail.thread'].findFromIdentifyingData({ @@ -665,48 +633,43 @@ QUnit.test('sidebar: basic chat rendering', async function (assert) { }).localId, "should have chat with Id 10" ); - assert.strictEqual( - chat.querySelectorAll(`:scope .o_DiscussSidebarItem_activeIndicator`).length, - 1, - "should have active indicator" - ); assert.strictEqual( chat.querySelectorAll(`:scope .o_ThreadIcon`).length, 1, "should have an icon" ); assert.strictEqual( - chat.querySelectorAll(`:scope .o_DiscussSidebarItem_name`).length, + chat.querySelectorAll(`:scope .o_DiscussSidebarCategoryItem_name`).length, 1, "should have a name" ); assert.strictEqual( - chat.querySelector(`:scope .o_DiscussSidebarItem_name`).textContent, + chat.querySelector(`:scope .o_DiscussSidebarCategoryItem_name`).textContent, "Demo", "should have correspondent name as name" ); assert.strictEqual( - chat.querySelectorAll(`:scope .o_DiscussSidebarItem_commands`).length, + chat.querySelectorAll(`:scope .o_DiscussSidebarCategoryItem_commands`).length, 1, "should have commands" ); assert.strictEqual( - chat.querySelectorAll(`:scope .o_DiscussSidebarItem_command`).length, + chat.querySelectorAll(`:scope .o_DiscussSidebarCategoryItem_command`).length, 2, "should have 2 commands" ); assert.strictEqual( - chat.querySelectorAll(`:scope .o_DiscussSidebarItem_commandRename`).length, + chat.querySelectorAll(`:scope .o_DiscussSidebarCategoryItem_commandRename`).length, 1, "should have 'rename' command" ); assert.strictEqual( - chat.querySelectorAll(`:scope .o_DiscussSidebarItem_commandUnpin`).length, + chat.querySelectorAll(`:scope .o_DiscussSidebarCategoryItem_commandUnpin`).length, 1, "should have 'unpin' command" ); assert.strictEqual( - chat.querySelectorAll(`:scope .o_DiscussSidebarItem_counter`).length, + chat.querySelectorAll(`:scope .o_DiscussSidebarCategoryItem_counter`).length, 0, "should have a counter when equals 0 (default value)" ); @@ -723,29 +686,29 @@ QUnit.test('sidebar: chat rendering with unread counter', async function (assert public: 'private', // expected value for testing a chat }); await this.start(); - const chat = document.querySelector(`.o_DiscussSidebar_groupChat .o_DiscussSidebar_item`); + const chat = document.querySelector(`.o_DiscussSidebar_categoryChat .o_DiscussSidebarCategory_item`); assert.strictEqual( - chat.querySelectorAll(`:scope .o_DiscussSidebarItem_counter`).length, + chat.querySelectorAll(`:scope .o_DiscussSidebarCategoryItem_counter`).length, 1, "should have a counter when different from 0" ); assert.strictEqual( - chat.querySelector(`:scope .o_DiscussSidebarItem_counter`).textContent, + chat.querySelector(`:scope .o_DiscussSidebarCategoryItem_counter`).textContent, "100", "should have counter value" ); assert.strictEqual( - chat.querySelectorAll(`:scope .o_DiscussSidebarItem_command`).length, + chat.querySelectorAll(`:scope .o_DiscussSidebarCategoryItem_command`).length, 1, "should have single command" ); assert.strictEqual( - chat.querySelectorAll(`:scope .o_DiscussSidebarItem_commandRename`).length, + chat.querySelectorAll(`:scope .o_DiscussSidebarCategoryItem_commandRename`).length, 1, "should have 'rename' command" ); assert.strictEqual( - chat.querySelectorAll(`:scope .o_DiscussSidebarItem_commandUnpin`).length, + chat.querySelectorAll(`:scope .o_DiscussSidebarCategoryItem_commandUnpin`).length, 0, "should not have 'unpin' command" ); @@ -784,14 +747,14 @@ QUnit.test('sidebar: chat im_status rendering', async function (assert) { ); await this.start(); assert.strictEqual( - document.querySelectorAll(`.o_DiscussSidebar_groupChat .o_DiscussSidebar_item`).length, + document.querySelectorAll(`.o_DiscussSidebar_categoryChat .o_DiscussSidebarCategory_item`).length, 3, "should have 3 chat items" ); assert.strictEqual( document.querySelectorAll(` - .o_DiscussSidebar_groupChat - .o_DiscussSidebar_item[data-thread-local-id="${ + .o_DiscussSidebar_categoryChat + .o_DiscussSidebarCategory_item[data-thread-local-id="${ this.messaging.models['mail.thread'].findFromIdentifyingData({ id: 11, model: 'mail.channel', @@ -803,8 +766,8 @@ QUnit.test('sidebar: chat im_status rendering', async function (assert) { ); assert.strictEqual( document.querySelectorAll(` - .o_DiscussSidebar_groupChat - .o_DiscussSidebar_item[data-thread-local-id="${ + .o_DiscussSidebar_categoryChat + .o_DiscussSidebarCategory_item[data-thread-local-id="${ this.messaging.models['mail.thread'].findFromIdentifyingData({ id: 12, model: 'mail.channel', @@ -816,8 +779,8 @@ QUnit.test('sidebar: chat im_status rendering', async function (assert) { ); assert.strictEqual( document.querySelectorAll(` - .o_DiscussSidebar_groupChat - .o_DiscussSidebar_item[data-thread-local-id="${ + .o_DiscussSidebar_categoryChat + .o_DiscussSidebarCategory_item[data-thread-local-id="${ this.messaging.models['mail.thread'].findFromIdentifyingData({ id: 13, model: 'mail.channel', @@ -828,8 +791,8 @@ QUnit.test('sidebar: chat im_status rendering', async function (assert) { "should have Partner3 (Id 13)" ); const chat1 = document.querySelector(` - .o_DiscussSidebar_groupChat - .o_DiscussSidebar_item[data-thread-local-id="${ + .o_DiscussSidebar_categoryChat + .o_DiscussSidebarCategory_item[data-thread-local-id="${ this.messaging.models['mail.thread'].findFromIdentifyingData({ id: 11, model: 'mail.channel', @@ -837,8 +800,8 @@ QUnit.test('sidebar: chat im_status rendering', async function (assert) { }"] `); const chat2 = document.querySelector(` - .o_DiscussSidebar_groupChat - .o_DiscussSidebar_item[data-thread-local-id="${ + .o_DiscussSidebar_categoryChat + .o_DiscussSidebarCategory_item[data-thread-local-id="${ this.messaging.models['mail.thread'].findFromIdentifyingData({ id: 12, model: 'mail.channel', @@ -846,8 +809,8 @@ QUnit.test('sidebar: chat im_status rendering', async function (assert) { }"] `); const chat3 = document.querySelector(` - .o_DiscussSidebar_groupChat - .o_DiscussSidebar_item[data-thread-local-id="${ + .o_DiscussSidebar_categoryChat + .o_DiscussSidebarCategory_item[data-thread-local-id="${ this.messaging.models['mail.thread'].findFromIdentifyingData({ id: 13, model: 'mail.channel', @@ -885,9 +848,9 @@ QUnit.test('sidebar: chat custom name', async function (assert) { public: 'private', // expected value for testing a chat }); await this.start(); - const chat = document.querySelector(`.o_DiscussSidebar_groupChat .o_DiscussSidebar_item`); + const chat = document.querySelector(`.o_DiscussSidebar_categoryChat .o_DiscussSidebarCategory_item`); assert.strictEqual( - chat.querySelector(`:scope .o_DiscussSidebarItem_name`).textContent, + chat.querySelector(`:scope .o_DiscussSidebarCategoryItem_name`).textContent, "Marc", "chat should have custom name as name" ); @@ -907,51 +870,51 @@ QUnit.test('sidebar: rename chat', async function (assert) { public: 'private', // expected value for testing a chat }); await this.start(); - const chat = document.querySelector(`.o_DiscussSidebar_groupChat .o_DiscussSidebar_item`); + const chat = document.querySelector(`.o_DiscussSidebar_categoryChat .o_DiscussSidebarCategory_item`); assert.strictEqual( - chat.querySelector(`:scope .o_DiscussSidebarItem_name`).textContent, + chat.querySelector(`:scope .o_DiscussSidebarCategoryItem_name`).textContent, "Marc", "chat should have custom name as name" ); assert.notOk( - chat.querySelector(`:scope .o_DiscussSidebarItem_name`).classList.contains('o-editable'), + chat.querySelector(`:scope .o_DiscussSidebarCategoryItem_name`).classList.contains('o-editable'), "chat name should not be editable" ); await afterNextRender(() => - chat.querySelector(`:scope .o_DiscussSidebarItem_commandRename`).click() + chat.querySelector(`:scope .o_DiscussSidebarCategoryItem_commandRename`).click() ); assert.ok( - chat.querySelector(`:scope .o_DiscussSidebarItem_name`).classList.contains('o-editable'), + chat.querySelector(`:scope .o_DiscussSidebarCategoryItem_name`).classList.contains('o-editable'), "chat should have editable name" ); assert.strictEqual( - chat.querySelectorAll(`:scope .o_DiscussSidebarItem_nameInput`).length, + chat.querySelectorAll(`:scope .o_DiscussSidebarCategoryItem_nameInput`).length, 1, "chat should have editable name input" ); assert.strictEqual( - chat.querySelector(`:scope .o_DiscussSidebarItem_nameInput`).value, + chat.querySelector(`:scope .o_DiscussSidebarCategoryItem_nameInput`).value, "Marc", "editable name input should have custom chat name as value by default" ); assert.strictEqual( - chat.querySelector(`:scope .o_DiscussSidebarItem_nameInput`).placeholder, + chat.querySelector(`:scope .o_DiscussSidebarCategoryItem_nameInput`).placeholder, "Marc Demo", "editable name input should have partner name as placeholder" ); await afterNextRender(() => { - chat.querySelector(`:scope .o_DiscussSidebarItem_nameInput`).value = "Demo"; + chat.querySelector(`:scope .o_DiscussSidebarCategoryItem_nameInput`).value = "Demo"; const kevt = new window.KeyboardEvent('keydown', { key: "Enter" }); - chat.querySelector(`:scope .o_DiscussSidebarItem_nameInput`).dispatchEvent(kevt); + chat.querySelector(`:scope .o_DiscussSidebarCategoryItem_nameInput`).dispatchEvent(kevt); }); assert.notOk( - chat.querySelector(`:scope .o_DiscussSidebarItem_name`).classList.contains('o-editable'), + chat.querySelector(`:scope .o_DiscussSidebarCategoryItem_name`).classList.contains('o-editable'), "chat should no longer show editable name" ); assert.strictEqual( - chat.querySelector(`:scope .o_DiscussSidebarItem_name`).textContent, + chat.querySelector(`:scope .o_DiscussSidebarCategoryItem_name`).textContent, "Demo", "chat should have renamed name as name" ); @@ -966,7 +929,7 @@ QUnit.test('default thread rendering', async function (assert) { await this.start(); assert.strictEqual( document.querySelectorAll(` - .o_DiscussSidebar_item[data-thread-local-id="${ + .o_DiscussSidebarMailbox[data-thread-local-id="${ this.messaging.inbox.localId }"] `).length, @@ -975,7 +938,7 @@ QUnit.test('default thread rendering', async function (assert) { ); assert.strictEqual( document.querySelectorAll(` - .o_DiscussSidebar_item[data-thread-local-id="${ + .o_DiscussSidebarMailbox[data-thread-local-id="${ this.messaging.starred.localId }"] `).length, @@ -984,7 +947,7 @@ QUnit.test('default thread rendering', async function (assert) { ); assert.strictEqual( document.querySelectorAll(` - .o_DiscussSidebar_item[data-thread-local-id="${ + .o_DiscussSidebarMailbox[data-thread-local-id="${ this.messaging.history.localId }"] `).length, @@ -993,7 +956,7 @@ QUnit.test('default thread rendering', async function (assert) { ); assert.strictEqual( document.querySelectorAll(` - .o_DiscussSidebar_item[data-thread-local-id="${ + .o_DiscussSidebarCategory_item[data-thread-local-id="${ this.messaging.models['mail.thread'].findFromIdentifyingData({ id: 20, model: 'mail.channel', @@ -1005,7 +968,7 @@ QUnit.test('default thread rendering', async function (assert) { ); assert.ok( document.querySelector(` - .o_DiscussSidebar_item[data-thread-local-id="${ + .o_DiscussSidebarMailbox[data-thread-local-id="${ this.messaging.inbox.localId }"] `).classList.contains('o-active'), @@ -1027,14 +990,14 @@ QUnit.test('default thread rendering', async function (assert) { await afterNextRender(() => document.querySelector(` - .o_DiscussSidebar_item[data-thread-local-id="${ + .o_DiscussSidebarMailbox[data-thread-local-id="${ this.messaging.starred.localId }"] `).click() ); assert.ok( document.querySelector(` - .o_DiscussSidebar_item[data-thread-local-id="${ + .o_DiscussSidebarMailbox[data-thread-local-id="${ this.messaging.starred.localId }"] `).classList.contains('o-active'), @@ -1056,14 +1019,14 @@ QUnit.test('default thread rendering', async function (assert) { await afterNextRender(() => document.querySelector(` - .o_DiscussSidebar_item[data-thread-local-id="${ + .o_DiscussSidebarMailbox[data-thread-local-id="${ this.messaging.history.localId }"] `).click() ); assert.ok( document.querySelector(` - .o_DiscussSidebar_item[data-thread-local-id="${ + .o_DiscussSidebarMailbox[data-thread-local-id="${ this.messaging.history.localId }"] `).classList.contains('o-active'), @@ -1083,7 +1046,7 @@ QUnit.test('default thread rendering', async function (assert) { await afterNextRender(() => document.querySelector(` - .o_DiscussSidebar_item[data-thread-local-id="${ + .o_DiscussSidebarCategory_item[data-thread-local-id="${ this.messaging.models['mail.thread'].findFromIdentifyingData({ id: 20, model: 'mail.channel', @@ -1093,7 +1056,7 @@ QUnit.test('default thread rendering', async function (assert) { ); assert.ok( document.querySelector(` - .o_DiscussSidebar_item[data-thread-local-id="${ + .o_DiscussSidebarCategory_item[data-thread-local-id="${ this.messaging.models['mail.thread'].findFromIdentifyingData({ id: 20, model: 'mail.channel', @@ -1153,11 +1116,10 @@ QUnit.test('default select thread in discuss params', async function (assert) { }); assert.ok( document.querySelector(` - .o_DiscussSidebar_item[data-thread-local-id="${ + .o_DiscussSidebarMailbox[data-thread-local-id="${ this.messaging.starred.localId }"] - .o_DiscussSidebarItem_activeIndicator - `).classList.contains('o-item-active'), + `).classList.contains('o-active'), "starred mailbox should become active" ); }); @@ -1174,11 +1136,10 @@ QUnit.test('auto-select thread in discuss context', async function (assert) { }); assert.ok( document.querySelector(` - .o_DiscussSidebar_item[data-thread-local-id="${ + .o_DiscussSidebarMailbox[data-thread-local-id="${ this.messaging.starred.localId }"] - .o_DiscussSidebarItem_activeIndicator - `).classList.contains('o-item-active'), + `).classList.contains('o-active'), "starred mailbox should become active" ); }); @@ -2036,8 +1997,8 @@ QUnit.test('restore thread scroll position', async function (assert) { func: () => { // select channel 12 document.querySelector(` - .o_DiscussSidebar_groupChannel - .o_DiscussSidebar_item[data-thread-local-id="${ + .o_DiscussSidebar_categoryChannel + .o_DiscussSidebarCategory_item[data-thread-local-id="${ this.messaging.models['mail.thread'].findFromIdentifyingData({ id: 12, model: 'mail.channel', @@ -2069,8 +2030,8 @@ QUnit.test('restore thread scroll position', async function (assert) { func: () => { // select channel 11 document.querySelector(` - .o_DiscussSidebar_groupChannel - .o_DiscussSidebar_item[data-thread-local-id="${ + .o_DiscussSidebar_categoryChannel + .o_DiscussSidebarCategory_item[data-thread-local-id="${ this.messaging.models['mail.thread'].findFromIdentifyingData({ id: 11, model: 'mail.channel', @@ -2099,8 +2060,8 @@ QUnit.test('restore thread scroll position', async function (assert) { func: () => { // select channel 12 document.querySelector(` - .o_DiscussSidebar_groupChannel - .o_DiscussSidebar_item[data-thread-local-id="${ + .o_DiscussSidebar_categoryChannel + .o_DiscussSidebarCategory_item[data-thread-local-id="${ this.messaging.models['mail.thread'].findFromIdentifyingData({ id: 12, model: 'mail.channel', @@ -2166,28 +2127,26 @@ QUnit.test('redirect to author (open chat)', async function (assert) { }); assert.ok( document.querySelector(` - .o_DiscussSidebar_groupChannel - .o_DiscussSidebar_item[data-thread-local-id="${ + .o_DiscussSidebar_categoryChannel + .o_DiscussSidebarCategory_item[data-thread-local-id="${ this.messaging.models['mail.thread'].findFromIdentifyingData({ id: 1, model: 'mail.channel', }).localId }"] - .o_DiscussSidebarItem_activeIndicator - `).classList.contains('o-item-active'), + `).classList.contains('o-active'), "channel 'General' should be active" ); assert.notOk( document.querySelector(` - .o_DiscussSidebar_groupChat - .o_DiscussSidebar_item[data-thread-local-id="${ + .o_DiscussSidebar_categoryChat + .o_DiscussSidebarCategory_item[data-thread-local-id="${ this.messaging.models['mail.thread'].findFromIdentifyingData({ id: 10, model: 'mail.channel', }).localId }"] - .o_DiscussSidebarItem_activeIndicator - `).classList.contains('o-item-active'), + `).classList.contains('o-active'), "Chat 'Demo' should not be active" ); assert.strictEqual( @@ -2216,28 +2175,26 @@ QUnit.test('redirect to author (open chat)', async function (assert) { ); assert.notOk( document.querySelector(` - .o_DiscussSidebar_groupChannel - .o_DiscussSidebar_item[data-thread-local-id="${ + .o_DiscussSidebar_categoryChannel + .o_DiscussSidebarCategory_item[data-thread-local-id="${ this.messaging.models['mail.thread'].findFromIdentifyingData({ id: 1, model: 'mail.channel', }).localId }"] - .o_DiscussSidebarItem_activeIndicator - `).classList.contains('o-item-active'), + `).classList.contains('o-active'), "channel 'General' should become inactive after author redirection" ); assert.ok( document.querySelector(` - .o_DiscussSidebar_groupChat - .o_DiscussSidebar_item[data-thread-local-id="${ + .o_DiscussSidebar_categoryChat + .o_DiscussSidebarCategory_item[data-thread-local-id="${ this.messaging.models['mail.thread'].findFromIdentifyingData({ id: 10, model: 'mail.channel', }).localId }"] - .o_DiscussSidebarItem_activeIndicator - `).classList.contains('o-item-active'), + `).classList.contains('o-active'), "chat 'Demo' should become active after author redirection" ); }); @@ -2251,7 +2208,7 @@ QUnit.test('sidebar quick search', async function (assert) { } await this.start(); assert.strictEqual( - document.querySelectorAll(`.o_DiscussSidebar_groupChannel .o_DiscussSidebar_item`).length, + document.querySelectorAll(`.o_DiscussSidebar_categoryChannel .o_DiscussSidebarCategory_item`).length, 20, "should have 20 channel items" ); @@ -2270,7 +2227,7 @@ QUnit.test('sidebar quick search', async function (assert) { quickSearch.dispatchEvent(kevt1); }); assert.strictEqual( - document.querySelectorAll(`.o_DiscussSidebar_groupChannel .o_DiscussSidebar_item`).length, + document.querySelectorAll(`.o_DiscussSidebar_categoryChannel .o_DiscussSidebarCategory_item`).length, 11, "should have filtered to 11 channel items" ); @@ -2281,13 +2238,13 @@ QUnit.test('sidebar quick search', async function (assert) { quickSearch.dispatchEvent(kevt2); }); assert.strictEqual( - document.querySelectorAll(`.o_DiscussSidebar_groupChannel .o_DiscussSidebar_item`).length, + document.querySelectorAll(`.o_DiscussSidebar_categoryChannel .o_DiscussSidebarCategory_item`).length, 1, "should have filtered to a single channel item" ); assert.strictEqual( document.querySelector(` - .o_DiscussSidebar_groupChannel .o_DiscussSidebar_item + .o_DiscussSidebar_categoryChannel .o_DiscussSidebarCategory_item `).dataset.threadLocalId, this.messaging.models['mail.thread'].findFromIdentifyingData({ id: 12, @@ -2302,7 +2259,7 @@ QUnit.test('sidebar quick search', async function (assert) { quickSearch.dispatchEvent(kevt3); }); assert.strictEqual( - document.querySelectorAll(`.o_DiscussSidebar_groupChannel .o_DiscussSidebar_item`).length, + document.querySelectorAll(`.o_DiscussSidebar_categoryChannel .o_DiscussSidebarCategory_item`).length, 0, "should have filtered to no channel item" ); @@ -2334,7 +2291,7 @@ QUnit.test('basic top bar rendering', async function (assert) { await afterNextRender(() => document.querySelector(` - .o_DiscussSidebar_item[data-thread-local-id="${ + .o_DiscussSidebarMailbox[data-thread-local-id="${ this.messaging.starred.localId }"] `).click() @@ -2358,7 +2315,7 @@ QUnit.test('basic top bar rendering', async function (assert) { await afterNextRender(() => document.querySelector(` - .o_DiscussSidebar_item[data-thread-local-id="${ + .o_DiscussSidebarCategory_item[data-thread-local-id="${ this.messaging.models['mail.thread'].findFromIdentifyingData({ id: 20, model: 'mail.channel', @@ -2423,23 +2380,23 @@ QUnit.test('inbox: mark all messages as read', async function (assert) { await this.start(); assert.strictEqual( document.querySelector(` - .o_DiscussSidebar_item[data-thread-local-id="${ + .o_DiscussSidebarMailbox[data-thread-local-id="${ this.messaging.inbox.localId }"] - .o_DiscussSidebarItem_counter + .o_DiscussSidebarMailbox_counter `).textContent, "2", "inbox should have counter of 2" ); assert.strictEqual( document.querySelector(` - .o_DiscussSidebar_item[data-thread-local-id="${ + .o_DiscussSidebarCategory_item[data-thread-local-id="${ this.messaging.models['mail.thread'].findFromIdentifyingData({ id: 20, model: 'mail.channel', }).localId }"] - .o_DiscussSidebarItem_counter + .o_DiscussSidebarCategoryItem_counter `).textContent, "2", "channel should have counter of 2" @@ -2458,23 +2415,23 @@ QUnit.test('inbox: mark all messages as read', async function (assert) { await afterNextRender(() => markAllReadButton.click()); assert.strictEqual( document.querySelectorAll(` - .o_DiscussSidebar_item[data-thread-local-id="${ + .o_DiscussSidebarMailbox[data-thread-local-id="${ this.messaging.inbox.localId }"] - .o_DiscussSidebarItem_counter + .o_DiscussSidebarMailbox_counter `).length, 0, "inbox should display no counter (= 0)" ); assert.strictEqual( document.querySelectorAll(` - .o_DiscussSidebar_item[data-thread-local-id="${ + .o_DiscussSidebarCategory_item[data-thread-local-id="${ this.messaging.models['mail.thread'].findFromIdentifyingData({ id: 20, model: 'mail.channel', }).localId }"] - .o_DiscussSidebarItem_counter + .o_DiscussSidebarCategoryItem_counter `).length, 0, "channel should display no counter (= 0)" @@ -2508,10 +2465,10 @@ QUnit.test('starred: unstar all', async function (assert) { }); assert.strictEqual( document.querySelector(` - .o_DiscussSidebar_item[data-thread-local-id="${ + .o_DiscussSidebarMailbox[data-thread-local-id="${ this.messaging.starred.localId }"] - .o_DiscussSidebarItem_counter + .o_DiscussSidebarMailbox_counter `).textContent, "2", "starred should have counter of 2" @@ -2530,10 +2487,10 @@ QUnit.test('starred: unstar all', async function (assert) { await afterNextRender(() => unstarAllButton.click()); assert.strictEqual( document.querySelectorAll(` - .o_DiscussSidebar_item[data-thread-local-id="${ + .o_DiscussSidebarMailbox[data-thread-local-id="${ this.messaging.starred.localId }"] - .o_DiscussSidebarItem_counter + .o_DiscussSidebarMailbox_counter `).length, 0, "starred should display no counter (= 0)" @@ -2582,10 +2539,10 @@ QUnit.test('toggle_star message', async function (assert) { }); assert.strictEqual( document.querySelectorAll(` - .o_DiscussSidebar_item[data-thread-local-id="${ + .o_DiscussSidebarMailbox[data-thread-local-id="${ this.messaging.starred.localId }"] - .o_DiscussSidebarItem_counter + .o_DiscussSidebarMailbox_counter `).length, 0, "starred should display no counter (= 0)" @@ -2610,10 +2567,10 @@ QUnit.test('toggle_star message', async function (assert) { assert.verifySteps(['rpc:toggle_message_starred']); assert.strictEqual( document.querySelector(` - .o_DiscussSidebar_item[data-thread-local-id="${ + .o_DiscussSidebarMailbox[data-thread-local-id="${ this.messaging.starred.localId }"] - .o_DiscussSidebarItem_counter + .o_DiscussSidebarMailbox_counter `).textContent, "1", "starred should display a counter of 1" @@ -2633,10 +2590,10 @@ QUnit.test('toggle_star message', async function (assert) { assert.verifySteps(['rpc:toggle_message_starred']); assert.strictEqual( document.querySelectorAll(` - .o_DiscussSidebar_item[data-thread-local-id="${ + .o_DiscussSidebarMailbox[data-thread-local-id="${ this.messaging.starred.localId }"] - .o_DiscussSidebarItem_counter + .o_DiscussSidebarMailbox_counter `).length, 0, "starred should no longer display a counter (= 0)" @@ -2677,7 +2634,7 @@ QUnit.test('composer state: text save and restore', async function (assert) { .dispatchEvent(new window.KeyboardEvent('input')); }); await afterNextRender(() => - document.querySelector(`.o_DiscussSidebarItem[data-thread-name="Special"]`).click() + document.querySelector(`.o_DiscussSidebarCategoryItem[data-thread-name="Special"]`).click() ); await afterNextRender(() => { document.querySelector(`.o_ComposerTextInput_textarea`).focus(); @@ -2687,7 +2644,7 @@ QUnit.test('composer state: text save and restore', async function (assert) { }); // Switch back to #general await afterNextRender(() => - document.querySelector(`.o_DiscussSidebarItem[data-thread-name="General"]`).click() + document.querySelector(`.o_DiscussSidebarCategoryItem[data-thread-name="General"]`).click() ); assert.strictEqual( document.querySelector(`.o_ComposerTextInput_textarea`).value, @@ -2696,7 +2653,7 @@ QUnit.test('composer state: text save and restore', async function (assert) { ); await afterNextRender(() => - document.querySelector(`.o_DiscussSidebarItem[data-thread-name="Special"]`).click() + document.querySelector(`.o_DiscussSidebarCategoryItem[data-thread-name="Special"]`).click() ); assert.strictEqual( document.querySelector(`.o_ComposerTextInput_textarea`).value, @@ -2722,7 +2679,7 @@ QUnit.test('composer state: attachments save and restore', async function (asser }, }); const channels = document.querySelectorAll(` - .o_DiscussSidebar_groupChannel .o_DiscussSidebar_item + .o_DiscussSidebar_categoryChannel .o_DiscussSidebarCategory_item `); // Add attachment in a message for #general await afterNextRender(async () => { @@ -3058,7 +3015,7 @@ QUnit.test('mark channel as seen on last message visible [REQUIRE FOCUS]', async await this.start(); assert.containsOnce( document.body, - `.o_DiscussSidebar_item[data-thread-local-id="${ + `.o_DiscussSidebarCategory_item[data-thread-local-id="${ this.messaging.models['mail.thread'].findFromIdentifyingData({ id: 10, model: 'mail.channel', @@ -3068,7 +3025,7 @@ QUnit.test('mark channel as seen on last message visible [REQUIRE FOCUS]', async ); assert.hasClass( document.querySelector(` - .o_DiscussSidebar_item[data-thread-local-id="${ + .o_DiscussSidebarCategory_item[data-thread-local-id="${ this.messaging.models['mail.thread'].findFromIdentifyingData({ id: 10, model: 'mail.channel', @@ -3083,7 +3040,7 @@ QUnit.test('mark channel as seen on last message visible [REQUIRE FOCUS]', async eventName: 'o-thread-last-seen-by-current-partner-message-id-changed', func: () => { document.querySelector(` - .o_DiscussSidebar_item[data-thread-local-id="${ + .o_DiscussSidebarCategory_item[data-thread-local-id="${ this.messaging.models['mail.thread'].findFromIdentifyingData({ id: 10, model: 'mail.channel', @@ -3102,7 +3059,7 @@ QUnit.test('mark channel as seen on last message visible [REQUIRE FOCUS]', async })); assert.doesNotHaveClass( document.querySelector(` - .o_DiscussSidebar_item[data-thread-local-id="${ + .o_DiscussSidebarCategory_item[data-thread-local-id="${ this.messaging.models['mail.thread'].findFromIdentifyingData({ id: 10, model: 'mail.channel', @@ -3120,7 +3077,7 @@ QUnit.test('receive new needaction messages', async function (assert) { await this.start(); assert.ok( document.querySelector(` - .o_DiscussSidebar_item[data-thread-local-id="${ + .o_DiscussSidebarMailbox[data-thread-local-id="${ this.messaging.inbox.localId }"] `), @@ -3128,7 +3085,7 @@ QUnit.test('receive new needaction messages', async function (assert) { ); assert.ok( document.querySelector(` - .o_DiscussSidebar_item[data-thread-local-id="${ + .o_DiscussSidebarMailbox[data-thread-local-id="${ this.messaging.inbox.localId }"] `).classList.contains('o-active'), @@ -3136,10 +3093,10 @@ QUnit.test('receive new needaction messages', async function (assert) { ); assert.notOk( document.querySelector(` - .o_DiscussSidebar_item[data-thread-local-id="${ + .o_DiscussSidebarMailbox[data-thread-local-id="${ this.messaging.inbox.localId }"] - .o_DiscussSidebarItem_counter + .o_DiscussSidebarMailbox_counter `), "inbox item in sidebar should not have any counter" ); @@ -3163,19 +3120,19 @@ QUnit.test('receive new needaction messages', async function (assert) { }); assert.ok( document.querySelector(` - .o_DiscussSidebar_item[data-thread-local-id="${ + .o_DiscussSidebarMailbox[data-thread-local-id="${ this.messaging.inbox.localId }"] - .o_DiscussSidebarItem_counter + .o_DiscussSidebarMailbox_counter `), "inbox item in sidebar should now have counter" ); assert.strictEqual( document.querySelector(` - .o_DiscussSidebar_item[data-thread-local-id="${ + .o_DiscussSidebarMailbox[data-thread-local-id="${ this.messaging.inbox.localId }"] - .o_DiscussSidebarItem_counter + .o_DiscussSidebarMailbox_counter `).textContent, '1', "inbox item in sidebar should have counter of '1'" @@ -3205,10 +3162,10 @@ QUnit.test('receive new needaction messages', async function (assert) { }); assert.strictEqual( document.querySelector(` - .o_DiscussSidebar_item[data-thread-local-id="${ + .o_DiscussSidebarMailbox[data-thread-local-id="${ this.messaging.inbox.localId }"] - .o_DiscussSidebarItem_counter + .o_DiscussSidebarMailbox_counter `).textContent, '2', "inbox item in sidebar should have counter of '2'" @@ -3415,7 +3372,7 @@ QUnit.test('messages marked as read move to "History" mailbox', async function ( }); assert.ok( document.querySelector(` - .o_DiscussSidebar_item[data-thread-local-id="${ + .o_DiscussSidebarMailbox[data-thread-local-id="${ this.messaging.history.localId }"] `).classList.contains('o-active'), @@ -3429,14 +3386,14 @@ QUnit.test('messages marked as read move to "History" mailbox', async function ( await afterNextRender(() => document.querySelector(` - .o_DiscussSidebar_item[data-thread-local-id="${ + .o_DiscussSidebarMailbox[data-thread-local-id="${ this.messaging.inbox.localId }"] `).click() ); assert.ok( document.querySelector(` - .o_DiscussSidebar_item[data-thread-local-id="${ + .o_DiscussSidebarMailbox[data-thread-local-id="${ this.messaging.inbox.localId }"] `).classList.contains('o-active'), @@ -3458,7 +3415,7 @@ QUnit.test('messages marked as read move to "History" mailbox', async function ( ); assert.ok( document.querySelector(` - .o_DiscussSidebar_item[data-thread-local-id="${ + .o_DiscussSidebarMailbox[data-thread-local-id="${ this.messaging.inbox.localId }"] `).classList.contains('o-active'), @@ -3472,14 +3429,14 @@ QUnit.test('messages marked as read move to "History" mailbox', async function ( await afterNextRender(() => document.querySelector(` - .o_DiscussSidebar_item[data-thread-local-id="${ + .o_DiscussSidebarMailbox[data-thread-local-id="${ this.messaging.history.localId }"] `).click() ); assert.ok( document.querySelector(` - .o_DiscussSidebar_item[data-thread-local-id="${ + .o_DiscussSidebarMailbox[data-thread-local-id="${ this.messaging.history.localId }"] `).classList.contains('o-active'), @@ -3533,7 +3490,7 @@ QUnit.test('mark a single message as read should only move this message to "Hist }); assert.hasClass( document.querySelector(` - .o_DiscussSidebar_item[data-thread-local-id="${ + .o_DiscussSidebarMailbox[data-thread-local-id="${ this.messaging.history.localId }"] `), @@ -3548,14 +3505,14 @@ QUnit.test('mark a single message as read should only move this message to "Hist await afterNextRender(() => document.querySelector(` - .o_DiscussSidebar_item[data-thread-local-id="${ + .o_DiscussSidebarMailbox[data-thread-local-id="${ this.messaging.inbox.localId }"] `).click() ); assert.hasClass( document.querySelector(` - .o_DiscussSidebar_item[data-thread-local-id="${ + .o_DiscussSidebarMailbox[data-thread-local-id="${ this.messaging.inbox.localId }"] `), @@ -3591,14 +3548,14 @@ QUnit.test('mark a single message as read should only move this message to "Hist await afterNextRender(() => document.querySelector(` - .o_DiscussSidebar_item[data-thread-local-id="${ + .o_DiscussSidebarMailbox[data-thread-local-id="${ this.messaging.history.localId }"] `).click() ); assert.hasClass( document.querySelector(` - .o_DiscussSidebar_item[data-thread-local-id="${ + .o_DiscussSidebarMailbox[data-thread-local-id="${ this.messaging.history.localId }"] `), @@ -3670,7 +3627,7 @@ QUnit.test('all messages in "Inbox" in "History" after marked all as read', asyn eventName: 'o-component-message-list-scrolled', func: () => { document.querySelector(` - .o_DiscussSidebarItem[data-thread-local-id="${ + .o_DiscussSidebarMailbox[data-thread-local-id="${ this.messaging.history.localId }"] `).click(); @@ -3883,40 +3840,40 @@ QUnit.test('auto-focus composer on opening thread', async function (assert) { await this.start(); assert.strictEqual( document.querySelectorAll(` - .o_DiscussSidebar_item[data-thread-name="Inbox"] + .o_DiscussSidebarMailbox[data-thread-name="Inbox"] `).length, 1, "should have mailbox 'Inbox' in the sidebar" ); assert.ok( document.querySelector(` - .o_DiscussSidebar_item[data-thread-name="Inbox"] + .o_DiscussSidebarMailbox[data-thread-name="Inbox"] `).classList.contains('o-active'), "mailbox 'Inbox' should be active initially" ); assert.strictEqual( document.querySelectorAll(` - .o_DiscussSidebar_item[data-thread-name="General"] + .o_DiscussSidebarCategory_item[data-thread-name="General"] `).length, 1, "should have channel 'General' in the sidebar" ); assert.notOk( document.querySelector(` - .o_DiscussSidebar_item[data-thread-name="General"] + .o_DiscussSidebarCategory_item[data-thread-name="General"] `).classList.contains('o-active'), "channel 'General' should not be active initially" ); assert.strictEqual( document.querySelectorAll(` - .o_DiscussSidebar_item[data-thread-name="Demo User"] + .o_DiscussSidebarCategory_item[data-thread-name="Demo User"] `).length, 1, "should have chat 'Demo User' in the sidebar" ); assert.notOk( document.querySelector(` - .o_DiscussSidebar_item[data-thread-name="Demo User"] + .o_DiscussSidebarCategory_item[data-thread-name="Demo User"] `).classList.contains('o-active'), "chat 'Demo User' should not be active initially" ); @@ -3927,11 +3884,11 @@ QUnit.test('auto-focus composer on opening thread', async function (assert) { ); await afterNextRender(() => - document.querySelector(`.o_DiscussSidebar_item[data-thread-name="General"]`).click() + document.querySelector(`.o_DiscussSidebarCategory_item[data-thread-name="General"]`).click() ); assert.ok( document.querySelector(` - .o_DiscussSidebar_item[data-thread-name="General"] + .o_DiscussSidebarCategory_item[data-thread-name="General"] `).classList.contains('o-active'), "channel 'General' should become active after selecting it from the sidebar" ); @@ -3953,11 +3910,11 @@ QUnit.test('auto-focus composer on opening thread', async function (assert) { ); await afterNextRender(() => - document.querySelector(`.o_DiscussSidebar_item[data-thread-name="Demo User"]`).click() + document.querySelector(`.o_DiscussSidebarCategory_item[data-thread-name="Demo User"]`).click() ); assert.ok( document.querySelector(` - .o_DiscussSidebar_item[data-thread-name="Demo User"] + .o_DiscussSidebarCategory_item[data-thread-name="Demo User"] `).classList.contains('o-active'), "chat 'Demo User' should become active after selecting it from the sidebar" ); @@ -4013,7 +3970,7 @@ QUnit.test('mark channel as seen if last message is visible when switching chann eventName: 'o-thread-last-seen-by-current-partner-message-id-changed', func: () => { document.querySelector(` - .o_DiscussSidebar_item[data-thread-local-id="${ + .o_DiscussSidebarCategory_item[data-thread-local-id="${ this.messaging.models['mail.thread'].findFromIdentifyingData({ id: 10, model: 'mail.channel', @@ -4032,7 +3989,7 @@ QUnit.test('mark channel as seen if last message is visible when switching chann })); assert.doesNotHaveClass( document.querySelector(` - .o_DiscussSidebar_item[data-thread-local-id="${ + .o_DiscussSidebarCategory_item[data-thread-local-id="${ this.messaging.models['mail.thread'].findFromIdentifyingData({ id: 10, model: 'mail.channel', diff --git a/addons/mail/static/src/components/discuss_sidebar/discuss_sidebar.js b/addons/mail/static/src/components/discuss_sidebar/discuss_sidebar.js index f2f0ec4384f..624f20b8443 100644 --- a/addons/mail/static/src/components/discuss_sidebar/discuss_sidebar.js +++ b/addons/mail/static/src/components/discuss_sidebar/discuss_sidebar.js @@ -19,12 +19,6 @@ export class DiscussSidebar extends Component { * chats based on this input content. */ this._quickSearchInputRef = useRef('quickSearchInput'); - - // bind since passed as props - this._onAddChannelAutocompleteSelect = this._onAddChannelAutocompleteSelect.bind(this); - this._onAddChannelAutocompleteSource = this._onAddChannelAutocompleteSource.bind(this); - this._onAddChatAutocompleteSelect = this._onAddChatAutocompleteSelect.bind(this); - this._onAddChatAutocompleteSource = this._onAddChatAutocompleteSource.bind(this); } //-------------------------------------------------------------------------- @@ -38,94 +32,6 @@ export class DiscussSidebar extends Component { return this.messaging && this.messaging.discuss; } - /** - * @returns {string} - */ - get FIND_OR_CREATE_CHANNEL() { - return this.env._t("Find or create a channel..."); - } - - /** - * @returns {mail.thread[]} - */ - get orderedMailboxes() { - return this.messaging.models['mail.thread'] - .all(thread => thread.isPinned && thread.model === 'mail.box') - .sort((mailbox1, mailbox2) => { - if (mailbox1 === this.messaging.inbox) { - return -1; - } - if (mailbox2 === this.messaging.inbox) { - return 1; - } - if (mailbox1 === this.messaging.starred) { - return -1; - } - if (mailbox2 === this.messaging.starred) { - return 1; - } - const mailbox1Name = mailbox1.displayName; - const mailbox2Name = mailbox2.displayName; - mailbox1Name < mailbox2Name ? -1 : 1; - }); - } - - /** - * Return the list of chats that match the quick search value input. - * - * @returns {mail.thread[]} - */ - get quickSearchPinnedAndOrderedChats() { - const allOrderedAndPinnedChats = this.messaging.models['mail.thread'] - .all(thread => - thread.channel_type === 'chat' && - thread.isPinned && - thread.model === 'mail.channel' - ) - .sort((c1, c2) => c1.displayName < c2.displayName ? -1 : 1); - if (!this.discuss.sidebarQuickSearchValue) { - return allOrderedAndPinnedChats; - } - const qsVal = this.discuss.sidebarQuickSearchValue.toLowerCase(); - return allOrderedAndPinnedChats.filter(chat => { - const nameVal = chat.displayName.toLowerCase(); - return nameVal.includes(qsVal); - }); - } - - /** - * Return the list of channels that match the quick search value input. - * - * @returns {mail.thread[]} - */ - get quickSearchOrderedAndPinnedMultiUserChannels() { - const allOrderedAndPinnedMultiUserChannels = this.messaging.models['mail.thread'] - .all(thread => - thread.channel_type === 'channel' && - thread.isPinned && - thread.model === 'mail.channel' - ) - .sort((c1, c2) => { - if (c1.displayName && !c2.displayName) { - return -1; - } else if (!c1.displayName && c2.displayName) { - return 1; - } else if (c1.displayName && c2.displayName && c1.displayName !== c2.displayName) { - return c1.displayName.toLowerCase() < c2.displayName.toLowerCase() ? -1 : 1; - } else { - return c1.id - c2.id; - } - }); - if (!this.discuss.sidebarQuickSearchValue) { - return allOrderedAndPinnedMultiUserChannels; - } - const qsVal = this.discuss.sidebarQuickSearchValue.toLowerCase(); - return allOrderedAndPinnedMultiUserChannels.filter(channel => { - const nameVal = channel.displayName.toLowerCase(); - return nameVal.includes(qsVal); - }); - } - //-------------------------------------------------------------------------- // Private //-------------------------------------------------------------------------- @@ -146,110 +52,13 @@ export class DiscussSidebar extends Component { // Handlers //-------------------------------------------------------------------------- - /** - * @private - * @param {Event} ev - * @param {Object} ui - * @param {Object} ui.item - * @param {integer} ui.item.id - */ - _onAddChannelAutocompleteSelect(ev, ui) { - this.discuss.handleAddChannelAutocompleteSelect(ev, ui); - } - - /** - * @private - * @param {Object} req - * @param {string} req.term - * @param {function} res - */ - _onAddChannelAutocompleteSource(req, res) { - this.discuss.handleAddChannelAutocompleteSource(req, res); - } - - /** - * @private - * @param {Event} ev - * @param {Object} ui - * @param {Object} ui.item - * @param {integer} ui.item.id - */ - _onAddChatAutocompleteSelect(ev, ui) { - this.discuss.handleAddChatAutocompleteSelect(ev, ui); - } - - /** - * @private - * @param {Object} req - * @param {string} req.term - * @param {function} res - */ - _onAddChatAutocompleteSource(req, res) { - this.discuss.handleAddChatAutocompleteSource(req, res); - } - - /** - * Called when clicking on add channel icon. - * - * @private - * @param {MouseEvent} ev - */ - _onClickChannelAdd(ev) { - ev.stopPropagation(); - this.discuss.update({ isAddingChannel: true }); - } - - /** - * Called when clicking on channel title. - * - * @private - * @param {MouseEvent} ev - */ - _onClickChannelTitle(ev) { - ev.stopPropagation(); - return this.env.bus.trigger('do-action', { - action: { - name: this.env._t("Public Channels"), - type: 'ir.actions.act_window', - res_model: 'mail.channel', - views: [[false, 'kanban'], [false, 'form']], - domain: [['public', '!=', 'private']] - }, - }); - } - - /** - * Called when clicking on add chat icon. - * - * @private - * @param {MouseEvent} ev - */ - _onClickChatAdd(ev) { - ev.stopPropagation(); - this.discuss.update({ isAddingChat: true }); - } - - /** - * @private - * @param {CustomEvent} ev - */ - _onHideAddingItem(ev) { - ev.stopPropagation(); - if (!this.discuss) { - return; - } - this.discuss.clearIsAddingItem(); - } - /** * @private * @param {KeyboardEvent} ev */ _onInputQuickSearch(ev) { ev.stopPropagation(); - this.discuss.update({ - sidebarQuickSearchValue: this._quickSearchInputRef.el.value, - }); + this.discuss.onInputQuickSearch(this._quickSearchInputRef.el.value); } } diff --git a/addons/mail/static/src/components/discuss_sidebar/discuss_sidebar.scss b/addons/mail/static/src/components/discuss_sidebar/discuss_sidebar.scss index 3e49cddf7dd..8ad191bf533 100644 --- a/addons/mail/static/src/components/discuss_sidebar/discuss_sidebar.scss +++ b/addons/mail/static/src/components/discuss_sidebar/discuss_sidebar.scss @@ -12,42 +12,12 @@ } } -.o_DiscussSidebar_group { +.o_DiscussSidebar_category { display: flex; flex-flow: column; flex: 0 0 auto; } -.o_DiscussSidebar_groupHeader { - display: flex; - align-items: center; - margin: 5px 0; -} - -.o_DiscussSidebar_groupHeaderItem { - margin-left: 3px; - margin-right: 3px; - - &:first-child { - margin-left: $o-mail-discuss-sidebar-active-indicator-margin-right; - } - - &:last-child { - margin-right: $o-mail-discuss-sidebar-scrollbar-width; - } -} - -.o_DiscussSidebar_itemNew { - display: flex; - justify-content: center; -} - -.o_DiscussSidebar_itemNewInput { - flex: 1 1 auto; - margin-left: $o-mail-discuss-sidebar-active-indicator-margin-right + 3px; - margin-right: $o-mail-discuss-sidebar-scrollbar-width; -} - .o_DiscussSidebar_quickSearch { border-radius: 10px; margin: 0 $o-mail-discuss-sidebar-scrollbar-width 10px; @@ -62,49 +32,11 @@ // Style // ------------------------------------------------------------------ -.o_DiscussSidebar { - background-color: gray('900'); - color: gray('300'); -} - -.o_DiscussSidebar_groupHeader { - font-size: $font-size-sm; - text-transform: uppercase; - font-weight: bolder; -} - -.o_DiscussSidebar_groupHeaderItemAdd { - cursor: pointer; - - &:not(:hover) { - color: gray('600'); - } -} - -.o_DiscussSidebar_groupTitle { - - &:not(.o-clickable) { - color: gray('600'); - } - - &.o-clickable { - cursor: pointer; - - &:not(:hover) { - color: gray('600'); - } - } -} - -.o_DiscussSidebar_itemNewInput { - outline: none; -} - .o_DiscussSidebar_quickSearch { border: none; outline: none; } .o_DiscussSidebar_separator { - background-color: gray('600'); + background-color: $border-color; } diff --git a/addons/mail/static/src/components/discuss_sidebar/discuss_sidebar.xml b/addons/mail/static/src/components/discuss_sidebar/discuss_sidebar.xml index 0859456cc62..1b3830cee8a 100644 --- a/addons/mail/static/src/components/discuss_sidebar/discuss_sidebar.xml +++ b/addons/mail/static/src/components/discuss_sidebar/discuss_sidebar.xml @@ -2,81 +2,24 @@ -
- -
- - - -
-
- - - -
-
-
- Channels -
-
-
-
-
- -
- -
-
- - - -
-
-
-
-
- Direct Messages -
-
-
-
-
- -
- -
-
- - - -
-
+
+
+ + + +
+
+ + + +
diff --git a/addons/mail/static/src/components/discuss_sidebar_category/discuss_sidebar_category.js b/addons/mail/static/src/components/discuss_sidebar_category/discuss_sidebar_category.js new file mode 100644 index 00000000000..2c44bf3dc39 --- /dev/null +++ b/addons/mail/static/src/components/discuss_sidebar_category/discuss_sidebar_category.js @@ -0,0 +1,28 @@ +/** @odoo-module **/ + +import { registerMessagingComponent } from '@mail/utils/messaging_component'; + +const { Component } = owl; + +export class DiscussSidebarCategory extends Component { + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + /** + * @returns {mail.discuss_sidebar_category} + */ + get category() { + return this.messaging.models['mail.discuss_sidebar_category'].get(this.props.categoryLocalId); + } +} + +Object.assign(DiscussSidebarCategory, { + props: { + categoryLocalId: String, + }, + template: 'mail.DiscussSidebarCategory', +}); + +registerMessagingComponent(DiscussSidebarCategory); diff --git a/addons/mail/static/src/components/discuss_sidebar_category/discuss_sidebar_category.scss b/addons/mail/static/src/components/discuss_sidebar_category/discuss_sidebar_category.scss new file mode 100644 index 00000000000..ba06695de0b --- /dev/null +++ b/addons/mail/static/src/components/discuss_sidebar_category/discuss_sidebar_category.scss @@ -0,0 +1,101 @@ +// ------------------------------------------------------------------ +// Layout +// ------------------------------------------------------------------ +.o_DiscussSidebarCategory_command { + margin-left: $o-mail-discuss-sidebar-category-item-margin; + margin-right:$o-mail-discuss-sidebar-category-item-margin; + + &:first-child { + margin-left: 0; + } + + &:last-child { + margin-right: 0; + } +} + +.o_DiscussSidebarCategory_commands { + display: flex; +} + +.o_DiscussSidebarCategory_header { + display: flex; + align-items: center; + margin: 5px 0; +} + +.o_DiscussSidebarCategory_headerItem { + margin-left: $o-mail-discuss-sidebar-category-item-margin; + margin-right: $o-mail-discuss-sidebar-category-item-margin; + + &:last-child { + margin-right: $o-mail-discuss-sidebar-scrollbar-width; + } +} + +.o_DiscussSidebarCategory_itemNewInput { + flex: 1 1 auto; + margin-left: $o-mail-discuss-sidebar-category-item-avatar-left-margin; + margin-right: $o-mail-discuss-sidebar-scrollbar-width; +} + +.o_DiscussSidebarCategory_newItem { + display: flex; + justify-content: center; + padding-bottom: map-get($spacers, 2); +} + +.o_DiscussSidebarCategory_title { + display: flex; + align-items: baseline; +} + +.o_DiscussSidebarCategory_titleIcon { + width: $o-mail-discuss-sidebar-category-title-icon-size; + height: $o-mail-discuss-sidebar-category-title-icon-size; +} + + +// ------------------------------------------------------------------ +// style +// ------------------------------------------------------------------ + +.o_DiscussSidebarCategory_command { + cursor: pointer; + + &:not(:hover) { + color: gray('600'); + } +} + +.o_DiscussSidebarCategory_counter { + background-color: $o-brand-primary; + color: gray('300'); +} + +.o_DiscussSidebarCategory_headerItem { + user-select: none; +} + +.o_DiscussSidebarCategory_itemNewInput { + outline: none; +} + +.o_DiscussSidebarCategory_title { + cursor: pointer; + + &:not(:hover) + { + color: gray('600'); + } +} + +.o_DiscussSidebarCategory_titleIcon { + font-size: 0.75em; +} + +.o_DiscussSidebarCategory_titleText { + font-size: $font-size-sm; + text-transform: uppercase; + font-weight: bolder; +} diff --git a/addons/mail/static/src/components/discuss_sidebar_category/discuss_sidebar_category.xml b/addons/mail/static/src/components/discuss_sidebar_category/discuss_sidebar_category.xml new file mode 100644 index 00000000000..0ded875e498 --- /dev/null +++ b/addons/mail/static/src/components/discuss_sidebar_category/discuss_sidebar_category.xml @@ -0,0 +1,58 @@ + + + + +
+
+
+ + +
+
+
+ + + + + + +
+ +
+ +
+
+
+
+ + +
+ +
+
+ + + +
+ + + +
+
+ + diff --git a/addons/mail/static/src/components/discuss_sidebar_category/tests/discuss_sidebar_category_tests.js b/addons/mail/static/src/components/discuss_sidebar_category/tests/discuss_sidebar_category_tests.js new file mode 100644 index 00000000000..14bf86bc357 --- /dev/null +++ b/addons/mail/static/src/components/discuss_sidebar_category/tests/discuss_sidebar_category_tests.js @@ -0,0 +1,834 @@ +/** @odoo-module **/ + +import { + afterEach, + afterNextRender, + beforeEach, + start, +} from '@mail/utils/test_utils'; + +QUnit.module('mail', {}, function () { +QUnit.module('components', {}, function () { +QUnit.module('discuss_sidebar_category', {}, function () { +QUnit.module('discuss_sidebar_category_tests.js', { + beforeEach() { + beforeEach(this); + + this.start = async params => { + const { env, widget } = await start(Object.assign({}, params, { + autoOpenDiscuss: true, + data: this.data, + hasDiscuss: true, + })); + this.env = env; + this.widget = widget; + }; + }, + afterEach() { + afterEach(this); + }, +}); + +QUnit.test('channel - counter: should not have a counter if the category is unfolded and without needaction messages', async function (assert) { + assert.expect(1); + + this.data['mail.channel'].records.push({ id: 20 }); + + await this.start(); + assert.strictEqual( + document.querySelectorAll(`.o_DiscussSidebar_categoryChannel .o_DiscussSidebarCategory_counter`).length, + 0, + "should not have a counter if the category is unfolded and without unread messages" + ); +}); + +QUnit.test('channel - counter: should not have a counter if the category is unfolded and with needaction messagens', async function (assert) { + assert.expect(1); + + this.data['mail.channel'].records.push( + { id: 20 }, + { id: 30 }, + ); + this.data['mail.message'].records.push({ + body: "message 1", + id: 100, + model: "mail.channel", + res_id: 20, + }, { + body: "message_2", + id: 200, + model: "mail.channel", + res_id: 30, + }); + this.data['mail.notification'].records.push({ + mail_message_id: 100, + res_partner_id: this.data.currentPartnerId, + }, { + mail_message_id: 200, + res_partner_id: this.data.currentPartnerId, + }); + await this.start(); + assert.strictEqual( + document.querySelectorAll(`.o_DiscussSidebar_categoryChannel .o_DiscussSidebarCategory_counter`).length, + 0, + "should not have a counter if the category is unfolded and with needaction messages", + ); +}); + +QUnit.test('channel - counter: should not have a counter if category is folded and without needaction messages', async function (assert) { + assert.expect(1); + + this.data['mail.channel'].records.push({ id: 20 }); + this.data['res.users.settings'].records.push({ + user_id: this.data.currentUserId, + is_discuss_sidebar_category_channel_open: false, + }); + + await this.start(); + + assert.strictEqual( + document.querySelectorAll(`.o_DiscussSidebar_categoryChannel .o_DiscussSidebarCategory_counter`).length, + 0, + "should not have a counter if the category is folded and without unread messages" + ); +}); + +QUnit.test('channel - counter: should have correct value of needaction threads if category is folded and with needaction messages', async function (assert) { + assert.expect(1); + + this.data['mail.channel'].records.push( + { id: 20 }, + { id: 30 }, + ); + this.data['mail.message'].records.push({ + body: "message 1", + id: 100, + model: "mail.channel", + res_id: 20, + }, { + body: "message_2", + id: 200, + model: "mail.channel", + res_id: 30, + }); + this.data['mail.notification'].records.push({ + mail_message_id: 100, + res_partner_id: this.data.currentPartnerId, + }, { + mail_message_id: 200, + res_partner_id: this.data.currentPartnerId, + }); + this.data['res.users.settings'].records.push({ + user_id: this.data.currentUserId, + is_discuss_sidebar_category_channel_open: false, + }); + await this.start(); + + assert.strictEqual( + document.querySelector(`.o_DiscussSidebar_categoryChannel .o_DiscussSidebarCategory_counter`).textContent, + "2", + "should have correct value of needaction threads if category is folded and with needaction messages" + ); +}); + +QUnit.test('channel - command: should have view command when category is unfolded', async function (assert) { + assert.expect(1); + + await this.start(); + + assert.strictEqual( + document.querySelectorAll(`.o_DiscussSidebar_categoryChannel .o_DiscussSidebarCategory_header .o_DiscussSidebarCategory_commandView`).length, + 1, + "should have view command when channel category is open" + ); +}); + +QUnit.test('channel - command: should have view command when category is folded', async function (assert) { + assert.expect(1); + + this.data['res.users.settings'].records.push({ + user_id: this.data.currentUserId, + is_discuss_sidebar_category_channel_open: false, + }); + await this.start(); + + await afterNextRender(() => { + document.querySelector(`.o_DiscussSidebar_categoryChannel .o_DiscussSidebarCategory_title`).click(); + }); + assert.strictEqual( + document.querySelectorAll(`.o_DiscussSidebar_categoryChannel .o_DiscussSidebarCategory_header .o_DiscussSidebarCategory_commandView`).length, + 1, + "should have view command when channel category is closed" + ); +}); + +QUnit.test('channel - command: should have add command when category is unfolded', async function (assert) { + assert.expect(1); + + await this.start(); + + assert.strictEqual( + document.querySelectorAll(`.o_DiscussSidebar_categoryChannel .o_DiscussSidebarCategory_header .o_DiscussSidebarCategory_commandAdd`).length, + 1, + "should have add command when channel category is open" + ); +}); + +QUnit.test('channel - command: should not have add command when category is folded', async function (assert) { + assert.expect(1); + + this.data['res.users.settings'].records.push({ + user_id: this.data.currentUserId, + is_discuss_sidebar_category_channel_open: false, + }); + await this.start(); + + assert.strictEqual( + document.querySelectorAll(`.o_DiscussSidebar_categoryChannel .o_DiscussSidebarCategory_header .o_DiscussSidebarCategory_commandAdd`).length, + 0, + "should not have add command when channel category is closed" + ); +}); + +QUnit.test('channel - states: close manually by clicking the title', async function (assert) { + assert.expect(1); + + this.data['mail.channel'].records.push({ id: 20 }); + this.data['res.users.settings'].records.push({ + user_id: this.data.currentUserId, + is_discuss_sidebar_category_channel_open: true, + }); + await this.start(); + + await afterNextRender(() => { + document.querySelector(`.o_DiscussSidebar_categoryChannel .o_DiscussSidebarCategory_title`).click(); + }); + assert.containsNone( + document.body, + `.o_DiscussSidebarCategoryItem[data-thread-local-id="${ + this.messaging.models['mail.thread'].findFromIdentifyingData({ + id: 20, + model: 'mail.channel', + }).localId + }"]`, + "Category channel should be closed and the content should be invisible" + ); +}); + +QUnit.test('channel - states: open manually by clicking the title', async function (assert) { + assert.expect(1); + + this.data['mail.channel'].records.push({ id: 20 }); + this.data['res.users.settings'].records.push({ + user_id: this.data.currentUserId, + is_discuss_sidebar_category_channel_open: false, + }); + await this.start(); + + await afterNextRender(() => { + document.querySelector(`.o_DiscussSidebar_categoryChannel .o_DiscussSidebarCategory_title`).click(); + }); + assert.containsOnce( + document.body, + `.o_DiscussSidebarCategoryItem[data-thread-local-id="${ + this.messaging.models['mail.thread'].findFromIdentifyingData({ + id: 20, + model: 'mail.channel', + }).localId + }"]`, + "Category channel should be open and the content should be visible" + ); +}); + +QUnit.test('channel - states: close should update the value on the server', async function (assert) { + assert.expect(2); + + this.data['mail.channel'].records.push({ id: 20 }); + this.data['res.users.settings'].records.push({ + user_id: this.data.currentUserId, + is_discuss_sidebar_category_channel_open: true, + }); + const currentUserId = this.data.currentUserId; + await this.start(); + + const initalSettings = await this.env.services.rpc({ + model: 'res.users.settings', + method: '_find_or_create_for_user', + args: [[currentUserId]], + }); + assert.strictEqual( + initalSettings.is_discuss_sidebar_category_channel_open, + true, + "the vaule in server side should be true" + ); + + await afterNextRender(() => { + document.querySelector(`.o_DiscussSidebar_categoryChannel .o_DiscussSidebarCategory_title`).click(); + }); + const newSettings = await this.env.services.rpc({ + model: 'res.users.settings', + method: '_find_or_create_for_user', + args: [[currentUserId]], + }); + assert.strictEqual( + newSettings.is_discuss_sidebar_category_channel_open, + false, + "the vaule in server side should be false" + ); +}); + +QUnit.test('channel - states: open should update the value on the server', async function (assert) { + assert.expect(2); + + this.data['mail.channel'].records.push({ id: 20 }); + this.data['res.users.settings'].records.push({ + user_id: this.data.currentUserId, + is_discuss_sidebar_category_channel_open: false, + }); + const currentUserId = this.data.currentUserId; + await this.start(); + + const initalSettings = await this.env.services.rpc({ + model: 'res.users.settings', + method: '_find_or_create_for_user', + args: [[currentUserId]], + }); + assert.strictEqual( + initalSettings.is_discuss_sidebar_category_channel_open, + false, + "the vaule in server side should be false" + ); + + await afterNextRender(() => { + document.querySelector(`.o_DiscussSidebar_categoryChannel .o_DiscussSidebarCategory_title`).click(); + }); + const newSettings = await this.env.services.rpc({ + model: 'res.users.settings', + method: '_find_or_create_for_user', + args: [[currentUserId]], + }); + assert.strictEqual( + newSettings.is_discuss_sidebar_category_channel_open, + true, + "the vaule in server side should be false" + ); +}); + +QUnit.test('channel - states: close from the bus', async function (assert) { + assert.expect(1); + + this.data['mail.channel'].records.push({ id: 20 }); + this.data['res.users.settings'].records.push({ + user_id: this.data.currentUserId, + is_discuss_sidebar_category_channel_open: true, + }); + await this.start(); + + await afterNextRender(() => { + const notif = [ + ["dbName", "res.partner", this.messaging.currentPartner.id], + { + type: "res.users_settings_changed", + payload: { + is_discuss_sidebar_category_channel_open: false, + }, + }, + ]; + this.env.services.bus_service.trigger('notification', [notif]); + }); + assert.containsNone( + document.body, + `.o_DiscussSidebarCategoryItem[data-thread-local-id="${ + this.messaging.models['mail.thread'].findFromIdentifyingData({ + id: 20, + model: 'mail.channel', + }).localId + }"]`, + "Category channel should be closed and the content should be invisible" + ); +}); + +QUnit.test('channel - states: open from the bus', async function (assert) { + assert.expect(1); + + this.data['mail.channel'].records.push({ id: 20 }); + this.data['res.users.settings'].records.push({ + user_id: this.data.currentUserId, + is_discuss_sidebar_category_channel_open: false, + }); + await this.start(); + + await afterNextRender(() => { + const notif = [ + ["dbName", "res.partner", this.messaging.currentPartner.id], + { + type: "res.users_settings_changed", + payload: { + is_discuss_sidebar_category_channel_open: true, + }, + }, + ]; + this.env.services.bus_service.trigger('notification', [notif]); + }); + assert.containsOnce( + document.body, + `.o_DiscussSidebarCategoryItem[data-thread-local-id="${ + this.messaging.models['mail.thread'].findFromIdentifyingData({ + id: 20, + model: 'mail.channel', + }).localId + }"]`, + "Category channel should be open and the content should be visible" + ); +}); + +QUnit.test('channel - states: the active category item should be visble even if the category is closed', async function (assert) { + assert.expect(4); + + this.data['mail.channel'].records.push({ id: 20 }); + await this.start(); + + assert.containsOnce( + document.body, + `.o_DiscussSidebarCategoryItem[data-thread-local-id="${ + this.messaging.models['mail.thread'].findFromIdentifyingData({ + id: 20, + model: 'mail.channel', + }).localId + }"]` + ); + + const channel = document.querySelector(`.o_DiscussSidebarCategoryItem[data-thread-local-id="${ + this.messaging.models['mail.thread'].findFromIdentifyingData({ + id: 20, + model: 'mail.channel', + }).localId + }"]`); + await afterNextRender(() => { + channel.click(); + }); + assert.ok(channel.classList.contains('o-active')); + + await afterNextRender(() => { + document.querySelector(`.o_DiscussSidebar_categoryChannel .o_DiscussSidebarCategory_title`).click(); + }); + + assert.containsOnce( + document.body, + `.o_DiscussSidebarCategoryItem[data-thread-local-id="${ + this.messaging.models['mail.thread'].findFromIdentifyingData({ + id: 20, + model: 'mail.channel', + }).localId + }"]`, + 'the active channel item should remain even if the category is folded' + ); + + await afterNextRender(() => { + document.querySelector(`.o_DiscussSidebarMailbox[data-thread-local-id="${ + this.messaging.inbox.localId + }"]`).click(); + }); + + assert.containsNone( + document.body, + `.o_DiscussSidebarCategoryItem[data-thread-local-id="${ + this.messaging.models['mail.thread'].findFromIdentifyingData({ + id: 20, + model: 'mail.channel', + }).localId + }"]`, + "inactive item should be invisible if the category is folded" + ); +}); + +QUnit.test('chat - counter: should not have a counter if the category is unfolded and without unread messages', async function (assert) { + assert.expect(1); + + this.data['mail.channel'].records.push({ + channel_type: 'chat', + id: 10, + message_unread_counter: 0, + public: 'private', + }); + + await this.start(); + assert.strictEqual( + document.querySelectorAll(`.o_DiscussSidebar_categoryChat .o_DiscussSidebarCategory_counter`).length, + 0, + "should not have a counter if the category is unfolded and without unread messages", + ); +}); + +QUnit.test('chat - counter: should not have a counter if the category is unfolded and with unread messagens', async function (assert) { + assert.expect(1); + + this.data['mail.channel'].records.push({ + channel_type: 'chat', + id: 10, + message_unread_counter: 10, + public: 'private', + }); + await this.start(); + assert.strictEqual( + document.querySelectorAll(`.o_DiscussSidebar_categoryChat .o_DiscussSidebarCategory_counter`).length, + 0, + "should not have a counter if the category is unfolded and with unread messages", + ); +}); + +QUnit.test('chat - counter: should not have a counter if category is folded and without unread messages', async function (assert) { + assert.expect(1); + + this.data['mail.channel'].records.push({ + channel_type: 'chat', + id: 10, + message_unread_counter: 0, + public: 'private', + }); + + await this.start(); + + await afterNextRender(() => { + document.querySelector(`.o_DiscussSidebar_categoryChat .o_DiscussSidebarCategory_title`).click(); + }); + assert.strictEqual( + document.querySelectorAll(`.o_DiscussSidebar_categoryChat .o_DiscussSidebarCategory_counter`).length, + 0, + "should not have a counter if the category is folded and without unread messages" + ); +}); + +QUnit.test('chat - counter: should have correct value of unread threads if category is folded and with unread messages', async function (assert) { + assert.expect(1); + + this.data['mail.channel'].records.push({ + channel_type: 'chat', + id: 10, + message_unread_counter: 10, + public: 'private', + }, { + channel_type: 'chat', + id: 20, + message_unread_counter: 20, + public: 'private', + }); + await this.start(); + + await afterNextRender(() => { + document.querySelector(`.o_DiscussSidebar_categoryChat .o_DiscussSidebarCategory_title`).click(); + }); + assert.strictEqual( + document.querySelector(`.o_DiscussSidebar_categoryChat .o_DiscussSidebarCategory_counter`).textContent, + "2", + "should have correct value of unread threads if category is folded and with unread messages" + ); +}); + +QUnit.test('chat - command: should have add command when category is unfolded', async function (assert) { + assert.expect(1); + + await this.start(); + assert.strictEqual( + document.querySelectorAll(`.o_DiscussSidebar_categoryChat .o_DiscussSidebarCategory_header .o_DiscussSidebarCategory_commandAdd`).length, + 1, + "should have add command when chat category is open" + ); +}); + +QUnit.test('chat - command: should not have add command when category is folded', async function (assert) { + assert.expect(1); + + this.data['res.users.settings'].records.push({ + user_id: this.data.currentUserId, + is_discuss_sidebar_category_chat_open: false, + }); + await this.start(); + + assert.strictEqual( + document.querySelectorAll(`.o_DiscussSidebar_categoryChat .o_DiscussSidebarCategory_header .o_DiscussSidebarCategory_commandAdd`).length, + 0, + "should not have add command when chat category is closed" + ); +}); + +QUnit.test('chat - states: close manually by clicking the title', async function (assert) { + assert.expect(1); + + this.data['mail.channel'].records.push({ + channel_type: 'chat', + id: 10, + message_unread_counter: 0, + public: 'private', + }); + this.data['res.users.settings'].records.push({ + user_id: this.data.currentUserId, + is_discuss_sidebar_category_chat_open: true, + }); + await this.start(); + + await afterNextRender(() => { + document.querySelector(`.o_DiscussSidebar_categoryChat .o_DiscussSidebarCategory_title`).click(); + }); + assert.containsNone( + document.body, + `.o_DiscussSidebarCategoryItem[data-thread-local-id="${ + this.messaging.models['mail.thread'].findFromIdentifyingData({ + id: 10, + model: 'mail.channel', + }).localId + }"]`, + "Category chat should be closed and the content should be invisible" + ); +}); + +QUnit.test('chat - states: open manually by clicking the title', async function (assert) { + assert.expect(1); + + this.data['mail.channel'].records.push({ + channel_type: 'chat', + id: 10, + message_unread_counter: 0, + public: 'private', + }); + this.data['res.users.settings'].records.push({ + user_id: this.data.currentUserId, + is_discuss_sidebar_category_chat_open: false, + }); + await this.start(); + + await afterNextRender(() => { + document.querySelector(`.o_DiscussSidebar_categoryChat .o_DiscussSidebarCategory_title`).click(); + }); + assert.containsOnce( + document.body, + `.o_DiscussSidebarCategoryItem[data-thread-local-id="${ + this.messaging.models['mail.thread'].findFromIdentifyingData({ + id: 10, + model: 'mail.channel', + }).localId + }"]`, + "Category chat should be closed and the content should be invisible" + ); +}); + +QUnit.test('chat - states: close should call update server data', async function (assert) { + assert.expect(2); + + this.data['mail.channel'].records.push({ id: 20 }); + this.data['res.users.settings'].records.push({ + user_id: this.data.currentUserId, + is_discuss_sidebar_category_chat_open: true, + }); + const currentUserId = this.data.currentUserId; + await this.start(); + + const initalSettings = await this.env.services.rpc({ + model: 'res.users.settings', + method: '_find_or_create_for_user', + args: [[currentUserId]], + }); + assert.strictEqual( + initalSettings.is_discuss_sidebar_category_chat_open, + true, + "the value in server side should be true" + ); + + await afterNextRender(() => { + document.querySelector(`.o_DiscussSidebar_categoryChat .o_DiscussSidebarCategory_title`).click(); + }); + const newSettings = await this.env.services.rpc({ + model: 'res.users.settings', + method: '_find_or_create_for_user', + args: [[currentUserId]], + }); + assert.strictEqual( + newSettings.is_discuss_sidebar_category_chat_open, + false, + "the value in server side should be false" + ); +}); + +QUnit.test('chat - states: open should call update server data', async function (assert) { + assert.expect(2); + + this.data['mail.channel'].records.push({ id: 20 }); + this.data['res.users.settings'].records.push({ + user_id: this.data.currentUserId, + is_discuss_sidebar_category_chat_open: false, + }); + const currentUserId = this.data.currentUserId; + await this.start(); + + const initalSettings = await this.env.services.rpc({ + model: 'res.users.settings', + method: '_find_or_create_for_user', + args: [[currentUserId]], + }); + assert.strictEqual( + initalSettings.is_discuss_sidebar_category_chat_open, + false, + "the value in server side should be false" + ); + + await afterNextRender(() => { + document.querySelector(`.o_DiscussSidebar_categoryChat .o_DiscussSidebarCategory_title`).click(); + }); + const newSettings = await this.env.services.rpc({ + model: 'res.users.settings', + method: '_find_or_create_for_user', + args: [[currentUserId]], + }); + assert.strictEqual( + newSettings.is_discuss_sidebar_category_chat_open, + true, + "the value in server side should be true" + ); +}); + +QUnit.test('chat - states: close from the bus', async function (assert) { + assert.expect(1); + + this.data['mail.channel'].records.push({ + channel_type: 'chat', + id: 10, + message_unread_counter: 0, + public: 'private', + }); + this.data['res.users.settings'].records.push({ + user_id: this.data.currentUserId, + is_discuss_sidebar_category_chat_open: true, + }); + await this.start(); + + await afterNextRender(() => { + const notif = [ + ["dbName", "res.partner", this.messaging.currentPartner.id], + { + type: "res.users_settings_changed", + payload: { + is_discuss_sidebar_category_chat_open: false, + }, + }, + ]; + this.env.services.bus_service.trigger('notification', [notif]); + }); + assert.containsNone( + document.body, + `.o_DiscussSidebarCategoryItem[data-thread-local-id="${ + this.messaging.models['mail.thread'].findFromIdentifyingData({ + id: 10, + model: 'mail.channel', + }).localId + }"]`, + "Category chat should be open and the content should be visible" + ); +}); + +QUnit.test('chat - states: open from the bus', async function (assert) { + assert.expect(1); + + this.data['mail.channel'].records.push({ + channel_type: 'chat', + id: 10, + message_unread_counter: 0, + public: 'private', + }); + this.data['res.users.settings'].records.push({ + user_id: this.data.currentUserId, + is_discuss_sidebar_category_chat_open: false, + }); + await this.start(); + + await afterNextRender(() => { + const notif = [ + ["dbName", "res.partner", this.messaging.currentPartner.id], + { + type: "res.users_settings_changed", + payload: { + is_discuss_sidebar_category_chat_open: true, + }, + }, + ]; + this.env.services.bus_service.trigger('notification', [notif]); + }); + assert.containsOnce( + document.body, + `.o_DiscussSidebarCategoryItem[data-thread-local-id="${ + this.messaging.models['mail.thread'].findFromIdentifyingData({ + id: 10, + model: 'mail.channel', + }).localId + }"]`, + "Category chat should be closed and the content should be invisible" + ); +}); + +QUnit.test('chat - states: the active category item should be visble even if the category is closed', async function (assert) { + assert.expect(4); + + this.data['mail.channel'].records.push({ + channel_type: 'chat', + id: 10, + message_unread_counter: 0, + public: 'private', + }); + await this.start(); + + assert.containsOnce( + document.body, + `.o_DiscussSidebarCategoryItem[data-thread-local-id="${ + this.messaging.models['mail.thread'].findFromIdentifyingData({ + id: 10, + model: 'mail.channel', + }).localId + }"]` + ); + + const chat = document.querySelector(`.o_DiscussSidebarCategoryItem[data-thread-local-id="${ + this.messaging.models['mail.thread'].findFromIdentifyingData({ + id: 10, + model: 'mail.channel', + }).localId + }"]`); + await afterNextRender(() => { + chat.click(); + }); + assert.ok(chat.classList.contains('o-active')); + + await afterNextRender(() => { + document.querySelector(`.o_DiscussSidebar_categoryChat .o_DiscussSidebarCategory_title`).click(); + }); + + assert.containsOnce( + document.body, + `.o_DiscussSidebarCategoryItem[data-thread-local-id="${ + this.messaging.models['mail.thread'].findFromIdentifyingData({ + id: 10, + model: 'mail.channel', + }).localId + }"]`, + 'the active chat item should remain even if the category is folded' + ); + + await afterNextRender(() => { + document.querySelector(`.o_DiscussSidebarMailbox[data-thread-local-id="${ + this.messaging.inbox.localId + }"]`).click(); + }); + + assert.containsNone( + document.body, + `.o_DiscussSidebarCategoryItem[data-thread-local-id="${ + this.messaging.models['mail.thread'].findFromIdentifyingData({ + id: 10, + model: 'mail.channel', + }).localId + }"]`, + "inactive item should be invisible if the category is folded" + ); +}); + +}); +}); +}); diff --git a/addons/mail/static/src/components/discuss_sidebar_category_item/discuss_sidebar_category_item.js b/addons/mail/static/src/components/discuss_sidebar_category_item/discuss_sidebar_category_item.js new file mode 100644 index 00000000000..88ddec25106 --- /dev/null +++ b/addons/mail/static/src/components/discuss_sidebar_category_item/discuss_sidebar_category_item.js @@ -0,0 +1,28 @@ +/** @odoo-module **/ + +import { registerMessagingComponent } from '@mail/utils/messaging_component'; + +const { Component } = owl; + +export class DiscussSidebarCategoryItem extends Component { + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + /** + * @returns {mail.discuss_sidebar_category_item} + */ + get categoryItem() { + return this.messaging.models['mail.discuss_sidebar_category_item'].get(this.props.categoryItemLocalId); + } +} + +Object.assign(DiscussSidebarCategoryItem, { + props: { + categoryItemLocalId: String, + }, + template: 'mail.DiscussSidebarCategoryItem', +}); + +registerMessagingComponent(DiscussSidebarCategoryItem); diff --git a/addons/mail/static/src/components/discuss_sidebar_category_item/discuss_sidebar_category_item.scss b/addons/mail/static/src/components/discuss_sidebar_category_item/discuss_sidebar_category_item.scss new file mode 100644 index 00000000000..f06ba7c83d6 --- /dev/null +++ b/addons/mail/static/src/components/discuss_sidebar_category_item/discuss_sidebar_category_item.scss @@ -0,0 +1,125 @@ +// ------------------------------------------------------------------ +// Layout +// ------------------------------------------------------------------ + +.o_DiscussSidebarCategoryItem { + cursor: pointer; + display: flex; + align-items: center; + padding: map-get($spacers, 2) 0; + + &:hover .o_DiscussSidebarCategoryItem_commands { + display: flex; + } +} + +.o_DiscussSidebarCategoryItem_command { + margin-left: $o-mail-discuss-sidebar-category-item-margin; + margin-right: $o-mail-discuss-sidebar-category-item-margin; + + &:first-child { + margin-left: 0px; + } + + &:last-child { + margin-right: 0px; + } +} + +.o_DiscussSidebarCategoryItem_commands { + display: none; +} + +.o_DiscussSidebarCategoryItem_image { + object-fit: cover; + width: map-get($sizes, 100); + height: map-get($sizes, 100); +} + + +.o_DiscussSidebarCategoryItem_imageContainer { + position: relative; + width: $o-mail-discuss-sidebar-category-item-avatar-size; + height: $o-mail-discuss-sidebar-category-item-avatar-size; +} + +.o_DiscussSidebarCategoryItem_item { + margin-left: $o-mail-discuss-sidebar-category-item-margin; + margin-right: $o-mail-discuss-sidebar-category-item-margin; + + &:first-child { + margin-left: $o-mail-discuss-sidebar-category-item-avatar-left-margin; + } + + &:last-child { + margin-right: $o-mail-discuss-sidebar-scrollbar-width; + } +} + +.o_DiscussSidebarCategoryItem_name { + @include text-truncate(); + margin-left: 10px; + + &.o-editable { + margin-right: $o-mail-discuss-sidebar-scrollbar-width; + } +} + +.o_DiscussSidebarCategoryItem_nameInput { + width: 100%; +} + +.o_DiscussSidebarCategoryItem_threadIcon { + @include o-position-absolute($bottom: 0, $right: 0); + + display: flex; + align-items: center; + justify-content: center; + width: 13px; + height: 13px; +} + +// ------------------------------------------------------------------ +// Style +// ------------------------------------------------------------------ + +.o_DiscussSidebarCategoryItem { + + .o_DiscussSidebarCategoryItem_threadIcon { + font-size: xx-small; + background-color: gray('100'); + border-radius: 50%; + } + + &.o-active { + background-color: gray('200'); + } + + &:hover { + background-color: gray('300'); + } +} + +.o_DiscussSidebarCategoryItem_command { + + &:not(:hover) { + color: gray('600'); + } +} + +.o_DiscussSidebarCategoryItem_counter { + background-color: $o-brand-primary; + color: gray('300'); +} + +.o_DiscussSidebarCategoryItem_name { + &.o-item-unread { + font-weight: bold; + } +} + +.o_DiscussSidebarCategoryItem_nameInput{ + outline: none; + border: none; + border-radius: 2px; +} diff --git a/addons/mail/static/src/components/discuss_sidebar_category_item/discuss_sidebar_category_item.xml b/addons/mail/static/src/components/discuss_sidebar_category_item/discuss_sidebar_category_item.xml new file mode 100644 index 00000000000..48bbedc780c --- /dev/null +++ b/addons/mail/static/src/components/discuss_sidebar_category_item/discuss_sidebar_category_item.xml @@ -0,0 +1,57 @@ + + + +
+
+
+ Thread Image + + + +
+
+ + + + + + + + + + +
+
+ +