odoo.define('mail/static/src/components/discuss/tests/discuss_inbox_tests.js', function (require) { 'use strict'; const { afterEach, afterNextRender, beforeEach, nextAnimationFrame, start, } = require('mail/static/src/utils/test_utils.js'); const Bus = require('web.Bus'); QUnit.module('mail', {}, function () { QUnit.module('components', {}, function () { QUnit.module('discuss', {}, function () { QUnit.module('discuss_inbox_tests.js', { beforeEach() { beforeEach(this); this.start = async params => { const { env, widget } = await start(Object.assign({}, params, { autoOpenDiscuss: true, data: this.data, hasDiscuss: true, })); this.env = env; this.widget = widget; }; }, afterEach() { afterEach(this); }, }); QUnit.test('reply: discard on pressing escape', async function (assert) { assert.expect(9); // partner expected to be found by mention this.data['res.partner'].records.push({ email: "testpartnert@odoo.com", id: 11, name: "TestPartner", }); // message expected to be found in inbox this.data['mail.message'].records.push({ body: "not empty", model: 'res.partner', needaction: true, needaction_partner_ids: [this.data.currentPartnerId], res_id: 20, }); await this.start(); assert.containsOnce( document.body, '.o_Message', "should display a single message" ); await afterNextRender(() => document.querySelector('.o_Message_commandReply').click() ); assert.containsOnce( document.body, '.o_Composer', "should have composer after clicking on reply to message" ); await afterNextRender(() => document.querySelector(`.o_Composer_buttonEmojis`).click() ); assert.containsOnce( document.body, '.o_EmojisPopover', "emojis popover should be opened after click on emojis button" ); await afterNextRender(() => { const ev = new window.KeyboardEvent('keydown', { bubbles: true, key: "Escape" }); document.querySelector(`.o_Composer_buttonEmojis`).dispatchEvent(ev); }); assert.containsNone( document.body, '.o_EmojisPopover', "emojis popover should be closed after pressing escape on emojis button" ); assert.containsOnce( document.body, '.o_Composer', "reply composer should still be opened after pressing escape on emojis button" ); await afterNextRender(() => { document.querySelector(`.o_ComposerTextInput_textarea`).focus(); document.execCommand('insertText', false, "@"); document.querySelector(`.o_ComposerTextInput_textarea`) .dispatchEvent(new window.KeyboardEvent('keydown')); document.querySelector(`.o_ComposerTextInput_textarea`) .dispatchEvent(new window.KeyboardEvent('keyup')); document.execCommand('insertText', false, "T"); document.querySelector(`.o_ComposerTextInput_textarea`) .dispatchEvent(new window.KeyboardEvent('keydown')); document.querySelector(`.o_ComposerTextInput_textarea`) .dispatchEvent(new window.KeyboardEvent('keyup')); document.execCommand('insertText', false, "e"); document.querySelector(`.o_ComposerTextInput_textarea`) .dispatchEvent(new window.KeyboardEvent('keydown')); document.querySelector(`.o_ComposerTextInput_textarea`) .dispatchEvent(new window.KeyboardEvent('keyup')); }); assert.containsOnce( document.body, '.o_ComposerSuggestion', "mention suggestion should be opened after typing @" ); await afterNextRender(() => { const ev = new window.KeyboardEvent('keydown', { bubbles: true, key: "Escape" }); document.querySelector(`.o_ComposerTextInput_textarea`).dispatchEvent(ev); }); assert.containsNone( document.body, '.o_ComposerSuggestion', "mention suggestion should be closed after pressing escape on mention suggestion" ); assert.containsOnce( document.body, '.o_Composer', "reply composer should still be opened after pressing escape on mention suggestion" ); await afterNextRender(() => { const ev = new window.KeyboardEvent('keydown', { bubbles: true, key: "Escape" }); document.querySelector(`.o_ComposerTextInput_textarea`).dispatchEvent(ev); }); assert.containsNone( document.body, '.o_Composer', "reply composer should be closed after pressing escape if there was no other priority escape handler" ); }); QUnit.test('reply: discard on discard button click', async function (assert) { assert.expect(4); this.data['mail.message'].records.push({ body: "not empty", model: 'res.partner', needaction: true, needaction_partner_ids: [this.data.currentPartnerId], res_id: 20, }); await this.start(); assert.containsOnce( document.body, '.o_Message', "should display a single message" ); await afterNextRender(() => document.querySelector('.o_Message_commandReply').click() ); assert.containsOnce( document.body, '.o_Composer', "should have composer after clicking on reply to message" ); assert.containsOnce( document.body, '.o_Composer_buttonDiscard', "composer should have a discard button" ); await afterNextRender(() => document.querySelector(`.o_Composer_buttonDiscard`).click() ); assert.containsNone( document.body, '.o_Composer', "reply composer should be closed after clicking on discard" ); }); QUnit.test('reply: discard on reply button toggle', async function (assert) { assert.expect(3); this.data['mail.message'].records.push({ body: "not empty", model: 'res.partner', needaction: true, needaction_partner_ids: [this.data.currentPartnerId], res_id: 20, }); await this.start(); assert.containsOnce( document.body, '.o_Message', "should display a single message" ); await afterNextRender(() => document.querySelector('.o_Message_commandReply').click() ); assert.containsOnce( document.body, '.o_Composer', "should have composer after clicking on reply to message" ); await afterNextRender(() => document.querySelector(`.o_Message_commandReply`).click() ); assert.containsNone( document.body, '.o_Composer', "reply composer should be closed after clicking on reply button again" ); }); QUnit.test('reply: discard on click away', async function (assert) { assert.expect(7); this.data['mail.message'].records.push({ body: "not empty", model: 'res.partner', needaction: true, needaction_partner_ids: [this.data.currentPartnerId], res_id: 20, }); await this.start(); assert.containsOnce( document.body, '.o_Message', "should display a single message" ); await afterNextRender(() => document.querySelector('.o_Message_commandReply').click() ); assert.containsOnce( document.body, '.o_Composer', "should have composer after clicking on reply to message" ); document.querySelector(`.o_ComposerTextInput_textarea`).click(); await nextAnimationFrame(); // wait just in case, but nothing is supposed to happen assert.containsOnce( document.body, '.o_Composer', "reply composer should still be there after clicking inside itself" ); await afterNextRender(() => document.querySelector(`.o_Composer_buttonEmojis`).click() ); assert.containsOnce( document.body, '.o_EmojisPopover', "emojis popover should be opened after clicking on emojis button" ); await afterNextRender(() => { document.querySelector(`.o_EmojisPopover_emoji`).click(); }); assert.containsNone( document.body, '.o_EmojisPopover', "emojis popover should be closed after selecting an emoji" ); assert.containsOnce( document.body, '.o_Composer', "reply composer should still be there after selecting an emoji (even though it is technically a click away, it should be considered inside)" ); await afterNextRender(() => document.querySelector(`.o_Message`).click() ); assert.containsNone( document.body, '.o_Composer', "reply composer should be closed after clicking away" ); }); QUnit.test('"reply to" composer should log note if message replied to is a note', async function (assert) { assert.expect(6); this.data['mail.message'].records.push({ body: "not empty", is_discussion: false, model: 'res.partner', needaction: true, needaction_partner_ids: [this.data.currentPartnerId], res_id: 20, }); await this.start({ async mockRPC(route, args) { if (args.method === 'message_post') { assert.step('message_post'); assert.strictEqual( args.kwargs.message_type, "comment", "should set message type as 'comment'" ); assert.strictEqual( args.kwargs.subtype_xmlid, "mail.mt_note", "should set subtype_xmlid as 'note'" ); } return this._super(...arguments); }, }); assert.containsOnce( document.body, '.o_Message', "should display a single message" ); await afterNextRender(() => document.querySelector('.o_Message_commandReply').click() ); assert.strictEqual( document.querySelector('.o_Composer_buttonSend').textContent.trim(), "Log", "Send button text should be 'Log'" ); await afterNextRender(() => document.execCommand('insertText', false, "Test") ); await afterNextRender(() => document.querySelector('.o_Composer_buttonSend').click() ); assert.verifySteps(['message_post']); }); QUnit.test('"reply to" composer should send message if message replied to is not a note', async function (assert) { assert.expect(6); this.data['mail.message'].records.push({ body: "not empty", is_discussion: true, model: 'res.partner', needaction: true, needaction_partner_ids: [this.data.currentPartnerId], res_id: 20, }); await this.start({ async mockRPC(route, args) { if (args.method === 'message_post') { assert.step('message_post'); assert.strictEqual( args.kwargs.message_type, "comment", "should set message type as 'comment'" ); assert.strictEqual( args.kwargs.subtype_xmlid, "mail.mt_comment", "should set subtype_xmlid as 'comment'" ); } return this._super(...arguments); }, }); assert.containsOnce( document.body, '.o_Message', "should display a single message" ); await afterNextRender(() => document.querySelector('.o_Message_commandReply').click() ); assert.strictEqual( document.querySelector('.o_Composer_buttonSend').textContent.trim(), "Send", "Send button text should be 'Send'" ); await afterNextRender(() => document.execCommand('insertText', false, "Test") ); await afterNextRender(() => document.querySelector('.o_Composer_buttonSend').click() ); assert.verifySteps(['message_post']); }); QUnit.test('error notifications should not be shown in Inbox', async function (assert) { assert.expect(3); this.data['mail.message'].records.push({ body: "not empty", id: 100, model: 'mail.channel', needaction: true, needaction_partner_ids: [this.data.currentPartnerId], res_id: 20, }); this.data['mail.notification'].records.push({ mail_message_id: 100, // id of related message res_partner_id: this.data.currentPartnerId, // must be for current partner notification_status: 'exception', notification_type: 'email', }); await this.start(); assert.containsOnce( document.body, '.o_Message', "should display a single message" ); assert.containsOnce( document.body, '.o_Message_originThreadLink', "should display origin thread link" ); assert.containsNone( document.body, '.o_Message_notificationIcon', "should not display any notification icon in Inbox" ); }); QUnit.test('show subject of message in Inbox', async function (assert) { assert.expect(3); this.data['mail.message'].records.push({ body: "not empty", model: 'mail.channel', // random existing model needaction: true, // message_fetch domain needaction_partner_ids: [this.data.currentPartnerId], // not needed, for consistency subject: "Salutations, voyageur", // will be asserted in the test }); await this.start(); assert.containsOnce( document.body, '.o_Message', "should display a single message" ); assert.containsOnce( document.body, '.o_Message_subject', "should display subject of the message" ); assert.strictEqual( document.querySelector('.o_Message_subject').textContent, "Subject: Salutations, voyageur", "Subject of the message should be 'Salutations, voyageur'" ); }); QUnit.test('show subject of message in history', async function (assert) { assert.expect(3); this.data['mail.message'].records.push({ body: "not empty", history_partner_ids: [3], // not needed, for consistency model: 'mail.channel', // random existing model subject: "Salutations, voyageur", // will be asserted in the test }); await this.start({ discuss: { params: { default_active_id: 'mail.box_history', }, }, }); assert.containsOnce( document.body, '.o_Message', "should display a single message" ); assert.containsOnce( document.body, '.o_Message_subject', "should display subject of the message" ); assert.strictEqual( document.querySelector('.o_Message_subject').textContent, "Subject: Salutations, voyageur", "Subject of the message should be 'Salutations, voyageur'" ); }); QUnit.test('click on (non-channel/non-partner) origin thread link should redirect to form view', async function (assert) { assert.expect(9); const bus = new Bus(); bus.on('do-action', null, payload => { // Callback of doing an action (action manager). // Expected to be called on click on origin thread link, // which redirects to form view of record related to origin thread assert.step('do-action'); assert.strictEqual( payload.action.type, 'ir.actions.act_window', "action should open a view" ); assert.deepEqual( payload.action.views, [[false, 'form']], "action should open form view" ); assert.strictEqual( payload.action.res_model, 'some.model', "action should open view with model 'some.model' (model of message origin thread)" ); assert.strictEqual( payload.action.res_id, 10, "action should open view with id 10 (id of message origin thread)" ); }); this.data['some.model'] = { fields: {}, records: [{ id: 10 }] }; this.data['mail.message'].records.push({ body: "not empty", model: 'some.model', needaction: true, needaction_partner_ids: [this.data.currentPartnerId], record_name: "Some record", res_id: 10, }); await this.start({ env: { bus, }, }); assert.containsOnce( document.body, '.o_Message', "should display a single message" ); assert.containsOnce( document.body, '.o_Message_originThreadLink', "should display origin thread link" ); assert.strictEqual( document.querySelector('.o_Message_originThreadLink').textContent, "Some record", "origin thread link should display record name" ); document.querySelector('.o_Message_originThreadLink').click(); assert.verifySteps(['do-action'], "should have made an action on click on origin thread (to open form view)"); }); QUnit.test('subject should not be shown when subject is the same as the thread name', async function (assert) { assert.expect(1); this.data['mail.message'].records.push({ body: "not empty", channel_ids: [100], model: 'mail.channel', res_id: 100, needaction: true, subject: "Salutations, voyageur", }); this.data['mail.channel'].records.push({ id: 100, name: "Salutations, voyageur", }); await this.start(); assert.containsNone( document.body, '.o_Message_subject', "subject should not be shown when subject is the same as the thread name" ); }); QUnit.test('subject should not be shown when subject is the same as the thread name and both have the same prefix', async function (assert) { assert.expect(1); this.data['mail.message'].records.push({ body: "not empty", channel_ids: [100], model: 'mail.channel', res_id: 100, needaction: true, subject: "Re: Salutations, voyageur", }); this.data['mail.channel'].records.push({ id: 100, name: "Re: Salutations, voyageur", }); await this.start(); assert.containsNone( document.body, '.o_Message_subject', "subject should not be shown when subject is the same as the thread name and both have the same prefix" ); }); QUnit.test('subject should not be shown when subject differs from thread name only by the "Re:" prefix', async function (assert) { assert.expect(1); this.data['mail.message'].records.push({ body: "not empty", channel_ids: [100], model: 'mail.channel', res_id: 100, needaction: true, subject: "Re: Salutations, voyageur", }); this.data['mail.channel'].records.push({ id: 100, name: "Salutations, voyageur", }); await this.start(); assert.containsNone( document.body, '.o_Message_subject', "should not display subject when subject differs from thread name only by the 'Re:' prefix" ); }); QUnit.test('subject should not be shown when subject differs from thread name only by the "Fw:" and "Re:" prefix', async function (assert) { assert.expect(1); this.data['mail.message'].records.push({ body: "not empty", channel_ids: [100], model: 'mail.channel', res_id: 100, needaction: true, subject: "Fw: Re: Salutations, voyageur", }); this.data['mail.channel'].records.push({ id: 100, name: "Salutations, voyageur", }); await this.start(); assert.containsNone( document.body, '.o_Message_subject', "should not display subject when subject differs from thread name only by the 'Fw:' and Re:' prefix" ); }); QUnit.test('subject should be shown when the thread name has an extra prefix compared to subject', async function (assert) { assert.expect(1); this.data['mail.message'].records.push({ body: "not empty", channel_ids: [100], model: 'mail.channel', res_id: 100, needaction: true, subject: "Salutations, voyageur", }); this.data['mail.channel'].records.push({ id: 100, name: "Re: Salutations, voyageur", }); await this.start(); assert.containsOnce( document.body, '.o_Message_subject', "subject should be shown when the thread name has an extra prefix compared to subject" ); }); QUnit.test('subject should not be shown when subject differs from thread name only by the "fw:" prefix and both contain another common prefix', async function (assert) { assert.expect(1); this.data['mail.message'].records.push({ body: "not empty", channel_ids: [100], model: 'mail.channel', res_id: 100, needaction: true, subject: "fw: re: Salutations, voyageur", }); this.data['mail.channel'].records.push({ id: 100, name: "Re: Salutations, voyageur", }); await this.start(); assert.containsNone( document.body, '.o_Message_subject', "subject should not be shown when subject differs from thread name only by the 'fw:' prefix and both contain another common prefix" ); }); QUnit.test('subject should not be shown when subject differs from thread name only by the "Re: Re:" prefix', async function (assert) { assert.expect(1); this.data['mail.message'].records.push({ body: "not empty", channel_ids: [100], model: 'mail.channel', res_id: 100, needaction: true, subject: "Re: Re: Salutations, voyageur", }); this.data['mail.channel'].records.push({ id: 100, name: "Salutations, voyageur", }); await this.start(); assert.containsNone( document.body, '.o_Message_subject', "should not display subject when subject differs from thread name only by the 'Re: Re:'' prefix" ); }); }); }); }); });