diff options
| author | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
|---|---|---|
| committer | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
| commit | 3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch) | |
| tree | a44932296ef4a9b71d5f010906253d8c53727726 /addons/mail/static/src/components/activity_mark_done_popover | |
| parent | 0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff) | |
initial commit 2
Diffstat (limited to 'addons/mail/static/src/components/activity_mark_done_popover')
4 files changed, 462 insertions, 0 deletions
diff --git a/addons/mail/static/src/components/activity_mark_done_popover/activity_mark_done_popover.js b/addons/mail/static/src/components/activity_mark_done_popover/activity_mark_done_popover.js new file mode 100644 index 00000000..de1ea5ce --- /dev/null +++ b/addons/mail/static/src/components/activity_mark_done_popover/activity_mark_done_popover.js @@ -0,0 +1,122 @@ +odoo.define('mail/static/src/components/activity_mark_done_popover/activity_mark_done_popover.js', function (require) { +'use strict'; + +const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js'); +const useStore = require('mail/static/src/component_hooks/use_store/use_store.js'); + +const { Component } = owl; +const { useRef } = owl.hooks; + +class ActivityMarkDonePopover extends Component { + + /** + * @override + */ + constructor(...args) { + super(...args); + useShouldUpdateBasedOnProps(); + useStore(props => { + const activity = this.env.models['mail.activity'].get(props.activityLocalId); + return { + activity: activity ? activity.__state : undefined, + }; + }); + this._feedbackTextareaRef = useRef('feedbackTextarea'); + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + mounted() { + this._feedbackTextareaRef.el.focus(); + if (this.activity.feedbackBackup) { + this._feedbackTextareaRef.el.value = this.activity.feedbackBackup; + } + } + + /** + * @returns {mail.activity} + */ + get activity() { + return this.env.models['mail.activity'].get(this.props.activityLocalId); + } + + /** + * @returns {string} + */ + get DONE_AND_SCHEDULE_NEXT() { + return this.env._t("Done & Schedule Next"); + } + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * @private + */ + _close() { + this.trigger('o-popover-close'); + } + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * @private + */ + _onBlur() { + this.activity.update({ + feedbackBackup: this._feedbackTextareaRef.el.value, + }); + } + + /** + * @private + */ + _onClickDiscard() { + this._close(); + } + + /** + * @private + */ + async _onClickDone() { + await this.activity.markAsDone({ + feedback: this._feedbackTextareaRef.el.value, + }); + this.trigger('reload', { keepChanges: true }); + } + + /** + * @private + */ + _onClickDoneAndScheduleNext() { + this.activity.markAsDoneAndScheduleNext({ + feedback: this._feedbackTextareaRef.el.value, + }); + } + + /** + * @private + */ + _onKeydown(ev) { + if (ev.key === 'Escape') { + this._close(); + } + } + +} + +Object.assign(ActivityMarkDonePopover, { + props: { + activityLocalId: String, + }, + template: 'mail.ActivityMarkDonePopover', +}); + +return ActivityMarkDonePopover; + +}); diff --git a/addons/mail/static/src/components/activity_mark_done_popover/activity_mark_done_popover.scss b/addons/mail/static/src/components/activity_mark_done_popover/activity_mark_done_popover.scss new file mode 100644 index 00000000..3479ffc3 --- /dev/null +++ b/addons/mail/static/src/components/activity_mark_done_popover/activity_mark_done_popover.scss @@ -0,0 +1,20 @@ +// ------------------------------------------------------------------ +// Layout +// ------------------------------------------------------------------ + +.o_ActivityMarkDonePopover { + min-height: 100px; +} + +.o_ActivityMarkDonePopover_buttons { + margin-top: map-get($spacers, 2); +} + +.o_ActivityMarkDonePopover_doneButton { + margin: map-get($spacers, 0) map-get($spacers, 2); +} + +.o_ActivityMarkDonePopover_feedback { + min-height: 70px; +} + diff --git a/addons/mail/static/src/components/activity_mark_done_popover/activity_mark_done_popover.xml b/addons/mail/static/src/components/activity_mark_done_popover/activity_mark_done_popover.xml new file mode 100644 index 00000000..357ab59b --- /dev/null +++ b/addons/mail/static/src/components/activity_mark_done_popover/activity_mark_done_popover.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<templates xml:space="preserve"> + + <t t-name="mail.ActivityMarkDonePopover" owl="1"> + <div class="o_ActivityMarkDonePopover" t-on-keydown="_onKeydown"> + <t t-if="activity"> + <textarea class="form-control o_ActivityMarkDonePopover_feedback" rows="3" placeholder="Write Feedback" t-on-blur="_onBlur" t-ref="feedbackTextarea"/> + <div class="o_ActivityMarkDonePopover_buttons"> + <button type="button" class="o_ActivityMarkDonePopover_doneScheduleNextButton btn btn-sm btn-primary" t-on-click="_onClickDoneAndScheduleNext" t-esc="DONE_AND_SCHEDULE_NEXT"/> + <t t-if="!activity.force_next"> + <button type="button" class="o_ActivityMarkDonePopover_doneButton btn btn-sm btn-primary" t-on-click="_onClickDone"> + Done + </button> + </t> + <button type="button" class="o_ActivityMarkDonePopover_discardButton btn btn-sm btn-link" t-on-click="_onClickDiscard"> + Discard + </button> + </div> + </t> + </div> + </t> + +</templates> diff --git a/addons/mail/static/src/components/activity_mark_done_popover/activity_mark_done_popover_tests.js b/addons/mail/static/src/components/activity_mark_done_popover/activity_mark_done_popover_tests.js new file mode 100644 index 00000000..0c019b2b --- /dev/null +++ b/addons/mail/static/src/components/activity_mark_done_popover/activity_mark_done_popover_tests.js @@ -0,0 +1,297 @@ +odoo.define('mail/static/src/components/activity_mark_done_popover/activity_mark_done_popover_tests.js', function (require) { +'use strict'; + +const components = { + ActivityMarkDonePopover: require('mail/static/src/components/activity_mark_done_popover/activity_mark_done_popover.js'), +}; + +const { + afterEach, + afterNextRender, + beforeEach, + createRootComponent, + start, +} = require('mail/static/src/utils/test_utils.js'); + +const Bus = require('web.Bus'); + +QUnit.module('mail', {}, function () { +QUnit.module('components', {}, function () { +QUnit.module('activity_mark_done_popover', {}, function () { +QUnit.module('activity_mark_done_popover_tests.js', { + beforeEach() { + beforeEach(this); + + this.createActivityMarkDonePopoverComponent = async activity => { + await createRootComponent(this, components.ActivityMarkDonePopover, { + props: { activityLocalId: activity.localId }, + target: this.widget.el, + }); + }; + + this.start = async params => { + const { env, widget } = await start(Object.assign({}, params, { + data: this.data, + })); + this.env = env; + this.widget = widget; + }; + }, + afterEach() { + afterEach(this); + }, +}); + +QUnit.test('activity mark done popover simplest layout', async function (assert) { + assert.expect(6); + + await this.start(); + const activity = this.env.models['mail.activity'].create({ + canWrite: true, + category: 'not_upload_file', + id: 12, + thread: [['insert', { id: 42, model: 'res.partner' }]], + }); + await this.createActivityMarkDonePopoverComponent(activity); + + assert.containsOnce( + document.body, + '.o_ActivityMarkDonePopover', + "Popover component should be present" + ); + assert.containsOnce( + document.body, + '.o_ActivityMarkDonePopover_feedback', + "Popover component should contain the feedback textarea" + ); + assert.containsOnce( + document.body, + '.o_ActivityMarkDonePopover_buttons', + "Popover component should contain the action buttons" + ); + assert.containsOnce( + document.body, + '.o_ActivityMarkDonePopover_doneScheduleNextButton', + "Popover component should contain the done & schedule next button" + ); + assert.containsOnce( + document.body, + '.o_ActivityMarkDonePopover_doneButton', + "Popover component should contain the done button" + ); + assert.containsOnce( + document.body, + '.o_ActivityMarkDonePopover_discardButton', + "Popover component should contain the discard button" + ); +}); + +QUnit.test('activity with force next mark done popover simplest layout', async function (assert) { + assert.expect(6); + + await this.start(); + const activity = this.env.models['mail.activity'].create({ + canWrite: true, + category: 'not_upload_file', + force_next: true, + id: 12, + thread: [['insert', { id: 42, model: 'res.partner' }]], + }); + await this.createActivityMarkDonePopoverComponent(activity); + + assert.containsOnce( + document.body, + '.o_ActivityMarkDonePopover', + "Popover component should be present" + ); + assert.containsOnce( + document.body, + '.o_ActivityMarkDonePopover_feedback', + "Popover component should contain the feedback textarea" + ); + assert.containsOnce( + document.body, + '.o_ActivityMarkDonePopover_buttons', + "Popover component should contain the action buttons" + ); + assert.containsOnce( + document.body, + '.o_ActivityMarkDonePopover_doneScheduleNextButton', + "Popover component should contain the done & schedule next button" + ); + assert.containsNone( + document.body, + '.o_ActivityMarkDonePopover_doneButton', + "Popover component should NOT contain the done button" + ); + assert.containsOnce( + document.body, + '.o_ActivityMarkDonePopover_discardButton', + "Popover component should contain the discard button" + ); +}); + +QUnit.test('activity mark done popover mark done without feedback', async function (assert) { + assert.expect(7); + + await this.start({ + async mockRPC(route, args) { + if (route === '/web/dataset/call_kw/mail.activity/action_feedback') { + assert.step('action_feedback'); + assert.strictEqual(args.args.length, 1); + assert.strictEqual(args.args[0].length, 1); + assert.strictEqual(args.args[0][0], 12); + assert.strictEqual(args.kwargs.attachment_ids.length, 0); + assert.notOk(args.kwargs.feedback); + return; + } + if (route === '/web/dataset/call_kw/mail.activity/unlink') { + // 'unlink' on non-existing record raises a server crash + throw new Error("'unlink' RPC on activity must not be called (already unlinked from mark as done)"); + } + return this._super(...arguments); + }, + }); + const activity = this.env.models['mail.activity'].create({ + canWrite: true, + category: 'not_upload_file', + id: 12, + thread: [['insert', { id: 42, model: 'res.partner' }]], + }); + await this.createActivityMarkDonePopoverComponent(activity); + + document.querySelector('.o_ActivityMarkDonePopover_doneButton').click(); + assert.verifySteps( + ['action_feedback'], + "Mark done and schedule next button should call the right rpc" + ); +}); + +QUnit.test('activity mark done popover mark done with feedback', async function (assert) { + assert.expect(7); + + await this.start({ + async mockRPC(route, args) { + if (route === '/web/dataset/call_kw/mail.activity/action_feedback') { + assert.step('action_feedback'); + assert.strictEqual(args.args.length, 1); + assert.strictEqual(args.args[0].length, 1); + assert.strictEqual(args.args[0][0], 12); + assert.strictEqual(args.kwargs.attachment_ids.length, 0); + assert.strictEqual(args.kwargs.feedback, 'This task is done'); + return; + } + if (route === '/web/dataset/call_kw/mail.activity/unlink') { + // 'unlink' on non-existing record raises a server crash + throw new Error("'unlink' RPC on activity must not be called (already unlinked from mark as done)"); + } + return this._super(...arguments); + }, + }); + const activity = this.env.models['mail.activity'].create({ + canWrite: true, + category: 'not_upload_file', + id: 12, + thread: [['insert', { id: 42, model: 'res.partner' }]], + }); + await this.createActivityMarkDonePopoverComponent(activity); + + let feedbackTextarea = document.querySelector('.o_ActivityMarkDonePopover_feedback'); + feedbackTextarea.focus(); + document.execCommand('insertText', false, 'This task is done'); + document.querySelector('.o_ActivityMarkDonePopover_doneButton').click(); + assert.verifySteps( + ['action_feedback'], + "Mark done and schedule next button should call the right rpc" + ); +}); + +QUnit.test('activity mark done popover mark done and schedule next', async function (assert) { + assert.expect(6); + + const bus = new Bus(); + bus.on('do-action', null, payload => { + assert.step('activity_action'); + throw new Error("The do-action event should not be triggered when the route doesn't return an action"); + }); + await this.start({ + async mockRPC(route, args) { + if (route === '/web/dataset/call_kw/mail.activity/action_feedback_schedule_next') { + assert.step('action_feedback_schedule_next'); + assert.strictEqual(args.args.length, 1); + assert.strictEqual(args.args[0].length, 1); + assert.strictEqual(args.args[0][0], 12); + assert.strictEqual(args.kwargs.feedback, 'This task is done'); + return false; + } + if (route === '/web/dataset/call_kw/mail.activity/unlink') { + // 'unlink' on non-existing record raises a server crash + throw new Error("'unlink' RPC on activity must not be called (already unlinked from mark as done)"); + } + return this._super(...arguments); + }, + env: { bus }, + }); + const activity = this.env.models['mail.activity'].create({ + canWrite: true, + category: 'not_upload_file', + id: 12, + thread: [['insert', { id: 42, model: 'res.partner' }]], + }); + await this.createActivityMarkDonePopoverComponent(activity); + + let feedbackTextarea = document.querySelector('.o_ActivityMarkDonePopover_feedback'); + feedbackTextarea.focus(); + document.execCommand('insertText', false, 'This task is done'); + await afterNextRender(() => { + document.querySelector('.o_ActivityMarkDonePopover_doneScheduleNextButton').click(); + }); + assert.verifySteps( + ['action_feedback_schedule_next'], + "Mark done and schedule next button should call the right rpc and not trigger an action" + ); +}); + +QUnit.test('[technical] activity mark done & schedule next with new action', async function (assert) { + assert.expect(3); + + const bus = new Bus(); + bus.on('do-action', null, payload => { + assert.step('activity_action'); + assert.deepEqual( + payload.action, + { type: 'ir.actions.act_window' }, + "The content of the action should be correct" + ); + }); + await this.start({ + async mockRPC(route, args) { + if (route === '/web/dataset/call_kw/mail.activity/action_feedback_schedule_next') { + return { type: 'ir.actions.act_window' }; + } + return this._super(...arguments); + }, + env: { bus }, + }); + const activity = this.env.models['mail.activity'].create({ + canWrite: true, + category: 'not_upload_file', + id: 12, + thread: [['insert', { id: 42, model: 'res.partner' }]], + }); + await this.createActivityMarkDonePopoverComponent(activity); + + await afterNextRender(() => { + document.querySelector('.o_ActivityMarkDonePopover_doneScheduleNextButton').click(); + }); + assert.verifySteps( + ['activity_action'], + "The action returned by the route should be executed" + ); +}); + +}); +}); +}); + +}); |
