Spaces:
Running
Running
| 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; | |
| } | |
| } | |