Codemod tests to `it.experimental` (#17243)
`it.experimental` marks that a test only works in Experimental builds. It also asserts that a test does *not* work in the stable builds. The main benefit is that we're less likely to accidentally expose an experimental API before we intend. It also forces us to un- mark an experimental test once it become stable.
Andrew Clark committed
Nov 1, 2019 at 10:20 UTC
6dc2734b41aef944e457eaa23ae218952fce0a54
10 files changed
+1005
-1008
packages/react-dom/src/__tests__/ReactDOMFiberAsync-test.internal.js
+295
-284
@@ -47,255 +47,254 @@ describe('ReactDOMFiberAsync', () => {
47
expect(ops).toEqual(['Hi', 'Bye']);
48
});
49
50
- if (__EXPERIMENTAL__) {
51
- describe('concurrent mode', () => {
52
- beforeEach(() => {
53
- jest.resetModules();
54
- ReactFeatureFlags = require('shared/ReactFeatureFlags');
55
- ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
56
- ReactDOM = require('react-dom');
57
- Scheduler = require('scheduler');
58
- });
59
-
60
- it('does not perform deferred updates synchronously', () => {
61
- let inputRef = React.createRef();
62
- let asyncValueRef = React.createRef();
63
- let syncValueRef = React.createRef();
64
-
65
- class Counter extends React.Component {
66
- state = {asyncValue: '', syncValue: ''};
67
-
68
- handleChange = e => {
69
- const nextValue = e.target.value;
70
- requestIdleCallback(() => {
71
- this.setState({
72
- asyncValue: nextValue,
73
- });
74
- // It should not be flushed yet.
75
- expect(asyncValueRef.current.textContent).toBe('');
76
- });
77
- this.setState({
78
- syncValue: nextValue,
79
- });
80
- };
81
-
82
- render() {
83
- return (
84
- <div>
85
- <input
86
- ref={inputRef}
87
- onChange={this.handleChange}
88
- defaultValue=""
89
- />
90
- <p ref={asyncValueRef}>{this.state.asyncValue}</p>
91
- <p ref={syncValueRef}>{this.state.syncValue}</p>
92
- </div>
93
- );
94
- }
95
- }
96
- const root = ReactDOM.createRoot(container);
97
- root.render(<Counter />);
98
- Scheduler.unstable_flushAll();
99
- expect(asyncValueRef.current.textContent).toBe('');
100
- expect(syncValueRef.current.textContent).toBe('');
101
-
102
- setUntrackedInputValue.call(inputRef.current, 'hello');
103
- inputRef.current.dispatchEvent(
104
- new MouseEvent('input', {bubbles: true}),
105
- );
106
- // Should only flush non-deferred update.
107
- expect(asyncValueRef.current.textContent).toBe('');
108
- expect(syncValueRef.current.textContent).toBe('hello');
109
-
110
- // Should flush both updates now.
111
- jest.runAllTimers();
112
- Scheduler.unstable_flushAll();
113
- expect(asyncValueRef.current.textContent).toBe('hello');
114
- expect(syncValueRef.current.textContent).toBe('hello');
115
- });
116
-
117
- it('top-level updates are concurrent', () => {
118
- const root = ReactDOM.createRoot(container);
119
- root.render(<div>Hi</div>);
120
- expect(container.textContent).toEqual('');
121
- Scheduler.unstable_flushAll();
122
- expect(container.textContent).toEqual('Hi');
123
-
124
- root.render(<div>Bye</div>);
125
- expect(container.textContent).toEqual('Hi');
126
- Scheduler.unstable_flushAll();
127
- expect(container.textContent).toEqual('Bye');
128
- });
129
-
130
- it('deep updates (setState) are concurrent', () => {
131
- let instance;
132
- class Component extends React.Component {
133
- state = {step: 0};
134
- render() {
135
- instance = this;
136
- return <div>{this.state.step}</div>;
137
- }
138
- }
139
-
140
- const root = ReactDOM.createRoot(container);
141
- root.render(<Component />);
142
- expect(container.textContent).toEqual('');
143
- Scheduler.unstable_flushAll();
144
- expect(container.textContent).toEqual('0');
145
-
146
- instance.setState({step: 1});
147
- expect(container.textContent).toEqual('0');
148
- Scheduler.unstable_flushAll();
149
- expect(container.textContent).toEqual('1');
150
- });
151
-
152
- it('flushSync batches sync updates and flushes them at the end of the batch', () => {
153
- let ops = [];
154
- let instance;
155
-
156
- class Component extends React.Component {
157
- state = {text: ''};
158
- push(val) {
159
- this.setState(state => ({text: state.text + val}));
160
- }
161
- componentDidUpdate() {
162
- ops.push(this.state.text);
163
- }
164
- render() {
165
- instance = this;
166
- return <span>{this.state.text}</span>;
167
- }
168
- }
169
-
170
- ReactDOM.render(<Component />, container);
171
-
172
- instance.push('A');
173
- expect(ops).toEqual(['A']);
174
- expect(container.textContent).toEqual('A');
50
+ it('flushSync batches sync updates and flushes them at the end of the batch', () => {
51
+ let ops = [];
52
+ let instance;
53
+
54
+ class Component extends React.Component {
55
+ state = {text: ''};
56
+ push(val) {
57
+ this.setState(state => ({text: state.text + val}));
58
+ }
59
+ componentDidUpdate() {
60
+ ops.push(this.state.text);
61
+ }
62
+ render() {
63
+ instance = this;
64
+ return <span>{this.state.text}</span>;
65
+ }
66
+ }
67
+
68
+ ReactDOM.render(<Component />, container);
69
+
70
+ instance.push('A');
71
+ expect(ops).toEqual(['A']);
72
+ expect(container.textContent).toEqual('A');
73
+
74
+ ReactDOM.flushSync(() => {
75
+ instance.push('B');
76
+ instance.push('C');
77
+ // Not flushed yet
78
+ expect(container.textContent).toEqual('A');
79
+ expect(ops).toEqual(['A']);
80
+ });
81
+ expect(container.textContent).toEqual('ABC');
82
+ expect(ops).toEqual(['A', 'ABC']);
83
+ instance.push('D');
84
+ expect(container.textContent).toEqual('ABCD');
85
+ expect(ops).toEqual(['A', 'ABC', 'ABCD']);
86
+ });
87
176
- ReactDOM.flushSync(() => {
177
- instance.push('B');
178
- instance.push('C');
179
- // Not flushed yet
180
- expect(container.textContent).toEqual('A');
181
- expect(ops).toEqual(['A']);
182
- });
183
- expect(container.textContent).toEqual('ABC');
184
- expect(ops).toEqual(['A', 'ABC']);
88
+ it('flushSync flushes updates even if nested inside another flushSync', () => {
89
+ let ops = [];
90
+ let instance;
91
+
92
+ class Component extends React.Component {
93
+ state = {text: ''};
94
+ push(val) {
95
+ this.setState(state => ({text: state.text + val}));
96
+ }
97
+ componentDidUpdate() {
98
+ ops.push(this.state.text);
99
+ }
100
+ render() {
101
+ instance = this;
102
+ return <span>{this.state.text}</span>;
103
+ }
104
+ }
105
+
106
+ ReactDOM.render(<Component />, container);
107
+
108
+ instance.push('A');
109
+ expect(ops).toEqual(['A']);
110
+ expect(container.textContent).toEqual('A');
111
+
112
+ ReactDOM.flushSync(() => {
113
+ instance.push('B');
114
+ instance.push('C');
115
+ // Not flushed yet
116
+ expect(container.textContent).toEqual('A');
117
+ expect(ops).toEqual(['A']);
118
+
119
+ ReactDOM.flushSync(() => {
120
instance.push('D');
186
- expect(container.textContent).toEqual('ABCD');
187
- expect(ops).toEqual(['A', 'ABC', 'ABCD']);
121
});
122
+ // The nested flushSync caused everything to flush.
123
+ expect(container.textContent).toEqual('ABCD');
124
+ expect(ops).toEqual(['A', 'ABCD']);
125
+ });
126
+ expect(container.textContent).toEqual('ABCD');
127
+ expect(ops).toEqual(['A', 'ABCD']);
128
+ });
129
190
- it('flushSync flushes updates even if nested inside another flushSync', () => {
191
- let ops = [];
192
- let instance;
193
-
194
- class Component extends React.Component {
195
- state = {text: ''};
196
- push(val) {
197
- this.setState(state => ({text: state.text + val}));
198
- }
199
- componentDidUpdate() {
200
- ops.push(this.state.text);
201
- }
202
- render() {
203
- instance = this;
204
- return <span>{this.state.text}</span>;
205
- }
206
- }
130
+ it('flushSync throws if already performing work', () => {
131
+ class Component extends React.Component {
132
+ componentDidUpdate() {
133
+ ReactDOM.flushSync(() => {});
134
+ }
135
+ render() {
136
+ return null;
137
+ }
138
+ }
139
+
140
+ // Initial mount
141
+ ReactDOM.render(<Component />, container);
142
+ // Update
143
+ expect(() => ReactDOM.render(<Component />, container)).toThrow(
144
+ 'flushSync was called from inside a lifecycle method',
145
+ );
146
+ });
147
208
- ReactDOM.render(<Component />, container);
148
+ describe('concurrent mode', () => {
149
+ beforeEach(() => {
150
+ jest.resetModules();
151
+ ReactFeatureFlags = require('shared/ReactFeatureFlags');
152
+ ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
153
+ ReactDOM = require('react-dom');
154
+ Scheduler = require('scheduler');
155
+ });
156
210
- instance.push('A');
211
- expect(ops).toEqual(['A']);
212
- expect(container.textContent).toEqual('A');
157
+ it.experimental('does not perform deferred updates synchronously', () => {
158
+ let inputRef = React.createRef();
159
+ let asyncValueRef = React.createRef();
160
+ let syncValueRef = React.createRef();
161
214
- ReactDOM.flushSync(() => {
215
- instance.push('B');
216
- instance.push('C');
217
- // Not flushed yet
218
- expect(container.textContent).toEqual('A');
219
- expect(ops).toEqual(['A']);
162
+ class Counter extends React.Component {
163
+ state = {asyncValue: '', syncValue: ''};
164
221
- ReactDOM.flushSync(() => {
222
- instance.push('D');
165
+ handleChange = e => {
166
+ const nextValue = e.target.value;
167
+ requestIdleCallback(() => {
168
+ this.setState({
169
+ asyncValue: nextValue,
170
+ });
171
+ // It should not be flushed yet.
172
+ expect(asyncValueRef.current.textContent).toBe('');
173
});
224
- // The nested flushSync caused everything to flush.
225
- expect(container.textContent).toEqual('ABCD');
226
- expect(ops).toEqual(['A', 'ABCD']);
227
- });
228
- expect(container.textContent).toEqual('ABCD');
229
- expect(ops).toEqual(['A', 'ABCD']);
230
- });
231
-
232
- it('flushSync throws if already performing work', () => {
233
- class Component extends React.Component {
234
- componentDidUpdate() {
235
- ReactDOM.flushSync(() => {});
236
- }
237
- render() {
238
- return null;
239
- }
174
+ this.setState({
175
+ syncValue: nextValue,
176
+ });
177
+ };
178
+
179
+ render() {
180
+ return (
181
+ <div>
182
+ <input
183
+ ref={inputRef}
184
+ onChange={this.handleChange}
185
+ defaultValue=""
186
+ />
187
+ <p ref={asyncValueRef}>{this.state.asyncValue}</p>
188
+ <p ref={syncValueRef}>{this.state.syncValue}</p>
189
+ </div>
190
+ );
191
}
192
+ }
193
+ const root = ReactDOM.createRoot(container);
194
+ root.render(<Counter />);
195
+ Scheduler.unstable_flushAll();
196
+ expect(asyncValueRef.current.textContent).toBe('');
197
+ expect(syncValueRef.current.textContent).toBe('');
198
+
199
+ setUntrackedInputValue.call(inputRef.current, 'hello');
200
+ inputRef.current.dispatchEvent(new MouseEvent('input', {bubbles: true}));
201
+ // Should only flush non-deferred update.
202
+ expect(asyncValueRef.current.textContent).toBe('');
203
+ expect(syncValueRef.current.textContent).toBe('hello');
204
+
205
+ // Should flush both updates now.
206
+ jest.runAllTimers();
207
+ Scheduler.unstable_flushAll();
208
+ expect(asyncValueRef.current.textContent).toBe('hello');
209
+ expect(syncValueRef.current.textContent).toBe('hello');
210
+ });
211
242
- // Initial mount
243
- ReactDOM.render(<Component />, container);
244
- // Update
245
- expect(() => ReactDOM.render(<Component />, container)).toThrow(
246
- 'flushSync was called from inside a lifecycle method',
247
- );
248
- });
249
-
250
- it('flushSync flushes updates before end of the tick', () => {
251
- let ops = [];
252
- let instance;
212
+ it.experimental('top-level updates are concurrent', () => {
213
+ const root = ReactDOM.createRoot(container);
214
+ root.render(<div>Hi</div>);
215
+ expect(container.textContent).toEqual('');
216
+ Scheduler.unstable_flushAll();
217
+ expect(container.textContent).toEqual('Hi');
218
+
219
+ root.render(<div>Bye</div>);
220
+ expect(container.textContent).toEqual('Hi');
221
+ Scheduler.unstable_flushAll();
222
+ expect(container.textContent).toEqual('Bye');
223
+ });
224
254
- class Component extends React.Component {
255
- state = {text: ''};
256
- push(val) {
257
- this.setState(state => ({text: state.text + val}));
258
- }
259
- componentDidUpdate() {
260
- ops.push(this.state.text);
261
- }
262
- render() {
263
- instance = this;
264
- return <span>{this.state.text}</span>;
265
- }
225
+ it.experimental('deep updates (setState) are concurrent', () => {
226
+ let instance;
227
+ class Component extends React.Component {
228
+ state = {step: 0};
229
+ render() {
230
+ instance = this;
231
+ return <div>{this.state.step}</div>;
232
}
233
+ }
234
+
235
+ const root = ReactDOM.createRoot(container);
236
+ root.render(<Component />);
237
+ expect(container.textContent).toEqual('');
238
+ Scheduler.unstable_flushAll();
239
+ expect(container.textContent).toEqual('0');
240
+
241
+ instance.setState({step: 1});
242
+ expect(container.textContent).toEqual('0');
243
+ Scheduler.unstable_flushAll();
244
+ expect(container.textContent).toEqual('1');
245
+ });
246
268
- const root = ReactDOM.createRoot(container);
269
- root.render(<Component />);
270
- Scheduler.unstable_flushAll();
247
+ it.experimental('flushSync flushes updates before end of the tick', () => {
248
+ let ops = [];
249
+ let instance;
250
272
- // Updates are async by default
273
- instance.push('A');
274
- expect(ops).toEqual([]);
275
- expect(container.textContent).toEqual('');
251
+ class Component extends React.Component {
252
+ state = {text: ''};
253
+ push(val) {
254
+ this.setState(state => ({text: state.text + val}));
255
+ }
256
+ componentDidUpdate() {
257
+ ops.push(this.state.text);
258
+ }
259
+ render() {
260
+ instance = this;
261
+ return <span>{this.state.text}</span>;
262
+ }
263
+ }
264
277
- ReactDOM.flushSync(() => {
278
- instance.push('B');
279
- instance.push('C');
280
- // Not flushed yet
281
- expect(container.textContent).toEqual('');
282
- expect(ops).toEqual([]);
283
- });
284
- // Only the active updates have flushed
285
- expect(container.textContent).toEqual('BC');
286
- expect(ops).toEqual(['BC']);
265
+ const root = ReactDOM.createRoot(container);
266
+ root.render(<Component />);
267
+ Scheduler.unstable_flushAll();
268
288
- instance.push('D');
289
- expect(container.textContent).toEqual('BC');
290
- expect(ops).toEqual(['BC']);
269
+ // Updates are async by default
270
+ instance.push('A');
271
+ expect(ops).toEqual([]);
272
+ expect(container.textContent).toEqual('');
273
292
- // Flush the async updates
293
- Scheduler.unstable_flushAll();
294
- expect(container.textContent).toEqual('ABCD');
295
- expect(ops).toEqual(['BC', 'ABCD']);
274
+ ReactDOM.flushSync(() => {
275
+ instance.push('B');
276
+ instance.push('C');
277
+ // Not flushed yet
278
+ expect(container.textContent).toEqual('');
279
+ expect(ops).toEqual([]);
280
});
281
+ // Only the active updates have flushed
282
+ expect(container.textContent).toEqual('BC');
283
+ expect(ops).toEqual(['BC']);
284
+
285
+ instance.push('D');
286
+ expect(container.textContent).toEqual('BC');
287
+ expect(ops).toEqual(['BC']);
288
+
289
+ // Flush the async updates
290
+ Scheduler.unstable_flushAll();
291
+ expect(container.textContent).toEqual('ABCD');
292
+ expect(ops).toEqual(['BC', 'ABCD']);
293
+ });
294
298
- it('flushControlled flushes updates before yielding to browser', () => {
295
+ it.experimental(
296
+ 'flushControlled flushes updates before yielding to browser',
297
+ () => {
298
let inst;
299
class Counter extends React.Component {
300
state = {counter: 0};
@@ -332,9 +331,12 @@ describe('ReactDOMFiberAsync', () => {
331
'end of outer flush: 1',
332
'after outer flush: 3',
333
]);
335
- });
334
+ },
335
+ );
336
337
- it('flushControlled does not flush until end of outermost batchedUpdates', () => {
337
+ it.experimental(
338
+ 'flushControlled does not flush until end of outermost batchedUpdates',
339
+ () => {
340
let inst;
341
class Counter extends React.Component {
342
state = {counter: 0};
@@ -362,32 +364,35 @@ describe('ReactDOMFiberAsync', () => {
364
'end of batchedUpdates fn: 0',
365
'after batchedUpdates: 2',
366
]);
365
- });
366
-
367
- it('flushControlled returns nothing', () => {
368
- // In the future, we may want to return a thenable "work" object.
369
- let inst;
370
- class Counter extends React.Component {
371
- state = {counter: 0};
372
- increment = () =>
373
- this.setState(state => ({counter: state.counter + 1}));
374
- render() {
375
- inst = this;
376
- return this.state.counter;
377
- }
367
+ },
368
+ );
369
+
370
+ it.experimental('flushControlled returns nothing', () => {
371
+ // In the future, we may want to return a thenable "work" object.
372
+ let inst;
373
+ class Counter extends React.Component {
374
+ state = {counter: 0};
375
+ increment = () =>
376
+ this.setState(state => ({counter: state.counter + 1}));
377
+ render() {
378
+ inst = this;
379
+ return this.state.counter;
380
}
379
- ReactDOM.render(<Counter />, container);
380
- expect(container.textContent).toEqual('0');
381
+ }
382
+ ReactDOM.render(<Counter />, container);
383
+ expect(container.textContent).toEqual('0');
384
382
- const returnValue = ReactDOM.unstable_flushControlled(() => {
383
- inst.increment();
384
- return 'something';
385
- });
386
- expect(container.textContent).toEqual('1');
387
- expect(returnValue).toBe(undefined);
385
+ const returnValue = ReactDOM.unstable_flushControlled(() => {
386
+ inst.increment();
387
+ return 'something';
388
});
389
+ expect(container.textContent).toEqual('1');
390
+ expect(returnValue).toBe(undefined);
391
+ });
392
390
- it('ignores discrete events on a pending removed element', () => {
393
+ it.experimental(
394
+ 'ignores discrete events on a pending removed element',
395
+ () => {
396
const disableButtonRef = React.createRef();
397
const submitButtonRef = React.createRef();
398
@@ -446,9 +451,12 @@ describe('ReactDOMFiberAsync', () => {
451
expect(formSubmitted).toBe(false);
452
453
expect(submitButtonRef.current).toBe(null);
449
- });
454
+ },
455
+ );
456
451
- it('ignores discrete events on a pending removed event listener', () => {
457
+ it.experimental(
458
+ 'ignores discrete events on a pending removed event listener',
459
+ () => {
460
const disableButtonRef = React.createRef();
461
const submitButtonRef = React.createRef();
462
@@ -512,9 +520,12 @@ describe('ReactDOMFiberAsync', () => {
520
521
// Therefore the form should never have been submitted.
522
expect(formSubmitted).toBe(false);
515
- });
523
+ },
524
+ );
525
517
- it('uses the newest discrete events on a pending changed event listener', () => {
526
+ it.experimental(
527
+ 'uses the newest discrete events on a pending changed event listener',
528
+ () => {
529
const enableButtonRef = React.createRef();
530
const submitButtonRef = React.createRef();
531
@@ -572,33 +583,33 @@ describe('ReactDOMFiberAsync', () => {
583
584
// Therefore the form should have been submitted.
585
expect(formSubmitted).toBe(true);
575
- });
576
- });
577
-
578
- describe('createBlockingRoot', () => {
579
- it('updates flush without yielding in the next event', () => {
580
- const root = ReactDOM.createBlockingRoot(container);
581
-
582
- function Text(props) {
583
- Scheduler.unstable_yieldValue(props.text);
584
- return props.text;
585
- }
586
-
587
- root.render(
588
- <>
589
- <Text text="A" />
590
- <Text text="B" />
591
- <Text text="C" />
592
- </>,
593
- );
594
-
595
- // Nothing should have rendered yet
596
- expect(container.textContent).toEqual('');
586
+ },
587
+ );
588
+ });
589
598
- // Everything should render immediately in the next event
599
- expect(Scheduler).toFlushExpired(['A', 'B', 'C']);
600
- expect(container.textContent).toEqual('ABC');
601
- });
590
+ describe('createBlockingRoot', () => {
591
+ it.experimental('updates flush without yielding in the next event', () => {
592
+ const root = ReactDOM.createBlockingRoot(container);
593
+
594
+ function Text(props) {
595
+ Scheduler.unstable_yieldValue(props.text);
596
+ return props.text;
597
+ }
598
+
599
+ root.render(
600
+ <>
601
+ <Text text="A" />
602
+ <Text text="B" />
603
+ <Text text="C" />
604
+ </>,
605
+ );
606
+
607
+ // Nothing should have rendered yet
608
+ expect(container.textContent).toEqual('');
609
+
610
+ // Everything should render immediately in the next event
611
+ expect(Scheduler).toFlushExpired(['A', 'B', 'C']);
612
+ expect(container.textContent).toEqual('ABC');
613
});
603
- }
614
+ });
615
});
packages/react-dom/src/__tests__/ReactDOMHooks-test.js
+5
-4
@@ -105,8 +105,9 @@ describe('ReactDOMHooks', () => {
105
expect(labelRef.current.innerHTML).toBe('abc');
106
});
107
108
- if (__EXPERIMENTAL__) {
109
- it('should not bail out when an update is scheduled from within an event handler in Concurrent Mode', () => {
108
+ it.experimental(
109
+ 'should not bail out when an update is scheduled from within an event handler in Concurrent Mode',
110
+ () => {
111
const {createRef, useCallback, useState} = React;
112
113
const Example = ({inputRef, labelRef}) => {
@@ -139,6 +140,6 @@ describe('ReactDOMHooks', () => {
140
Scheduler.unstable_flushAll();
141
142
expect(labelRef.current.innerHTML).toBe('abc');
142
- });
143
- }
143
+ },
144
+ );
145
});
packages/react-dom/src/__tests__/ReactServerRenderingHydration-test.js
+5
-4
@@ -500,8 +500,9 @@ describe('ReactDOMServerHydration', () => {
500
expect(element.textContent).toBe('Hello world');
501
});
502
503
- if (__EXPERIMENTAL__) {
504
- it('does not re-enter hydration after committing the first one', () => {
503
+ it.experimental(
504
+ 'does not re-enter hydration after committing the first one',
505
+ () => {
506
let finalHTML = ReactDOMServer.renderToString(<div />);
507
let container = document.createElement('div');
508
container.innerHTML = finalHTML;
@@ -514,8 +515,8 @@ describe('ReactDOMServerHydration', () => {
515
// warnings.
516
root.render(<div />);
517
Scheduler.unstable_flushAll();
517
- });
518
- }
518
+ },
519
+ );
520
521
it('Suspense + hydration in legacy mode', () => {
522
const element = document.createElement('div');
packages/react-dom/src/__tests__/ReactTestUtilsAct-test.js
+20
-24
@@ -125,31 +125,27 @@ describe('ReactTestUtils.act()', () => {
125
]);
126
});
127
128
- if (__EXPERIMENTAL__) {
129
- it('warns in blocking mode', () => {
130
- expect(() => {
131
- const root = ReactDOM.createBlockingRoot(
132
- document.createElement('div'),
133
- );
134
- root.render(<App />);
135
- Scheduler.unstable_flushAll();
136
- }).toWarnDev([
137
- 'An update to App ran an effect, but was not wrapped in act(...)',
138
- 'An update to App ran an effect, but was not wrapped in act(...)',
139
- ]);
140
- });
128
+ it.experimental('warns in blocking mode', () => {
129
+ expect(() => {
130
+ const root = ReactDOM.createBlockingRoot(document.createElement('div'));
131
+ root.render(<App />);
132
+ Scheduler.unstable_flushAll();
133
+ }).toWarnDev([
134
+ 'An update to App ran an effect, but was not wrapped in act(...)',
135
+ 'An update to App ran an effect, but was not wrapped in act(...)',
136
+ ]);
137
+ });
138
142
- it('warns in concurrent mode', () => {
143
- expect(() => {
144
- const root = ReactDOM.createRoot(document.createElement('div'));
145
- root.render(<App />);
146
- Scheduler.unstable_flushAll();
147
- }).toWarnDev([
148
- 'An update to App ran an effect, but was not wrapped in act(...)',
149
- 'An update to App ran an effect, but was not wrapped in act(...)',
150
- ]);
151
- });
152
- }
139
+ it.experimental('warns in concurrent mode', () => {
140
+ expect(() => {
141
+ const root = ReactDOM.createRoot(document.createElement('div'));
142
+ root.render(<App />);
143
+ Scheduler.unstable_flushAll();
144
+ }).toWarnDev([
145
+ 'An update to App ran an effect, but was not wrapped in act(...)',
146
+ 'An update to App ran an effect, but was not wrapped in act(...)',
147
+ ]);
148
+ });
149
});
150
});
151
packages/react-dom/src/__tests__/ReactUnmockedSchedulerWarning-test.js
+26
-32
@@ -27,36 +27,30 @@ it('does not warn when rendering in legacy mode', () => {
27
}).toWarnDev([]);
28
});
29
30
-if (__EXPERIMENTAL__) {
31
- it('should warn when rendering in concurrent mode', () => {
32
- expect(() => {
33
- ReactDOM.createRoot(document.createElement('div')).render(<App />);
34
- }).toWarnDev(
35
- 'In Concurrent or Sync modes, the "scheduler" module needs to be mocked ' +
36
- 'to guarantee consistent behaviour across tests and browsers.',
37
- {withoutStack: true},
38
- );
39
- // does not warn twice
40
- expect(() => {
41
- ReactDOM.createRoot(document.createElement('div')).render(<App />);
42
- }).toWarnDev([]);
43
- });
30
+it.experimental('should warn when rendering in concurrent mode', () => {
31
+ expect(() => {
32
+ ReactDOM.createRoot(document.createElement('div')).render(<App />);
33
+ }).toWarnDev(
34
+ 'In Concurrent or Sync modes, the "scheduler" module needs to be mocked ' +
35
+ 'to guarantee consistent behaviour across tests and browsers.',
36
+ {withoutStack: true},
37
+ );
38
+ // does not warn twice
39
+ expect(() => {
40
+ ReactDOM.createRoot(document.createElement('div')).render(<App />);
41
+ }).toWarnDev([]);
42
+});
43
45
- it('should warn when rendering in blocking mode', () => {
46
- expect(() => {
47
- ReactDOM.createBlockingRoot(document.createElement('div')).render(
48
- <App />,
49
- );
50
- }).toWarnDev(
51
- 'In Concurrent or Sync modes, the "scheduler" module needs to be mocked ' +
52
- 'to guarantee consistent behaviour across tests and browsers.',
53
- {withoutStack: true},
54
- );
55
- // does not warn twice
56
- expect(() => {
57
- ReactDOM.createBlockingRoot(document.createElement('div')).render(
58
- <App />,
59
- );
60
- }).toWarnDev([]);
61
- });
62
-}
44
+it.experimental('should warn when rendering in blocking mode', () => {
45
+ expect(() => {
46
+ ReactDOM.createBlockingRoot(document.createElement('div')).render(<App />);
47
+ }).toWarnDev(
48
+ 'In Concurrent or Sync modes, the "scheduler" module needs to be mocked ' +
49
+ 'to guarantee consistent behaviour across tests and browsers.',
50
+ {withoutStack: true},
51
+ );
52
+ // does not warn twice
53
+ expect(() => {
54
+ ReactDOM.createBlockingRoot(document.createElement('div')).render(<App />);
55
+ }).toWarnDev([]);
56
+});
packages/react-dom/src/events/__tests__/ChangeEventPlugin-test.internal.js
+264
-263
@@ -474,281 +474,282 @@ describe('ChangeEventPlugin', () => {
474
}
475
});
476
477
- if (__EXPERIMENTAL__) {
478
- describe('concurrent mode', () => {
479
- beforeEach(() => {
480
- jest.resetModules();
481
- ReactFeatureFlags = require('shared/ReactFeatureFlags');
482
- ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
483
- React = require('react');
484
- ReactDOM = require('react-dom');
485
- TestUtils = require('react-dom/test-utils');
486
- Scheduler = require('scheduler');
487
- });
477
+ describe('concurrent mode', () => {
478
+ beforeEach(() => {
479
+ jest.resetModules();
480
+ ReactFeatureFlags = require('shared/ReactFeatureFlags');
481
+ ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
482
+ React = require('react');
483
+ ReactDOM = require('react-dom');
484
+ TestUtils = require('react-dom/test-utils');
485
+ Scheduler = require('scheduler');
486
+ });
487
489
- it('text input', () => {
490
- const root = ReactDOM.createRoot(container);
491
- let input;
492
-
493
- let ops = [];
494
-
495
- class ControlledInput extends React.Component {
496
- state = {value: 'initial'};
497
- onChange = event => this.setState({value: event.target.value});
498
- render() {
499
- ops.push(`render: ${this.state.value}`);
500
- const controlledValue =
501
- this.state.value === 'changed' ? 'changed [!]' : this.state.value;
502
- return (
503
- <input
504
- ref={el => (input = el)}
505
- type="text"
506
- value={controlledValue}
507
- onChange={this.onChange}
508
- />
509
- );
510
- }
511
- }
488
+ it.experimental('text input', () => {
489
+ const root = ReactDOM.createRoot(container);
490
+ let input;
491
513
- // Initial mount. Test that this is async.
514
- root.render(<ControlledInput />);
515
- // Should not have flushed yet.
516
- expect(ops).toEqual([]);
517
- expect(input).toBe(undefined);
518
- // Flush callbacks.
519
- Scheduler.unstable_flushAll();
520
- expect(ops).toEqual(['render: initial']);
521
- expect(input.value).toBe('initial');
522
-
523
- ops = [];
524
-
525
- // Trigger a change event.
526
- setUntrackedValue.call(input, 'changed');
527
- input.dispatchEvent(
528
- new Event('input', {bubbles: true, cancelable: true}),
529
- );
530
- // Change should synchronously flush
531
- expect(ops).toEqual(['render: changed']);
532
- // Value should be the controlled value, not the original one
533
- expect(input.value).toBe('changed [!]');
534
- });
535
-
536
- it('checkbox input', () => {
537
- const root = ReactDOM.createRoot(container);
538
- let input;
539
-
540
- let ops = [];
541
-
542
- class ControlledInput extends React.Component {
543
- state = {checked: false};
544
- onChange = event => {
545
- this.setState({checked: event.target.checked});
546
- };
547
- render() {
548
- ops.push(`render: ${this.state.checked}`);
549
- const controlledValue = this.props.reverse
550
- ? !this.state.checked
551
- : this.state.checked;
552
- return (
553
- <input
554
- ref={el => (input = el)}
555
- type="checkbox"
556
- checked={controlledValue}
557
- onChange={this.onChange}
558
- />
559
- );
560
- }
492
+ let ops = [];
493
+
494
+ class ControlledInput extends React.Component {
495
+ state = {value: 'initial'};
496
+ onChange = event => this.setState({value: event.target.value});
497
+ render() {
498
+ ops.push(`render: ${this.state.value}`);
499
+ const controlledValue =
500
+ this.state.value === 'changed' ? 'changed [!]' : this.state.value;
501
+ return (
502
+ <input
503
+ ref={el => (input = el)}
504
+ type="text"
505
+ value={controlledValue}
506
+ onChange={this.onChange}
507
+ />
508
+ );
509
}
510
+ }
511
563
- // Initial mount. Test that this is async.
564
- root.render(<ControlledInput reverse={false} />);
565
- // Should not have flushed yet.
566
- expect(ops).toEqual([]);
567
- expect(input).toBe(undefined);
568
- // Flush callbacks.
569
- Scheduler.unstable_flushAll();
570
- expect(ops).toEqual(['render: false']);
571
- expect(input.checked).toBe(false);
572
-
573
- ops = [];
574
-
575
- // Trigger a change event.
576
- input.dispatchEvent(
577
- new MouseEvent('click', {bubbles: true, cancelable: true}),
578
- );
579
- // Change should synchronously flush
580
- expect(ops).toEqual(['render: true']);
581
- expect(input.checked).toBe(true);
582
-
583
- // Now let's make sure we're using the controlled value.
584
- root.render(<ControlledInput reverse={true} />);
585
- Scheduler.unstable_flushAll();
586
-
587
- ops = [];
588
-
589
- // Trigger another change event.
590
- input.dispatchEvent(
591
- new MouseEvent('click', {bubbles: true, cancelable: true}),
592
- );
593
- // Change should synchronously flush
594
- expect(ops).toEqual(['render: true']);
595
- expect(input.checked).toBe(false);
596
- });
597
-
598
- it('textarea', () => {
599
- const root = ReactDOM.createRoot(container);
600
- let textarea;
601
-
602
- let ops = [];
603
-
604
- class ControlledTextarea extends React.Component {
605
- state = {value: 'initial'};
606
- onChange = event => this.setState({value: event.target.value});
607
- render() {
608
- ops.push(`render: ${this.state.value}`);
609
- const controlledValue =
610
- this.state.value === 'changed' ? 'changed [!]' : this.state.value;
611
- return (
612
- <textarea
613
- ref={el => (textarea = el)}
614
- type="text"
615
- value={controlledValue}
616
- onChange={this.onChange}
617
- />
618
- );
619
- }
512
+ // Initial mount. Test that this is async.
513
+ root.render(<ControlledInput />);
514
+ // Should not have flushed yet.
515
+ expect(ops).toEqual([]);
516
+ expect(input).toBe(undefined);
517
+ // Flush callbacks.
518
+ Scheduler.unstable_flushAll();
519
+ expect(ops).toEqual(['render: initial']);
520
+ expect(input.value).toBe('initial');
521
+
522
+ ops = [];
523
+
524
+ // Trigger a change event.
525
+ setUntrackedValue.call(input, 'changed');
526
+ input.dispatchEvent(
527
+ new Event('input', {bubbles: true, cancelable: true}),
528
+ );
529
+ // Change should synchronously flush
530
+ expect(ops).toEqual(['render: changed']);
531
+ // Value should be the controlled value, not the original one
532
+ expect(input.value).toBe('changed [!]');
533
+ });
534
+
535
+ it.experimental('checkbox input', () => {
536
+ const root = ReactDOM.createRoot(container);
537
+ let input;
538
+
539
+ let ops = [];
540
+
541
+ class ControlledInput extends React.Component {
542
+ state = {checked: false};
543
+ onChange = event => {
544
+ this.setState({checked: event.target.checked});
545
+ };
546
+ render() {
547
+ ops.push(`render: ${this.state.checked}`);
548
+ const controlledValue = this.props.reverse
549
+ ? !this.state.checked
550
+ : this.state.checked;
551
+ return (
552
+ <input
553
+ ref={el => (input = el)}
554
+ type="checkbox"
555
+ checked={controlledValue}
556
+ onChange={this.onChange}
557
+ />
558
+ );
559
}
560
+ }
561
622
- // Initial mount. Test that this is async.
623
- root.render(<ControlledTextarea />);
624
- // Should not have flushed yet.
625
- expect(ops).toEqual([]);
626
- expect(textarea).toBe(undefined);
627
- // Flush callbacks.
628
- Scheduler.unstable_flushAll();
629
- expect(ops).toEqual(['render: initial']);
630
- expect(textarea.value).toBe('initial');
631
-
632
- ops = [];
633
-
634
- // Trigger a change event.
635
- setUntrackedTextareaValue.call(textarea, 'changed');
636
- textarea.dispatchEvent(
637
- new Event('input', {bubbles: true, cancelable: true}),
638
- );
639
- // Change should synchronously flush
640
- expect(ops).toEqual(['render: changed']);
641
- // Value should be the controlled value, not the original one
642
- expect(textarea.value).toBe('changed [!]');
643
- });
644
-
645
- it('parent of input', () => {
646
- const root = ReactDOM.createRoot(container);
647
- let input;
648
-
649
- let ops = [];
650
-
651
- class ControlledInput extends React.Component {
652
- state = {value: 'initial'};
653
- onChange = event => this.setState({value: event.target.value});
654
- render() {
655
- ops.push(`render: ${this.state.value}`);
656
- const controlledValue =
657
- this.state.value === 'changed' ? 'changed [!]' : this.state.value;
658
- return (
659
- <div onChange={this.onChange}>
660
- <input
661
- ref={el => (input = el)}
662
- type="text"
663
- value={controlledValue}
664
- onChange={() => {
665
- // Does nothing. Parent handler is responsible for updating.
666
- }}
667
- />
668
- </div>
669
- );
670
- }
562
+ // Initial mount. Test that this is async.
563
+ root.render(<ControlledInput reverse={false} />);
564
+ // Should not have flushed yet.
565
+ expect(ops).toEqual([]);
566
+ expect(input).toBe(undefined);
567
+ // Flush callbacks.
568
+ Scheduler.unstable_flushAll();
569
+ expect(ops).toEqual(['render: false']);
570
+ expect(input.checked).toBe(false);
571
+
572
+ ops = [];
573
+
574
+ // Trigger a change event.
575
+ input.dispatchEvent(
576
+ new MouseEvent('click', {bubbles: true, cancelable: true}),
577
+ );
578
+ // Change should synchronously flush
579
+ expect(ops).toEqual(['render: true']);
580
+ expect(input.checked).toBe(true);
581
+
582
+ // Now let's make sure we're using the controlled value.
583
+ root.render(<ControlledInput reverse={true} />);
584
+ Scheduler.unstable_flushAll();
585
+
586
+ ops = [];
587
+
588
+ // Trigger another change event.
589
+ input.dispatchEvent(
590
+ new MouseEvent('click', {bubbles: true, cancelable: true}),
591
+ );
592
+ // Change should synchronously flush
593
+ expect(ops).toEqual(['render: true']);
594
+ expect(input.checked).toBe(false);
595
+ });
596
+
597
+ it.experimental('textarea', () => {
598
+ const root = ReactDOM.createRoot(container);
599
+ let textarea;
600
+
601
+ let ops = [];
602
+
603
+ class ControlledTextarea extends React.Component {
604
+ state = {value: 'initial'};
605
+ onChange = event => this.setState({value: event.target.value});
606
+ render() {
607
+ ops.push(`render: ${this.state.value}`);
608
+ const controlledValue =
609
+ this.state.value === 'changed' ? 'changed [!]' : this.state.value;
610
+ return (
611
+ <textarea
612
+ ref={el => (textarea = el)}
613
+ type="text"
614
+ value={controlledValue}
615
+ onChange={this.onChange}
616
+ />
617
+ );
618
}
619
+ }
620
673
- // Initial mount. Test that this is async.
674
- root.render(<ControlledInput />);
675
- // Should not have flushed yet.
676
- expect(ops).toEqual([]);
677
- expect(input).toBe(undefined);
678
- // Flush callbacks.
679
- Scheduler.unstable_flushAll();
680
- expect(ops).toEqual(['render: initial']);
681
- expect(input.value).toBe('initial');
682
-
683
- ops = [];
684
-
685
- // Trigger a change event.
686
- setUntrackedValue.call(input, 'changed');
687
- input.dispatchEvent(
688
- new Event('input', {bubbles: true, cancelable: true}),
689
- );
690
- // Change should synchronously flush
691
- expect(ops).toEqual(['render: changed']);
692
- // Value should be the controlled value, not the original one
693
- expect(input.value).toBe('changed [!]');
694
- });
695
-
696
- it('is async for non-input events', () => {
697
- const root = ReactDOM.createRoot(container);
698
- let input;
699
-
700
- let ops = [];
701
-
702
- class ControlledInput extends React.Component {
703
- state = {value: 'initial'};
704
- onChange = event => this.setState({value: event.target.value});
705
- reset = () => {
706
- this.setState({value: ''});
707
- };
708
- render() {
709
- ops.push(`render: ${this.state.value}`);
710
- const controlledValue =
711
- this.state.value === 'changed' ? 'changed [!]' : this.state.value;
712
- return (
621
+ // Initial mount. Test that this is async.
622
+ root.render(<ControlledTextarea />);
623
+ // Should not have flushed yet.
624
+ expect(ops).toEqual([]);
625
+ expect(textarea).toBe(undefined);
626
+ // Flush callbacks.
627
+ Scheduler.unstable_flushAll();
628
+ expect(ops).toEqual(['render: initial']);
629
+ expect(textarea.value).toBe('initial');
630
+
631
+ ops = [];
632
+
633
+ // Trigger a change event.
634
+ setUntrackedTextareaValue.call(textarea, 'changed');
635
+ textarea.dispatchEvent(
636
+ new Event('input', {bubbles: true, cancelable: true}),
637
+ );
638
+ // Change should synchronously flush
639
+ expect(ops).toEqual(['render: changed']);
640
+ // Value should be the controlled value, not the original one
641
+ expect(textarea.value).toBe('changed [!]');
642
+ });
643
+
644
+ it.experimental('parent of input', () => {
645
+ const root = ReactDOM.createRoot(container);
646
+ let input;
647
+
648
+ let ops = [];
649
+
650
+ class ControlledInput extends React.Component {
651
+ state = {value: 'initial'};
652
+ onChange = event => this.setState({value: event.target.value});
653
+ render() {
654
+ ops.push(`render: ${this.state.value}`);
655
+ const controlledValue =
656
+ this.state.value === 'changed' ? 'changed [!]' : this.state.value;
657
+ return (
658
+ <div onChange={this.onChange}>
659
<input
660
ref={el => (input = el)}
661
type="text"
662
value={controlledValue}
717
- onChange={this.onChange}
718
- onClick={this.reset}
663
+ onChange={() => {
664
+ // Does nothing. Parent handler is responsible for updating.
665
+ }}
666
/>
720
- );
721
- }
667
+ </div>
668
+ );
669
+ }
670
+ }
671
+
672
+ // Initial mount. Test that this is async.
673
+ root.render(<ControlledInput />);
674
+ // Should not have flushed yet.
675
+ expect(ops).toEqual([]);
676
+ expect(input).toBe(undefined);
677
+ // Flush callbacks.
678
+ Scheduler.unstable_flushAll();
679
+ expect(ops).toEqual(['render: initial']);
680
+ expect(input.value).toBe('initial');
681
+
682
+ ops = [];
683
+
684
+ // Trigger a change event.
685
+ setUntrackedValue.call(input, 'changed');
686
+ input.dispatchEvent(
687
+ new Event('input', {bubbles: true, cancelable: true}),
688
+ );
689
+ // Change should synchronously flush
690
+ expect(ops).toEqual(['render: changed']);
691
+ // Value should be the controlled value, not the original one
692
+ expect(input.value).toBe('changed [!]');
693
+ });
694
+
695
+ it.experimental('is async for non-input events', () => {
696
+ const root = ReactDOM.createRoot(container);
697
+ let input;
698
+
699
+ let ops = [];
700
+
701
+ class ControlledInput extends React.Component {
702
+ state = {value: 'initial'};
703
+ onChange = event => this.setState({value: event.target.value});
704
+ reset = () => {
705
+ this.setState({value: ''});
706
+ };
707
+ render() {
708
+ ops.push(`render: ${this.state.value}`);
709
+ const controlledValue =
710
+ this.state.value === 'changed' ? 'changed [!]' : this.state.value;
711
+ return (
712
+ <input
713
+ ref={el => (input = el)}
714
+ type="text"
715
+ value={controlledValue}
716
+ onChange={this.onChange}
717
+ onClick={this.reset}
718
+ />
719
+ );
720
}
721
+ }
722
+
723
+ // Initial mount. Test that this is async.
724
+ root.render(<ControlledInput />);
725
+ // Should not have flushed yet.
726
+ expect(ops).toEqual([]);
727
+ expect(input).toBe(undefined);
728
+ // Flush callbacks.
729
+ Scheduler.unstable_flushAll();
730
+ expect(ops).toEqual(['render: initial']);
731
+ expect(input.value).toBe('initial');
732
+
733
+ ops = [];
734
+
735
+ // Trigger a click event
736
+ input.dispatchEvent(
737
+ new Event('click', {bubbles: true, cancelable: true}),
738
+ );
739
+ // Nothing should have changed
740
+ expect(ops).toEqual([]);
741
+ expect(input.value).toBe('initial');
742
+
743
+ // Flush callbacks.
744
+ Scheduler.unstable_flushAll();
745
+ // Now the click update has flushed.
746
+ expect(ops).toEqual(['render: ']);
747
+ expect(input.value).toBe('');
748
+ });
749
724
- // Initial mount. Test that this is async.
725
- root.render(<ControlledInput />);
726
- // Should not have flushed yet.
727
- expect(ops).toEqual([]);
728
- expect(input).toBe(undefined);
729
- // Flush callbacks.
730
- Scheduler.unstable_flushAll();
731
- expect(ops).toEqual(['render: initial']);
732
- expect(input.value).toBe('initial');
733
-
734
- ops = [];
735
-
736
- // Trigger a click event
737
- input.dispatchEvent(
738
- new Event('click', {bubbles: true, cancelable: true}),
739
- );
740
- // Nothing should have changed
741
- expect(ops).toEqual([]);
742
- expect(input.value).toBe('initial');
743
-
744
- // Flush callbacks.
745
- Scheduler.unstable_flushAll();
746
- // Now the click update has flushed.
747
- expect(ops).toEqual(['render: ']);
748
- expect(input.value).toBe('');
749
- });
750
-
751
- it('mouse enter/leave should be user-blocking but not discrete', async () => {
750
+ it.experimental(
751
+ 'mouse enter/leave should be user-blocking but not discrete',
752
+ async () => {
753
// This is currently behind a feature flag
754
jest.resetModules();
755
React = require('react');
@@ -788,7 +789,7 @@ describe('ChangeEventPlugin', () => {
789
Scheduler.unstable_advanceTime(3000);
790
expect(container.textContent).toEqual('hovered');
791
});
791
- });
792
- });
793
- }
792
+ },
793
+ );
794
+ });
795
});
packages/react-dom/src/events/__tests__/DOMEventResponderSystem-test.internal.js
+41
-43
@@ -845,56 +845,54 @@ describe('DOMEventResponderSystem', () => {
845
buttonRef.current.dispatchEvent(createEvent('foobar'));
846
});
847
848
- if (__EXPERIMENTAL__) {
849
- it('should work with concurrent mode updates', async () => {
850
- const log = [];
851
- const TestResponder = createEventResponder({
852
- targetEventTypes: ['click'],
853
- onEvent(event, context, props) {
854
- log.push(props);
855
- },
856
- });
857
- const ref = React.createRef();
858
-
859
- function Test({counter}) {
860
- const listener = React.unstable_useResponder(TestResponder, {counter});
861
- Scheduler.unstable_yieldValue('Test');
862
- return (
863
- <button listeners={listener} ref={ref}>
864
- Press me
865
- </button>
866
- );
867
- }
848
+ it.experimental('should work with concurrent mode updates', async () => {
849
+ const log = [];
850
+ const TestResponder = createEventResponder({
851
+ targetEventTypes: ['click'],
852
+ onEvent(event, context, props) {
853
+ log.push(props);
854
+ },
855
+ });
856
+ const ref = React.createRef();
857
869
- let root = ReactDOM.createRoot(container);
870
- root.render(<Test counter={0} />);
871
- expect(Scheduler).toFlushAndYield(['Test']);
858
+ function Test({counter}) {
859
+ const listener = React.unstable_useResponder(TestResponder, {counter});
860
+ Scheduler.unstable_yieldValue('Test');
861
+ return (
862
+ <button listeners={listener} ref={ref}>
863
+ Press me
864
+ </button>
865
+ );
866
+ }
867
873
- // Click the button
874
- dispatchClickEvent(ref.current);
875
- expect(log).toEqual([{counter: 0}]);
868
+ let root = ReactDOM.createRoot(container);
869
+ root.render(<Test counter={0} />);
870
+ expect(Scheduler).toFlushAndYield(['Test']);
871
+
872
+ // Click the button
873
+ dispatchClickEvent(ref.current);
874
+ expect(log).toEqual([{counter: 0}]);
875
877
- // Clear log
878
- log.length = 0;
876
+ // Clear log
877
+ log.length = 0;
878
880
- // Increase counter
881
- root.render(<Test counter={1} />);
882
- // Yield before committing
883
- expect(Scheduler).toFlushAndYieldThrough(['Test']);
879
+ // Increase counter
880
+ root.render(<Test counter={1} />);
881
+ // Yield before committing
882
+ expect(Scheduler).toFlushAndYieldThrough(['Test']);
883
885
- // Click the button again
886
- dispatchClickEvent(ref.current);
887
- expect(log).toEqual([{counter: 0}]);
884
+ // Click the button again
885
+ dispatchClickEvent(ref.current);
886
+ expect(log).toEqual([{counter: 0}]);
887
889
- // Clear log
890
- log.length = 0;
888
+ // Clear log
889
+ log.length = 0;
890
892
- // Commit
893
- expect(Scheduler).toFlushAndYield([]);
894
- dispatchClickEvent(ref.current);
895
- expect(log).toEqual([{counter: 1}]);
896
- });
897
- }
891
+ // Commit
892
+ expect(Scheduler).toFlushAndYield([]);
893
+ dispatchClickEvent(ref.current);
894
+ expect(log).toEqual([{counter: 1}]);
895
+ });
896
897
it('should correctly pass through event properties', () => {
898
const timeStamps = [];
packages/react-dom/src/events/__tests__/SimpleEventPlugin-test.internal.js
+106
-110
@@ -230,17 +230,18 @@ describe('SimpleEventPlugin', function() {
230
expect(button.textContent).toEqual('Count: 3');
231
});
232
233
- if (__EXPERIMENTAL__) {
234
- describe('interactive events, in concurrent mode', () => {
235
- beforeEach(() => {
236
- jest.resetModules();
237
- ReactFeatureFlags = require('shared/ReactFeatureFlags');
238
- ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
239
- ReactDOM = require('react-dom');
240
- Scheduler = require('scheduler');
241
- });
233
+ describe('interactive events, in concurrent mode', () => {
234
+ beforeEach(() => {
235
+ jest.resetModules();
236
+ ReactFeatureFlags = require('shared/ReactFeatureFlags');
237
+ ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
238
+ ReactDOM = require('react-dom');
239
+ Scheduler = require('scheduler');
240
+ });
241
243
- it('flushes pending interactive work before extracting event handler', () => {
242
+ it.experimental(
243
+ 'flushes pending interactive work before extracting event handler',
244
+ () => {
245
container = document.createElement('div');
246
const root = ReactDOM.createRoot(container);
247
document.body.appendChild(container);
@@ -318,9 +319,12 @@ describe('SimpleEventPlugin', function() {
319
click();
320
Scheduler.unstable_flushAll();
321
expect(ops).toEqual([]);
321
- });
322
+ },
323
+ );
324
323
- it('end result of many interactive updates is deterministic', () => {
325
+ it.experimental(
326
+ 'end result of many interactive updates is deterministic',
327
+ () => {
328
container = document.createElement('div');
329
const root = ReactDOM.createRoot(container);
330
document.body.appendChild(container);
@@ -373,113 +377,105 @@ describe('SimpleEventPlugin', function() {
377
Scheduler.unstable_flushAll();
378
// The counter should equal the total number of clicks
379
expect(button.textContent).toEqual('Count: 7');
376
- });
377
-
378
- it('flushes discrete updates in order', () => {
379
- container = document.createElement('div');
380
- document.body.appendChild(container);
381
-
382
- let button;
383
- class Button extends React.Component {
384
- state = {lowPriCount: 0};
385
- render() {
386
- const text = `High-pri count: ${
387
- this.props.highPriCount
388
- }, Low-pri count: ${this.state.lowPriCount}`;
389
- Scheduler.unstable_yieldValue(text);
390
- return (
391
- <button
392
- ref={el => (button = el)}
393
- onClick={() => {
394
- Scheduler.unstable_next(() => {
395
- this.setState(state => ({
396
- lowPriCount: state.lowPriCount + 1,
397
- }));
398
- });
399
- }}>
400
- {text}
401
- </button>
402
- );
403
- }
404
- }
380
+ },
381
+ );
382
406
- class Wrapper extends React.Component {
407
- state = {highPriCount: 0};
408
- render() {
409
- return (
410
- <div
411
- onClick={
412
- // Intentionally not using the updater form here, to test
413
- // that updates are serially processed.
414
- () => {
415
- this.setState({highPriCount: this.state.highPriCount + 1});
416
- }
417
- }>
418
- <Button highPriCount={this.state.highPriCount} />
419
- </div>
420
- );
421
- }
383
+ it.experimental('flushes discrete updates in order', () => {
384
+ container = document.createElement('div');
385
+ document.body.appendChild(container);
386
+
387
+ let button;
388
+ class Button extends React.Component {
389
+ state = {lowPriCount: 0};
390
+ render() {
391
+ const text = `High-pri count: ${
392
+ this.props.highPriCount
393
+ }, Low-pri count: ${this.state.lowPriCount}`;
394
+ Scheduler.unstable_yieldValue(text);
395
+ return (
396
+ <button
397
+ ref={el => (button = el)}
398
+ onClick={() => {
399
+ Scheduler.unstable_next(() => {
400
+ this.setState(state => ({
401
+ lowPriCount: state.lowPriCount + 1,
402
+ }));
403
+ });
404
+ }}>
405
+ {text}
406
+ </button>
407
+ );
408
}
409
+ }
410
424
- // Initial mount
425
- const root = ReactDOM.createRoot(container);
426
- root.render(<Wrapper />);
427
- expect(Scheduler).toFlushAndYield([
428
- 'High-pri count: 0, Low-pri count: 0',
429
- ]);
430
- expect(button.textContent).toEqual(
431
- 'High-pri count: 0, Low-pri count: 0',
432
- );
433
-
434
- function click() {
435
- button.dispatchEvent(
436
- new MouseEvent('click', {bubbles: true, cancelable: true}),
411
+ class Wrapper extends React.Component {
412
+ state = {highPriCount: 0};
413
+ render() {
414
+ return (
415
+ <div
416
+ onClick={
417
+ // Intentionally not using the updater form here, to test
418
+ // that updates are serially processed.
419
+ () => {
420
+ this.setState({highPriCount: this.state.highPriCount + 1});
421
+ }
422
+ }>
423
+ <Button highPriCount={this.state.highPriCount} />
424
+ </div>
425
);
426
}
427
+ }
428
440
- // Click the button a single time
441
- click();
442
- // Nothing should flush on the first click.
443
- expect(Scheduler).toHaveYielded([]);
444
- // Click again. This will force the previous discrete update to flush. But
445
- // only the high-pri count will increase.
446
- click();
447
- expect(Scheduler).toHaveYielded([
448
- 'High-pri count: 1, Low-pri count: 0',
449
- ]);
450
- expect(button.textContent).toEqual(
451
- 'High-pri count: 1, Low-pri count: 0',
429
+ // Initial mount
430
+ const root = ReactDOM.createRoot(container);
431
+ root.render(<Wrapper />);
432
+ expect(Scheduler).toFlushAndYield([
433
+ 'High-pri count: 0, Low-pri count: 0',
434
+ ]);
435
+ expect(button.textContent).toEqual('High-pri count: 0, Low-pri count: 0');
436
+
437
+ function click() {
438
+ button.dispatchEvent(
439
+ new MouseEvent('click', {bubbles: true, cancelable: true}),
440
);
441
+ }
442
454
- // Click the button many more times
455
- click();
456
- click();
457
- click();
458
- click();
459
- click();
460
- click();
461
-
462
- // Flush the remaining work.
463
- expect(Scheduler).toHaveYielded([
464
- 'High-pri count: 2, Low-pri count: 0',
465
- 'High-pri count: 3, Low-pri count: 0',
466
- 'High-pri count: 4, Low-pri count: 0',
467
- 'High-pri count: 5, Low-pri count: 0',
468
- 'High-pri count: 6, Low-pri count: 0',
469
- 'High-pri count: 7, Low-pri count: 0',
470
- ]);
471
-
472
- // At the end, both counters should equal the total number of clicks
473
- expect(Scheduler).toFlushAndYield([
474
- 'High-pri count: 8, Low-pri count: 0',
475
- 'High-pri count: 8, Low-pri count: 8',
476
- ]);
477
- expect(button.textContent).toEqual(
478
- 'High-pri count: 8, Low-pri count: 8',
479
- );
480
- });
443
+ // Click the button a single time
444
+ click();
445
+ // Nothing should flush on the first click.
446
+ expect(Scheduler).toHaveYielded([]);
447
+ // Click again. This will force the previous discrete update to flush. But
448
+ // only the high-pri count will increase.
449
+ click();
450
+ expect(Scheduler).toHaveYielded(['High-pri count: 1, Low-pri count: 0']);
451
+ expect(button.textContent).toEqual('High-pri count: 1, Low-pri count: 0');
452
+
453
+ // Click the button many more times
454
+ click();
455
+ click();
456
+ click();
457
+ click();
458
+ click();
459
+ click();
460
+
461
+ // Flush the remaining work.
462
+ expect(Scheduler).toHaveYielded([
463
+ 'High-pri count: 2, Low-pri count: 0',
464
+ 'High-pri count: 3, Low-pri count: 0',
465
+ 'High-pri count: 4, Low-pri count: 0',
466
+ 'High-pri count: 5, Low-pri count: 0',
467
+ 'High-pri count: 6, Low-pri count: 0',
468
+ 'High-pri count: 7, Low-pri count: 0',
469
+ ]);
470
+
471
+ // At the end, both counters should equal the total number of clicks
472
+ expect(Scheduler).toFlushAndYield([
473
+ 'High-pri count: 8, Low-pri count: 0',
474
+ 'High-pri count: 8, Low-pri count: 8',
475
+ ]);
476
+ expect(button.textContent).toEqual('High-pri count: 8, Low-pri count: 8');
477
});
482
- }
478
+ });
479
480
describe('iOS bubbling click fix', function() {
481
// See http://www.quirksmode.org/blog/archives/2010/09/click_event_del.html
packages/react-interactions/events/src/dom/__tests__/Input-test.internal.js
+169
-175
@@ -750,208 +750,202 @@ describe('Input event responder', () => {
750
}
751
});
752
753
- if (__EXPERIMENTAL__) {
754
- describe('concurrent mode', () => {
755
- it('text input', () => {
756
- const root = ReactDOM.createRoot(container);
757
- let input;
758
-
759
- let ops = [];
760
-
761
- function Component({innerRef, onChange, controlledValue}) {
762
- const listener = useInput({
763
- onChange,
764
- });
753
+ describe('concurrent mode', () => {
754
+ it.experimental('text input', () => {
755
+ const root = ReactDOM.createRoot(container);
756
+ let input;
757
+
758
+ let ops = [];
759
+
760
+ function Component({innerRef, onChange, controlledValue}) {
761
+ const listener = useInput({
762
+ onChange,
763
+ });
764
+ return (
765
+ <input
766
+ type="text"
767
+ ref={innerRef}
768
+ value={controlledValue}
769
+ listeners={listener}
770
+ />
771
+ );
772
+ }
773
+
774
+ class ControlledInput extends React.Component {
775
+ state = {value: 'initial'};
776
+ onChange = event => this.setState({value: event.target.value});
777
+ render() {
778
+ ops.push(`render: ${this.state.value}`);
779
+ const controlledValue =
780
+ this.state.value === 'changed' ? 'changed [!]' : this.state.value;
781
return (
766
- <input
767
- type="text"
768
- ref={innerRef}
769
- value={controlledValue}
770
- listeners={listener}
782
+ <Component
783
+ onChange={this.onChange}
784
+ innerRef={el => (input = el)}
785
+ controlledValue={controlledValue}
786
/>
787
);
788
}
789
+ }
790
775
- class ControlledInput extends React.Component {
776
- state = {value: 'initial'};
777
- onChange = event => this.setState({value: event.target.value});
778
- render() {
779
- ops.push(`render: ${this.state.value}`);
780
- const controlledValue =
781
- this.state.value === 'changed'
782
- ? 'changed [!]'
783
- : this.state.value;
784
- return (
785
- <Component
786
- onChange={this.onChange}
787
- innerRef={el => (input = el)}
788
- controlledValue={controlledValue}
789
- />
790
- );
791
- }
792
- }
791
+ // Initial mount. Test that this is async.
792
+ root.render(<ControlledInput />);
793
+ // Should not have flushed yet.
794
+ expect(ops).toEqual([]);
795
+ expect(input).toBe(undefined);
796
+ // Flush callbacks.
797
+ Scheduler.unstable_flushAll();
798
+ expect(ops).toEqual(['render: initial']);
799
+ expect(input.value).toBe('initial');
800
+
801
+ ops = [];
802
+
803
+ // Trigger a change event.
804
+ setUntrackedValue.call(input, 'changed');
805
+ input.dispatchEvent(
806
+ new Event('input', {bubbles: true, cancelable: true}),
807
+ );
808
+ // Change should synchronously flush
809
+ expect(ops).toEqual(['render: changed']);
810
+ // Value should be the controlled value, not the original one
811
+ expect(input.value).toBe('changed [!]');
812
+ });
813
794
- // Initial mount. Test that this is async.
795
- root.render(<ControlledInput />);
796
- // Should not have flushed yet.
797
- expect(ops).toEqual([]);
798
- expect(input).toBe(undefined);
799
- // Flush callbacks.
800
- Scheduler.unstable_flushAll();
801
- expect(ops).toEqual(['render: initial']);
802
- expect(input.value).toBe('initial');
803
-
804
- ops = [];
805
-
806
- // Trigger a change event.
807
- setUntrackedValue.call(input, 'changed');
808
- input.dispatchEvent(
809
- new Event('input', {bubbles: true, cancelable: true}),
810
- );
811
- // Change should synchronously flush
812
- expect(ops).toEqual(['render: changed']);
813
- // Value should be the controlled value, not the original one
814
- expect(input.value).toBe('changed [!]');
815
- });
814
+ it.experimental('checkbox input', () => {
815
+ const root = ReactDOM.createRoot(container);
816
+ let input;
817
817
- it('checkbox input', () => {
818
- const root = ReactDOM.createRoot(container);
819
- let input;
818
+ let ops = [];
819
821
- let ops = [];
820
+ function Component({innerRef, onChange, controlledValue}) {
821
+ const listener = useInput({
822
+ onChange,
823
+ });
824
+ return (
825
+ <input
826
+ type="checkbox"
827
+ ref={innerRef}
828
+ checked={controlledValue}
829
+ listeners={listener}
830
+ />
831
+ );
832
+ }
833
823
- function Component({innerRef, onChange, controlledValue}) {
824
- const listener = useInput({
825
- onChange,
826
- });
834
+ class ControlledInput extends React.Component {
835
+ state = {checked: false};
836
+ onChange = event => {
837
+ this.setState({checked: event.target.checked});
838
+ };
839
+ render() {
840
+ ops.push(`render: ${this.state.checked}`);
841
+ const controlledValue = this.props.reverse
842
+ ? !this.state.checked
843
+ : this.state.checked;
844
return (
828
- <input
829
- type="checkbox"
830
- ref={innerRef}
831
- checked={controlledValue}
832
- listeners={listener}
845
+ <Component
846
+ controlledValue={controlledValue}
847
+ onChange={this.onChange}
848
+ innerRef={el => (input = el)}
849
/>
850
);
851
}
852
+ }
853
837
- class ControlledInput extends React.Component {
838
- state = {checked: false};
839
- onChange = event => {
840
- this.setState({checked: event.target.checked});
841
- };
842
- render() {
843
- ops.push(`render: ${this.state.checked}`);
844
- const controlledValue = this.props.reverse
845
- ? !this.state.checked
846
- : this.state.checked;
847
- return (
848
- <Component
849
- controlledValue={controlledValue}
850
- onChange={this.onChange}
851
- innerRef={el => (input = el)}
852
- />
853
- );
854
- }
855
- }
854
+ // Initial mount. Test that this is async.
855
+ root.render(<ControlledInput reverse={false} />);
856
+ // Should not have flushed yet.
857
+ expect(ops).toEqual([]);
858
+ expect(input).toBe(undefined);
859
+ // Flush callbacks.
860
+ Scheduler.unstable_flushAll();
861
+ expect(ops).toEqual(['render: false']);
862
+ expect(input.checked).toBe(false);
863
+
864
+ ops = [];
865
+
866
+ // Trigger a change event.
867
+ input.dispatchEvent(
868
+ new MouseEvent('click', {bubbles: true, cancelable: true}),
869
+ );
870
+ // Change should synchronously flush
871
+ expect(ops).toEqual(['render: true']);
872
+ expect(input.checked).toBe(true);
873
857
- // Initial mount. Test that this is async.
858
- root.render(<ControlledInput reverse={false} />);
859
- // Should not have flushed yet.
860
- expect(ops).toEqual([]);
861
- expect(input).toBe(undefined);
862
- // Flush callbacks.
863
- Scheduler.unstable_flushAll();
864
- expect(ops).toEqual(['render: false']);
865
- expect(input.checked).toBe(false);
866
-
867
- ops = [];
868
-
869
- // Trigger a change event.
870
- input.dispatchEvent(
871
- new MouseEvent('click', {bubbles: true, cancelable: true}),
872
- );
873
- // Change should synchronously flush
874
- expect(ops).toEqual(['render: true']);
875
- expect(input.checked).toBe(true);
874
+ // Now let's make sure we're using the controlled value.
875
+ root.render(<ControlledInput reverse={true} />);
876
+ Scheduler.unstable_flushAll();
877
877
- // Now let's make sure we're using the controlled value.
878
- root.render(<ControlledInput reverse={true} />);
879
- Scheduler.unstable_flushAll();
878
+ ops = [];
879
881
- ops = [];
880
+ // Trigger another change event.
881
+ input.dispatchEvent(
882
+ new MouseEvent('click', {bubbles: true, cancelable: true}),
883
+ );
884
+ // Change should synchronously flush
885
+ expect(ops).toEqual(['render: true']);
886
+ expect(input.checked).toBe(false);
887
+ });
888
883
- // Trigger another change event.
884
- input.dispatchEvent(
885
- new MouseEvent('click', {bubbles: true, cancelable: true}),
886
- );
887
- // Change should synchronously flush
888
- expect(ops).toEqual(['render: true']);
889
- expect(input.checked).toBe(false);
890
- });
889
+ it.experimental('textarea', () => {
890
+ const root = ReactDOM.createRoot(container);
891
+ let textarea;
892
892
- it('textarea', () => {
893
- const root = ReactDOM.createRoot(container);
894
- let textarea;
893
+ let ops = [];
894
896
- let ops = [];
895
+ function Component({innerRef, onChange, controlledValue}) {
896
+ const listener = useInput({
897
+ onChange,
898
+ });
899
+ return (
900
+ <textarea
901
+ type="text"
902
+ ref={innerRef}
903
+ value={controlledValue}
904
+ listeners={listener}
905
+ />
906
+ );
907
+ }
908
898
- function Component({innerRef, onChange, controlledValue}) {
899
- const listener = useInput({
900
- onChange,
901
- });
909
+ class ControlledTextarea extends React.Component {
910
+ state = {value: 'initial'};
911
+ onChange = event => this.setState({value: event.target.value});
912
+ render() {
913
+ ops.push(`render: ${this.state.value}`);
914
+ const controlledValue =
915
+ this.state.value === 'changed' ? 'changed [!]' : this.state.value;
916
return (
903
- <textarea
904
- type="text"
905
- ref={innerRef}
906
- value={controlledValue}
907
- listeners={listener}
917
+ <Component
918
+ onChange={this.onChange}
919
+ innerRef={el => (textarea = el)}
920
+ controlledValue={controlledValue}
921
/>
922
);
923
}
924
+ }
925
912
- class ControlledTextarea extends React.Component {
913
- state = {value: 'initial'};
914
- onChange = event => this.setState({value: event.target.value});
915
- render() {
916
- ops.push(`render: ${this.state.value}`);
917
- const controlledValue =
918
- this.state.value === 'changed'
919
- ? 'changed [!]'
920
- : this.state.value;
921
- return (
922
- <Component
923
- onChange={this.onChange}
924
- innerRef={el => (textarea = el)}
925
- controlledValue={controlledValue}
926
- />
927
- );
928
- }
929
- }
930
-
931
- // Initial mount. Test that this is async.
932
- root.render(<ControlledTextarea />);
933
- // Should not have flushed yet.
934
- expect(ops).toEqual([]);
935
- expect(textarea).toBe(undefined);
936
- // Flush callbacks.
937
- Scheduler.unstable_flushAll();
938
- expect(ops).toEqual(['render: initial']);
939
- expect(textarea.value).toBe('initial');
940
-
941
- ops = [];
942
-
943
- // Trigger a change event.
944
- setUntrackedTextareaValue.call(textarea, 'changed');
945
- textarea.dispatchEvent(
946
- new Event('input', {bubbles: true, cancelable: true}),
947
- );
948
- // Change should synchronously flush
949
- expect(ops).toEqual(['render: changed']);
950
- // Value should be the controlled value, not the original one
951
- expect(textarea.value).toBe('changed [!]');
952
- });
926
+ // Initial mount. Test that this is async.
927
+ root.render(<ControlledTextarea />);
928
+ // Should not have flushed yet.
929
+ expect(ops).toEqual([]);
930
+ expect(textarea).toBe(undefined);
931
+ // Flush callbacks.
932
+ Scheduler.unstable_flushAll();
933
+ expect(ops).toEqual(['render: initial']);
934
+ expect(textarea.value).toBe('initial');
935
+
936
+ ops = [];
937
+
938
+ // Trigger a change event.
939
+ setUntrackedTextareaValue.call(textarea, 'changed');
940
+ textarea.dispatchEvent(
941
+ new Event('input', {bubbles: true, cancelable: true}),
942
+ );
943
+ // Change should synchronously flush
944
+ expect(ops).toEqual(['render: changed']);
945
+ // Value should be the controlled value, not the original one
946
+ expect(textarea.value).toBe('changed [!]');
947
});
954
- }
948
+ });
949
});
950
951
it('expect displayName to show up for event component', () => {
packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.internal.js
+74
-69
@@ -1973,9 +1973,10 @@ describe('ReactHooksWithNoopRenderer', () => {
1973
expect(totalRefUpdates).toBe(2); // Should not increase since last time
1974
});
1975
});
1976
- if (__EXPERIMENTAL__) {
1977
- describe('useTransition', () => {
1978
- it('delays showing loading state until after timeout', async () => {
1976
+ describe('useTransition', () => {
1977
+ it.experimental(
1978
+ 'delays showing loading state until after timeout',
1979
+ async () => {
1980
let transition;
1981
function App() {
1982
const [show, setShow] = useState(false);
@@ -2037,8 +2038,11 @@ describe('ReactHooksWithNoopRenderer', () => {
2038
expect(ReactNoop.getChildren()).toEqual([
2039
span('After... Pending: false'),
2040
]);
2040
- });
2041
- it('delays showing loading state until after busyDelayMs + busyMinDurationMs', async () => {
2041
+ },
2042
+ );
2043
+ it.experimental(
2044
+ 'delays showing loading state until after busyDelayMs + busyMinDurationMs',
2045
+ async () => {
2046
let transition;
2047
function App() {
2048
const [show, setShow] = useState(false);
@@ -2105,81 +2109,82 @@ describe('ReactHooksWithNoopRenderer', () => {
2109
expect(ReactNoop.getChildren()).toEqual([
2110
span('After... Pending: false'),
2111
]);
2108
- });
2109
- });
2110
- describe('useDeferredValue', () => {
2111
- it('defers text value until specified timeout', async () => {
2112
- function TextBox({text}) {
2113
- return <AsyncText ms={1000} text={text} />;
2114
- }
2115
-
2116
- let _setText;
2117
- function App() {
2118
- const [text, setText] = useState('A');
2119
- const deferredText = useDeferredValue(text, {
2120
- timeoutMs: 500,
2121
- });
2122
- _setText = setText;
2123
- return (
2124
- <>
2125
- <Text text={text} />
2126
- <Suspense fallback={<Text text={'Loading'} />}>
2127
- <TextBox text={deferredText} />
2128
- </Suspense>
2129
- </>
2130
- );
2131
- }
2112
+ },
2113
+ );
2114
+ });
2115
+ describe('useDeferredValue', () => {
2116
+ it.experimental('defers text value until specified timeout', async () => {
2117
+ function TextBox({text}) {
2118
+ return <AsyncText ms={1000} text={text} />;
2119
+ }
2120
2133
- act(() => {
2134
- ReactNoop.render(<App />);
2121
+ let _setText;
2122
+ function App() {
2123
+ const [text, setText] = useState('A');
2124
+ const deferredText = useDeferredValue(text, {
2125
+ timeoutMs: 500,
2126
});
2127
+ _setText = setText;
2128
+ return (
2129
+ <>
2130
+ <Text text={text} />
2131
+ <Suspense fallback={<Text text={'Loading'} />}>
2132
+ <TextBox text={deferredText} />
2133
+ </Suspense>
2134
+ </>
2135
+ );
2136
+ }
2137
2137
- expect(Scheduler).toHaveYielded(['A', 'Suspend! [A]', 'Loading']);
2138
- expect(ReactNoop.getChildren()).toEqual([span('A'), span('Loading')]);
2138
+ act(() => {
2139
+ ReactNoop.render(<App />);
2140
+ });
2141
2140
- Scheduler.unstable_advanceTime(1000);
2141
- await advanceTimers(1000);
2142
- expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
2143
- expect(Scheduler).toFlushAndYield(['A']);
2144
- expect(ReactNoop.getChildren()).toEqual([span('A'), span('A')]);
2142
+ expect(Scheduler).toHaveYielded(['A', 'Suspend! [A]', 'Loading']);
2143
+ expect(ReactNoop.getChildren()).toEqual([span('A'), span('Loading')]);
2144
2146
- act(() => {
2147
- _setText('B');
2148
- });
2149
- expect(Scheduler).toHaveYielded([
2150
- 'B',
2151
- 'A',
2152
- 'B',
2153
- 'Suspend! [B]',
2154
- 'Loading',
2155
- ]);
2156
- expect(Scheduler).toFlushAndYield([]);
2157
- expect(ReactNoop.getChildren()).toEqual([span('B'), span('A')]);
2145
+ Scheduler.unstable_advanceTime(1000);
2146
+ await advanceTimers(1000);
2147
+ expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
2148
+ expect(Scheduler).toFlushAndYield(['A']);
2149
+ expect(ReactNoop.getChildren()).toEqual([span('A'), span('A')]);
2150
2159
- Scheduler.unstable_advanceTime(250);
2160
- await advanceTimers(250);
2161
- expect(Scheduler).toFlushAndYield([]);
2162
- expect(ReactNoop.getChildren()).toEqual([span('B'), span('A')]);
2151
+ act(() => {
2152
+ _setText('B');
2153
+ });
2154
+ expect(Scheduler).toHaveYielded([
2155
+ 'B',
2156
+ 'A',
2157
+ 'B',
2158
+ 'Suspend! [B]',
2159
+ 'Loading',
2160
+ ]);
2161
+ expect(Scheduler).toFlushAndYield([]);
2162
+ expect(ReactNoop.getChildren()).toEqual([span('B'), span('A')]);
2163
2164
- Scheduler.unstable_advanceTime(500);
2165
- await advanceTimers(500);
2166
- expect(ReactNoop.getChildren()).toEqual([
2167
- span('B'),
2168
- hiddenSpan('A'),
2169
- span('Loading'),
2170
- ]);
2164
+ Scheduler.unstable_advanceTime(250);
2165
+ await advanceTimers(250);
2166
+ expect(Scheduler).toFlushAndYield([]);
2167
+ expect(ReactNoop.getChildren()).toEqual([span('B'), span('A')]);
2168
2172
- Scheduler.unstable_advanceTime(250);
2173
- await advanceTimers(250);
2174
- expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
2169
+ Scheduler.unstable_advanceTime(500);
2170
+ await advanceTimers(500);
2171
+ expect(ReactNoop.getChildren()).toEqual([
2172
+ span('B'),
2173
+ hiddenSpan('A'),
2174
+ span('Loading'),
2175
+ ]);
2176
2176
- act(() => {
2177
- expect(Scheduler).toFlushAndYield(['B']);
2178
- });
2179
- expect(ReactNoop.getChildren()).toEqual([span('B'), span('B')]);
2177
+ Scheduler.unstable_advanceTime(250);
2178
+ await advanceTimers(250);
2179
+ expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
2180
+
2181
+ act(() => {
2182
+ expect(Scheduler).toFlushAndYield(['B']);
2183
});
2184
+ expect(ReactNoop.getChildren()).toEqual([span('B'), span('B')]);
2185
});
2182
- }
2186
+ });
2187
+
2188
describe('progressive enhancement (not supported)', () => {
2189
it('mount additional state', () => {
2190
let updateA;