Spaces:
Running
Running
File size: 3,048 Bytes
e8a57cb | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 | import { options } from 'preact';
/**
* Setup a rerender function that will drain the queue of pending renders
* @returns {() => void}
*/
export function setupRerender() {
options.__test__previousDebounce = options.debounceRendering;
options.debounceRendering = cb => (options.__test__drainQueue = cb);
return () => options.__test__drainQueue && options.__test__drainQueue();
}
const isThenable = value => value != null && typeof value.then == 'function';
/** Depth of nested calls to `act`. */
let actDepth = 0;
/**
* Run a test function, and flush all effects and rerenders after invoking it.
*
* Returns a Promise which resolves "immediately" if the callback is
* synchronous or when the callback's result resolves if it is asynchronous.
*
* @param {() => void|Promise<void>} cb The function under test. This may be sync or async.
* @return {Promise<void>}
*/
export function act(cb) {
if (++actDepth > 1) {
// If calls to `act` are nested, a flush happens only when the
// outermost call returns. In the inner call, we just execute the
// callback and return since the infrastructure for flushing has already
// been set up.
//
// If an exception occurs, the outermost `act` will handle cleanup.
try {
const result = cb();
if (isThenable(result)) {
return result.then(
() => {
--actDepth;
},
e => {
--actDepth;
throw e;
}
);
}
} catch (e) {
--actDepth;
throw e;
}
--actDepth;
return Promise.resolve();
}
const previousRequestAnimationFrame = options.requestAnimationFrame;
const rerender = setupRerender();
/** @type {() => void} */
let flushes = [], toFlush;
// Override requestAnimationFrame so we can flush pending hooks.
options.requestAnimationFrame = fc => flushes.push(fc);
const finish = () => {
try {
rerender();
while (flushes.length) {
toFlush = flushes;
flushes = [];
toFlush.forEach(x => x());
rerender();
}
} catch (e) {
if (!err) {
err = e;
}
} finally {
teardown();
}
options.requestAnimationFrame = previousRequestAnimationFrame;
--actDepth;
};
let err;
let result;
try {
result = cb();
} catch (e) {
err = e;
}
if (isThenable(result)) {
return result.then(finish, err => {
finish();
throw err;
});
}
// nb. If the callback is synchronous, effects must be flushed before
// `act` returns, so that the caller does not have to await the result,
// even though React recommends this.
finish();
if (err) {
throw err;
}
return Promise.resolve();
}
/**
* Teardown test environment and reset preact's internal state
*/
export function teardown() {
if (options.__test__drainQueue) {
// Flush any pending updates leftover by test
options.__test__drainQueue();
delete options.__test__drainQueue;
}
if (typeof options.__test__previousDebounce != 'undefined') {
options.debounceRendering = options.__test__previousDebounce;
delete options.__test__previousDebounce;
} else {
options.debounceRendering = undefined;
}
}
|