@samitouri / QOS-React-2 / commits / b380c24852

Convert class equivlance tests to flushSync (#26333)

There's an old collection of test suites that test class component behavior across ES6 (regular JavaScript classes), CoffeeScript classes, and TypeScript classes. They work by running the same tests in all environments and comparing the results. Rather than use `act` or `waitFor` in these, I've changed them to use `flushSync` instead so that they can flush synchronously. The reason is that CoffeeScript doesn't have async/await, so we'd have to write those tests differently than how they are written in the corresponding modules. Since none of these tests cover any concurrent behavior, I believe it's fine in this case to do everything synchronously; they don't use any concurrent features, anyway, so effectively it's just skipping a microtask.

Andrew Clark committed Mar 7, 2023 at 10:14 UTC b380c24852b43856028167d5355926483f51d9fc
4 files changed +32 -35
packages/react/src/__tests__/ReactCoffeeScriptClass-test.coffee
+11 -11
@@ -22,7 +22,6 @@ describe 'ReactCoffeeScriptClass', ->
22 React = require 'react'
23 ReactDOM = require 'react-dom'
24 ReactDOMClient = require 'react-dom/client'
25 - act = require('jest-react').act
25 PropTypes = require 'prop-types'
26 container = document.createElement 'div'
27 root = ReactDOMClient.createRoot container
@@ -36,7 +35,7 @@ describe 'ReactCoffeeScriptClass', ->
35 return React.createElement('div', className: this.props.name)
36
37 test = (element, expectedTag, expectedClassName) ->
39 - act ->
38 + ReactDOM.flushSync ->
39 root.render(element)
40 expect(container.firstChild).not.toBeNull()
41 expect(container.firstChild.tagName).toBe(expectedTag)
@@ -50,7 +49,7 @@ describe 'ReactCoffeeScriptClass', ->
49 class Foo extends React.Component
50 expect(->
51 expect(->
53 - act ->
52 + ReactDOM.flushSync ->
53 root.render React.createElement(Foo)
54 ).toThrow()
55 ).toErrorDev([
@@ -103,7 +102,8 @@ describe 'ReactCoffeeScriptClass', ->
102
103 ref = React.createRef()
104 test React.createElement(Foo, initialValue: 'foo', ref: ref), 'DIV', 'foo'
106 - ref.current.changeState()
105 + ReactDOM.flushSync ->
106 + ref.current.changeState()
107 test React.createElement(Foo), 'SPAN', 'bar'
108
109 it 'sets initial state with value returned by static getDerivedStateFromProps', ->
@@ -129,7 +129,7 @@ describe 'ReactCoffeeScriptClass', ->
129 getDerivedStateFromProps: ->
130 {}
131 expect(->
132 - act ->
132 + ReactDOM.flushSync ->
133 root.render React.createElement(Foo, foo: 'foo')
134 return
135 ).toErrorDev 'Foo: getDerivedStateFromProps() is defined as an instance method and will be ignored. Instead, declare it as a static method.'
@@ -141,7 +141,7 @@ describe 'ReactCoffeeScriptClass', ->
141 getDerivedStateFromError: ->
142 {}
143 expect(->
144 - act ->
144 + ReactDOM.flushSync ->
145 root.render React.createElement(Foo, foo: 'foo')
146 return
147 ).toErrorDev 'Foo: getDerivedStateFromError() is defined as an instance method and will be ignored. Instead, declare it as a static method.'
@@ -153,7 +153,7 @@ describe 'ReactCoffeeScriptClass', ->
153 Foo.getSnapshotBeforeUpdate = () ->
154 {}
155 expect(->
156 - act ->
156 + ReactDOM.flushSync ->
157 root.render React.createElement(Foo, foo: 'foo')
158 return
159 ).toErrorDev 'Foo: getSnapshotBeforeUpdate() is defined as a static method and will be ignored. Instead, declare it as an instance method.'
@@ -170,7 +170,7 @@ describe 'ReactCoffeeScriptClass', ->
170 bar: 'bar'
171 }
172 expect(->
173 - act ->
173 + ReactDOM.flushSync ->
174 root.render React.createElement(Foo, foo: 'foo')
175 return
176 ).toErrorDev (
@@ -303,7 +303,7 @@ describe 'ReactCoffeeScriptClass', ->
303 )
304
305 test React.createElement(Foo, initialValue: 'foo'), 'DIV', 'foo'
306 - act ->
306 + ReactDOM.flushSync ->
307 attachedListener()
308 expect(renderedName).toBe 'bar'
309
@@ -340,7 +340,7 @@ describe 'ReactCoffeeScriptClass', ->
340 )
341
342 test React.createElement(Foo, initialValue: 'foo'), 'DIV', 'foo'
343 - act ->
343 + ReactDOM.flushSync ->
344 attachedListener()
345 expect(renderedName).toBe 'bar'
346
@@ -391,7 +391,7 @@ describe 'ReactCoffeeScriptClass', ->
391 'did-update', { value: 'foo' }, {}
392 ]
393 lifeCycles = [] # reset
394 - act ->
394 + ReactDOM.flushSync ->
395 root.unmount()
396 expect(lifeCycles).toEqual ['will-unmount']
397
packages/react/src/__tests__/ReactES6Class-test.js
+10 -12
@@ -13,7 +13,6 @@ let PropTypes;
13 let React;
14 let ReactDOM;
15 let ReactDOMClient;
16 -let act;
16
17 describe('ReactES6Class', () => {
18 let container;
@@ -31,7 +30,6 @@ describe('ReactES6Class', () => {
30 React = require('react');
31 ReactDOM = require('react-dom');
32 ReactDOMClient = require('react-dom/client');
34 - act = require('jest-react').act;
33 container = document.createElement('div');
34 root = ReactDOMClient.createRoot(container);
35 attachedListener = null;
@@ -49,7 +47,7 @@ describe('ReactES6Class', () => {
47 });
48
49 function test(element, expectedTag, expectedClassName) {
52 - act(() => root.render(element));
50 + ReactDOM.flushSync(() => root.render(element));
51 expect(container.firstChild).not.toBeNull();
52 expect(container.firstChild.tagName).toBe(expectedTag);
53 expect(container.firstChild.className).toBe(expectedClassName);
@@ -63,7 +61,7 @@ describe('ReactES6Class', () => {
61 it('throws if no render function is defined', () => {
62 class Foo extends React.Component {}
63 expect(() => {
66 - expect(() => act(() => root.render(<Foo />))).toThrow();
64 + expect(() => ReactDOM.flushSync(() => 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 ' +
@@ -118,7 +116,7 @@ describe('ReactES6Class', () => {
116 }
117 const ref = React.createRef();
118 test(<Foo initialValue="foo" ref={ref} />, 'DIV', 'foo');
121 - act(() => ref.current.changeState());
119 + ReactDOM.flushSync(() => ref.current.changeState());
120 test(<Foo />, 'SPAN', 'bar');
121 });
122
@@ -148,7 +146,7 @@ describe('ReactES6Class', () => {
146 }
147 }
148 expect(() => {
151 - act(() => root.render(<Foo foo="foo" />));
149 + ReactDOM.flushSync(() => root.render(<Foo foo="foo" />));
150 }).toErrorDev(
151 'Foo: getDerivedStateFromProps() is defined as an instance method ' +
152 'and will be ignored. Instead, declare it as a static method.',
@@ -165,7 +163,7 @@ describe('ReactES6Class', () => {
163 }
164 }
165 expect(() => {
168 - act(() => root.render(<Foo foo="foo" />));
166 + ReactDOM.flushSync(() => root.render(<Foo foo="foo" />));
167 }).toErrorDev(
168 'Foo: getDerivedStateFromError() is defined as an instance method ' +
169 'and will be ignored. Instead, declare it as a static method.',
@@ -180,7 +178,7 @@ describe('ReactES6Class', () => {
178 }
179 }
180 expect(() => {
183 - act(() => root.render(<Foo foo="foo" />));
181 + ReactDOM.flushSync(() => root.render(<Foo foo="foo" />));
182 }).toErrorDev(
183 'Foo: getSnapshotBeforeUpdate() is defined as a static method ' +
184 'and will be ignored. Instead, declare it as an instance method.',
@@ -200,7 +198,7 @@ describe('ReactES6Class', () => {
198 }
199 }
200 expect(() => {
203 - act(() => root.render(<Foo foo="foo" />));
201 + ReactDOM.flushSync(() => root.render(<Foo foo="foo" />));
202 }).toErrorDev(
203 '`Foo` uses `getDerivedStateFromProps` but its initial state is ' +
204 'undefined. This is not recommended. Instead, define the initial state by ' +
@@ -347,7 +345,7 @@ describe('ReactES6Class', () => {
345 }
346 test(<Foo initialValue="foo" />, 'DIV', 'foo');
347
350 - act(() => attachedListener());
348 + ReactDOM.flushSync(() => attachedListener());
349 expect(renderedName).toBe('bar');
350 });
351
@@ -388,7 +386,7 @@ describe('ReactES6Class', () => {
386 }
387 }
388 test(<Foo initialValue="foo" />, 'DIV', 'foo');
391 - act(() => attachedListener());
389 + ReactDOM.flushSync(() => attachedListener());
390 expect(renderedName).toBe('bar');
391 });
392
@@ -437,7 +435,7 @@ describe('ReactES6Class', () => {
435 'did-update', freeze({value: 'foo'}), {},
436 ]);
437 lifeCycles = []; // reset
440 - act(() => root.unmount());
438 + ReactDOM.flushSync(() => root.unmount());
439 expect(lifeCycles).toEqual(['will-unmount']);
440 });
441
packages/react/src/__tests__/ReactTypeScriptClass-test.ts
+10 -12
@@ -16,7 +16,6 @@ import ReactDOM = require('react-dom');
16 import ReactDOMClient = require('react-dom/client');
17 import ReactDOMTestUtils = require('react-dom/test-utils');
18 import PropTypes = require('prop-types');
19 -import internalAct = require('jest-react');
19
20 // Before Each
21
@@ -24,7 +23,6 @@ let container;
23 let root;
24 let attachedListener = null;
25 let renderedName = null;
27 -let act = internalAct.act;
26
27 class Inner extends React.Component {
28 getName() {
@@ -38,7 +36,7 @@ class Inner extends React.Component {
36 }
37
38 function test(element, expectedTag, expectedClassName) {
41 - act(() => root.render(element));
39 + ReactDOM.flushSync(() => root.render(element));
40 expect(container.firstChild).not.toBeNull();
41 expect(container.firstChild.tagName).toBe(expectedTag);
42 expect(container.firstChild.className).toBe(expectedClassName);
@@ -331,7 +329,7 @@ describe('ReactTypeScriptClass', function() {
329 it('throws if no render function is defined', function() {
330 expect(() => {
331 expect(() =>
334 - act(() => root.render(React.createElement(Empty)))
332 + ReactDOM.flushSync(() => root.render(React.createElement(Empty)))
333 ).toThrow();
334 }).toErrorDev([
335 // A failed component renders four times in DEV in concurrent mode
@@ -366,7 +364,7 @@ describe('ReactTypeScriptClass', function() {
364 'DIV',
365 'foo'
366 );
369 - act(() => ref.current.changeState());
367 + ReactDOM.flushSync(() => ref.current.changeState());
368 test(React.createElement(StateBasedOnProps), 'SPAN', 'bar');
369 });
370
@@ -401,7 +399,7 @@ describe('ReactTypeScriptClass', function() {
399 }
400 }
401 expect(function() {
404 - act(() =>
402 + ReactDOM.flushSync(() =>
403 root.render(React.createElement(Foo, {foo: 'foo'}))
404 );
405 }).toErrorDev(
@@ -420,7 +418,7 @@ describe('ReactTypeScriptClass', function() {
418 }
419 }
420 expect(function() {
423 - act(() =>
421 + ReactDOM.flushSync(() =>
422 root.render(React.createElement(Foo, {foo: 'foo'}))
423 );
424 }).toErrorDev(
@@ -437,7 +435,7 @@ describe('ReactTypeScriptClass', function() {
435 }
436 }
437 expect(function() {
440 - act(() =>
438 + ReactDOM.flushSync(() =>
439 root.render(React.createElement(Foo, {foo: 'foo'}))
440 );
441 }).toErrorDev(
@@ -461,7 +459,7 @@ describe('ReactTypeScriptClass', function() {
459 }
460 }
461 expect(function() {
464 - act(() =>
462 + ReactDOM.flushSync(() =>
463 root.render(React.createElement(Foo, {foo: 'foo'}))
464 );
465 }).toErrorDev(
@@ -547,7 +545,7 @@ describe('ReactTypeScriptClass', function() {
545 'DIV',
546 'foo'
547 );
550 - act(() => attachedListener());
548 + ReactDOM.flushSync(() => attachedListener());
549 expect(renderedName).toBe('bar');
550 });
551
@@ -566,7 +564,7 @@ describe('ReactTypeScriptClass', function() {
564 'DIV',
565 'foo'
566 );
569 - act(() => attachedListener());
567 + ReactDOM.flushSync(() => attachedListener());
568 expect(renderedName).toBe('bar');
569 });
570
@@ -590,7 +588,7 @@ describe('ReactTypeScriptClass', function() {
588 {},
589 ]);
590 lifeCycles = []; // reset
593 - act(() => root.unmount(container));
591 + ReactDOM.flushSync(() => root.unmount(container));
592 expect(lifeCycles).toEqual(['will-unmount']);
593 });
594
packages/react/src/__tests__/testDefinitions/ReactDOM.d.ts
+1
@@ -16,4 +16,5 @@ declare module 'react-dom' {
16 export function render(element : any, container : any) : any
17 export function unmountComponentAtNode(container : any) : void
18 export function findDOMNode(instance : any) : any
19 + export function flushSync(cb : any) : any
20 }