Revision on https://github.com/odoo/odoo/commit/9a66619db33fca5f0cb81d9f6bfeb0fb0290cf68 Commit above improves the web client startup by not having to wait for discuss widgets to fully load beforehand. However, it introduced an issue with chatter when the 1st rendering happens before messaging becomes ready: it displays "Creating a record..." instead of the whole history of messages. This happens because when messaging is ready, it updates data from the chatter but it does not trigger a re-render. This commit fixes the issue by triggering a re-render on messaging ready. When messaging is not ready, thread field now displays "Loading..." instead of the message "Creating a record...". Task-Id 2078193 closes odoo/odoo#38751 X-original-commit: 93bdd7c91095025f4d6e7a058e0ab1c4e1a0ad60 Signed-off-by: Alexandre Kühn (aku) <aku@odoo.com>
3477 lines
133 KiB
JavaScript
3477 lines
133 KiB
JavaScript
odoo.define('mail.chatter_tests', function (require) {
|
|
"use strict";
|
|
|
|
var mailTestUtils = require('mail.testUtils');
|
|
|
|
var core = require('web.core');
|
|
var FormView = require('web.FormView');
|
|
var KanbanView = require('web.KanbanView');
|
|
var ListView = require('web.ListView');
|
|
var testUtils = require('web.test_utils');
|
|
var concurrency = require('web.concurrency');
|
|
|
|
var createView = testUtils.createView;
|
|
|
|
var Activity = require('mail.Activity');
|
|
var _t = core._t;
|
|
|
|
QUnit.module('mail', {}, function () {
|
|
|
|
QUnit.module('Chatter', {
|
|
beforeEach: function () {
|
|
// patch _.debounce and _.throttle to be fast and synchronous
|
|
this.underscoreDebounce = _.debounce;
|
|
this.underscoreThrottle = _.throttle;
|
|
_.debounce = _.identity;
|
|
_.throttle = _.identity;
|
|
|
|
this.services = mailTestUtils.getMailServices();
|
|
this.data = {
|
|
'res.partner': {
|
|
fields: {
|
|
im_status: {
|
|
string: "im_status",
|
|
type: "char",
|
|
}
|
|
},
|
|
records: [{
|
|
id: 1,
|
|
im_status: 'online',
|
|
}]
|
|
},
|
|
partner: {
|
|
fields: {
|
|
display_name: { string: "Displayed name", type: "char" },
|
|
foo: {string: "Foo", type: "char", default: "My little Foo Value"},
|
|
message_follower_ids: {
|
|
string: "Followers",
|
|
type: "one2many",
|
|
relation: 'mail.followers',
|
|
relation_field: "res_id",
|
|
},
|
|
message_ids: {
|
|
string: "messages",
|
|
type: "one2many",
|
|
relation: 'mail.message',
|
|
relation_field: "res_id",
|
|
},
|
|
activity_ids: {
|
|
string: 'Activities',
|
|
type: 'one2many',
|
|
relation: 'mail.activity',
|
|
relation_field: 'res_id',
|
|
},
|
|
activity_exception_decoration: {
|
|
string: 'Decoration',
|
|
type: 'selection',
|
|
selection: [['warning', 'Alert'], ['danger', 'Error']],
|
|
},
|
|
activity_exception_icon: {
|
|
string: 'icon',
|
|
type: 'char',
|
|
},
|
|
activity_state: {
|
|
string: 'State',
|
|
type: 'selection',
|
|
selection: [['overdue', 'Overdue'], ['today', 'Today'], ['planned', 'Planned']],
|
|
},
|
|
message_attachment_count: {
|
|
string: 'Attachment count',
|
|
type: 'integer',
|
|
},
|
|
},
|
|
records: [{
|
|
id: 2,
|
|
message_attachment_count: 3,
|
|
display_name: "first partner",
|
|
foo: "HELLO",
|
|
message_follower_ids: [],
|
|
message_ids: [],
|
|
activity_ids: [],
|
|
}]
|
|
},
|
|
'mail.activity': {
|
|
fields: {
|
|
activity_type_id: { string: "Activity type", type: "many2one", relation: "mail.activity.type" },
|
|
create_uid: { string: "Created By", type: "many2one", relation: 'partner' },
|
|
can_write: { string: "Can write", type: "boolean" },
|
|
display_name: { string: "Display name", type: "char" },
|
|
date_deadline: { string: "Due Date", type: "date" },
|
|
user_id: { string: "Assigned to", type: "many2one", relation: 'partner' },
|
|
state: {
|
|
string: 'State',
|
|
type: 'selection',
|
|
selection: [['overdue', 'Overdue'], ['today', 'Today'], ['planned', 'Planned']],
|
|
},
|
|
activity_category: {
|
|
string: 'Category',
|
|
type: 'selection',
|
|
selection: [['default', 'Other'], ['upload_file', 'Upload File']],
|
|
},
|
|
note : { string: "Note", type: "char" },
|
|
},
|
|
},
|
|
'mail.activity.type': {
|
|
fields: {
|
|
name: { string: "Name", type: "char" },
|
|
category: {
|
|
string: 'Category',
|
|
type: 'selection',
|
|
selection: [['default', 'Other'], ['upload_file', 'Upload File']],
|
|
},
|
|
decoration_type: { string: "Decoration Type", type: "selection", selection: [['warning', 'Alert'], ['danger', 'Error']]},
|
|
icon: {string: 'icon', type:"char"},
|
|
},
|
|
records: [
|
|
{ id: 1, name: "Type 1" },
|
|
{ id: 2, name: "Type 2" },
|
|
{ id: 3, name: "Type 3", category: 'upload_file' },
|
|
{ id: 4, name: "Exception", decoration_type: "warning", icon: "fa-warning"}
|
|
],
|
|
},
|
|
'mail.message': {
|
|
fields: {
|
|
attachment_ids: {
|
|
string: "Attachments",
|
|
type: 'many2many',
|
|
relation: 'ir.attachment',
|
|
default: [],
|
|
},
|
|
author_id: {
|
|
string: "Author",
|
|
relation: 'res.partner',
|
|
},
|
|
body: {
|
|
string: "Contents",
|
|
type: 'html',
|
|
},
|
|
date: {
|
|
string: "Date",
|
|
type: 'datetime',
|
|
},
|
|
is_note: {
|
|
string: "Note",
|
|
type: 'boolean',
|
|
},
|
|
is_discussion: {
|
|
string: "Discussion",
|
|
type: 'boolean',
|
|
},
|
|
is_notification: {
|
|
string: "Notification",
|
|
type: 'boolean',
|
|
},
|
|
is_starred: {
|
|
string: "Starred",
|
|
type: 'boolean',
|
|
},
|
|
model: {
|
|
string: "Related Document Model",
|
|
type: 'char',
|
|
},
|
|
res_id: {
|
|
string: "Related Document ID",
|
|
type: 'integer',
|
|
}
|
|
},
|
|
records: [],
|
|
},
|
|
'ir.attachment': {
|
|
fields:{
|
|
name:{type:'char', string:"attachment name", required:true},
|
|
res_model:{type:'char', string:"res model"},
|
|
res_id:{type:'integer', string:"res id"},
|
|
url:{type:'char', string:'url'},
|
|
type:{ type:'selection', selection:[['url',"URL"],['binary',"BINARY"]]},
|
|
mimetype:{type:'char', string:"mimetype"},
|
|
},
|
|
records:[
|
|
{id:1, type:'url', mimetype:'image/png', name:'filename.jpg',
|
|
res_id: 7, res_model: 'partner'},
|
|
{id:2, type:'binary', mimetype:"application/x-msdos-program",
|
|
name:"file2.txt", res_id: 7, res_model: 'partner'},
|
|
{id:3, type:'binary', mimetype:"application/x-msdos-program",
|
|
name:"file3.txt", res_id: 5, res_model: 'partner'},
|
|
],
|
|
},
|
|
};
|
|
},
|
|
afterEach: function () {
|
|
// unpatch _.debounce and _.throttle
|
|
_.debounce = this.underscoreDebounce;
|
|
_.throttle = this.underscoreThrottle;
|
|
}
|
|
});
|
|
|
|
QUnit.test('messaging becomes ready', async function (assert) {
|
|
assert.expect(2);
|
|
|
|
const initMessagingProm = testUtils.makeTestPromise();
|
|
const form = await createView({
|
|
View: FormView,
|
|
model: 'partner',
|
|
data: this.data,
|
|
services: this.services,
|
|
arch: `<form string="Partners">
|
|
<sheet>
|
|
<field name="foo"/>
|
|
</sheet>
|
|
<div class="oe_chatter">
|
|
<field name="message_ids" widget="mail_thread"/>
|
|
</div>
|
|
</form>`,
|
|
async mockRPC(route, args) {
|
|
const _super = this._super.bind(this, route, args); // limitation on class.js with async/await
|
|
if (route === '/mail/init_messaging') {
|
|
await initMessagingProm;
|
|
}
|
|
return _super();
|
|
},
|
|
res_id: 2,
|
|
});
|
|
await testUtils.nextTick();
|
|
assert.containsOnce(
|
|
form,
|
|
'.o_mail_thread_loading',
|
|
"thread should be loading when messaging is not yet ready");
|
|
|
|
// simulate messaging becoming ready
|
|
initMessagingProm.resolve();
|
|
await testUtils.nextTick();
|
|
assert.containsNone(
|
|
form,
|
|
'.o_mail_thread_loading',
|
|
"thread should no longer be loading when messaging becomes ready");
|
|
|
|
form.destroy();
|
|
});
|
|
|
|
QUnit.test('basic rendering', async function (assert) {
|
|
assert.expect(9);
|
|
|
|
var count = 0;
|
|
var unwanted_read_count = 0;
|
|
var form = await createView({
|
|
View: FormView,
|
|
model: 'partner',
|
|
data: this.data,
|
|
services: this.services,
|
|
arch: '<form string="Partners">' +
|
|
'<sheet>' +
|
|
'<field name="foo"/>' +
|
|
'</sheet>' +
|
|
'<div class="oe_chatter">' +
|
|
'<field name="message_follower_ids" widget="mail_followers"/>' +
|
|
'<field name="message_ids" widget="mail_thread"/>' +
|
|
'<field name="activity_ids" widget="mail_activity"/>' +
|
|
'</div>' +
|
|
'</form>',
|
|
mockRPC: function (route, args) {
|
|
if (route === '/web/dataset/call_kw/mail.followers/read') {
|
|
unwanted_read_count++;
|
|
}
|
|
if (route === '/mail/read_followers') {
|
|
count++;
|
|
return Promise.resolve({
|
|
followers: [],
|
|
subtypes: [],
|
|
});
|
|
}
|
|
return this._super(route, args);
|
|
},
|
|
res_id: 2,
|
|
});
|
|
assert.containsOnce(form, '.o_mail_activity', "there should be an activity widget");
|
|
assert.containsOnce(form, '.o_chatter_topbar .o_chatter_button_schedule_activity',
|
|
"there should be a 'Schedule an activity' button in the chatter's topbar");
|
|
assert.containsOnce(form, '.o_chatter_topbar .o_followers',
|
|
"there should be a followers widget, moved inside the chatter's topbar");
|
|
assert.containsOnce(form, '.o_chatter', "there should be a chatter widget");
|
|
assert.containsOnce(form, '.o_mail_thread');
|
|
assert.containsOnce(form, '.o_chatter_button_attachment', "should have one attachment button");
|
|
assert.containsNone(form, '.o_chatter_topbar .o_chatter_button_log_note',
|
|
"log note button should not be available");
|
|
|
|
await testUtils.form.clickEdit(form);
|
|
assert.strictEqual(count, 0, "should have done no read_followers rpc as there are no followers");
|
|
assert.strictEqual(unwanted_read_count, 0, "followers should only be fetched with read_followers route");
|
|
form.destroy();
|
|
});
|
|
|
|
QUnit.test('basic rendering: message_attachment_count can be in view standalone', async function (assert) {
|
|
assert.expect(1);
|
|
|
|
var form = await createView({
|
|
View: FormView,
|
|
model: 'partner',
|
|
data: this.data,
|
|
services: this.services,
|
|
arch: '<form string="Partners">' +
|
|
'<sheet>' +
|
|
'<group>' +
|
|
'<field name="message_attachment_count" string="I\'m here"/>' +
|
|
'</group>' +
|
|
'</sheet>' +
|
|
'</form>',
|
|
res_id: 2,
|
|
});
|
|
|
|
assert.strictEqual(form.$('.o_form_label').text(), "I'm here",
|
|
"The field message_attachment_count must be present according to the view's specs");
|
|
|
|
form.destroy();
|
|
});
|
|
|
|
QUnit.test('basic rendering: message_attachment_count can be in view with chatter', async function (assert) {
|
|
assert.expect(1);
|
|
|
|
var form = await createView({
|
|
View: FormView,
|
|
model: 'partner',
|
|
data: this.data,
|
|
services: this.services,
|
|
arch: '<form string="Partners">' +
|
|
'<sheet>' +
|
|
'<group>' +
|
|
'<field name="message_attachment_count" string="I\'m here"/>' +
|
|
'</group>' +
|
|
'</sheet>' +
|
|
'<div class="oe_chatter">' +
|
|
'<field name="message_follower_ids" widget="mail_followers"/>' +
|
|
'<field name="message_ids" widget="mail_thread"/>' +
|
|
'<field name="activity_ids" widget="mail_activity"/>' +
|
|
'</div>' +
|
|
'</form>',
|
|
res_id: 2,
|
|
});
|
|
|
|
assert.strictEqual(form.$('.o_form_label').text(), "I'm here",
|
|
"The field message_attachment_count must be present according to the view's specs");
|
|
|
|
form.destroy();
|
|
});
|
|
|
|
QUnit.test('Activity Done keep feedback on blur', async function (assert) {
|
|
assert.expect(3);
|
|
var done = assert.async();
|
|
|
|
this.data['mail.activity'].records = [
|
|
{activity_type_id: 1, id: 1, can_write: true, user_id: 2, state: 'today', note: 'But I\'m talkin\' about Shaft'},
|
|
];
|
|
this.data.partner.records[0].activity_ids = [1];
|
|
|
|
var shownDef = testUtils.makeTestPromise();
|
|
var hiddenDef = testUtils.makeTestPromise();
|
|
testUtils.mock.patch(Activity, {
|
|
_bindPopoverFocusout: function () {
|
|
this._super.apply(this, arguments);
|
|
shownDef.resolve();
|
|
},
|
|
});
|
|
|
|
var form = await createView({
|
|
View: FormView,
|
|
model: 'partner',
|
|
data: this.data,
|
|
services: this.services,
|
|
res_id: 2,
|
|
arch:'<form string="Partners">' +
|
|
'<div class="oe_chatter">' +
|
|
'<field name="activity_ids" widget="mail_activity"/>' +
|
|
'</div>' +
|
|
'</form>',
|
|
});
|
|
// sanity checks
|
|
var $activityEl = form.$('.o_mail_activity[name=activity_ids]');
|
|
assert.strictEqual($activityEl.find('.o_thread_message').length, 1,
|
|
'There should be one activity');
|
|
assert.strictEqual($activityEl.find('.o_thread_message .o_thread_message_note').text().trim(),
|
|
'But I\'m talkin\' about Shaft', 'The activity should have the right note');
|
|
|
|
var $popoverEl = $activityEl.find('.o_thread_message_tools .o_mark_as_done');
|
|
$popoverEl.on('hidden.bs.popover', hiddenDef.resolve.bind(hiddenDef));
|
|
|
|
// open popover
|
|
await testUtils.dom.click($popoverEl);
|
|
|
|
shownDef.then(function () {
|
|
// write a feedback and focusout
|
|
var $feedbackPopover = $($popoverEl.data('bs.popover').tip);
|
|
$feedbackPopover.find('#activity_feedback').val('John Shaft').focusout();
|
|
|
|
hiddenDef.then(async function () {
|
|
shownDef = testUtils.makeTestPromise();
|
|
|
|
// re-open popover
|
|
await testUtils.dom.click($popoverEl);
|
|
|
|
shownDef.then(function () {
|
|
var $feedbackPopover = $($popoverEl.data('bs.popover').tip);
|
|
assert.strictEqual($feedbackPopover.find('#activity_feedback').val(), 'John Shaft',
|
|
"feedback should have been kept");
|
|
|
|
form.destroy();
|
|
testUtils.mock.unpatch(Activity);
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
QUnit.test('Activity Done by uploading a file', async function (assert) {
|
|
assert.expect(4);
|
|
|
|
// simulate (shortcut) the upload and trigger the event when the
|
|
// attachment is created serverside.
|
|
testUtils.mock.patch(Activity, {
|
|
_onMarkActivityDoneUploadFile: function (ev) {
|
|
var $markDoneBtn = $(ev.currentTarget);
|
|
var fileuploadID = $markDoneBtn.data('fileupload-id');
|
|
|
|
$(window).trigger(fileuploadID, [{
|
|
id:3,
|
|
type:'binary',
|
|
mimetype:"application/x-msdos-program",
|
|
name:"file2.txt",
|
|
res_id: 5,
|
|
res_model: 'partner'
|
|
}]);
|
|
},
|
|
});
|
|
|
|
// create the activity
|
|
this.data['mail.activity'].records = [{
|
|
activity_type_id: 3,
|
|
id: 1,
|
|
can_write: true,
|
|
user_id: 2,
|
|
state: 'today',
|
|
note: "But I'm talkin' about Shaft",
|
|
activity_category: 'upload_file'
|
|
}];
|
|
this.data.partner.records[0].activity_ids = [1];
|
|
|
|
var form = await createView({
|
|
View: FormView,
|
|
model: 'partner',
|
|
data: this.data,
|
|
services: this.services,
|
|
res_id: 2,
|
|
arch:'<form string="Partners">' +
|
|
'<div class="oe_chatter">' +
|
|
'<field name="activity_ids" widget="mail_activity"/>' +
|
|
'</div>' +
|
|
'</form>',
|
|
mockRPC: function (route, args) {
|
|
if (args.method === 'action_feedback') {
|
|
var current_ids = this.data.partner.records[0].activity_ids;
|
|
var done_ids = args.args[0];
|
|
this.data.partner.records[0].activity_ids = _.difference(current_ids, done_ids);
|
|
this.data.partner.records[0].activity_state = false;
|
|
return Promise.resolve();
|
|
}
|
|
return this._super.apply(this, arguments);
|
|
},
|
|
});
|
|
|
|
var $activity = form.$('.o_mail_activity[name=activity_ids]');
|
|
assert.containsOnce($activity, '.o_thread_message', 'There should be one activity');
|
|
assert.containsOnce($activity, '.o_hidden_input_file', 'There should be one hidden file upload form');
|
|
assert.strictEqual($activity.find('.o_thread_message .o_thread_message_note').text().trim(),
|
|
'But I\'m talkin\' about Shaft', 'The activity should have the right note');
|
|
|
|
// click on "upload" button
|
|
var $uploadBtn = $activity.find('.o_mark_as_done_upload_file');
|
|
await testUtils.dom.click($uploadBtn);
|
|
|
|
assert.containsNone($activity, '.o_thread_message', 'The only activity should be marked as done');
|
|
|
|
testUtils.mock.unpatch(Activity);
|
|
form.destroy();
|
|
});
|
|
|
|
QUnit.test('attachmentBox basic rendering', async function (assert) {
|
|
assert.expect(19);
|
|
this.data.partner.records.push({
|
|
id: 7,
|
|
display_name: "attachment_test",
|
|
});
|
|
|
|
var form = await createView({
|
|
View: FormView,
|
|
model: 'partner',
|
|
data: this.data,
|
|
services: this.services,
|
|
arch: '<form string="Partners">' +
|
|
'<sheet>' +
|
|
'<field name="foo"/>' +
|
|
'</sheet>' +
|
|
'<div class="oe_chatter">' +
|
|
'<field name="message_ids" widget="mail_thread"/>' +
|
|
'</div>' +
|
|
'</form>',
|
|
res_id: 7,
|
|
mockRPC: function (route, args) {
|
|
var result = this._super.apply(this, arguments);
|
|
if (args.method === 'read' && args.model === 'partner') {
|
|
return result.then(function (records) {
|
|
// here we force the attachment_count to 1 (which is correct
|
|
// actually), so that the attachment button is visible
|
|
// FIXME: this could be handled by an extension of mockRead
|
|
records[0].message_attachment_count = 1;
|
|
return records;
|
|
});
|
|
}
|
|
return result;
|
|
},
|
|
});
|
|
var $button = form.$('.o_chatter_button_attachment');
|
|
assert.strictEqual($button.length, 1, "should have one attachment button");
|
|
await testUtils.dom.click($button);
|
|
assert.containsOnce(form, '.o_mail_chatter_attachments',
|
|
"attachment widget should exist after a first click on the button");
|
|
assert.containsOnce(form, '.o_attachment_image', "there should be an image preview");
|
|
assert.containsOnce(form, '.o_attachments_previews', "there should be a list of previews");
|
|
assert.containsOnce(form, '.o_attachments_list', "there should be a list of non previewable attachments");
|
|
assert.containsOnce(form, '.o_upload_attachments_button', "there should be an 'Add Attachments' button");
|
|
assert.containsOnce(form, '.o_form_binary_form', "there should be a binary form");
|
|
|
|
assert.containsOnce(form, 'input[name="model"]', "there should be an model input");
|
|
var $modelInput = form.$('input[name="model"]');
|
|
assert.hasAttrValue($modelInput, 'value', 'partner');
|
|
assert.hasAttrValue($modelInput, 'type', 'hidden');
|
|
|
|
assert.containsOnce(form, 'input[name="model"]', "there should be an id input");
|
|
var $resIdInput = form.$('input[name="id"]');
|
|
assert.hasAttrValue($resIdInput, 'value', '7');
|
|
assert.hasAttrValue($resIdInput, 'type', 'hidden');
|
|
|
|
assert.strictEqual(form.$('.o_attachment_title').text(), 'filename.jpg',
|
|
"the image name should be correct");
|
|
// since there are two elements "Download name2"; one "name" and the other "txt" as text content, the following test
|
|
// asserts both at the same time.
|
|
assert.strictEqual(form.$('a[title = "Download file2.txt"]').text().trim(), 'file2.txttxt',
|
|
"the attachment name and the extension display should be correct");
|
|
assert.ok(form.$('.o_attachment_image').css('background-image').indexOf('/web/image/1/160x160/?crop=true') >= 0,
|
|
"the attachment image URL should be correct");
|
|
assert.hasAttrValue(form.$('.o_attachment_download').eq(0), 'href', '/web/content/1?download=true',
|
|
"the download URL of name1 must be correct");
|
|
assert.hasAttrValue(form.$('.o_attachment_download').eq(1), 'href', '/web/content/2?download=true',
|
|
"the download URL of name2 must be correct");
|
|
await testUtils.dom.click($button);
|
|
assert.containsNone(form, '.o_mail_chatter_attachments')
|
|
form.destroy();
|
|
});
|
|
|
|
QUnit.test('attachmentBox: deletable attachments', async function (assert) {
|
|
assert.expect(1);
|
|
this.data.partner.records.push({
|
|
id: 7,
|
|
display_name: "attachment_test",
|
|
});
|
|
|
|
const form = await createView({
|
|
View: FormView,
|
|
model: 'partner',
|
|
data: this.data,
|
|
services: this.services,
|
|
arch: `
|
|
<form string="Partners">
|
|
<sheet>
|
|
'<field name="foo"/>
|
|
</sheet>
|
|
<div class="oe_chatter">
|
|
'<field name="message_ids" widget="mail_thread"/>
|
|
</div>
|
|
</form>`,
|
|
res_id: 7,
|
|
});
|
|
await testUtils.dom.click(form.$('.o_chatter_button_attachment'));
|
|
assert.containsN(
|
|
form,
|
|
'.o_attachment_delete_cross',
|
|
2,
|
|
"Attachments should be deletable inside attachment box");
|
|
|
|
form.destroy();
|
|
});
|
|
|
|
QUnit.test('chatter in create mode', async function (assert) {
|
|
assert.expect(9);
|
|
|
|
var form = await createView({
|
|
View: FormView,
|
|
model: 'partner',
|
|
data: this.data,
|
|
services: this.services,
|
|
arch: '<form string="Partners">' +
|
|
'<sheet>' +
|
|
'<field name="foo"/>' +
|
|
'</sheet>' +
|
|
'<div class="oe_chatter">' +
|
|
'<field name="message_ids" widget="mail_thread"/>' +
|
|
'</div>' +
|
|
'</form>',
|
|
res_id: 2,
|
|
mockRPC: function (route, args) {
|
|
if (route === "/mail/get_suggested_recipients") {
|
|
return Promise.resolve({2: []});
|
|
}
|
|
return this._super(route, args);
|
|
},
|
|
});
|
|
|
|
assert.containsOnce(form, '.o_chatter',
|
|
"chatter should be displayed");
|
|
|
|
// entering create mode
|
|
await testUtils.form.clickCreate(form);
|
|
assert.hasClass(form.$el.find('.o_form_view'),'o_form_editable',
|
|
"we should be in create mode");
|
|
assert.containsOnce(form, '.o_chatter',
|
|
"chatter should still be displayed in create mode");
|
|
|
|
// topbar buttons disabled in create mode (e.g. 'send message')
|
|
assert.strictEqual(form.$('.o_chatter_topbar button:not(:disabled)').length, 0,
|
|
"button should be disabled in create mode");
|
|
|
|
// chatter containing a single message with 'Creating a record...'
|
|
assert.containsOnce(form, '.o_mail_thread',
|
|
"there should be a mail thread");
|
|
assert.containsOnce(form, '.o_thread_message',
|
|
"there should be a single thread message");
|
|
assert.strictEqual(form.$('.o_thread_message_content').text().trim(),
|
|
"Creating a new record...",
|
|
"the content of the message should be 'Creating a new record...'");
|
|
|
|
// getting out of create mode by saving
|
|
await testUtils.fields.editInput(form.$('.o_field_char'), 'coucou');
|
|
await testUtils.form.clickSave(form);
|
|
|
|
assert.containsOnce(form, '.o_chatter',
|
|
"chatter should still be displayed after saving from create mode");
|
|
|
|
// check if chatter buttons still work
|
|
await testUtils.dom.click(form.$('.o_chatter_button_new_message'));
|
|
assert.containsOnce(form, '.o_thread_composer:visible',
|
|
"chatter should be opened");
|
|
|
|
form.destroy();
|
|
});
|
|
|
|
QUnit.test('chatter rendering inside the sheet', async function (assert) {
|
|
assert.expect(5);
|
|
|
|
var form = await createView({
|
|
View: FormView,
|
|
model: 'partner',
|
|
data: this.data,
|
|
services: this.services,
|
|
arch: '<form string="Partners">' +
|
|
'<sheet>' +
|
|
'<field name="foo"/>' +
|
|
'<notebook>' +
|
|
'<page>' +
|
|
'<div class="oe_chatter">' +
|
|
'<field name="message_ids" widget="mail_thread"/>' +
|
|
'</div>' +
|
|
'</page>' +
|
|
'</notebook>' +
|
|
'</sheet>' +
|
|
'</form>',
|
|
res_id: 2,
|
|
mockRPC: function (route, args) {
|
|
if (route === "/mail/get_suggested_recipients") {
|
|
return Promise.resolve({2: []});
|
|
}
|
|
return this._super(route, args);
|
|
},
|
|
});
|
|
|
|
assert.containsOnce(form, '.o_chatter',
|
|
"chatter should be displayed");
|
|
|
|
await testUtils.form.clickCreate(form);
|
|
assert.hasClass(form.$el.find('.o_form_view'),'o_form_editable',
|
|
"we should be in create mode");
|
|
|
|
assert.containsOnce(form, '.o_chatter',
|
|
"chatter should be displayed");
|
|
|
|
await testUtils.fields.editInput(form.$('.o_field_char'), 'coucou');
|
|
await testUtils.form.clickSave(form);
|
|
|
|
assert.containsOnce(form, '.o_chatter',
|
|
"chatter should be displayed");
|
|
|
|
// check if chatter buttons still work
|
|
await testUtils.dom.click(form.$('.o_chatter_button_new_message'));
|
|
assert.containsOnce(form, '.o_thread_composer:visible',
|
|
"chatter should be opened");
|
|
|
|
form.destroy();
|
|
});
|
|
|
|
QUnit.test('kanban activity widget with no activity', async function (assert) {
|
|
assert.expect(4);
|
|
|
|
var rpcCount = 0;
|
|
var kanban = await createView({
|
|
View: KanbanView,
|
|
model: 'partner',
|
|
data: this.data,
|
|
arch: '<kanban>' +
|
|
'<field name="activity_state"/>' +
|
|
'<templates><t t-name="kanban-box">' +
|
|
'<div><field name="activity_ids" widget="kanban_activity"/></div>' +
|
|
'</t></templates>' +
|
|
'</kanban>',
|
|
mockRPC: function (route, args) {
|
|
rpcCount++;
|
|
return this._super(route, args);
|
|
},
|
|
session: {uid: 2},
|
|
});
|
|
|
|
var $record = kanban.$('.o_kanban_record').first();
|
|
assert.ok($record.find('.o_mail_activity .o_activity_color_default').length,
|
|
"activity widget should have been rendered correctly");
|
|
assert.strictEqual(rpcCount, 1, '1 RPC (search_read) should have been done');
|
|
|
|
// click on the activity button
|
|
await testUtils.dom.click($record.find('.o_activity_btn'));
|
|
assert.strictEqual(rpcCount, 1, 'no RPC should have been done as there is no activity');
|
|
assert.strictEqual($record.find('.o_no_activity').length, 1, "should have no activity scheduled");
|
|
|
|
// fixme: it would be nice to be able to test the scheduling of a new activity, but not
|
|
// possible for now as we can't mock a fields_view_get (required by the do_action)
|
|
kanban.destroy();
|
|
});
|
|
|
|
QUnit.test('kanban activity widget with an activity', async function (assert) {
|
|
assert.expect(14);
|
|
|
|
this.data.partner.records[0].activity_ids = [1, 2];
|
|
this.data.partner.records[0].activity_state = 'today';
|
|
this.data.partner.records[0].activity_exception_decoration = 'warning';
|
|
this.data.partner.records[0].activity_exception_icon = 'fa-warning';
|
|
this.data['mail.activity'].records = [{
|
|
id: 1,
|
|
display_name: "An activity",
|
|
date_deadline: moment().format("YYYY-MM-DD"), // now
|
|
can_write: true,
|
|
state: "today",
|
|
user_id: 2,
|
|
create_uid: 2,
|
|
activity_type_id: 1,
|
|
},{
|
|
id: 2,
|
|
display_name: "An exception activity",
|
|
date_deadline: moment().format("YYYY-MM-DD"), // now
|
|
can_write: true,
|
|
state: "today",
|
|
user_id: 2,
|
|
create_uid: 2,
|
|
activity_type_id: 4,
|
|
}];
|
|
var rpcCount = 0;
|
|
var kanban = await createView({
|
|
View: KanbanView,
|
|
model: 'partner',
|
|
data: this.data,
|
|
arch: '<kanban>' +
|
|
'<field name="activity_state"/>' +
|
|
'<templates><t t-name="kanban-box">' +
|
|
'<div><field name="activity_ids" widget="kanban_activity"/></div>' +
|
|
'</t></templates>' +
|
|
'</kanban>',
|
|
mockRPC: function (route, args) {
|
|
rpcCount++;
|
|
if (route === '/web/dataset/call_kw/mail.activity/action_feedback') {
|
|
var current_ids = this.data.partner.records[0].activity_ids;
|
|
var done_ids = args.args[0];
|
|
this.data.partner.records[0].activity_ids = _.difference(current_ids, done_ids);
|
|
this.data.partner.records[0].activity_exception_decoration = false;
|
|
this.data.partner.records[0].activity_state = false;
|
|
return Promise.resolve();
|
|
}
|
|
return this._super(route, args);
|
|
},
|
|
session: {uid:2},
|
|
});
|
|
|
|
var $record = kanban.$('.o_kanban_record').first();
|
|
assert.ok($record.find('.o_mail_activity .o_activity_color_today').length,
|
|
"activity widget should have been rendered correctly");
|
|
assert.strictEqual(rpcCount, 1, '1 RPC (search_read) should have been done');
|
|
|
|
// Check exception activity icon fa-warning
|
|
assert.hasClass($record.find('.o_activity_btn span'), "fa-warning", "should display correct exception activity icon");
|
|
// click on the activity button
|
|
await testUtils.dom.click($record.find('.o_activity_btn'));
|
|
assert.strictEqual(rpcCount, 2, 'a read should have been done to fetch the activity details');
|
|
assert.strictEqual($record.find('.o_activity_title').length, 2, "should have two activity scheduled");
|
|
var label = $record.find('.o_activity_log .o_activity_color_today');
|
|
assert.strictEqual(label.find('strong').text(), "Today", "should display the correct label");
|
|
assert.strictEqual(label.find('.badge-warning').text(), "2", "should display the correct count");
|
|
|
|
// click on the activity button to close the dropdown
|
|
await testUtils.dom.click($record.find('.o_activity_btn'));
|
|
assert.strictEqual(rpcCount, 2, 'no RPC should be done when closing the dropdown');
|
|
|
|
// click on the activity button to re-open dropdown
|
|
await testUtils.dom.click($record.find('.o_activity_btn'));
|
|
assert.strictEqual(rpcCount, 3, 'should have reloaded the activities');
|
|
|
|
// mark exception activity as done
|
|
await testUtils.dom.click($record.find('.o_mark_as_done:eq(1)'));
|
|
await testUtils.dom.click($record.find('.o_activity_popover_done'));
|
|
$record = kanban.$('.o_kanban_record').first(); // the record widget has been reset
|
|
|
|
// check activity icon replaced with fa-clock
|
|
assert.hasClass($record.find('.o_activity_btn span'), "fa-clock-o", "should display correct activity icon");
|
|
|
|
// mark activity as done
|
|
await testUtils.dom.click($record.find('.o_mark_as_done'));
|
|
await testUtils.dom.click($record.find('.o_activity_popover_done'));
|
|
$record = kanban.$('.o_kanban_record').first(); // the record widget has been reset
|
|
assert.strictEqual(rpcCount, 8, 'should have done an RPC to mark activity as done, and a read');
|
|
assert.ok($record.find('.o_mail_activity .o_activity_color_default:not(.o_activity_color_today)').length,
|
|
"activity widget should have been updated correctly");
|
|
assert.strictEqual($record.find('.o_mail_activity.show').length, 1,
|
|
"dropdown should remain open when marking an activity as done");
|
|
assert.strictEqual($record.find('.o_no_activity').length, 1, "should have no activity scheduled");
|
|
|
|
kanban.destroy();
|
|
});
|
|
|
|
QUnit.test('kanban activity widget popover test', async function (assert) {
|
|
assert.expect(3);
|
|
|
|
this.data.partner.records[0].activity_ids = [1];
|
|
this.data.partner.records[0].activity_state = 'today';
|
|
this.data['mail.activity'].records = [{
|
|
id: 1,
|
|
display_name: "An activity",
|
|
date_deadline: moment().format("YYYY-MM-DD"), // now
|
|
can_write: true,
|
|
state: "today",
|
|
user_id: 2,
|
|
create_uid: 2,
|
|
activity_type_id: 1,
|
|
}];
|
|
var rpcCount = 0;
|
|
var kanban = await createView({
|
|
View: KanbanView,
|
|
model: 'partner',
|
|
data: this.data,
|
|
arch: '<kanban>' +
|
|
'<field name="activity_state"/>' +
|
|
'<templates><t t-name="kanban-box">' +
|
|
'<div><field name="activity_ids" widget="kanban_activity"/></div>' +
|
|
'</t></templates>' +
|
|
'</kanban>',
|
|
mockRPC: function (route, args) {
|
|
if (route === '/web/dataset/call_kw/mail.activity/action_feedback_schedule_next') {
|
|
rpcCount++;
|
|
|
|
var current_ids = this.data.partner.records[0].activity_ids;
|
|
var done_ids = args.args[0];
|
|
this.data.partner.records[0].activity_ids = _.difference(current_ids, done_ids);
|
|
this.data.partner.records[0].activity_state = false;
|
|
return Promise.resolve();
|
|
}
|
|
return this._super(route, args);
|
|
},
|
|
});
|
|
|
|
var $record = kanban.$('.o_kanban_record').first();
|
|
|
|
await testUtils.dom.click($record.find('.o_activity_btn'));
|
|
|
|
// Click on button and see popover no RPC call
|
|
await testUtils.dom.click($record.find('.o_mark_as_done'));
|
|
assert.equal(rpcCount, 0, "");
|
|
// Click on discard no RPC call
|
|
await testUtils.dom.click($record.find('.o_activity_popover_discard'));
|
|
assert.equal(rpcCount, 0, "");
|
|
// Click on button and then on done and schedule next
|
|
// RPC call
|
|
await testUtils.dom.click($record.find('.o_activity_popover_done_next'));
|
|
assert.equal(rpcCount, 1, "");
|
|
|
|
kanban.destroy();
|
|
});
|
|
|
|
QUnit.test('list activity exception widget with activity', async function (assert) {
|
|
assert.expect(3);
|
|
|
|
this.data.partner.records[0].activity_ids = [1];
|
|
this.data.partner.records.push({
|
|
id: 3,
|
|
message_attachment_count: 3,
|
|
display_name: "second partner",
|
|
foo: "Tommy",
|
|
message_follower_ids: [],
|
|
message_ids: [],
|
|
activity_ids: [2],
|
|
activity_exception_decoration: 'warning',
|
|
activity_exception_icon: 'fa-warning',
|
|
});
|
|
this.data['mail.activity'].records = [{
|
|
id: 1,
|
|
display_name: "An activity",
|
|
date_deadline: moment().format("YYYY-MM-DD"), // now
|
|
can_write: true,
|
|
state: "today",
|
|
user_id: 2,
|
|
create_uid: 2,
|
|
activity_type_id: 1,
|
|
},{
|
|
id: 2,
|
|
display_name: "An exception activity",
|
|
date_deadline: moment().format("YYYY-MM-DD"), // now
|
|
can_write: true,
|
|
state: "today",
|
|
user_id: 2,
|
|
create_uid: 2,
|
|
activity_type_id: 4,
|
|
}];
|
|
|
|
var list = await createView({
|
|
View: ListView,
|
|
model: 'partner',
|
|
data: this.data,
|
|
arch: '<tree>' +
|
|
'<field name="foo"/>'+
|
|
'<field name="activity_exception_decoration" widget="activity_exception"/> ' +
|
|
'</tree>',
|
|
});
|
|
|
|
assert.containsN(list, '.o_data_row', 2, "should have two records");
|
|
assert.doesNotHaveClass(list.$('.o_data_row:eq(0) .o_activity_exception_cell div'), 'fa-warning',
|
|
"there is no any exception activity on record");
|
|
assert.hasClass(list.$('.o_data_row:eq(1) .o_activity_exception_cell div'), 'fa-warning',
|
|
"there is an exception on a record");
|
|
|
|
list.destroy();
|
|
});
|
|
|
|
QUnit.test('chatter: post, receive and star messages', async function (assert) {
|
|
assert.expect(26);
|
|
|
|
this.data.partner.records[0].message_ids = [1];
|
|
this.data['mail.message'].records = [{
|
|
author_id: ["1", "John Doe"],
|
|
body: "A message",
|
|
date: "2016-12-20 09:35:40",
|
|
id: 1,
|
|
is_note: false,
|
|
is_discussion: true,
|
|
is_notification: false,
|
|
is_starred: false,
|
|
model: 'partner',
|
|
res_id: 2,
|
|
}];
|
|
|
|
var getSuggestionsDef = testUtils.makeTestPromise();
|
|
var form = await createView({
|
|
View: FormView,
|
|
model: 'partner',
|
|
data: this.data,
|
|
services: this.services,
|
|
arch: '<form string="Partners">' +
|
|
'<sheet>' +
|
|
'<field name="foo"/>' +
|
|
'</sheet>' +
|
|
'<div class="oe_chatter">' +
|
|
'<field name="message_ids" widget="mail_thread" options="{\'display_log_button\': True}"/>' +
|
|
'</div>' +
|
|
'</form>',
|
|
res_id: 2,
|
|
mockRPC: function (route, args) {
|
|
if (route === '/mail/get_suggested_recipients') {
|
|
return Promise.resolve({2: []});
|
|
}
|
|
if (args.method === 'get_mention_suggestions') {
|
|
getSuggestionsDef.resolve();
|
|
return Promise.resolve([[{email: "test@odoo.com", id: 1, name: "Test User"}], []]);
|
|
}
|
|
if (args.method === 'message_post') {
|
|
var lastMessageData = _.max(this.data['mail.message'].records, function (messageData) {
|
|
return messageData.id;
|
|
});
|
|
var messageID = lastMessageData.id + 1;
|
|
this.data['mail.message'].records.push({
|
|
attachment_ids: args.kwargs.attachment_ids,
|
|
author_id: ["42", "Me"],
|
|
body: args.kwargs.body,
|
|
date: "2016-12-20 10:35:40",
|
|
id: messageID,
|
|
is_note: args.kwargs.subtype === 'mail.mt_note',
|
|
is_discussion: args.kwargs.subtype === 'mail.mt_comment',
|
|
is_notification: false,
|
|
is_starred: false,
|
|
model: 'partner',
|
|
res_id: 2,
|
|
});
|
|
return Promise.resolve(messageID);
|
|
}
|
|
if (args.method === 'toggle_message_starred') {
|
|
assert.ok(_.contains(args.args[0], 2),
|
|
"toggle_star_status should have been triggered for message 2 (twice)");
|
|
var messageData = _.findWhere(
|
|
this.data['mail.message'].records,
|
|
{ id: args.args[0][0] }
|
|
);
|
|
messageData.is_starred = !messageData.is_starred;
|
|
// simulate notification received by mail_service from longpoll
|
|
var data = {
|
|
info: false,
|
|
message_ids: [messageData.id],
|
|
starred: messageData.is_starred,
|
|
type: 'toggle_star',
|
|
};
|
|
var notification = [[false, 'res.partner'], data];
|
|
form.call('bus_service', 'trigger', 'notification', [notification]);
|
|
return Promise.resolve();
|
|
}
|
|
return this._super(route, args);
|
|
},
|
|
session: {},
|
|
});
|
|
|
|
assert.ok(form.$('.o_chatter_topbar .o_chatter_button_log_note').length,
|
|
"log note button should be available");
|
|
assert.containsOnce(form, '.o_thread_message', "thread should contain one message");
|
|
assert.ok(form.$('.o_thread_message:first().o_mail_discussion').length,
|
|
"the message should be a discussion");
|
|
assert.ok(form.$('.o_thread_message:first() .o_thread_message_core').text().indexOf('A message') >= 0,
|
|
"the message's body should be correct");
|
|
assert.ok(form.$('.o_thread_message:first() .o_mail_info').text().indexOf('John Doe') >= 0,
|
|
"the message's author should be correct");
|
|
|
|
// send a message
|
|
await testUtils.dom.click(form.$('.o_chatter_button_new_message'));
|
|
assert.isVisible($('.oe_chatter .o_thread_composer'), "chatter should be opened");
|
|
form.$('.oe_chatter .o_composer_text_field:first()').val("My first message");
|
|
await testUtils.dom.click(form.$('.oe_chatter .o_composer_button_send'));
|
|
assert.isNotVisible($('.oe_chatter .o_thread_composer'), "chatter should be closed");
|
|
assert.containsN(form, '.o_thread_message', 2, "thread should contain two messages");
|
|
assert.ok(form.$('.o_thread_message:first().o_mail_discussion').length,
|
|
"the last message should be a discussion");
|
|
assert.ok(form.$('.o_thread_message:first() .o_thread_message_core').text().indexOf('My first message') >= 0,
|
|
"the message's body should be correct");
|
|
assert.ok(form.$('.o_thread_message:first() .o_mail_info').text().indexOf('Me') >= 0,
|
|
"the message's author should be correct");
|
|
|
|
// log a note
|
|
await testUtils.dom.click(form.$('.o_chatter_button_log_note'));
|
|
assert.isVisible($('.oe_chatter .o_thread_composer'), "chatter should be opened");
|
|
form.$('.oe_chatter .o_composer_text_field:first()').val("My first note");
|
|
await testUtils.dom.click(form.$('.oe_chatter .o_composer_button_send'));
|
|
assert.isNotVisible($('.oe_chatter .o_thread_composer'), "chatter should be closed");
|
|
assert.containsN(form, '.o_thread_message', 3, "thread should contain three messages");
|
|
assert.ok(!form.$('.o_thread_message:first().o_mail_discussion').length,
|
|
"the last message should not be a discussion");
|
|
assert.ok(form.$('.o_thread_message:first() .o_thread_message_core').text().indexOf('My first note') >= 0,
|
|
"the message's body should be correct");
|
|
assert.ok(form.$('.o_thread_message:first() .o_mail_info').text().indexOf('Me') >= 0,
|
|
"the message's author should be correct");
|
|
|
|
// star message 2
|
|
assert.ok(form.$('.o_thread_message[data-message-id=2] .o_thread_message_star.fa-star-o').length,
|
|
"message 2 should not be starred");
|
|
await testUtils.dom.click(form.$('.o_thread_message[data-message-id=2] .o_thread_message_star'));
|
|
assert.ok(form.$('.o_thread_message[data-message-id=2] .o_thread_message_star.fa-star').length,
|
|
"message 2 should be starred");
|
|
|
|
// unstar message 2
|
|
await testUtils.dom.click(form.$('.o_thread_message[data-message-id=2] .o_thread_message_star'));
|
|
assert.ok(form.$('.o_thread_message[data-message-id=2] .o_thread_message_star.fa-star-o').length,
|
|
"message 2 should not be starred");
|
|
|
|
// very basic test of mention
|
|
await testUtils.dom.click(form.$('.o_chatter_button_new_message'));
|
|
var $input = form.$('.oe_chatter .o_composer_text_field:first()');
|
|
$input.val('@');
|
|
// the cursor position must be set for the mention manager to detect that we are mentionning
|
|
$input[0].selectionStart = 1;
|
|
$input[0].selectionEnd = 1;
|
|
$input.trigger('keyup');
|
|
|
|
await testUtils.nextTick();
|
|
await getSuggestionsDef;
|
|
await testUtils.nextTick();
|
|
assert.containsOnce(form, '.o_mention_proposition:visible',
|
|
"there should be one mention suggestion");
|
|
assert.strictEqual(form.$('.o_mention_proposition').data('id'), 1,
|
|
"suggestion's id should be correct");
|
|
assert.strictEqual(form.$('.o_mention_proposition .o_mention_name').text(), 'Test User',
|
|
"suggestion should be displayed correctly");
|
|
assert.strictEqual(form.$('.o_mention_proposition .o_mention_info').text(), '(test@odoo.com)',
|
|
"suggestion should be displayed correctly");
|
|
//cleanup
|
|
form.destroy();
|
|
});
|
|
|
|
QUnit.test('chatter: post a message disable the send button', async function(assert) {
|
|
assert.expect(3);
|
|
var form = await createView({
|
|
View: FormView,
|
|
model: 'partner',
|
|
data: this.data,
|
|
services: this.services,
|
|
arch: '<form string="Partners">' +
|
|
'<sheet>' +
|
|
'<field name="foo"/>' +
|
|
'</sheet>' +
|
|
'<div class="oe_chatter">' +
|
|
'<field name="message_ids" widget="mail_thread"/>' +
|
|
'</div>' +
|
|
'</form>',
|
|
res_id: 2,
|
|
session: {},
|
|
mockRPC: function (route, args) {
|
|
if (route === '/mail/get_suggested_recipients') {
|
|
return Promise.resolve({2: []});
|
|
}
|
|
if (args.method === 'message_post') {
|
|
assert.ok(form.$('.o_composer_button_send').prop("disabled"),
|
|
"Send button should be disabled when a message is being sent");
|
|
return Promise.resolve(57923);
|
|
}
|
|
if (args.method === 'message_format') {
|
|
return Promise.resolve([{
|
|
author_id: ["42", "Me"],
|
|
model: 'partner',
|
|
}]);
|
|
}
|
|
return this._super(route, args);
|
|
},
|
|
});
|
|
|
|
await testUtils.dom.click(form.$('.o_chatter_button_new_message'));
|
|
assert.notOk(form.$('.o_composer_button_send').prop('disabled'),
|
|
"Send button should be enabled when posting a message");
|
|
form.$('.oe_chatter .o_composer_text_field:first()').val("My first message");
|
|
await testUtils.dom.click(form.$('.oe_chatter .o_composer_button_send'));
|
|
await testUtils.dom.click(form.$('.o_chatter_button_new_message'));
|
|
assert.notOk(form.$('.o_composer_button_send').prop('disabled'),
|
|
"Send button should be enabled when posting another message");
|
|
form.destroy();
|
|
});
|
|
|
|
QUnit.test('chatter: post message failure keep message', async function(assert) {
|
|
assert.expect(4);
|
|
var form = await createView({
|
|
View: FormView,
|
|
model: 'partner',
|
|
data: this.data,
|
|
services: this.services,
|
|
arch: '<form string="Partners">' +
|
|
'<sheet>' +
|
|
'<field name="foo"/>' +
|
|
'</sheet>' +
|
|
'<div class="oe_chatter">' +
|
|
'<field name="message_ids" widget="mail_thread"/>' +
|
|
'</div>' +
|
|
'</form>',
|
|
res_id: 2,
|
|
session: {},
|
|
mockRPC: function (route, args) {
|
|
if (route === '/mail/get_suggested_recipients') {
|
|
return Promise.resolve({2: []});
|
|
}
|
|
if (args.method === 'message_post') {
|
|
assert.ok(form.$('.o_composer_button_send').prop("disabled"),
|
|
"Send button should be disabled when a message is being sent");
|
|
// simulate failure
|
|
return Promise.reject();
|
|
|
|
}
|
|
if (args.method === 'message_format') {
|
|
return Promise.resolve([]);
|
|
}
|
|
return this._super(route, args);
|
|
},
|
|
});
|
|
|
|
await testUtils.dom.click(form.$('.o_chatter_button_new_message'));
|
|
assert.notOk(form.$('.o_composer_button_send').prop('disabled'),
|
|
"Send button should be enabled initially");
|
|
await testUtils.fields.editInput(form.$('.oe_chatter .o_composer_text_field:first()'), "My first message");
|
|
await testUtils.dom.click(form.$('.oe_chatter .o_composer_button_send'));
|
|
assert.strictEqual(form.$('.o_composer_text_field').val(), "My first message",
|
|
"Should keep unsent message in the composer on failure");
|
|
assert.notOk(form.$('.o_composer_button_send').prop('disabled'),
|
|
"Send button should be re-enabled on message post failure");
|
|
form.destroy();
|
|
});
|
|
|
|
QUnit.test('chatter: receive notif when document is open', async function (assert) {
|
|
assert.expect(2);
|
|
|
|
var form = await createView({
|
|
View: FormView,
|
|
model: 'partner',
|
|
data: this.data,
|
|
services: this.services,
|
|
arch: '<form string="Partners">' +
|
|
'<sheet>' +
|
|
'<field name="foo"/>' +
|
|
'</sheet>' +
|
|
'<div class="oe_chatter">' +
|
|
'<field name="message_ids" widget="mail_thread"/>' +
|
|
'</div>' +
|
|
'</form>',
|
|
res_id: 2,
|
|
session: {
|
|
partner_id: 3,
|
|
},
|
|
});
|
|
|
|
var thread = form.call('mail_service', 'getDocumentThread', 'partner', 2);
|
|
assert.strictEqual(thread.getUnreadCounter(), 0,
|
|
"document thread should have no unread messages initially");
|
|
|
|
// simulate receiving a needaction message on this document thread
|
|
var needactionMessageData = {
|
|
id: 5,
|
|
author_id: [42, "Someone"],
|
|
body: 'important message',
|
|
channel_ids: [],
|
|
res_id: 2,
|
|
model: 'partner',
|
|
needaction: true,
|
|
needaction_partner_ids: [3],
|
|
};
|
|
this.data['mail.message'].records.push(needactionMessageData);
|
|
var notification = [[false, 'mail.channel', 1], needactionMessageData];
|
|
form.call('bus_service', 'trigger', 'notification', [notification]);
|
|
await testUtils.nextMicrotaskTick();
|
|
assert.strictEqual(thread.getUnreadCounter(), 1,
|
|
"document thread should now have one unread message");
|
|
|
|
// do not destroy form too early. wait rendering to avoid race condition in service call.
|
|
await testUtils.nextTick();
|
|
form.destroy();
|
|
});
|
|
|
|
QUnit.test('chatter: access document with some notifs', async function (assert) {
|
|
assert.expect(3);
|
|
|
|
// simulate received needaction message on this document thread
|
|
var needactionMessageData = {
|
|
id: 5,
|
|
author_id: [42, "Someone"],
|
|
body: 'important message',
|
|
channel_ids: [],
|
|
res_id: 2,
|
|
model: 'partner',
|
|
needaction: true,
|
|
needaction_partner_ids: [3],
|
|
};
|
|
this.data['mail.message'].records.push(needactionMessageData);
|
|
this.data['partner'].records[0].message_ids = [5];
|
|
|
|
var form = await createView({
|
|
View: FormView,
|
|
model: 'partner',
|
|
data: this.data,
|
|
services: this.services,
|
|
arch: '<form string="Partners">' +
|
|
'<sheet>' +
|
|
'<field name="foo"/>' +
|
|
'</sheet>' +
|
|
'<div class="oe_chatter">' +
|
|
'<field name="message_ids" widget="mail_thread"/>' +
|
|
'</div>' +
|
|
'</form>',
|
|
res_id: 2,
|
|
session: {
|
|
partner_id: 3,
|
|
},
|
|
mockRPC: function (route, args) {
|
|
if (args.method === 'set_message_done') {
|
|
assert.step('set_message_done');
|
|
}
|
|
return this._super.apply(this, arguments);
|
|
},
|
|
});
|
|
|
|
assert.verifySteps(['set_message_done']);
|
|
|
|
var thread = form.call('mail_service', 'getDocumentThread', 'partner', 2);
|
|
assert.strictEqual(thread.getUnreadCounter(), 0,
|
|
"document thread should have no unread messages (marked as read)");
|
|
|
|
form.destroy();
|
|
});
|
|
|
|
QUnit.test('chatter: new message notification from document', async function (assert) {
|
|
// when receiving new messages from a document that is open, it should
|
|
// not mark the messages are read
|
|
assert.expect(4);
|
|
|
|
var form = await createView({
|
|
View: FormView,
|
|
model: 'partner',
|
|
data: this.data,
|
|
services: this.services,
|
|
arch: '<form string="Partners">' +
|
|
'<sheet>' +
|
|
'<field name="foo"/>' +
|
|
'</sheet>' +
|
|
'<div class="oe_chatter">' +
|
|
'<field name="message_ids" widget="mail_thread"/>' +
|
|
'</div>' +
|
|
'</form>',
|
|
res_id: 2,
|
|
session: {
|
|
partner_id: 3,
|
|
},
|
|
mockRPC: function (route, args) {
|
|
if (args.method === 'set_message_done') {
|
|
throw new Error("should not mark message as read");
|
|
}
|
|
return this._super.apply(this, arguments);
|
|
},
|
|
});
|
|
|
|
var thread = form.call('mail_service', 'getDocumentThread', 'partner', 2);
|
|
assert.strictEqual(
|
|
thread.getUnreadCounter(),
|
|
0,
|
|
"document thread should have no unread messages initially");
|
|
assert.containsNone(
|
|
form,
|
|
'.o_thread_message',
|
|
"should have no message in the chatter");
|
|
|
|
// Simulate received needaction`` message on this document
|
|
var message = {
|
|
author_id: [1, "Me"],
|
|
body: '<p>test</p>',
|
|
channel_ids: [],
|
|
id: 2,
|
|
model: 'partner',
|
|
needaction_partner_ids: [3],
|
|
res_id: 2,
|
|
};
|
|
const notifications = [ [['myDB', 'ir.needaction'], message] ];
|
|
form.call('bus_service', 'trigger', 'notification', notifications);
|
|
await testUtils.nextTick();
|
|
assert.strictEqual(
|
|
thread.getUnreadCounter(),
|
|
1,
|
|
"document thread should have one unread message (not marked as read)");
|
|
assert.containsOnce(
|
|
form,
|
|
'.o_thread_message',
|
|
"should have one message in the chatter");
|
|
|
|
form.destroy();
|
|
});
|
|
|
|
QUnit.test('chatter: post a message and switch in edit mode', async function (assert) {
|
|
assert.expect(5);
|
|
|
|
var form = await createView({
|
|
View: FormView,
|
|
model: 'partner',
|
|
data: this.data,
|
|
services: this.services,
|
|
arch: '<form string="Partners">' +
|
|
'<sheet>' +
|
|
'<field name="foo"/>' +
|
|
'</sheet>' +
|
|
'<div class="oe_chatter">' +
|
|
'<field name="message_ids" widget="mail_thread" options="{\'display_log_button\': True}"/>' +
|
|
'</div>' +
|
|
'</form>',
|
|
res_id: 2,
|
|
session: {},
|
|
mockRPC: function (route, args) {
|
|
if (route === '/mail/get_suggested_recipients') {
|
|
return Promise.resolve({2: []});
|
|
}
|
|
if (args.method === 'message_post') {
|
|
this.data['mail.message'].records.push({
|
|
attachment_ids: args.kwargs.attachment_ids,
|
|
author_id: ["42", "Me"],
|
|
body: args.kwargs.body,
|
|
date: "2016-12-20 10:35:40",
|
|
id: 42,
|
|
is_note: args.kwargs.subtype === 'mail.mt_note',
|
|
is_discussion: args.kwargs.subtype === 'mail.mt_comment',
|
|
is_starred: false,
|
|
model: 'partner',
|
|
res_id: 2,
|
|
});
|
|
return Promise.resolve(42);
|
|
}
|
|
|
|
return this._super(route, args);
|
|
},
|
|
});
|
|
|
|
assert.containsNone(form, '.o_thread_message', "thread should not contain messages");
|
|
|
|
// send a message
|
|
await testUtils.dom.click(form.$('.o_chatter_button_new_message'));
|
|
form.$('.oe_chatter .o_composer_text_field:first()').val("My first message");
|
|
await testUtils.dom.click(form.$('.oe_chatter .o_composer_button_send'));
|
|
assert.containsOnce(form, '.o_thread_message', "thread should contain a message");
|
|
assert.ok(form.$('.o_thread_message:first() .o_thread_message_core').text().indexOf('My first message') >= 0,
|
|
"the message's body should be correct");
|
|
|
|
// switch in edit mode
|
|
await testUtils.form.clickEdit(form);
|
|
assert.containsOnce(form, '.o_thread_message', "thread should contain a message");
|
|
assert.ok(form.$('.o_thread_message:first() .o_thread_message_core').text().indexOf('My first message') >= 0,
|
|
"the message's body should be correct");
|
|
|
|
form.destroy();
|
|
});
|
|
|
|
QUnit.test('chatter: discard changes on message post with post_refresh "always"', async function (assert) {
|
|
// After posting a message that always reloads the record, if the record
|
|
// is dirty (= has some unsaved changes), we should warn the user that
|
|
// these changes will be lost if he proceeds.
|
|
assert.expect(2);
|
|
|
|
var form = await createView({
|
|
View: FormView,
|
|
model: 'partner',
|
|
data: this.data,
|
|
services: this.services,
|
|
arch: '<form string="Partners">' +
|
|
'<sheet>' +
|
|
'<field name="foo"/>' +
|
|
'</sheet>' +
|
|
'<div class="oe_chatter">' +
|
|
'<field name="message_ids" widget="mail_thread"' +
|
|
' options="{\'display_log_button\': True, \'post_refresh\': \'always\'}"/>' +
|
|
'</div>' +
|
|
'</form>',
|
|
res_id: 2,
|
|
session: {},
|
|
mockRPC: function (route, args) {
|
|
if (route === "/mail/get_suggested_recipients") {
|
|
return Promise.resolve({2: []});
|
|
}
|
|
return this._super(route, args);
|
|
},
|
|
viewOptions: {
|
|
mode: 'edit',
|
|
},
|
|
});
|
|
|
|
// Make record dirty
|
|
await testUtils.fields.editInput(form.$('.o_form_sheet input'), 'trululu');
|
|
|
|
// Send a message
|
|
await testUtils.dom.click(form.$('.o_chatter_button_new_message'));
|
|
form.$('.oe_chatter .o_composer_text_field:first()').val("My first message");
|
|
await testUtils.dom.click(form.$('.oe_chatter .o_composer_button_send'));
|
|
|
|
var $modal = $('.modal-dialog');
|
|
assert.strictEqual($modal.length, 1, "should have a modal opened");
|
|
assert.strictEqual($modal.find('.modal-body').text(),
|
|
"The record has been modified, your changes will be discarded. Do you want to proceed?",
|
|
"should warn the user that any unsaved changes will be lost");
|
|
|
|
form.destroy();
|
|
});
|
|
|
|
QUnit.test('chatter: discard changes on message post without post_refresh', async function (assert) {
|
|
// After posting a message, if the record is dirty and there are no
|
|
// post_refresh rule, it will not discard the changes on the record.
|
|
assert.expect(2);
|
|
|
|
var hasDiscardChanges = false; // set if `discard_changes` has been triggered up
|
|
var messages = [];
|
|
var form = await createView({
|
|
View: FormView,
|
|
model: 'partner',
|
|
data: this.data,
|
|
services: this.services,
|
|
arch: '<form string="Partners">' +
|
|
'<sheet>' +
|
|
'<field name="foo"/>' +
|
|
'</sheet>' +
|
|
'<div class="oe_chatter">' +
|
|
'<field name="message_ids" widget="mail_thread"' +
|
|
' options="{\'display_log_button\': True}"/>' +
|
|
'</div>' +
|
|
'</form>',
|
|
res_id: 2,
|
|
session: {},
|
|
mockRPC: function (route, args) {
|
|
if (route === "/mail/get_suggested_recipients") {
|
|
return Promise.resolve({2: []});
|
|
}
|
|
if (args.method === 'message_format') {
|
|
var requested_msgs = _.filter(messages, function (msg) {
|
|
return _.contains(args.args[0], msg.id);
|
|
});
|
|
return Promise.resolve(requested_msgs);
|
|
}
|
|
if (args.method === 'message_post') {
|
|
messages.push({
|
|
attachment_ids: [],
|
|
author_id: ["42", "Me"],
|
|
body: args.kwargs.body,
|
|
date: moment().format('YYYY-MM-DD HH:MM:SS'), // now
|
|
displayed_author: "Me",
|
|
id: 42,
|
|
is_note: args.kwargs.subtype === 'mail.mt_note',
|
|
is_starred: false,
|
|
model: 'partner',
|
|
res_id: 2,
|
|
});
|
|
return Promise.resolve(42);
|
|
}
|
|
return this._super(route, args);
|
|
},
|
|
intercepts: {
|
|
discard_changes: function () {
|
|
hasDiscardChanges = true; // should not do that
|
|
},
|
|
},
|
|
viewOptions: {
|
|
mode: 'edit',
|
|
},
|
|
});
|
|
|
|
// Make record dirty
|
|
await testUtils.fields.editInput(form.$('.o_form_sheet input'), 'trululu');
|
|
|
|
// Send a message
|
|
await testUtils.dom.click(form.$('.o_chatter_button_new_message'));
|
|
form.$('.oe_chatter .o_composer_text_field:first()').val("My first message");
|
|
await testUtils.dom.click(form.$('.oe_chatter .o_composer_button_send'));
|
|
|
|
var $modal = $('.modal-dialog');
|
|
assert.strictEqual($modal.length, 0, "should have no modal opened");
|
|
assert.notOk(hasDiscardChanges);
|
|
|
|
form.destroy();
|
|
});
|
|
|
|
QUnit.test('chatter: discard changes on message post with post_refresh "recipients"', async function (assert) {
|
|
// After posting a message with mentions, the record will be reloaded,
|
|
// as the rpc `message_post` may make changes on some fields of the record.
|
|
// If the record is dirty (= has some unsaved changes), we should warn the
|
|
// user that these changes will be lost if he proceeds.
|
|
assert.expect(2);
|
|
|
|
var getSuggestionsDef = testUtils.makeTestPromise();
|
|
|
|
var messages = [];
|
|
var form = await createView({
|
|
View: FormView,
|
|
model: 'partner',
|
|
data: this.data,
|
|
services: this.services,
|
|
arch: '<form string="Partners">' +
|
|
'<sheet>' +
|
|
'<field name="foo"/>' +
|
|
'</sheet>' +
|
|
'<div class="oe_chatter">' +
|
|
'<field name="message_ids" widget="mail_thread"' +
|
|
' options="{\'display_log_button\': True, \'post_refresh\': \'recipients\'}"/>' +
|
|
'</div>' +
|
|
'</form>',
|
|
res_id: 2,
|
|
session: {},
|
|
mockRPC: function (route, args) {
|
|
if (route === "/mail/get_suggested_recipients") {
|
|
return Promise.resolve({2: [[42, "Me"]]});
|
|
}
|
|
if (args.method === 'get_mention_suggestions') {
|
|
getSuggestionsDef.resolve();
|
|
return Promise.resolve([[{email: "me@odoo.com", id: 42, name: "Me"}], []]);
|
|
}
|
|
if (args.method === 'message_format') {
|
|
var requested_msgs = _.filter(messages, function (msg) {
|
|
return _.contains(args.args[0], msg.id);
|
|
});
|
|
return Promise.resolve(requested_msgs);
|
|
}
|
|
if (args.method === 'message_post') {
|
|
messages.push({
|
|
attachment_ids: [],
|
|
author_id: ["42", "Me"],
|
|
body: args.kwargs.body,
|
|
date: moment().format('YYYY-MM-DD HH:MM:SS'), // now
|
|
displayed_author: "Me",
|
|
id: 42,
|
|
is_note: args.kwargs.subtype === 'mail.mt_note',
|
|
is_starred: false,
|
|
model: 'partner',
|
|
res_id: 2,
|
|
});
|
|
return Promise.resolve(42);
|
|
}
|
|
return this._super(route, args);
|
|
},
|
|
viewOptions: {
|
|
mode: 'edit',
|
|
},
|
|
});
|
|
|
|
// Make record dirty
|
|
await testUtils.fields.editInput(form.$('.o_form_sheet input'), 'trululu');
|
|
|
|
// create a new message
|
|
await testUtils.dom.click(form.$('.o_chatter_button_new_message'));
|
|
|
|
// Add a user as mention
|
|
await testUtils.fields.editInput(form.$('.oe_chatter .o_composer_text_field:first()'), "@");
|
|
|
|
var $input = form.$('.oe_chatter .o_composer_text_field:first()');
|
|
$input.val('@');
|
|
// the cursor position must be set for the mention manager to detect that we are mentionning
|
|
$input[0].selectionStart = 1;
|
|
$input[0].selectionEnd = 1;
|
|
$input.trigger('keyup');
|
|
await getSuggestionsDef;
|
|
await testUtils.nextTick();
|
|
// click on mention
|
|
$input.trigger($.Event('keyup', {which: $.ui.keyCode.ENTER}));
|
|
await testUtils.nextTick();
|
|
|
|
// untick recipient as follower (prompts a res.partner form otherwise)
|
|
form.$('input[type="checkbox"]').prop('checked', false);
|
|
await testUtils.nextTick();
|
|
// send message
|
|
await testUtils.dom.click(form.$('.oe_chatter .o_composer_button_send'));
|
|
|
|
var $modal = $('.modal-dialog');
|
|
assert.strictEqual($modal.length, 1, "should have a modal opened");
|
|
assert.strictEqual($modal.find('.modal-body').text(),
|
|
"The record has been modified, your changes will be discarded. Do you want to proceed?",
|
|
"should warn the user that any unsaved changes will be lost");
|
|
|
|
form.destroy();
|
|
});
|
|
|
|
QUnit.test('chatter: discard changes on opening full-composer and open missing partner info popup', async function (assert) {
|
|
// When we open the full-composer, any following operations by the user
|
|
// will reload the record (even closing the full-composer). Therefore,
|
|
// we should warn the user when we open the full-composer if the record
|
|
// is dirty (= has some unsaved changes).
|
|
assert.expect(3);
|
|
|
|
var form = await createView({
|
|
View: FormView,
|
|
model: 'partner',
|
|
data: this.data,
|
|
services: this.services,
|
|
arch: '<form string="Partners">' +
|
|
'<sheet>' +
|
|
'<field name="foo"/>' +
|
|
'</sheet>' +
|
|
'<div class="oe_chatter">' +
|
|
'<field name="message_ids" widget="mail_thread"' +
|
|
' options="{\'display_log_button\': True,' +
|
|
' \'post_refresh\': \'always\'}"/>' +
|
|
'</div>' +
|
|
'</form>',
|
|
archs: {
|
|
'res.partner,false,form':
|
|
'<form string="partners">' +
|
|
'<field name="name"/>' +
|
|
'</form>',
|
|
},
|
|
res_id: 2,
|
|
session: {},
|
|
mockRPC: function (route, args) {
|
|
if (route === "/mail/get_suggested_recipients") {
|
|
return Promise.resolve({2: [[
|
|
false,
|
|
'pikapika@pikachu.com',
|
|
''
|
|
]]});
|
|
}
|
|
if (route === "/mail/get_partner_info") {
|
|
return Promise.resolve([{full_name: "pikapika@pikachu.com", partner_id: false}]);
|
|
}
|
|
return this._super(route, args);
|
|
},
|
|
viewOptions: {
|
|
mode: 'edit',
|
|
},
|
|
});
|
|
|
|
// Make record dirty
|
|
await testUtils.fields.editInput(form.$('.o_form_sheet input'), 'trululu');
|
|
|
|
// Open full-composer
|
|
await testUtils.dom.click(form.$('.o_chatter_button_new_message'));
|
|
await testUtils.dom.click(form.$('.o_composer_button_full_composer'));
|
|
|
|
var $modal = $('.modal-dialog');
|
|
assert.strictEqual($modal.length, 1, "should have a modal opened");
|
|
assert.strictEqual($modal.find('.modal-body').text(),
|
|
"The record has been modified, your changes will be discarded. Do you want to proceed?",
|
|
"should warn the user that any unsaved changes will be lost");
|
|
await testUtils.dom.click($modal.find('.modal-footer .btn.btn-primary'));
|
|
assert.strictEqual($modal.length, 1, "should have a modal opened");
|
|
|
|
form.destroy();
|
|
});
|
|
|
|
QUnit.test('chatter in x2many form view', async function (assert) {
|
|
// the purpose of this test is to ensure that it doesn't crash when a x2many
|
|
// record is opened in form view (thus in a dialog), and when there is a
|
|
// chatter in the arch (typically, this may occur when the view used for the
|
|
// x2many is a default one, which is also used in another context, as the
|
|
// chatter is hidden in the dialog anyway)
|
|
assert.expect(2);
|
|
|
|
this.data.partner.fields.o2m = {
|
|
string: "one2many field", type: "one2many", relation: 'partner',
|
|
};
|
|
this.data.partner.records[0].o2m = [2];
|
|
|
|
var form = await createView({
|
|
View: FormView,
|
|
model: 'partner',
|
|
data: this.data,
|
|
services: this.services,
|
|
arch: '<form><field name="o2m"/></form>',
|
|
archs: {
|
|
'partner,false,form': '<form>' +
|
|
'<field name="foo"/>' +
|
|
'<div class="oe_chatter">' +
|
|
'<field name="message_ids" widget="mail_thread"/>' +
|
|
'</div>' +
|
|
'</form>',
|
|
'partner,false,list': '<tree><field name="display_name"/></tree>',
|
|
},
|
|
res_id: 2,
|
|
viewOptions: {
|
|
mode: 'edit',
|
|
},
|
|
});
|
|
|
|
await testUtils.dom.click(form.$('.o_data_row:first'));
|
|
|
|
assert.strictEqual($('.modal .o_form_view').length, 1,
|
|
"should have open a form view in a modal");
|
|
assert.strictEqual($('.modal .o_chatter:visible').length, 0,
|
|
"chatter should be hidden (as in a dialog)");
|
|
|
|
form.destroy();
|
|
});
|
|
|
|
QUnit.test('chatter: Attachment viewer', async function (assert) {
|
|
assert.expect(6);
|
|
this.data.partner.records[0].message_ids = [1];
|
|
this.data['mail.message'].records = [{
|
|
attachment_ids: [{
|
|
filename: 'image1.jpg',
|
|
id:1,
|
|
checksum: 999,
|
|
mimetype: 'image/jpeg',
|
|
name: 'Test Image 1',
|
|
url: '/web/content/1?download=true'
|
|
},{
|
|
filename: 'image2.jpg',
|
|
id:2,
|
|
checksum: 999,
|
|
mimetype: 'image/jpeg',
|
|
name: 'Test Image 2',
|
|
url: '/web/content/2?download=true'
|
|
},{
|
|
filename: 'image3.jpg',
|
|
id:3,
|
|
checksum: 999,
|
|
mimetype: 'image/jpeg',
|
|
name: 'Test Image 3',
|
|
url: '/web/content/3?download=true'
|
|
},{
|
|
filename: 'pdf1.pdf',
|
|
id:4,
|
|
mimetype: 'application/pdf',
|
|
name: 'Test PDF 1',
|
|
url: '/web/content/4?download=true'
|
|
}],
|
|
author_id: ["1", "John Doe"],
|
|
body: "Attachement viewer test",
|
|
date: "2016-12-20 09:35:40",
|
|
id: 1,
|
|
is_note: false,
|
|
is_discussion: true,
|
|
is_starred: false,
|
|
model: 'partner',
|
|
res_id: 2,
|
|
}];
|
|
|
|
var form = await createView({
|
|
View: FormView,
|
|
model: 'partner',
|
|
data: this.data,
|
|
services: this.services,
|
|
arch: '<form string="Partners">' +
|
|
'<sheet>' +
|
|
'<field name="foo"/>' +
|
|
'</sheet>' +
|
|
'<div class="oe_chatter">' +
|
|
'<field name="message_ids" widget="mail_thread" options="{\'display_log_button\': True}"/>' +
|
|
'</div>' +
|
|
'</form>',
|
|
res_id: 2,
|
|
mockRPC: function (route, args) {
|
|
if (_.str.contains(route, '/mail/attachment/preview/') ||
|
|
_.str.contains(route, '/web/static/lib/pdfjs/web/viewer.html')){
|
|
var canvas = document.createElement('canvas');
|
|
return Promise.resolve(canvas.toDataURL());
|
|
}
|
|
return this._super.apply(this, arguments);
|
|
},
|
|
});
|
|
assert.containsN(form, '.o_thread_message .o_attachment', 4,
|
|
"there should be three attachment on message");
|
|
assert.hasAttrValue(form.$('.o_thread_message .o_attachment a').first(), 'href', '/web/content/1?download=true',
|
|
"image caption should have correct download link");
|
|
// click on first image attachement
|
|
await testUtils.dom.click(form.$('.o_thread_message .o_attachment .o_image_box .o_image_overlay').first());
|
|
assert.strictEqual($('.o_modal_fullscreen img.o_viewer_img[data-src="/web/image/1?unique=1&signature=999&model=ir.attachment"]').length, 1,
|
|
"Modal popup should open with first image src");
|
|
// click on next button
|
|
await testUtils.dom.click($('.modal .arrow.arrow-right.move_next span'));
|
|
assert.strictEqual($('.o_modal_fullscreen img.o_viewer_img[data-src="/web/image/2?unique=1&signature=999&model=ir.attachment"]').length, 1,
|
|
"Modal popup should have now second image src");
|
|
assert.strictEqual($('.o_modal_fullscreen .o_viewer_toolbar .o_download_btn').length, 1,
|
|
"Modal popup should have download button");
|
|
// close attachment popup
|
|
await testUtils.dom.click($('.o_modal_fullscreen .o_viewer-header .o_close_btn'));
|
|
// click on pdf attachement
|
|
await testUtils.dom.click(form.$('span:contains(Test PDF 1)'));
|
|
assert.strictEqual($('.o_modal_fullscreen iframe[data-src*="/web/content/4"]').length, 1,
|
|
"Modal popup should open with the pdf preview");
|
|
// close attachment popup
|
|
await testUtils.dom.click($('.o_modal_fullscreen .o_viewer-header .o_close_btn'));
|
|
form.destroy();
|
|
});
|
|
|
|
QUnit.test('chatter: keep context when sending a message', async function(assert) {
|
|
assert.expect(1);
|
|
var form = await createView({
|
|
View: FormView,
|
|
model: 'partner',
|
|
data: this.data,
|
|
services: this.services,
|
|
arch: '<form string="Partners">' +
|
|
'<sheet>' +
|
|
'<field name="foo"/>' +
|
|
'</sheet>' +
|
|
'<div class="oe_chatter">' +
|
|
'<field name="message_ids" widget="mail_thread"/>' +
|
|
'</div>' +
|
|
'</form>',
|
|
res_id: 2,
|
|
session: {
|
|
user_context: {lang: 'en_US'},
|
|
},
|
|
mockRPC: function (route, args) {
|
|
if (route === '/mail/get_suggested_recipients') {
|
|
return Promise.resolve({2: []});
|
|
}
|
|
if (args.method === 'message_post') {
|
|
assert.deepEqual(args.kwargs.context, {
|
|
default_model: "partner",
|
|
default_res_id: 2,
|
|
lang: "en_US",
|
|
mail_post_autofollow: true,
|
|
},
|
|
"the context is incorrect");
|
|
return Promise.resolve(57923);
|
|
}
|
|
if (args.method === 'message_format') {
|
|
return Promise.resolve([{
|
|
author_id: [42, "Me"],
|
|
model: 'partner',
|
|
}]);
|
|
}
|
|
return this._super(route, args);
|
|
},
|
|
});
|
|
|
|
await testUtils.dom.click(form.$('.o_chatter_button_new_message'));
|
|
await testUtils.fields.editInput(form.$('.oe_chatter .o_composer_text_field:first()'), 'Pouet');
|
|
await testUtils.dom.click(form.$('.oe_chatter .o_composer_button_send'));
|
|
form.destroy();
|
|
});
|
|
|
|
QUnit.test('form activity widget: read RPCs', async function (assert) {
|
|
// Records of model 'mail.activity' may be updated in business flows (e.g.
|
|
// the date of a 'Meeting' activity is updated when the associated meeting
|
|
// is dragged&dropped in the Calendar view). Because of that, the activities
|
|
// must be reloaded when the form is reloaded (the widget can't keep an
|
|
// internal cache).
|
|
assert.expect(6);
|
|
this.data.partner.records[0].activity_ids = [1];
|
|
this.data.partner.records[0].activity_state = 'today';
|
|
this.data['mail.activity'].records = [{
|
|
id: 1,
|
|
display_name: "An activity",
|
|
date_deadline: moment().format("YYYY-MM-DD"), // now
|
|
can_write: true,
|
|
state: "today",
|
|
user_id: 2,
|
|
create_uid: 2,
|
|
activity_type_id: 2,
|
|
}];
|
|
|
|
var nbReads = 0;
|
|
var form = await createView({
|
|
View: FormView,
|
|
model: 'partner',
|
|
data: this.data,
|
|
services: this.services,
|
|
arch: '<form string="Partners">' +
|
|
'<div class="oe_chatter">' +
|
|
'<field name="activity_ids" widget="mail_activity"/>' +
|
|
'</div>' +
|
|
'</form>',
|
|
res_id: 2,
|
|
mockRPC: function (route, args) {
|
|
if (args.method === 'activity_format' && args.model === 'mail.activity') {
|
|
nbReads++;
|
|
}
|
|
return this._super.apply(this, arguments);
|
|
},
|
|
});
|
|
|
|
assert.strictEqual(nbReads, 1, "should have read the activities");
|
|
assert.containsOnce(form, '.o_mail_activity .o_thread_message',
|
|
"should display an activity");
|
|
assert.strictEqual(form.$('.o_mail_activity .o_thread_message .o_activity_date').text(),
|
|
'Today', "the activity should be today");
|
|
|
|
await testUtils.form.clickEdit(form);
|
|
await testUtils.form.clickSave(form);
|
|
|
|
assert.strictEqual(nbReads, 1, "should not have re-read the activities");
|
|
|
|
// simulate a date change, and a reload of the form view
|
|
var tomorrow = moment().add(1, 'day').format("YYYY-MM-DD");
|
|
this.data['mail.activity'].records[0].date_deadline = tomorrow;
|
|
await form.reload();
|
|
|
|
assert.strictEqual(nbReads, 2, "should have re-read the activities");
|
|
assert.strictEqual(form.$('.o_mail_activity .o_thread_message .o_activity_date').text(),
|
|
'Tomorrow', "the activity should be tomorrow");
|
|
|
|
form.destroy();
|
|
});
|
|
|
|
QUnit.test('form activity widget on a new record', async function (assert) {
|
|
assert.expect(0);
|
|
|
|
var form = await createView({
|
|
View: FormView,
|
|
model: 'partner',
|
|
data: this.data,
|
|
services: this.services,
|
|
arch: '<form string="Partners">' +
|
|
'<div class="oe_chatter">' +
|
|
'<field name="activity_ids" widget="mail_activity"/>' +
|
|
'</div>' +
|
|
'</form>',
|
|
mockRPC: function (route, args) {
|
|
if (args.method === 'read' && args.model === 'mail.activity') {
|
|
throw new Error("should not do a read on mail.activity");
|
|
}
|
|
return this._super.apply(this, arguments);
|
|
},
|
|
});
|
|
|
|
form.destroy();
|
|
});
|
|
|
|
QUnit.test('form activity widget with another x2many field in view', async function (assert) {
|
|
assert.expect(1);
|
|
|
|
this.data.partner.fields.m2m = {string: "M2M", type: 'many2many', relation: 'partner'};
|
|
|
|
this.data.partner.records[0].m2m = [2];
|
|
this.data.partner.records[0].activity_ids = [1];
|
|
this.data.partner.records[0].activity_state = 'today';
|
|
this.data['mail.activity'].records = [{
|
|
id: 1,
|
|
display_name: "An activity",
|
|
date_deadline: moment().format("YYYY-MM-DD"), // now
|
|
can_write: true,
|
|
state: "today",
|
|
user_id: 2,
|
|
create_uid: 2,
|
|
activity_type_id: 2,
|
|
}];
|
|
|
|
var form = await createView({
|
|
View: FormView,
|
|
model: 'partner',
|
|
data: this.data,
|
|
services: this.services,
|
|
arch: '<form string="Partners">' +
|
|
'<field name="m2m" widget="many2many_tags"/>' +
|
|
'<div class="oe_chatter">' +
|
|
'<field name="activity_ids" widget="mail_activity"/>' +
|
|
'</div>' +
|
|
'</form>',
|
|
res_id: 2,
|
|
});
|
|
|
|
assert.containsOnce(form, '.o_mail_activity .o_thread_message',
|
|
"should display an activity");
|
|
|
|
form.destroy();
|
|
});
|
|
|
|
QUnit.test('form activity widget: schedule next activity', async function (assert) {
|
|
assert.expect(4);
|
|
this.data.partner.records[0].activity_ids = [1];
|
|
this.data.partner.records[0].activity_state = 'today';
|
|
this.data['mail.activity'].records = [{
|
|
id: 1,
|
|
display_name: "An activity",
|
|
date_deadline: moment().format("YYYY-MM-DD"), // now
|
|
can_write: true,
|
|
state: "today",
|
|
user_id: 2,
|
|
create_uid: 2,
|
|
activity_type_id: 2,
|
|
}];
|
|
|
|
var form = await createView({
|
|
View: FormView,
|
|
model: 'partner',
|
|
data: this.data,
|
|
services: this.services,
|
|
arch: '<form string="Partners">' +
|
|
'<sheet>' +
|
|
'<field name="foo"/>' +
|
|
'</sheet>' +
|
|
'<div class="oe_chatter">' +
|
|
'<field name="message_ids" widget="mail_thread"/>' +
|
|
'<field name="activity_ids" widget="mail_activity"/>' +
|
|
'</div>' +
|
|
'</form>',
|
|
res_id: 2,
|
|
mockRPC: function (route, args) {
|
|
if (route === '/web/dataset/call_kw/mail.activity/action_feedback_schedule_next') {
|
|
assert.ok(_.isEqual(args.args[0], [1]), "should call 'action_feedback_schedule_next' for id 1");
|
|
assert.strictEqual(args.kwargs.feedback, 'everything is ok',
|
|
"the feedback should be sent correctly");
|
|
return Promise.resolve('test_result');
|
|
}
|
|
return this._super.apply(this, arguments);
|
|
},
|
|
intercepts: {
|
|
do_action: function (event) {
|
|
assert.strictEqual(event.data.action,'test_result' , "should do a do_action with correct parameters");
|
|
event.data.options.on_close();
|
|
},
|
|
},
|
|
});
|
|
//Schedule next activity
|
|
await testUtils.dom.click(form.$('.o_mail_activity .o_mark_as_done[data-activity-id=1]'));
|
|
assert.containsOnce(form, '.o_mail_activity_feedback.popover',
|
|
"a feedback popover should be visible");
|
|
$('.o_mail_activity_feedback.popover textarea').val('everything is ok'); // write a feedback
|
|
await testUtils.dom.click(form.$('.o_activity_popover_done_next'));
|
|
form.destroy();
|
|
});
|
|
|
|
QUnit.test('form activity widget: do not close activity popover on click', async function (assert) {
|
|
assert.expect(3);
|
|
this.data.partner.records[0].activity_ids = [1];
|
|
this.data.partner.records[0].activity_state = 'today';
|
|
this.data['mail.activity'].records = [{
|
|
id: 1,
|
|
display_name: "An activity",
|
|
date_deadline: moment().format("YYYY-MM-DD"), // now
|
|
can_write: true,
|
|
state: "today",
|
|
user_id: 2,
|
|
create_user_id: 2,
|
|
activity_type_id: 2,
|
|
}];
|
|
|
|
const form = await createView({
|
|
View: FormView,
|
|
model: 'partner',
|
|
data: this.data,
|
|
services: this.services,
|
|
arch: `<form string="Partners">
|
|
<sheet>
|
|
<field name="foo"/>
|
|
</sheet>
|
|
<div class="oe_chatter">
|
|
<field name="message_ids" widget="mail_thread"/>
|
|
<field name="activity_ids" widget="mail_activity"/>
|
|
</div>
|
|
</form>`,
|
|
res_id: 2,
|
|
mockRPC(route, args) {
|
|
if (route === '/web/dataset/call_kw/mail.activity/action_feedback_schedule_next') {
|
|
return Promise.resolve();
|
|
}
|
|
return this._super.apply(this, arguments);
|
|
},
|
|
intercepts: {
|
|
do_action(ev) {
|
|
ev.data.options.on_close();
|
|
},
|
|
},
|
|
});
|
|
|
|
//Schedule next activity
|
|
await testUtils.dom.click(form.$('.o_mail_activity .o_mark_as_done[data-activity-id=1]'));
|
|
|
|
for (let i = 0; i < 2; i++) {
|
|
// click twice in feedback area
|
|
await testUtils.dom.click(form.$('.o_mail_activity_feedback.popover textarea'));
|
|
const $popover = form.$('.o_mail_activity_feedback.popover');
|
|
const $PopoverInDom = form.$(`#${$popover.attr('id')}`);
|
|
assert.isVisible(
|
|
$PopoverInDom,
|
|
"the feedback popover should still be visible on click");
|
|
}
|
|
await concurrency.delay(250); // wait for popover transition
|
|
const $popover = form.$('.o_mail_activity_feedback.popover');
|
|
if (!$popover) {
|
|
throw new Error("popover should be in DOM");
|
|
}
|
|
const $PopoverInDom = form.$(`#${$popover.attr('id')}`);
|
|
assert.isVisible(
|
|
$PopoverInDom,
|
|
"the feedback popover should still be visible on click");
|
|
|
|
form.destroy();
|
|
});
|
|
|
|
|
|
QUnit.test('form activity widget: edit next activity', async function (assert) {
|
|
assert.expect(3);
|
|
var self = this;
|
|
this.data.partner.records[0].activity_ids = [1];
|
|
this.data.partner.records[0].activity_state = 'today';
|
|
this.data['mail.activity'].records = [{
|
|
id: 1,
|
|
display_name: "An activity",
|
|
date_deadline: moment().format("YYYY-MM-DD"), // now
|
|
can_write: true,
|
|
state: "today",
|
|
user_id: 2,
|
|
create_uid: 2,
|
|
activity_type_id: 2,
|
|
}];
|
|
|
|
var form = await createView({
|
|
View: FormView,
|
|
model: 'partner',
|
|
data: this.data,
|
|
services: this.services,
|
|
arch: '<form string="Partners">' +
|
|
'<sheet>' +
|
|
'<field name="foo"/>' +
|
|
'</sheet>' +
|
|
'<div class="oe_chatter">' +
|
|
'<field name="message_ids" widget="mail_thread"/>' +
|
|
'<field name="activity_ids" widget="mail_activity"/>' +
|
|
'</div>' +
|
|
'</form>',
|
|
res_id: 2,
|
|
intercepts: {
|
|
do_action: function (event) {
|
|
assert.deepEqual(event.data.action, {
|
|
context: {
|
|
default_res_id: 2,
|
|
default_res_model: "partner"
|
|
},
|
|
name: "Schedule Activity",
|
|
res_id: 1,
|
|
res_model: "mail.activity",
|
|
target: "new",
|
|
type: "ir.actions.act_window",
|
|
view_mode: "form",
|
|
views: [
|
|
[
|
|
false,
|
|
"form"
|
|
]
|
|
]
|
|
},
|
|
"should do a do_action with correct parameters");
|
|
self.data['mail.activity'].records[0].activity_type_id = 1;
|
|
event.data.options.on_close();
|
|
},
|
|
},
|
|
});
|
|
assert.strictEqual(form.$('.o_mail_activity .o_mail_info strong:eq(1)').text(), " Type 2",
|
|
"Initial type should be Type 2");
|
|
await testUtils.dom.click(form.$('.o_mail_activity .o_edit_activity[data-activity-id=1]'));
|
|
assert.strictEqual(form.$('.o_mail_activity .o_mail_info strong:eq(1)').text(), " Type 1",
|
|
"After edit type should be Type 1");
|
|
form.destroy();
|
|
});
|
|
|
|
QUnit.test('form activity widget: clic mail template', async function (assert) {
|
|
assert.expect(4);
|
|
this.data.partner.records[0].activity_ids = [1];
|
|
this.data.partner.records[0].activity_state = 'today';
|
|
this.data['mail.activity'].records = [{
|
|
id: 1,
|
|
display_name: "An activity",
|
|
date_deadline: moment().format("YYYY-MM-DD"), // now
|
|
can_write: true,
|
|
state: "today",
|
|
user_id: 2,
|
|
activity_type_id: 2,
|
|
}];
|
|
|
|
var form = await createView({
|
|
View: FormView,
|
|
model: 'partner',
|
|
data: this.data,
|
|
services: this.services,
|
|
arch: '<form string="Partners">' +
|
|
'<sheet>' +
|
|
'<field name="foo"/>' +
|
|
'</sheet>' +
|
|
'<div class="oe_chatter">' +
|
|
'<field name="message_ids" widget="mail_thread"/>' +
|
|
'<field name="activity_ids" widget="mail_activity"/>' +
|
|
'</div>' +
|
|
'</form>',
|
|
res_id: 2,
|
|
mockRPC: function (route, args) {
|
|
if (args.method === 'activity_format') {
|
|
return this._super.apply(this, arguments).then(function (res) {
|
|
res[0].mail_template_ids = [{ id: 100, name: 'Temp1' }];
|
|
return res;
|
|
});
|
|
}
|
|
return this._super.apply(this, arguments);
|
|
},
|
|
intercepts: {
|
|
do_action: function (ev) {
|
|
assert.deepEqual(ev.data.action, {
|
|
name: _t('Compose Email'),
|
|
type: 'ir.actions.act_window',
|
|
res_model: 'mail.compose.message',
|
|
views: [[false, 'form']],
|
|
target: 'new',
|
|
context: {
|
|
default_res_id: 2,
|
|
default_model: 'partner',
|
|
default_use_template: true,
|
|
default_template_id: 100,
|
|
force_email: true,
|
|
},
|
|
},
|
|
"should do a do_action with correct parameters");
|
|
ev.data.options.on_close();
|
|
},
|
|
},
|
|
});
|
|
assert.containsOnce(form, '.o_mail_activity .o_thread_message',
|
|
"we should have one activity");
|
|
assert.containsOnce(form, '.o_activity_template_preview',
|
|
"Activity should contains one mail template");
|
|
await testUtils.dom.click(form.$('.o_activity_template_preview[data-template-id=100]'));
|
|
assert.containsOnce(form, '.o_mail_activity .o_thread_message',
|
|
"activity should still be there");
|
|
form.destroy();
|
|
});
|
|
|
|
QUnit.test('form activity widget: schedule activity does not discard changes', async function (assert) {
|
|
assert.expect(1);
|
|
|
|
var form = await createView({
|
|
View: FormView,
|
|
model: 'partner',
|
|
data: this.data,
|
|
services: this.services,
|
|
arch: '<form string="Partners">' +
|
|
'<sheet>' +
|
|
'<field name="foo"/>' +
|
|
'</sheet>' +
|
|
'<div class="oe_chatter">' +
|
|
'<field name="activity_ids" widget="mail_activity"/>' +
|
|
'</div>' +
|
|
'</form>',
|
|
res_id: 2,
|
|
mockRPC: function (route, args) {
|
|
if (args.method === 'write') {
|
|
assert.deepEqual(args.args[1], {foo: 'new value'},
|
|
"should correctly save the change");
|
|
}
|
|
return this._super.apply(this, arguments);
|
|
},
|
|
intercepts: {
|
|
do_action: function (event) {
|
|
event.data.options.on_close();
|
|
},
|
|
},
|
|
viewOptions: {
|
|
mode: 'edit',
|
|
},
|
|
});
|
|
|
|
// update value of foo field
|
|
await testUtils.fields.editInput(form.$('.o_field_widget[name=foo]'), 'new value');
|
|
|
|
// schedule an activity (this triggers a do_action)
|
|
await testUtils.dom.click(form.$('.o_chatter_button_schedule_activity'));
|
|
|
|
// save the record
|
|
await testUtils.form.clickSave(form);
|
|
|
|
form.destroy();
|
|
});
|
|
|
|
QUnit.test('form activity widget: mark as done and remove', async function (assert) {
|
|
assert.expect(15);
|
|
|
|
var self = this;
|
|
|
|
var nbReads = 0;
|
|
this.data.partner.records[0].activity_ids = [1, 2];
|
|
this.data.partner.records[0].activity_state = 'today';
|
|
this.data['mail.activity'].records = [{
|
|
id: 1,
|
|
display_name: "An activity",
|
|
date_deadline: moment().format("YYYY-MM-DD"), // now
|
|
can_write: true,
|
|
state: "today",
|
|
user_id: 2,
|
|
create_uid: 2,
|
|
activity_type_id: 1,
|
|
}, {
|
|
id: 2,
|
|
display_name: "A second activity",
|
|
date_deadline: moment().format("YYYY-MM-DD"), // now
|
|
can_write: true,
|
|
state: "today",
|
|
user_id: 2,
|
|
create_uid: 2,
|
|
activity_type_id: 1,
|
|
}];
|
|
|
|
var form = await createView({
|
|
View: FormView,
|
|
model: 'partner',
|
|
data: this.data,
|
|
services: this.services,
|
|
arch: '<form string="Partners">' +
|
|
'<sheet>' +
|
|
'<field name="foo"/>' +
|
|
'</sheet>' +
|
|
'<div class="oe_chatter">' +
|
|
'<field name="message_ids" widget="mail_thread"/>' +
|
|
'<field name="activity_ids" widget="mail_activity"/>' +
|
|
'</div>' +
|
|
'</form>',
|
|
res_id: 2,
|
|
mockRPC: function (route, args) {
|
|
if (route === '/web/dataset/call_kw/mail.activity/unlink') {
|
|
assert.ok(_.isEqual(args.args[0], [1]), "should call 'unlink' for id 1");
|
|
} else if (route === '/web/dataset/call_kw/mail.activity/action_feedback') {
|
|
assert.ok(_.isEqual(args.args[0], [2]), "should call 'action_feedback' for id 2");
|
|
assert.strictEqual(args.kwargs.feedback, 'everything is ok',
|
|
"the feedback should be sent correctly");
|
|
// should generate a message and unlink the activity
|
|
self.data.partner.records[0].message_ids = [1];
|
|
this.data['mail.message'].records.push({
|
|
attachment_ids: [],
|
|
author_id: ["1", "John Doe"],
|
|
body: "The activity has been done",
|
|
date: "2016-12-20 09:35:40",
|
|
id: 1,
|
|
is_note: true,
|
|
is_discussion: false,
|
|
model: 'partner',
|
|
res_id: 2,
|
|
});
|
|
route = '/web/dataset/call_kw/mail.activity/unlink';
|
|
args.method = 'unlink';
|
|
} else if (route === '/web/dataset/call_kw/partner/read') {
|
|
nbReads++;
|
|
if (nbReads === 1) { // first read
|
|
assert.strictEqual(args.args[1].length, 5, 'should read all fiels the first time');
|
|
} else if (nbReads === 2) { // second read: after the unlink
|
|
assert.ok(_.isEqual(args.args[1], ['activity_ids', 'display_name']),
|
|
'should only read the activities (+ display_name) after an unlink');
|
|
} else { // third read: after marking an activity done
|
|
assert.ok(_.isEqual(args.args[1], ['activity_ids', 'message_ids', 'display_name']),
|
|
'should read the activities and messages (+ display_name) after marking an activity done');
|
|
}
|
|
}
|
|
return this._super.apply(this, arguments);
|
|
},
|
|
});
|
|
|
|
assert.containsN(form, '.o_mail_activity .o_thread_message', 2,
|
|
"there should be two activities");
|
|
|
|
// remove activity 1
|
|
await testUtils.dom.click(form.$('.o_mail_activity .o_unlink_activity[data-activity-id=1]'));
|
|
assert.containsOnce(form, '.o_mail_activity .o_thread_message',
|
|
"there should be one remaining activity");
|
|
assert.ok(!form.$('.o_mail_activity .o_unlink_activity[data-activity-id=1]').length,
|
|
"activity 1 should have been removed");
|
|
|
|
// mark activity done
|
|
assert.ok(!form.$('.o_mail_thread .o_thread_message').length,
|
|
"there should be no chatter message");
|
|
await testUtils.dom.click(form.$('.o_mail_activity .o_mark_as_done[data-activity-id=2]'));
|
|
assert.containsOnce(form, '.o_mail_activity_feedback.popover',
|
|
"a feedback popover should be visible");
|
|
$('.o_mail_activity_feedback.popover textarea').val('everything is ok'); // write a feedback
|
|
await testUtils.dom.click(form.$('.o_activity_popover_done'));
|
|
assert.containsNone(form, '.o_mail_activity_feedback.popover')
|
|
assert.ok(!form.$('.o_mail_activity .o_thread_message').length,
|
|
"there should be no more activity");
|
|
assert.containsOnce(form, '.o_mail_thread .o_thread_message',
|
|
"a chatter message should have been generated");
|
|
assert.strictEqual(form.$('.o_thread_message:contains(The activity has been done)').length, 1,
|
|
"the message's body should be correct");
|
|
form.destroy();
|
|
});
|
|
|
|
QUnit.test('followers widget: follow/unfollow, edit subtypes', async function (assert) {
|
|
assert.expect(14);
|
|
|
|
var resID = 2;
|
|
var partnerID = 2;
|
|
var followers = [];
|
|
var nbReads = 0;
|
|
var subtypes = [
|
|
{id: 1, name: "First subtype", followed: true},
|
|
{id: 2, name: "Second subtype", followed: true},
|
|
{id: 3, name: "Third subtype", followed: false},
|
|
];
|
|
var form = await createView({
|
|
View: FormView,
|
|
model: 'partner',
|
|
data: this.data,
|
|
services: this.services,
|
|
arch: '<form string="Partners">' +
|
|
'<sheet>' +
|
|
'<field name="foo"/>' +
|
|
'</sheet>' +
|
|
'<div class="oe_chatter">' +
|
|
'<field name="message_follower_ids" widget="mail_followers"/>' +
|
|
'</div>' +
|
|
'</form>',
|
|
res_id: resID,
|
|
mockRPC: function (route, args) {
|
|
if (route === '/web/dataset/call_kw/partner/message_subscribe') {
|
|
assert.strictEqual(args.args[0][0], resID, 'should call route for correct record');
|
|
assert.ok(_.isEqual(args.kwargs.partner_ids, [partnerID]),
|
|
'should call route for correct partner');
|
|
if (args.kwargs.subtype_ids) {
|
|
// edit subtypes
|
|
assert.ok(_.isEqual(args.kwargs.subtype_ids, [1]),
|
|
'should call route with the correct subtypes');
|
|
_.each(subtypes, function (subtype) {
|
|
subtype.followed = _.contains(args.kwargs.subtype_ids, subtype.id);
|
|
});
|
|
} else {
|
|
// follow
|
|
this.data.partner.records[0].message_follower_ids = [1];
|
|
followers.push({
|
|
id: 1,
|
|
is_uid: true,
|
|
name: "Admin",
|
|
email: "admin@example.com",
|
|
res_id: resID,
|
|
res_model: 'partner',
|
|
});
|
|
}
|
|
return Promise.resolve(true);
|
|
}
|
|
if (route === '/mail/read_followers') {
|
|
return Promise.resolve({
|
|
followers: followers,
|
|
subtypes: subtypes,
|
|
// caution, subtype will only be returned if current user is in args follower list
|
|
});
|
|
}
|
|
if (route === '/web/dataset/call_kw/partner/message_unsubscribe') {
|
|
assert.strictEqual(args.args[0][0], resID, 'should call route for correct record');
|
|
assert.ok(_.isEqual(args.args[1], [partnerID]), 'should call route for correct partner');
|
|
this.data.partner.records[0].message_follower_ids = [];
|
|
followers = [];
|
|
return Promise.resolve(true);
|
|
}
|
|
if (route === '/web/dataset/call_kw/partner/read') {
|
|
nbReads++;
|
|
if (nbReads === 1) { // first read: should read all fields
|
|
assert.strictEqual(args.args[1].length, 3,
|
|
'should read "foo", "message_follower_ids" and "display_name"');
|
|
} else { // three next reads: only read 'message_follower_ids' field
|
|
assert.deepEqual(args.args[1], ['message_follower_ids', 'display_name'],
|
|
'should only read "message_follower_ids" and "display_name"');
|
|
}
|
|
}
|
|
return this._super.apply(this, arguments);
|
|
},
|
|
session: {partner_id: partnerID},
|
|
});
|
|
|
|
assert.strictEqual(form.$('.o_followers_count').text(), "0", 'should have no followers');
|
|
assert.ok(form.$('.o_followers_follow_button.o_followers_notfollow').length,
|
|
'should display the "Follow" button');
|
|
|
|
// click to follow the document
|
|
await testUtils.dom.click(form.$('.o_followers_follow_button'));
|
|
assert.strictEqual(form.$('.o_followers_count').text(), "1", 'should have one follower');
|
|
assert.ok(form.$('.o_followers_follow_button.o_followers_following').length,
|
|
'should display the "Following/Unfollow" button');
|
|
assert.containsOnce(form, '.o_followers_list .o_partner',
|
|
"there should be one follower in the follower dropdown");
|
|
|
|
// click to unfollow
|
|
await testUtils.dom.click(form.$('.o_followers_follow_button'));
|
|
assert.strictEqual(form.$('.o_followers_count').text(), "0", 'should have no followers');
|
|
assert.ok(form.$('.o_followers_follow_button.o_followers_notfollow').length,
|
|
'should display the "Follow" button');
|
|
|
|
form.destroy();
|
|
});
|
|
|
|
QUnit.test('followers widget: follow/unfollow confirmation dialog', async function (assert) {
|
|
assert.expect(5);
|
|
|
|
this.data.partner.records[0].message_follower_ids = [1, 2];
|
|
var resID = 2;
|
|
var partnerID = 2;
|
|
var followers = [{
|
|
id: 1,
|
|
is_uid: true,
|
|
name: "Admin",
|
|
email: "admin@example.com",
|
|
res_id: resID,
|
|
res_model: 'res.partner',
|
|
}, {
|
|
id: 2,
|
|
is_uid: true,
|
|
name: "Demo",
|
|
email: "demo@example.com",
|
|
res_id: 3,
|
|
res_model: 'res.partner',
|
|
}];
|
|
|
|
var form = await createView({
|
|
View: FormView,
|
|
model: 'partner',
|
|
data: this.data,
|
|
services: this.services,
|
|
arch: '<form string="Partners">' +
|
|
'<sheet>' +
|
|
'<field name="foo"/>' +
|
|
'</sheet>' +
|
|
'<div class="oe_chatter">' +
|
|
'<field name="message_follower_ids" widget="mail_followers"/>' +
|
|
'</div>' +
|
|
'</form>',
|
|
res_id: resID,
|
|
|
|
mockRPC: function (route, args) {
|
|
if (route === '/web/dataset/call_kw/partner/message_unsubscribe') {
|
|
this.data.partner.records[0].message_follower_ids.shift();
|
|
followers.shift();
|
|
return Promise.resolve(true);
|
|
}
|
|
if (route === '/mail/read_followers') {
|
|
return Promise.resolve({
|
|
followers: followers,
|
|
});
|
|
}
|
|
return this._super.apply(this, arguments);
|
|
},
|
|
session: {partner_id: partnerID},
|
|
});
|
|
|
|
assert.strictEqual(form.$('.o_followers_count').text(), "2", 'should have 2 followers');
|
|
// click to unfollow current user
|
|
await testUtils.dom.click(form.$('.o_followers_follow_button'));
|
|
assert.containsNone($, '.modal', 'a confirm modal should not be opened');
|
|
assert.strictEqual(form.$('.o_followers_count').text(), "1", 'should have 1 follower');
|
|
|
|
await testUtils.dom.click(form.$('.o_followers_title_box [data-toggle="dropdown"]'));
|
|
// remove demo user from followers
|
|
await testUtils.dom.click(form.$('.o_remove_follower'));
|
|
assert.containsOnce($, '.modal', 'a confirm modal should be opened');
|
|
await testUtils.dom.click($('.modal .modal-footer .btn-primary'));
|
|
assert.strictEqual(form.$('.o_followers_count').text(), "0", 'should have no followers');
|
|
|
|
form.destroy();
|
|
});
|
|
|
|
QUnit.test('followers widget: do not display follower duplications', async function (assert) {
|
|
assert.expect(2);
|
|
|
|
this.data.partner.records[0].message_follower_ids = [1];
|
|
var resID = 2;
|
|
var followers = [{
|
|
id: 1,
|
|
name: "Admin",
|
|
email: "admin@example.com",
|
|
res_id: resID,
|
|
res_model: 'partner',
|
|
}];
|
|
var def;
|
|
var form = await createView({
|
|
View: FormView,
|
|
model: 'partner',
|
|
data: this.data,
|
|
services: this.services,
|
|
arch: '<form>' +
|
|
'<sheet></sheet>' +
|
|
'<div class="oe_chatter">' +
|
|
'<field name="message_follower_ids" widget="mail_followers"/>' +
|
|
'</div>' +
|
|
'</form>',
|
|
mockRPC: function (route, args) {
|
|
if (route === '/mail/read_followers') {
|
|
return Promise.resolve(def).then(function () {
|
|
return {
|
|
followers: _.filter(followers, function (follower) {
|
|
return _.contains(args.follower_ids, follower.id);
|
|
}),
|
|
subtypes: [],
|
|
};
|
|
});
|
|
}
|
|
return this._super.apply(this, arguments);
|
|
},
|
|
res_id: resID,
|
|
session: {partner_id: 1},
|
|
});
|
|
|
|
|
|
followers.push({
|
|
id: 2,
|
|
is_uid: false,
|
|
name: "A follower",
|
|
email: "follower@example.com",
|
|
res_id: resID,
|
|
res_model: 'partner',
|
|
});
|
|
this.data.partner.records[0].message_follower_ids.push(2);
|
|
|
|
// simulate concurrent calls to read_followers and check that those followers
|
|
// are not added twice in the dropdown
|
|
def = testUtils.makeTestPromise();
|
|
form.reload();
|
|
form.reload();
|
|
await def.resolve();
|
|
await testUtils.nextTick();
|
|
assert.strictEqual(form.$('.o_followers_count').text(), '2',
|
|
"should have 2 followers");
|
|
assert.containsN(form, '.o_followers_list .o_partner', 2,
|
|
"there should be 2 followers in the follower dropdown");
|
|
|
|
form.destroy();
|
|
});
|
|
|
|
QUnit.test('followers widget: display inactive followers with a different style', async function (assert) {
|
|
assert.expect(6);
|
|
|
|
this.data.partner.records[0].message_follower_ids = [1,2,3];
|
|
|
|
var followers = [{
|
|
id: 1,
|
|
name: "Admin",
|
|
email: "admin@example.com",
|
|
res_id: 101,
|
|
res_model: 'partner',
|
|
active: true,
|
|
},{
|
|
id: 2,
|
|
name: "Active_partner",
|
|
email: "active_partner@example.com",
|
|
res_id: 102,
|
|
res_model: 'partner',
|
|
active: true,
|
|
},{
|
|
id: 3,
|
|
name: "Inactive_partner",
|
|
email: "inactive_partner@example.com",
|
|
res_id: 103,
|
|
res_model: 'partner',
|
|
active: false,
|
|
}];
|
|
|
|
var form = await createView({
|
|
View: FormView,
|
|
model: 'partner',
|
|
data: this.data,
|
|
services: this.services,
|
|
arch: '<form>' +
|
|
'<div class="oe_chatter">' +
|
|
'<field name="message_follower_ids" widget="mail_followers"/>' +
|
|
'</div>' +
|
|
'</form>',
|
|
mockRPC: function (route, args) {
|
|
if (route === '/mail/read_followers') {
|
|
return Promise.resolve({
|
|
followers: _.filter(followers, function (follower) {
|
|
return _.contains(args.follower_ids, follower.id);
|
|
}),
|
|
});
|
|
}
|
|
return this._super.apply(this, arguments);
|
|
},
|
|
res_id: 2,
|
|
});
|
|
|
|
assert.doesNotHaveClass(form.$(".o_partner:has(a[data-oe-id='101'])"), 'o_inactive', 'Partner should be active');
|
|
assert.hasAttrValue(form.$(".o_partner:has(a[data-oe-id='101']) > a"),'title','Admin');
|
|
assert.doesNotHaveClass(form.$(".o_partner:has(a[data-oe-id='102'])"), 'o_inactive', 'Partner should be active');
|
|
assert.hasAttrValue(form.$(".o_partner:has(a[data-oe-id='102']) > a"),'title','Active_partner');
|
|
assert.hasClass(form.$(".o_partner:has(a[data-oe-id='103'])"), 'o_inactive', 'Partner should be inactive');
|
|
assert.hasAttrValue(form.$(".o_partner:has(a[data-oe-id='103']) > a"),'title','Inactive_partner \n(inactive)');
|
|
|
|
form.destroy();
|
|
});
|
|
|
|
QUnit.test('does not render and crash when destroyed before chat system is ready', async function (assert) {
|
|
assert.expect(0);
|
|
|
|
var def = testUtils.makeTestPromise();
|
|
|
|
var form = await createView({
|
|
View: FormView,
|
|
model: 'partner',
|
|
data: this.data,
|
|
services: this.services,
|
|
arch: '<form string="Partners">' +
|
|
'<sheet>' +
|
|
'<field name="foo"/>' +
|
|
'</sheet>' +
|
|
'<div class="oe_chatter">' +
|
|
'<field name="message_follower_ids" widget="mail_followers"/>' +
|
|
'<field name="message_ids" widget="mail_thread"/>' +
|
|
'<field name="activity_ids" widget="mail_activity"/>' +
|
|
'</div>' +
|
|
'</form>',
|
|
res_id: 2,
|
|
mockRPC: function (route, args) {
|
|
if (args.method === 'message_format') {
|
|
return Promise.resolve([{
|
|
attachment_ids: [],
|
|
body: "",
|
|
date: "2016-12-20 09:35:40",
|
|
id: 34,
|
|
res_id: 3,
|
|
author_id: ["3", "Fu Ck Mil Grom"],
|
|
}]);
|
|
}
|
|
if (route === '/mail/read_followers') {
|
|
return Promise.resolve({
|
|
followers: [],
|
|
subtypes: [],
|
|
});
|
|
}
|
|
return this._super(route, args);
|
|
},
|
|
intercepts: {
|
|
get_session: function (event) {
|
|
event.stopPropagation();
|
|
event.data.callback({uid: 1, origin: 'http://web'});
|
|
},
|
|
},
|
|
});
|
|
|
|
form.destroy();
|
|
// here, the chat service system is ready, and the chatter can try to render
|
|
// itself. We simply make sure here that no crashes occur (since the form
|
|
// view is destroyed, all rpcs will be dropped, and many other mechanisms
|
|
// relying on events will not work, such as the chat bus)
|
|
await def.resolve();
|
|
});
|
|
|
|
QUnit.test('chatter: do not duplicate messages on (un)star message', async function (assert) {
|
|
assert.expect(4);
|
|
|
|
this.data.partner.records[0].message_ids = [1];
|
|
this.data['mail.message'].records = [{
|
|
author_id: ["1", "John Doe"],
|
|
body: "A message",
|
|
date: "2016-12-20 09:35:40",
|
|
id: 1,
|
|
is_note: false,
|
|
is_discussion: true,
|
|
is_notification: false,
|
|
is_starred: false,
|
|
model: 'partner',
|
|
res_id: 2,
|
|
}];
|
|
|
|
var form = await createView({
|
|
View: FormView,
|
|
model: 'partner',
|
|
data: this.data,
|
|
services: this.services,
|
|
arch: '<form string="Partners">' +
|
|
'<sheet>' +
|
|
'<field name="foo"/>' +
|
|
'</sheet>' +
|
|
'<div class="oe_chatter">' +
|
|
'<field name="message_ids" widget="mail_thread" options="{\'display_log_button\': True}"/>' +
|
|
'</div>' +
|
|
'</form>',
|
|
res_id: 2,
|
|
mockRPC: function (route, args) {
|
|
if (args.method === 'toggle_message_starred') {
|
|
var messageData = _.findWhere(
|
|
this.data['mail.message'].records,
|
|
{ id: args.args[0][0] }
|
|
);
|
|
messageData.is_starred = !messageData.is_starred;
|
|
// simulate notification received by mail_service from longpoll
|
|
var data = {
|
|
info: false,
|
|
message_ids: [messageData.id],
|
|
starred: messageData.is_starred,
|
|
type: 'toggle_star',
|
|
};
|
|
var notification = [[false, 'res.partner'], data];
|
|
form.call('bus_service', 'trigger', 'notification', [notification]);
|
|
return Promise.resolve();
|
|
}
|
|
return this._super(route, args);
|
|
},
|
|
session: {},
|
|
});
|
|
|
|
assert.containsOnce(form, '.o_thread_message',
|
|
"there should be a single message in the chatter");
|
|
assert.ok(form.$('.o_thread_message .o_thread_message_star.fa-star-o').length,
|
|
"message should not be starred");
|
|
|
|
// star message
|
|
await testUtils.dom.click(form.$('.o_thread_message .o_thread_message_star'));
|
|
assert.containsOnce(form, '.o_thread_message',
|
|
"there should still be a single message in the chatter after starring the message");
|
|
|
|
// unstar message
|
|
await testUtils.dom.click(form.$('.o_thread_message .o_thread_message_star'));
|
|
assert.containsOnce(form, '.o_thread_message',
|
|
"there should still be a single message in the chatter after unstarring the message");
|
|
|
|
//cleanup
|
|
form.destroy();
|
|
});
|
|
|
|
QUnit.test('test open attachment option', async function (assert) {
|
|
assert.expect(5);
|
|
|
|
const form = await createView({
|
|
View: FormView,
|
|
model: 'partner',
|
|
data: this.data,
|
|
services: this.services,
|
|
res_id: 2,
|
|
arch: `
|
|
<form string="Partners">
|
|
<sheet>
|
|
<field name="foo"/>
|
|
</sheet>
|
|
<div class="oe_chatter">
|
|
<field name="message_ids" widget="mail_thread" options="{'open_attachments': True}"/>
|
|
</div>
|
|
</form>`,
|
|
});
|
|
assert.containsOnce(form, '.o_mail_chatter_attachments', "Attachment box should be open by default")
|
|
await testUtils.form.clickEdit(form);
|
|
assert.containsOnce(form, '.o_mail_chatter_attachments', "Attachment box should still be open")
|
|
await testUtils.fields.editInput(form.$('.o_field_char'), 'Coucou Petite Perruche');
|
|
assert.containsOnce(form, '.o_mail_chatter_attachments', "Attachment box should still be open")
|
|
|
|
// Close the attachment box with the button
|
|
await testUtils.dom.click(form.$('.o_chatter_button_attachment'))
|
|
assert.containsNone(form, '.o_mail_chatter_attachments', "Attachment box should be closed")
|
|
await testUtils.form.clickSave(form);
|
|
assert.containsNone(form, '.o_mail_chatter_attachments', "Attachment box should still be closed")
|
|
|
|
form.destroy();
|
|
});
|
|
|
|
QUnit.test('chatter: new messages on document without any "display_name"', async function (assert) {
|
|
assert.expect(5);
|
|
|
|
this.data.partner.records[0].message_ids = [1];
|
|
this.data.partner.records[0].display_name = false;
|
|
this.data['mail.message'].records = [{
|
|
author_id: [1, "John Doe"],
|
|
body: "A message",
|
|
date: "2016-12-20 09:35:40",
|
|
id: 1,
|
|
is_note: false,
|
|
is_discussion: true,
|
|
is_notification: false,
|
|
is_starred: false,
|
|
model: 'partner',
|
|
res_id: 2,
|
|
}];
|
|
|
|
var form = await createView({
|
|
View: FormView,
|
|
model: 'partner',
|
|
data: this.data,
|
|
services: this.services,
|
|
arch: '<form string="Partners">' +
|
|
'<sheet>' +
|
|
'<field name="foo"/>' +
|
|
'</sheet>' +
|
|
'<div class="oe_chatter">' +
|
|
'<field name="message_ids" widget="mail_thread" options="{\'display_log_button\': True}"/>' +
|
|
'</div>' +
|
|
'</form>',
|
|
res_id: 2,
|
|
session: {},
|
|
});
|
|
|
|
assert.containsOnce(form, '.o_thread_message',
|
|
"should have a single message in the chatter");
|
|
assert.containsOnce(form, '.o_thread_message[data-message-id="1"]',
|
|
"single message should have ID 1");
|
|
|
|
// Simulate a new message in the chatter
|
|
this.data['mail.message'].records.push({
|
|
author_id: [2, "Mister Smith"],
|
|
body: "Second message",
|
|
date: "2016-12-20 09:35:40",
|
|
id: 2,
|
|
is_note: false,
|
|
is_discussion: true,
|
|
is_notification: false,
|
|
is_starred: false,
|
|
model: 'partner',
|
|
res_id: 2,
|
|
});
|
|
this.data.partner.records[0].message_ids.push(2);
|
|
|
|
await form.reload();
|
|
|
|
assert.containsN(form, '.o_thread_message', 2,
|
|
"should have a two messages in the chatter after reload");
|
|
assert.containsOnce(form, '.o_thread_message[data-message-id="1"]',
|
|
"one of the message should have ID 1");
|
|
assert.containsOnce(form, '.o_thread_message[data-message-id="2"]',
|
|
"the other message should have ID 2");
|
|
|
|
//cleanup
|
|
form.destroy();
|
|
});
|
|
|
|
QUnit.test('chatter: suggested partner auto-follow on message post', async function (assert) {
|
|
// need post_refresh 'recipient' to auto-follow suggested recipients
|
|
// whose checkbox is checked.
|
|
assert.expect(20);
|
|
|
|
var self = this;
|
|
this.data.partner.records[0].message_follower_ids = [1];
|
|
this.data.partner.records[0].message_ids = [1];
|
|
this.data['mail.message'].records = [{
|
|
author_id: ["1", "John Doe"],
|
|
body: "A message",
|
|
date: "2016-12-20 09:35:40",
|
|
id: 1,
|
|
is_note: false,
|
|
is_discussion: true,
|
|
is_notification: false,
|
|
is_starred: false,
|
|
model: 'partner',
|
|
res_id: 2,
|
|
}];
|
|
|
|
var followers = [];
|
|
followers.push({
|
|
id: 1,
|
|
is_uid: true,
|
|
name: "Admin",
|
|
email: "admin@example.com",
|
|
res_id: 5,
|
|
res_model: 'partner',
|
|
});
|
|
|
|
var form = await createView({
|
|
View: FormView,
|
|
model: 'partner',
|
|
data: this.data,
|
|
services: this.services,
|
|
arch: '<form string="Partners">' +
|
|
'<sheet>' +
|
|
'<field name="foo"/>' +
|
|
'</sheet>' +
|
|
'<div class="oe_chatter">' +
|
|
'<field name="message_follower_ids" widget="mail_followers"/>' +
|
|
'<field name="message_ids" widget="mail_thread" options="{\'post_refresh\': \'recipients\'}"/>' +
|
|
'</div>' +
|
|
'</form>',
|
|
res_id: 2,
|
|
mockRPC: function (route, args) {
|
|
if (route === '/mail/get_suggested_recipients') {
|
|
return Promise.resolve({2: [
|
|
[
|
|
8,
|
|
'DemoUser <demo-user@example.com>',
|
|
'Customer Email',
|
|
],
|
|
]
|
|
});
|
|
}
|
|
if (args.method === 'message_post') {
|
|
assert.ok(args.kwargs.context.mail_post_autofollow,
|
|
"should autofollow checked suggested partners when posting message");
|
|
assert.deepEqual(args.kwargs.partner_ids, [8],
|
|
"should have provided Demo User to auto-follow chatter on message_post");
|
|
|
|
// add demo user in followers
|
|
self.data.partner.records[0].message_follower_ids.push(2);
|
|
followers.push({
|
|
id: 2,
|
|
is_uid: true,
|
|
name: "Demo User",
|
|
email: "demo-user@example.com",
|
|
res_id: 8,
|
|
res_model: 'partner',
|
|
});
|
|
|
|
// post a legit message so that it does not crashes
|
|
var lastMessageData = _.max(this.data['mail.message'].records, function (messageData) {
|
|
return messageData.id;
|
|
});
|
|
var messageID = lastMessageData.id + 1;
|
|
this.data['mail.message'].records.push({
|
|
author_id: ["42", "Me"],
|
|
body: args.kwargs.body,
|
|
date: "2016-12-20 10:35:40",
|
|
id: messageID,
|
|
is_note: args.kwargs.subtype === 'mail.mt_note',
|
|
is_discussion: args.kwargs.subtype === 'mail.mt_comment',
|
|
is_notification: false,
|
|
is_starred: false,
|
|
model: 'partner',
|
|
res_id: 2,
|
|
});
|
|
return Promise.resolve(messageID);
|
|
}
|
|
if (route === '/mail/read_followers') {
|
|
return Promise.resolve({
|
|
followers: followers,
|
|
});
|
|
}
|
|
return this._super(route, args);
|
|
},
|
|
session: {},
|
|
});
|
|
|
|
assert.containsOnce(form, '.o_thread_message', "thread should contain one message");
|
|
assert.ok(form.$('.o_thread_message:first().o_mail_discussion').length,
|
|
"the message should be a discussion");
|
|
assert.ok(form.$('.o_thread_message:first() .o_thread_message_core').text().indexOf('A message') >= 0,
|
|
"the message's body should be correct");
|
|
assert.ok(form.$('.o_thread_message:first() .o_mail_info').text().indexOf('John Doe') >= 0,
|
|
"the message's author should be correct");
|
|
assert.containsOnce(form, '.o_followers',
|
|
"should display follower widget");
|
|
assert.strictEqual(form.$('.o_followers_count').text(), "1",
|
|
"should have a single follower (widget counter)");
|
|
assert.containsOnce(form, '.o_followers_list > div.o_partner',
|
|
"should have a single follower (listed partners)");
|
|
assert.strictEqual(form.$('.o_followers_list > div.o_partner > a').text().trim(), "Admin",
|
|
"should have 'Admin' as follower");
|
|
|
|
// open composer
|
|
await testUtils.dom.click(form.$('.o_chatter_button_new_message'));
|
|
assert.isVisible($('.oe_chatter .o_thread_composer'), "chatter should be opened");
|
|
assert.strictEqual($('.o_composer_suggested_partners').length, 1,
|
|
"should display suggested partners");
|
|
assert.strictEqual($('.o_composer_suggested_partners > div').length, 1,
|
|
"should display 1 suggested partner");
|
|
assert.ok($('.o_composer_suggested_partners input').is(':checked'),
|
|
"should have checkbox that is checked by default");
|
|
assert.strictEqual($('.o_composer_suggested_partners input').data('fullname'),
|
|
"DemoUser <demo-user@example.com>",
|
|
"should have partner suggestion with correct fullname (data)");
|
|
assert.strictEqual($('.o_composer_suggested_partners label').text().replace(/\s+/g, ''),
|
|
"DemoUser(demo-user@example.com)",
|
|
"should have partner suggestion with correct fullname (rendering)");
|
|
|
|
// send message
|
|
form.$('.oe_chatter .o_composer_text_field:first()').val("My first message");
|
|
await testUtils.dom.click(form.$('.oe_chatter .o_composer_button_send'));
|
|
|
|
assert.strictEqual(form.$('.o_followers_count').text(), "2",
|
|
"should have a two followers (widget counter)");
|
|
assert.containsN(form, '.o_followers_list > div.o_partner', 2,
|
|
"should have two followers (listed partners)");
|
|
assert.strictEqual(form.$('.o_followers_list > div.o_partner > a[data-oe-id="5"]').text().trim(),
|
|
"Admin",
|
|
"should have 'Admin' as follower");
|
|
assert.strictEqual(form.$('.o_followers_list > div.o_partner > a[data-oe-id="8"]').text().trim(),
|
|
"Demo User",
|
|
"should have 'Demo User' as follower");
|
|
|
|
//cleanup
|
|
form.destroy();
|
|
});
|
|
|
|
QUnit.test('chatter: mention prefetched partners (followers & employees)', async function (assert) {
|
|
// Note: employees are in prefeteched partner for mentions in chatter when
|
|
// the module hr is installed.
|
|
assert.expect(10);
|
|
|
|
var followerSuggestions = [{
|
|
id: 1,
|
|
name: 'FollowerUser1',
|
|
email: 'follower-user1@example.com',
|
|
}, {
|
|
id: 2,
|
|
name: 'FollowerUser2',
|
|
email: 'follower-user2@example.com',
|
|
}];
|
|
|
|
var nonFollowerSuggestions = [{
|
|
id: 3,
|
|
name: 'NonFollowerUser1',
|
|
email: 'non-follower-user1@example.com',
|
|
}, {
|
|
id: 4,
|
|
name: 'NonFollowerUser2',
|
|
email: 'non-follower-user2@example.com',
|
|
}];
|
|
|
|
// link followers
|
|
this.data.partner.records[0].message_follower_ids = [10, 20];
|
|
|
|
// prefetched partners
|
|
this.data.initMessaging = {
|
|
mention_partner_suggestions: [followerSuggestions.concat(nonFollowerSuggestions)],
|
|
};
|
|
|
|
var form = await createView({
|
|
View: FormView,
|
|
model: 'partner',
|
|
data: this.data,
|
|
services: this.services,
|
|
arch: '<form string="Partners">' +
|
|
'<sheet>' +
|
|
'<field name="foo"/>' +
|
|
'</sheet>' +
|
|
'<div class="oe_chatter">' +
|
|
'<field name="message_follower_ids" widget="mail_followers"/>' +
|
|
'<field name="message_ids" widget="mail_thread" options="{\'display_log_button\': True}"/>' +
|
|
'</div>' +
|
|
'</form>',
|
|
res_id: 2,
|
|
mockRPC: function (route, args) {
|
|
if (route === '/mail/read_followers') {
|
|
return Promise.resolve({
|
|
followers: [{
|
|
id: 10,
|
|
name: 'FollowerUser1',
|
|
email: 'follower-user1@example.com',
|
|
res_model: 'res.partner',
|
|
res_id: 1,
|
|
}, {
|
|
id: 20,
|
|
name: 'FollowerUser2',
|
|
email: 'follower-user2@example.com',
|
|
res_model: 'res.partner',
|
|
res_id: 2,
|
|
}],
|
|
subtypes: [],
|
|
});
|
|
}
|
|
if (route === '/mail/get_suggested_recipients') {
|
|
return Promise.resolve({2: []});
|
|
}
|
|
if (args.method === 'get_mention_suggestions') {
|
|
throw new Error('should not fetch partners for mentions');
|
|
}
|
|
return this._super(route, args);
|
|
},
|
|
session: {},
|
|
});
|
|
|
|
assert.strictEqual(form.$('.o_followers_count').text(), '2',
|
|
"should have two followers of this document");
|
|
assert.strictEqual(form.$('.o_followers_list > .o_partner').text().replace(/\s+/g, ''),
|
|
'FollowerUser1FollowerUser2',
|
|
"should have correct follower names");
|
|
assert.strictEqual(form.$('.o_composer_mention_dropdown').length, 0,
|
|
"should not show the mention suggestion dropdown");
|
|
|
|
await testUtils.dom.click(form.$('.o_chatter_button_new_message'));
|
|
var $input = form.$('.oe_chatter .o_composer_text_field:first()');
|
|
$input.val('@');
|
|
// the cursor position must be set for the mention manager to detect that we are mentionning
|
|
$input[0].selectionStart = 1;
|
|
$input[0].selectionEnd = 1;
|
|
$input.trigger('keyup');
|
|
await testUtils.nextTick();
|
|
assert.strictEqual(form.$('.o_composer_mention_dropdown').length, 1,
|
|
"should show the mention suggestion dropdown");
|
|
|
|
assert.strictEqual(form.$('.o_mention_proposition').length, 4,
|
|
"should show 4 mention suggestions");
|
|
assert.strictEqual(form.$('.o_mention_proposition').eq(0).text().replace(/\s+/g, ''),
|
|
"FollowerUser1(follower-user1@example.com)",
|
|
"should display correct 1st mention suggestion");
|
|
assert.strictEqual(form.$('.o_mention_proposition').eq(1).text().replace(/\s+/g, ''),
|
|
"FollowerUser2(follower-user2@example.com)",
|
|
"should display correct 2nd mention suggestion");
|
|
assert.ok(form.$('.o_mention_proposition').eq(1).next().hasClass('dropdown-divider'),
|
|
"should have a mention separator after last follower mention suggestion");
|
|
assert.strictEqual(form.$('.o_mention_proposition').eq(2).text().replace(/\s+/g, ''),
|
|
"NonFollowerUser1(non-follower-user1@example.com)",
|
|
"should display correct 3rd mention suggestion");
|
|
assert.strictEqual(form.$('.o_mention_proposition').eq(3).text().replace(/\s+/g, ''),
|
|
"NonFollowerUser2(non-follower-user2@example.com)",
|
|
"should display correct 4th mention suggestion");
|
|
|
|
//cleanup
|
|
form.destroy();
|
|
});
|
|
|
|
QUnit.test('chatter: display suggested partners only once', async function (assert) {
|
|
assert.expect(3);
|
|
|
|
let isMessageSent = false;
|
|
|
|
const form = await createView({
|
|
View: FormView,
|
|
model: 'partner',
|
|
data: this.data,
|
|
services: this.services,
|
|
arch: `
|
|
<form string="Partners">
|
|
<sheet>
|
|
<field name="foo"/>
|
|
</sheet>
|
|
<div class="oe_chatter">
|
|
<field name="message_follower_ids" widget="mail_followers"/>
|
|
<field name="message_ids" widget="mail_thread" options="{'display_log_button': True}"/>
|
|
</div>
|
|
</form>`,
|
|
res_id: 2,
|
|
async mockRPC(route, args) {
|
|
if (route === '/mail/read_followers') {
|
|
return {
|
|
followers: [],
|
|
subtypes: [],
|
|
};
|
|
}
|
|
if (route === '/mail/get_suggested_recipients') {
|
|
if (isMessageSent) {
|
|
return {
|
|
2: [],
|
|
};
|
|
}
|
|
return {
|
|
2: [[2, "Jack <jack@example.com>", "Partner Profile"]],
|
|
};
|
|
}
|
|
if (args.method === 'message_post') {
|
|
isMessageSent = true;
|
|
return 57923;
|
|
}
|
|
if (args.method === 'message_format') {
|
|
return [{
|
|
author_id: ["42", "Me"],
|
|
model: 'partner',
|
|
}];
|
|
}
|
|
return this._super(route, args);
|
|
},
|
|
session: {},
|
|
});
|
|
|
|
await testUtils.dom.click(form.$('.o_chatter_button_new_message'));
|
|
const $input = form.$('.oe_chatter .o_composer_text_field:first()');
|
|
assert.containsOnce(
|
|
form,
|
|
'div.o_composer_suggested_partners input',
|
|
"should show one suggested recipient");
|
|
assert.strictEqual(
|
|
form.$('div.o_composer_suggested_partners label').text().replace(/\s+/g, ''),
|
|
"Jack(jack@example.com)",
|
|
"should have the correct label");
|
|
|
|
$input.val("BBQ and beers tonight! Are you in?");
|
|
await testUtils.dom.click(form.$('.o_composer_button_send'));
|
|
// Open composer for new message
|
|
await testUtils.dom.click(form.$('.o_chatter_button_new_message'));
|
|
assert.containsNone(
|
|
form,
|
|
'div.o_composer_suggested_partners input',
|
|
"should no longer show the suggested recipient");
|
|
|
|
form.destroy();
|
|
});
|
|
|
|
QUnit.module('FieldMany2ManyTagsEmail', {
|
|
beforeEach: function () {
|
|
this.data = {
|
|
partner: {
|
|
fields: {
|
|
display_name: { string: "Displayed name", type: "char" },
|
|
timmy: { string: "pokemon", type: "many2many", relation: 'partner_type'},
|
|
},
|
|
records: [{
|
|
id: 1,
|
|
display_name: "first record",
|
|
timmy: [],
|
|
}],
|
|
},
|
|
partner_type: {
|
|
fields: {
|
|
name: {string: "Partner Type", type: "char"},
|
|
email: {string: "Email", type: "char"},
|
|
},
|
|
records: [
|
|
{id: 12, display_name: "gold", email: 'coucou@petite.perruche'},
|
|
{id: 14, display_name: "silver", email: ''},
|
|
]
|
|
},
|
|
};
|
|
},
|
|
});
|
|
|
|
QUnit.test('fieldmany2many tags email', function (assert) {
|
|
assert.expect(13);
|
|
var done = assert.async();
|
|
|
|
this.data.partner.records[0].timmy = [12, 14];
|
|
|
|
// the modals need to be closed before the form view rendering
|
|
createView({
|
|
View: FormView,
|
|
model: 'partner',
|
|
data: this.data,
|
|
res_id: 1,
|
|
arch:'<form string="Partners">' +
|
|
'<sheet>' +
|
|
'<field name="display_name"/>' +
|
|
'<field name="timmy" widget="many2many_tags_email"/>' +
|
|
'</sheet>' +
|
|
'</form>',
|
|
viewOptions: {
|
|
mode: 'edit',
|
|
},
|
|
mockRPC: function (route, args) {
|
|
if (args.method ==='read' && args.model === 'partner_type') {
|
|
assert.step(JSON.stringify(args.args[0]));
|
|
assert.deepEqual(args.args[1] , ['display_name', 'email'], "should read the email");
|
|
}
|
|
return this._super.apply(this, arguments);
|
|
},
|
|
archs: {
|
|
'partner_type,false,form': '<form string="Types"><field name="display_name"/><field name="email"/></form>',
|
|
},
|
|
}).then(async function (form) {
|
|
// should read it 3 times (1 with the form view, one with the form dialog and one after save)
|
|
assert.verifySteps(['[12,14]', '[14]', '[14]']);
|
|
await testUtils.nextTick();
|
|
assert.containsN(form, '.o_field_many2manytags[name="timmy"] .badge.o_tag_color_0', 2,
|
|
"two tags should be present");
|
|
var firstTag = form.$('.o_field_many2manytags[name="timmy"] .badge.o_tag_color_0').first();
|
|
assert.strictEqual(firstTag.find('.o_badge_text').text(), "gold",
|
|
"tag should only show display_name");
|
|
assert.hasAttrValue(firstTag.find('.o_badge_text'), 'title', "coucou@petite.perruche",
|
|
"tag should show email address on mouse hover");
|
|
form.destroy();
|
|
done();
|
|
});
|
|
testUtils.nextTick().then(function() {
|
|
assert.strictEqual($('.modal-body.o_act_window').length, 1,
|
|
"there should be one modal opened to edit the empty email");
|
|
assert.strictEqual($('.modal-body.o_act_window input[name="display_name"]').val(), "silver",
|
|
"the opened modal should be a form view dialog with the partner_type 14");
|
|
assert.strictEqual($('.modal-body.o_act_window input[name="email"]').length, 1,
|
|
"there should be an email field in the modal");
|
|
|
|
// set the email and save the modal (will render the form view)
|
|
testUtils.fields.editInput($('.modal-body.o_act_window input[name="email"]'), 'coucou@petite.perruche');
|
|
testUtils.dom.click($('.modal-footer .btn-primary'));
|
|
});
|
|
|
|
});
|
|
|
|
QUnit.test('fieldmany2many tags email (edition)', async function (assert) {
|
|
assert.expect(15);
|
|
|
|
this.data.partner.records[0].timmy = [12];
|
|
|
|
var form = await createView({
|
|
View: FormView,
|
|
model: 'partner',
|
|
data: this.data,
|
|
res_id: 1,
|
|
arch:'<form string="Partners">' +
|
|
'<sheet>' +
|
|
'<field name="display_name"/>' +
|
|
'<field name="timmy" widget="many2many_tags_email"/>' +
|
|
'</sheet>' +
|
|
'</form>',
|
|
viewOptions: {
|
|
mode: 'edit',
|
|
},
|
|
mockRPC: function (route, args) {
|
|
if (args.method ==='read' && args.model === 'partner_type') {
|
|
assert.step(JSON.stringify(args.args[0]));
|
|
assert.deepEqual(args.args[1] , ['display_name', 'email'], "should read the email");
|
|
}
|
|
return this._super.apply(this, arguments);
|
|
},
|
|
archs: {
|
|
'partner_type,false,form': '<form string="Types"><field name="display_name"/><field name="email"/></form>',
|
|
},
|
|
});
|
|
|
|
assert.verifySteps(['[12]']);
|
|
assert.containsOnce(form, '.o_field_many2manytags[name="timmy"] .badge.o_tag_color_0',
|
|
"should contain one tag");
|
|
|
|
// add an other existing tag
|
|
await testUtils.fields.many2one.clickOpenDropdown('timmy');
|
|
await testUtils.fields.many2one.clickHighlightedItem('timmy');
|
|
|
|
assert.strictEqual($('.modal-body.o_act_window').length, 1,
|
|
"there should be one modal opened to edit the empty email");
|
|
assert.strictEqual($('.modal-body.o_act_window input[name="display_name"]').val(), "silver",
|
|
"the opened modal in edit mode should be a form view dialog with the partner_type 14");
|
|
assert.strictEqual($('.modal-body.o_act_window input[name="email"]').length, 1,
|
|
"there should be an email field in the modal");
|
|
|
|
// set the email and save the modal (will rerender the form view)
|
|
await testUtils.fields.editInput($('.modal-body.o_act_window input[name="email"]'), 'coucou@petite.perruche');
|
|
await testUtils.dom.click($('.modal-footer .btn-primary'));
|
|
|
|
assert.containsN(form, '.o_field_many2manytags[name="timmy"] .badge.o_tag_color_0', 2,
|
|
"should contain the second tag");
|
|
// should have read [14] three times: when opening the dropdown, when opening the modal, and
|
|
// after the save
|
|
assert.verifySteps(['[14]', '[14]', '[14]']);
|
|
|
|
form.destroy();
|
|
});
|
|
|
|
});
|
|
});
|