Files
odoo_source/addons/mail/static/tests/discuss_tests.js
T
c80fa71adb [IMP] web,*: search menus always visible
Part of task 2234547

Co-authored-by: Mohammed Shekha <msh@odoo.com>
Co-authored-by: Aaron Bohy <aab@odoo.com>
2020-05-04 14:31:11 +00:00

2039 lines
69 KiB
JavaScript

odoo.define('mail.discuss_test', function (require) {
"use strict";
const Discuss = require('mail.Discuss');
var mailTestUtils = require('mail.testUtils');
var testUtils = require('web.test_utils');
var createDiscuss = mailTestUtils.createDiscuss;
const cpHelpers = testUtils.controlPanel;
QUnit.module('mail', {}, function () {
QUnit.module('Discuss', {
beforeEach: function () {
// patch _.debounce and _.throttle to be fast and synchronous
this.underscoreDebounce = _.debounce;
this.underscoreThrottle = _.throttle;
_.debounce = _.identity;
_.throttle = _.identity;
this.data = {
'mail.message': {
fields: {
body: {
string: "Contents",
type: 'html',
},
author_id: {
string: "Author",
relation: 'res.partner',
},
channel_ids: {
string: "Channels",
type: 'many2many',
relation: 'mail.channel',
},
starred: {
string: "Starred",
type: 'boolean',
},
needaction: {
string: "Need Action",
type: 'boolean',
},
needaction_partner_ids: {
string: "Partners with Need Action",
type: 'many2many',
relation: 'res.partner',
},
starred_partner_ids: {
string: "Favorited By",
type: 'many2many',
relation: 'res.partner',
},
history_partner_ids: {
string: "Partners with History",
type: 'many2many',
relation: 'res.partner',
},
model: {
string: "Related Document model",
type: 'char',
},
res_id: {
string: "Related Document ID",
type: 'integer',
},
},
records: [],
},
'res.partner': {
fields: {
im_status: {
string: "status",
type: 'char',
},
},
records: [{
id: 1,
im_status: 'online',
}]
},
'mail.notification': {
fields: {
is_read: {
string: "Is Read",
type: 'boolean',
},
mail_message_id: {
string: "Message",
type: 'many2one',
relation: 'mail.message',
},
res_partner_id: {
string: "Needaction Recipient",
type: 'many2one',
relation: 'res.partner',
},
},
},
};
this.services = mailTestUtils.getMailServices();
},
afterEach: function () {
// unpatch _.debounce and _.throttle
_.debounce = this.underscoreDebounce;
_.throttle = this.underscoreThrottle;
}
});
QUnit.test('basic rendering', async function (assert) {
assert.expect(6);
var discuss = await createDiscuss({
id: 1,
context: {},
params: {},
data: this.data,
services: this.services,
});
// test basic rendering
var $sidebar = discuss.$('.o_mail_discuss_sidebar');
assert.strictEqual($sidebar.length, 1,
"should have rendered a sidebar");
assert.containsOnce(discuss, '.o_mail_discuss_content',
"should have rendered the content");
assert.containsOnce(discuss, '.o_mail_no_content',
"should display no content message");
var $inbox = $sidebar.find('.o_mail_discuss_item[data-thread-id=mailbox_inbox]');
assert.strictEqual($inbox.length, 1,
"should have the mailbox item 'mailbox_inbox' in the sidebar");
var $starred = $sidebar.find('.o_mail_discuss_item[data-thread-id=mailbox_starred]');
assert.strictEqual($starred.length, 1,
"should have the mailbox item 'mailbox_starred' in the sidebar");
var $history = $sidebar.find('.o_mail_discuss_item[data-thread-id=mailbox_history]');
assert.strictEqual($history.length, 1,
"should have the mailbox item 'mailbox_history' in the sidebar");
discuss.destroy();
});
QUnit.test('messaging not ready', async function (assert) {
assert.expect(9);
const messagingReadyProm = testUtils.makeTestPromise();
const discuss = await createDiscuss({
id: 1,
context: {},
params: {},
data: this.data,
services: this.services,
async mockRPC(route, args) {
if (route === '/mail/init_messaging') {
const _super = this._super.bind(this, ...arguments); // limitation of class.js
assert.step('/mail/init_messaging:pending');
await messagingReadyProm;
assert.step('/mail/init_messaging:resolved');
return _super();
}
return this._super(...arguments);
},
});
assert.verifySteps(['/mail/init_messaging:pending']);
assert.ok(
discuss.el,
"discuss should be rendered");
assert.strictEqual(
$('.o_action')[0],
discuss.el,
"should display discuss even when messaging is not ready");
assert.containsOnce(
discuss,
'.o_mail_discuss_sidebar .o_mail_discuss_loading',
"should display sidebar is loading (messaging not yet ready)");
assert.containsOnce(
discuss,
'.o_mail_discuss_content .o_mail_discuss_loading',
"should display content is loading (messaging not yet ready)");
messagingReadyProm.resolve();
await testUtils.nextTick();
assert.verifySteps(['/mail/init_messaging:resolved']);
assert.containsNone(
discuss,
'.o_mail_discuss_loading',
"should no longer display sidebar or content is loading (messaging is ready)");
discuss.destroy();
});
QUnit.test('messaging initially ready', async function (assert) {
assert.expect(7);
const startDiscussProm = testUtils.makeTestPromise();
testUtils.mock.patch(Discuss, {
/**
* @override
*/
async start() {
const _super = this._super.bind(this, ...arguments); // due to limitation of class.js
assert.step('discuss:starting');
await startDiscussProm;
assert.step('discuss:started');
return _super();
},
});
const discussProm = createDiscuss({
id: 1,
context: {},
params: {},
data: this.data,
services: this.services,
mockRPC(route) {
if (route === '/mail/init_messaging') {
assert.step(route);
}
return this._super(...arguments);
}
});
await testUtils.nextTick();
assert.verifySteps([
'/mail/init_messaging',
'discuss:starting',
]);
startDiscussProm.resolve();
await testUtils.nextTick();
assert.verifySteps(['discuss:started']);
const discuss = await discussProm;
assert.ok(
discuss.el,
"discuss should be rendered");
assert.containsNone(
discuss,
'.o_mail_discuss_loading',
"should not display sidebar or content is loading (messaging is ready)");
testUtils.mock.unpatch(Discuss);
discuss.destroy();
});
QUnit.test('searchview options visibility', async function (assert) {
assert.expect(2);
var discuss = await createDiscuss({
id: 1,
context: {},
params: {},
data: this.data,
services: this.services,
});
assert.hasClass(discuss.$('.o_control_panel .o_searchview_icon.fa'), 'fa-search',
"should have a search icon");
assert.isVisible(discuss.$('.o_control_panel .o_search_options'),
"search options should always be visible");
discuss.destroy();
});
QUnit.test('searchview filter messages', async function (assert) {
assert.expect(10);
this.data['mail.message'].records = [{
author_id: [5, 'Demo User'],
body: '<p>abc</p>',
id: 1,
needaction: true,
needaction_partner_ids: [3],
}, {
author_id: [6, 'Test User'],
body: '<p>def</p>',
id: 2,
needaction: true,
needaction_partner_ids: [3],
}];
var discuss = await createDiscuss({
id: 1,
context: {},
params: {},
data: this.data,
services: this.services,
session: {
partner_id: 3
},
archs: {
'mail.message,false,search':
'<search>' +
'<field name="body"/>' +
'</search>',
},
});
assert.containsN(discuss, '.o_thread_message', 2,
"there should be two messages in the inbox mailbox");
assert.strictEqual($('.o_searchview_input').length, 1,
"there should be a searchview on discuss");
assert.strictEqual($('.o_searchview_input').val(), '',
"the searchview should be empty initially");
// interact with searchview so that there is only once message
await cpHelpers.editSearch(discuss, "ab");
await cpHelpers.validateSearch(discuss);
assert.strictEqual($('.o_searchview_facet').length, 1,
"the searchview should have a facet");
assert.strictEqual($('.o_facet_values').text().trim(), 'ab',
"the facet should be a search on 'ab'");
assert.containsOnce(discuss, '.o_thread_message',
"there should be a single message after filter");
// interact with search view so that there are no matching messages
await testUtils.dom.click($('.o_facet_remove'));
await cpHelpers.editSearch(discuss, "abcd");
await cpHelpers.validateSearch(discuss);
assert.strictEqual($('.o_searchview_facet').length, 1,
"the searchview should have a facet");
assert.strictEqual($('.o_facet_values').text().trim(), 'abcd',
"the facet should be a search on 'abcd'");
assert.containsNone(discuss, '.o_thread_message',
"there should be no message after 2nd filter");
assert.strictEqual(discuss.$('.o_thread_title').text().trim(),
"No matches found",
"should display that there are no matching messages");
discuss.destroy();
});
QUnit.test('unescape channel name in the sidebar', async function (assert) {
// When the user creates a channel, the channel's name is escaped, this in
// order to prevent XSS attacks. However, the user should see visually the
// unescaped name of the channel. For instance, when the user creates a
// channel named "R&D", he should see "R&D" and not "R&amp;D".
assert.expect(2);
this.data.initMessaging = {
channel_slots: {
channel_channel: [{
id: 1,
channel_type: "channel",
name: "R&amp;D",
}],
},
};
var discuss = await createDiscuss({
id: 1,
context: {},
params: {},
data: this.data,
services: this.services,
});
var $sidebar = discuss.$('.o_mail_discuss_sidebar');
var $channel = $sidebar.find('.o_mail_discuss_item[data-thread-id=1]');
assert.strictEqual($channel.length, 1,
"should have the channel item for channel 1 in the sidebar");
assert.strictEqual($channel.find('.o_thread_name').text().replace(/\s/g, ''),
"#R&D",
"should have unescaped channel name in the sidebar");
discuss.destroy();
});
QUnit.test('@ mention in channel', async function (assert) {
assert.expect(34);
var fetchListenersDef = testUtils.makeTestPromise();
var receiveMessageDef = testUtils.makeTestPromise();
this.data.initMessaging = {
channel_slots: {
channel_channel: [{
id: 1,
channel_type: "channel",
name: "general",
}],
},
};
var objectDiscuss;
var discuss = await createDiscuss({
id: 1,
context: {},
params: {},
data: this.data,
services: this.services,
mockRPC: function (route, args) {
if (args.method === 'channel_fetch_listeners') {
fetchListenersDef.resolve();
return Promise.resolve([
{id: 1, name: 'Admin'},
{id: 2, name: 'TestUser'},
{id: 3, name: 'DemoUser'}
]);
}
if (args.method === 'message_post') {
var data = {
author_id: ["42", "Me"],
body: args.kwargs.body,
channel_ids: [1],
};
var notification = [[false, 'mail.channel', 1], data];
objectDiscuss.call('bus_service', 'trigger', 'notification', [notification]);
receiveMessageDef.resolve();
return Promise.resolve(42);
}
return this._super.apply(this, arguments);
},
});
objectDiscuss = discuss;
var $general = discuss.$('.o_mail_discuss_sidebar')
.find('.o_mail_discuss_item[data-thread-id=1]');
assert.strictEqual($general.length, 1,
"should have the channel item with id 1");
assert.hasAttrValue($general, 'title', 'general',
"should have the title 'general'");
// click on general
await testUtils.dom.click($general);
var $input = discuss.$('textarea.o_composer_text_field').first();
assert.ok($input.length, "should display a composer input");
// Note: focus is needed in order to trigger rpc 'channel_fetch_listeners'
$input.focus();
$input.val("@");
$input.trigger('keyup');
fetchListenersDef;
await testUtils.nextTick();
assert.containsOnce(discuss, '.dropup.o_composer_mention_dropdown.show',
"dropup menu for partner mentions should be open");
var $mentionPropositions = discuss.$('.o_mention_proposition');
assert.strictEqual($mentionPropositions.length, 3,
"should display 3 partner mention propositions");
var $mention1 = $mentionPropositions.first();
var $mention2 = $mentionPropositions.first().next();
var $mention3 = $mentionPropositions.first().next().next();
// correct 1st mention proposition
assert.hasClass($mention1,'active',
"first partner mention should be active");
assert.strictEqual($mention1.data('id'), 1,
"first partner mention should link to the correct partner id");
assert.strictEqual($mention1.find('.o_mention_name').text(), "Admin",
"first partner mention should display the correct partner name");
// correct 2nd mention proposition
assert.doesNotHaveClass($mention2, 'active',
"second partner mention should not be active");
assert.strictEqual($mention2.data('id'), 2,
"second partner mention should link to the correct partner id");
assert.strictEqual($mention2.find('.o_mention_name').text(), "TestUser",
"second partner mention should display the correct partner name");
// correct 3rd mention proposition
assert.doesNotHaveClass($mention3, 'active',
"third partner mention should not be active");
assert.strictEqual($mention3.data('id'), 3,
"third partner mention should link to the correct partner id");
assert.strictEqual($mention3.find('.o_mention_name').text(), "DemoUser",
"third partner mention should display the correct partner name");
// check DOWN event
await testUtils.fields.triggerKey('up', $input, 'down');
assert.doesNotHaveClass($mention1, 'active',
"first partner mention should not be active");
assert.hasClass($mention2,'active',
"second partner mention should be active");
assert.doesNotHaveClass($mention3, 'active',
"third partner mention should not be active");
// check UP event
await testUtils.fields.triggerKey('up', $input, 'up');
assert.hasClass($mention1,'active',
"first partner mention should be active");
assert.doesNotHaveClass($mention2, 'active',
"second partner mention should not be active");
assert.doesNotHaveClass($mention3, 'active',
"third partner mention should not be active");
// check TAB event (full cycle, hence 3 TABs)
await testUtils.fields.triggerKey('up', $input, 'tab');
assert.doesNotHaveClass($mention1, 'active',
"first partner mention should not be active");
assert.hasClass($mention2,'active',
"second partner mention should be active");
assert.doesNotHaveClass($mention3, 'active',
"third partner mention should not be active");
await testUtils.fields.triggerKey('up', $input, 'tab');
assert.doesNotHaveClass($mention1, 'active',
"first partner mention should not be active");
assert.doesNotHaveClass($mention2, 'active',
"second partner mention should not be active");
assert.hasClass($mention3,'active',
"third partner mention should be active");
await testUtils.fields.triggerKey('up', $input, 'tab');
assert.hasClass($mention1,'active',
"first partner mention should be active");
assert.doesNotHaveClass($mention2, 'active',
"second partner mention should not be active");
assert.doesNotHaveClass($mention3, 'active',
"third partner mention should not be active");
await testUtils.fields.triggerKey('up', $input, 'enter');
assert.containsNone(discuss, '.o_mention_proposition',
"should not have any partner mention proposition after ENTER");
assert.strictEqual($input.val().trim() , "@Admin",
"should have the correct mention link in the composer input");
// send message
await testUtils.fields.triggerKeydown($input, 'enter');
await receiveMessageDef;
await testUtils.nextTick();
assert.containsOnce(discuss, '.o_thread_message_content',
"should display one message with some content");
assert.containsOnce(discuss, '.o_thread_message_content a',
"should contain a link in the message content");
assert.strictEqual(discuss.$('.o_thread_message_content a').text(),
"@Admin", "should have correct mention link in the message content");
discuss.destroy();
});
QUnit.test('@ mention with special chars', async function (assert) {
assert.expect(11);
var fetchListenersDef = testUtils.makeTestPromise();
var receiveMessageDef = testUtils.makeTestPromise();
this.data.initMessaging = {
channel_slots: {
channel_channel: [{
id: 1,
channel_type: "channel",
name: "general",
}],
},
};
var objectDiscuss;
var discuss = await createDiscuss({
id: 1,
context: {},
params: {},
data: this.data,
services: this.services,
mockRPC: function (route, args) {
if (args.method === 'channel_fetch_listeners') {
fetchListenersDef.resolve();
return Promise.resolve([
{id: 1, name: '\u0405pëciãlUser<&>"`\' \u30C4'},
]);
}
if (args.method === 'message_post') {
assert.deepEqual(args.kwargs.partner_ids, [1],
"mentioned partners are sent to server"
)
var data = {
author_id: ["42", "Me"],
body: args.kwargs.body,
channel_ids: [1],
};
var notification = [[false, 'mail.channel', 1], data];
objectDiscuss.call('bus_service', 'trigger', 'notification', [notification]);
receiveMessageDef.resolve();
return Promise.resolve(42);
}
return this._super.apply(this, arguments);
},
});
objectDiscuss = discuss;
var $general = discuss.$('.o_mail_discuss_sidebar')
.find('.o_mail_discuss_item[data-thread-id=1]');
// click on general
$general.click();
await testUtils.nextTick();
var $input = discuss.$('textarea.o_composer_text_field').first();
assert.ok($input.length, "should display a composer input");
// Note: focus is needed in order to trigger rpc 'channel_fetch_listeners'
$input.focus();
$input.val("@");
$input.trigger('keyup');
await fetchListenersDef;
await testUtils.nextTick();
var $mention = discuss.$('.o_mention_proposition');
// correct mention proposition
assert.ok($mention.hasClass('active'),
"first partner mention should be active");
assert.strictEqual($mention.data('id'), 1,
"first partner mention should link to the correct partner id");
assert.strictEqual($mention.find('.o_mention_name').text(), '\u0405pëciãlUser<&>"`\' \u30C4',
"first partner mention should display the correct partner name");
// equivalent to $mentionPropositions.find('active').click();
await testUtils.fields.triggerKey('up', $input, 'enter');
assert.strictEqual(discuss.$('.o_mention_proposition').length, 0,
"should not have any partner mention proposition after ENTER");
assert.strictEqual($input.val().trim() , "@\u0405pëciãlUser<&>\"`'\u00A0\u30C4",
"should have the correct mention link in the composer input");
// send message
await testUtils.fields.triggerKeydown($input, 'enter');
await receiveMessageDef;
await testUtils.nextTick();
assert.strictEqual(discuss.$('.o_thread_message_content').length, 1,
"should display one message with some content");
assert.strictEqual(discuss.$('.o_thread_message_content a').length, 1,
"should contain a link in the message content");
assert.strictEqual(discuss.$('.o_thread_message_content a').text(),
"@\u0405pëciãlUser<&>\"`' \u30C4",
"should have correct mention link in the message content");
$input.val("@");
$input.trigger('keyup');
await testUtils.nextTick();
var $mention = discuss.$('.o_mention_proposition');
assert.strictEqual($mention.find('.o_mention_name').text(),
'\u0405pëciãlUser<&>"`\' \u30C4',
"first partner mention should still display the correct partner name");
discuss.destroy();
});
QUnit.test('@ mention with removed space', async function (assert) {
assert.expect(4);
var fetchListenersDef = testUtils.makeTestPromise();
var receiveMessageDef = testUtils.makeTestPromise();
this.data.initMessaging = {
channel_slots: {
channel_channel: [{
id: 1,
channel_type: "channel",
name: "general",
}],
},
};
var objectDiscuss;
var discuss = await createDiscuss({
id: 1,
context: {},
params: {},
data: this.data,
services: this.services,
mockRPC: function (route, args) {
if (args.method === 'channel_fetch_listeners') {
fetchListenersDef.resolve();
return Promise.resolve([
{id: 1, name: 'Admin'},
]);
}
if (args.method === 'message_post') {
assert.deepEqual(args.kwargs.partner_ids, [],
"mentioned partners are sent to server"
);
var data = {
author_id: ["42", "Me"],
body: args.kwargs.body,
channel_ids: [1],
};
var notification = [[false, 'mail.channel', 1], data];
objectDiscuss.call('bus_service', 'trigger', 'notification', [notification]);
receiveMessageDef.resolve();
return Promise.resolve(42);
}
return this._super.apply(this, arguments);
},
});
objectDiscuss = discuss;
var $general = discuss.$('.o_mail_discuss_sidebar')
.find('.o_mail_discuss_item[data-thread-id=1]');
// click on general
$general.click();
var $input = discuss.$('textarea.o_composer_text_field').first();
assert.ok($input.length, "should display a composer input");
// Note: focus is needed in order to trigger rpc 'channel_fetch_listeners'
$input.focus();
$input.val("@");
$input.trigger('keyup');
await fetchListenersDef;
await testUtils.nextTick();
// equivalent to $mentionPropositions.find('active').click();
$input.trigger($.Event('keyup', {which: $.ui.keyCode.ENTER}));
$input.val('@Admin: hi');
// send message
await testUtils.fields.triggerKeydown($input, 'enter');
await receiveMessageDef;
await testUtils.nextTick();
assert.containsNone(discuss, '.o_thread_message_content a',
"should not contain a link in the message content");
assert.strictEqual(discuss.$('.o_thread_message_content').html().trim(),
"@Admin: hi", "should not have linked mention without space");
discuss.destroy();
});
QUnit.test('@ mention in mailing channel', async function (assert) {
assert.expect(8);
var fetchListenersDef = testUtils.makeTestPromise();
this.data.initMessaging = {
channel_slots: {
channel_channel: [{
id: 1,
channel_type: "channel",
name: "general",
mass_mailing: true,
}],
},
};
var discuss = await createDiscuss({
id: 1,
context: {},
params: {},
data: this.data,
services: this.services,
mockRPC: function (route, args) {
if (args.method === 'channel_fetch_listeners') {
fetchListenersDef.resolve();
return Promise.resolve([
{id: 1, name: 'Admin'},
]);
}
return this._super.apply(this, arguments);
},
});
var $general = discuss.$('.o_mail_discuss_sidebar')
.find('.o_mail_discuss_item[data-thread-id=1]');
// click on general
$general.click();
await testUtils.nextTick();
// 1st composer: basic composer (hidden), 2nd composer: extended (shown)
var $input = discuss.$('textarea.o_composer_text_field').eq(1);
assert.ok($input.length, "should display a composer input");
// Simulate '@' typed by user with mocked Window.getSelection
// Note: focus is needed in order to trigger rpc 'channel_fetch_listeners'
$input.focus();
$input.val("@");
$input.trigger('keyup');
await fetchListenersDef;
await testUtils.nextTick();
assert.strictEqual(discuss.$('.dropup.o_composer_mention_dropdown.show').length, 1,
"dropup menu for partner mentions should be open");
var $mention = discuss.$('.o_mention_proposition');
assert.strictEqual($mention.length, 1,
"should display 1 partner mention proposition");
// correct mention proposition
assert.ok($mention.hasClass('active'),
"partner mention should be active");
assert.strictEqual($mention.data('id'), 1,
"partner mention should link to the correct partner id");
assert.strictEqual($mention.find('.o_mention_name').text(), "Admin",
"partner mention should display the correct partner name");
// equivalent to $mentionPropositions.find('active').click();
await testUtils.fields.triggerKey('up', $input, 'enter');
assert.ok($input.is(':focus'), "composer body should have focus");
assert.notOk(discuss.$('.o_composer_subject').is(':focus'));
discuss.destroy();
});
QUnit.test('no crash focusout emoji button', async function (assert) {
assert.expect(3);
this.data.initMessaging = {
channel_slots: {
channel_channel: [{
id: 1,
channel_type: "channel",
name: "general",
}],
},
};
var discuss = await createDiscuss({
id: 1,
context: {},
params: {},
data: this.data,
services: this.services,
})
var $general = discuss.$('.o_mail_discuss_sidebar')
.find('.o_mail_discuss_item[data-thread-id=1]');
assert.strictEqual($general.length, 1,
"should have the channel item with id 1");
assert.hasAttrValue($general, 'title', 'general',
"should have the title 'general'");
// click on general
await testUtils.dom.click($general);
discuss.$('.o_composer_button_emoji').focus();
try {
discuss.$('.o_composer_button_emoji').focusout();
assert.ok(true, "should not crash on focusout of emoji button");
} finally {
discuss.destroy();
}
});
QUnit.test('older messages are loaded on scroll', async function (assert) {
assert.expect(10);
var fetchCount = 0;
var loadMoreDef = testUtils.makeTestPromise();
var messageData = [];
for (var i = 0; i < 35; i++) {
messageData.push({
author_id: ['1', 'Me'],
body: '<p>test ' + i + '</p>',
channel_ids: [1],
id: i,
});
}
this.data.initMessaging = {
channel_slots: {
channel_channel: [{
id: 1,
channel_type: "channel",
name: "general",
static: true,
}],
},
};
this.data['mail.message'].records = messageData;
var discuss = await createDiscuss({
context: {},
data: this.data,
params: {},
services: this.services,
mockRPC: function (route, args) {
if (args.method === 'message_fetch') {
assert.step(args.method);
fetchCount++;
// 1st fetch: inbox initial fetch
// 2nd fetch: general initial fetch
// 3rd fetch: general load more
if (fetchCount === 1) {
assert.strictEqual(args.kwargs.limit, 30,
"should ask to fetch 30 messages at most");
}
if (fetchCount === 3) {
loadMoreDef.resolve();
}
}
return this._super.apply(this, arguments);
},
});
assert.verifySteps(['message_fetch'],
"should fetch messages once for needaction messages (Inbox)");
var $general = discuss.$('.o_mail_discuss_item[data-thread-id=1]');
assert.strictEqual($general.length, 1,
"should have a channel item with id 1");
// switch to 'general'
await testUtils.dom.click($general);
assert.verifySteps(['message_fetch'],
"should fetch a second time for general channel messages (30 last messages)");
assert.containsN(discuss, '.o_thread_message', 30,
"should display the 30 messages");
// simulate a scroll to top to load more messages
discuss.$('.o_mail_thread').scrollTop(0);
await loadMoreDef;
await testUtils.nextTick();
assert.verifySteps(['message_fetch'],
"should fetch a third time for general channel messages (5 remaining messages)");
assert.containsN(discuss, '.o_thread_message', 35,
"all messages should now be loaded");
discuss.destroy();
});
QUnit.test('"Unstar all" button should reset the starred counter', async function (assert) {
assert.expect(2);
var messageData = [];
_.each(_.range(1, 41), function (num) {
messageData.push({
id: num,
body: "<p>test" + num + "</p>",
author_id: ["1", "Me"],
channel_ids: [1],
starred: true,
starred_partner_ids: [1],
}
);
});
this.data.initMessaging = {
channel_slots: {
channel_channel: [{
id: 1,
channel_type: "channel",
name: "general",
}],
},
starred_counter: messageData.length,
};
this.data['mail.message'].records = messageData;
var objectDiscuss;
var discuss = await createDiscuss({
id: 1,
context: {},
params: {},
data: this.data,
services: this.services,
mockRPC: function (route, args) {
if (args.method === 'unstar_all') {
var data = {
message_ids: _.range(1, 41),
starred: false,
type: 'toggle_star',
};
var notification = [[false, 'res.partner'], data];
objectDiscuss.call('bus_service', 'trigger', 'notification', [notification]);
return Promise.resolve(42);
}
return this._super.apply(this, arguments);
},
session: {partner_id: 1},
})
objectDiscuss = discuss;
var $starred = discuss.$('.o_mail_discuss_sidebar').find('.o_mail_mailbox_title_starred');
var $starredCounter = $('.o_mail_mailbox_title_starred > .o_mail_sidebar_needaction');
// Go to Starred channel
await testUtils.dom.click($starred);
// Test Initial Value
assert.strictEqual($starredCounter.text().trim(), "40", "40 messages should be starred");
// Unstar all and wait 'update_starred'
await testUtils.dom.click($('.o_control_panel .o_mail_discuss_button_unstar_all'));
$starredCounter = $('.o_mail_mailbox_title_starred > .o_mail_sidebar_needaction');
assert.strictEqual($starredCounter.text().trim(), "0",
"All messages should be unstarred");
discuss.destroy();
});
QUnit.test('confirm dialog when administrator leave (not chat) channel', async function (assert) {
assert.expect(2);
this.data.initMessaging = {
channel_slots: {
channel_channel: [{
id: 1,
channel_type: "channel",
name: "MyChannel",
create_uid: 3,
}],
},
};
var discuss = await createDiscuss({
id: 1,
context: {},
params: {},
data: this.data,
services: this.services,
session: {
uid: 3,
},
});
// Unsubscribe on MyChannel as administrator
// The unpin icon is only shown when hovering on sidebar item.
// helpter dom.click needs the element to be visible, hence the
// hack on display of this icon.
discuss.$('.o_mail_partner_unpin').css('display', 'block');
await testUtils.dom.click(discuss.$('.o_mail_partner_unpin'));
assert.strictEqual($('.modal-dialog').length, 1,
"should display a dialog");
assert.strictEqual($('.modal-body').text(),
"You are the administrator of this channel. Are you sure you want to unsubscribe?",
"Warn user that he will be unsubscribed from channel as admin.");
discuss.destroy();
});
QUnit.test('convert emoji sources to unicodes on message_post', async function (assert) {
assert.expect(2);
var receiveMessageDef = testUtils.makeTestPromise();
this.data.initMessaging = {
channel_slots: {
channel_channel: [{
id: 1,
channel_type: "channel",
name: "general",
}],
},
};
var objectDiscuss;
var discuss = await createDiscuss({
id: 1,
context: {},
params: {},
data: this.data,
services: this.services,
mockRPC: function (route, args) {
if (args.method === 'message_post') {
assert.strictEqual(args.kwargs.body, "😊 😂",
"message_post data should have all emojis in their unicode representation");
var data = {
author_id: ["42", "Me"],
body: args.kwargs.body,
channel_ids: [1],
};
var notification = [[false, 'mail.channel', 1], data];
objectDiscuss.call('bus_service', 'trigger', 'notification', [notification]);
receiveMessageDef.resolve();
return Promise.resolve(42);
}
return this._super.apply(this, arguments);
},
})
objectDiscuss= discuss;
var $general = discuss.$('.o_mail_discuss_sidebar')
.find('.o_mail_discuss_item[data-thread-id=1]');
// click on general
await testUtils.dom.click($general);
var $input = discuss.$('textarea.o_composer_text_field').first();
$input.focus();
$input.val(":) x'D");
await testUtils.fields.triggerKeydown($input, 'enter');
await receiveMessageDef.then(function () {
assert.strictEqual(discuss.$('.o_thread_message_content').text().replace(/\s/g, ""),
"😊😂",
"New posted message should contain all emojis in their unicode representation");
discuss.destroy();
});
});
QUnit.test('mark all messages as read from Inbox', async function (assert) {
assert.expect(9);
this.data['mail.message'].records = [{
author_id: [5, 'Demo User'],
body: '<p>test 1</p>',
id: 1,
needaction: true,
needaction_partner_ids: [3],
}, {
author_id: [6, 'Test User'],
body: '<p>test 2</p>',
id: 2,
needaction: true,
needaction_partner_ids: [3],
}];
this.data.initMessaging = {
needaction_inbox_counter: 2,
};
var markAllReadDef = testUtils.makeTestPromise();
var objectDiscuss;
var discuss = await createDiscuss({
id: 1,
context: {},
params: {},
data: this.data,
services: this.services,
session: { partner_id: 3 },
mockRPC: function (route, args) {
if (args.method === 'mark_all_as_read') {
_.each(this.data['mail.message'].records, function (message) {
message.needaction = false;
});
var notificationData = {
type: 'mark_as_read',
message_ids: [1, 2],
};
var notification = [[false, 'res.partner', 3], notificationData];
objectDiscuss.call('bus_service', 'trigger', 'notification', [notification]);
markAllReadDef.resolve();
return Promise.resolve();
}
return this._super.apply(this, arguments);
},
})
objectDiscuss = discuss;
var $inbox = discuss.$('.o_mail_discuss_item[data-thread-id="mailbox_inbox"]');
assert.strictEqual($inbox.length, 1,
"there should be an 'Inbox' item in Discuss sidebar");
assert.strictEqual($inbox.find('.o_mail_sidebar_needaction').text().trim(), "2",
"the mailbox counter of 'Inbox' should be 2");
assert.hasClass($inbox,'o_active',
"'Inbox' should be the currently active thread");
assert.containsN(discuss, '.o_thread_message', 2,
"there should be 2 messages in inbox");
var $markAllReadButton = $('.o_mail_discuss_button_mark_all_read');
assert.strictEqual($markAllReadButton.length, 1,
"there should be a 'Mark All As Read' button");
assert.hasAttrValue($markAllReadButton, 'style',
'display: inline-block;',
"the 'Mark All As Read' button should be visible");
assert.notOk($markAllReadButton.prop('disabled'),
"the 'Mark All As Read' button should not be disabled");
await testUtils.dom.click($markAllReadButton);
markAllReadDef.then(function () {
// immediately jump to end of the fadeout animation on messages
$inbox = discuss.$('.o_mail_discuss_item[data-thread-id="mailbox_inbox"]');
discuss.$('.o_thread_message').stop(false, true);
assert.strictEqual($inbox.find('.o_mail_sidebar_needaction').text().trim(), "0",
"the mailbox counter of 'Inbox' should have reset to 0");
assert.containsNone(discuss, '.o_thread_message',
"there should no message in inbox anymore");
discuss.destroy();
});
});
QUnit.test('drag and drop file in composer [REQUIRE NON-INCOGNITO WINDOW]', async function (assert) {
assert.expect(8);
this.data.initMessaging = {
channel_slots: {
channel_channel: [{
id: 1,
channel_type: "channel",
name: "general",
}],
},
};
var discuss = await createDiscuss({
id: 1,
context: {},
params: {},
data: this.data,
services: this.services,
})
var $general = discuss.$('.o_mail_discuss_sidebar')
.find('.o_mail_discuss_item[data-thread-id=1]');
// click on general
await testUtils.dom.click($general);
// first composer is active (basic), 2nd is hidden (extended)
var $composer = discuss.$('.o_thread_composer').first();
assert.containsNone($composer, '.o_attachments_list',
"should not display any attachment on the composer initially");
assert.containsOnce($composer, '.o_file_drop_zone_container',
"should have a dropzone to drag-and-drop files");
var $dropZoneContainer = $composer.find('.o_file_drop_zone_container');
assert.isNotVisible($dropZoneContainer,
"dropzone should not be visible");
var file = await testUtils.file.createFile({
name: 'text.txt',
content: 'hello, world',
contentType: 'text/plain',
});
await testUtils.file.dragoverFile($dropZoneContainer, file);
assert.isVisible($dropZoneContainer,
"dropzone should be visible");
await testUtils.file.dropFile($dropZoneContainer, file);
assert.containsOnce($composer, '.o_attachments_list',
"should display some attachments on the composer");
assert.containsOnce($composer, '.o_attachment',
"should display one attachment on the composer");
var filename = $('.o_attachment').find('.caption').first().text().trim();
assert.strictEqual(filename, 'text.txt',
"should display the correct filename");
assert.hasClass($('.o_attachment_uploaded i'), 'fa-check',
"text file should have been uploaded");
discuss.destroy();
});
QUnit.test('non-deletable message attachments', async function (assert) {
assert.expect(2);
this.data['mail.message'].records = [{
attachment_ids: [{
filename: "text.txt",
id: 250,
mimetype: 'text/plain',
name: "text.txt",
}, {
filename: "image.png",
id: 251,
mimetype: 'image/png',
name: "image.png",
}],
author_id: [5, "Demo User"],
body: "<p>test</p>",
id: 1,
needaction: true,
needaction_partner_ids: [3],
model: 'some.document',
record_name: "SomeDocument",
res_id: 100,
}];
const discuss = await createDiscuss({
context: {},
data: this.data,
params: {},
services: this.services,
session: {
partner_id: 3,
},
});
assert.containsN(
discuss,
'.o_attachment',
2,
"should display 2 attachments");
assert.containsNone(
discuss.$('.o_attachment'),
'o_attachment_delete_cross',
"attachments should not be deletable");
discuss.destroy();
});
QUnit.test('reply to message from inbox', async function (assert) {
assert.expect(11);
var self = this;
this.data['mail.message'].records = [{
author_id: [5, 'Demo User'],
body: '<p>test 1</p>',
id: 1,
needaction: true,
needaction_partner_ids: [3],
res_id: 100,
model: 'some.document',
record_name: 'SomeDocument',
}];
this.data.initMessaging = {
needaction_inbox_counter: 1,
};
var discuss = await createDiscuss({
id: 1,
context: {},
params: {},
data: this.data,
services: this.services,
session: { partner_id: 3 },
mockRPC: function (route, args) {
if (args.method === 'message_post') {
assert.step(args.method);
assert.strictEqual(args.model, 'some.document',
"should post message to correct document model");
assert.strictEqual(args.args[0], 100,
"should post message to correct document ID");
self.data['mail.message'].records.push({
author_id: [3, 'Me'],
body: args.kwargs.body,
id: 2,
res_id: 100,
model: 'some.document',
record_name: 'SomeDocument',
});
return Promise.resolve(2);
}
return this._super.apply(this, arguments);
},
})
assert.strictEqual(discuss.$('.o_mail_discuss_item.o_active').data('thread-id'),
'mailbox_inbox',
"Inbox should be selected by default");
assert.containsOnce(discuss, '.o_thread_message',
"should display a single message in inbox");
assert.strictEqual(discuss.$('.o_thread_message').data('message-id'), 1,
"message should be linked to correct message");
assert.containsOnce(discuss.$('.o_thread_message'), '.o_thread_message_reply',
"should display the reply icon for message linked to a document");
await testUtils.dom.click(discuss.$('.o_thread_message_reply'));
var $composer = discuss.$('.o_thread_composer_extended');
assert.isVisible($composer,
"extended composer should become visible");
assert.strictEqual($composer.find('.o_composer_subject input').val(),
'Re: SomeDocument',
"composer should have copied document name as subject of message");
var $textarea = $composer.find('.o_composer_input textarea').first();
await testUtils.fields.editInput($textarea, 'someContent');
assert.containsOnce($composer, '.o_composer_button_send',
"should have button to send reply message");
await testUtils.dom.click($composer.find('.o_composer_button_send'));
assert.verifySteps(['message_post']);
discuss.destroy();
});
QUnit.test('discard replying to message from inbox', async function (assert) {
assert.expect(4);
var self = this;
this.data['mail.message'].records = [{
author_id: [5, 'Demo User'],
body: '<p>test 1</p>',
id: 1,
needaction: true,
needaction_partner_ids: [3],
res_id: 100,
model: 'some.document',
record_name: 'SomeDocument',
}];
this.data.initMessaging = {
needaction_inbox_counter: 1,
};
var discuss = await createDiscuss({
id: 1,
context: {},
params: {},
data: this.data,
services: this.services,
session: { partner_id: 3 },
mockRPC: function (route, args) {
if (args.method === 'message_post') {
assert.step(args.method);
assert.strictEqual(args.model, 'some.document',
"should post message to correct document model");
assert.strictEqual(args.args[0], 100,
"should post message to correct document ID");
self.data['mail.message'].records.push({
author_id: [3, 'Me'],
body: args.kwargs.body,
id: 2,
res_id: 100,
model: 'some.document',
record_name: 'SomeDocument',
});
return Promise.resolve(2);
}
return this._super.apply(this, arguments);
},
})
await testUtils.dom.click(discuss.$('.o_thread_message_reply'));
assert.containsOnce(discuss, '.o_thread_selected_message',
"should have a message selected");
var $composer = discuss.$('.o_thread_composer_extended');
assert.containsOnce($composer, '.o_composer_button_discard',
"should have button to discard replying to message");
await testUtils.dom.click($composer.find('.o_composer_button_discard'));
assert.isNotVisible($composer,
"extended composer should become hidden on discard");
assert.containsNone(discuss, '.o_thread_selected_message',
"should not longer have a message selected");
discuss.destroy();
});
QUnit.test('no quick search channels in the sidebar with less than 20 channels', async function (assert) {
assert.expect(3);
var channelsData = [];
_.each(_.range(0, 19), function (num) {
channelsData.push({
id: num,
channel_type: "channel",
name: "Channel" + num,
});
});
this.data.initMessaging = {
channel_slots: {
channel_channel: channelsData,
},
};
var discuss = await createDiscuss({
id: 1,
context: {},
params: {},
data: this.data,
services: this.services,
})
var $sidebar = discuss.$('.o_mail_discuss_sidebar');
var $channelsSidebar = $sidebar.find('.o_mail_discuss_sidebar_channels');
assert.strictEqual($channelsSidebar.length, 1,
"should show channels and dms in sidebar");
assert.strictEqual($channelsSidebar.find('.o_mail_discuss_item').length, 19,
"should show 19 channels in sidebar");
assert.strictEqual(discuss.$('.o_discuss_sidebar_quick_search').length, 0,
"should not display a quick search");
discuss.destroy();
});
QUnit.test('quick search channels in the sidebar with more than 20 channels', async function (assert) {
assert.expect(7);
var channelsData = [];
var channelANum = 15;
_.each(_.range(0, channelANum), function (num) {
var channel = {
id: num,
channel_type: "channel",
name: "ChannelA" + num,
};
channelsData.push(channel);
});
var channelBNum = 5;
_.each(_.range(0, channelBNum), function (num) {
var channel = {
id: channelANum + num,
channel_type: "channel",
name: "ChannelB" + num,
};
channelsData.push(channel);
});
this.data.initMessaging = {
channel_slots: {
channel_channel: channelsData,
},
};
var discuss = await createDiscuss({
id: 1,
context: {},
params: {},
data: this.data,
services: this.services,
})
var $sidebar = discuss.$('.o_mail_discuss_sidebar');
var $channelsSidebar = $sidebar.find('.o_mail_discuss_sidebar_channels');
assert.strictEqual($channelsSidebar.length, 1,
"should show channels in sidebar");
assert.strictEqual($channelsSidebar.find('.o_mail_discuss_item').length, 20,
"should show 20 channels in sidebar");
assert.strictEqual(discuss.$('.o_discuss_sidebar_quick_search').length, 1,
"should display a quick search");
discuss.$('.o_discuss_sidebar_quick_search input').val('channelA').trigger('input');
$channelsSidebar = $sidebar.find('.o_mail_discuss_sidebar_channels');
assert.strictEqual($channelsSidebar.find('.o_mail_discuss_item').length, 15,
"should now show 15 channels in sidebar");
discuss.$('.o_discuss_sidebar_quick_search input').val('channelB').trigger('input');
$channelsSidebar = $sidebar.find('.o_mail_discuss_sidebar_channels');
assert.strictEqual($channelsSidebar.find('.o_mail_discuss_item').length, 5,
"should now show 5 channels in sidebar");
discuss.$('.o_discuss_sidebar_quick_search input').val('channelB4').trigger('input');
$channelsSidebar = $sidebar.find('.o_mail_discuss_sidebar_channels');
assert.strictEqual($channelsSidebar.find('.o_mail_discuss_item').length, 1,
"should now show a single channel in sidebar");
assert.strictEqual($channelsSidebar.find('.o_thread_name').text().replace(/\s/g, ''),
'#ChannelB4',
"should have searched the correct channel B4");
discuss.destroy();
});
QUnit.test('select emoji replaces cursor position', async function (assert) {
assert.expect(5);
this.data.initMessaging = {
channel_slots: {
channel_channel: [{
id: 1,
channel_type: "channel",
name: "general",
}],
},
};
var discuss = await createDiscuss({
id: 1,
context: {},
params: {},
data: this.data,
services: this.services,
});
var $general = discuss.$('.o_mail_discuss_sidebar')
.find('.o_mail_discuss_item[data-thread-id=1]');
assert.strictEqual($general.length, 1,
"should have the channel item with id 1");
assert.hasAttrValue($general, 'title', 'general',
"should have the title 'general'");
// click on general
await testUtils.dom.click($general);
// general uses basic composer, so this is the 1st composer
// (2nd composer is hidden)
var $composer = discuss.$('.o_composer_text_field').first();
await testUtils.fields.editInput($composer, 'abcdefgh');
assert.strictEqual($composer.val(), "abcdefgh");
// cursor position: ab|cd|efgh (selecting c and d)
$composer[0].setSelectionRange(2, 4);
await testUtils.dom.click(discuss.$('.o_composer_button_emoji'));
await testUtils.dom.triggerMouseEvent(discuss.$('.o_mail_emoji[data-emoji=":)"]'), 'mousedown');
assert.strictEqual($composer.val(), "ab :) efgh",
"should have inserted emoji ");
await testUtils.dom.click(discuss.$('.o_composer_button_emoji'));
await testUtils.dom.triggerMouseEvent(discuss.$('.o_mail_emoji[data-emoji=":)"]'), 'mousedown');
assert.strictEqual($composer.val(), "ab :) :) efgh",
"should have inserted emoji after previously inserted emoji");
discuss.destroy();
});
QUnit.test('rename DM conversation', async function (assert) {
assert.expect(11);
this.data.initMessaging = {
channel_slots: {
channel_direct_message: [{
id: 1,
channel_type: "chat",
create_uid: 3,
direct_partner: [{
id: 7,
im_status: 'online',
name: 'Demo User',
}],
}],
},
};
var discuss = await createDiscuss({
id: 1,
context: {},
params: {},
data: this.data,
services: this.services,
session: { partner_id: 3 },
mockRPC: function (route, args) {
if (args.method === 'channel_set_custom_name') {
assert.step(args.method);
assert.strictEqual(args.args[0], 1);
assert.strictEqual(args.kwargs.name, "Demo");
return Promise.resolve();
}
return this._super.apply(this, arguments);
},
});
var $dm = discuss.$('.o_mail_discuss_item[data-thread-id=1]');
assert.isVisible($dm, "should display DM in the discuss sidebar");
assert.strictEqual($dm.find('.o_thread_name').text().trim(), "Demo User");
// The settings icon is only shown when hovering on sidebar item.
// helpter dom.click needs the element to be visible, hence the
// hack on display of this icon.
discuss.$('.o_mail_channel_settings').css('display', 'block');
await testUtils.dom.click(discuss.$('.o_mail_channel_settings'));
assert.isVisible($('.modal-dialog'));
assert.strictEqual($('.modal-title').text(), "Rename conversation");
assert.isVisible($('.o_mail_discuss_rename_channel_input'));
assert.strictEqual($('.o_mail_discuss_rename_channel_input').val(), "Demo User");
await testUtils.fields.editInput($('.o_mail_discuss_rename_channel_input'), "Demo");
await testUtils.dom.click($('.o_mail_conversation_rename'));
$dm = discuss.$('.o_mail_discuss_item[data-thread-id=1]');
assert.verifySteps(['channel_set_custom_name']);
assert.strictEqual($dm.find('.o_thread_name').text().trim(), "Demo");
discuss.destroy();
});
QUnit.test('custom-named DM conversation', async function (assert) {
assert.expect(2);
this.data.initMessaging = {
channel_slots: {
channel_direct_message: [{
id: 1,
channel_type: "chat",
create_uid: 3,
direct_partner: [{
id: 7,
im_status: 'online',
name: 'Demo User',
}],
custom_channel_name: 'My Buddy',
}],
},
};
var discuss = await createDiscuss({
id: 1,
context: {},
params: {},
data: this.data,
services: this.services,
session: { partner_id: 3 },
})
var $dm = discuss.$('.o_mail_discuss_item[data-thread-id=1]');
assert.isVisible($dm, "should display DM in the discuss sidebar");
assert.strictEqual($dm.find('.o_thread_name').text().trim(), "My Buddy");
discuss.destroy();
});
QUnit.test('receive channel message notification then delayed needaction notification', async function (assert) {
assert.expect(3);
const message = {
author_id: [5, 'Demo User'],
body: '<p>test</p>',
channel_ids: [1],
id: 100,
model: 'mail.channel',
needaction: true,
needaction_partner_ids: [3],
res_id: 1,
};
this.data.initMessaging = {
channel_slots: {
channel_channel: [{
id: 1,
channel_type: 'channel',
name: "general",
}],
},
};
const discuss = await createDiscuss({
context: {},
params: {},
data: this.data,
services: this.services,
session: {
partner_id: 3
},
});
const $inbox = discuss.$('.o_mail_discuss_item[data-thread-id="mailbox_inbox"]');
assert.hasClass(
$inbox,
'o_active',
"'Inbox' should be the currently active thread");
assert.containsNone(
discuss,
'.o_thread_message',
"inbox should contain no messages initially");
// simulate new needaction message posted on channnel
this.data['mail.message'].records.push(message);
// simulate receiving channel notification
discuss.call('bus_service', 'trigger', 'notification', [
[['myDB', 'mail.channel', 1], message]
]);
// short delay after receiving needaction notification
await testUtils.nextTick();
// simulate receiving needaction message notification after a short delay
discuss.call('bus_service', 'trigger', 'notification', [
[['myDB', 'ir.needaction', 3], message]
]);
await testUtils.nextTick();
assert.containsOnce(
discuss,
'.o_thread_message',
"inbox should contain one message");
discuss.destroy();
});
QUnit.test('messages marked as read move to "History" mailbox', async function (assert) {
assert.expect(3);
this.data['mail.message'].records = [{
author_id: [5, 'Demo User'],
body: '<p>test 1</p>',
id: 1,
needaction: true,
needaction_partner_ids: [3],
}, {
author_id: [6, 'Test User'],
body: '<p>test 2</p>',
id: 2,
needaction: true,
needaction_partner_ids: [3],
}];
this.data['mail.notification'].records = [{
id: 50,
is_read: false,
mail_message_id: 1,
res_partner_id: 3,
}, {
id: 51,
is_read: false,
mail_message_id: 1,
res_partner_id: 3,
}];
this.data.initMessaging = {
needaction_inbox_counter: 2,
};
const markAllReadDef = testUtils.makeTestPromise();
let objectDiscuss;
const discuss = await createDiscuss({
id: 1,
context: {},
params: {},
data: this.data,
services: this.services,
session: { partner_id: 3 },
mockRPC(route, args) {
if (args.method === 'mark_all_as_read') {
for (const message of (this.data['mail.message'].records)) {
message.history_partner_ids = [3];
message.needaction_partner_ids = [];
}
const notificationData = {
type: 'mark_as_read',
message_ids: [1, 2],
};
const notification = [[false, 'res.partner', 3], notificationData];
objectDiscuss.call('bus_service', 'trigger', 'notification', [notification]);
markAllReadDef.resolve();
return Promise.resolve(3);
}
return this._super.apply(this, arguments);
},
});
objectDiscuss = discuss;
const $inbox = discuss.$('.o_mail_discuss_item[data-thread-id="mailbox_inbox"]');
let $history = discuss.$('.o_mail_discuss_item[data-thread-id="mailbox_history"]');
await testUtils.dom.click($history);
assert.containsOnce(discuss, '.o_mail_no_content',
"should display no content message");
await testUtils.dom.click($inbox);
var $markAllReadButton = $('.o_mail_discuss_button_mark_all_read');
testUtils.dom.click($markAllReadButton);
await markAllReadDef;
// immediately jump to end of the fadeout animation on messages
discuss.$('.o_thread_message').stop(false, true);
assert.containsNone(discuss, '.o_thread_message',
"there should no message in inbox anymore");
$history = discuss.$('.o_mail_discuss_item[data-thread-id="mailbox_history"]');
await testUtils.dom.click($history);
assert.containsN(discuss, '.o_thread_message', 2,
"there should be two messages in History");
discuss.destroy();
});
QUnit.test('all messages in "Inbox" in "History" after marked all as read', async function (assert) {
assert.expect(10);
const messagesData = [];
for (let i = 0; i < 40; i++) {
messagesData.push({
author_id: [i, 'User ' + i],
body: '<p>test ' + i + '</p>',
id: i,
needaction: true,
needaction_partner_ids: [3],
});
}
this.data['mail.message'].records = messagesData;
this.data.initMessaging = {
needaction_inbox_counter: 2,
};
let messageFetchCount = 0;
const initDef = testUtils.makeTestPromise();
const markAllReadDef = testUtils.makeTestPromise();
const clickHistoryDef = testUtils.makeTestPromise();
const loadMoreDef = testUtils.makeTestPromise();
let objectDiscuss;
const discuss = await createDiscuss({
id: 1,
context: {},
params: {},
data: this.data,
services: this.services,
session: { partner_id: 3 },
mockRPC(route, args) {
if (args.method === 'mark_all_as_read') {
const messageIDs = [];
for (let i = 0; i < messagesData.length; i++) {
this.data['mail.message'].records[i].history_partner_ids = [3];
this.data['mail.message'].records[i].needaction_partner_ids = [];
this.data['mail.message'].records[i].needaction = false;
messageIDs.push(i);
}
const notificationData = {
type: 'mark_as_read',
message_ids: messageIDs,
};
const notification = [[false, 'res.partner', 3], notificationData];
objectDiscuss.call('bus_service', 'trigger', 'notification', [notification]);
markAllReadDef.resolve();
return Promise.resolve(3);
}
if (args.method === 'message_fetch') {
// 1st message_fetch: 'Inbox' initially
// 2nd message_fetch: 'History' initially
// 3rd message_fetch: 'History' load more
assert.step(args.method);
messageFetchCount++;
if (messageFetchCount === 1) {
initDef.resolve();
}
if (messageFetchCount === 2) {
clickHistoryDef.resolve();
}
if (messageFetchCount === 3) {
loadMoreDef.resolve();
}
}
return this._super.apply(this, arguments);
},
});
objectDiscuss = discuss;
await initDef;
await testUtils.nextTick();
assert.verifySteps(['message_fetch'],
"should fetch messages once for needaction messages (Inbox)");
assert.containsN(discuss, '.o_thread_message', 30,
"there should be 30 messages that are loaded in Inbox");
const $markAllReadButton = $('.o_mail_discuss_button_mark_all_read');
await testUtils.dom.click($markAllReadButton);
await markAllReadDef;
await testUtils.nextTick();
// immediately jump to end of the fadeout animation on messages
discuss.$('.o_thread_message').stop(false, true);
assert.containsNone(discuss, '.o_thread_message',
"there should no message in inbox anymore");
const $history = discuss.$('.o_mail_discuss_item[data-thread-id="mailbox_history"]');
await testUtils.dom.click($history);
await clickHistoryDef;
await testUtils.nextTick();
assert.verifySteps(['message_fetch'],
"should fetch messages once for history");
let hasEnoughMessages = false;
while (!hasEnoughMessages) {
if (discuss.$('.o_thread_message').length === 30) {
hasEnoughMessages = true;
} else {
await testUtils.nextTick();
}
}
assert.containsN(discuss, '.o_thread_message', 30,
"there should be 30 messages in History");
// simulate a scroll to top to load more messages
discuss.$('.o_mail_thread').scrollTop(0);
await loadMoreDef;
await testUtils.nextTick();
assert.verifySteps(['message_fetch'],
"should fetch more messages in history for loadMore");
assert.containsN(discuss, '.o_thread_message', 40,
"there should be 40 messages in History");
discuss.destroy();
});
QUnit.test('save filter discuss', async function (assert) {
assert.expect(3);
var messageFetchCount = 0;
const discuss = await createDiscuss({
context: {},
params: {},
data: this.data,
services: this.services,
archs: {
'mail.message,false,search': '<search>' +
'<field name="body" string="Content" filter_domain="[\'|\', (\'subject\', \'ilike\', self), (\'body\', \'ilike\', self)]"/>' +
'</search>',
},
session: {
partner_id: 3
},
mockRPC: async function (route, args) {
if (args.method === 'message_fetch' && messageFetchCount === 1) {
assert.deepEqual(args.args[0], [
["needaction", "=", true],
"|",
["subject", "ilike", "she was born in a hurricane"],
["body", "ilike", "she was born in a hurricane"],
], 'The fetch domain is correct');
}
return this._super.apply(this,arguments);
},
env: {
dataManager: {
create_filter: async function (filter) {
assert.deepEqual(
JSON.parse(filter.domain), [
"|",
["subject", "ilike", "she was born in a hurricane"],
["body", "ilike", "she was born in a hurricane"]
], 'The filter should have been saved with the right domain');
}
}
}
});
assert.containsOnce(discuss, '.o_searchview_input_container', 'search view input present');
await cpHelpers.editSearch(discuss, "she was born in a hurricane");
messageFetchCount = 1;
await cpHelpers.validateSearch(discuss);
await cpHelpers.toggleFavoriteMenu(discuss);
await cpHelpers.toggleSaveFavorite(discuss);
await cpHelpers.editFavoriteName(discuss, "War");
await cpHelpers.saveFavorite(discuss);
discuss.destroy();
});
QUnit.test('no crash on receiving needaction channel message notif with messaging not ready', async function (assert) {
assert.expect(1);
const message = {
author_id: [5, 'Demo User'],
body: '<p>test</p>',
channel_ids: [1],
id: 100,
model: 'mail.channel',
needaction: true,
needaction_partner_ids: [3],
res_id: 1,
};
const discuss = await createDiscuss({
context: {},
params: {},
data: this.data,
services: this.services,
session: {
partner_id: 3
},
async mockRPC(route, args) {
if (route === '/mail/init_messaging') {
// infinite messaging not ready
await new Promise(() => {});
}
return this._super(...arguments);
},
});
// simulate new needaction message posted on channnel
this.data['mail.message'].records.push(message);
// simulate receiving channel notification
discuss.call('bus_service', 'trigger', 'notification', [
[['myDB', 'mail.channel', 1], message]
]);
// short delay after receiving needaction notification
await testUtils.nextTick();
// simulate receiving needaction message notification after a short delay
discuss.call('bus_service', 'trigger', 'notification', [
[['myDB', 'ir.needaction', 3], message]
]);
await testUtils.nextTick();
assert.ok(true, "should not crash on receiving new needaction message when messaging is not ready");
discuss.destroy();
});
});
});