summaryrefslogtreecommitdiff
path: root/addons/mail/static/src/components
diff options
context:
space:
mode:
authorSébastien Theys <seb@odoo.com>2020-09-29 18:50:23 +0000
committerSébastien Theys <seb@odoo.com>2020-09-29 19:47:12 +0000
commitfb9b1c8a169840951925fa810f71e865a54e3a31 (patch)
tree591c34b02dfdde95ebaf296db7abfee5614e22ea /addons/mail/static/src/components
parentb4783500db5b7c645859227e8be5cd55025b2882 (diff)
[FIX] mail: reintroduce auto scroll on receiving message
task-2336251 closes odoo/odoo#58867 X-original-commit: f732980b2e5e83bcf2fdc8d3ce756f256cacbd4f Signed-off-by: Sébastien Theys (seb) <seb@odoo.com>
Diffstat (limited to 'addons/mail/static/src/components')
-rw-r--r--addons/mail/static/src/components/discuss/tests/discuss_tests.js33
-rw-r--r--addons/mail/static/src/components/message_list/message_list.js63
-rw-r--r--addons/mail/static/src/components/thread_view/thread_view_tests.js151
3 files changed, 223 insertions, 24 deletions
diff --git a/addons/mail/static/src/components/discuss/tests/discuss_tests.js b/addons/mail/static/src/components/discuss/tests/discuss_tests.js
index cbd7c8136a1..67d52c33c88 100644
--- a/addons/mail/static/src/components/discuss/tests/discuss_tests.js
+++ b/addons/mail/static/src/components/discuss/tests/discuss_tests.js
@@ -1809,7 +1809,20 @@ QUnit.test('new messages separator [REQUIRE FOCUS]', async function (assert) {
"should not display 'new messages' separator"
);
// scroll to top
- document.querySelector(`.o_Discuss_thread .o_ThreadView_messageList`).scrollTop = 0;
+ await this.afterEvent({
+ eventName: 'o-component-message-list-scrolled',
+ func: () => {
+ document.querySelector(`.o_Discuss_thread .o_ThreadView_messageList`).scrollTop = 0;
+ },
+ message: "should wait until channel scrolled to top",
+ predicate: ({ scrollTop, threadViewer }) => {
+ return (
+ threadViewer.thread.model === 'mail.channel' &&
+ threadViewer.thread.id === 20 &&
+ scrollTop === 0
+ );
+ },
+ });
// composer is focused by default, we remove that focus
document.querySelector('.o_ComposerTextInput_textarea').blur();
// simulate receiving a message
@@ -1835,9 +1848,21 @@ QUnit.test('new messages separator [REQUIRE FOCUS]', async function (assert) {
'.o_MessageList_separatorNewMessages',
"should display 'new messages' separator"
);
- await afterNextRender(() => {
- document.querySelector(`.o_Discuss_thread .o_ThreadView_messageList`).scrollTop =
- document.querySelector(`.o_Discuss_thread .o_ThreadView_messageList`).scrollHeight;
+ await this.afterEvent({
+ eventName: 'o-component-message-list-scrolled',
+ func: () => {
+ const messageList = document.querySelector(`.o_Discuss_thread .o_ThreadView_messageList`);
+ messageList.scrollTop = messageList.scrollHeight - messageList.clientHeight;
+ },
+ message: "should wait until channel scrolled to bottom",
+ predicate: ({ scrollTop, threadViewer }) => {
+ const messageList = document.querySelector(`.o_Discuss_thread .o_ThreadView_messageList`);
+ return (
+ threadViewer.thread.model === 'mail.channel' &&
+ threadViewer.thread.id === 20 &&
+ scrollTop === messageList.scrollHeight - messageList.clientHeight
+ );
+ },
});
assert.containsOnce(
document.body,
diff --git a/addons/mail/static/src/components/message_list/message_list.js b/addons/mail/static/src/components/message_list/message_list.js
index bc1ab67bbfc..72368ec3689 100644
--- a/addons/mail/static/src/components/message_list/message_list.js
+++ b/addons/mail/static/src/components/message_list/message_list.js
@@ -118,15 +118,15 @@ class MessageList extends Component {
case 'change-of-thread-cache':
this._adjustFromChangeOfThreadCache(hint);
break;
- case 'current-partner-just-posted-message':
- this._adjustFromCurrentPartnerJustPostedMessage(hint);
- break;
case 'home-menu-hidden':
this._adjustFromHomeMenuHidden(hint);
break;
case 'home-menu-shown':
this._adjustFromHomeMenuShown(hint);
break;
+ case 'message-received':
+ this._adjustFromMessageReceived(hint);
+ break;
case 'more-messages-loaded':
this._adjustFromMoreMessagesLoaded(hint);
break;
@@ -352,28 +352,17 @@ class MessageList extends Component {
/**
* @private
* @param {Object} hint
- * @param {Object} hint.data
- * @param {integer} hint.data.messageId
*/
- async _adjustFromCurrentPartnerJustPostedMessage(hint) {
- const threadCache = this.threadView.threadCache;
- if (threadCache.isLoaded && hint.data) {
- const { messageId } = hint.data;
- const threadCacheMessageIds = threadCache.messages.map(message => message.id);
- if (threadCacheMessageIds.includes(messageId) && this.messageRefFromId(messageId)) {
- if (this.props.hasScrollAdjust) {
- await this._scrollToMessage(messageId);
- }
- this.threadView.markComponentHintProcessed(hint);
- }
- }
+ _adjustFromHomeMenuHidden(hint) {
+ this._adjustScrollFromModel();
+ this.threadView.markComponentHintProcessed(hint);
}
/**
* @private
* @param {Object} hint
*/
- _adjustFromHomeMenuHidden(hint) {
+ _adjustFromHomeMenuShown(hint) {
this._adjustScrollFromModel();
this.threadView.markComponentHintProcessed(hint);
}
@@ -382,8 +371,34 @@ class MessageList extends Component {
* @private
* @param {Object} hint
*/
- _adjustFromHomeMenuShown(hint) {
- this._adjustScrollFromModel();
+ async _adjustFromMessageReceived(hint) {
+ const threadCache = this.threadView.threadCache;
+ if (!threadCache.isLoaded) {
+ return;
+ }
+ const { message } = hint.data;
+ if (!threadCache.messages.includes(message)) {
+ return;
+ }
+ if (!this.messageRefFromId(message.id)) {
+ return;
+ }
+ if (!this.props.hasScrollAdjust) {
+ this.threadView.markComponentHintProcessed(hint);
+ return;
+ }
+ if (!this.threadView.hasAutoScrollOnMessageReceived) {
+ this.threadView.markComponentHintProcessed(hint);
+ return;
+ }
+ if (
+ this.threadView.lastVisibleMessage &&
+ (message.id < this.threadView.lastVisibleMessage.id)
+ ) {
+ this.threadView.markComponentHintProcessed(hint);
+ return;
+ }
+ await this._scrollToMessage(message.id);
this.threadView.markComponentHintProcessed(hint);
}
@@ -537,6 +552,14 @@ class MessageList extends Component {
scrollTop,
threadViewer: this.threadView.threadViewer,
});
+ // Margin to compensate for inaccurate scrolling to bottom.
+ const margin = 4;
+ // Automatically scroll to new received messages only when the list is
+ // currently fully scrolled.
+ const hasAutoScrollOnMessageReceived = (this.props.order === 'asc')
+ ? scrollTop >= this.el.scrollHeight - this.el.clientHeight - margin
+ : scrollTop <= margin;
+ this.threadView.update({ hasAutoScrollOnMessageReceived });
this.threadView.threadViewer.saveThreadCacheScrollPositionsAsInitial(scrollTop);
if (!this._isAutoLoadOnScrollActive) {
return;
diff --git a/addons/mail/static/src/components/thread_view/thread_view_tests.js b/addons/mail/static/src/components/thread_view/thread_view_tests.js
index 4aa2d195686..9dc5145c121 100644
--- a/addons/mail/static/src/components/thread_view/thread_view_tests.js
+++ b/addons/mail/static/src/components/thread_view/thread_view_tests.js
@@ -786,6 +786,157 @@ QUnit.test('basic rendering of canceled notification', async function (assert) {
);
});
+QUnit.test('should scroll to bottom on receiving new message if the list is initially scrolled to bottom (asc order)', async function (assert) {
+ assert.expect(2);
+
+ // Needed partner & user to allow simulation of message reception
+ this.data['res.partner'].records.push({
+ id: 11,
+ name: "Foreigner partner",
+ });
+ this.data['res.users'].records.push({
+ id: 42,
+ name: "Foreigner user",
+ partner_id: 11,
+ });
+ this.data['mail.channel'].records.push({ id: 20 });
+ for (let i = 0; i <= 10; i++) {
+ this.data['mail.message'].records.push({
+ channel_ids: [20],
+ });
+ }
+ await this.start();
+ const thread = this.env.models['mail.thread'].findFromIdentifyingData({
+ id: 20,
+ model: 'mail.channel'
+ });
+ const threadViewer = this.env.models['mail.thread_viewer'].create({
+ hasThreadView: true,
+ thread: [['link', thread]],
+ });
+ await this.afterEvent({
+ eventName: 'o-component-message-list-scrolled',
+ func: () => this.createThreadViewComponent(
+ threadViewer.threadView,
+ { order: 'asc' },
+ { isFixedSize: true },
+ ),
+ message: "should wait until channel 20 scrolled initially",
+ predicate: data => threadViewer === data.threadViewer,
+ });
+ const initialMessageList = document.querySelector('.o_ThreadView_messageList');
+ assert.strictEqual(
+ initialMessageList.scrollTop,
+ initialMessageList.scrollHeight - initialMessageList.clientHeight,
+ "should have scrolled to bottom of channel 20 initially"
+ );
+
+ // simulate receiving a message
+ await this.afterEvent({
+ eventName: 'o-component-message-list-scrolled',
+ func: () =>
+ this.env.services.rpc({
+ route: '/mail/chat_post',
+ params: {
+ context: {
+ mockedUserId: 42,
+ },
+ uuid: thread.uuid,
+ message_content: "hello",
+ },
+ }),
+ message: "should wait until channel 20 scrolled after receiving a message",
+ predicate: data => threadViewer === data.threadViewer,
+ });
+ const messageList = document.querySelector('.o_ThreadView_messageList');
+ assert.strictEqual(
+ messageList.scrollTop,
+ messageList.scrollHeight - messageList.clientHeight,
+ "should scroll to bottom on receiving new message because the list is initially scrolled to bottom"
+ );
+});
+
+QUnit.test('should not scroll on receiving new message if the list is initially scrolled anywhere else than bottom (asc order)', async function (assert) {
+ assert.expect(3);
+
+ // Needed partner & user to allow simulation of message reception
+ this.data['res.partner'].records.push({
+ id: 11,
+ name: "Foreigner partner",
+ });
+ this.data['res.users'].records.push({
+ id: 42,
+ name: "Foreigner user",
+ partner_id: 11,
+ });
+ this.data['mail.channel'].records.push({ id: 20 });
+ for (let i = 0; i <= 10; i++) {
+ this.data['mail.message'].records.push({
+ channel_ids: [20],
+ });
+ }
+ await this.start();
+ const thread = this.env.models['mail.thread'].findFromIdentifyingData({
+ id: 20,
+ model: 'mail.channel'
+ });
+ const threadViewer = this.env.models['mail.thread_viewer'].create({
+ hasThreadView: true,
+ thread: [['link', thread]],
+ });
+ await this.afterEvent({
+ eventName: 'o-component-message-list-scrolled',
+ func: () => this.createThreadViewComponent(
+ threadViewer.threadView,
+ { order: 'asc' },
+ { isFixedSize: true },
+ ),
+ message: "should wait until channel 20 scrolled initially",
+ predicate: data => threadViewer === data.threadViewer,
+ });
+ const initialMessageList = document.querySelector('.o_ThreadView_messageList');
+ assert.strictEqual(
+ initialMessageList.scrollTop,
+ initialMessageList.scrollHeight - initialMessageList.clientHeight,
+ "should have scrolled to bottom of channel 20 initially"
+ );
+
+ await this.afterEvent({
+ eventName: 'o-component-message-list-scrolled',
+ func: () => initialMessageList.scrollTop = 0,
+ message: "should wait until channel 20 processed manual scroll",
+ predicate: data => threadViewer === data.threadViewer,
+ });
+ assert.strictEqual(
+ initialMessageList.scrollTop,
+ 0,
+ "should have scrolled to the top of channel 20 manually"
+ );
+
+ // simulate receiving a message
+ await this.afterEvent({
+ eventName: 'o-thread-view-hint-processed',
+ func: () =>
+ this.env.services.rpc({
+ route: '/mail/chat_post',
+ params: {
+ context: {
+ mockedUserId: 42,
+ },
+ uuid: thread.uuid,
+ message_content: "hello",
+ },
+ }),
+ message: "should wait until channel 20 processed new message hint",
+ predicate: data => threadViewer === data.threadViewer && data.hint.type === 'message-received',
+ });
+ assert.strictEqual(
+ document.querySelector('.o_ThreadView_messageList').scrollTop,
+ 0,
+ "should not scroll on receiving new message because the list is initially scrolled anywhere else than bottom"
+ );
+});
+
});
});
});