-
-
Notifications
You must be signed in to change notification settings - Fork 2k
Expand file tree
/
Copy pathindex.js
More file actions
141 lines (124 loc) · 3.63 KB
/
Copy pathindex.js
File metadata and controls
141 lines (124 loc) · 3.63 KB
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
131
132
133
134
135
136
137
138
139
140
141
import { options } from 'preact';
// The scheduler in place when test-utils loads (e.g. the effect-flushing
// scheduler installed by preact/hooks). Used as the reset value on teardown
// so that resetting does not strip renderer-installed schedulers.
const initialDebounce = options.debounceRendering;
/**
* 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;
}
// The `in` check tells a saved `undefined` apart from "setupRerender was
// never called": act() must restore `undefined` when the scheduler was
// explicitly unset before it ran. Without a saved value we reset to the
// scheduler present at load time instead of `undefined`, so that
// schedulers installed by renderers (e.g. the effect-flushing scheduler
// from preact/hooks) survive test teardowns.
if ('__test__previousDebounce' in options) {
options.debounceRendering = options.__test__previousDebounce;
delete options.__test__previousDebounce;
} else {
options.debounceRendering = initialDebounce;
}
}