[FIX] mail: prevent multiple send of message by spamming send button
task-2388702 closes odoo/odoo#62123 X-original-commit: 909b66883c1085c3eba59452fd89f1328948acab Signed-off-by: Sébastien Theys (seb) <seb@odoo.com>
This commit is contained in:
committed by
Sébastien Theys
parent
a37c11e113
commit
2cf0390100
@@ -2029,6 +2029,76 @@ QUnit.test("Show a thread name in the recipient status text.", async function (a
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('send message only once when button send is clicked twice quickly', async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
this.data['mail.channel'].records.push({ id: 20 });
|
||||
await this.start({
|
||||
async mockRPC(route, args) {
|
||||
if (args.method === 'message_post') {
|
||||
assert.step('message_post');
|
||||
}
|
||||
return this._super(...arguments);
|
||||
},
|
||||
});
|
||||
const thread = this.env.models['mail.thread'].find(thread =>
|
||||
thread.id === 20 &&
|
||||
thread.model === 'mail.channel'
|
||||
);
|
||||
await this.createComposerComponent(thread.composer);
|
||||
// Type message
|
||||
await afterNextRender(() => {
|
||||
document.querySelector(`.o_ComposerTextInput_textarea`).focus();
|
||||
document.execCommand('insertText', false, "test message");
|
||||
});
|
||||
|
||||
await afterNextRender(() => {
|
||||
document.querySelector(`.o_Composer_buttonSend`).click();
|
||||
document.querySelector(`.o_Composer_buttonSend`).click();
|
||||
});
|
||||
assert.verifySteps(
|
||||
['message_post'],
|
||||
"The message has been posted only once"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('send message only once when enter is pressed twice quickly', async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
this.data['mail.channel'].records.push({ id: 20 });
|
||||
await this.start({
|
||||
async mockRPC(route, args) {
|
||||
if (args.method === 'message_post') {
|
||||
assert.step('message_post');
|
||||
}
|
||||
return this._super(...arguments);
|
||||
},
|
||||
});
|
||||
const thread = this.env.models['mail.thread'].find(thread =>
|
||||
thread.id === 20 &&
|
||||
thread.model === 'mail.channel'
|
||||
);
|
||||
await this.createComposerComponent(thread.composer, {
|
||||
textInputSendShortcuts: ['enter'],
|
||||
});
|
||||
// Type message
|
||||
await afterNextRender(() => {
|
||||
document.querySelector(`.o_ComposerTextInput_textarea`).focus();
|
||||
document.execCommand('insertText', false, "test message");
|
||||
});
|
||||
await afterNextRender(() => {
|
||||
const enterEvent = new window.KeyboardEvent('keydown', { key: 'Enter' });
|
||||
document.querySelector(`.o_ComposerTextInput_textarea`)
|
||||
.dispatchEvent(enterEvent);
|
||||
document.querySelector(`.o_ComposerTextInput_textarea`)
|
||||
.dispatchEvent(enterEvent);
|
||||
});
|
||||
assert.verifySteps(
|
||||
['message_post'],
|
||||
"The message has been posted only once"
|
||||
);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -265,19 +265,33 @@ function factory(dependencies) {
|
||||
if (this.subjectContent) {
|
||||
postData.subject = this.subjectContent;
|
||||
}
|
||||
let messageId;
|
||||
if (thread.model === 'mail.channel') {
|
||||
const command = this._getCommandFromText(body);
|
||||
Object.assign(postData, {
|
||||
subtype_xmlid: 'mail.mt_comment',
|
||||
});
|
||||
if (command) {
|
||||
messageId = await this.async(() => this.env.models['mail.thread'].performRpcExecuteCommand({
|
||||
channelId: thread.id,
|
||||
command: command.name,
|
||||
postData,
|
||||
}));
|
||||
try {
|
||||
let messageId;
|
||||
this.update({ isPostingMessage: true });
|
||||
if (thread.model === 'mail.channel') {
|
||||
const command = this._getCommandFromText(body);
|
||||
Object.assign(postData, {
|
||||
subtype_xmlid: 'mail.mt_comment',
|
||||
});
|
||||
if (command) {
|
||||
messageId = await this.async(() => this.env.models['mail.thread'].performRpcExecuteCommand({
|
||||
channelId: thread.id,
|
||||
command: command.name,
|
||||
postData,
|
||||
}));
|
||||
} else {
|
||||
messageId = await this.async(() =>
|
||||
this.env.models['mail.thread'].performRpcMessagePost({
|
||||
postData,
|
||||
threadId: thread.id,
|
||||
threadModel: thread.model,
|
||||
})
|
||||
);
|
||||
}
|
||||
} else {
|
||||
Object.assign(postData, {
|
||||
subtype_xmlid: this.isLog ? 'mail.mt_note' : 'mail.mt_comment',
|
||||
});
|
||||
messageId = await this.async(() =>
|
||||
this.env.models['mail.thread'].performRpcMessagePost({
|
||||
postData,
|
||||
@@ -285,42 +299,33 @@ function factory(dependencies) {
|
||||
threadModel: thread.model,
|
||||
})
|
||||
);
|
||||
const [messageData] = await this.async(() => this.env.services.rpc({
|
||||
model: 'mail.message',
|
||||
method: 'message_format',
|
||||
args: [[messageId]],
|
||||
}, { shadow: true }));
|
||||
this.env.models['mail.message'].insert(Object.assign(
|
||||
{},
|
||||
this.env.models['mail.message'].convertData(messageData),
|
||||
{
|
||||
originThread: [['insert', {
|
||||
id: thread.id,
|
||||
model: thread.model,
|
||||
}]],
|
||||
})
|
||||
);
|
||||
thread.loadNewMessages();
|
||||
}
|
||||
} else {
|
||||
Object.assign(postData, {
|
||||
subtype_xmlid: this.isLog ? 'mail.mt_note' : 'mail.mt_comment',
|
||||
});
|
||||
messageId = await this.async(() =>
|
||||
this.env.models['mail.thread'].performRpcMessagePost({
|
||||
postData,
|
||||
threadId: thread.id,
|
||||
threadModel: thread.model,
|
||||
})
|
||||
);
|
||||
const [messageData] = await this.async(() => this.env.services.rpc({
|
||||
model: 'mail.message',
|
||||
method: 'message_format',
|
||||
args: [[messageId]],
|
||||
}, { shadow: true }));
|
||||
this.env.models['mail.message'].insert(Object.assign(
|
||||
{},
|
||||
this.env.models['mail.message'].convertData(messageData),
|
||||
{
|
||||
originThread: [['insert', {
|
||||
id: thread.id,
|
||||
model: thread.model,
|
||||
}]],
|
||||
})
|
||||
);
|
||||
thread.loadNewMessages();
|
||||
for (const threadView of this.thread.threadViews) {
|
||||
// Reset auto scroll to be able to see the newly posted message.
|
||||
threadView.update({ hasAutoScrollOnMessageReceived: true });
|
||||
}
|
||||
thread.refreshFollowers();
|
||||
thread.fetchAndUpdateSuggestedRecipients();
|
||||
this._reset();
|
||||
} finally {
|
||||
this.update({ isPostingMessage: false });
|
||||
}
|
||||
for (const threadView of this.thread.threadViews) {
|
||||
// Reset auto scroll to be able to see the newly posted message.
|
||||
threadView.update({ hasAutoScrollOnMessageReceived: true });
|
||||
}
|
||||
thread.refreshFollowers();
|
||||
thread.fetchAndUpdateSuggestedRecipients();
|
||||
this._reset();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -449,7 +454,7 @@ function factory(dependencies) {
|
||||
if (!this.textInputContent && this.attachments.length === 0) {
|
||||
return false;
|
||||
}
|
||||
return !this.hasUploadingAttachment;
|
||||
return !this.hasUploadingAttachment && !this.isPostingMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -874,6 +879,7 @@ function factory(dependencies) {
|
||||
dependencies: [
|
||||
'attachments',
|
||||
'hasUploadingAttachment',
|
||||
'isPostingMessage',
|
||||
'textInputContent',
|
||||
],
|
||||
default: false,
|
||||
@@ -947,6 +953,10 @@ function factory(dependencies) {
|
||||
isLog: attr({
|
||||
default: false,
|
||||
}),
|
||||
/**
|
||||
* Determines whether a post_message request is currently pending.
|
||||
*/
|
||||
isPostingMessage: attr(),
|
||||
mainSuggestedRecordsList: attr({
|
||||
compute: '_computeMainSuggestedRecordsList',
|
||||
dependencies: [
|
||||
|
||||
Reference in New Issue
Block a user