summaryrefslogtreecommitdiff
path: root/addons/mail/static/src/components/chatter_topbar
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/mail/static/src/components/chatter_topbar
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/mail/static/src/components/chatter_topbar')
-rw-r--r--addons/mail/static/src/components/chatter_topbar/chatter_topbar.js137
-rw-r--r--addons/mail/static/src/components/chatter_topbar/chatter_topbar.scss106
-rw-r--r--addons/mail/static/src/components/chatter_topbar/chatter_topbar.xml74
-rw-r--r--addons/mail/static/src/components/chatter_topbar/chatter_topbar_tests.js730
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"
+ );
+});
+
+});
+});
+});
+
+});