flectra/addons/mail/static/tests/chatter_tests.js

1492 lines
59 KiB
JavaScript

flectra.define('mail.chatter_tests', function (require) {
"use strict";
var Composers = require('mail.composer');
var Bus = require('web.Bus');
var concurrency = require('web.concurrency');
var FormView = require('web.FormView');
var KanbanView = require('web.KanbanView');
var testUtils = require('web.test_utils');
var BasicComposer = Composers.BasicComposer;
var createAsyncView = testUtils.createAsyncView;
var createView = testUtils.createView;
QUnit.module('mail', {}, function () {
QUnit.module('Chatter', {
beforeEach: function () {
this.data = {
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_state: {
string: 'State',
type: 'selection',
selection: [['overdue', 'Overdue'], ['today', 'Today'], ['planned', 'Planned']],
},
},
records: [{
id: 2,
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: "Assigned to", type: "many2one", relation: 'partner' },
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']],
},
},
},
'mail.activity.type': {
fields: {
name: { string: "Name", type: "char" },
},
records: [
{ id: 1, name: "Type 1" },
{ id: 2, name: "Type 2" },
],
}
};
}
});
QUnit.test('basic rendering', function (assert) {
assert.expect(8);
var count = 0;
var unwanted_read_count = 0;
// var msgRpc = 0;
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
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 ('/web/dataset/call_kw/mail.followers/read' === route) {
unwanted_read_count++;
}
if (route === '/mail/read_followers') {
count++;
return $.when({
followers: [],
subtypes: [],
});
}
return this._super(route, args);
},
intercepts: {
get_messages: function (event) {
// msgRpc++;
event.stopPropagation();
event.data.callback($.when([{
attachment_ids: [],
body: "",
date: moment("2016-12-20 09:35:40"),
id: 34,
res_id: 3,
author_id: ["3", "Fu Ck Mil Grom"],
}]));
},
get_bus: function (event) {
event.stopPropagation();
event.data.callback(new Bus());
},
get_session: function (event) {
event.stopPropagation();
event.data.callback({uid: 1});
},
},
});
assert.ok(form.$('.o_mail_activity').length, "there should be an activity widget");
assert.ok(form.$('.o_chatter_topbar .o_chatter_button_schedule_activity').length,
"there should be a 'Schedule an activity' button in the chatter's topbar");
assert.ok(form.$('.o_chatter_topbar .o_followers').length,
"there should be a followers widget, moved inside the chatter's topbar");
assert.ok(form.$('.o_chatter').length, "there should be a chatter widget");
assert.ok(form.$('.o_mail_thread').length, "there should be a mail thread");
assert.ok(!form.$('.o_chatter_topbar .o_chatter_button_log_note').length,
"log note button should not be available");
// assert.strictEqual(msgRpc, 1, "should have fetched messages once");
form.$buttons.find('.o_form_button_edit').click();
// assert.strictEqual(msgRpc, 1, "should still have fetched messages only once");
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('chatter is not rendered in mode === create', function (assert) {
assert.expect(4);
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
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 === "/web/dataset/call_kw/partner/message_get_suggested_recipients") {
return $.when({2: []});
}
return this._super(route, args);
},
intercepts: {
get_messages: function (event) {
event.stopPropagation();
event.data.callback($.when([]));
},
get_bus: function (event) {
event.stopPropagation();
event.data.callback(new Bus());
},
},
});
assert.strictEqual(form.$('.o_chatter').length, 1,
"chatter should be displayed");
form.$buttons.find('.o_form_button_create').click();
assert.strictEqual(form.$('.o_chatter').length, 0,
"chatter should not be displayed");
form.$('.o_field_char').val('coucou').trigger('input');
form.$buttons.find('.o_form_button_save').click();
assert.strictEqual(form.$('.o_chatter').length, 1,
"chatter should be displayed");
// check if chatter buttons still work
form.$('.o_chatter_button_new_message').click();
assert.strictEqual(form.$('.o_chat_composer:visible').length, 1,
"chatter should be opened");
form.destroy();
});
QUnit.test('chatter rendering inside the sheet', function (assert) {
assert.expect(4);
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
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 === "/web/dataset/call_kw/partner/message_get_suggested_recipients") {
return $.when({2: []});
}
return this._super(route, args);
},
intercepts: {
get_messages: function (event) {
event.stopPropagation();
event.data.callback($.when([]));
},
get_bus: function (event) {
event.stopPropagation();
event.data.callback(new Bus());
},
},
});
assert.strictEqual(form.$('.o_chatter').length, 1,
"chatter should be displayed");
form.$buttons.find('.o_form_button_create').click();
assert.strictEqual(form.$('.o_chatter').length, 0,
"chatter should not be displayed");
form.$('.o_field_char').val('coucou').trigger('input');
form.$buttons.find('.o_form_button_save').click();
assert.strictEqual(form.$('.o_chatter').length, 1,
"chatter should be displayed");
// check if chatter buttons still work
form.$('.o_chatter_button_new_message').click();
assert.strictEqual(form.$('.o_chat_composer:visible').length, 1,
"chatter should be opened");
form.destroy();
});
QUnit.test('kanban activity widget with no activity', function (assert) {
assert.expect(4);
var rpcCount = 0;
var kanban = 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
$record.find('.o_activity_btn').click();
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', function (assert) {
assert.expect(11);
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
state: "today",
user_id: 2,
activity_type_id: 1,
}];
var rpcCount = 0;
var kanban = 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_state = false;
return $.when();
}
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');
// click on the activity button
$record.find('.o_activity_btn').click();
assert.strictEqual(rpcCount, 2, 'a read should have been done to fetch the activity details');
assert.strictEqual($record.find('.o_activity_title').length, 1, "should have an activity scheduled");
var label_text = $record.find('.o_activity_label .o_activity_color_today').text();
assert.ok(label_text.indexOf('Today (1)') >= 0, "should display the correct label and count");
// click on the activity button to close the dropdown
$record.find('.o_activity_btn').click();
assert.strictEqual(rpcCount, 2, 'no RPC should be done when closing the dropdown');
// click on the activity button to re-open dropdown
$record.find('.o_activity_btn').click();
assert.strictEqual(rpcCount, 3, 'should have reloaded the activities');
// mark activity as done
$record.find('.o_mark_as_done').click();
$record = kanban.$('.o_kanban_record').first(); // the record widget has been reset
assert.strictEqual(rpcCount, 5, '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.open').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('chatter: post, receive and star messages', function (assert) {
var done = assert.async();
assert.expect(27);
// Remove the mention throttle to speed up the test
var mentionThrottle = BasicComposer.prototype.MENTION_THROTTLE;
BasicComposer.prototype.MENTION_THROTTLE = 1;
this.data.partner.records[0].message_ids = [1];
var messages = [{
attachment_ids: [],
author_id: ["1", "John Doe"],
body: "A message",
date: moment("2016-12-20 09:35:40"),
displayed_author: "John Doe",
id: 1,
is_note: false,
is_starred: false,
model: 'partner',
res_id: 2,
}];
var bus = new Bus();
var getSuggestionsDef = $.Deferred();
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
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 === 'message_get_suggested_recipients') {
return $.when({2: []});
}
if (args.method === 'get_mention_suggestions') {
getSuggestionsDef.resolve();
return $.when([{email: "test@flectrahq.com", id: 1, name: "Test User"}]);
}
return this._super(route, args);
},
session: {},
intercepts: {
get_messages: function (event) {
event.stopPropagation();
var requested_msgs = _.filter(messages, function (msg) {
return _.contains(event.data.options.ids, msg.id);
});
event.data.callback($.when(requested_msgs));
},
post_message: function (event) {
event.stopPropagation();
var msg_id = messages[messages.length-1].id + 1;
messages.push({
attachment_ids: [],
author_id: ["42", "Me"],
body: event.data.message.content,
date: moment(), // now
displayed_author: "Me",
id: msg_id,
is_note: event.data.message.subtype === 'mail.mt_note',
is_starred: false,
model: 'partner',
res_id: 2,
});
bus.trigger('new_message', {
id: msg_id,
model: event.data.options.model,
res_id: event.data.options.res_id,
});
},
get_bus: function (event) {
event.stopPropagation();
event.data.callback(bus);
},
toggle_star_status: function (event) {
event.stopPropagation();
assert.strictEqual(event.data.message_id, 2,
"toggle_star_status should have been triggered for message 2 (twice)");
var msg = _.findWhere(messages, {id: event.data.message_id});
msg.is_starred = !msg.is_starred;
bus.trigger('update_message', msg);
},
},
});
assert.ok(form.$('.o_chatter_topbar .o_chatter_button_log_note').length,
"log note button should be available");
assert.strictEqual(form.$('.o_thread_message').length, 1, "thread should contain one message");
assert.ok(!form.$('.o_thread_message:first() .o_mail_note').length,
"the message shouldn't be a note");
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
form.$('.o_chatter_button_new_message').click();
assert.ok(!$('.oe_chatter .o_chat_composer').hasClass('o_hidden'), "chatter should be opened");
form.$('.oe_chatter .o_composer_text_field:first()').val("My first message");
form.$('.oe_chatter .o_composer_button_send').click();
assert.ok($('.oe_chatter .o_chat_composer').hasClass('o_hidden'), "chatter should be closed");
assert.strictEqual(form.$('.o_thread_message').length, 2, "thread should contain two messages");
assert.ok(!form.$('.o_thread_message:first() .o_mail_note').length,
"the last message shouldn't be a note");
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
form.$('.o_chatter_button_log_note').click();
assert.ok(!$('.oe_chatter .o_chat_composer').hasClass('o_hidden'), "chatter should be opened");
form.$('.oe_chatter .o_composer_text_field:first()').val("My first note");
form.$('.oe_chatter .o_composer_button_send').click();
assert.ok($('.oe_chatter .o_chat_composer').hasClass('o_hidden'), "chatter should be closed");
assert.strictEqual(form.$('.o_thread_message').length, 3, "thread should contain three messages");
assert.ok(form.$('.o_thread_message:first() .o_mail_note').length,
"the last message should be a note");
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");
form.$('.o_thread_message[data-message-id=2] .o_thread_message_star').click();
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
form.$('.o_thread_message[data-message-id=2] .o_thread_message_star').click();
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
form.$('.o_chatter_button_new_message').click();
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');
assert.strictEqual(getSuggestionsDef.state(), "pending",
"the mention suggestion RPC should be throttled");
getSuggestionsDef
.then(concurrency.delay.bind(concurrency, 0))
.then(function () {
assert.strictEqual(form.$('.o_mention_proposition:visible').length, 1,
"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@flectrahq.com)',
"suggestion should be displayed correctly");
BasicComposer.prototype.MENTION_THROTTLE = mentionThrottle;
form.destroy();
done();
});
});
QUnit.test('chatter: post a message and switch in edit mode', function (assert) {
assert.expect(5);
var messages = [];
var bus = new Bus();
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
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 === "/web/dataset/call_kw/partner/message_get_suggested_recipients") {
return $.when({2: []});
}
return this._super(route, args);
},
intercepts: {
get_messages: function (event) {
event.stopPropagation();
var requested_msgs = _.filter(messages, function (msg) {
return _.contains(event.data.options.ids, msg.id);
});
event.data.callback($.when(requested_msgs));
},
post_message: function (event) {
event.stopPropagation();
messages.push({
attachment_ids: [],
author_id: ["42", "Me"],
body: event.data.message.content,
date: moment(), // now
displayed_author: "Me",
id: 42,
is_note: event.data.message.subtype === 'mail.mt_note',
is_starred: false,
model: 'partner',
res_id: 2,
});
bus.trigger('new_message', {
id: 42,
model: event.data.options.model,
res_id: event.data.options.res_id,
});
},
get_bus: function (event) {
event.stopPropagation();
event.data.callback(bus);
},
},
});
assert.strictEqual(form.$('.o_thread_message').length, 0, "thread should not contain messages");
// send a message
form.$('.o_chatter_button_new_message').click();
form.$('.oe_chatter .o_composer_text_field:first()').val("My first message");
form.$('.oe_chatter .o_composer_button_send').click();
assert.strictEqual(form.$('.o_thread_message').length, 1, "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
form.$buttons.find('.o_form_button_edit').click();
assert.strictEqual(form.$('.o_thread_message').length, 1, "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: Attachment viewer', function (assert) {
assert.expect(6);
this.data.partner.records[0].message_ids = [1];
var messages = [{
attachment_ids: [{
filename: 'image1.jpg',
id:1,
mimetype: 'image/jpeg',
name: 'Test Image 1',
url: '/web/content/1?download=true'
},{
filename: 'image2.jpg',
id:2,
mimetype: 'image/jpeg',
name: 'Test Image 2',
url: '/web/content/2?download=true'
},{
filename: 'image3.jpg',
id:3,
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: moment("2016-12-20 09:35:40"),
displayed_author: "John Doe",
id: 1,
is_note: false,
is_starred: false,
model: 'partner',
res_id: 2,
}];
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
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) {
if(_.str.contains(route, '/mail/attachment/preview/') ||
_.str.contains(route, '/web/static/lib/pdfjs/web/viewer.html')){
var canvas = document.createElement('canvas');
return $.when(canvas.toDataURL());
}
return this._super.apply(this, arguments);
},
intercepts: {
get_messages: function (event) {
event.stopPropagation();
var requested_msgs = _.filter(messages, function (msg) {
return _.contains(event.data.options.ids, msg.id);
});
event.data.callback($.when(requested_msgs));
},
get_bus: function (event) {
event.stopPropagation();
event.data.callback(new Bus());
},
},
});
assert.strictEqual(form.$('.o_thread_message .o_attachment').length, 4,
"there should be three attachment on message");
assert.strictEqual(form.$('.o_thread_message .o_attachment .caption a').first().attr('href'), '/web/content/1?download=true',
"image caption should have correct download link");
// click on first image attachement
form.$('.o_thread_message .o_attachment .o_image_box .o_image_overlay').first().click();
assert.strictEqual($('.o_modal_fullscreen img.o_viewer_img[src*="/web/image/1?unique=1"]').length, 1,
"Modal popup should open with first image src");
// click on next button
$('.modal .arrow.arrow-right.move_next span').click();
assert.strictEqual($('.o_modal_fullscreen img.o_viewer_img[src*="/web/image/2?unique=1"]').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
$('.o_modal_fullscreen .o_viewer-header .o_close_btn').click();
// click on pdf attachement
form.$('.o_thread_message .o_attachment .o_image_box .o_image_overlay').eq(3).click();
assert.strictEqual($('.o_modal_fullscreen iframe[src*="/web/content/4"]').length, 1,
"Modal popup should open with the pdf preview");
// close attachment popup
$('.o_modal_fullscreen .o_viewer-header .o_close_btn').click();
form.destroy();
});
QUnit.test('form activity widget: read RPCs', 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
state: "today",
user_id: 2,
activity_type_id: 2,
}];
var nbReads = 0;
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
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 === 'read' && args.model === 'mail.activity') {
nbReads++;
}
return this._super.apply(this, arguments);
},
});
assert.strictEqual(nbReads, 1, "should have read the activities");
assert.strictEqual(form.$('.o_mail_activity .o_thread_message').length, 1,
"should display an activity");
assert.strictEqual(form.$('.o_mail_activity .o_thread_message .o_activity_date').text(),
'Today', "the activity should be today");
form.$buttons.find('.o_form_button_edit').click();
form.$buttons.find('.o_form_button_save').click();
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;
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', function (assert) {
assert.expect(0);
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
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', 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
state: "today",
user_id: 2,
activity_type_id: 2,
}];
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
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.strictEqual(form.$('.o_mail_activity .o_thread_message').length, 1,
"should display an activity");
form.destroy();
});
QUnit.test('form activity widget: schedule next activity', function (assert) {
assert.expect(5);
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
state: "today",
user_id: 2,
activity_type_id: 2,
}];
var checkReadArgs = false;
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
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') {
assert.ok(_.isEqual(args.args[0], [1]), "should call 'action_feedback' for id 1");
assert.strictEqual(args.kwargs.feedback, 'everything is ok',
"the feedback should be sent correctly");
return $.when();
}
if (args.method === 'read' && args.model === 'partner' && checkReadArgs) {
assert.deepEqual(args.args[1], ['activity_ids', 'message_ids', 'display_name'],
"should only read the mail fields");
}
return this._super.apply(this, arguments);
},
intercepts: {
get_messages: function (event) {
event.stopPropagation();
event.data.callback($.when([]));
},
get_bus: function (event) {
event.stopPropagation();
event.data.callback(new Bus());
},
do_action: function (event) {
assert.deepEqual(event.data.action, {
context: {
default_res_id: 2,
default_res_model: "partner",
default_previous_activity_type_id: 2,
},
res_id: false,
res_model: 'mail.activity',
type: 'ir.actions.act_window',
target: "new",
view_mode: "form",
view_type: "form",
views: [[false, "form"]],
}, "should do a do_action with correct parameters");
checkReadArgs = true; // should re-read the activities when closing the dialog
event.data.options.on_close();
},
},
});
//Schedule next activity
form.$('.o_mail_activity .o_activity_done[data-activity-id=1]').click();
assert.strictEqual(form.$('.o_mail_activity_feedback.popover').length, 1,
"a feedback popover should be visible");
$('.o_mail_activity_feedback.popover textarea').val('everything is ok'); // write a feedback
form.$('.o_activity_popover_done_next').click(); // schedule next activity
form.destroy();
});
QUnit.test('form activity widget: schedule activity does not discard changes', function (assert) {
assert.expect(1);
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
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: {
get_bus: function (event) {
event.stopPropagation();
event.data.callback(new Bus());
},
do_action: function (event) {
event.data.options.on_close();
},
},
viewOptions: {
mode: 'edit',
},
});
// update value of foo field
form.$('.o_field_widget[name=foo]').val('new value').trigger('input');
// schedule an activity (this triggers a do_action)
form.$('.o_chatter_button_schedule_activity').click();
// save the record
form.$buttons.find('.o_form_button_save').click();
form.destroy();
});
QUnit.test('form activity widget: mark as done and remove', function (assert) {
assert.expect(14);
var self = this;
var nbReads = 0;
var messages = [];
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
state: "today",
user_id: 2,
activity_type_id: 1,
}, {
id: 2,
display_name: "A second activity",
date_deadline: moment().format("YYYY-MM-DD"), // now
state: "today",
user_id: 2,
activity_type_id: 1,
}];
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
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];
messages.push({
attachment_ids: [],
author_id: ["1", "John Doe"],
body: "The activity has been done",
date: moment("2016-12-20 09:35:40"),
displayed_author: "John Doe",
id: 1,
is_note: true,
});
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, 4, '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);
},
intercepts: {
get_messages: function (event) {
event.stopPropagation();
event.data.callback($.when(messages));
},
get_bus: function (event) {
event.stopPropagation();
event.data.callback(new Bus());
}
},
});
assert.strictEqual(form.$('.o_mail_activity .o_thread_message').length, 2,
"there should be two activities");
// remove activity 1
form.$('.o_mail_activity .o_activity_unlink[data-activity-id=1]').click();
assert.strictEqual(form.$('.o_mail_activity .o_thread_message').length, 1,
"there should be one remaining activity");
assert.ok(!form.$('.o_mail_activity .o_activity_unlink[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");
form.$('.o_mail_activity .o_activity_done[data-activity-id=2]').click();
assert.strictEqual(form.$('.o_mail_activity_feedback.popover').length, 1,
"a feedback popover should be visible");
$('.o_mail_activity_feedback.popover textarea').val('everything is ok'); // write a feedback
form.$('.o_activity_popover_done').click(); // send feedback
assert.strictEqual(form.$('.o_mail_activity_feedback.popover').length, 0,
"the feedback popover should be closed");
assert.ok(!form.$('.o_mail_activity .o_thread_message').length,
"there should be no more activity");
assert.strictEqual(form.$('.o_mail_thread .o_thread_message').length, 1,
"a chatter message should have been generated");
form.destroy();
});
QUnit.test('followers widget: follow/unfollow, edit subtypes', function (assert) {
assert.expect(24);
var resID = 2;
var partnerID = 1;
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 = createView({
View: FormView,
model: 'partner',
data: this.data,
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);
});
// hack: the server creates a new follower each time the subtypes are updated
// so we need here to mock that weird behavior here, as the followers widget
// relies on that behavior
this.data.partner.records[0].message_follower_ids = [2];
followers[0].id = 2;
} 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 $.when(true);
}
if (route === '/mail/read_followers') {
return $.when({
followers: followers,
subtypes: subtypes,
});
}
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 $.when(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
form.$('.o_followers_follow_button').click();
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.strictEqual(form.$('.o_followers_list .o_partner').length, 1,
"there should be one follower in the follower dropdown");
// edit the subtypes
assert.strictEqual(form.$('.o_subtypes_list .o_subtype').length, 3,
'subtype list should contain 3 subtypes');
assert.strictEqual(form.$('.o_subtypes_list .o_subtype_checkbox:checked').length, 2,
'two subtypes should be checked by default');
form.$('.o_subtypes_list .dropdown-toggle').click(); // click to open the dropdown
assert.ok(form.$('.o_subtypes_list.open').length, 'dropdown should be opened');
form.$('.o_subtypes_list .o_subtype input[data-id=2]').click(); // uncheck second subtype
assert.ok(form.$('.o_subtypes_list.open').length, 'dropdown should remain opened');
assert.ok(!form.$('.o_subtypes_list .o_subtype_checkbox[data-id=2]:checked').length,
'second subtype should now be unchecked');
// click to unfollow
form.$('.o_followers_follow_button').click(); // click to open the dropdown
assert.ok($('.modal').length, 'a confirm modal should be opened');
$('.modal .modal-footer .btn-primary').click(); // click on 'OK'
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: do not display follower duplications', 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 = createView({
View: FormView,
model: 'partner',
data: this.data,
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 $.when(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 = $.Deferred();
form.reload();
form.reload();
def.resolve();
assert.strictEqual(form.$('.o_followers_count').text(), '2',
"should have 2 followers");
assert.strictEqual(form.$('.o_followers_list .o_partner').length, 2,
"there should be 2 followers in the follower dropdown");
form.destroy();
});
QUnit.test('does not render and crash when destroyed before chat system is ready', function (assert) {
assert.expect(0);
var def = $.Deferred();
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
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 (route === '/mail/read_followers') {
return $.when({
followers: [],
subtypes: [],
});
}
return this._super(route, args);
},
intercepts: {
chat_manager_ready: function (event) {
// we delay the return of the chat_manager ready event
event.data.callback(def);
},
get_messages: function (event) {
event.stopPropagation();
event.data.callback($.when([{
attachment_ids: [],
body: "",
date: moment("2016-12-20 09:35:40"),
id: 34,
res_id: 3,
author_id: ["3", "Fu Ck Mil Grom"],
}]));
},
get_bus: function (event) {
event.stopPropagation();
event.data.callback(new Bus());
},
get_session: function (event) {
event.stopPropagation();
event.data.callback({uid: 1});
},
},
});
form.destroy();
// here, the chat_manager 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 _getBus)
def.resolve();
});
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();
var nameGottenIds = [[12], [12, 14]];
this.data.partner.records[0].timmy = [12, 14];
// the modals need to be closed before the form view rendering
createAsyncView({
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 (route === "/web/dataset/call_kw/partner_type/name_get") {
assert.deepEqual(args.args[0], nameGottenIds.shift(),
"partner with email should be name_get'ed");
}
else if (args.method ==='read' && args.model === 'partner_type') {
assert.step(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(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]]);
assert.strictEqual(form.$('.o_field_many2manytags[name="timmy"] span.o_tag_color_0').length, 2,
"the second tag should be present");
form.destroy();
done();
});
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)
$('.modal-body.o_act_window input[name="email"]').val('coucou@petite.perruche').trigger('input');
$('.modal-footer .btn-primary').click();
});
QUnit.test('fieldmany2many tags email (edition)', function (assert) {
assert.expect(15);
this.data.partner.records[0].timmy = [12];
var form = 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(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.strictEqual(form.$('.o_field_many2manytags[name="timmy"] span.o_tag_color_0').length, 1,
"should contain one tag");
// add an other existing tag
var $input = form.$('.o_field_many2manytags input');
$input.click(); // opens the dropdown
$input.autocomplete('widget').find('li:first').click(); // add 'silver'
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 rerender the form view)
$('.modal-body.o_act_window input[name="email"]').val('coucou@petite.perruche').trigger('input');
$('.modal-footer .btn-primary').click();
assert.strictEqual(form.$('.o_field_many2manytags[name="timmy"] span.o_tag_color_0').length, 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([[12], [14], [14], [14]]);
form.destroy();
});
});
});