[FIX] mail: scroll to bottom assertion in Chrome 94+

Since (at least) Chrome 94+, some element's sizing computation returns a
slightly different value (in the order of a fraction of a pixel). Sadly,
due to rounding, this difference has an impact on exact sizing
assertion.

As the difference is *really* small, introducing a margin of error (i.e.
<= 1px) looks reasonnable ; as implemented in this commit. More
specifically as Element.scrollHeight can have either an int or a decimal
value (cf. fractional-scaling), this commit allows to property handle
both usecases.

Reference:
https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollHeight#determine_if_an_element_has_been_totally_scrolled

closes odoo/odoo#92328

X-original-commit: 93348ae0d0b076fe2dfa094a2d7b3ef74442d390
Related: odoo/enterprise#27790
Signed-off-by: Sébastien Theys (seb) <seb@odoo.com>
Signed-off-by: Pierre Paridans (app) <app@odoo.com>
This commit is contained in:
Pierre Paridans
2022-05-26 10:48:41 +02:00
parent 01f683f41a
commit 24f89c6dff
5 changed files with 50 additions and 36 deletions
@@ -1003,6 +1003,23 @@ function pasteFiles(el, files) {
});
}
//------------------------------------------------------------------------------
// Public: DOM utilities
//------------------------------------------------------------------------------
/**
* Determine if a DOM element has been totally scrolled
*
* A 1px margin of error is given to accomodate subpixel rounding issues and
* Element.scrollHeight value being either int or decimal
*
* @param {DOM.Element} el
* @returns {boolean}
*/
function isScrolledToBottom(el) {
return Math.abs(el.scrollHeight - el.clientHeight - el.scrollTop) <= 1;
}
//------------------------------------------------------------------------------
// Export
//------------------------------------------------------------------------------
@@ -1011,6 +1028,7 @@ export {
afterNextRender,
dragenterFiles,
dropFiles,
isScrolledToBottom,
nextAnimationFrame,
nextTick,
pasteFiles,
@@ -4,6 +4,7 @@ import { makeDeferred } from '@mail/utils/deferred';
import { patchUiSize, SIZES } from '@mail/../tests/helpers/patch_ui_size';
import {
afterNextRender,
isScrolledToBottom,
nextAnimationFrame,
start,
startServer,
@@ -958,7 +959,7 @@ QUnit.test('chat window: scroll conservation on toggle discuss', async function
thread &&
thread.model === 'mail.channel' &&
thread.id === mailChannelId1 &&
scrollTop === messageList.scrollHeight - messageList.clientHeight
isScrolledToBottom(messageList)
);
},
});
@@ -1694,7 +1695,7 @@ QUnit.test('chat window with a thread: keep scroll position in message list on f
thread &&
thread.model === 'mail.channel' &&
thread.id === mailChannelId1 &&
scrollTop === messageList.scrollHeight - messageList.clientHeight
isScrolledToBottom(messageList)
);
},
});
@@ -1782,9 +1783,8 @@ QUnit.test('chat window should scroll to the newly posted message just after pos
)
);
const messageList = document.querySelector('.o_MessageList');
assert.strictEqual(
messageList.scrollHeight - messageList.scrollTop,
messageList.clientHeight,
assert.ok(
isScrolledToBottom(messageList),
"chat window should scroll to the newly posted message just after posting it"
);
});
@@ -1845,7 +1845,7 @@ QUnit.test('chat window with a thread: keep scroll position in message list on t
thread &&
thread.model === 'mail.channel' &&
thread.id === mailChannelId1 &&
scrollTop === messageList.scrollHeight - messageList.clientHeight
isScrolledToBottom(messageList)
);
},
});
@@ -1915,7 +1915,7 @@ QUnit.test('chat window with a thread: keep scroll position in message list on t
thread &&
thread.model === 'mail.channel' &&
thread.id === mailChannelId1 &&
scrollTop === messageList.scrollHeight - messageList.clientHeight
isScrolledToBottom(messageList)
);
},
});
@@ -4,6 +4,7 @@ import BusService from 'bus.BusService';
import {
afterNextRender,
isScrolledToBottom,
nextAnimationFrame,
start,
startServer,
@@ -1636,7 +1637,7 @@ QUnit.test('auto-scroll to bottom of thread', async function (assert) {
thread &&
thread.model === 'mail.channel' &&
thread.id === mailChannelId1 &&
scrollTop === messageList.scrollHeight - messageList.clientHeight
isScrolledToBottom(messageList)
);
},
},
@@ -1649,9 +1650,8 @@ QUnit.test('auto-scroll to bottom of thread', async function (assert) {
"should have 25 messages"
);
const messageList = document.querySelector(`.o_Discuss_thread .o_ThreadView_messageList`);
assert.strictEqual(
messageList.scrollTop,
messageList.scrollHeight - messageList.clientHeight,
assert.ok(
isScrolledToBottom(messageList),
"should have scrolled to bottom of thread"
);
});
@@ -1684,7 +1684,7 @@ QUnit.test('load more messages from channel (auto-load on scroll)', async functi
thread &&
thread.model === 'mail.channel' &&
thread.id === mailChannelId1 &&
scrollTop === messageList.scrollHeight - messageList.clientHeight
isScrolledToBottom(messageList)
);
},
},
@@ -1764,7 +1764,7 @@ QUnit.test('new messages separator [REQUIRE FOCUS]', async function (assert) {
thread &&
thread.model === 'mail.channel' &&
thread.id === mailChannelId1 &&
scrollTop === messageList.scrollHeight - messageList.clientHeight
isScrolledToBottom(messageList)
);
},
},
@@ -1834,7 +1834,7 @@ QUnit.test('new messages separator [REQUIRE FOCUS]', async function (assert) {
thread &&
thread.model === 'mail.channel' &&
thread.id === mailChannelId1 &&
scrollTop === messageList.scrollHeight - messageList.clientHeight
isScrolledToBottom(messageList)
);
},
});
@@ -1898,9 +1898,8 @@ QUnit.test('restore thread scroll position', async function (assert) {
.o_Discuss_thread
.o_ThreadView_messageList
`);
assert.strictEqual(
initialMessageList.scrollTop,
initialMessageList.scrollHeight - initialMessageList.clientHeight,
assert.ok(
isScrolledToBottom(initialMessageList),
"should have scrolled to bottom of channel 1 initially"
);
@@ -1943,7 +1942,7 @@ QUnit.test('restore thread scroll position', async function (assert) {
thread &&
thread.model === 'mail.channel' &&
thread.id === mailChannelId2 &&
scrollTop === messageList.scrollHeight - messageList.clientHeight
isScrolledToBottom(messageList)
);
},
});
@@ -2006,14 +2005,13 @@ QUnit.test('restore thread scroll position', async function (assert) {
thread &&
thread.model === 'mail.channel' &&
thread.id === mailChannelId2 &&
scrollTop === messageList.scrollHeight - messageList.clientHeight
isScrolledToBottom(messageList)
);
},
});
const messageList = document.querySelector('.o_ThreadView_messageList');
assert.strictEqual(
messageList.scrollTop,
messageList.scrollHeight - messageList.clientHeight,
assert.ok(
isScrolledToBottom(messageList),
"should have recovered scroll position of channel 2 (scroll to bottom)"
);
});
@@ -3472,7 +3470,7 @@ QUnit.test('all messages in "Inbox" in "History" after marked all as read', asyn
thread.model === 'mail.box' &&
thread.id === 'inbox' &&
orderedMessages.length === 30 &&
scrollTop === messageList.scrollHeight - messageList.clientHeight
isScrolledToBottom(messageList)
);
},
},
@@ -3502,7 +3500,7 @@ QUnit.test('all messages in "Inbox" in "History" after marked all as read', asyn
thread.model === 'mail.box' &&
thread.id === 'history' &&
orderedMessages.length === 30 &&
scrollTop === messageList.scrollHeight - messageList.clientHeight
isScrolledToBottom(messageList)
);
},
});
@@ -4,6 +4,7 @@ import { insert, insertAndReplace, replace } from '@mail/model/model_field_comma
import {
afterNextRender,
dragenterFiles,
isScrolledToBottom,
start,
startServer,
} from '@mail/../tests/helpers/test_utils';
@@ -794,9 +795,8 @@ QUnit.test('should scroll to bottom on receiving new message if the list is init
predicate: data => threadViewer === data.threadViewer,
});
const initialMessageList = document.querySelector('.o_ThreadView_messageList');
assert.strictEqual(
initialMessageList.scrollTop,
initialMessageList.scrollHeight - initialMessageList.clientHeight,
assert.ok(
isScrolledToBottom(initialMessageList),
"should have scrolled to bottom of channel 20 initially"
);
@@ -818,9 +818,8 @@ QUnit.test('should scroll to bottom on receiving new message if the list is init
predicate: data => threadViewer === data.threadViewer,
});
const messageList = document.querySelector('.o_ThreadView_messageList');
assert.strictEqual(
messageList.scrollTop,
messageList.scrollHeight - messageList.clientHeight,
assert.ok(
isScrolledToBottom(messageList),
"should scroll to bottom on receiving new message because the list is initially scrolled to bottom"
);
});
@@ -866,9 +865,8 @@ QUnit.test('should not scroll on receiving new message if the list is initially
predicate: data => threadViewer === data.threadViewer,
});
const initialMessageList = document.querySelector('.o_ThreadView_messageList');
assert.strictEqual(
initialMessageList.scrollTop,
initialMessageList.scrollHeight - initialMessageList.clientHeight,
assert.ok(
isScrolledToBottom(initialMessageList),
"should have scrolled to bottom of channel 1 initially"
);
@@ -4,6 +4,7 @@ import { makeDeferred } from '@mail/utils/deferred';
import { patchUiSize, SIZES } from '@mail/../tests/helpers/patch_ui_size';
import {
afterNextRender,
isScrolledToBottom,
nextAnimationFrame,
start,
startServer,
@@ -816,9 +817,8 @@ QUnit.test('Form view not scrolled when switching record', async function (asser
'scroll'
);
});
assert.strictEqual(
controllerContentEl.scrollTop,
controllerContentEl.scrollHeight - controllerContentEl.clientHeight,
assert.ok(
isScrolledToBottom(controllerContentEl),
"The controller container should be scrolled to its bottom"
);