From 5ba1322f6079d4c183b5fc9d67da9984c94f7564 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Debongnie?= Date: Mon, 19 Dec 2022 14:59:36 +0000 Subject: [PATCH] [FIX] web: owl compatibility test helper fix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) Signed-off-by: Géry Debongnie --- .../web/static/tests/owl2_compatibility_app.js | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/addons/web/static/tests/owl2_compatibility_app.js b/addons/web/static/tests/owl2_compatibility_app.js index 6a3431995bb..01acb1db141 100644 --- a/addons/web/static/tests/owl2_compatibility_app.js +++ b/addons/web/static/tests/owl2_compatibility_app.js @@ -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); } };