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/snailmail/static/src/components/message | |
| parent | 0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff) | |
initial commit 2
Diffstat (limited to 'addons/snailmail/static/src/components/message')
3 files changed, 804 insertions, 0 deletions
diff --git a/addons/snailmail/static/src/components/message/message.js b/addons/snailmail/static/src/components/message/message.js new file mode 100644 index 00000000..1033c901 --- /dev/null +++ b/addons/snailmail/static/src/components/message/message.js @@ -0,0 +1,87 @@ +odoo.define('snailmail/static/src/components/message/message.js', function (require) { +'use strict'; + +const components = { + Message: require('mail/static/src/components/message/message.js'), + SnailmailErrorDialog: require('snailmail/static/src/components/snailmail_error_dialog/snailmail_error_dialog.js'), + SnailmailNotificationPopover: require('snailmail/static/src/components/snailmail_notification_popover/snailmail_notification_popover.js'), +}; + +const { patch } = require('web.utils'); + +const { useState } = owl; + +Object.assign(components.Message.components, { + SnailmailErrorDialog: components.SnailmailErrorDialog, + SnailmailNotificationPopover: components.SnailmailNotificationPopover, +}); + +patch(components.Message, 'snailmail/static/src/components/message/message.js', { + /** + * @override + */ + _constructor() { + this._super(...arguments); + this.snailmailState = useState({ + // Determine if the error dialog is displayed. + hasDialog: false, + }); + }, + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * @override + */ + _onClickFailure() { + if (this.message.message_type === 'snailmail') { + /** + * Messages from snailmail are considered to have at most one + * notification. The failure type of the whole message is considered + * to be the same as the one from that first notification, and the + * click action will depend on it. + */ + switch (this.message.notifications[0].failure_type) { + case 'sn_credit': + // URL only used in this component, not received at init + this.env.messaging.fetchSnailmailCreditsUrl(); + this.snailmailState.hasDialog = true; + break; + case 'sn_error': + this.snailmailState.hasDialog = true; + break; + case 'sn_fields': + this.message.openMissingFieldsLetterAction(); + break; + case 'sn_format': + this.message.openFormatLetterAction(); + break; + case 'sn_price': + this.snailmailState.hasDialog = true; + break; + case 'sn_trial': + // URL only used in this component, not received at init + this.env.messaging.fetchSnailmailCreditsUrlTrial(); + this.snailmailState.hasDialog = true; + break; + } + } else { + this._super(...arguments); + } + }, + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * @private + */ + _onDialogClosedSnailmailError() { + this.snailmailState.hasDialog = false; + }, +}); + +}); diff --git a/addons/snailmail/static/src/components/message/message.xml b/addons/snailmail/static/src/components/message/message.xml new file mode 100644 index 00000000..d563083f --- /dev/null +++ b/addons/snailmail/static/src/components/message/message.xml @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="UTF-8"?> +<templates xml:space="preserve"> + + <t t-inherit="mail.Message" t-inherit-mode="extension"> + <xpath expr="//*[@name='failureIcon']" position="replace"> + <t t-if="message.message_type === 'snailmail'"> + <i class="o_Message_notificationIcon fa fa-paper-plane"/> + </t> + <t t-else="">$0</t> + </xpath> + + <xpath expr="//*[@name='notificationIcon']" position="replace"> + <t t-if="message.message_type === 'snailmail'"> + <i class="o_Message_notificationIcon fa fa-paper-plane"/> + </t> + <t t-else="">$0</t> + </xpath> + + <xpath expr="//*[@name='rootCondition']" position="inside"> + <t t-if="snailmailState.hasDialog"> + <SnailmailErrorDialog messageLocalId="message.localId" t-on-dialog-closed="_onDialogClosedSnailmailError"/> + </t> + </xpath> + + <!-- + It was decided that the information displayed for snailmail messages + has to be different than for standard messages, see task-1907998. + --> + <xpath expr="//NotificationPopover" position="replace"> + <t t-if="message.message_type === 'snailmail'"> + <SnailmailNotificationPopover messageLocalId="message.localId"/> + </t> + <t t-else="">$0</t> + </xpath> + </t> + +</templates> diff --git a/addons/snailmail/static/src/components/message/message_tests.js b/addons/snailmail/static/src/components/message/message_tests.js new file mode 100644 index 00000000..67044509 --- /dev/null +++ b/addons/snailmail/static/src/components/message/message_tests.js @@ -0,0 +1,680 @@ +odoo.define('snailmail/static/src/components/message/message_tests.js', function (require) { +'use strict'; + +const components = { + Message: require('mail/static/src/components/message/message.js'), +}; +const { + afterEach, + afterNextRender, + beforeEach, + createRootComponent, + start, +} = require('mail/static/src/utils/test_utils.js'); + +const Bus = require('web.Bus'); + +QUnit.module('snailmail', {}, function () { +QUnit.module('components', {}, function () { +QUnit.module('message', {}, function () { +QUnit.module('message_tests.js', { + beforeEach() { + beforeEach(this); + + this.createMessageComponent = async (message, otherProps) => { + const props = Object.assign({ messageLocalId: message.localId }, otherProps); + await createRootComponent(this, components.Message, { + props, + 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('Sent', async function (assert) { + assert.expect(8); + + await this.start(); + const threadViewer = this.env.models['mail.thread_viewer'].create({ + hasThreadView: true, + thread: [['create', { + id: 11, + model: 'mail.channel', + }]], + }); + const message = this.env.models['mail.message'].create({ + id: 10, + message_type: 'snailmail', + notifications: [['insert', { + id: 11, + notification_status: 'sent', + notification_type: 'snail', + }]], + originThread: [['link', threadViewer.thread]], + }); + await this.createMessageComponent(message, { + threadViewLocalId: threadViewer.threadView.localId + }); + + assert.containsOnce( + document.body, + '.o_Message', + "should display a message component" + ); + assert.containsOnce( + document.body, + '.o_Message_notificationIconClickable', + "should display the notification icon container" + ); + assert.containsOnce( + document.body, + '.o_Message_notificationIcon', + "should display the notification icon" + ); + assert.hasClass( + document.querySelector('.o_Message_notificationIcon'), + 'fa-paper-plane', + "icon should represent snailmail" + ); + + await afterNextRender(() => { + document.querySelector('.o_Message_notificationIconClickable').click(); + }); + assert.containsOnce( + document.body, + '.o_SnailmailNotificationPopover', + "notification popover should be open" + ); + assert.containsOnce( + document.body, + '.o_SnailmailNotificationPopover_icon', + "popover should have one icon" + ); + assert.hasClass( + document.querySelector('.o_SnailmailNotificationPopover_icon'), + 'fa-check', + "popover should have the sent icon" + ); + assert.strictEqual( + document.querySelector('.o_SnailmailNotificationPopover').textContent.trim(), + "Sent", + "popover should have the sent text" + ); +}); + +QUnit.test('Canceled', async function (assert) { + assert.expect(8); + + await this.start(); + const threadViewer = this.env.models['mail.thread_viewer'].create({ + hasThreadView: true, + thread: [['create', { + id: 11, + model: 'mail.channel', + }]], + }); + const message = this.env.models['mail.message'].create({ + id: 10, + message_type: 'snailmail', + notifications: [['insert', { + id: 11, + notification_status: 'canceled', + notification_type: 'snail', + }]], + originThread: [['link', threadViewer.thread]], + }); + await this.createMessageComponent(message, { + threadViewLocalId: threadViewer.threadView.localId + }); + + assert.containsOnce( + document.body, + '.o_Message', + "should display a message component" + ); + assert.containsOnce( + document.body, + '.o_Message_notificationIconClickable', + "should display the notification icon container" + ); + assert.containsOnce( + document.body, + '.o_Message_notificationIcon', + "should display the notification icon" + ); + assert.hasClass( + document.querySelector('.o_Message_notificationIcon'), + 'fa-paper-plane', + "icon should represent snailmail" + ); + + await afterNextRender(() => { + document.querySelector('.o_Message_notificationIconClickable').click(); + }); + assert.containsOnce( + document.body, + '.o_SnailmailNotificationPopover', + "notification popover should be open" + ); + assert.containsOnce( + document.body, + '.o_SnailmailNotificationPopover_icon', + "popover should have one icon" + ); + assert.hasClass( + document.querySelector('.o_SnailmailNotificationPopover_icon'), + 'fa-trash-o', + "popover should have the canceled icon" + ); + assert.strictEqual( + document.querySelector('.o_SnailmailNotificationPopover').textContent.trim(), + "Canceled", + "popover should have the canceled text" + ); +}); + +QUnit.test('Pending', async function (assert) { + assert.expect(8); + + await this.start(); + const threadViewer = this.env.models['mail.thread_viewer'].create({ + hasThreadView: true, + thread: [['create', { + id: 11, + model: 'mail.channel', + }]], + }); + const message = this.env.models['mail.message'].create({ + id: 10, + message_type: 'snailmail', + notifications: [['insert', { + id: 11, + notification_status: 'ready', + notification_type: 'snail', + }]], + originThread: [['link', threadViewer.thread]], + }); + await this.createMessageComponent(message, { + threadViewLocalId: threadViewer.threadView.localId + }); + + assert.containsOnce( + document.body, + '.o_Message', + "should display a message component" + ); + assert.containsOnce( + document.body, + '.o_Message_notificationIconClickable', + "should display the notification icon container" + ); + assert.containsOnce( + document.body, + '.o_Message_notificationIcon', + "should display the notification icon" + ); + assert.hasClass( + document.querySelector('.o_Message_notificationIcon'), + 'fa-paper-plane', + "icon should represent snailmail" + ); + + await afterNextRender(() => { + document.querySelector('.o_Message_notificationIconClickable').click(); + }); + assert.containsOnce( + document.body, + '.o_SnailmailNotificationPopover', + "notification popover should be open" + ); + assert.containsOnce( + document.body, + '.o_SnailmailNotificationPopover_icon', + "popover should have one icon" + ); + assert.hasClass( + document.querySelector('.o_SnailmailNotificationPopover_icon'), + 'fa-clock-o', + "popover should have the pending icon" + ); + assert.strictEqual( + document.querySelector('.o_SnailmailNotificationPopover').textContent.trim(), + "Awaiting Dispatch", + "popover should have the pending text" + ); +}); + +QUnit.test('No Price Available', async function (assert) { + assert.expect(10); + + await this.start({ + async mockRPC(route, args) { + if (args.method === 'cancel_letter' && args.model === 'mail.message' && args.args[0][0] === 10) { + assert.step(args.method); + } + return this._super(...arguments); + }, + }); + const threadViewer = this.env.models['mail.thread_viewer'].create({ + hasThreadView: true, + thread: [['create', { + id: 11, + model: 'mail.channel', + }]], + }); + const message = this.env.models['mail.message'].create({ + id: 10, + message_type: 'snailmail', + notifications: [['insert', { + failure_type: 'sn_price', + id: 11, + notification_status: 'exception', + notification_type: 'snail', + }]], + originThread: [['link', threadViewer.thread]], + }); + await this.createMessageComponent(message, { + threadViewLocalId: threadViewer.threadView.localId + }); + + assert.containsOnce( + document.body, + '.o_Message', + "should display a message component" + ); + assert.containsOnce( + document.body, + '.o_Message_notificationIconClickable', + "should display the notification icon container" + ); + assert.containsOnce( + document.body, + '.o_Message_notificationIcon', + "should display the notification icon" + ); + assert.hasClass( + document.querySelector('.o_Message_notificationIcon'), + 'fa-paper-plane', + "icon should represent snailmail" + ); + + await afterNextRender(() => { + document.querySelector('.o_Message_notificationIconClickable').click(); + }); + assert.containsOnce( + document.body, + '.o_SnailmailErrorDialog', + "error dialog should be open" + ); + assert.containsOnce( + document.body, + '.o_SnailmailErrorDialog_contentPrice', + "error dialog should have the 'no price' content" + ); + assert.containsOnce( + document.body, + '.o_SnailmailErrorDialog_cancelLetterButton', + "dialog should have a 'Cancel letter' button" + ); + + await afterNextRender(() => { + document.querySelector('.o_SnailmailErrorDialog_cancelLetterButton').click(); + }); + assert.containsNone( + document.body, + '.o_SnailmailErrorDialog', + "dialog should be closed after click on 'Cancel letter'" + ); + assert.verifySteps( + ['cancel_letter'], + "should have made a RPC call to 'cancel_letter'" + ); +}); + +QUnit.test('Credit Error', async function (assert) { + assert.expect(11); + + await this.start({ + async mockRPC(route, args) { + if (args.method === 'send_letter' && args.model === 'mail.message' && args.args[0][0] === 10) { + assert.step(args.method); + } + return this._super(...arguments); + }, + }); + const threadViewer = this.env.models['mail.thread_viewer'].create({ + hasThreadView: true, + thread: [['create', { + id: 11, + model: 'mail.channel', + }]], + }); + const message = this.env.models['mail.message'].create({ + id: 10, + message_type: 'snailmail', + notifications: [['insert', { + failure_type: 'sn_credit', + id: 11, + notification_status: 'exception', + notification_type: 'snail', + }]], + originThread: [['link', threadViewer.thread]], + }); + await this.createMessageComponent(message, { + threadViewLocalId: threadViewer.threadView.localId + }); + + assert.containsOnce( + document.body, + '.o_Message', + "should display a message component" + ); + assert.containsOnce( + document.body, + '.o_Message_notificationIconClickable', + "should display the notification icon container" + ); + assert.containsOnce( + document.body, + '.o_Message_notificationIcon', + "should display the notification icon" + ); + assert.hasClass( + document.querySelector('.o_Message_notificationIcon'), + 'fa-paper-plane', + "icon should represent snailmail" + ); + + await afterNextRender(() => { + document.querySelector('.o_Message_notificationIconClickable').click(); + }); + assert.containsOnce( + document.body, + '.o_SnailmailErrorDialog', + "error dialog should be open" + ); + assert.containsOnce( + document.body, + '.o_SnailmailErrorDialog_contentCredit', + "error dialog should have the 'credit' content" + ); + assert.containsOnce( + document.body, + '.o_SnailmailErrorDialog_resendLetterButton', + "dialog should have a 'Re-send letter' button" + ); + assert.containsOnce( + document.body, + '.o_SnailmailErrorDialog_cancelLetterButton', + "dialog should have a 'Cancel letter' button" + ); + + await afterNextRender(() => { + document.querySelector('.o_SnailmailErrorDialog_resendLetterButton').click(); + }); + assert.containsNone( + document.body, + '.o_SnailmailErrorDialog', + "dialog should be closed after click on 'Re-send letter'" + ); + assert.verifySteps( + ['send_letter'], + "should have made a RPC call to 'send_letter'" + ); +}); + +QUnit.test('Trial Error', async function (assert) { + assert.expect(11); + + await this.start({ + async mockRPC(route, args) { + if (args.method === 'send_letter' && args.model === 'mail.message' && args.args[0][0] === 10) { + assert.step(args.method); + } + return this._super(...arguments); + }, + }); + const threadViewer = this.env.models['mail.thread_viewer'].create({ + hasThreadView: true, + thread: [['create', { + id: 11, + model: 'mail.channel', + }]], + }); + const message = this.env.models['mail.message'].create({ + id: 10, + message_type: 'snailmail', + notifications: [['insert', { + failure_type: 'sn_trial', + id: 11, + notification_status: 'exception', + notification_type: 'snail', + }]], + originThread: [['link', threadViewer.thread]], + }); + await this.createMessageComponent(message, { + threadViewLocalId: threadViewer.threadView.localId + }); + + assert.containsOnce( + document.body, + '.o_Message', + "should display a message component" + ); + assert.containsOnce( + document.body, + '.o_Message_notificationIconClickable', + "should display the notification icon container" + ); + assert.containsOnce( + document.body, + '.o_Message_notificationIcon', + "should display the notification icon" + ); + assert.hasClass( + document.querySelector('.o_Message_notificationIcon'), + 'fa-paper-plane', + "icon should represent snailmail" + ); + + await afterNextRender(() => { + document.querySelector('.o_Message_notificationIconClickable').click(); + }); + assert.containsOnce( + document.body, + '.o_SnailmailErrorDialog', + "error dialog should be open" + ); + assert.containsOnce( + document.body, + '.o_SnailmailErrorDialog_contentTrial', + "error dialog should have the 'trial' content" + ); + assert.containsOnce( + document.body, + '.o_SnailmailErrorDialog_resendLetterButton', + "dialog should have a 'Re-send letter' button" + ); + assert.containsOnce( + document.body, + '.o_SnailmailErrorDialog_cancelLetterButton', + "dialog should have a 'Cancel letter' button" + ); + + await afterNextRender(() => { + document.querySelector('.o_SnailmailErrorDialog_resendLetterButton').click(); + }); + assert.containsNone( + document.body, + '.o_SnailmailErrorDialog', + "dialog should be closed after click on 'Re-send letter'" + ); + assert.verifySteps( + ['send_letter'], + "should have made a RPC call to 'send_letter'" + ); +}); + +QUnit.test('Format Error', async function (assert) { + assert.expect(8); + + const bus = new Bus(); + bus.on('do-action', null, payload => { + assert.step('do_action'); + assert.strictEqual( + payload.action, + 'snailmail.snailmail_letter_format_error_action', + "action should be the one for format error" + ); + assert.strictEqual( + payload.options.additional_context.message_id, + 10, + "action should have correct message id" + ); + }); + + await this.start({ env: { bus } }); + const threadViewer = this.env.models['mail.thread_viewer'].create({ + hasThreadView: true, + thread: [['create', { + id: 11, + model: 'mail.channel', + }]], + }); + const message = this.env.models['mail.message'].create({ + id: 10, + message_type: 'snailmail', + notifications: [['insert', { + failure_type: 'sn_format', + id: 11, + notification_status: 'exception', + notification_type: 'snail', + }]], + originThread: [['link', threadViewer.thread]], + }); + await this.createMessageComponent(message, { + threadViewLocalId: threadViewer.threadView.localId + }); + + assert.containsOnce( + document.body, + '.o_Message', + "should display a message component" + ); + assert.containsOnce( + document.body, + '.o_Message_notificationIconClickable', + "should display the notification icon container" + ); + assert.containsOnce( + document.body, + '.o_Message_notificationIcon', + "should display the notification icon" + ); + assert.hasClass( + document.querySelector('.o_Message_notificationIcon'), + 'fa-paper-plane', + "icon should represent snailmail" + ); + + await afterNextRender(() => { + document.querySelector('.o_Message_notificationIconClickable').click(); + }); + assert.verifySteps( + ['do_action'], + "should do an action to display the format error dialog" + ); +}); + +QUnit.test('Missing Required Fields', async function (assert) { + assert.expect(8); + + this.data['mail.message'].records.push({ + id: 10, // random unique id, useful to link letter and notification + message_type: 'snailmail', + res_id: 20, // non 0 id, necessary to fetch failure at init + model: 'res.partner', // not mail.compose.message, necessary to fetch failure at init + }); + this.data['mail.notification'].records.push({ + failure_type: 'sn_fields', + mail_message_id: 10, + notification_status: 'exception', + notification_type: 'snail', + }); + this.data['snailmail.letter'].records.push({ + id: 22, // random unique id, will be asserted in the test + message_id: 10, // id of related message + }); + const bus = new Bus(); + bus.on('do-action', null, payload => { + assert.step('do_action'); + assert.strictEqual( + payload.action, + 'snailmail.snailmail_letter_missing_required_fields_action', + "action should be the one for missing fields" + ); + assert.strictEqual( + payload.options.additional_context.default_letter_id, + 22, + "action should have correct letter id" + ); + }); + + await this.start({ + env: { bus }, + }); + const threadViewer = this.env.models['mail.thread_viewer'].create({ + hasThreadView: true, + thread: [['insert', { id: 20, model: 'res.partner' }]], + }); + const message = this.env.models['mail.message'].findFromIdentifyingData({ id: 10 }); + await this.createMessageComponent(message, { + threadViewLocalId: threadViewer.threadView.localId, + }); + + assert.containsOnce( + document.body, + '.o_Message', + "should display a message component" + ); + assert.containsOnce( + document.body, + '.o_Message_notificationIconClickable', + "should display the notification icon container" + ); + assert.containsOnce( + document.body, + '.o_Message_notificationIcon', + "should display the notification icon" + ); + assert.hasClass( + document.querySelector('.o_Message_notificationIcon'), + 'fa-paper-plane', + "icon should represent snailmail" + ); + + await afterNextRender(() => { + document.querySelector('.o_Message_notificationIconClickable').click(); + }); + assert.verifySteps( + ['do_action'], + "an action should be done to display the missing fields dialog" + ); +}); + +}); +}); +}); + +}); |
