summaryrefslogtreecommitdiff
path: root/addons/mail/static/src/components/chatter
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
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/mail/static/src/components/chatter')
-rw-r--r--addons/mail/static/src/components/chatter/chatter.js150
-rw-r--r--addons/mail/static/src/components/chatter/chatter.scss42
-rw-r--r--addons/mail/static/src/components/chatter/chatter.xml56
-rw-r--r--addons/mail/static/src/components/chatter/chatter_suggested_recipient_tests.js420
-rw-r--r--addons/mail/static/src/components/chatter/chatter_tests.js469
5 files changed, 1137 insertions, 0 deletions
diff --git a/addons/mail/static/src/components/chatter/chatter.js b/addons/mail/static/src/components/chatter/chatter.js
new file mode 100644
index 00000000..3f6ca7dc
--- /dev/null
+++ b/addons/mail/static/src/components/chatter/chatter.js
@@ -0,0 +1,150 @@
+odoo.define('mail/static/src/components/chatter/chatter.js', function (require) {
+'use strict';
+
+const components = {
+ ActivityBox: require('mail/static/src/components/activity_box/activity_box.js'),
+ AttachmentBox: require('mail/static/src/components/attachment_box/attachment_box.js'),
+ ChatterTopbar: require('mail/static/src/components/chatter_topbar/chatter_topbar.js'),
+ Composer: require('mail/static/src/components/composer/composer.js'),
+ ThreadView: require('mail/static/src/components/thread_view/thread_view.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 useUpdate = require('mail/static/src/component_hooks/use_update/use_update.js');
+
+const { Component } = owl;
+const { useRef } = owl.hooks;
+
+class Chatter 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;
+ let attachments = [];
+ if (thread) {
+ attachments = thread.allAttachments;
+ }
+ return {
+ attachments: attachments.map(attachment => attachment.__state),
+ chatter: chatter ? chatter.__state : undefined,
+ composer: thread && thread.composer,
+ thread,
+ threadActivitiesLength: thread && thread.activities.length,
+ };
+ }, {
+ compareDepth: {
+ attachments: 1,
+ },
+ });
+ useUpdate({ func: () => this._update() });
+ /**
+ * Reference of the composer. Useful to focus it.
+ */
+ this._composerRef = useRef('composer');
+ /**
+ * Reference of the scroll Panel (Real scroll element). Useful to pass the Scroll element to
+ * child component to handle proper scrollable element.
+ */
+ this._scrollPanelRef = useRef('scrollPanel');
+ /**
+ * Reference of the message list. Useful to trigger the scroll event on it.
+ */
+ this._threadRef = useRef('thread');
+ this.getScrollableElement = this.getScrollableElement.bind(this);
+ }
+
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ /**
+ * @returns {mail.chatter}
+ */
+ get chatter() {
+ return this.env.models['mail.chatter'].get(this.props.chatterLocalId);
+ }
+
+ /**
+ * @returns {Element|undefined} Scrollable Element
+ */
+ getScrollableElement() {
+ if (!this._scrollPanelRef.el) {
+ return;
+ }
+ return this._scrollPanelRef.el;
+ }
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ /**
+ * @private
+ */
+ _notifyRendered() {
+ this.trigger('o-chatter-rendered', {
+ attachments: this.chatter.thread.allAttachments,
+ thread: this.chatter.thread.localId,
+ });
+ }
+
+ /**
+ * @private
+ */
+ _update() {
+ if (!this.chatter) {
+ return;
+ }
+ if (this.chatter.thread) {
+ this._notifyRendered();
+ }
+ if (this.chatter.isDoFocus) {
+ this.chatter.update({ isDoFocus: false });
+ const composer = this._composerRef.comp;
+ if (composer) {
+ composer.focus();
+ }
+ }
+ }
+
+ //--------------------------------------------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
+
+ /**
+ * @private
+ */
+ _onComposerMessagePosted() {
+ this.chatter.update({ isComposerVisible: false });
+ }
+
+ /**
+ * @private
+ * @param {MouseEvent} ev
+ */
+ _onScrollPanelScroll(ev) {
+ if (!this._threadRef.comp) {
+ return;
+ }
+ this._threadRef.comp.onScroll(ev);
+ }
+
+}
+
+Object.assign(Chatter, {
+ components,
+ props: {
+ chatterLocalId: String,
+ },
+ template: 'mail.Chatter',
+});
+
+return Chatter;
+
+});
diff --git a/addons/mail/static/src/components/chatter/chatter.scss b/addons/mail/static/src/components/chatter/chatter.scss
new file mode 100644
index 00000000..d722e03b
--- /dev/null
+++ b/addons/mail/static/src/components/chatter/chatter.scss
@@ -0,0 +1,42 @@
+// ------------------------------------------------------------------
+// Layout
+// ------------------------------------------------------------------
+
+.o_Chatter {
+ position: relative;
+ display: flex;
+ flex: 1 1 auto;
+ flex-direction: column;
+ width: map-get($sizes, 100);
+}
+
+.o_Chatter_composer {
+ border-bottom: $border-width solid;
+
+ &.o-bordered {
+ border-left: $border-width solid;
+ border-right: $border-width solid;
+ }
+}
+
+.o_Chatter_scrollPanel {
+ overflow-y: auto;
+}
+
+// ------------------------------------------------------------------
+// Style
+// ------------------------------------------------------------------
+
+.o_Chatter {
+ background-color: white;
+ border-color: $border-color;
+}
+
+.o_Chatter_composer {
+ border-bottom-color: $border-color;
+
+ &.o-bordered {
+ border-left-color: $border-color;
+ border-right-color: $border-color;
+ }
+}
diff --git a/addons/mail/static/src/components/chatter/chatter.xml b/addons/mail/static/src/components/chatter/chatter.xml
new file mode 100644
index 00000000..d9cf20b4
--- /dev/null
+++ b/addons/mail/static/src/components/chatter/chatter.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<templates xml:space="preserve">
+
+ <t t-name="mail.Chatter" owl="1">
+ <div class="o_Chatter">
+ <t t-if="chatter">
+ <div class="o_Chatter_fixedPanel">
+ <ChatterTopbar
+ class="o_Chatter_topbar"
+ chatterLocalId="chatter.localId"
+ />
+ <t t-if="chatter.threadView and chatter.isComposerVisible">
+ <Composer
+ class="o_Chatter_composer"
+ t-att-class="{ 'o-bordered': chatter.hasExternalBorder }"
+ composerLocalId="chatter.thread.composer.localId"
+ hasFollowers="true"
+ hasMentionSuggestionsBelowPosition="true"
+ isCompact="false"
+ isExpandable="true"
+ textInputSendShortcuts="['ctrl-enter', 'meta-enter']"
+ t-on-o-message-posted="_onComposerMessagePosted"
+ t-ref="composer"
+ />
+ </t>
+ </div>
+ <div class="o_Chatter_scrollPanel" t-on-scroll="_onScrollPanelScroll" t-ref="scrollPanel">
+ <t t-if="chatter.isAttachmentBoxVisible">
+ <AttachmentBox
+ class="o_Chatter_attachmentBox"
+ threadLocalId="chatter.thread.localId"
+ />
+ </t>
+ <t t-if="chatter.thread and chatter.hasActivities and chatter.thread.activities.length > 0">
+ <ActivityBox
+ class="o_Chatter_activityBox"
+ chatterLocalId="chatter.localId"
+ />
+ </t>
+ <t t-if="chatter.threadView">
+ <ThreadView
+ class="o_Chatter_thread"
+ getScrollableElement="getScrollableElement"
+ hasComposer="false"
+ hasScrollAdjust="chatter.hasMessageListScrollAdjust"
+ order="'desc'"
+ threadViewLocalId="chatter.threadView.localId"
+ t-ref="thread"
+ />
+ </t>
+ </div>
+ </t>
+ </div>
+ </t>
+
+</templates>
diff --git a/addons/mail/static/src/components/chatter/chatter_suggested_recipient_tests.js b/addons/mail/static/src/components/chatter/chatter_suggested_recipient_tests.js
new file mode 100644
index 00000000..9cb57d83
--- /dev/null
+++ b/addons/mail/static/src/components/chatter/chatter_suggested_recipient_tests.js
@@ -0,0 +1,420 @@
+odoo.define('mail/static/src/components/chatter/chatter_suggested_recipient_tests', function (require) {
+'use strict';
+
+const components = {
+ Chatter: require('mail/static/src/components/chatter/chatter.js'),
+ Composer: require('mail/static/src/components/composer/composer.js'),
+};
+const {
+ afterEach,
+ afterNextRender,
+ beforeEach,
+ createRootComponent,
+ start,
+} = require('mail/static/src/utils/test_utils.js');
+
+QUnit.module('mail', {}, function () {
+QUnit.module('components', {}, function () {
+QUnit.module('chatter', {}, function () {
+QUnit.module('chatter_suggested_recipients_tests.js', {
+ beforeEach() {
+ beforeEach(this);
+
+ this.createChatterComponent = async ({ chatter }, otherProps) => {
+ const props = Object.assign({ chatterLocalId: chatter.localId }, otherProps);
+ await createRootComponent(this, components.Chatter, {
+ 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("suggest recipient on 'Send message' composer", async function (assert) {
+ assert.expect(1);
+
+ this.data['res.partner'].records.push({
+ display_name: "John Jane",
+ email: "john@jane.be",
+ id: 100,
+ });
+ this.data['res.fake'].records.push({
+ id: 10,
+ email_cc: "john@test.be",
+ partner_ids: [100],
+ });
+ await this.start ();
+ const chatter = this.env.models['mail.chatter'].create({
+ threadId: 10,
+ threadModel: 'res.fake',
+ });
+ await this.createChatterComponent({ chatter });
+ await afterNextRender(() =>
+ document.querySelector(`.o_ChatterTopbar_buttonSendMessage`).click()
+ );
+ assert.containsOnce(
+ document.body,
+ '.o_ComposerSuggestedRecipientList',
+ "Should display a list of suggested recipients after opening the composer from 'Send message' button"
+ );
+});
+
+QUnit.test("with 3 or less suggested recipients: no 'show more' button", async function (assert) {
+ assert.expect(1);
+
+ this.data['res.partner'].records.push({
+ display_name: "John Jane",
+ email: "john@jane.be",
+ id: 100,
+ });
+ this.data['res.fake'].records.push({
+ id: 10,
+ email_cc: "john@test.be",
+ partner_ids: [100],
+ });
+ await this.start ();
+ const chatter = this.env.models['mail.chatter'].create({
+ threadId: 10,
+ threadModel: 'res.fake',
+ });
+ await this.createChatterComponent({ chatter });
+ await afterNextRender(() =>
+ document.querySelector(`.o_ChatterTopbar_buttonSendMessage`).click()
+ );
+ assert.containsNone(
+ document.body,
+ '.o_ComposerSuggestedRecipientList_showMore',
+ "should not display 'show more' button with 3 or less suggested recipients"
+ );
+});
+
+QUnit.test("display reason for suggested recipient on mouse over", async function (assert) {
+ assert.expect(1);
+
+ this.data['res.partner'].records.push({
+ display_name: "John Jane",
+ email: "john@jane.be",
+ id: 100,
+ });
+ this.data['res.fake'].records.push({
+ id: 10,
+ partner_ids: [100],
+ });
+ await this.start();
+ const chatter = this.env.models['mail.chatter'].create({
+ threadId: 10,
+ threadModel: 'res.fake',
+ });
+ await this.createChatterComponent({ chatter });
+ await afterNextRender(() =>
+ document.querySelector(`.o_ChatterTopbar_buttonSendMessage`).click()
+ );
+ const partnerTitle = document.querySelector('.o_ComposerSuggestedRecipient[data-partner-id="100"]').getAttribute('title');
+ assert.strictEqual(
+ partnerTitle,
+ "Add as recipient and follower (reason: Email partner)",
+ "must display reason for suggested recipient on mouse over",
+ );
+});
+
+QUnit.test("suggested recipient without partner are unchecked by default", async function (assert) {
+ assert.expect(1);
+
+ this.data['res.fake'].records.push({
+ id: 10,
+ email_cc: "john@test.be",
+ });
+ await this.start();
+ const chatter = this.env.models['mail.chatter'].create({
+ threadId: 10,
+ threadModel: 'res.fake',
+ });
+ await this.createChatterComponent({ chatter });
+ await afterNextRender(() =>
+ document.querySelector(`.o_ChatterTopbar_buttonSendMessage`).click()
+ );
+ const checkboxUnchecked = document.querySelector('.o_ComposerSuggestedRecipient:not([data-partner-id]) input[type=checkbox]');
+ assert.notOk(
+ checkboxUnchecked.checked,
+ "suggested recipient without partner must be unchecked by default",
+ );
+});
+
+QUnit.test("suggested recipient with partner are checked by default", async function (assert) {
+ assert.expect(1);
+
+ this.data['res.partner'].records.push({
+ display_name: "John Jane",
+ email: "john@jane.be",
+ id: 100,
+ });
+ this.data['res.fake'].records.push({
+ id: 10,
+ partner_ids: [100],
+ });
+ await this.start();
+ const chatter = this.env.models['mail.chatter'].create({
+ threadId: 10,
+ threadModel: 'res.fake',
+ });
+ await this.createChatterComponent({ chatter });
+ await afterNextRender(() =>
+ document.querySelector(`.o_ChatterTopbar_buttonSendMessage`).click()
+ );
+ const checkboxChecked = document.querySelector('.o_ComposerSuggestedRecipient[data-partner-id="100"] input[type=checkbox]');
+ assert.ok(
+ checkboxChecked.checked,
+ "suggested recipient with partner must be checked by default",
+ );
+});
+
+QUnit.test("more than 3 suggested recipients: display only 3 and 'show more' button", async function (assert) {
+ assert.expect(1);
+
+ this.data['res.partner'].records.push({
+ display_name: "John Jane",
+ email: "john@jane.be",
+ id: 100,
+ });
+ this.data['res.partner'].records.push({
+ display_name: "Jack Jone",
+ email: "jack@jone.be",
+ id: 1000,
+ });
+ this.data['res.partner'].records.push({
+ display_name: "jolly Roger",
+ email: "Roger@skullflag.com",
+ id: 1001,
+ });
+ this.data['res.partner'].records.push({
+ display_name: "jack sparrow",
+ email: "jsparrow@blackpearl.bb",
+ id: 1002,
+ });
+ this.data['res.fake'].records.push({
+ id: 10,
+ partner_ids: [100, 1000, 1001, 1002],
+ });
+ await this.start ();
+ const chatter = this.env.models['mail.chatter'].create({
+ threadId: 10,
+ threadModel: 'res.fake',
+ });
+ await this.createChatterComponent({ chatter });
+
+ await afterNextRender(() =>
+ document.querySelector(`.o_ChatterTopbar_buttonSendMessage`).click()
+ );
+ assert.containsOnce(
+ document.body,
+ '.o_ComposerSuggestedRecipientList_showMore',
+ "more than 3 suggested recipients display 'show more' button"
+ );
+});
+
+QUnit.test("more than 3 suggested recipients: show all of them on click 'show more' button", async function (assert) {
+ assert.expect(1);
+
+ this.data['res.partner'].records.push({
+ display_name: "John Jane",
+ email: "john@jane.be",
+ id: 100,
+ });
+ this.data['res.partner'].records.push({
+ display_name: "Jack Jone",
+ email: "jack@jone.be",
+ id: 1000,
+ });
+ this.data['res.partner'].records.push({
+ display_name: "jolly Roger",
+ email: "Roger@skullflag.com",
+ id: 1001,
+ });
+ this.data['res.partner'].records.push({
+ display_name: "jack sparrow",
+ email: "jsparrow@blackpearl.bb",
+ id: 1002,
+ });
+ this.data['res.fake'].records.push({
+ id: 10,
+ partner_ids: [100, 1000, 1001, 1002],
+ });
+ await this.start ();
+ const chatter = this.env.models['mail.chatter'].create({
+ threadId: 10,
+ threadModel: 'res.fake',
+ });
+ await this.createChatterComponent({ chatter });
+
+ await afterNextRender(() =>
+ document.querySelector(`.o_ChatterTopbar_buttonSendMessage`).click()
+ );
+ await afterNextRender(() =>
+ document.querySelector(`.o_ComposerSuggestedRecipientList_showMore`).click()
+ );
+ assert.containsN(
+ document.body,
+ '.o_ComposerSuggestedRecipient',
+ 4,
+ "more than 3 suggested recipients: show all of them on click 'show more' button"
+ );
+});
+
+QUnit.test("more than 3 suggested recipients -> click 'show more' -> 'show less' button", async function (assert) {
+ assert.expect(1);
+
+ this.data['res.partner'].records.push({
+ display_name: "John Jane",
+ email: "john@jane.be",
+ id: 100,
+ });
+ this.data['res.partner'].records.push({
+ display_name: "Jack Jone",
+ email: "jack@jone.be",
+ id: 1000,
+ });
+ this.data['res.partner'].records.push({
+ display_name: "jolly Roger",
+ email: "Roger@skullflag.com",
+ id: 1001,
+ });
+ this.data['res.partner'].records.push({
+ display_name: "jack sparrow",
+ email: "jsparrow@blackpearl.bb",
+ id: 1002,
+ });
+ this.data['res.fake'].records.push({
+ id: 10,
+ partner_ids: [100, 1000, 1001, 1002],
+ });
+ await this.start ();
+ const chatter = this.env.models['mail.chatter'].create({
+ threadId: 10,
+ threadModel: 'res.fake',
+ });
+ await this.createChatterComponent({ chatter });
+
+ await afterNextRender(() =>
+ document.querySelector(`.o_ChatterTopbar_buttonSendMessage`).click()
+ );
+ await afterNextRender(() =>
+ document.querySelector(`.o_ComposerSuggestedRecipientList_showMore`).click()
+ );
+ assert.containsOnce(
+ document.body,
+ '.o_ComposerSuggestedRecipientList_showLess',
+ "more than 3 suggested recipients -> click 'show more' -> 'show less' button"
+ );
+});
+
+QUnit.test("suggested recipients list display 3 suggested recipient and 'show more' button when 'show less' button is clicked", async function (assert) {
+ assert.expect(2);
+
+ this.data['res.partner'].records.push({
+ display_name: "John Jane",
+ email: "john@jane.be",
+ id: 100,
+ });
+ this.data['res.partner'].records.push({
+ display_name: "Jack Jone",
+ email: "jack@jone.be",
+ id: 1000,
+ });
+ this.data['res.partner'].records.push({
+ display_name: "jolly Roger",
+ email: "Roger@skullflag.com",
+ id: 1001,
+ });
+ this.data['res.partner'].records.push({
+ display_name: "jack sparrow",
+ email: "jsparrow@blackpearl.bb",
+ id: 1002,
+ });
+ this.data['res.fake'].records.push({
+ id: 10,
+ partner_ids: [100, 1000, 1001, 1002],
+ });
+ await this.start ();
+ const chatter = this.env.models['mail.chatter'].create({
+ threadId: 10,
+ threadModel: 'res.fake',
+ });
+ await this.createChatterComponent({ chatter });
+
+ await afterNextRender(() =>
+ document.querySelector(`.o_ChatterTopbar_buttonSendMessage`).click()
+ );
+ await afterNextRender(() =>
+ document.querySelector(`.o_ComposerSuggestedRecipientList_showMore`).click()
+ );
+ await afterNextRender(() =>
+ document.querySelector(`.o_ComposerSuggestedRecipientList_showLess`).click()
+ );
+ assert.containsN(
+ document.body,
+ '.o_ComposerSuggestedRecipient',
+ 3,
+ "suggested recipient list should display 3 suggested recipients after clicking on 'show less'."
+ );
+ assert.containsOnce(
+ document.body,
+ '.o_ComposerSuggestedRecipientList_showMore',
+ "suggested recipient list should containt a 'show More' button after clicking on 'show less'."
+ );
+});
+
+QUnit.test("suggested recipients should not be notified when posting an internal note", async function (assert) {
+ assert.expect(1);
+
+ this.data['res.partner'].records.push({
+ display_name: "John Jane",
+ email: "john@jane.be",
+ id: 100,
+ });
+ this.data['res.fake'].records.push({
+ id: 10,
+ partner_ids: [100],
+ });
+ await this.start({
+ async mockRPC(route, args) {
+ if (args.model === 'res.fake' && args.method === 'message_post') {
+ assert.strictEqual(
+ args.kwargs.partner_ids.length,
+ 0,
+ "message_post should not contain suggested recipients when posting an internal note"
+ );
+ }
+ return this._super(...arguments);
+ },
+ });
+ const chatter = this.env.models['mail.chatter'].create({
+ threadId: 10,
+ threadModel: 'res.fake',
+ });
+ await this.createChatterComponent({ chatter });
+ await afterNextRender(() =>
+ document.querySelector(`.o_ChatterTopbar_buttonLogNote`).click()
+ );
+ document.querySelector('.o_ComposerTextInput_textarea').focus();
+ await afterNextRender(() => document.execCommand('insertText', false, "Dummy Message"));
+ await afterNextRender(() => {
+ document.querySelector('.o_Composer_buttonSend').click();
+ });
+});
+
+});
+});
+});
+
+});
diff --git a/addons/mail/static/src/components/chatter/chatter_tests.js b/addons/mail/static/src/components/chatter/chatter_tests.js
new file mode 100644
index 00000000..15163e85
--- /dev/null
+++ b/addons/mail/static/src/components/chatter/chatter_tests.js
@@ -0,0 +1,469 @@
+odoo.define('mail/static/src/components/chatter/chatter_tests', function (require) {
+'use strict';
+
+const components = {
+ Chatter: require('mail/static/src/components/chatter/chatter.js'),
+ Composer: require('mail/static/src/components/composer/composer.js'),
+};
+const {
+ afterEach,
+ afterNextRender,
+ beforeEach,
+ createRootComponent,
+ nextAnimationFrame,
+ start,
+} = require('mail/static/src/utils/test_utils.js');
+
+QUnit.module('mail', {}, function () {
+QUnit.module('components', {}, function () {
+QUnit.module('chatter', {}, function () {
+QUnit.module('chatter_tests.js', {
+ beforeEach() {
+ beforeEach(this);
+
+ this.createChatterComponent = async ({ chatter }, otherProps) => {
+ const props = Object.assign({ chatterLocalId: chatter.localId }, otherProps);
+ await createRootComponent(this, components.Chatter, {
+ props,
+ target: this.widget.el,
+ });
+ };
+
+ this.createComposerComponent = async (composer, otherProps) => {
+ const props = Object.assign({ composerLocalId: composer.localId }, otherProps);
+ await createRootComponent(this, components.Composer, {
+ 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 when chatter has no attachment', async function (assert) {
+ assert.expect(6);
+
+ this.data['res.partner'].records.push({ id: 100 });
+ for (let i = 0; i < 60; i++) {
+ this.data['mail.message'].records.push({
+ body: "not empty",
+ model: 'res.partner',
+ res_id: 100,
+ });
+ }
+ await this.start();
+ const chatter = this.env.models['mail.chatter'].create({
+ threadId: 100,
+ threadModel: 'res.partner',
+ });
+ await this.createChatterComponent({ chatter });
+ assert.strictEqual(
+ document.querySelectorAll(`.o_Chatter`).length,
+ 1,
+ "should have a chatter"
+ );
+ assert.strictEqual(
+ document.querySelectorAll(`.o_ChatterTopbar`).length,
+ 1,
+ "should have a chatter topbar"
+ );
+ assert.strictEqual(
+ document.querySelectorAll(`.o_Chatter_attachmentBox`).length,
+ 0,
+ "should not have an attachment box in the chatter"
+ );
+ assert.strictEqual(
+ document.querySelectorAll(`.o_Chatter_thread`).length,
+ 1,
+ "should have a thread in the chatter"
+ );
+ assert.strictEqual(
+ document.querySelector(`.o_Chatter_thread`).dataset.threadLocalId,
+ this.env.models['mail.thread'].findFromIdentifyingData({
+ id: 100,
+ model: 'res.partner',
+ }).localId,
+ "thread should have the right thread local id"
+ );
+ assert.strictEqual(
+ document.querySelectorAll(`.o_Message`).length,
+ 30,
+ "the first 30 messages of thread should be loaded"
+ );
+});
+
+QUnit.test('base rendering when chatter has no record', async function (assert) {
+ assert.expect(8);
+
+ await this.start();
+ const chatter = this.env.models['mail.chatter'].create({
+ threadModel: 'res.partner',
+ });
+ await this.createChatterComponent({ chatter });
+ assert.strictEqual(
+ document.querySelectorAll(`.o_Chatter`).length,
+ 1,
+ "should have a chatter"
+ );
+ assert.strictEqual(
+ document.querySelectorAll(`.o_ChatterTopbar`).length,
+ 1,
+ "should have a chatter topbar"
+ );
+ assert.strictEqual(
+ document.querySelectorAll(`.o_Chatter_attachmentBox`).length,
+ 0,
+ "should not have an attachment box in the chatter"
+ );
+ assert.strictEqual(
+ document.querySelectorAll(`.o_Chatter_thread`).length,
+ 1,
+ "should have a thread in the chatter"
+ );
+ assert.ok(
+ chatter.thread.isTemporary,
+ "thread should have a temporary thread linked to chatter"
+ );
+ assert.strictEqual(
+ document.querySelectorAll(`.o_Message`).length,
+ 1,
+ "should have a message"
+ );
+ assert.strictEqual(
+ document.querySelector(`.o_Message_content`).textContent,
+ "Creating a new record...",
+ "should have the 'Creating a new record ...' message"
+ );
+ assert.containsNone(
+ document.body,
+ '.o_MessageList_loadMore',
+ "should not have the 'load more' button"
+ );
+});
+
+QUnit.test('base rendering when chatter has attachments', async function (assert) {
+ assert.expect(3);
+
+ 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.createChatterComponent({ chatter });
+ assert.strictEqual(
+ document.querySelectorAll(`.o_Chatter`).length,
+ 1,
+ "should have a chatter"
+ );
+ assert.strictEqual(
+ document.querySelectorAll(`.o_ChatterTopbar`).length,
+ 1,
+ "should have a chatter topbar"
+ );
+ assert.strictEqual(
+ document.querySelectorAll(`.o_Chatter_attachmentBox`).length,
+ 0,
+ "should not have an attachment box in the chatter"
+ );
+});
+
+QUnit.test('show attachment box', async function (assert) {
+ assert.expect(6);
+
+ 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.createChatterComponent({ chatter });
+ assert.strictEqual(
+ document.querySelectorAll(`.o_Chatter`).length,
+ 1,
+ "should have a 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 topbar"
+ );
+ assert.strictEqual(
+ document.querySelectorAll(`.o_ChatterTopbar_buttonAttachmentsCount`).length,
+ 1,
+ "attachments button should have a counter"
+ );
+ assert.strictEqual(
+ document.querySelectorAll(`.o_Chatter_attachmentBox`).length,
+ 0,
+ "should not have an attachment box in the chatter"
+ );
+
+ await afterNextRender(() =>
+ document.querySelector(`.o_ChatterTopbar_buttonAttachments`).click()
+ );
+ assert.strictEqual(
+ document.querySelectorAll(`.o_Chatter_attachmentBox`).length,
+ 1,
+ "should have an attachment box in the chatter"
+ );
+});
+
+QUnit.test('composer show/hide on log note/send message [REQUIRE FOCUS]', async function (assert) {
+ assert.expect(10);
+
+ 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.createChatterComponent({ chatter });
+ assert.strictEqual(
+ document.querySelectorAll(`.o_ChatterTopbar_buttonSendMessage`).length,
+ 1,
+ "should have a send message button"
+ );
+ assert.strictEqual(
+ document.querySelectorAll(`.o_ChatterTopbar_buttonLogNote`).length,
+ 1,
+ "should have a log note button"
+ );
+ assert.strictEqual(
+ document.querySelectorAll(`.o_Chatter_composer`).length,
+ 0,
+ "should not have a composer"
+ );
+
+ await afterNextRender(() =>
+ document.querySelector(`.o_ChatterTopbar_buttonSendMessage`).click()
+ );
+ assert.strictEqual(
+ document.querySelectorAll(`.o_Chatter_composer`).length,
+ 1,
+ "should have a composer"
+ );
+ assert.hasClass(
+ document.querySelector('.o_Chatter_composer'),
+ 'o-focused',
+ "composer 'send message' in chatter should have focus just after being displayed"
+ );
+
+ await afterNextRender(() =>
+ document.querySelector(`.o_ChatterTopbar_buttonLogNote`).click()
+ );
+ assert.strictEqual(
+ document.querySelectorAll(`.o_Chatter_composer`).length,
+ 1,
+ "should still have a composer"
+ );
+ assert.hasClass(
+ document.querySelector('.o_Chatter_composer'),
+ 'o-focused',
+ "composer 'log note' in chatter should have focus just after being displayed"
+ );
+
+ await afterNextRender(() =>
+ document.querySelector(`.o_ChatterTopbar_buttonLogNote`).click()
+ );
+ assert.strictEqual(
+ document.querySelectorAll(`.o_Chatter_composer`).length,
+ 0,
+ "should have no composer anymore"
+ );
+
+ await afterNextRender(() =>
+ document.querySelector(`.o_ChatterTopbar_buttonSendMessage`).click()
+ );
+ assert.strictEqual(
+ document.querySelectorAll(`.o_Chatter_composer`).length,
+ 1,
+ "should have a composer"
+ );
+
+ await afterNextRender(() =>
+ document.querySelector(`.o_ChatterTopbar_buttonSendMessage`).click()
+ );
+ assert.strictEqual(
+ document.querySelectorAll(`.o_Chatter_composer`).length,
+ 0,
+ "should have no composer anymore"
+ );
+});
+
+QUnit.test('should not display user notification messages in chatter', async function (assert) {
+ assert.expect(1);
+
+ this.data['res.partner'].records.push({ id: 100 });
+ this.data['mail.message'].records.push({
+ id: 102,
+ message_type: 'user_notification',
+ model: 'res.partner',
+ res_id: 100,
+ });
+ await this.start();
+ const chatter = this.env.models['mail.chatter'].create({
+ threadId: 100,
+ threadModel: 'res.partner',
+ });
+ await this.createChatterComponent({ chatter });
+
+ assert.containsNone(
+ document.body,
+ '.o_Message',
+ "should display no messages"
+ );
+});
+
+QUnit.test('post message with "CTRL-Enter" keyboard shortcut', async function (assert) {
+ assert.expect(2);
+
+ 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.createChatterComponent({ chatter });
+ assert.containsNone(
+ document.body,
+ '.o_Message',
+ "should not have any message initially in chatter"
+ );
+
+ await afterNextRender(() =>
+ document.querySelector('.o_ChatterTopbar_buttonSendMessage').click()
+ );
+ await afterNextRender(() => {
+ document.querySelector(`.o_ComposerTextInput_textarea`).focus();
+ document.execCommand('insertText', false, "Test");
+ });
+ await afterNextRender(() => {
+ const kevt = new window.KeyboardEvent('keydown', { ctrlKey: true, key: "Enter" });
+ document.querySelector('.o_ComposerTextInput_textarea').dispatchEvent(kevt);
+ });
+ assert.containsOnce(
+ document.body,
+ '.o_Message',
+ "should now have single message in chatter after posting message from pressing 'CTRL-Enter' in text input of composer"
+ );
+});
+
+QUnit.test('post message with "META-Enter" keyboard shortcut', async function (assert) {
+ assert.expect(2);
+
+ 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.createChatterComponent({ chatter });
+ assert.containsNone(
+ document.body,
+ '.o_Message',
+ "should not have any message initially in chatter"
+ );
+
+ await afterNextRender(() =>
+ document.querySelector('.o_ChatterTopbar_buttonSendMessage').click()
+ );
+ await afterNextRender(() => {
+ document.querySelector(`.o_ComposerTextInput_textarea`).focus();
+ document.execCommand('insertText', false, "Test");
+ });
+ await afterNextRender(() => {
+ const kevt = new window.KeyboardEvent('keydown', { key: "Enter", metaKey: true });
+ document.querySelector('.o_ComposerTextInput_textarea').dispatchEvent(kevt);
+ });
+ assert.containsOnce(
+ document.body,
+ '.o_Message',
+ "should now have single message in channel after posting message from pressing 'META-Enter' in text input of composer"
+ );
+});
+
+QUnit.test('do not post message with "Enter" keyboard shortcut', async function (assert) {
+ // Note that test doesn't assert Enter makes a newline, because this
+ // default browser cannot be simulated with just dispatching
+ // programmatically crafted events...
+ assert.expect(2);
+
+ 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.createChatterComponent({ chatter });
+ assert.containsNone(
+ document.body,
+ '.o_Message',
+ "should not have any message initially in chatter"
+ );
+
+ await afterNextRender(() =>
+ document.querySelector('.o_ChatterTopbar_buttonSendMessage').click()
+ );
+ await afterNextRender(() => {
+ document.querySelector(`.o_ComposerTextInput_textarea`).focus();
+ document.execCommand('insertText', false, "Test");
+ });
+ const kevt = new window.KeyboardEvent('keydown', { key: "Enter" });
+ document.querySelector('.o_ComposerTextInput_textarea').dispatchEvent(kevt);
+ await nextAnimationFrame();
+ assert.containsNone(
+ document.body,
+ '.o_Message',
+ "should still not have any message in mailing channel after pressing 'Enter' in text input of composer"
+ );
+});
+
+});
+});
+});
+
+});