[FIX] mail: ask for confirmation before attachment deletion
also hide all messages with empty body and no attachments task-2282322 closes odoo/odoo#59675 X-original-commit: 60ae42dcda777cd66e4e421dbc02f9ef12a430ef Related: odoo/enterprise#13974 Signed-off-by: Alexandre Kühn (aku) <aku@odoo.com> Signed-off-by: Sébastien Theys (seb) <seb@odoo.com>
This commit is contained in:
committed by
Sébastien Theys
parent
30036759e8
commit
ec3f5cdaf8
@@ -3,7 +3,11 @@ odoo.define('mail/static/src/components/attachment/attachment.js', function (req
|
||||
|
||||
const useStore = require('mail/static/src/component_hooks/use_store/use_store.js');
|
||||
|
||||
const { Component } = owl;
|
||||
const components = {
|
||||
AttachmentDeleteConfirmDialog: require('mail/static/src/components/attachment_delete_confirm_dialog/attachment_delete_confirm_dialog.js'),
|
||||
};
|
||||
|
||||
const { Component, useState } = owl;
|
||||
|
||||
class Attachment extends Component {
|
||||
|
||||
@@ -18,6 +22,9 @@ class Attachment extends Component {
|
||||
attachment: attachment ? attachment.__state : undefined,
|
||||
};
|
||||
});
|
||||
this.state = useState({
|
||||
hasDeleteConfirmDialog: false,
|
||||
});
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
@@ -125,13 +132,24 @@ class Attachment extends Component {
|
||||
*/
|
||||
_onClickUnlink(ev) {
|
||||
ev.stopPropagation();
|
||||
this.attachment.remove();
|
||||
this.trigger('o-attachment-removed');
|
||||
if (this.attachment.isLinkedToComposer) {
|
||||
this.attachment.remove();
|
||||
this.trigger('o-attachment-removed', { attachmentLocalId: this.props.attachmentLocalId });
|
||||
} else {
|
||||
this.state.hasDeleteConfirmDialog = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_onDeleteConfirmDialogClosed() {
|
||||
this.state.hasDeleteConfirmDialog = false;
|
||||
}
|
||||
}
|
||||
|
||||
Object.assign(Attachment, {
|
||||
components,
|
||||
defaultProps: {
|
||||
attachmentLocalIds: [],
|
||||
detailsMode: 'auto',
|
||||
|
||||
@@ -102,6 +102,12 @@
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
<t t-if="state.hasDeleteConfirmDialog">
|
||||
<AttachmentDeleteConfirmDialog
|
||||
attachmentLocalId="props.attachmentLocalId"
|
||||
t-on-dialog-closed="_onDeleteConfirmDialogClosed"
|
||||
/>
|
||||
</t>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
@@ -284,6 +284,54 @@ QUnit.test('view attachments', async function (assert) {
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('remove attachment should ask for confirmation', async function (assert) {
|
||||
assert.expect(5);
|
||||
|
||||
await this.start();
|
||||
const thread = this.env.models['mail.thread'].create({
|
||||
attachments: [
|
||||
['insert', {
|
||||
id: 143,
|
||||
mimetype: 'text/plain',
|
||||
name: 'Blah.txt'
|
||||
}],
|
||||
],
|
||||
id: 100,
|
||||
model: 'res.partner',
|
||||
});
|
||||
await this.createAttachmentBoxComponent(thread);
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
'.o_Attachment',
|
||||
"should have an attachment",
|
||||
);
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
'.o_Attachment_actionUnlink',
|
||||
"attachment should have a delete button"
|
||||
);
|
||||
|
||||
await afterNextRender(() => document.querySelector('.o_Attachment_actionUnlink').click());
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
'.o_AttachmentDeleteConfirmDialog',
|
||||
"A confirmation dialog should have been opened"
|
||||
);
|
||||
assert.strictEqual(
|
||||
document.querySelector('.o_AttachmentDeleteConfirmDialog_mainText').textContent,
|
||||
`Do you really want to delete "Blah.txt"?`,
|
||||
"Confirmation dialog should contain the attachment delete confirmation text"
|
||||
);
|
||||
|
||||
// Confirm the deletion
|
||||
await afterNextRender(() => document.querySelector('.o_AttachmentDeleteConfirmDialog_confirmButton').click());
|
||||
assert.containsNone(
|
||||
document.body,
|
||||
'.o_Attachment',
|
||||
"should no longer have an attachment",
|
||||
);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+85
-1
@@ -1,6 +1,90 @@
|
||||
odoo.define('mail/static/src/components/attachment_delete_confirm_dialog/attachment_delete_confirm_dialog.js', function (require) {
|
||||
'use strict';
|
||||
|
||||
// TODO FIXME file to be completed in task-2282322
|
||||
const useStore = require('mail/static/src/component_hooks/use_store/use_store.js');
|
||||
|
||||
const components = {
|
||||
Dialog: require('web.OwlDialog'),
|
||||
};
|
||||
|
||||
const { Component } = owl;
|
||||
const { useRef } = owl.hooks;
|
||||
|
||||
class AttachmentDeleteConfirmDialog extends Component {
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
useStore(props => {
|
||||
const attachment = this.env.models['mail.attachment'].get(props.attachmentLocalId);
|
||||
return {
|
||||
attachment: attachment ? attachment.__state : undefined,
|
||||
};
|
||||
});
|
||||
// to manually trigger the dialog close event
|
||||
this._dialogRef = useRef('dialog');
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Public
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @returns {mail.attachment}
|
||||
*/
|
||||
get attachment() {
|
||||
return this.env.models['mail.attachment'].get(this.props.attachmentLocalId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
getBody() {
|
||||
return _.str.sprintf(
|
||||
this.env._t(`Do you really want to delete "%s"?`),
|
||||
owl.utils.escape(this.attachment.displayName)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
getTitle() {
|
||||
return this.env._t("Confirmation");
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Handlers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_onClickCancel() {
|
||||
this._dialogRef.comp._close();
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_onClickOk() {
|
||||
this._dialogRef.comp._close();
|
||||
this.attachment.remove();
|
||||
this.trigger('o-attachment-removed', { attachmentLocalId: this.props.attachmentLocalId });
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Object.assign(AttachmentDeleteConfirmDialog, {
|
||||
components,
|
||||
props: {
|
||||
attachmentLocalId: String,
|
||||
},
|
||||
template: 'mail.AttachmentDeleteConfirmDialog',
|
||||
});
|
||||
|
||||
return AttachmentDeleteConfirmDialog;
|
||||
|
||||
});
|
||||
|
||||
+9
-1
@@ -1,4 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
<!-- TODO FIXME file to be completed in task-2282322 -->
|
||||
<t t-name="mail.AttachmentDeleteConfirmDialog" owl="1">
|
||||
<Dialog contentClass="'o_AttachmentDeleteConfirmDialog'" title="getTitle()" size="'medium'" t-ref="dialog">
|
||||
<p class="o_AttachmentDeleteConfirmDialog_mainText" t-esc="getBody()"/>
|
||||
<t t-set-slot="buttons">
|
||||
<button class="o_AttachmentDeleteConfirmDialog_confirmButton btn btn-primary" t-on-click="_onClickOk">Ok</button>
|
||||
<button class="o_AttachmentDeleteConfirmDialog_cancelButton btn btn-secondary" t-on-click="_onClickCancel">Cancel</button>
|
||||
</t>
|
||||
</Dialog>
|
||||
</t>
|
||||
</templates>
|
||||
|
||||
@@ -802,9 +802,8 @@ QUnit.test('[technical] chat window: scroll conservation on toggle home menu', a
|
||||
this.data['mail.channel'].records.push({ id: 20 });
|
||||
for (let i = 0; i < 10; i++) {
|
||||
this.data['mail.message'].records.push({
|
||||
body: "not empty",
|
||||
channel_ids: [20],
|
||||
model: 'mail.channel',
|
||||
res_id: 20,
|
||||
});
|
||||
}
|
||||
await this.start();
|
||||
@@ -1543,9 +1542,8 @@ QUnit.test('chat window with a thread: keep scroll position in message list on f
|
||||
this.data['mail.channel'].records.push({ id: 20 });
|
||||
for (let i = 0; i < 10; i++) {
|
||||
this.data['mail.message'].records.push({
|
||||
body: "not empty",
|
||||
channel_ids: [20],
|
||||
model: 'mail.channel',
|
||||
res_id: 20,
|
||||
});
|
||||
}
|
||||
await this.start();
|
||||
@@ -1588,9 +1586,8 @@ QUnit.test('chat window should scroll to the newly posted message just after pos
|
||||
});
|
||||
for (let i = 0; i < 10; i++) {
|
||||
this.data['mail.message'].records.push({
|
||||
body: "not empty",
|
||||
channel_ids: [20],
|
||||
model: 'mail.channel',
|
||||
res_id: 20,
|
||||
});
|
||||
}
|
||||
await this.start();
|
||||
@@ -1710,9 +1707,8 @@ QUnit.test('[technical] chat window with a thread: keep scroll position in messa
|
||||
this.data['mail.channel'].records.push({ id: 20 });
|
||||
for (let i = 0; i < 10; i++) {
|
||||
this.data['mail.message'].records.push({
|
||||
body: "not empty",
|
||||
channel_ids: [20],
|
||||
model: 'mail.channel',
|
||||
res_id: 20,
|
||||
});
|
||||
}
|
||||
await this.start();
|
||||
|
||||
@@ -47,6 +47,7 @@ QUnit.test('base rendering when chatter has no attachment', async function (asse
|
||||
this.data['res.partner'].records.push({ id: 100 });
|
||||
for (let i = 0; i < 60; i++) {
|
||||
this.data['mail.message'].records.push({
|
||||
body: "not empty",
|
||||
model: 'res.partner',
|
||||
res_id: 100,
|
||||
});
|
||||
|
||||
@@ -1746,6 +1746,44 @@ QUnit.test('warning on send with shortcut when attempting to post message with s
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('remove an attachment from composer does not need any confirmation', async function (assert) {
|
||||
assert.expect(3);
|
||||
|
||||
await this.start();
|
||||
const composer = this.env.models['mail.composer'].create();
|
||||
await this.createComposerComponent(composer);
|
||||
const file = await createFile({
|
||||
content: 'hello, world',
|
||||
contentType: 'text/plain',
|
||||
name: 'text.txt',
|
||||
});
|
||||
await afterNextRender(() =>
|
||||
inputFiles(
|
||||
document.querySelector('.o_FileUploader_input'),
|
||||
[file]
|
||||
)
|
||||
);
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
'.o_Composer_attachmentList',
|
||||
"should have an attachment list"
|
||||
);
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
'.o_Composer .o_Attachment',
|
||||
"should have only one attachment"
|
||||
);
|
||||
|
||||
await afterNextRender(() =>
|
||||
document.querySelector('.o_Attachment_asideItemUnlink').click()
|
||||
);
|
||||
assert.containsNone(
|
||||
document.body,
|
||||
'.o_Composer .o_Attachment',
|
||||
"should not have any attachment left after unlinking the only one"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('remove an uploading attachment', async function (assert) {
|
||||
assert.expect(4);
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@ QUnit.test('reply: discard on pressing escape', async function (assert) {
|
||||
});
|
||||
// message expected to be found in inbox
|
||||
this.data['mail.message'].records.push({
|
||||
body: "not empty",
|
||||
model: 'res.partner',
|
||||
needaction: true,
|
||||
needaction_partner_ids: [this.data.currentPartnerId],
|
||||
@@ -143,6 +144,7 @@ QUnit.test('reply: discard on discard button click', async function (assert) {
|
||||
assert.expect(4);
|
||||
|
||||
this.data['mail.message'].records.push({
|
||||
body: "not empty",
|
||||
model: 'res.partner',
|
||||
needaction: true,
|
||||
needaction_partner_ids: [this.data.currentPartnerId],
|
||||
@@ -183,6 +185,7 @@ QUnit.test('reply: discard on reply button toggle', async function (assert) {
|
||||
assert.expect(3);
|
||||
|
||||
this.data['mail.message'].records.push({
|
||||
body: "not empty",
|
||||
model: 'res.partner',
|
||||
needaction: true,
|
||||
needaction_partner_ids: [this.data.currentPartnerId],
|
||||
@@ -218,6 +221,7 @@ QUnit.test('reply: discard on click away', async function (assert) {
|
||||
assert.expect(7);
|
||||
|
||||
this.data['mail.message'].records.push({
|
||||
body: "not empty",
|
||||
model: 'res.partner',
|
||||
needaction: true,
|
||||
needaction_partner_ids: [this.data.currentPartnerId],
|
||||
@@ -284,6 +288,7 @@ QUnit.test('"reply to" composer should log note if message replied to is a note'
|
||||
assert.expect(6);
|
||||
|
||||
this.data['mail.message'].records.push({
|
||||
body: "not empty",
|
||||
is_discussion: false,
|
||||
model: 'res.partner',
|
||||
needaction: true,
|
||||
@@ -336,6 +341,7 @@ QUnit.test('"reply to" composer should send message if message replied to is not
|
||||
assert.expect(6);
|
||||
|
||||
this.data['mail.message'].records.push({
|
||||
body: "not empty",
|
||||
is_discussion: true,
|
||||
model: 'res.partner',
|
||||
needaction: true,
|
||||
@@ -388,6 +394,7 @@ QUnit.test('error notifications should not be shown in Inbox', async function (a
|
||||
assert.expect(3);
|
||||
|
||||
this.data['mail.message'].records.push({
|
||||
body: "not empty",
|
||||
id: 100,
|
||||
model: 'mail.channel',
|
||||
needaction: true,
|
||||
@@ -422,6 +429,7 @@ QUnit.test('show subject of message in Inbox', async function (assert) {
|
||||
assert.expect(3);
|
||||
|
||||
this.data['mail.message'].records.push({
|
||||
body: "not empty",
|
||||
model: 'mail.channel', // random existing model
|
||||
needaction: true, // message_fetch domain
|
||||
needaction_partner_ids: [this.data.currentPartnerId], // not needed, for consistency
|
||||
@@ -449,6 +457,7 @@ QUnit.test('show subject of message in history', async function (assert) {
|
||||
assert.expect(3);
|
||||
|
||||
this.data['mail.message'].records.push({
|
||||
body: "not empty",
|
||||
history_partner_ids: [3], // not needed, for consistency
|
||||
model: 'mail.channel', // random existing model
|
||||
subject: "Salutations, voyageur", // will be asserted in the test
|
||||
@@ -509,6 +518,7 @@ QUnit.test('click on (non-channel/non-partner) origin thread link should redirec
|
||||
});
|
||||
this.data['some.model'] = { fields: {}, records: [{ id: 10 }] };
|
||||
this.data['mail.message'].records.push({
|
||||
body: "not empty",
|
||||
model: 'some.model',
|
||||
needaction: true,
|
||||
needaction_partner_ids: [this.data.currentPartnerId],
|
||||
|
||||
@@ -747,6 +747,7 @@ QUnit.test('as author, sent message accepted in moderated channel', async functi
|
||||
name: "general", // random name, will be asserted in the test
|
||||
});
|
||||
this.data['mail.message'].records.push({
|
||||
body: "not empty",
|
||||
id: 100, // random unique id, will be referenced in the test
|
||||
model: 'mail.channel', // expected value to link message to channel
|
||||
moderation_status: 'pending_moderation', // message is expected to be pending
|
||||
@@ -820,6 +821,7 @@ QUnit.test('as author, sent message rejected in moderated channel', async functi
|
||||
name: "general", // random name, will be asserted in the test
|
||||
});
|
||||
this.data['mail.message'].records.push({
|
||||
body: "not empty",
|
||||
id: 100, // random unique id, will be referenced in the test
|
||||
model: 'mail.channel', // expected value to link message to channel
|
||||
moderation_status: 'pending_moderation', // message is expected to be pending
|
||||
@@ -884,6 +886,7 @@ QUnit.test('as moderator, pending moderation message accessibility', async funct
|
||||
moderation: true, // channel must be moderated to test the feature
|
||||
});
|
||||
this.data['mail.message'].records.push({
|
||||
body: "not empty",
|
||||
id: 100, // random unique id, will be referenced in the test
|
||||
model: 'mail.channel', // expected value to link message to channel
|
||||
moderation_status: 'pending_moderation', // message is expected to be pending
|
||||
@@ -936,6 +939,7 @@ QUnit.test('as author, pending moderation message should appear in origin thread
|
||||
});
|
||||
this.data['mail.message'].records.push({
|
||||
author_id: this.data.currentPartnerId, // test as author of message
|
||||
body: "not empty",
|
||||
id: 100, // random unique id, will be referenced in the test
|
||||
model: 'mail.channel', // expected value to link message to channel
|
||||
moderation_status: 'pending_moderation', // message is expected to be pending
|
||||
@@ -983,6 +987,7 @@ QUnit.test('as moderator, new pending moderation message posted by someone else'
|
||||
// simulate receiving the message
|
||||
const messageData = {
|
||||
author_id: 10, // random id, different than current partner
|
||||
body: "not empty",
|
||||
channel_ids: [], // server do NOT return channel_id of the message if pending moderation
|
||||
id: 1, // random unique id
|
||||
model: 'mail.channel', // expected value to link message to channel
|
||||
@@ -1027,16 +1032,19 @@ QUnit.test('accept multiple moderation messages', async function (assert) {
|
||||
});
|
||||
this.data['mail.message'].records.push(
|
||||
{
|
||||
body: "not empty",
|
||||
model: 'mail.channel',
|
||||
moderation_status: 'pending_moderation',
|
||||
res_id: 20,
|
||||
},
|
||||
{
|
||||
body: "not empty",
|
||||
model: 'mail.channel',
|
||||
moderation_status: 'pending_moderation',
|
||||
res_id: 20,
|
||||
},
|
||||
{
|
||||
body: "not empty",
|
||||
model: 'mail.channel',
|
||||
moderation_status: 'pending_moderation',
|
||||
res_id: 20,
|
||||
@@ -1100,16 +1108,19 @@ QUnit.test('accept multiple moderation messages after having accepted other mess
|
||||
});
|
||||
this.data['mail.message'].records.push(
|
||||
{
|
||||
body: "not empty",
|
||||
model: 'mail.channel',
|
||||
moderation_status: 'pending_moderation',
|
||||
res_id: 20,
|
||||
},
|
||||
{
|
||||
body: "not empty",
|
||||
model: 'mail.channel',
|
||||
moderation_status: 'pending_moderation',
|
||||
res_id: 20,
|
||||
},
|
||||
{
|
||||
body: "not empty",
|
||||
model: 'mail.channel',
|
||||
moderation_status: 'pending_moderation',
|
||||
res_id: 20,
|
||||
|
||||
@@ -513,6 +513,7 @@ QUnit.test('sidebar: channel rendering with needaction counter', async function
|
||||
this.data['mail.channel'].records.push({ id: 20 });
|
||||
// expected needaction message
|
||||
this.data['mail.message'].records.push({
|
||||
body: "not empty",
|
||||
channel_ids: [20], // link message to channel
|
||||
id: 100, // random unique id, useful to link notification
|
||||
});
|
||||
@@ -1186,6 +1187,7 @@ QUnit.test('load single message from channel initially', async function (assert)
|
||||
// channel expected to be rendered, with a random unique id that will be referenced in the test
|
||||
this.data['mail.channel'].records.push({ id: 20 });
|
||||
this.data['mail.message'].records.push({
|
||||
body: "not empty",
|
||||
channel_ids: [20],
|
||||
date: "2019-04-20 10:00:00",
|
||||
id: 100,
|
||||
@@ -1554,6 +1556,7 @@ QUnit.test('load all messages from channel initially, less than fetch limit (29
|
||||
this.data['res.partner'].records.push({ id: 11 });
|
||||
for (let i = 28; i >= 0; i--) {
|
||||
this.data['mail.message'].records.push({
|
||||
body: "not empty",
|
||||
channel_ids: [20],
|
||||
date: "2019-04-20 10:00:00",
|
||||
model: 'mail.channel',
|
||||
@@ -1613,6 +1616,7 @@ QUnit.test('load more messages from channel', async function (assert) {
|
||||
this.data['res.partner'].records.push({ id: 11 });
|
||||
for (let i = 0; i < 40; i++) {
|
||||
this.data['mail.message'].records.push({
|
||||
body: "not empty",
|
||||
channel_ids: [20],
|
||||
date: "2019-04-20 10:00:00",
|
||||
model: 'mail.channel',
|
||||
@@ -1684,6 +1688,7 @@ QUnit.test('auto-scroll to bottom of thread', async function (assert) {
|
||||
this.data['mail.channel'].records.push({ id: 20 });
|
||||
for (let i = 1; i <= 25; i++) {
|
||||
this.data['mail.message'].records.push({
|
||||
body: "not empty",
|
||||
channel_ids: [20],
|
||||
model: 'mail.channel',
|
||||
res_id: 20,
|
||||
@@ -1719,6 +1724,7 @@ QUnit.test('load more messages from channel (auto-load on scroll)', async functi
|
||||
this.data['mail.channel'].records.push({ id: 20 });
|
||||
for (let i = 0; i < 40; i++) {
|
||||
this.data['mail.message'].records.push({
|
||||
body: "not empty",
|
||||
channel_ids: [20],
|
||||
model: 'mail.channel',
|
||||
res_id: 20,
|
||||
@@ -1784,6 +1790,7 @@ QUnit.test('new messages separator [REQUIRE FOCUS]', async function (assert) {
|
||||
});
|
||||
for (let i = 1; i <= 25; i++) {
|
||||
this.data['mail.message'].records.push({
|
||||
body: "not empty",
|
||||
channel_ids: [20],
|
||||
id: 100 + i, // for setting proper value for seen_message_id
|
||||
model: 'mail.channel',
|
||||
@@ -1886,6 +1893,7 @@ QUnit.test('restore thread scroll position', async function (assert) {
|
||||
this.data['mail.channel'].records.push({ id: 11 }, { id: 12 });
|
||||
for (let i = 1; i <= 25; i++) {
|
||||
this.data['mail.message'].records.push({
|
||||
body: "not empty",
|
||||
channel_ids: [11],
|
||||
model: 'mail.channel',
|
||||
res_id: 11,
|
||||
@@ -1893,6 +1901,7 @@ QUnit.test('restore thread scroll position', async function (assert) {
|
||||
}
|
||||
for (let i = 1; i <= 24; i++) {
|
||||
this.data['mail.message'].records.push({
|
||||
body: "not empty",
|
||||
channel_ids: [12],
|
||||
model: 'mail.channel',
|
||||
res_id: 12,
|
||||
@@ -2036,6 +2045,7 @@ QUnit.test('message origin redirect to channel', async function (assert) {
|
||||
this.data['mail.channel'].records.push({ id: 11 }, { id: 12 });
|
||||
this.data['mail.message'].records.push(
|
||||
{
|
||||
body: "not empty",
|
||||
channel_ids: [11, 12],
|
||||
id: 100,
|
||||
model: 'mail.channel',
|
||||
@@ -2043,6 +2053,7 @@ QUnit.test('message origin redirect to channel', async function (assert) {
|
||||
res_id: 11,
|
||||
},
|
||||
{
|
||||
body: "not empty",
|
||||
channel_ids: [11, 12],
|
||||
id: 101,
|
||||
model: 'mail.channel',
|
||||
@@ -2245,6 +2256,7 @@ QUnit.test('redirect to author (open chat)', async function (assert) {
|
||||
this.data['mail.message'].records.push(
|
||||
{
|
||||
author_id: 7,
|
||||
body: "not empty",
|
||||
channel_ids: [1],
|
||||
id: 100,
|
||||
model: 'mail.channel',
|
||||
@@ -2483,6 +2495,7 @@ QUnit.test('inbox: mark all messages as read', async function (assert) {
|
||||
this.data['mail.message'].records.push(
|
||||
// first expected message
|
||||
{
|
||||
body: "not empty",
|
||||
channel_ids: [20], // link message to channel
|
||||
id: 100, // random unique id, useful to link notification
|
||||
// needaction needs to be set here for message_fetch domain, because
|
||||
@@ -2491,6 +2504,7 @@ QUnit.test('inbox: mark all messages as read', async function (assert) {
|
||||
},
|
||||
// second expected message
|
||||
{
|
||||
body: "not empty",
|
||||
channel_ids: [20], // link message to channel
|
||||
id: 101, // random unique id, useful to link notification
|
||||
// needaction needs to be set here for message_fetch domain, because
|
||||
@@ -2586,8 +2600,8 @@ QUnit.test('starred: unstar all', async function (assert) {
|
||||
|
||||
// messages expected to be starred
|
||||
this.data['mail.message'].records.push(
|
||||
{ starred_partner_ids: [this.data.currentPartnerId] },
|
||||
{ starred_partner_ids: [this.data.currentPartnerId] }
|
||||
{ body: "not empty", starred_partner_ids: [this.data.currentPartnerId] },
|
||||
{ body: "not empty", starred_partner_ids: [this.data.currentPartnerId] }
|
||||
);
|
||||
await this.start({
|
||||
discuss: {
|
||||
@@ -2647,6 +2661,7 @@ QUnit.test('toggle_star message', async function (assert) {
|
||||
// with a random unique id, will be referenced in the test
|
||||
this.data['mail.channel'].records.push({ id: 20 });
|
||||
this.data['mail.message'].records.push({
|
||||
body: "not empty",
|
||||
channel_ids: [20],
|
||||
id: 100,
|
||||
model: 'mail.channel',
|
||||
@@ -3176,6 +3191,7 @@ QUnit.test('rendering of inbox message', async function (assert) {
|
||||
assert.expect(7);
|
||||
|
||||
this.data['mail.message'].records.push({
|
||||
body: "not empty",
|
||||
model: 'res.partner', // random existing model
|
||||
needaction: true, // for message_fetch domain
|
||||
needaction_partner_ids: [this.data.currentPartnerId], // for consistency
|
||||
@@ -3228,6 +3244,7 @@ QUnit.test('mark channel as seen on last message visible [REQUIRE FOCUS]', async
|
||||
// and a random unique id that will be referenced in the test
|
||||
this.data['mail.channel'].records.push({ id: 10, message_unread_counter: 1 });
|
||||
this.data['mail.message'].records.push({
|
||||
body: "not empty",
|
||||
channel_ids: [10],
|
||||
model: 'mail.channel',
|
||||
res_id: 10,
|
||||
@@ -3318,6 +3335,7 @@ QUnit.test('receive new needaction messages', async function (assert) {
|
||||
// simulate receiving a new needaction message
|
||||
await afterNextRender(() => {
|
||||
const data = {
|
||||
body: "not empty",
|
||||
id: 100,
|
||||
needaction_partner_ids: [3],
|
||||
model: 'res.partner',
|
||||
@@ -3359,6 +3377,7 @@ QUnit.test('receive new needaction messages', async function (assert) {
|
||||
// simulate receiving another new needaction message
|
||||
await afterNextRender(() => {
|
||||
const data2 = {
|
||||
body: "not empty",
|
||||
id: 101,
|
||||
needaction_partner_ids: [3],
|
||||
model: 'res.partner',
|
||||
@@ -3533,6 +3552,7 @@ QUnit.test('load recent messages from thread (already loaded some old messages)'
|
||||
this.data['mail.channel'].records.push({ id: 20 });
|
||||
for (let i = 0; i < 50; i++) {
|
||||
this.data['mail.message'].records.push({
|
||||
body: "not empty",
|
||||
channel_ids: [20], // id of related channel
|
||||
id: 100 + i, // random unique id, will be referenced in the test
|
||||
model: 'mail.channel', // expected value to link message to channel
|
||||
@@ -3610,6 +3630,7 @@ QUnit.test('messages marked as read move to "History" mailbox', async function (
|
||||
// expected messages
|
||||
this.data['mail.message'].records.push(
|
||||
{
|
||||
body: "not empty",
|
||||
id: 100, // random unique id, useful to link notification
|
||||
model: 'mail.channel', // value to link message to channel
|
||||
// needaction needs to be set here for message_fetch domain, because
|
||||
@@ -3618,6 +3639,7 @@ QUnit.test('messages marked as read move to "History" mailbox', async function (
|
||||
res_id: 20, // id of related channel
|
||||
},
|
||||
{
|
||||
body: "not empty",
|
||||
id: 101, // random unique id, useful to link notification
|
||||
model: 'mail.channel', // value to link message to channel
|
||||
// needaction needs to be set here for message_fetch domain, because
|
||||
@@ -3734,11 +3756,13 @@ QUnit.test('mark a single message as read should only move this message to "Hist
|
||||
|
||||
this.data['mail.message'].records.push(
|
||||
{
|
||||
body: "not empty",
|
||||
id: 1,
|
||||
needaction: true,
|
||||
needaction_partner_ids: [this.data.currentPartnerId],
|
||||
},
|
||||
{
|
||||
body: "not empty",
|
||||
id: 2,
|
||||
needaction: true,
|
||||
needaction_partner_ids: [this.data.currentPartnerId],
|
||||
@@ -3856,6 +3880,7 @@ QUnit.test('all messages in "Inbox" in "History" after marked all as read', asyn
|
||||
for (let id = messageOffset; id < messageOffset + 40; id++) {
|
||||
// message expected to be found in Inbox
|
||||
this.data['mail.message'].records.push({
|
||||
body: "not empty",
|
||||
id, // will be used to link notification to message
|
||||
// needaction needs to be set here for message_fetch domain, because
|
||||
// mocked models don't have computed fields
|
||||
|
||||
@@ -708,7 +708,7 @@ QUnit.test('only show messaging seen indicator if authored by me, after last see
|
||||
});
|
||||
|
||||
QUnit.test('allow attachment delete on authored message', async function (assert) {
|
||||
assert.expect(3);
|
||||
assert.expect(5);
|
||||
|
||||
await this.start();
|
||||
const message = this.env.models['mail.message'].create({
|
||||
@@ -735,6 +735,20 @@ QUnit.test('allow attachment delete on authored message', async function (assert
|
||||
);
|
||||
|
||||
await afterNextRender(() => document.querySelector('.o_Attachment_asideItemUnlink').click());
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
'.o_AttachmentDeleteConfirmDialog',
|
||||
"An attachment delete confirmation dialog should have been opened"
|
||||
);
|
||||
assert.strictEqual(
|
||||
document.querySelector('.o_AttachmentDeleteConfirmDialog_mainText').textContent,
|
||||
`Do you really want to delete "BLAH"?`,
|
||||
"Confirmation dialog should contain the attachment delete confirmation text"
|
||||
);
|
||||
|
||||
await afterNextRender(() =>
|
||||
document.querySelector('.o_AttachmentDeleteConfirmDialog_confirmButton').click()
|
||||
);
|
||||
assert.containsNone(
|
||||
document.body,
|
||||
'.o_Attachment',
|
||||
|
||||
@@ -107,7 +107,7 @@ class MessageList extends Component {
|
||||
* Because of this, the scroll position must be changed when whole UI
|
||||
* is rendered. To make this simpler, this is done when <ThreadView/>
|
||||
* component is patched. This is acceptable when <ThreadView/> has a
|
||||
* fixed height, which is the case for the moment.
|
||||
* fixed height, which is the case for the moment. task-2358066
|
||||
*/
|
||||
async adjustFromComponentHints() {
|
||||
if (!this.threadView) {
|
||||
@@ -418,9 +418,6 @@ class MessageList extends Component {
|
||||
if (this.props.order === 'asc' && this.props.hasScrollAdjust) {
|
||||
this.el.scrollTop = this.el.scrollHeight - scrollHeight + scrollTop;
|
||||
}
|
||||
this.env.messagingBus.trigger('o-component-message-list-more-messages-loaded', {
|
||||
threadViewer: this.threadView.threadViewer,
|
||||
});
|
||||
this.threadView.markComponentHintProcessed(hint);
|
||||
}
|
||||
|
||||
|
||||
@@ -49,36 +49,38 @@
|
||||
</div>
|
||||
</t>
|
||||
<t t-foreach="orderedMessages" t-as="message" t-key="message.localId">
|
||||
<t t-set="message_day" t-value="getDateDay(message)"/>
|
||||
<t t-if="current_day !== message_day">
|
||||
<div class="o_MessageList_separator o_MessageList_separatorDate o_MessageList_item">
|
||||
<hr class="o_MessageList_separatorLine"/><span class="o_MessageList_separatorLabel o_MessageList_separatorLabelDate"><t t-esc="message_day"/></span><hr class="o_MessageList_separatorLine"/>
|
||||
<t t-set="current_day" t-value="message_day"/>
|
||||
<t t-set="isMessageSquashed" t-value="false"/>
|
||||
</div>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<t t-set="isMessageSquashed" t-value="shouldMessageBeSquashed(prev_message, message)"/>
|
||||
</t>
|
||||
<Message
|
||||
class="o_MessageList_item o_MessageList_message"
|
||||
t-att-class="{
|
||||
'o-has-message-selection': props.selectedMessageLocalId,
|
||||
}"
|
||||
hasMarkAsReadIcon="props.haveMessagesMarkAsReadIcon"
|
||||
hasCheckbox="props.hasMessageCheckbox"
|
||||
hasReplyIcon="props.haveMessagesReplyIcon"
|
||||
isSelected="props.selectedMessageLocalId === message.localId"
|
||||
isSquashed="isMessageSquashed"
|
||||
messageLocalId="message.localId"
|
||||
threadViewLocalId="threadView.localId"
|
||||
t-ref="{{ message.localId }}"
|
||||
/>
|
||||
<t t-set="prev_message" t-value="message"/>
|
||||
<t t-if="threadView.thread.model === 'mail.channel' and threadView.thread.localMessageUnreadCounter > 0 and threadView.thread.lastSeenByCurrentPartnerMessageId === message.id">
|
||||
<div class="o_MessageList_separator o_MessageList_separatorNewMessages o_MessageList_item" t-att-class="{ 'o-disable-animation': env.disableAnimation }" t-transition="fade">
|
||||
<hr class="o_MessageList_separatorLine o_MessageList_separatorLineNewMessages"/><span class="o_MessageList_separatorLabel o_MessageList_separatorLabelNewMessages">New messages</span>
|
||||
</div>
|
||||
<t t-if="!message.isEmpty">
|
||||
<t t-set="message_day" t-value="getDateDay(message)"/>
|
||||
<t t-if="current_day !== message_day">
|
||||
<div class="o_MessageList_separator o_MessageList_separatorDate o_MessageList_item">
|
||||
<hr class="o_MessageList_separatorLine"/><span class="o_MessageList_separatorLabel o_MessageList_separatorLabelDate"><t t-esc="message_day"/></span><hr class="o_MessageList_separatorLine"/>
|
||||
<t t-set="current_day" t-value="message_day"/>
|
||||
<t t-set="isMessageSquashed" t-value="false"/>
|
||||
</div>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<t t-set="isMessageSquashed" t-value="shouldMessageBeSquashed(prev_message, message)"/>
|
||||
</t>
|
||||
<Message
|
||||
class="o_MessageList_item o_MessageList_message"
|
||||
t-att-class="{
|
||||
'o-has-message-selection': props.selectedMessageLocalId,
|
||||
}"
|
||||
hasMarkAsReadIcon="props.haveMessagesMarkAsReadIcon"
|
||||
hasCheckbox="props.hasMessageCheckbox"
|
||||
hasReplyIcon="props.haveMessagesReplyIcon"
|
||||
isSelected="props.selectedMessageLocalId === message.localId"
|
||||
isSquashed="isMessageSquashed"
|
||||
messageLocalId="message.localId"
|
||||
threadViewLocalId="threadView.localId"
|
||||
t-ref="{{ message.localId }}"
|
||||
/>
|
||||
<t t-set="prev_message" t-value="message"/>
|
||||
<t t-if="threadView.thread.model === 'mail.channel' and threadView.thread.localMessageUnreadCounter > 0 and threadView.thread.lastSeenByCurrentPartnerMessageId === message.id">
|
||||
<div class="o_MessageList_separator o_MessageList_separatorNewMessages o_MessageList_item" t-att-class="{ 'o-disable-animation': env.disableAnimation }" t-transition="fade">
|
||||
<hr class="o_MessageList_separatorLine o_MessageList_separatorLineNewMessages"/><span class="o_MessageList_separatorLabel o_MessageList_separatorLabelNewMessages">New messages</span>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
<!-- LOADING (if order desc)-->
|
||||
|
||||
@@ -3,7 +3,9 @@ odoo.define('mail/static/src/components/moderation_ban_dialog/moderation_ban_dia
|
||||
|
||||
const useStore = require('mail/static/src/component_hooks/use_store/use_store.js');
|
||||
|
||||
const Dialog = require('web.OwlDialog');
|
||||
const components = {
|
||||
Dialog: require('web.OwlDialog'),
|
||||
};
|
||||
|
||||
const { Component } = owl;
|
||||
const { useRef } = owl.hooks;
|
||||
@@ -71,7 +73,7 @@ class ModerationBanDialog extends Component {
|
||||
}
|
||||
|
||||
Object.assign(ModerationBanDialog, {
|
||||
components: { Dialog },
|
||||
components,
|
||||
props: {
|
||||
messageLocalIds: {
|
||||
type: Array,
|
||||
|
||||
+6
-4
@@ -3,7 +3,9 @@ odoo.define('mail/static/src/components/moderation_discard_dialog/moderation_dis
|
||||
|
||||
const useStore = require('mail/static/src/component_hooks/use_store/use_store.js');
|
||||
|
||||
const Dialog = require('web.OwlDialog');
|
||||
const components = {
|
||||
Dialog: require('web.OwlDialog'),
|
||||
};
|
||||
|
||||
const { Component } = owl;
|
||||
const { useRef } = owl.hooks;
|
||||
@@ -38,7 +40,7 @@ class ModerationDiscardDialog extends Component {
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
getText() {
|
||||
getBody() {
|
||||
if (this.messages.length === 1) {
|
||||
return this.env._t("You are going to discard 1 message.");
|
||||
}
|
||||
@@ -60,7 +62,7 @@ class ModerationDiscardDialog extends Component {
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
get CONFIRMATION() {
|
||||
getTitle() {
|
||||
return this.env._t("Confirmation");
|
||||
}
|
||||
|
||||
@@ -86,7 +88,7 @@ class ModerationDiscardDialog extends Component {
|
||||
}
|
||||
|
||||
Object.assign(ModerationDiscardDialog, {
|
||||
components: { Dialog },
|
||||
components,
|
||||
props: {
|
||||
messageLocalIds: {
|
||||
type: Array,
|
||||
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
<t t-name="mail.ModerationDiscardDialog" owl="1">
|
||||
<Dialog contentClass="'o_ModerationDiscardDialog'" title="CONFIRMATION" size="'medium'" t-ref="dialog">
|
||||
<p t-esc="getText()"/>
|
||||
<Dialog contentClass="'o_ModerationDiscardDialog'" title="getTitle()" size="'medium'" t-ref="dialog">
|
||||
<p t-esc="getBody()"/>
|
||||
<p>Do you confirm the action?</p>
|
||||
<t t-set-slot="buttons">
|
||||
<button class="o-discard btn btn-primary" t-on-click="_onClickDiscard">Discard</button>
|
||||
|
||||
+4
-2
@@ -3,7 +3,9 @@ odoo.define('mail/static/src/components/moderation_reject_dialog/moderation_reje
|
||||
|
||||
const useStore = require('mail/static/src/component_hooks/use_store/use_store.js');
|
||||
|
||||
const Dialog = require('web.OwlDialog');
|
||||
const components = {
|
||||
Dialog: require('web.OwlDialog'),
|
||||
};
|
||||
|
||||
const { Component, useState } = owl;
|
||||
const { useRef } = owl.hooks;
|
||||
@@ -81,7 +83,7 @@ class ModerationRejectDialog extends Component {
|
||||
}
|
||||
|
||||
Object.assign(ModerationRejectDialog, {
|
||||
components: { Dialog },
|
||||
components,
|
||||
props: {
|
||||
messageLocalIds: {
|
||||
type: Array,
|
||||
|
||||
@@ -101,6 +101,7 @@ QUnit.test('message list desc order', async function (assert) {
|
||||
|
||||
for (let i = 0; i <= 60; i++) {
|
||||
this.data['mail.message'].records.push({
|
||||
body: "not empty",
|
||||
channel_ids: [100],
|
||||
model: 'mail.channel',
|
||||
res_id: 100,
|
||||
@@ -172,6 +173,7 @@ QUnit.test('message list asc order', async function (assert) {
|
||||
|
||||
for (let i = 0; i <= 60; i++) {
|
||||
this.data['mail.message'].records.push({
|
||||
body: "not empty",
|
||||
channel_ids: [100],
|
||||
model: 'mail.channel',
|
||||
res_id: 100,
|
||||
@@ -389,6 +391,7 @@ QUnit.test('show message subject if thread is mailing channel', async function (
|
||||
assert.expect(3);
|
||||
|
||||
this.data['mail.message'].records.push({
|
||||
body: "not empty",
|
||||
channel_ids: [100],
|
||||
model: 'mail.channel',
|
||||
res_id: 100,
|
||||
@@ -717,6 +720,7 @@ QUnit.test('basic rendering of canceled notification', async function (assert) {
|
||||
this.data['mail.channel'].records.push({ id: 11 });
|
||||
this.data['res.partner'].records.push({ id: 12, name: "Someone" });
|
||||
this.data['mail.message'].records.push({
|
||||
body: "not empty",
|
||||
channel_ids: [11],
|
||||
id: 10,
|
||||
message_type: 'email',
|
||||
@@ -817,6 +821,7 @@ QUnit.test('should scroll to bottom on receiving new message if the list is init
|
||||
this.data['mail.channel'].records.push({ id: 20 });
|
||||
for (let i = 0; i <= 10; i++) {
|
||||
this.data['mail.message'].records.push({
|
||||
body: "not empty",
|
||||
channel_ids: [20],
|
||||
});
|
||||
}
|
||||
@@ -887,6 +892,7 @@ QUnit.test('should not scroll on receiving new message if the list is initially
|
||||
this.data['mail.channel'].records.push({ id: 20 });
|
||||
for (let i = 0; i <= 10; i++) {
|
||||
this.data['mail.message'].records.push({
|
||||
body: "not empty",
|
||||
channel_ids: [20],
|
||||
});
|
||||
}
|
||||
@@ -952,6 +958,186 @@ QUnit.test('should not scroll on receiving new message if the list is initially
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("delete all attachments of message without content should no longer display the message", async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
this.data['ir.attachment'].records.push({
|
||||
id: 143,
|
||||
mimetype: 'text/plain',
|
||||
name: "Blah.txt",
|
||||
});
|
||||
this.data['mail.channel'].records.push({ id: 11 });
|
||||
this.data['mail.message'].records.push(
|
||||
{
|
||||
attachment_ids: [143],
|
||||
channel_ids: [11],
|
||||
id: 101,
|
||||
}
|
||||
);
|
||||
await this.start();
|
||||
const threadViewer = this.env.models['mail.thread_viewer'].create({
|
||||
hasThreadView: true,
|
||||
thread: [['insert', { id: 11, model: 'mail.channel' }]],
|
||||
});
|
||||
// wait for messages of the thread to be loaded
|
||||
await this.afterEvent({
|
||||
eventName: 'o-thread-view-hint-processed',
|
||||
func: () => {
|
||||
this.createThreadViewComponent(threadViewer.threadView);
|
||||
},
|
||||
message: "thread become loaded with messages",
|
||||
predicate: ({ hint, threadViewer }) => {
|
||||
return (
|
||||
hint.type === 'messages-loaded' &&
|
||||
threadViewer.thread.model === 'mail.channel' &&
|
||||
threadViewer.thread.id === 11
|
||||
);
|
||||
},
|
||||
});
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
'.o_Message',
|
||||
"there should be 1 message displayed initially"
|
||||
);
|
||||
|
||||
await afterNextRender(() => {
|
||||
document.querySelector(`.o_Attachment[data-attachment-local-id="${
|
||||
this.env.models['mail.attachment'].findFromIdentifyingData({ id: 143 }).localId
|
||||
}"] .o_Attachment_asideItemUnlink`).click();
|
||||
});
|
||||
await afterNextRender(() =>
|
||||
document.querySelector('.o_AttachmentDeleteConfirmDialog_confirmButton').click()
|
||||
);
|
||||
assert.containsNone(
|
||||
document.body,
|
||||
'.o_Message',
|
||||
"message should no longer be displayed after removing all its attachments (empty content)"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('delete all attachments of a message with some text content should still keep it displayed', async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
this.data['ir.attachment'].records.push({
|
||||
id: 143,
|
||||
mimetype: 'text/plain',
|
||||
name: "Blah.txt",
|
||||
});
|
||||
this.data['mail.channel'].records.push({ id: 11 });
|
||||
this.data['mail.message'].records.push(
|
||||
{
|
||||
attachment_ids: [143],
|
||||
body: "Some content",
|
||||
channel_ids: [11],
|
||||
id: 101,
|
||||
},
|
||||
);
|
||||
await this.start();
|
||||
const threadViewer = this.env.models['mail.thread_viewer'].create({
|
||||
hasThreadView: true,
|
||||
thread: [['insert', { id: 11, model: 'mail.channel' }]],
|
||||
});
|
||||
// wait for messages of the thread to be loaded
|
||||
await this.afterEvent({
|
||||
eventName: 'o-thread-view-hint-processed',
|
||||
func: () => {
|
||||
this.createThreadViewComponent(threadViewer.threadView);
|
||||
},
|
||||
message: "thread become loaded with messages",
|
||||
predicate: ({ hint, threadViewer }) => {
|
||||
return (
|
||||
hint.type === 'messages-loaded' &&
|
||||
threadViewer.thread.model === 'mail.channel' &&
|
||||
threadViewer.thread.id === 11
|
||||
);
|
||||
},
|
||||
});
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
'.o_Message',
|
||||
"there should be 1 message displayed initially"
|
||||
);
|
||||
|
||||
await afterNextRender(() => {
|
||||
document.querySelector(`.o_Attachment[data-attachment-local-id="${
|
||||
this.env.models['mail.attachment'].findFromIdentifyingData({ id: 143 }).localId
|
||||
}"] .o_Attachment_asideItemUnlink`).click();
|
||||
});
|
||||
await afterNextRender(() =>
|
||||
document.querySelector('.o_AttachmentDeleteConfirmDialog_confirmButton').click()
|
||||
);
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
'.o_Message',
|
||||
"message should still be displayed after removing its attachments (non-empty content)"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('delete all attachments of a message with tracking fields should still keep it displayed', async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
this.data['ir.attachment'].records.push({
|
||||
id: 143,
|
||||
mimetype: 'text/plain',
|
||||
name: "Blah.txt",
|
||||
});
|
||||
this.data['mail.channel'].records.push({ id: 11 });
|
||||
this.data['mail.message'].records.push(
|
||||
{
|
||||
attachment_ids: [143],
|
||||
channel_ids: [11],
|
||||
id: 101,
|
||||
tracking_value_ids: [6]
|
||||
},
|
||||
);
|
||||
this.data['mail.tracking.value'].records.push({
|
||||
changed_field: "Name",
|
||||
field_type: "char",
|
||||
id: 6,
|
||||
new_value: "New name",
|
||||
old_value: "Old name",
|
||||
});
|
||||
await this.start();
|
||||
const threadViewer = this.env.models['mail.thread_viewer'].create({
|
||||
hasThreadView: true,
|
||||
thread: [['insert', { id: 11, model: 'mail.channel' }]],
|
||||
});
|
||||
// wait for messages of the thread to be loaded
|
||||
await this.afterEvent({
|
||||
eventName: 'o-thread-view-hint-processed',
|
||||
func: () => {
|
||||
this.createThreadViewComponent(threadViewer.threadView);
|
||||
},
|
||||
message: "thread become loaded with messages",
|
||||
predicate: ({ hint, threadViewer }) => {
|
||||
return (
|
||||
hint.type === 'messages-loaded' &&
|
||||
threadViewer.thread.model === 'mail.channel' &&
|
||||
threadViewer.thread.id === 11
|
||||
);
|
||||
},
|
||||
});
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
'.o_Message',
|
||||
"there should be 1 message displayed initially"
|
||||
);
|
||||
|
||||
await afterNextRender(() => {
|
||||
document.querySelector(`.o_Attachment[data-attachment-local-id="${
|
||||
this.env.models['mail.attachment'].findFromIdentifyingData({ id: 143 }).localId
|
||||
}"] .o_Attachment_asideItemUnlink`).click();
|
||||
});
|
||||
await afterNextRender(() =>
|
||||
document.querySelector('.o_AttachmentDeleteConfirmDialog_confirmButton').click()
|
||||
);
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
'.o_Message',
|
||||
"message should still be displayed after removing its attachments (non-empty content)"
|
||||
);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -401,6 +401,17 @@ function factory(dependencies) {
|
||||
return inlineBody.toLowerCase() === this.subtype_description.toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_computeIsEmpty() {
|
||||
return (
|
||||
(!this.body || htmlToTextContentInline(this.body) === '') &&
|
||||
this.attachments.length === 0 &&
|
||||
this.tracking_value_ids.length === 0
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @returns {boolean}
|
||||
@@ -551,6 +562,19 @@ function factory(dependencies) {
|
||||
'subtype_description',
|
||||
],
|
||||
}),
|
||||
/**
|
||||
* Determine whether the message has to be considered empty or not.
|
||||
*
|
||||
* An empty message has no text, no attachment and no tracking value.
|
||||
*/
|
||||
isEmpty: attr({
|
||||
compute: '_computeIsEmpty',
|
||||
dependencies: [
|
||||
'attachments',
|
||||
'body',
|
||||
'tracking_value_ids',
|
||||
],
|
||||
}),
|
||||
isModeratedByCurrentPartner: attr({
|
||||
compute: '_computeIsModeratedByCurrentPartner',
|
||||
default: false,
|
||||
|
||||
@@ -463,7 +463,7 @@ QUnit.test('basic chatter rendering without messages', async function (assert) {
|
||||
QUnit.test('chatter updating', async function (assert) {
|
||||
assert.expect(3);
|
||||
|
||||
this.data['mail.message'].records.push({ model: 'res.partner', res_id: 12 });
|
||||
this.data['mail.message'].records.push({ body: "not empty", model: 'res.partner', res_id: 12 });
|
||||
this.data['res.partner'].records.push(
|
||||
{ display_name: "first partner", id: 11 },
|
||||
{ display_name: "second partner", id: 12 }
|
||||
|
||||
@@ -136,6 +136,7 @@ class MockModels {
|
||||
res_model_name: { string: "Res Model Name", type: 'char' },
|
||||
starred_partner_ids: { string: "Favorited By", type: 'many2many', relation: 'res.partner' },
|
||||
subject: { string: "Subject", type: 'char' },
|
||||
tracking_value_ids: { relation: 'mail.tracking.value', string: "Tracking values", type: 'one2many' },
|
||||
},
|
||||
records: [],
|
||||
},
|
||||
@@ -177,6 +178,15 @@ class MockModels {
|
||||
},
|
||||
records: [],
|
||||
},
|
||||
'mail.tracking.value': {
|
||||
fields: {
|
||||
changed_field: { string: 'Changed field', type: 'char' },
|
||||
field_type: { string: 'Field type', type: 'char' },
|
||||
new_value: { string: 'New value', type: 'char' },
|
||||
old_value: { string: 'Old value', type: 'char' },
|
||||
},
|
||||
records: [],
|
||||
},
|
||||
'res.country': {
|
||||
fields: {
|
||||
code: { string: "Code", type: 'char' },
|
||||
|
||||
Reference in New Issue
Block a user