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