17
describe('ReactDOMRoot', () => {
18
let container;
19
20
- let scheduledCallback;
21
- let flush;
22
- let now;
23
- let expire;
20
+ let advanceCurrentTime;
21
22
beforeEach(() => {
23
container = document.createElement('div');
27
-
28
- // Override requestIdleCallback
29
- scheduledCallback = null;
30
- flush = function(units = Infinity) {
31
- if (scheduledCallback !== null) {
32
- let didStop = false;
33
- while (scheduledCallback !== null && !didStop) {
34
- const cb = scheduledCallback;
35
- scheduledCallback = null;
36
- cb({
37
- timeRemaining() {
38
- if (units > 0) {
39
- return 999;
40
- }
41
- didStop = true;
42
- return 0;
43
- },
44
- });
45
- units--;
46
- }
24
+ // TODO pull this into helper method, reduce repetition.
25
+ // mock the browser APIs which are used in react-scheduler:
26
+ // - requestAnimationFrame should pass the DOMHighResTimeStamp argument
27
+ // - calling 'window.postMessage' should actually fire postmessage handlers
28
+ // - must allow artificially changing time returned by Date.now
29
+ // Performance.now is not supported in the test environment
30
+ const originalDateNow = Date.now;
31
+ let advancedTime = null;
32
+ global.Date.now = function() {
33
+ if (advancedTime) {
34
+ return originalDateNow() + advancedTime;
35
}
36
+ return originalDateNow();
37
};
49
- global.performance = {
50
- now() {
51
- return now;
52
- },
38
+ advanceCurrentTime = function(amount) {
39
+ advancedTime = amount;
40
};
54
- global.requestIdleCallback = function(cb) {
55
- scheduledCallback = cb;
41
+ global.requestAnimationFrame = function(cb) {
42
+ return setTimeout(() => {
43
+ cb(Date.now());
44
+ });
45
};
57
-
58
- now = 0;
59
- expire = function(ms) {
60
- now += ms;
46
+ const originalAddEventListener = global.addEventListener;
47
+ let postMessageCallback;
48
+ global.addEventListener = function(eventName, callback, useCapture) {
49
+ if (eventName === 'message') {
50
+ postMessageCallback = callback;
51
+ } else {
52
+ originalAddEventListener(eventName, callback, useCapture);
53
+ }
54
+ };
55
+ global.postMessage = function(messageKey, targetOrigin) {
56
+ const postMessageEvent = {source: window, data: messageKey};
57
+ if (postMessageCallback) {
58
+ postMessageCallback(postMessageEvent);
59
+ }
60
};
61
62
jest.resetModules();
69
it('renders children', () => {
70
const root = ReactDOM.unstable_createRoot(container);
71
root.render(<div>Hi</div>);
73
- flush();
72
+ jest.runAllTimers();
73
expect(container.textContent).toEqual('Hi');
74
});
75
76
it('unmounts children', () => {
77
const root = ReactDOM.unstable_createRoot(container);
78
root.render(<div>Hi</div>);
80
- flush();
79
+ jest.runAllTimers();
80
expect(container.textContent).toEqual('Hi');
81
root.unmount();
83
- flush();
82
+ jest.runAllTimers();
83
expect(container.textContent).toEqual('');
84
});
85
91
ops.push('inside callback: ' + container.textContent);
92
});
93
ops.push('before committing: ' + container.textContent);
95
- flush();
94
+ jest.runAllTimers();
95
ops.push('after committing: ' + container.textContent);
96
expect(ops).toEqual([
97
'before committing: ',
104
it('resolves `work.then` callback synchronously if the work already committed', () => {
105
const root = ReactDOM.unstable_createRoot(container);
106
const work = root.render(<AsyncMode>Hi</AsyncMode>);
108
- flush();
107
+ jest.runAllTimers();
108
let ops = [];
109
work.then(() => {
110
ops.push('inside callback');
132
<span />
133
</div>,
134
);
136
- flush();
135
+ jest.runAllTimers();
136
137
// Accepts `hydrate` option
138
const container2 = document.createElement('div');
143
<span />
144
</div>,
145
);
147
- expect(flush).toWarnDev('Extra attributes');
146
+ expect(jest.runAllTimers).toWarnDev('Extra attributes');
147
});
148
149
it('does not clear existing children', async () => {
155
<span>d</span>
156
</div>,
157
);
159
- flush();
158
+ jest.runAllTimers();
159
expect(container.textContent).toEqual('abcd');
160
root.render(
161
<div>
163
<span>c</span>
164
</div>,
165
);
167
- flush();
166
+ jest.runAllTimers();
167
expect(container.textContent).toEqual('abdc');
168
});
169
199
</AsyncMode>,
200
);
201
203
- flush();
202
+ jest.runAllTimers();
203
204
// Hasn't updated yet
205
expect(container.textContent).toEqual('');
228
const batch = root.createBatch();
229
batch.render(<Foo>Hi</Foo>);
230
// Flush all async work.
232
- flush();
231
+ jest.runAllTimers();
232
// Root should complete without committing.
233
expect(ops).toEqual(['Foo']);
234
expect(container.textContent).toEqual('');
246
const batch = root.createBatch();
247
batch.render(<AsyncMode>Foo</AsyncMode>);
248
250
- flush();
249
+ jest.runAllTimers();
250
251
// Hasn't updated yet
252
expect(container.textContent).toEqual('');
286
const root = ReactDOM.unstable_createRoot(container);
287
root.render(<AsyncMode>1</AsyncMode>);
288
290
- expire(2000);
289
+ advanceCurrentTime(2000);
290
// This batch has a later expiration time than the earlier update.
291
const batch = root.createBatch();
292
294
batch.commit();
295
expect(container.textContent).toEqual('');
296
298
- flush();
297
+ jest.runAllTimers();
298
expect(container.textContent).toEqual('1');
299
});
300
321
batch1.render(1);
322
323
// This batch has a later expiration time
325
- expire(2000);
324
+ advanceCurrentTime(2000);
325
const batch2 = root.createBatch();
326
batch2.render(2);
327
340
batch1.render(1);
341
342
// This batch has a later expiration time
344
- expire(2000);
343
+ advanceCurrentTime(2000);
344
const batch2 = root.createBatch();
345
batch2.render(2);
346
350
expect(container.textContent).toEqual('2');
351
352
batch1.commit();
354
- flush();
353
+ jest.runAllTimers();
354
expect(container.textContent).toEqual('1');
355
});
356