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" ); }); }); }); }); });