Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit a8a4742

Browse files
authoredJun 2, 2021
Convert ES6/TypeScript/CoffeeScript Tests to createRoot + act (#21598)
* Convert ES6/TypeScript CoffeeScript Tests to createRoot + act * Change expectation for WWW+VARIANT because the deferRenderPhaseUpdateToNextBatch flag breaks this behavior
1 parent 1d35589 commit a8a4742

File tree

7 files changed

+152
-113
lines changed

7 files changed

+152
-113
lines changed
 

‎packages/react/src/__tests__/ReactCoffeeScriptClass-test.coffee

Lines changed: 43 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,25 @@ This source code is licensed under the MIT license found in the
55
LICENSE file in the root directory of this source tree.
66
###
77

8+
PropTypes = null
89
React = null
910
ReactDOM = null
10-
PropTypes = null
11+
act = null
1112

1213
describe 'ReactCoffeeScriptClass', ->
1314
container = null
15+
root = null
1416
InnerComponent = null
1517
attachedListener = null;
1618
renderedName = null;
1719

1820
beforeEach ->
1921
React = require 'react'
2022
ReactDOM = require 'react-dom'
23+
act = require('react-dom/test-utils').act
2124
PropTypes = require 'prop-types'
2225
container = document.createElement 'div'
26+
root = ReactDOM.createRoot container
2327
attachedListener = null
2428
renderedName = null
2529
InnerComponent = class extends React.Component
@@ -30,11 +34,11 @@ describe 'ReactCoffeeScriptClass', ->
3034
return React.createElement('div', className: this.props.name)
3135

3236
test = (element, expectedTag, expectedClassName) ->
33-
instance = ReactDOM.render(element, container)
37+
act ->
38+
root.render(element)
3439
expect(container.firstChild).not.toBeNull()
3540
expect(container.firstChild.tagName).toBe(expectedTag)
3641
expect(container.firstChild.className).toBe(expectedClassName)
37-
instance;
3842

3943
it 'preserves the name of the class for use in error messages', ->
4044
class Foo extends React.Component
@@ -44,14 +48,16 @@ describe 'ReactCoffeeScriptClass', ->
4448
class Foo extends React.Component
4549
expect(->
4650
expect(->
47-
ReactDOM.render React.createElement(Foo), container
51+
act ->
52+
root.render React.createElement(Foo)
4853
).toThrow()
4954
).toErrorDev([
50-
# A failed component renders twice in DEV
55+
# A failed component renders four times in DEV in concurrent mode
56+
'No `render` method found on the returned component instance',
57+
'No `render` method found on the returned component instance',
5158
'No `render` method found on the returned component instance',
5259
'No `render` method found on the returned component instance',
5360
])
54-
undefined
5561

5662
it 'renders a simple stateless component with prop', ->
5763
class Foo extends React.Component
@@ -62,7 +68,6 @@ describe 'ReactCoffeeScriptClass', ->
6268

6369
test React.createElement(Foo, bar: 'foo'), 'DIV', 'foo'
6470
test React.createElement(Foo, bar: 'bar'), 'DIV', 'bar'
65-
undefined
6671

6772
it 'renders based on state using initial values in this.props', ->
6873
class Foo extends React.Component
@@ -76,7 +81,6 @@ describe 'ReactCoffeeScriptClass', ->
7681
)
7782

7883
test React.createElement(Foo, initialValue: 'foo'), 'SPAN', 'foo'
79-
undefined
8084

8185
it 'renders based on state using props in the constructor', ->
8286
class Foo extends React.Component
@@ -95,10 +99,10 @@ describe 'ReactCoffeeScriptClass', ->
9599
className: @state.bar
96100
)
97101

98-
instance = test React.createElement(Foo, initialValue: 'foo'), 'DIV', 'foo'
99-
instance.changeState()
102+
ref = React.createRef()
103+
test React.createElement(Foo, initialValue: 'foo', ref: ref), 'DIV', 'foo'
104+
ref.current.changeState()
100105
test React.createElement(Foo), 'SPAN', 'bar'
101-
undefined
102106

103107
it 'sets initial state with value returned by static getDerivedStateFromProps', ->
104108
class Foo extends React.Component
@@ -115,7 +119,6 @@ describe 'ReactCoffeeScriptClass', ->
115119
bar: 'bar'
116120
}
117121
test React.createElement(Foo, foo: 'foo'), 'DIV', 'foo bar'
118-
undefined
119122

120123
it 'warns if getDerivedStateFromProps is not static', ->
121124
class Foo extends React.Component
@@ -124,9 +127,9 @@ describe 'ReactCoffeeScriptClass', ->
124127
getDerivedStateFromProps: ->
125128
{}
126129
expect(->
127-
ReactDOM.render(React.createElement(Foo, foo: 'foo'), container)
130+
act ->
131+
root.render React.createElement(Foo, foo: 'foo')
128132
).toErrorDev 'Foo: getDerivedStateFromProps() is defined as an instance method and will be ignored. Instead, declare it as a static method.'
129-
undefined
130133

131134
it 'warns if getDerivedStateFromError is not static', ->
132135
class Foo extends React.Component
@@ -135,9 +138,9 @@ describe 'ReactCoffeeScriptClass', ->
135138
getDerivedStateFromError: ->
136139
{}
137140
expect(->
138-
ReactDOM.render(React.createElement(Foo, foo: 'foo'), container)
141+
act ->
142+
root.render React.createElement(Foo, foo: 'foo')
139143
).toErrorDev 'Foo: getDerivedStateFromError() is defined as an instance method and will be ignored. Instead, declare it as a static method.'
140-
undefined
141144

142145
it 'warns if getSnapshotBeforeUpdate is static', ->
143146
class Foo extends React.Component
@@ -146,9 +149,9 @@ describe 'ReactCoffeeScriptClass', ->
146149
Foo.getSnapshotBeforeUpdate = () ->
147150
{}
148151
expect(->
149-
ReactDOM.render(React.createElement(Foo, foo: 'foo'), container)
152+
act ->
153+
root.render React.createElement(Foo, foo: 'foo')
150154
).toErrorDev 'Foo: getSnapshotBeforeUpdate() is defined as a static method and will be ignored. Instead, declare it as an instance method.'
151-
undefined
152155

153156
it 'warns if state not initialized before static getDerivedStateFromProps', ->
154157
class Foo extends React.Component
@@ -162,14 +165,14 @@ describe 'ReactCoffeeScriptClass', ->
162165
bar: 'bar'
163166
}
164167
expect(->
165-
ReactDOM.render(React.createElement(Foo, foo: 'foo'), container)
168+
act ->
169+
root.render React.createElement(Foo, foo: 'foo')
166170
).toErrorDev (
167171
'`Foo` uses `getDerivedStateFromProps` but its initial state is ' +
168172
'undefined. This is not recommended. Instead, define the initial state by ' +
169173
'assigning an object to `this.state` in the constructor of `Foo`. ' +
170174
'This ensures that `getDerivedStateFromProps` arguments have a consistent shape.'
171175
)
172-
undefined
173176

174177
it 'updates initial state with values returned by static getDerivedStateFromProps', ->
175178
class Foo extends React.Component
@@ -187,7 +190,6 @@ describe 'ReactCoffeeScriptClass', ->
187190
foo: "not-#{prevState.foo}"
188191
}
189192
test React.createElement(Foo), 'DIV', 'not-foo bar'
190-
undefined
191193

192194
it 'renders updated state with values returned by static getDerivedStateFromProps', ->
193195
class Foo extends React.Component
@@ -207,7 +209,6 @@ describe 'ReactCoffeeScriptClass', ->
207209
return null
208210
test React.createElement(Foo, update: false), 'DIV', 'initial'
209211
test React.createElement(Foo, update: true), 'DIV', 'updated'
210-
undefined
211212

212213
it 'renders based on context in the constructor', ->
213214
class Foo extends React.Component
@@ -239,7 +240,6 @@ describe 'ReactCoffeeScriptClass', ->
239240
React.createElement Foo
240241

241242
test React.createElement(Outer), 'SPAN', 'foo'
242-
undefined
243243

244244
it 'renders only once when setting state in componentWillMount', ->
245245
renderCount = 0
@@ -255,8 +255,9 @@ describe 'ReactCoffeeScriptClass', ->
255255
React.createElement('span', className: @state.bar)
256256

257257
test React.createElement(Foo, initialValue: 'foo'), 'SPAN', 'bar'
258-
expect(renderCount).toBe 1
259-
undefined
258+
# This is broken with deferRenderPhaseUpdateToNextBatch flag on.
259+
# We can't use the gate feature here because this test is also in CoffeeScript and TypeScript.
260+
expect(renderCount).toBe(if global.__WWW__ and !global.__VARIANT__ then 2 else 1)
260261

261262
it 'should warn with non-object in the initial state property', ->
262263
[['an array'], 'a string', 1234].forEach (state) ->
@@ -270,7 +271,6 @@ describe 'ReactCoffeeScriptClass', ->
270271
expect(->
271272
test React.createElement(Foo), 'SPAN', ''
272273
).toErrorDev('Foo.state: must be set to an object or null')
273-
undefined
274274

275275
it 'should render with null in the initial state property', ->
276276
class Foo extends React.Component
@@ -281,7 +281,6 @@ describe 'ReactCoffeeScriptClass', ->
281281
React.createElement('span')
282282

283283
test React.createElement(Foo), 'SPAN', ''
284-
undefined
285284

286285
it 'setState through an event handler', ->
287286
class Foo extends React.Component
@@ -298,9 +297,9 @@ describe 'ReactCoffeeScriptClass', ->
298297
)
299298

300299
test React.createElement(Foo, initialValue: 'foo'), 'DIV', 'foo'
301-
attachedListener()
300+
act ->
301+
attachedListener()
302302
expect(renderedName).toBe 'bar'
303-
undefined
304303

305304
it 'should not implicitly bind event handlers', ->
306305
class Foo extends React.Component
@@ -318,7 +317,6 @@ describe 'ReactCoffeeScriptClass', ->
318317

319318
test React.createElement(Foo, initialValue: 'foo'), 'DIV', 'foo'
320319
expect(attachedListener).toThrow()
321-
undefined
322320

323321
it 'renders using forceUpdate even when there is no state', ->
324322
class Foo extends React.Component
@@ -336,9 +334,9 @@ describe 'ReactCoffeeScriptClass', ->
336334
)
337335

338336
test React.createElement(Foo, initialValue: 'foo'), 'DIV', 'foo'
339-
attachedListener()
337+
act ->
338+
attachedListener()
340339
expect(renderedName).toBe 'bar'
341-
undefined
342340

343341
it 'will call all the normal life cycle methods', ->
344342
lifeCycles = []
@@ -387,9 +385,9 @@ describe 'ReactCoffeeScriptClass', ->
387385
'did-update', { value: 'foo' }, {}
388386
]
389387
lifeCycles = [] # reset
390-
ReactDOM.unmountComponentAtNode container
388+
act ->
389+
root.unmount()
391390
expect(lifeCycles).toEqual ['will-unmount']
392-
undefined
393391

394392
it 'warns when classic properties are defined on the instance,
395393
but does not invoke them.', ->
@@ -425,7 +423,6 @@ describe 'ReactCoffeeScriptClass', ->
425423
])
426424
expect(getInitialStateWasCalled).toBe false
427425
expect(getDefaultPropsWasCalled).toBe false
428-
undefined
429426

430427
it 'does not warn about getInitialState() on class components
431428
if state is also defined.', ->
@@ -443,7 +440,6 @@ describe 'ReactCoffeeScriptClass', ->
443440
)
444441

445442
test React.createElement(Foo), 'SPAN', 'foo'
446-
undefined
447443

448444
it 'should warn when misspelling shouldComponentUpdate', ->
449445
class NamedComponent extends React.Component
@@ -462,7 +458,6 @@ describe 'ReactCoffeeScriptClass', ->
462458
Did you mean shouldComponentUpdate()? The name is phrased as a
463459
question because the function is expected to return a value.'
464460
)
465-
undefined
466461

467462
it 'should warn when misspelling componentWillReceiveProps', ->
468463
class NamedComponent extends React.Component
@@ -480,7 +475,6 @@ describe 'ReactCoffeeScriptClass', ->
480475
'Warning: NamedComponent has a method called componentWillRecieveProps().
481476
Did you mean componentWillReceiveProps()?'
482477
)
483-
undefined
484478

485479
it 'should warn when misspelling UNSAFE_componentWillReceiveProps', ->
486480
class NamedComponent extends React.Component
@@ -498,24 +492,22 @@ describe 'ReactCoffeeScriptClass', ->
498492
'Warning: NamedComponent has a method called UNSAFE_componentWillRecieveProps().
499493
Did you mean UNSAFE_componentWillReceiveProps()?'
500494
)
501-
undefined
502495

503496
it 'should throw AND warn when trying to access classic APIs', ->
504-
instance =
505-
test React.createElement(InnerComponent, name: 'foo'), 'DIV', 'foo'
497+
ref = React.createRef()
498+
test React.createElement(InnerComponent, name: 'foo', ref: ref), 'DIV', 'foo'
506499
expect(->
507-
expect(-> instance.replaceState {}).toThrow()
500+
expect(-> ref.current.replaceState {}).toThrow()
508501
).toWarnDev(
509502
'replaceState(...) is deprecated in plain JavaScript React classes',
510503
{withoutStack: true}
511504
)
512505
expect(->
513-
expect(-> instance.isMounted()).toThrow()
506+
expect(-> ref.current.isMounted()).toThrow()
514507
).toWarnDev(
515508
'isMounted(...) is deprecated in plain JavaScript React classes',
516509
{withoutStack: true}
517510
)
518-
undefined
519511

520512
it 'supports this.context passed via getChildContext', ->
521513
class Bar extends React.Component
@@ -533,7 +525,6 @@ describe 'ReactCoffeeScriptClass', ->
533525
React.createElement Bar
534526

535527
test React.createElement(Foo), 'DIV', 'bar-through-context'
536-
undefined
537528

538529
it 'supports classic refs', ->
539530
class Foo extends React.Component
@@ -543,13 +534,14 @@ describe 'ReactCoffeeScriptClass', ->
543534
ref: 'inner'
544535
)
545536

546-
instance = test(React.createElement(Foo), 'DIV', 'foo')
547-
expect(instance.refs.inner.getName()).toBe 'foo'
548-
undefined
537+
ref = React.createRef()
538+
test(React.createElement(Foo, ref: ref), 'DIV', 'foo')
539+
expect(ref.current.refs.inner.getName()).toBe 'foo'
549540

550541
it 'supports drilling through to the DOM using findDOMNode', ->
551-
instance = test React.createElement(InnerComponent, name: 'foo'), 'DIV', 'foo'
552-
node = ReactDOM.findDOMNode(instance)
542+
ref = React.createRef()
543+
test React.createElement(InnerComponent, name: 'foo', ref: ref), 'DIV', 'foo'
544+
node = ReactDOM.findDOMNode(ref.current)
553545
expect(node).toBe container.firstChild
554-
undefined
546+
555547
undefined

‎packages/react/src/__tests__/ReactES6Class-test.js

Lines changed: 37 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@
1212
let PropTypes;
1313
let React;
1414
let ReactDOM;
15+
let act;
1516

1617
describe('ReactES6Class', () => {
1718
let container;
19+
let root;
1820
const freeze = function(expectation) {
1921
Object.freeze(expectation);
2022
return expectation;
@@ -27,7 +29,9 @@ describe('ReactES6Class', () => {
2729
PropTypes = require('prop-types');
2830
React = require('react');
2931
ReactDOM = require('react-dom');
32+
act = require('react-dom/test-utils').act;
3033
container = document.createElement('div');
34+
root = ReactDOM.createRoot(container);
3135
attachedListener = null;
3236
renderedName = null;
3337
Inner = class extends React.Component {
@@ -43,11 +47,10 @@ describe('ReactES6Class', () => {
4347
});
4448

4549
function test(element, expectedTag, expectedClassName) {
46-
const instance = ReactDOM.render(element, container);
50+
act(() => root.render(element));
4751
expect(container.firstChild).not.toBeNull();
4852
expect(container.firstChild.tagName).toBe(expectedTag);
4953
expect(container.firstChild.className).toBe(expectedClassName);
50-
return instance;
5154
}
5255

5356
it('preserves the name of the class for use in error messages', () => {
@@ -57,10 +60,14 @@ describe('ReactES6Class', () => {
5760

5861
it('throws if no render function is defined', () => {
5962
class Foo extends React.Component {}
60-
expect(() =>
61-
expect(() => ReactDOM.render(<Foo />, container)).toThrow(),
62-
).toErrorDev([
63-
// A failed component renders twice in DEV
63+
expect(() => {
64+
expect(() => act(() => root.render(<Foo />))).toThrow();
65+
}).toErrorDev([
66+
// A failed component renders four times in DEV in concurrent mode
67+
'Warning: Foo(...): No `render` method found on the returned component ' +
68+
'instance: you may have forgotten to define `render`.',
69+
'Warning: Foo(...): No `render` method found on the returned component ' +
70+
'instance: you may have forgotten to define `render`.',
6471
'Warning: Foo(...): No `render` method found on the returned component ' +
6572
'instance: you may have forgotten to define `render`.',
6673
'Warning: Foo(...): No `render` method found on the returned component ' +
@@ -107,8 +114,9 @@ describe('ReactES6Class', () => {
107114
return <span className={this.state.bar} />;
108115
}
109116
}
110-
const instance = test(<Foo initialValue="foo" />, 'DIV', 'foo');
111-
instance.changeState();
117+
const ref = React.createRef();
118+
test(<Foo initialValue="foo" ref={ref} />, 'DIV', 'foo');
119+
act(() => ref.current.changeState());
112120
test(<Foo />, 'SPAN', 'bar');
113121
});
114122

@@ -137,7 +145,7 @@ describe('ReactES6Class', () => {
137145
return <div />;
138146
}
139147
}
140-
expect(() => ReactDOM.render(<Foo foo="foo" />, container)).toErrorDev(
148+
expect(() => act(() => root.render(<Foo foo="foo" />))).toErrorDev(
141149
'Foo: getDerivedStateFromProps() is defined as an instance method ' +
142150
'and will be ignored. Instead, declare it as a static method.',
143151
);
@@ -152,7 +160,7 @@ describe('ReactES6Class', () => {
152160
return <div />;
153161
}
154162
}
155-
expect(() => ReactDOM.render(<Foo foo="foo" />, container)).toErrorDev(
163+
expect(() => act(() => root.render(<Foo foo="foo" />))).toErrorDev(
156164
'Foo: getDerivedStateFromError() is defined as an instance method ' +
157165
'and will be ignored. Instead, declare it as a static method.',
158166
);
@@ -165,7 +173,7 @@ describe('ReactES6Class', () => {
165173
return <div />;
166174
}
167175
}
168-
expect(() => ReactDOM.render(<Foo foo="foo" />, container)).toErrorDev(
176+
expect(() => act(() => root.render(<Foo foo="foo" />))).toErrorDev(
169177
'Foo: getSnapshotBeforeUpdate() is defined as a static method ' +
170178
'and will be ignored. Instead, declare it as an instance method.',
171179
);
@@ -183,7 +191,7 @@ describe('ReactES6Class', () => {
183191
return <div className={`${this.state.foo} ${this.state.bar}`} />;
184192
}
185193
}
186-
expect(() => ReactDOM.render(<Foo foo="foo" />, container)).toErrorDev(
194+
expect(() => act(() => root.render(<Foo foo="foo" />))).toErrorDev(
187195
'`Foo` uses `getDerivedStateFromProps` but its initial state is ' +
188196
'undefined. This is not recommended. Instead, define the initial state by ' +
189197
'assigning an object to `this.state` in the constructor of `Foo`. ' +
@@ -277,7 +285,9 @@ describe('ReactES6Class', () => {
277285
}
278286
}
279287
test(<Foo initialValue="foo" />, 'SPAN', 'bar');
280-
expect(renderCount).toBe(1);
288+
// This is broken with deferRenderPhaseUpdateToNextBatch flag on.
289+
// We can't use the gate feature here because this test is also in CoffeeScript and TypeScript.
290+
expect(renderCount).toBe(global.__WWW__ && !global.__VARIANT__ ? 2 : 1);
281291
});
282292

283293
it('should warn with non-object in the initial state property', () => {
@@ -326,7 +336,8 @@ describe('ReactES6Class', () => {
326336
}
327337
}
328338
test(<Foo initialValue="foo" />, 'DIV', 'foo');
329-
attachedListener();
339+
340+
act(() => attachedListener());
330341
expect(renderedName).toBe('bar');
331342
});
332343

@@ -367,7 +378,7 @@ describe('ReactES6Class', () => {
367378
}
368379
}
369380
test(<Foo initialValue="foo" />, 'DIV', 'foo');
370-
attachedListener();
381+
act(() => attachedListener());
371382
expect(renderedName).toBe('bar');
372383
});
373384

@@ -416,7 +427,7 @@ describe('ReactES6Class', () => {
416427
'did-update', freeze({value: 'foo'}), {},
417428
]);
418429
lifeCycles = []; // reset
419-
ReactDOM.unmountComponentAtNode(container);
430+
act(() => root.unmount());
420431
expect(lifeCycles).toEqual(['will-unmount']);
421432
});
422433

@@ -520,15 +531,16 @@ describe('ReactES6Class', () => {
520531
});
521532

522533
it('should throw AND warn when trying to access classic APIs', () => {
523-
const instance = test(<Inner name="foo" />, 'DIV', 'foo');
534+
const ref = React.createRef();
535+
test(<Inner name="foo" ref={ref} />, 'DIV', 'foo');
524536
expect(() =>
525-
expect(() => instance.replaceState({})).toThrow(),
537+
expect(() => ref.current.replaceState({})).toThrow(),
526538
).toWarnDev(
527539
'replaceState(...) is deprecated in plain JavaScript React classes',
528540
{withoutStack: true},
529541
);
530542
expect(() =>
531-
expect(() => instance.isMounted()).toThrow(),
543+
expect(() => ref.current.isMounted()).toThrow(),
532544
).toWarnDev(
533545
'isMounted(...) is deprecated in plain JavaScript React classes',
534546
{withoutStack: true},
@@ -560,13 +572,15 @@ describe('ReactES6Class', () => {
560572
return <Inner name="foo" ref="inner" />;
561573
}
562574
}
563-
const instance = test(<Foo />, 'DIV', 'foo');
564-
expect(instance.refs.inner.getName()).toBe('foo');
575+
const ref = React.createRef();
576+
test(<Foo ref={ref} />, 'DIV', 'foo');
577+
expect(ref.current.refs.inner.getName()).toBe('foo');
565578
});
566579

567580
it('supports drilling through to the DOM using findDOMNode', () => {
568-
const instance = test(<Inner name="foo" />, 'DIV', 'foo');
569-
const node = ReactDOM.findDOMNode(instance);
581+
const ref = React.createRef();
582+
test(<Inner name="foo" ref={ref} />, 'DIV', 'foo');
583+
const node = ReactDOM.findDOMNode(ref.current);
570584
expect(node).toBe(container.firstChild);
571585
});
572586
});

‎packages/react/src/__tests__/ReactTypeScriptClass-test.ts

Lines changed: 50 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/// <reference path="./testDefinitions/PropTypes.d.ts" />
22
/// <reference path="./testDefinitions/React.d.ts" />
33
/// <reference path="./testDefinitions/ReactDOM.d.ts" />
4+
/// <reference path="./testDefinitions/ReactDOMTestUtils.d.ts" />
45

56
/*!
67
* Copyright (c) Facebook, Inc. and its affiliates.
@@ -11,11 +12,13 @@
1112

1213
import React = require('react');
1314
import ReactDOM = require('react-dom');
15+
import ReactDOMTestUtils = require('react-dom/test-utils');
1416
import PropTypes = require('prop-types');
1517

1618
// Before Each
1719

1820
let container;
21+
let root;
1922
let attachedListener = null;
2023
let renderedName = null;
2124

@@ -31,11 +34,10 @@ class Inner extends React.Component {
3134
}
3235

3336
function test(element, expectedTag, expectedClassName) {
34-
const instance = ReactDOM.render(element, container);
37+
ReactDOMTestUtils.act(() => root.render(element));
3538
expect(container.firstChild).not.toBeNull();
3639
expect(container.firstChild.tagName).toBe(expectedTag);
3740
expect(container.firstChild.className).toBe(expectedClassName);
38-
return instance;
3941
}
4042

4143
// Classes need to be declared at the top level scope, so we declare all the
@@ -313,6 +315,7 @@ class ClassicRefs extends React.Component {
313315
describe('ReactTypeScriptClass', function() {
314316
beforeEach(function() {
315317
container = document.createElement('div');
318+
root = ReactDOM.createRoot(container);
316319
attachedListener = null;
317320
renderedName = null;
318321
});
@@ -322,12 +325,16 @@ describe('ReactTypeScriptClass', function() {
322325
});
323326

324327
it('throws if no render function is defined', function() {
325-
expect(() =>
328+
expect(() => {
326329
expect(() =>
327-
ReactDOM.render(React.createElement(Empty), container)
328-
).toThrow()
329-
).toErrorDev([
330-
// A failed component renders twice in DEV
330+
ReactDOMTestUtils.act(() => root.render(React.createElement(Empty)))
331+
).toThrow();
332+
}).toErrorDev([
333+
// A failed component renders four times in DEV in concurrent mode
334+
'Warning: Empty(...): No `render` method found on the returned ' +
335+
'component instance: you may have forgotten to define `render`.',
336+
'Warning: Empty(...): No `render` method found on the returned ' +
337+
'component instance: you may have forgotten to define `render`.',
331338
'Warning: Empty(...): No `render` method found on the returned ' +
332339
'component instance: you may have forgotten to define `render`.',
333340
'Warning: Empty(...): No `render` method found on the returned ' +
@@ -349,12 +356,13 @@ describe('ReactTypeScriptClass', function() {
349356
});
350357

351358
it('renders based on state using props in the constructor', function() {
352-
const instance = test(
353-
React.createElement(StateBasedOnProps, {initialValue: 'foo'}),
359+
const ref = React.createRef();
360+
test(
361+
React.createElement(StateBasedOnProps, {initialValue: 'foo', ref: ref}),
354362
'DIV',
355363
'foo'
356364
);
357-
instance.changeState();
365+
ReactDOMTestUtils.act(() => ref.current.changeState());
358366
test(React.createElement(StateBasedOnProps), 'SPAN', 'bar');
359367
});
360368

@@ -389,7 +397,9 @@ describe('ReactTypeScriptClass', function() {
389397
}
390398
}
391399
expect(function() {
392-
ReactDOM.render(React.createElement(Foo, {foo: 'foo'}), container);
400+
ReactDOMTestUtils.act(() =>
401+
root.render(React.createElement(Foo, {foo: 'foo'}))
402+
);
393403
}).toErrorDev(
394404
'Foo: getDerivedStateFromProps() is defined as an instance method ' +
395405
'and will be ignored. Instead, declare it as a static method.'
@@ -406,7 +416,9 @@ describe('ReactTypeScriptClass', function() {
406416
}
407417
}
408418
expect(function() {
409-
ReactDOM.render(React.createElement(Foo, {foo: 'foo'}), container);
419+
ReactDOMTestUtils.act(() =>
420+
root.render(React.createElement(Foo, {foo: 'foo'}))
421+
);
410422
}).toErrorDev(
411423
'Foo: getDerivedStateFromError() is defined as an instance method ' +
412424
'and will be ignored. Instead, declare it as a static method.'
@@ -415,14 +427,15 @@ describe('ReactTypeScriptClass', function() {
415427

416428
it('warns if getSnapshotBeforeUpdate is static', function() {
417429
class Foo extends React.Component {
418-
static getSnapshotBeforeUpdate() {
419-
}
430+
static getSnapshotBeforeUpdate() {}
420431
render() {
421432
return React.createElement('div', {});
422433
}
423434
}
424435
expect(function() {
425-
ReactDOM.render(React.createElement(Foo, {foo: 'foo'}), container);
436+
ReactDOMTestUtils.act(() =>
437+
root.render(React.createElement(Foo, {foo: 'foo'}))
438+
);
426439
}).toErrorDev(
427440
'Foo: getSnapshotBeforeUpdate() is defined as a static method ' +
428441
'and will be ignored. Instead, declare it as an instance method.'
@@ -444,12 +457,14 @@ describe('ReactTypeScriptClass', function() {
444457
}
445458
}
446459
expect(function() {
447-
ReactDOM.render(React.createElement(Foo, {foo: 'foo'}), container);
460+
ReactDOMTestUtils.act(() =>
461+
root.render(React.createElement(Foo, {foo: 'foo'}))
462+
);
448463
}).toErrorDev(
449464
'`Foo` uses `getDerivedStateFromProps` but its initial state is ' +
450-
'undefined. This is not recommended. Instead, define the initial state by ' +
451-
'assigning an object to `this.state` in the constructor of `Foo`. ' +
452-
'This ensures that `getDerivedStateFromProps` arguments have a consistent shape.'
465+
'undefined. This is not recommended. Instead, define the initial state by ' +
466+
'assigning an object to `this.state` in the constructor of `Foo`. ' +
467+
'This ensures that `getDerivedStateFromProps` arguments have a consistent shape.'
453468
);
454469
});
455470

@@ -501,7 +516,9 @@ describe('ReactTypeScriptClass', function() {
501516
it('renders only once when setting state in componentWillMount', function() {
502517
renderCount = 0;
503518
test(React.createElement(RenderOnce, {initialValue: 'foo'}), 'SPAN', 'bar');
504-
expect(renderCount).toBe(1);
519+
// This is broken with deferRenderPhaseUpdateToNextBatch flag on.
520+
// We can't use the gate feature in TypeScript.
521+
expect(renderCount).toBe(global.__WWW__ && !global.__VARIANT__ ? 2 : 1);
505522
});
506523

507524
it('should warn with non-object in the initial state property', function() {
@@ -526,7 +543,7 @@ describe('ReactTypeScriptClass', function() {
526543
'DIV',
527544
'foo'
528545
);
529-
attachedListener();
546+
ReactDOMTestUtils.act(() => attachedListener());
530547
expect(renderedName).toBe('bar');
531548
});
532549

@@ -545,7 +562,7 @@ describe('ReactTypeScriptClass', function() {
545562
'DIV',
546563
'foo'
547564
);
548-
attachedListener();
565+
ReactDOMTestUtils.act(() => attachedListener());
549566
expect(renderedName).toBe('bar');
550567
});
551568

@@ -569,7 +586,7 @@ describe('ReactTypeScriptClass', function() {
569586
{},
570587
]);
571588
lifeCycles = []; // reset
572-
ReactDOM.unmountComponentAtNode(container);
589+
ReactDOMTestUtils.act(() => root.unmount(container));
573590
expect(lifeCycles).toEqual(['will-unmount']);
574591
});
575592

@@ -645,19 +662,16 @@ describe('ReactTypeScriptClass', function() {
645662
});
646663

647664
it('should throw AND warn when trying to access classic APIs', function() {
648-
const instance = test(
649-
React.createElement(Inner, {name: 'foo'}),
650-
'DIV',
651-
'foo'
652-
);
665+
const ref = React.createRef();
666+
test(React.createElement(Inner, {name: 'foo', ref: ref}), 'DIV', 'foo');
653667
expect(() =>
654-
expect(() => instance.replaceState({})).toThrow()
668+
expect(() => ref.current.replaceState({})).toThrow()
655669
).toWarnDev(
656670
'replaceState(...) is deprecated in plain JavaScript React classes',
657671
{withoutStack: true}
658672
);
659673
expect(() =>
660-
expect(() => instance.isMounted()).toThrow()
674+
expect(() => ref.current.isMounted()).toThrow()
661675
).toWarnDev(
662676
'isMounted(...) is deprecated in plain JavaScript React classes',
663677
{withoutStack: true}
@@ -669,17 +683,15 @@ describe('ReactTypeScriptClass', function() {
669683
});
670684

671685
it('supports classic refs', function() {
672-
const instance = test(React.createElement(ClassicRefs), 'DIV', 'foo');
673-
expect(instance.refs.inner.getName()).toBe('foo');
686+
const ref = React.createRef();
687+
test(React.createElement(ClassicRefs, {ref: ref}), 'DIV', 'foo');
688+
expect(ref.current.refs.inner.getName()).toBe('foo');
674689
});
675690

676691
it('supports drilling through to the DOM using findDOMNode', function() {
677-
const instance = test(
678-
React.createElement(Inner, {name: 'foo'}),
679-
'DIV',
680-
'foo'
681-
);
682-
const node = ReactDOM.findDOMNode(instance);
692+
const ref = React.createRef();
693+
test(React.createElement(Inner, {name: 'foo', ref: ref}), 'DIV', 'foo');
694+
const node = ReactDOM.findDOMNode(ref.current);
683695
expect(node).toBe(container.firstChild);
684696
});
685697
});

‎packages/react/src/__tests__/testDefinitions/React.d.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
* just helpers for the unit test.
1313
*/
1414

15+
declare let global: any;
16+
1517
declare module 'react' {
1618
export class Component {
1719
props: any;
@@ -24,4 +26,5 @@ declare module 'react' {
2426
}
2527
export let PropTypes : any;
2628
export function createElement(tag : any, props ?: any, ...children : any[]) : any
29+
export function createRef(): any;
2730
}

‎packages/react/src/__tests__/testDefinitions/ReactDOM.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
*/
1414

1515
declare module 'react-dom' {
16+
export function createRoot(container : any) : any
1617
export function render(element : any, container : any) : any
1718
export function unmountComponentAtNode(container : any) : void
1819
export function findDOMNode(instance : any) : any
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/*!
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
/**
9+
* TypeScript Definition File for React.
10+
*
11+
* Full type definitions are not yet officially supported. These are mostly
12+
* just helpers for the unit test.
13+
*/
14+
15+
declare module 'react-dom/test-utils' {
16+
export function act(cb : () => any) : any
17+
}

‎scripts/jest/typescript/preprocessor.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ function compile(content, contentFilename) {
4242
let source;
4343
const libRegex = /lib\.(.+\.)?d\.ts$/;
4444
const jestRegex = /jest\.d\.ts/;
45-
const reactRegex = /(?:React|ReactDOM|PropTypes)(?:\.d)?\.ts$/;
45+
const reactRegex = /(?:React|ReactDOM|ReactDOMTestUtils|PropTypes)(?:\.d)?\.ts$/;
4646

4747
// `path.normalize` is used to turn forward slashes in
4848
// the file path into backslashes on Windows.

0 commit comments

Comments
 (0)
Please sign in to comment.