[FIX] web: owl compatibility test helper fix

The `afternextrender` helper is meant to be used in some tests that
want to do something after owl has completed all its work following
a specific action. It is useful when the action may cause some
additional renders, so waiting for one nextTick is not enough.

Unfortunately, it has a bug: it waits for Owl scheduler to be empty. But
in some cases, when a component is destroyed because of an error,
the scheduler task list will not be cleared until the next render
completes. It is implemented that way in Owl to reduce the amount of
work it does. But this means that in some testing scenarios, the
afterNextRender promise will simply not be resolved, since there is no
upcoming render to clear the tasklist.

With this commit, we just avoid the issue by ignoring the fibers coming
from destroyed component.

Note that I think that it is not a good semantic for tests, as it is a
little bit too "black-boxy". Ideally, we should rather wait for something
in the DOM. There is a waitUntil helper somewhere that may be useful for
this situation

closes odoo/odoo#115229

X-original-commit: ca8d1e7ea74120dd088c78bb4916aa994e852a0f
Signed-off-by: Aaron Bohy (aab) <aab@odoo.com>
Signed-off-by: Géry Debongnie <ged@odoo.com>
This commit is contained in:
Géry Debongnie
2023-03-14 18:09:10 +01:00
parent eb33fdf9d8
commit 5ba1322f60
@@ -4,12 +4,21 @@
const stopPromises = [];
const schedulers = new Set();
function isRunning(scheduler) {
for (const fiber of scheduler.tasks) {
if (fiber.node.status !== 2) {
return true;
}
}
return false;
}
function hookIntoScheduler(scheduler) {
const { flush: originalFlush } = scheduler;
scheduler.flush = function flush() {
originalFlush.call(this, ...arguments);
window.requestAnimationFrame(() => {
if ([...schedulers].every(({ tasks }) => tasks.size === 0)) {
if ([...schedulers].every((scheduler) => !isRunning(scheduler))) {
while (stopPromises.length) {
stopPromises.pop().resolve();
}
@@ -47,10 +56,11 @@
const stopError = new Error("Timeout: the render didn't stop.");
// Set up the timeout to reject if no render happens.
let timeoutNoRender;
const timeoutProm = new Promise((resolve, reject) => {
timeoutNoRender = setTimeout(() => {
let error = startError;
const runningSchedulers = [...schedulers].filter(({ tasks }) => tasks.size > 0);
const runningSchedulers = [...schedulers].filter(isRunning);
if (runningSchedulers.length) {
error = stopError;
}
@@ -82,7 +92,7 @@
await new Promise(function (resolve) {
setTimeout(() => window.requestAnimationFrame(() => resolve()));
});
if ([...schedulers].some(({ tasks }) => tasks.size > 0)) {
if ([...schedulers].some(isRunning)) {
await afterNextRender(() => {}, timeoutDelay);
}
};