Skip to content

Commit

Permalink
bento unit tests: flush all hooks/renders after each test run (#36261)
Browse files Browse the repository at this point in the history
* test-amp-iframe: use act() to flush effects

* generic solution

* clean up

* address feedback

* clean up, address feedback

* fixes + new bug?

* improve flush()

* cleanup

* address jridgewell feedback

* fix bad merge conflict

* missed an await
  • Loading branch information
samouri committed Oct 12, 2021
1 parent 30886d6 commit df11f9e
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 8 deletions.
4 changes: 1 addition & 3 deletions extensions/amp-iframe/1.0/component.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,7 @@ export function BentoIframe({

useEffect(() => {
const iframe = iframeRef.current;
// TODO(36239): Ensure that effects are properly isolated between test runs.
// Guarding for iframe truthiness should be enough.
if (!iframe?.ownerDocument.defaultView) {
if (!iframe) {
return;
}
const win = getWin(iframe);
Expand Down
7 changes: 3 additions & 4 deletions extensions/amp-iframe/1.0/test/test-amp-iframe.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {htmlFor} from '#core/dom/static-template';
import {toggleExperiment} from '#experiments';
import {waitFor} from '#testing/test-helper';
import {whenUpgradedToCustomElement} from '#core/dom/amp-element-helpers';
import {flush} from '#testing/preact';

describes.realWin(
'amp-iframe-v1.0',
Expand All @@ -17,10 +18,8 @@ describes.realWin(
async function waitRendered() {
await whenUpgradedToCustomElement(element);
await element.mount();
await waitFor(
() => element.shadowRoot.querySelector('iframe'),
'iframe rendered'
);
await flush();
await waitFor(() => element.shadowRoot.querySelector('iframe'));
}

beforeEach(() => {
Expand Down
5 changes: 4 additions & 1 deletion testing/init-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
warnForConsoleError,
} from './console-logging-setup';
import * as describes from './describes';
import {flush as flushPreactEffects} from './preact';
import {TestConfig} from './test-config';
import {installYieldIt} from './yield';

Expand Down Expand Up @@ -156,8 +157,10 @@ function resetTestingState() {

/**
* Cleans up global state added during tests.
* @return {Promise<void>}
*/
function cleanupTestcase() {
async function cleanupTestcase() {
await flushPreactEffects();
setTestRunner(this);
restoreConsoleSandbox();
restoreConsoleError();
Expand Down
64 changes: 64 additions & 0 deletions testing/preact.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import * as preact from /*OK*/ 'preact';

/**
* This file introduces a helper for draining Preact's queue of renders and effects.
* We use this as part of the afterEach() cleanup in unit tests, to ensure no effects are run
* in subsequent tests.
*
* There is still a test isolation issue in that an effect can asynchronously schedule work
* which cannot be guarded from at this layer. For that we'd likely need to refresh the window
* in between each test run.
*
* We should be able to remove this file if this feature lands in Preact.
* @fileoverview
*/

const rafs = [];
/**
* @param {(ts: (DOMHighResTimeStamp) => number)} cb
* @return {number}
*/
function flushableRaf(cb) {
rafs.push(cb);
return requestAnimationFrame(flushRaf);
}

function flushRaf(ts = performance.now()) {
for (const raf of rafs) {
raf(ts);
}
rafs.length = 0;
}

let pendingRender;

/**
* @param {() => void} process
* @return {Promise<void>}
*/
async function flushableRender(process) {
pendingRender = () => {
pendingRender = null;
return process();
};
await Promise.resolve().then(pendingRender);
}

/**
* Flushes Preact renders and effects.
*
* Effects may queue up further rerenders, etc. etc,
* so this function will loop until everything to resolves.
*
* @return {Promise<void>}
*/
export async function flush() {
flushRaf();
while (pendingRender) {
await pendingRender();
flushRaf();
}
}

preact.options.requestAnimationFrame = flushableRaf;
preact.options.debounceRendering = flushableRender;

0 comments on commit df11f9e

Please sign in to comment.