diff options
| author | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
|---|---|---|
| committer | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
| commit | 3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch) | |
| tree | a44932296ef4a9b71d5f010906253d8c53727726 /addons/mail/static/src/components/chatter_topbar | |
| parent | 0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff) | |
initial commit 2
Diffstat (limited to 'addons/mail/static/src/components/chatter_topbar')
4 files changed, 1047 insertions, 0 deletions
diff --git a/addons/mail/static/src/components/chatter_topbar/chatter_topbar.js b/addons/mail/static/src/components/chatter_topbar/chatter_topbar.js new file mode 100644 index 00000000..41d2a461 --- /dev/null +++ b/addons/mail/static/src/components/chatter_topbar/chatter_topbar.js @@ -0,0 +1,137 @@ +odoo.define('mail/static/src/components/chatter_topbar/chatter_topbar.js', function (require) { +'use strict'; + +const components = { + FollowButton: require('mail/static/src/components/follow_button/follow_button.js'), + FollowerListMenu: require('mail/static/src/components/follower_list_menu/follower_list_menu.js'), +}; +const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js'); +const useStore = require('mail/static/src/component_hooks/use_store/use_store.js'); + +const { Component } = owl; + +class ChatterTopbar extends Component { + + /** + * @override + */ + constructor(...args) { + super(...args); + useShouldUpdateBasedOnProps(); + useStore(props => { + const chatter = this.env.models['mail.chatter'].get(props.chatterLocalId); + const thread = chatter ? chatter.thread : undefined; + const threadAttachments = thread ? thread.allAttachments : []; + return { + areThreadAttachmentsLoaded: thread && thread.areAttachmentsLoaded, + chatter: chatter ? chatter.__state : undefined, + composerIsLog: chatter && chatter.composer && chatter.composer.isLog, + threadAttachmentsAmount: threadAttachments.length, + }; + }); + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + /** + * @returns {mail.chatter} + */ + get chatter() { + return this.env.models['mail.chatter'].get(this.props.chatterLocalId); + } + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * @private + * @param {MouseEvent} ev + */ + _onClickAttachments(ev) { + this.chatter.update({ + isAttachmentBoxVisible: !this.chatter.isAttachmentBoxVisible, + }); + } + + /** + * @private + * @param {MouseEvent} ev + */ + _onClickClose(ev) { + this.trigger('o-close-chatter'); + } + + /** + * @private + * @param {MouseEvent} ev + */ + _onClickLogNote(ev) { + if (!this.chatter.composer) { + return; + } + if (this.chatter.isComposerVisible && this.chatter.composer.isLog) { + this.chatter.update({ isComposerVisible: false }); + } else { + this.chatter.showLogNote(); + } + } + + /** + * @private + * @param {MouseEvent} ev + */ + _onClickScheduleActivity(ev) { + const action = { + type: 'ir.actions.act_window', + name: this.env._t("Schedule Activity"), + res_model: 'mail.activity', + view_mode: 'form', + views: [[false, 'form']], + target: 'new', + context: { + default_res_id: this.chatter.thread.id, + default_res_model: this.chatter.thread.model, + }, + res_id: false, + }; + return this.env.bus.trigger('do-action', { + action, + options: { + on_close: () => { + this.trigger('reload', { keepChanges: true }); + }, + }, + }); + } + + /** + * @private + * @param {MouseEvent} ev + */ + _onClickSendMessage(ev) { + if (!this.chatter.composer) { + return; + } + if (this.chatter.isComposerVisible && !this.chatter.composer.isLog) { + this.chatter.update({ isComposerVisible: false }); + } else { + this.chatter.showSendMessage(); + } + } + +} + +Object.assign(ChatterTopbar, { + components, + props: { + chatterLocalId: String, + }, + template: 'mail.ChatterTopbar', +}); + +return ChatterTopbar; + +}); diff --git a/addons/mail/static/src/components/chatter_topbar/chatter_topbar.scss b/addons/mail/static/src/components/chatter_topbar/chatter_topbar.scss new file mode 100644 index 00000000..062e5219 --- /dev/null +++ b/addons/mail/static/src/components/chatter_topbar/chatter_topbar.scss @@ -0,0 +1,106 @@ +// ------------------------------------------------------------------ +// Layout +// ------------------------------------------------------------------ + +.o_ChatterTopbar { + display: flex; + flex-direction: row; + justify-content: space-between; + // We need the +1 to handle the status bar border-bottom. + // The var is called `$o-statusbar-height`, but is used on button, therefore + // doesn't include the border-bottom. + // We use min-height to allow multiples buttons lines on mobile. + min-height: $o-statusbar-height + 1; +} + +.o_ChatterTopbar_actions { + border-bottom: $border-width solid; + display: flex; + flex: 1; + flex-direction: row; + flex-wrap: wrap-reverse; // reverse to ensure send buttons are directly above composer +} + +.o_ChatterTopbar_button { + margin-bottom: -$border-width; /* Needed to allow "overriding" of the bottom border */ +} + +.o_ChatterTopbar_buttonAttachmentsCountLoader { + margin-left: 2px; +} + +.o_ChatterTopbar_buttonCount { + padding-left: 0.25rem; +} + +.o_ChatterTopbar_buttonClose { + display: flex; + flex-shrink: 0; + justify-content: center; + align-items: center; + width: 34px; + height: 34px; +} + +.o_ChatterTopbar_followerListMenu { + display: flex; +} + +.o_ChatterTopbar_rightSection { + display: flex; + flex: 1 0 auto; + justify-content: flex-end; +} + +// ------------------------------------------------------------------ +// Style +// ------------------------------------------------------------------ + +.o_ChatterTopbar_actions { + border-color: transparent; + + &.o-has-active-button { + border-color: $border-color; + } +} + +.o_ChatterTopbar_button { + border-radius: 0; + + &:hover { + background-color: gray('300'); + } + + &.o-active { + color: $o-brand-odoo; + background-color: lighten(gray('300'), 7%); + border-right-color: $border-color; + + &:not(:first-of-type), + &:first-of-type.o-bordered { + border-left-color: $border-color; + } + + &.o-bordered { + border-top-color: $border-color; + } + + &:hover { + background-color: gray('300'); + color: $link-hover-color; + } + } +} + +.o_ChatterTopbar_buttonClose { + border-radius: 0 0 10px 10px; + font-size: $font-size-lg; + background-color: gray('700'); + color: gray('100'); + cursor: pointer; + + &:hover { + background-color: gray('600'); + color: $white; + } +} diff --git a/addons/mail/static/src/components/chatter_topbar/chatter_topbar.xml b/addons/mail/static/src/components/chatter_topbar/chatter_topbar.xml new file mode 100644 index 00000000..5d4029e6 --- /dev/null +++ b/addons/mail/static/src/components/chatter_topbar/chatter_topbar.xml @@ -0,0 +1,74 @@ +<?xml version="1.0" encoding="UTF-8"?> +<templates xml:space="preserve"> + + <t t-name="mail.ChatterTopbar" owl="1"> + <div class="o_ChatterTopbar"> + <t t-if="chatter"> + <div class="o_ChatterTopbar_actions" t-att-class="{'o-has-active-button': chatter.isComposerVisible }"> + <t t-if="chatter.threadView"> + <button class="btn btn-link o_ChatterTopbar_button o_ChatterTopbar_buttonSendMessage" + type="button" + t-att-class="{ + 'o-active': chatter.isComposerVisible and chatter.composer and !chatter.composer.isLog, + 'o-bordered': chatter.hasExternalBorder, + }" + t-att-disabled="chatter.isDisabled" + t-on-click="_onClickSendMessage" + > + Send message + </button> + <button class="btn btn-link o_ChatterTopbar_button o_ChatterTopbar_buttonLogNote" + type="button" + t-att-class="{ + 'o-active': chatter.isComposerVisible and chatter.composer and chatter.composer.isLog, + 'o-bordered': chatter.hasExternalBorder, + }" + t-att-disabled="chatter.isDisabled" + t-on-click="_onClickLogNote" + > + Log note + </button> + </t> + <t t-if="chatter.hasActivities"> + <button class="btn btn-link o_ChatterTopbar_button o_ChatterTopbar_buttonScheduleActivity" type="button" t-att-disabled="chatter.isDisabled" t-on-click="_onClickScheduleActivity"> + <i class="fa fa-clock-o"/> + Schedule activity + </button> + </t> + <div class="o-autogrow"/> + <div class="o_ChatterTopbar_rightSection"> + <button class="btn btn-link o_ChatterTopbar_button o_ChatterTopbar_buttonAttachments" type="button" t-att-disabled="chatter.isDisabled" t-on-click="_onClickAttachments"> + <i class="fa fa-paperclip"/> + <t t-if="chatter.isDisabled or !chatter.isShowingAttachmentsLoading"> + <span class="o_ChatterTopbar_buttonCount o_ChatterTopbar_buttonAttachmentsCount" t-esc="chatter.thread ? chatter.thread.allAttachments.length : 0"/> + </t> + <t t-else=""> + <i class="o_ChatterTopbar_buttonAttachmentsCountLoader fa fa-spinner fa-spin" aria-label="Attachment counter loading..."/> + </t> + </button> + <t t-if="chatter.hasFollowers and chatter.thread"> + <t t-if="chatter.thread.channel_type !== 'chat'"> + <FollowButton + class="o_ChatterTopbar_button o_ChatterTopbar_followButton" + isDisabled="chatter.isDisabled" + threadLocalId="chatter.thread.localId" + /> + </t> + <FollowerListMenu + class="o_ChatterTopbar_button o_ChatterTopbar_followerListMenu" + isDisabled="chatter.isDisabled" + threadLocalId="chatter.thread.localId" + /> + </t> + </div> + </div> + <t t-if="chatter.hasTopbarCloseButton"> + <div class="o_ChatterTopbar_buttonClose" title="Close" t-on-click="_onClickClose"> + <i class="fa fa-times"/> + </div> + </t> + </t> + </div> + </t> + +</templates> diff --git a/addons/mail/static/src/components/chatter_topbar/chatter_topbar_tests.js b/addons/mail/static/src/components/chatter_topbar/chatter_topbar_tests.js new file mode 100644 index 00000000..3063b389 --- /dev/null +++ b/addons/mail/static/src/components/chatter_topbar/chatter_topbar_tests.js @@ -0,0 +1,730 @@ +odoo.define('mail/static/src/components/chatter_topbar/chatter_topbar_tests.js', function (require) { +'use strict'; + +const components = { + ChatterTopBar: require('mail/static/src/components/chatter_topbar/chatter_topbar.js'), +}; +const { + afterEach, + afterNextRender, + beforeEach, + createRootComponent, + start, +} = require('mail/static/src/utils/test_utils.js'); + +const { makeTestPromise } = require('web.test_utils'); + +QUnit.module('mail', {}, function () { +QUnit.module('components', {}, function () { +QUnit.module('chatter_topbar', {}, function () { +QUnit.module('chatter_topbar_tests.js', { + beforeEach() { + beforeEach(this); + + this.createChatterTopbarComponent = async (chatter, otherProps) => { + const props = Object.assign({ chatterLocalId: chatter.localId }, otherProps); + await createRootComponent(this, components.ChatterTopBar, { + props, + target: this.widget.el, + }); + }; + + this.start = async params => { + const { env, widget } = await start(Object.assign({}, params, { + data: this.data, + })); + this.env = env; + this.widget = widget; + }; + }, + afterEach() { + afterEach(this); + }, +}); + +QUnit.test('base rendering', async function (assert) { + assert.expect(8); + + this.data['res.partner'].records.push({ id: 100 }); + await this.start(); + const chatter = this.env.models['mail.chatter'].create({ + threadId: 100, + threadModel: 'res.partner', + }); + await this.createChatterTopbarComponent(chatter); + + assert.strictEqual( + document.querySelectorAll(`.o_ChatterTopbar`).length, + 1, + "should have a chatter topbar" + ); + assert.strictEqual( + document.querySelectorAll(`.o_ChatterTopbar_buttonSendMessage`).length, + 1, + "should have a send message button in chatter menu" + ); + assert.strictEqual( + document.querySelectorAll(`.o_ChatterTopbar_buttonLogNote`).length, + 1, + "should have a log note button in chatter menu" + ); + assert.strictEqual( + document.querySelectorAll(`.o_ChatterTopbar_buttonScheduleActivity`).length, + 1, + "should have a schedule activity button in chatter menu" + ); + assert.strictEqual( + document.querySelectorAll(`.o_ChatterTopbar_buttonAttachments`).length, + 1, + "should have an attachments button in chatter menu" + ); + assert.strictEqual( + document.querySelectorAll(`.o_ChatterTopbar_buttonAttachmentsCountLoader`).length, + 0, + "attachments button should not have a loader" + ); + assert.strictEqual( + document.querySelectorAll(`.o_ChatterTopbar_buttonAttachmentsCount`).length, + 1, + "attachments button should have a counter" + ); + assert.strictEqual( + document.querySelectorAll(`.o_ChatterTopbar_followerListMenu`).length, + 1, + "should have a follower menu" + ); +}); + +QUnit.test('base disabled rendering', async function (assert) { + assert.expect(8); + + await this.start(); + const chatter = this.env.models['mail.chatter'].create({ + threadModel: 'res.partner', + }); + await this.createChatterTopbarComponent(chatter); + assert.strictEqual( + document.querySelectorAll(`.o_ChatterTopbar`).length, + 1, + "should have a chatter topbar" + ); + assert.ok( + document.querySelector(`.o_ChatterTopbar_buttonSendMessage`).disabled, + "send message button should be disabled" + ); + assert.ok( + document.querySelector(`.o_ChatterTopbar_buttonLogNote`).disabled, + "log note button should be disabled" + ); + assert.ok( + document.querySelector(`.o_ChatterTopbar_buttonScheduleActivity`).disabled, + "schedule activity should be disabled" + ); + assert.ok( + document.querySelector(`.o_ChatterTopbar_buttonAttachments`).disabled, + "attachments button should be disabled" + ); + assert.strictEqual( + document.querySelectorAll(`.o_ChatterTopbar_buttonAttachmentsCountLoader`).length, + 0, + "attachments button should not have a loader" + ); + assert.strictEqual( + document.querySelectorAll(`.o_ChatterTopbar_buttonAttachmentsCount`).length, + 1, + "attachments button should have a counter" + ); + assert.strictEqual( + document.querySelector(`.o_ChatterTopbar_buttonAttachmentsCount`).textContent, + '0', + "attachments button counter should be 0" + ); +}); + +QUnit.test('attachment loading is delayed', async function (assert) { + assert.expect(4); + + this.data['res.partner'].records.push({ id: 100 }); + await this.start({ + hasTimeControl: true, + loadingBaseDelayDuration: 100, + async mockRPC(route) { + if (route.includes('ir.attachment/search_read')) { + await makeTestPromise(); // simulate long loading + } + return this._super(...arguments); + } + }); + const chatter = this.env.models['mail.chatter'].create({ + threadId: 100, + threadModel: 'res.partner', + }); + await this.createChatterTopbarComponent(chatter); + + assert.strictEqual( + document.querySelectorAll(`.o_ChatterTopbar`).length, + 1, + "should have a chatter topbar" + ); + assert.strictEqual( + document.querySelectorAll(`.o_ChatterTopbar_buttonAttachments`).length, + 1, + "should have an attachments button in chatter menu" + ); + assert.strictEqual( + document.querySelectorAll(`.o_ChatterTopbar_buttonAttachmentsCountLoader`).length, + 0, + "attachments button should not have a loader yet" + ); + + await afterNextRender(async () => this.env.testUtils.advanceTime(100)); + assert.strictEqual( + document.querySelectorAll(`.o_ChatterTopbar_buttonAttachmentsCountLoader`).length, + 1, + "attachments button should now have a loader" + ); +}); + +QUnit.test('attachment counter while loading attachments', async function (assert) { + assert.expect(4); + + this.data['res.partner'].records.push({ id: 100 }); + await this.start({ + async mockRPC(route) { + if (route.includes('ir.attachment/search_read')) { + await makeTestPromise(); // simulate long loading + } + return this._super(...arguments); + } + }); + const chatter = this.env.models['mail.chatter'].create({ + threadId: 100, + threadModel: 'res.partner', + }); + await this.createChatterTopbarComponent(chatter); + + assert.strictEqual( + document.querySelectorAll(`.o_ChatterTopbar`).length, + 1, + "should have a chatter topbar" + ); + assert.strictEqual( + document.querySelectorAll(`.o_ChatterTopbar_buttonAttachments`).length, + 1, + "should have an attachments button in chatter menu" + ); + assert.strictEqual( + document.querySelectorAll(`.o_ChatterTopbar_buttonAttachmentsCountLoader`).length, + 1, + "attachments button should have a loader" + ); + assert.strictEqual( + document.querySelectorAll(`.o_ChatterTopbar_buttonAttachmentsCount`).length, + 0, + "attachments button should not have a counter" + ); +}); + +QUnit.test('attachment counter transition when attachments become loaded)', async function (assert) { + assert.expect(7); + + this.data['res.partner'].records.push({ id: 100 }); + const attachmentPromise = makeTestPromise(); + await this.start({ + async mockRPC(route) { + const _super = this._super.bind(this, ...arguments); // limitation of class.js + if (route.includes('ir.attachment/search_read')) { + await attachmentPromise; + } + return _super(); + }, + }); + const chatter = this.env.models['mail.chatter'].create({ + threadId: 100, + threadModel: 'res.partner', + }); + await this.createChatterTopbarComponent(chatter); + + assert.strictEqual( + document.querySelectorAll(`.o_ChatterTopbar`).length, + 1, + "should have a chatter topbar" + ); + assert.strictEqual( + document.querySelectorAll(`.o_ChatterTopbar_buttonAttachments`).length, + 1, + "should have an attachments button in chatter menu" + ); + assert.strictEqual( + document.querySelectorAll(`.o_ChatterTopbar_buttonAttachmentsCountLoader`).length, + 1, + "attachments button should have a loader" + ); + assert.strictEqual( + document.querySelectorAll(`.o_ChatterTopbar_buttonAttachmentsCount`).length, + 0, + "attachments button should not have a counter" + ); + + await afterNextRender(() => attachmentPromise.resolve()); + assert.strictEqual( + document.querySelectorAll(`.o_ChatterTopbar_buttonAttachments`).length, + 1, + "should have an attachments button in chatter menu" + ); + assert.strictEqual( + document.querySelectorAll(`.o_ChatterTopbar_buttonAttachmentsCountLoader`).length, + 0, + "attachments button should not have a loader" + ); + assert.strictEqual( + document.querySelectorAll(`.o_ChatterTopbar_buttonAttachmentsCount`).length, + 1, + "attachments button should have a counter" + ); +}); + +QUnit.test('attachment counter without attachments', async function (assert) { + assert.expect(4); + + this.data['res.partner'].records.push({ id: 100 }); + await this.start(); + const chatter = this.env.models['mail.chatter'].create({ + threadId: 100, + threadModel: 'res.partner', + }); + await this.createChatterTopbarComponent(chatter); + + assert.strictEqual( + document.querySelectorAll(`.o_ChatterTopbar`).length, + 1, + "should have a chatter topbar" + ); + assert.strictEqual( + document.querySelectorAll(`.o_ChatterTopbar_buttonAttachments`).length, + 1, + "should have an attachments button in chatter menu" + ); + assert.strictEqual( + document.querySelectorAll(`.o_ChatterTopbar_buttonAttachmentsCount`).length, + 1, + "attachments button should have a counter" + ); + assert.strictEqual( + document.querySelector(`.o_ChatterTopbar_buttonAttachmentsCount`).textContent, + '0', + 'attachment counter should contain "0"' + ); +}); + +QUnit.test('attachment counter with attachments', async function (assert) { + assert.expect(4); + + this.data['res.partner'].records.push({ id: 100 }); + this.data['ir.attachment'].records.push( + { + mimetype: 'text/plain', + name: 'Blah.txt', + res_id: 100, + res_model: 'res.partner', + }, + { + mimetype: 'text/plain', + name: 'Blu.txt', + res_id: 100, + res_model: 'res.partner', + } + ); + await this.start(); + const chatter = this.env.models['mail.chatter'].create({ + threadId: 100, + threadModel: 'res.partner', + }); + await this.createChatterTopbarComponent(chatter); + + assert.strictEqual( + document.querySelectorAll(`.o_ChatterTopbar`).length, + 1, + "should have a chatter topbar" + ); + assert.strictEqual( + document.querySelectorAll(`.o_ChatterTopbar_buttonAttachments`).length, + 1, + "should have an attachments button in chatter menu" + ); + assert.strictEqual( + document.querySelectorAll(`.o_ChatterTopbar_buttonAttachmentsCount`).length, + 1, + "attachments button should have a counter" + ); + assert.strictEqual( + document.querySelector(`.o_ChatterTopbar_buttonAttachmentsCount`).textContent, + '2', + 'attachment counter should contain "2"' + ); +}); + +QUnit.test('composer state conserved when clicking on another topbar button', async function (assert) { + assert.expect(8); + + this.data['res.partner'].records.push({ id: 100 }); + await this.start(); + const chatter = this.env.models['mail.chatter'].create({ + threadId: 100, + threadModel: 'res.partner', + }); + await this.createChatterTopbarComponent(chatter); + + assert.containsOnce( + document.body, + `.o_ChatterTopbar`, + "should have a chatter topbar" + ); + assert.containsOnce( + document.body, + `.o_ChatterTopbar_buttonSendMessage`, + "should have a send message button in chatter menu" + ); + assert.containsOnce( + document.body, + `.o_ChatterTopbar_buttonLogNote`, + "should have a log note button in chatter menu" + ); + assert.containsOnce( + document.body, + `.o_ChatterTopbar_buttonAttachments`, + "should have an attachments button in chatter menu" + ); + + await afterNextRender(() => { + document.querySelector(`.o_ChatterTopbar_buttonLogNote`).click(); + }); + assert.containsOnce( + document.body, + `.o_ChatterTopbar_buttonLogNote.o-active`, + "log button should now be active" + ); + assert.containsNone( + document.body, + `.o_ChatterTopbar_buttonSendMessage.o-active`, + "send message button should not be active" + ); + + await afterNextRender(() => { + document.querySelector(`.o_ChatterTopbar_buttonAttachments`).click(); + }); + assert.containsOnce( + document.body, + `.o_ChatterTopbar_buttonLogNote.o-active`, + "log button should still be active" + ); + assert.containsNone( + document.body, + `.o_ChatterTopbar_buttonSendMessage.o-active`, + "send message button should still be not active" + ); +}); + +QUnit.test('rendering with multiple partner followers', async function (assert) { + assert.expect(7); + + await this.start(); + this.data['res.partner'].records.push({ + id: 100, + message_follower_ids: [1, 2], + }); + this.data['mail.followers'].records.push( + { + // simulate real return from RPC + // (the presence of the key and the falsy value need to be handled correctly) + channel_id: false, + id: 1, + name: "Jean Michang", + partner_id: 12, + res_id: 100, + res_model: 'res.partner', + }, { + // simulate real return from RPC + // (the presence of the key and the falsy value need to be handled correctly) + channel_id: false, + id: 2, + name: "Eden Hazard", + partner_id: 11, + res_id: 100, + res_model: 'res.partner', + }, + ); + const chatter = this.env.models['mail.chatter'].create({ + followerIds: [1, 2], + threadId: 100, + threadModel: 'res.partner', + }); + await this.createChatterTopbarComponent(chatter); + + assert.containsOnce( + document.body, + '.o_FollowerListMenu', + "should have followers menu component" + ); + assert.containsOnce( + document.body, + '.o_FollowerListMenu_buttonFollowers', + "should have followers button" + ); + + await afterNextRender(() => { + document.querySelector('.o_FollowerListMenu_buttonFollowers').click(); + }); + assert.containsOnce( + document.body, + '.o_FollowerListMenu_dropdown', + "followers dropdown should be opened" + ); + assert.containsN( + document.body, + '.o_Follower', + 2, + "exactly two followers should be listed" + ); + assert.containsN( + document.body, + '.o_Follower_name', + 2, + "exactly two follower names should be listed" + ); + assert.strictEqual( + document.querySelectorAll('.o_Follower_name')[0].textContent.trim(), + "Jean Michang", + "first follower is 'Jean Michang'" + ); + assert.strictEqual( + document.querySelectorAll('.o_Follower_name')[1].textContent.trim(), + "Eden Hazard", + "second follower is 'Eden Hazard'" + ); +}); + +QUnit.test('rendering with multiple channel followers', async function (assert) { + assert.expect(7); + + this.data['res.partner'].records.push({ + id: 100, + message_follower_ids: [1, 2], + }); + await this.start(); + this.data['mail.followers'].records.push( + { + channel_id: 11, + id: 1, + name: "channel numero 5", + // simulate real return from RPC + // (the presence of the key and the falsy value need to be handled correctly) + partner_id: false, + res_id: 100, + res_model: 'res.partner', + }, { + channel_id: 12, + id: 2, + name: "channel armstrong", + // simulate real return from RPC + // (the presence of the key and the falsy value need to be handled correctly) + partner_id: false, + res_id: 100, + res_model: 'res.partner', + }, + ); + const chatter = this.env.models['mail.chatter'].create({ + followerIds: [1, 2], + threadId: 100, + threadModel: 'res.partner', + }); + await this.createChatterTopbarComponent(chatter); + + assert.containsOnce( + document.body, + '.o_FollowerListMenu', + "should have followers menu component" + ); + assert.containsOnce( + document.body, + '.o_FollowerListMenu_buttonFollowers', + "should have followers button" + ); + + await afterNextRender(() => { + document.querySelector('.o_FollowerListMenu_buttonFollowers').click(); + }); + assert.containsOnce( + document.body, + '.o_FollowerListMenu_dropdown', + "followers dropdown should be opened" + ); + assert.containsN( + document.body, + '.o_Follower', + 2, + "exactly two followers should be listed" + ); + assert.containsN( + document.body, + '.o_Follower_name', + 2, + "exactly two follower names should be listed" + ); + assert.strictEqual( + document.querySelectorAll('.o_Follower_name')[0].textContent.trim(), + "channel numero 5", + "first follower is 'channel numero 5'" + ); + assert.strictEqual( + document.querySelectorAll('.o_Follower_name')[1].textContent.trim(), + "channel armstrong", + "second follower is 'channel armstrong'" + ); +}); + +QUnit.test('log note/send message switching', async function (assert) { + assert.expect(8); + + this.data['res.partner'].records.push({ id: 100 }); + await this.start(); + const chatter = this.env.models['mail.chatter'].create({ + threadId: 100, + threadModel: 'res.partner', + }); + await this.createChatterTopbarComponent(chatter); + assert.containsOnce( + document.body, + '.o_ChatterTopbar_buttonSendMessage', + "should have a 'Send Message' button" + ); + assert.doesNotHaveClass( + document.querySelector('.o_ChatterTopbar_buttonSendMessage'), + 'o-active', + "'Send Message' button should not be active" + ); + assert.containsOnce( + document.body, + '.o_ChatterTopbar_buttonLogNote', + "should have a 'Log Note' button" + ); + assert.doesNotHaveClass( + document.querySelector('.o_ChatterTopbar_buttonLogNote'), + 'o-active', + "'Log Note' button should not be active" + ); + + await afterNextRender(() => + document.querySelector(`.o_ChatterTopbar_buttonSendMessage`).click() + ); + assert.hasClass( + document.querySelector('.o_ChatterTopbar_buttonSendMessage'), + 'o-active', + "'Send Message' button should be active" + ); + assert.doesNotHaveClass( + document.querySelector('.o_ChatterTopbar_buttonLogNote'), + 'o-active', + "'Log Note' button should not be active" + ); + + await afterNextRender(() => + document.querySelector(`.o_ChatterTopbar_buttonLogNote`).click() + ); + assert.doesNotHaveClass( + document.querySelector('.o_ChatterTopbar_buttonSendMessage'), + 'o-active', + "'Send Message' button should not be active" + ); + assert.hasClass( + document.querySelector('.o_ChatterTopbar_buttonLogNote'), + 'o-active', + "'Log Note' button should be active" + ); +}); + +QUnit.test('log note toggling', async function (assert) { + assert.expect(4); + + this.data['res.partner'].records.push({ id: 100 }); + await this.start(); + const chatter = this.env.models['mail.chatter'].create({ + threadId: 100, + threadModel: 'res.partner', + }); + await this.createChatterTopbarComponent(chatter); + assert.containsOnce( + document.body, + '.o_ChatterTopbar_buttonLogNote', + "should have a 'Log Note' button" + ); + assert.doesNotHaveClass( + document.querySelector('.o_ChatterTopbar_buttonLogNote'), + 'o-active', + "'Log Note' button should not be active" + ); + + await afterNextRender(() => + document.querySelector(`.o_ChatterTopbar_buttonLogNote`).click() + ); + assert.hasClass( + document.querySelector('.o_ChatterTopbar_buttonLogNote'), + 'o-active', + "'Log Note' button should be active" + ); + + await afterNextRender(() => + document.querySelector(`.o_ChatterTopbar_buttonLogNote`).click() + ); + assert.doesNotHaveClass( + document.querySelector('.o_ChatterTopbar_buttonLogNote'), + 'o-active', + "'Log Note' button should not be active" + ); +}); + +QUnit.test('send message toggling', async function (assert) { + assert.expect(4); + + this.data['res.partner'].records.push({ id: 100 }); + await this.start(); + const chatter = this.env.models['mail.chatter'].create({ + threadId: 100, + threadModel: 'res.partner', + }); + await this.createChatterTopbarComponent(chatter); + assert.containsOnce( + document.body, + '.o_ChatterTopbar_buttonSendMessage', + "should have a 'Send Message' button" + ); + assert.doesNotHaveClass( + document.querySelector('.o_ChatterTopbar_buttonSendMessage'), + 'o-active', + "'Send Message' button should not be active" + ); + + await afterNextRender(() => + document.querySelector(`.o_ChatterTopbar_buttonSendMessage`).click() + ); + assert.hasClass( + document.querySelector('.o_ChatterTopbar_buttonSendMessage'), + 'o-active', + "'Send Message' button should be active" + ); + + await afterNextRender(() => + document.querySelector(`.o_ChatterTopbar_buttonSendMessage`).click() + ); + assert.doesNotHaveClass( + document.querySelector('.o_ChatterTopbar_buttonSendMessage'), + 'o-active', + "'Send Message' button should not be active" + ); +}); + +}); +}); +}); + +}); |
