From c8db947024d3218b2d12381ae40a5bc02d1a243a Mon Sep 17 00:00:00 2001 From: Ipsita Borisagar Date: Tue, 28 Dec 2021 10:02:24 +0000 Subject: [PATCH] [IMP] mail: activity keyboard navigation improvement MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PURPOSE Improve some mail activity issues regarding keyboard navigation. SPECIFICATIONS When done an activity using the record list view, allowing user ENTER key support on "Done" or "Done and Schedule Next" button to post feedback and mark the activity as done. Task-2528109 closes odoo/odoo#82879 Signed-off-by: Alexandre Kühn (aku) --- addons/mail/static/src/js/activity.js | 26 +++++ .../mail/static/tests/helpers/mock_server.js | 49 ++++++++++ .../tests/qunit_suite_tests/chatter_tests.js | 94 +++++++++++++++++++ 3 files changed, 169 insertions(+) diff --git a/addons/mail/static/src/js/activity.js b/addons/mail/static/src/js/activity.js index 0b9cfbff599..fd3b13391ac 100644 --- a/addons/mail/static/src/js/activity.js +++ b/addons/mail/static/src/js/activity.js @@ -377,6 +377,7 @@ var BasicActivity = AbstractField.extend({ this.$('.o_activity_selected').removeClass('o_activity_selected'); $actLi.toggleClass('o_activity_selected'); $panel.collapse('toggle'); + $panel.find('#activity_feedback').focus(); } else if (!$markDoneBtn.data('bs.popover')) { $markDoneBtn.popover({ @@ -418,6 +419,11 @@ var BasicActivity = AbstractField.extend({ _onMarkActivityDoneActions: function ($btn, $form, activityID) { var self = this; $form.find('#activity_feedback').val(self._draftFeedback[activityID]); + $form.on('keydown', '#activity_feedback', function (ev) { + if (ev.key === 'Enter') { + ev.stopPropagation(); // Prevent list view actions + } + }); $form.on('click', '.o_activity_popover_done', function (ev) { ev.stopPropagation(); self._markActivityDone({ @@ -425,6 +431,16 @@ var BasicActivity = AbstractField.extend({ feedback: $form.find('#activity_feedback').val(), }); }); + $form.on('keydown', '.o_activity_popover_done', function (ev) { + if (ev.key === 'Enter') { + ev.stopPropagation(); // Prevent list view actions + ev.preventDefault(); + self._markActivityDone({ + activityID, + feedback: $form.find('#activity_feedback').val(), + }); + } + }); $form.on('click', '.o_activity_popover_done_next', function (ev) { ev.stopPropagation(); self._markActivityDoneAndScheduleNext({ @@ -432,6 +448,16 @@ var BasicActivity = AbstractField.extend({ feedback: $form.find('#activity_feedback').val(), }); }); + $form.on('keydown', '.o_activity_popover_done_next', function (ev) { + if (ev.key === 'Enter') { + ev.stopPropagation(); // Prevent list view actions + ev.preventDefault(); + self._markActivityDoneAndScheduleNext({ + activityID, + feedback: $form.find('#activity_feedback').val(), + }); + } + }); $form.on('click', '.o_activity_popover_discard', function (ev) { ev.stopPropagation(); if ($btn.data('bs.popover')) { diff --git a/addons/mail/static/tests/helpers/mock_server.js b/addons/mail/static/tests/helpers/mock_server.js index 58302ad8a60..72b78722633 100644 --- a/addons/mail/static/tests/helpers/mock_server.js +++ b/addons/mail/static/tests/helpers/mock_server.js @@ -154,6 +154,14 @@ MockServer.include({ return; } // mail.activity methods + if (args.model === 'mail.activity' && args.method === 'action_feedback') { + const ids = args.args[0]; + return this._mockMailActivityActionFeedback(ids); + } + if (args.model === 'mail.activity' && args.method === 'action_feedback_schedule_next') { + const ids = args.args[0]; + return this._mockMailActivityActionFeedbackScheduleNext(ids); + } if (args.model === 'mail.activity' && args.method === 'activity_format') { const ids = args.args[0]; return this._mockMailActivityActivityFormat(ids); @@ -635,6 +643,47 @@ MockServer.include({ return res; }); }, + /** + * Simulates `_action_done` on `mail.activity`. + * + * @private + * @param {string} model + * @param {integer[]} ids + * @returns {Object} + */ + _mockMailActivityActionDone(ids) { + const activities = this.getRecords('mail.activity', [['id', 'in', ids]]); + this._mockUnlink('mail.activity', [activities.map(activity => activity.id)]); + }, + /** + * Simulates `action_feedback` on `mail.activity`. + * + * @private + * @param {string} model + * @param {integer[]} ids + * @returns {Object} + */ + _mockMailActivityActionFeedback(ids) { + this._mockMailActivityActionDone(ids); + }, + /** + * Simulates `action_feedback_schedule_next` on `mail.activity`. + * + * @private + * @param {string} model + * @param {integer[]} ids + * @returns {Object} + */ + _mockMailActivityActionFeedbackScheduleNext(ids) { + this._mockMailActivityActionDone(ids); + return { + name: 'Schedule an Activity', + view_mode: 'form', + res_model: 'mail.activity', + views: [[false, 'form']], + type: 'ir.actions.act_window', + } + }, /** * Simulates `get_activity_data` on `mail.activity`. * diff --git a/addons/mail/static/tests/qunit_suite_tests/chatter_tests.js b/addons/mail/static/tests/qunit_suite_tests/chatter_tests.js index 3f320f48774..5ed2fc831bc 100644 --- a/addons/mail/static/tests/qunit_suite_tests/chatter_tests.js +++ b/addons/mail/static/tests/qunit_suite_tests/chatter_tests.js @@ -252,6 +252,100 @@ QUnit.test('list activity exception widget with activity', async function (asser "there is an exception on a record"); }); +QUnit.test('list activity widget: done the activity with "ENTER" keyboard shortcut', async function (assert) { + assert.expect(1); + + const pyEnv = await startServer(); + const mailActivityTypeId1 = pyEnv['mail.activity.type'].create(); + const mailActivityId1 = pyEnv['mail.activity'].create([ + { + display_name: "Call with Al", + date_deadline: moment().format("YYYY-MM-DD"), // now + can_write: true, + state: "today", + user_id: pyEnv.currentUserId, + create_uid: pyEnv.currentUserId, + activity_type_id: mailActivityTypeId1, + } + ]); + pyEnv['res.users'].write([pyEnv.currentUserId], { + activity_ids: [mailActivityId1], + activity_state: 'today', + activity_summary: 'Call with Al', + activity_type_id: mailActivityTypeId1, + }); + + const { target: list } = await start({ + hasView: true, + View: ListView, + model: 'res.users', + arch: ` + + + `, + }); + + await testUtils.dom.click(list.querySelector('.o_activity_btn span')); + await testUtils.dom.click(list.querySelector('.o_mark_as_done')); + pyEnv['res.users'].write([pyEnv.currentUserId], { + activity_state: '', + }); + await testUtils.dom.triggerEvent( + document.querySelector('.o_activity_popover_done'), + 'keydown', + { key: 'Enter' }, + ); + + assert.containsOnce(list, '.o_mail_activity .o_activity_color_default'); +}); + +QUnit.test('list activity widget: done and schedule the next activity with "ENTER" keyboard shortcut', async function (assert) { + assert.expect(1); + + const pyEnv = await startServer(); + const mailActivityTypeId1 = pyEnv['mail.activity.type'].create(); + const mailActivityId1 = pyEnv['mail.activity'].create([ + { + display_name: "Call with Al", + date_deadline: moment().format("YYYY-MM-DD"), // now + can_write: true, + state: "today", + user_id: pyEnv.currentUserId, + create_uid: pyEnv.currentUserId, + activity_type_id: mailActivityTypeId1, + } + ]); + pyEnv['res.users'].write([pyEnv.currentUserId], { + activity_ids: [mailActivityId1], + activity_state: 'today', + activity_summary: 'Call with Al', + activity_type_id: mailActivityTypeId1, + }); + + const { target: list } = await start({ + hasView: true, + View: ListView, + model: 'res.users', + arch: ` + + + `, + intercepts: { + do_action: function (ev) { + assert.strictEqual(ev.data.action.name, "Schedule an Activity", 'should do a do_action with correct parameters'); + }, + }, + }); + + await testUtils.dom.click(list.querySelector('.o_activity_btn span')); + await testUtils.dom.click(list.querySelector('.o_mark_as_done')); + await testUtils.dom.triggerEvent( + document.querySelector('.o_activity_popover_done_next'), + 'keydown', + { key: 'Enter' }, + ); +}); + QUnit.module('FieldMany2ManyTagsEmail'); QUnit.test('fieldmany2many tags email (edition)', async function (assert) {