diff --git a/addons/mail/static/tests/activity_tests.js b/addons/mail/static/tests/activity_tests.js
index 4e3e509936c..fb5f3eda08f 100644
--- a/addons/mail/static/tests/activity_tests.js
+++ b/addons/mail/static/tests/activity_tests.js
@@ -7,70 +7,6 @@ var testUtils = require('web.test_utils');
var createActionManager = testUtils.createActionManager;
var createView = testUtils.createView;
-var ACTIVITY_DATA = {
- "grouped_activities":{
- "13":{
- "1":{
- "count":1,
- "state":"planned",
- "o_closest_deadline":"2018-08-16",
- "ids":[
- 1
- ]
- }
- },
- "30":{
- "1":{
- "count":1,
- "state":"today",
- "o_closest_deadline":"2018-08-16",
- "ids":[
- 2
- ]
- },
- "2":{
- "count":2,
- "state":"overdue",
- "o_closest_deadline":"2018-09-16",
- "ids":[
- 3
- ]
- }
- }
- },
- "activity_types":[
- [
- 1,
- "Email",
- [
- {
- "id":9,
- "name":"Task: Rating Request"
- },
- {
- "id":8,
- "name":"Task: Reception Acknowledgment"
- }
- ]
- ],
- [
- 2,
- "Call",
- []
- ]
- ],
- "model":"task",
- "res_ids":[
- [
- 30,
- "Office planning"
- ],
- [
- 13,
- "Meeting Room Furnitures"
- ]
- ]
-};
QUnit.module('mail', {}, function () {
QUnit.module('activity view', {
@@ -79,11 +15,18 @@ QUnit.module('activity view', {
task: {
fields: {
id: {string: 'ID', type: 'integer'},
+ foo: {string: "Foo", type: "char"},
+ activity_ids: {
+ string: 'Activities',
+ type: 'one2many',
+ relation: 'mail.activity',
+ relation_field: 'res_id',
+ },
},
records: [
- {id: 13},
- {id: 30},
- ]
+ {id: 13, foo: 'Meeting Room Furnitures', activity_ids: [1]},
+ {id: 30, foo: 'Office planning', activity_ids: [2, 3]},
+ ],
},
partner: {
fields: {
@@ -96,6 +39,7 @@ QUnit.module('activity view', {
},
'mail.activity': {
fields: {
+ res_id: { string: 'Related document id', type: 'integer' },
activity_type_id: { string: "Activity type", type: "many2one", relation: "mail.activity.type" },
display_name: { string: "Display name", type: "char" },
date_deadline: { string: "Due Date", type: "date" },
@@ -111,6 +55,7 @@ QUnit.module('activity view', {
records:[
{
id: 1,
+ res_id: 13,
display_name: "An activity",
date_deadline: moment().add(3, "days").format("YYYY-MM-DD"), // now
can_write: true,
@@ -121,6 +66,7 @@ QUnit.module('activity view', {
user_id:2,
},{
id: 2,
+ res_id: 30,
display_name: "An activity",
date_deadline: moment().format("YYYY-MM-DD"), // now
can_write: true,
@@ -130,6 +76,7 @@ QUnit.module('activity view', {
user_id:2,
},{
id: 3,
+ res_id: 30,
display_name: "An activity",
date_deadline: moment().subtract(2, "days").format("YYYY-MM-DD"), // now
can_write: true,
@@ -151,62 +98,116 @@ QUnit.module('activity view', {
},
'mail.activity.type': {
fields: {
+ mail_template_ids: { string: "Mail templates", type: "many2many", relation: "mail.template" },
name: { string: "Name", type: "char" },
},
records: [
- { id: 1, name: "Email" },
+ { id: 1, name: "Email", mail_template_ids: [8, 9]},
{ id: 2, name: "Call" },
+ { id: 3, name: "Call for Demo" },
+ { id: 4, name: "To Do" },
],
},
};
}
});
+var activityDateFormat = function (date) {
+ return date.toLocaleDateString(moment().locale(), { day: 'numeric', month: 'short' });
+};
+
QUnit.test('activity view: simple activity rendering', async function (assert) {
- assert.expect(7);
+ assert.expect(14);
var activity = await createView({
View: ActivityView,
model: 'task',
data: this.data,
- arch: '',
- mockRPC: function(route, args) {
- if (args.method === 'get_activity_data') {
- return Promise.resolve(ACTIVITY_DATA);
- }
- return this._super(route, args);
+ arch: '' +
+ '' +
+ '' +
+ '' +
+ '
' +
+ '' +
+ '',
+ intercepts: {
+ do_action: function (event) {
+ assert.deepEqual(event.data.action, {
+ context: {
+ default_res_id: 30,
+ default_res_model: "task",
+ default_activity_type_id: 3,
+ },
+ res_id: false,
+ res_model: "mail.activity",
+ target: "new",
+ type: "ir.actions.act_window",
+ view_mode: "form",
+ view_type: "form",
+ views: [[false, "form"]]
+ },
+ "should do a do_action with correct parameters");
+ event.data.options.on_close();
+ },
},
});
assert.containsOnce(activity, 'table',
'should have a table');
- assert.ok(activity.$('table thead tr:first th:nth-child(2) span:first:contains(Email)').length,
- 'should contain "Email" in header of first column');
- assert.ok(activity.$('table thead tr:first th:nth-child(3) span:first:contains(Call)').length,
- 'should contain "Call" in header of second column');
+ var $th1 = activity.$('table thead tr:first th:nth-child(2)');
+ assert.containsOnce($th1, 'span:first:contains(Email)', 'should contain "Email" in header of first column');
+ assert.containsOnce($th1, '.o_kanban_counter', 'should contain a progressbar in header of first column');
+ assert.hasAttrValue($th1.find('.o_kanban_counter_progress .progress-bar:first'), 'data-original-title', '1 planned',
+ 'the counter progressbars should be correctly displayed');
+ assert.hasAttrValue($th1.find('.o_kanban_counter_progress .progress-bar:nth-child(2)'), 'data-original-title', '1 today',
+ 'the counter progressbars should be correctly displayed');
+ var $th2 = activity.$('table thead tr:first th:nth-child(3)');
+ assert.containsOnce($th2, 'span:first:contains(Call)', 'should contain "Call" in header of second column');
+ assert.hasAttrValue($th2.find('.o_kanban_counter_progress .progress-bar:last'), 'data-original-title', '1 overdue',
+ 'the counter progressbars should be correctly displayed');
+ assert.containsNone(activity, 'table thead tr:first th:nth-child(4) .o_kanban_counter',
+ 'should not contain a progressbar in header of 3rd column');
assert.ok(activity.$('table tbody tr:first td:first:contains(Office planning)').length,
'should contain "Office planning" in first colum of first row');
assert.ok(activity.$('table tbody tr:nth-child(2) td:first:contains(Meeting Room Furnitures)').length,
'should contain "Meeting Room Furnitures" in first colum of second row');
- assert.ok(activity.$('table tbody tr:first td:nth-child(2).today .o_closest_deadline:contains(Aug 16)').length,
- 'should contain an activity for today with date Aug 16 in second cell of first line');
- assert.ok(activity.$('table tbody tr:nth-child(2) td:nth-child(2).planned .o_closest_deadline:contains(Aug 16)').length,
- 'should contain a planned activity with date Aug 16 in second cell of second line');
+
+ var today = activityDateFormat(new Date());
+
+ assert.ok(activity.$('table tbody tr:first td:nth-child(2).today .o_closest_deadline:contains(' + today + ')').length,
+ 'should contain an activity for today in second cell of first line ' + today);
+ var td = 'table tbody tr:nth-child(1) td.o_activity_empty_cell';
+ assert.containsN(activity, td, 2, 'should contain an empty cell as no activity scheduled yet.');
+
+ // schedule an activity (this triggers a do_action)
+ testUtils.fields.editAndTrigger(activity.$(td + ':first'), null, ['mouseenter', 'click']);
+ assert.containsOnce(activity, 'table tfoot tr .o_record_selector',
+ 'should contain search more selector to choose the record to schedule an activity for it');
+
activity.destroy();
});
QUnit.test('activity view: no content rendering', async function (assert) {
assert.expect(2);
+
+ // remove activities from tasks
+ this.data.task.records.forEach(function (task) {
+ task.activity_ids = false;
+ });
+
+ // remove activity types
+ this.data['mail.activity.type'].records = [];
+
var activity = await createView({
View: ActivityView,
model: 'task',
data: this.data,
- arch: '',
- mockRPC: function (route, args) {
- if (args.method === 'get_activity_data') {
- return Promise.resolve({ activity_types: [] });
- }
- return this._super(route, args);
- },
+ arch: '' +
+ '' +
+ '' +
+ '' +
+ '
' +
+ '' +
+ '',
});
assert.containsOnce(activity, '.o_view_nocontent',
@@ -224,16 +225,19 @@ QUnit.test('activity view: batch send mail on activity', async function (assert)
View: ActivityView,
model: 'task',
data: this.data,
- arch: '',
+ arch: '' +
+ '' +
+ '' +
+ '' +
+ '
' +
+ '' +
+ '',
mockRPC: function(route, args) {
if (args.method === 'activity_send_mail'){
assert.step(JSON.stringify(args.args));
return Promise.resolve();
}
- if (args.method === 'get_activity_data') {
- return Promise.resolve(ACTIVITY_DATA);
- }
- return this._super(route, args);
+ return this._super.apply(this, arguments);
},
});
assert.notOk(activity.$('table thead tr:first th:nth-child(2) span:nth-child(2) .dropdown-menu.show').length,
@@ -243,12 +247,12 @@ QUnit.test('activity view: batch send mail on activity', async function (assert)
assert.ok(activity.$('table thead tr:first th:nth-child(2) span:nth-child(2) .dropdown-menu.show').length,
'dropdown should have appeared');
- testUtils.dom.click(activity.$('table thead tr:first th:nth-child(2) span:nth-child(2) .dropdown-menu.show .o_send_mail_template:first:contains(Task: Rating Request)'));
+ testUtils.dom.click(activity.$('table thead tr:first th:nth-child(2) span:nth-child(2) .dropdown-menu.show .o_send_mail_template:contains(Template2)'));
assert.notOk(activity.$('table thead tr:first th:nth-child(2) span:nth-child(2) .dropdown-menu.show').length,
'dropdown shouldn\'t be displayed');
testUtils.dom.click(activity.$('table thead tr:first th:nth-child(2) span:nth-child(2) i.fa-ellipsis-v'));
- testUtils.dom.click(activity.$('table thead tr:first th:nth-child(2) span:nth-child(2) .dropdown-menu.show .o_send_mail_template:nth-child(2):contains(Task: Reception Acknowledgment)'));
+ testUtils.dom.click(activity.$('table thead tr:first th:nth-child(2) span:nth-child(2) .dropdown-menu.show .o_send_mail_template:contains(Template1)'));
assert.verifySteps([
'[[13,30],9]', // send mail template 9 on tasks 13 and 30
'[[13,30],8]', // send mail template 8 on tasks 13 and 30
@@ -263,7 +267,13 @@ QUnit.test('activity view: activity widget', async function (assert) {
View: ActivityView,
model: 'task',
data: this.data,
- arch: '',
+ arch: '' +
+ '' +
+ '' +
+ '' +
+ '
' +
+ ''+
+ '',
mockRPC: function(route, args) {
if (args.method === 'activity_send_mail'){
assert.deepEqual([[30],8],args.args, "Should send template 8 on record 30");
@@ -276,10 +286,7 @@ QUnit.test('activity view: activity widget', async function (assert) {
assert.step('action_feedback_schedule_next');
return Promise.resolve({serverGeneratedAction: true});
}
- if (args.method === 'get_activity_data') {
- return Promise.resolve(ACTIVITY_DATA);
- }
- return this._super(route, args);
+ return this._super.apply(this, arguments);
},
intercepts: {
do_action: function (ev) {
@@ -354,7 +361,13 @@ QUnit.test('activity view: no group_by_menu and no time_range_menu', async funct
views: [[false, 'activity']],
}],
archs: {
- 'task,false,activity': '',
+ 'task,false,activity': '' +
+ '' +
+ '' +
+ '' +
+ '
' +
+ '' +
+ '',
'task,false,search': '',
},
data: this.data,
@@ -365,9 +378,8 @@ QUnit.test('activity view: no group_by_menu and no time_range_menu', async funct
if (args.method === 'get_activity_data') {
assert.deepEqual(args.kwargs.context, {lang: 'zz_ZZ'},
'The context should have been passed');
- return Promise.resolve(ACTIVITY_DATA);
}
- return this._super(route, args);
+ return this._super.apply(this, arguments);
},
});
@@ -382,5 +394,69 @@ QUnit.test('activity view: no group_by_menu and no time_range_menu', async funct
actionManager.destroy();
});
+QUnit.test('activity view: search more to schedule an activity for a record of a respecting model', async function (assert) {
+ assert.expect(5);
+ _.extend(this.data.task.fields, {
+ name: { string: "Name", type: "char" },
+ });
+ this.data.task.records[2] = { id: 31, name: "Task 3" };
+ var activity = await createView({
+ View: ActivityView,
+ model: 'task',
+ data: this.data,
+ arch: '' +
+ '' +
+ '' +
+ '' +
+ '
' +
+ '' +
+ '',
+ archs: {
+ "task,false,list": '',
+ "task,false,search": '',
+ },
+ mockRPC: function(route, args) {
+ if (args.method === 'name_search') {
+ args.kwargs.name = "Task";
+ }
+ return this._super.apply(this, arguments);
+ },
+ intercepts: {
+ do_action: function (ev) {
+ assert.step('doAction');
+ var expectedAction = {
+ context: {
+ default_res_id: { id: 31, display_name: undefined },
+ default_res_model: "task",
+ },
+ name: "Schedule Activity",
+ res_id: false,
+ res_model: "mail.activity",
+ target: "new",
+ type: "ir.actions.act_window",
+ view_mode: "form",
+ view_type: "form",
+ views: [[false, "form"]],
+ };
+ assert.deepEqual(ev.data.action, expectedAction,
+ "should execute an action with correct params");
+ ev.data.options.on_close();
+ },
+ },
+ });
+
+ assert.containsOnce(activity, 'table tfoot tr .o_record_selector',
+ 'should contain search more selector to choose the record to schedule an activity for it');
+ await testUtils.dom.click(activity.$('table tfoot tr .o_record_selector'));
+ // search create dialog
+ var $modal = $('.modal-lg');
+ assert.strictEqual($modal.find('.o_data_row').length, 3, "all tasks should be available to select");
+ // select a record to schedule an activity for it (this triggers a do_action)
+ testUtils.dom.click($modal.find('.o_data_row:last'));
+ assert.verifySteps(['doAction']);
+
+ activity.destroy();
+});
+
});
});
diff --git a/addons/mail/static/tests/helpers/mock_server.js b/addons/mail/static/tests/helpers/mock_server.js
index 32203db5dfb..98e63d6448a 100644
--- a/addons/mail/static/tests/helpers/mock_server.js
+++ b/addons/mail/static/tests/helpers/mock_server.js
@@ -48,6 +48,81 @@ MockServer.include({
});
return previews;
},
+ /**
+ * Simulate the 'get_activity_method' on 'mail.activity'
+ *
+ * @private
+ * @return {Object}
+ */
+ _mockGetActivityData: function (args) {
+ var self = this;
+
+ var domain = args.kwargs.domain;
+ var model = args.kwargs.res_model;
+ var records = this._getRecords(model, domain);
+
+ var activityTypes = this._getRecords('mail.activity.type', []);
+ var activityIds = _.pluck(records, 'activity_ids').flat();
+
+ var groupedActivities = {};
+ var resIdToDeadline = {};
+ var groups = self._mockReadGroup('mail.activity', {
+ domain: [['id', 'in', activityIds]],
+ fields: ['res_id', 'activity_type_id', 'ids:array_agg(id)', 'date_deadline:min(date_deadline)'],
+ groupby: ['res_id', 'activity_type_id'],
+ lazy: false,
+ });
+ groups.forEach(function (group) {
+ // mockReadGroup doesn't correctly return all asked fields
+ var activites = self._getRecords('mail.activity', group.__domain);
+ group.activity_type_id = group.activity_type_id[0];
+ var minDate;
+ activites.forEach(function (activity) {
+ if (!minDate || moment(activity.date_deadline) < moment(minDate)) {
+ minDate = activity.date_deadline;
+ }
+ });
+ group.date_deadline = minDate;
+ resIdToDeadline[group.res_id] = minDate;
+ var state;
+ if (group.date_deadline === moment().format("YYYY-MM-DD")) {
+ state = 'today';
+ } else if (moment(group.date_deadline) > moment()) {
+ state = 'planned';
+ } else {
+ state = 'overdue';
+ }
+ if (!groupedActivities[group.res_id]) {
+ groupedActivities[group.res_id] = {};
+ }
+ groupedActivities[group.res_id][group.activity_type_id] = {
+ count: group.__count,
+ state: state,
+ o_closest_deadline: group.date_deadline,
+ ids: _.pluck(activites, 'id'),
+ };
+ });
+
+ return {
+ activity_types: activityTypes.map(function (type) {
+ var mailTemplates = [];
+ if (type.mail_template_ids) {
+ mailTemplates = type.mail_template_ids.map(function (id) {
+ var template = _.findWhere(self.data['mail.template'].records, {id: id});
+ return {
+ id: id,
+ name: template.name,
+ };
+ });
+ }
+ return [type.id, type.display_name, mailTemplates];
+ }),
+ activity_res_ids: _.sortBy(_.pluck(records, 'id'), function (id) {
+ return moment(resIdToDeadline[id]);
+ }),
+ grouped_activities: groupedActivities,
+ };
+ },
/**
* Simulate the '/mail/init_messaging' route
*
@@ -201,6 +276,9 @@ MockServer.include({
if (args.method === 'channel_fetched') {
return Promise.resolve();
}
+ if (args.method === 'get_activity_data') {
+ return Promise.resolve(this._mockGetActivityData(args));
+ }
if (args.method === 'message_fetch') {
return Promise.resolve(this._mockMessageFetch(args));
}