From 3ff1540e9bbe30aae52e2c9ab61c843bd0c94237 Mon Sep 17 00:00:00 2001 From: Sebastian Silbermann Date: Thu, 9 Feb 2023 11:54:35 +0100 Subject: [PATCH] Prefer JSX in ReactNoop assertions (to combat out-of-memory test runs) (#26127) ## Summary Prefer `getChildrenAsJSX` or `toMatchRenderedOutput` over `getChildren`. Use `dangerouslyGetChildren` if you really need to (e.g. for `toBe` assertions). Prefer `getPendingChildrenAsJSX` over `getPendingChildren`. Use `dangerouslyGetPendingChildren` if you really need to (e.g. for `toBe` assertions). `ReactNoop.getChildren` contains the fibers as non-enumerable properties. If you pass the children to `toEqual` and have a mismatch, Jest performance is very poor (to the point of causing out-of-memory crashes e.g. https://app.circleci.com/pipelines/github/facebook/react/38084/workflows/02ca0cbb-bab4-4c19-8d7d-ada814eeebb9/jobs/624297/parallel-runs/5?filterBy=ALL&invite=true#step-106-27). Mismatches can sometimes be intended e.g. on gated tests. Instead, I converted almost all of the `toEqual` assertions to `toMatchRenderedOutput` assertions or compare the JSX instead. For ReactNoopPersistent we still use `getChildren` since we have assertions on referential equality. `toMatchRenderedOutput` is more accurate in some instances anyway. I highlighted some of those more accurate assertions in review-comments. ## How did you test this change? - [x] `CIRCLE_NODE_TOTAL=20 CIRCLE_NODE_INDEX=5 yarn test -r=experimental --env=development --ci`: Can take up to 350s (and use up to 7GB of memory) on `main` but 11s on this branch - [x] No more slow `yarn test` parallel runs of `yarn_test` jobs (the steps in these runs should take <1min but sometimes they take 3min and end with OOM like https://app.circleci.com/pipelines/github/facebook/react/38084/workflows/02ca0cbb-bab4-4c19-8d7d-ada814eeebb9/jobs/624258/parallel-runs/5?filterBy=ALL: Looks good with a sample size of 1 https://app.circleci.com/pipelines/github/facebook/react/38110/workflows/745109a2-b86b-429f-8c01-9b23a245417a/jobs/624651 --- packages/react-noop-renderer/src/ReactNoop.js | 2 + .../src/ReactNoopPersistent.js | 2 + .../src/createReactNoop.js | 26 +- .../src/__tests__/ReactExpiration-test.js | 24 +- .../src/__tests__/ReactFragment-test.js | 164 +-- .../ReactHooksWithNoopRenderer-test.js | 427 ++++---- ...tIncrementalErrorHandling-test.internal.js | 207 ++-- .../ReactIncrementalScheduling-test.js | 12 +- .../ReactIncrementalSideEffects-test.js | 454 +++++---- .../ReactIncrementalTriangle-test.js | 5 +- .../__tests__/ReactIncrementalUpdates-test.js | 24 +- .../src/__tests__/ReactMemo-test.js | 34 +- .../src/__tests__/ReactNewContext-test.js | 180 ++-- .../__tests__/ReactNoopRendererAct-test.js | 2 +- .../src/__tests__/ReactPersistent-test.js | 24 +- .../__tests__/ReactSuspenseCallback-test.js | 25 +- .../ReactSuspenseEffectsSemantics-test.js | 937 ++++++++++-------- .../__tests__/ReactSuspenseFallback-test.js | 16 +- .../ReactSuspenseWithNoopRenderer-test.js | 514 ++++++---- .../src/__tests__/useEffectEvent-test.js | 278 +++--- 20 files changed, 1985 insertions(+), 1372 deletions(-) diff --git a/packages/react-noop-renderer/src/ReactNoop.js b/packages/react-noop-renderer/src/ReactNoop.js index d25e2dcac7307..f7f750cd30652 100644 --- a/packages/react-noop-renderer/src/ReactNoop.js +++ b/packages/react-noop-renderer/src/ReactNoop.js @@ -20,7 +20,9 @@ import createReactNoop from './createReactNoop'; export const { _Scheduler, getChildren, + dangerouslyGetChildren, getPendingChildren, + dangerouslyGetPendingChildren, getOrCreateRootContainer, createRoot, createLegacyRoot, diff --git a/packages/react-noop-renderer/src/ReactNoopPersistent.js b/packages/react-noop-renderer/src/ReactNoopPersistent.js index 1a94a5a179cc2..c4bce280f216f 100644 --- a/packages/react-noop-renderer/src/ReactNoopPersistent.js +++ b/packages/react-noop-renderer/src/ReactNoopPersistent.js @@ -20,7 +20,9 @@ import createReactNoop from './createReactNoop'; export const { _Scheduler, getChildren, + dangerouslyGetChildren, getPendingChildren, + dangerouslyGetPendingChildren, getOrCreateRootContainer, createRoot, createLegacyRoot, diff --git a/packages/react-noop-renderer/src/createReactNoop.js b/packages/react-noop-renderer/src/createReactNoop.js index 68d415a12aa71..c8ce26b4f6dc0 100644 --- a/packages/react-noop-renderer/src/createReactNoop.js +++ b/packages/react-noop-renderer/src/createReactNoop.js @@ -789,11 +789,35 @@ function createReactNoop(reconciler: Function, useMutation: boolean) { _Scheduler: Scheduler, getChildren(rootID: string = DEFAULT_ROOT_ID) { + throw new Error( + 'No longer supported due to bad performance when used with `expect()`. ' + + 'Use `ReactNoop.getChildrenAsJSX()` instead or, if you really need to, `dangerouslyGetChildren` after you carefully considered the warning in its JSDOC.', + ); + }, + + getPendingChildren(rootID: string = DEFAULT_ROOT_ID) { + throw new Error( + 'No longer supported due to bad performance when used with `expect()`. ' + + 'Use `ReactNoop.getPendingChildrenAsJSX()` instead or, if you really need to, `dangerouslyGetPendingChildren` after you carefully considered the warning in its JSDOC.', + ); + }, + + /** + * Prefer using `getChildrenAsJSX`. + * Using the returned children in `.toEqual` has very poor performance on mismatch due to deep equality checking of fiber structures. + * Make sure you deeply remove enumerable properties before passing it to `.toEqual`, or, better, use `getChildrenAsJSX` or `toMatchRenderedOutput`. + */ + dangerouslyGetChildren(rootID: string = DEFAULT_ROOT_ID) { const container = rootContainers.get(rootID); return getChildren(container); }, - getPendingChildren(rootID: string = DEFAULT_ROOT_ID) { + /** + * Prefer using `getPendingChildrenAsJSX`. + * Using the returned children in `.toEqual` has very poor performance on mismatch due to deep equality checking of fiber structures. + * Make sure you deeply remove enumerable properties before passing it to `.toEqual`, or, better, use `getChildrenAsJSX` or `toMatchRenderedOutput`. + */ + dangerouslyGetPendingChildren(rootID: string = DEFAULT_ROOT_ID) { const container = rootContainers.get(rootID); return getPendingChildren(container); }, diff --git a/packages/react-reconciler/src/__tests__/ReactExpiration-test.js b/packages/react-reconciler/src/__tests__/ReactExpiration-test.js index 72ca257fefa8d..7b96efc2b9f9e 100644 --- a/packages/react-reconciler/src/__tests__/ReactExpiration-test.js +++ b/packages/react-reconciler/src/__tests__/ReactExpiration-test.js @@ -105,10 +105,6 @@ describe('ReactExpiration', () => { } } - function span(prop) { - return {type: 'span', children: [], prop, hidden: false}; - } - function flushNextRenderIfExpired() { // This will start rendering the next level of work. If the work hasn't // expired yet, React will exit without doing anything. If it has expired, @@ -127,21 +123,21 @@ describe('ReactExpiration', () => { ReactNoop.render(); } - expect(ReactNoop.getChildren()).toEqual([]); + expect(ReactNoop).toMatchRenderedOutput(null); // Nothing has expired yet because time hasn't advanced. flushNextRenderIfExpired(); - expect(ReactNoop.getChildren()).toEqual([]); + expect(ReactNoop).toMatchRenderedOutput(null); // Advance time a bit, but not enough to expire the low pri update. ReactNoop.expire(4500); flushNextRenderIfExpired(); - expect(ReactNoop.getChildren()).toEqual([]); + expect(ReactNoop).toMatchRenderedOutput(null); // Advance by another second. Now the update should expire and flush. ReactNoop.expire(500); flushNextRenderIfExpired(); - expect(ReactNoop.getChildren()).toEqual([span('done')]); + expect(ReactNoop).toMatchRenderedOutput(); }); it('two updates of like priority in the same event always flush within the same batch', () => { @@ -181,20 +177,20 @@ describe('ReactExpiration', () => { // Don't advance time by enough to expire the first update. expect(Scheduler).toHaveYielded([]); - expect(ReactNoop.getChildren()).toEqual([]); + expect(ReactNoop).toMatchRenderedOutput(null); // Schedule another update. ReactNoop.render(); // Both updates are batched expect(Scheduler).toFlushAndYield(['B [render]', 'B [commit]']); - expect(ReactNoop.getChildren()).toEqual([span('B')]); + expect(ReactNoop).toMatchRenderedOutput(); // Now do the same thing again, except this time don't flush any work in // between the two updates. ReactNoop.render(); Scheduler.unstable_advanceTime(2000); expect(Scheduler).toHaveYielded([]); - expect(ReactNoop.getChildren()).toEqual([span('B')]); + expect(ReactNoop).toMatchRenderedOutput(); // Schedule another update. ReactNoop.render(); // The updates should flush in the same batch, since as far as the scheduler @@ -242,20 +238,20 @@ describe('ReactExpiration', () => { // Don't advance time by enough to expire the first update. expect(Scheduler).toHaveYielded([]); - expect(ReactNoop.getChildren()).toEqual([]); + expect(ReactNoop).toMatchRenderedOutput(null); // Schedule another update. ReactNoop.render(); // Both updates are batched expect(Scheduler).toFlushAndYield(['B [render]', 'B [commit]']); - expect(ReactNoop.getChildren()).toEqual([span('B')]); + expect(ReactNoop).toMatchRenderedOutput(); // Now do the same thing again, except this time don't flush any work in // between the two updates. ReactNoop.render(); Scheduler.unstable_advanceTime(2000); expect(Scheduler).toHaveYielded([]); - expect(ReactNoop.getChildren()).toEqual([span('B')]); + expect(ReactNoop).toMatchRenderedOutput(); // Perform some synchronous work. The scheduler must assume we're inside // the same event. diff --git a/packages/react-reconciler/src/__tests__/ReactFragment-test.js b/packages/react-reconciler/src/__tests__/ReactFragment-test.js index 423cee2ed3895..f56602881dfcc 100644 --- a/packages/react-reconciler/src/__tests__/ReactFragment-test.js +++ b/packages/react-reconciler/src/__tests__/ReactFragment-test.js @@ -22,21 +22,6 @@ describe('ReactFragment', () => { Scheduler = require('scheduler'); }); - function div(...children) { - children = children.map(c => - typeof c === 'string' ? {text: c, hidden: false} : c, - ); - return {type: 'div', children, prop: undefined, hidden: false}; - } - - function span(prop) { - return {type: 'span', children: [], prop, hidden: false}; - } - - function text(t) { - return {text: t, hidden: false}; - } - it('should render a single child via noop renderer', () => { const element = ( <> @@ -47,7 +32,7 @@ describe('ReactFragment', () => { ReactNoop.render(element); expect(Scheduler).toFlushWithoutYielding(); - expect(ReactNoop.getChildren()).toEqual([span()]); + expect(ReactNoop).toMatchRenderedOutput(foo); }); it('should render zero children via noop renderer', () => { @@ -56,7 +41,7 @@ describe('ReactFragment', () => { ReactNoop.render(element); expect(Scheduler).toFlushWithoutYielding(); - expect(ReactNoop.getChildren()).toEqual([]); + expect(ReactNoop).toMatchRenderedOutput(null); }); it('should render multiple children via noop renderer', () => { @@ -69,7 +54,11 @@ describe('ReactFragment', () => { ReactNoop.render(element); expect(Scheduler).toFlushWithoutYielding(); - expect(ReactNoop.getChildren()).toEqual([text('hello '), span()]); + expect(ReactNoop).toMatchRenderedOutput( + <> + hello world + , + ); }); it('should render an iterable via noop renderer', () => { @@ -80,7 +69,12 @@ describe('ReactFragment', () => { ReactNoop.render(element); expect(Scheduler).toFlushWithoutYielding(); - expect(ReactNoop.getChildren()).toEqual([span(), span()]); + expect(ReactNoop).toMatchRenderedOutput( + <> + hi + bye + , + ); }); it('should preserve state of children with 1 level nesting', function () { @@ -114,13 +108,18 @@ describe('ReactFragment', () => { expect(Scheduler).toFlushWithoutYielding(); expect(ops).toEqual(['Update Stateful']); - expect(ReactNoop.getChildren()).toEqual([div(), div()]); + expect(ReactNoop).toMatchRenderedOutput( + <> +
Hello
+
World
+ , + ); ReactNoop.render(); expect(Scheduler).toFlushWithoutYielding(); expect(ops).toEqual(['Update Stateful', 'Update Stateful']); - expect(ReactNoop.getChildren()).toEqual([div()]); + expect(ReactNoop).toMatchRenderedOutput(
Hello
); }); it('should preserve state between top-level fragments', function () { @@ -155,13 +154,13 @@ describe('ReactFragment', () => { expect(Scheduler).toFlushWithoutYielding(); expect(ops).toEqual(['Update Stateful']); - expect(ReactNoop.getChildren()).toEqual([div()]); + expect(ReactNoop).toMatchRenderedOutput(
Hello
); ReactNoop.render(); expect(Scheduler).toFlushWithoutYielding(); expect(ops).toEqual(['Update Stateful', 'Update Stateful']); - expect(ReactNoop.getChildren()).toEqual([div()]); + expect(ReactNoop).toMatchRenderedOutput(
Hello
); }); it('should preserve state of children nested at same level', function () { @@ -205,13 +204,18 @@ describe('ReactFragment', () => { expect(Scheduler).toFlushWithoutYielding(); expect(ops).toEqual(['Update Stateful']); - expect(ReactNoop.getChildren()).toEqual([div(), div()]); + expect(ReactNoop).toMatchRenderedOutput( + <> +
+
Hello
+ , + ); ReactNoop.render(); expect(Scheduler).toFlushWithoutYielding(); expect(ops).toEqual(['Update Stateful', 'Update Stateful']); - expect(ReactNoop.getChildren()).toEqual([div()]); + expect(ReactNoop).toMatchRenderedOutput(
Hello
); }); it('should not preserve state in non-top-level fragment nesting', function () { @@ -248,13 +252,13 @@ describe('ReactFragment', () => { expect(Scheduler).toFlushWithoutYielding(); expect(ops).toEqual([]); - expect(ReactNoop.getChildren()).toEqual([div()]); + expect(ReactNoop).toMatchRenderedOutput(
Hello
); ReactNoop.render(); expect(Scheduler).toFlushWithoutYielding(); expect(ops).toEqual([]); - expect(ReactNoop.getChildren()).toEqual([div()]); + expect(ReactNoop).toMatchRenderedOutput(
Hello
); }); it('should not preserve state of children if nested 2 levels without siblings', function () { @@ -289,13 +293,13 @@ describe('ReactFragment', () => { expect(Scheduler).toFlushWithoutYielding(); expect(ops).toEqual([]); - expect(ReactNoop.getChildren()).toEqual([div()]); + expect(ReactNoop).toMatchRenderedOutput(
Hello
); ReactNoop.render(); expect(Scheduler).toFlushWithoutYielding(); expect(ops).toEqual([]); - expect(ReactNoop.getChildren()).toEqual([div()]); + expect(ReactNoop).toMatchRenderedOutput(
Hello
); }); it('should not preserve state of children if nested 2 levels with siblings', function () { @@ -331,13 +335,18 @@ describe('ReactFragment', () => { expect(Scheduler).toFlushWithoutYielding(); expect(ops).toEqual([]); - expect(ReactNoop.getChildren()).toEqual([div(), div()]); + expect(ReactNoop).toMatchRenderedOutput( + <> +
Hello
+
+ , + ); ReactNoop.render(); expect(Scheduler).toFlushWithoutYielding(); expect(ops).toEqual([]); - expect(ReactNoop.getChildren()).toEqual([div()]); + expect(ReactNoop).toMatchRenderedOutput(
Hello
); }); it('should preserve state between array nested in fragment and fragment', function () { @@ -370,13 +379,13 @@ describe('ReactFragment', () => { expect(Scheduler).toFlushWithoutYielding(); expect(ops).toEqual(['Update Stateful']); - expect(ReactNoop.getChildren()).toEqual([div()]); + expect(ReactNoop).toMatchRenderedOutput(
Hello
); ReactNoop.render(); expect(Scheduler).toFlushWithoutYielding(); expect(ops).toEqual(['Update Stateful', 'Update Stateful']); - expect(ReactNoop.getChildren()).toEqual([div()]); + expect(ReactNoop).toMatchRenderedOutput(
Hello
); }); it('should preserve state between top level fragment and array', function () { @@ -409,13 +418,13 @@ describe('ReactFragment', () => { expect(Scheduler).toFlushWithoutYielding(); expect(ops).toEqual(['Update Stateful']); - expect(ReactNoop.getChildren()).toEqual([div()]); + expect(ReactNoop).toMatchRenderedOutput(
Hello
); ReactNoop.render(); expect(Scheduler).toFlushWithoutYielding(); expect(ops).toEqual(['Update Stateful', 'Update Stateful']); - expect(ReactNoop.getChildren()).toEqual([div()]); + expect(ReactNoop).toMatchRenderedOutput(
Hello
); }); it('should not preserve state between array nested in fragment and double nested fragment', function () { @@ -450,13 +459,13 @@ describe('ReactFragment', () => { expect(Scheduler).toFlushWithoutYielding(); expect(ops).toEqual([]); - expect(ReactNoop.getChildren()).toEqual([div()]); + expect(ReactNoop).toMatchRenderedOutput(
Hello
); ReactNoop.render(); expect(Scheduler).toFlushWithoutYielding(); expect(ops).toEqual([]); - expect(ReactNoop.getChildren()).toEqual([div()]); + expect(ReactNoop).toMatchRenderedOutput(
Hello
); }); it('should not preserve state between array nested in fragment and double nested array', function () { @@ -487,13 +496,13 @@ describe('ReactFragment', () => { expect(Scheduler).toFlushWithoutYielding(); expect(ops).toEqual([]); - expect(ReactNoop.getChildren()).toEqual([div()]); + expect(ReactNoop).toMatchRenderedOutput(
Hello
); ReactNoop.render(); expect(Scheduler).toFlushWithoutYielding(); expect(ops).toEqual([]); - expect(ReactNoop.getChildren()).toEqual([div()]); + expect(ReactNoop).toMatchRenderedOutput(
Hello
); }); it('should preserve state between double nested fragment and double nested array', function () { @@ -528,13 +537,13 @@ describe('ReactFragment', () => { expect(Scheduler).toFlushWithoutYielding(); expect(ops).toEqual(['Update Stateful']); - expect(ReactNoop.getChildren()).toEqual([div()]); + expect(ReactNoop).toMatchRenderedOutput(
Hello
); ReactNoop.render(); expect(Scheduler).toFlushWithoutYielding(); expect(ops).toEqual(['Update Stateful', 'Update Stateful']); - expect(ReactNoop.getChildren()).toEqual([div()]); + expect(ReactNoop).toMatchRenderedOutput(
Hello
); }); it('should not preserve state of children when the keys are different', function () { @@ -570,13 +579,18 @@ describe('ReactFragment', () => { expect(Scheduler).toFlushWithoutYielding(); expect(ops).toEqual([]); - expect(ReactNoop.getChildren()).toEqual([div(), span()]); + expect(ReactNoop).toMatchRenderedOutput( + <> +
Hello
+ World + , + ); ReactNoop.render(); expect(Scheduler).toFlushWithoutYielding(); expect(ops).toEqual([]); - expect(ReactNoop.getChildren()).toEqual([div()]); + expect(ReactNoop).toMatchRenderedOutput(
Hello
); }); it('should not preserve state between unkeyed and keyed fragment', function () { @@ -611,13 +625,13 @@ describe('ReactFragment', () => { expect(Scheduler).toFlushWithoutYielding(); expect(ops).toEqual([]); - expect(ReactNoop.getChildren()).toEqual([div()]); + expect(ReactNoop).toMatchRenderedOutput(
Hello
); ReactNoop.render(); expect(Scheduler).toFlushWithoutYielding(); expect(ops).toEqual([]); - expect(ReactNoop.getChildren()).toEqual([div()]); + expect(ReactNoop).toMatchRenderedOutput(
Hello
); }); it('should preserve state with reordering in multiple levels', function () { @@ -664,13 +678,29 @@ describe('ReactFragment', () => { expect(Scheduler).toFlushWithoutYielding(); expect(ops).toEqual(['Update Stateful']); - expect(ReactNoop.getChildren()).toEqual([div(span(), div(div()), span())]); + expect(ReactNoop).toMatchRenderedOutput( +
+ beep +
+
Hello
+
+ bar +
, + ); ReactNoop.render(); expect(Scheduler).toFlushWithoutYielding(); expect(ops).toEqual(['Update Stateful', 'Update Stateful']); - expect(ReactNoop.getChildren()).toEqual([div(span(), div(div()), span())]); + expect(ReactNoop).toMatchRenderedOutput( +
+ foo +
+
Hello
+
+ boop +
, + ); }); it('should not preserve state when switching to a keyed fragment to an array', function () { @@ -713,13 +743,23 @@ describe('ReactFragment', () => { ); expect(ops).toEqual([]); - expect(ReactNoop.getChildren()).toEqual([div(div(), span())]); + expect(ReactNoop).toMatchRenderedOutput( +
+
Hello
+ +
, + ); ReactNoop.render(); expect(Scheduler).toFlushWithoutYielding(); expect(ops).toEqual([]); - expect(ReactNoop.getChildren()).toEqual([div(div(), span())]); + expect(ReactNoop).toMatchRenderedOutput( +
+
Hello
+ +
, + ); }); it('should not preserve state when switching a nested unkeyed fragment to a passthrough component', function () { @@ -762,13 +802,13 @@ describe('ReactFragment', () => { expect(Scheduler).toFlushWithoutYielding(); expect(ops).toEqual([]); - expect(ReactNoop.getChildren()).toEqual([div()]); + expect(ReactNoop).toMatchRenderedOutput(
Hello
); ReactNoop.render(); expect(Scheduler).toFlushWithoutYielding(); expect(ops).toEqual([]); - expect(ReactNoop.getChildren()).toEqual([div()]); + expect(ReactNoop).toMatchRenderedOutput(
Hello
); }); it('should not preserve state when switching a nested keyed fragment to a passthrough component', function () { @@ -811,13 +851,13 @@ describe('ReactFragment', () => { expect(Scheduler).toFlushWithoutYielding(); expect(ops).toEqual([]); - expect(ReactNoop.getChildren()).toEqual([div()]); + expect(ReactNoop).toMatchRenderedOutput(
Hello
); ReactNoop.render(); expect(Scheduler).toFlushWithoutYielding(); expect(ops).toEqual([]); - expect(ReactNoop.getChildren()).toEqual([div()]); + expect(ReactNoop).toMatchRenderedOutput(
Hello
); }); it('should not preserve state when switching a nested keyed array to a passthrough component', function () { @@ -856,13 +896,13 @@ describe('ReactFragment', () => { expect(Scheduler).toFlushWithoutYielding(); expect(ops).toEqual([]); - expect(ReactNoop.getChildren()).toEqual([div()]); + expect(ReactNoop).toMatchRenderedOutput(
Hello
); ReactNoop.render(); expect(Scheduler).toFlushWithoutYielding(); expect(ops).toEqual([]); - expect(ReactNoop.getChildren()).toEqual([div()]); + expect(ReactNoop).toMatchRenderedOutput(
Hello
); }); it('should preserve state when it does not change positions', function () { @@ -904,13 +944,23 @@ describe('ReactFragment', () => { expect(Scheduler).toFlushWithoutYielding(); expect(ops).toEqual(['Update Stateful']); - expect(ReactNoop.getChildren()).toEqual([span(), div()]); + expect(ReactNoop).toMatchRenderedOutput( + <> + +
Hello
+ , + ); ReactNoop.render(); // The key warning gets deduped because it's in the same component. expect(Scheduler).toFlushWithoutYielding(); expect(ops).toEqual(['Update Stateful', 'Update Stateful']); - expect(ReactNoop.getChildren()).toEqual([span(), div()]); + expect(ReactNoop).toMatchRenderedOutput( + <> + +
Hello
+ , + ); }); }); diff --git a/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js b/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js index 417077c8179f5..2455f51965c21 100644 --- a/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js +++ b/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js @@ -113,10 +113,6 @@ describe('ReactHooksWithNoopRenderer', () => { }; }); - function span(prop) { - return {type: 'span', hidden: false, children: [], prop}; - } - function Text(props) { Scheduler.unstable_yieldValue(props.text); return ; @@ -167,7 +163,7 @@ describe('ReactHooksWithNoopRenderer', () => { const counter = React.createRef(null); ReactNoop.render(); expect(Scheduler).toFlushAndYield(['Count: 0']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); + expect(ReactNoop).toMatchRenderedOutput(); // Schedule some updates act(() => { @@ -183,7 +179,7 @@ describe('ReactHooksWithNoopRenderer', () => { // Partially flush without committing expect(Scheduler).toFlushAndYieldThrough(['Count: 11']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); + expect(ReactNoop).toMatchRenderedOutput(); // Interrupt with a high priority update ReactNoop.flushSync(() => { @@ -193,7 +189,7 @@ describe('ReactHooksWithNoopRenderer', () => { // Resume rendering expect(Scheduler).toFlushAndYield(['Total: 11']); - expect(ReactNoop.getChildren()).toEqual([span('Total: 11')]); + expect(ReactNoop).toMatchRenderedOutput(); }); }); @@ -289,15 +285,15 @@ describe('ReactHooksWithNoopRenderer', () => { const counter = React.createRef(null); ReactNoop.render(); expect(Scheduler).toFlushAndYield(['Count: 0']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); + expect(ReactNoop).toMatchRenderedOutput(); act(() => counter.current.updateCount(1)); expect(Scheduler).toHaveYielded(['Count: 1']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]); + expect(ReactNoop).toMatchRenderedOutput(); act(() => counter.current.updateCount(count => count + 10)); expect(Scheduler).toHaveYielded(['Count: 11']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 11')]); + expect(ReactNoop).toMatchRenderedOutput(); }); it('lazy state initializer', () => { @@ -313,11 +309,11 @@ describe('ReactHooksWithNoopRenderer', () => { const counter = React.createRef(null); ReactNoop.render(); expect(Scheduler).toFlushAndYield(['getInitialState', 'Count: 42']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 42')]); + expect(ReactNoop).toMatchRenderedOutput(); act(() => counter.current.updateCount(7)); expect(Scheduler).toHaveYielded(['Count: 7']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 7')]); + expect(ReactNoop).toMatchRenderedOutput(); }); it('multiple states', () => { @@ -331,7 +327,7 @@ describe('ReactHooksWithNoopRenderer', () => { const counter = React.createRef(null); ReactNoop.render(); expect(Scheduler).toFlushAndYield(['Count: 0']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); + expect(ReactNoop).toMatchRenderedOutput(); act(() => counter.current.updateCount(7)); expect(Scheduler).toHaveYielded(['Count: 7']); @@ -349,19 +345,19 @@ describe('ReactHooksWithNoopRenderer', () => { } ReactNoop.render(); expect(Scheduler).toFlushAndYield(['Count: 0']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); + expect(ReactNoop).toMatchRenderedOutput(); const firstUpdater = updater; act(() => firstUpdater(1)); expect(Scheduler).toHaveYielded(['Count: 1']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]); + expect(ReactNoop).toMatchRenderedOutput(); const secondUpdater = updater; act(() => firstUpdater(count => count + 10)); expect(Scheduler).toHaveYielded(['Count: 11']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 11')]); + expect(ReactNoop).toMatchRenderedOutput(); expect(firstUpdater).toBe(secondUpdater); }); @@ -392,15 +388,15 @@ describe('ReactHooksWithNoopRenderer', () => { ReactNoop.render(); expect(Scheduler).toFlushAndYield(['Count: 0']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); + expect(ReactNoop).toMatchRenderedOutput(); ReactNoop.render(); expect(Scheduler).toFlushAndYield([]); - expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); + expect(ReactNoop).toMatchRenderedOutput(); act(() => _updateCount(1)); expect(Scheduler).toHaveYielded(['Count: 1']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]); + expect(ReactNoop).toMatchRenderedOutput(); }); }); @@ -421,27 +417,39 @@ describe('ReactHooksWithNoopRenderer', () => { ReactNoop.render(); expect(Scheduler).toFlushAndYield(['Scrolling down: false']); - expect(ReactNoop.getChildren()).toEqual([span('Scrolling down: false')]); + expect(ReactNoop).toMatchRenderedOutput( + , + ); ReactNoop.render(); expect(Scheduler).toFlushAndYield(['Scrolling down: true']); - expect(ReactNoop.getChildren()).toEqual([span('Scrolling down: true')]); + expect(ReactNoop).toMatchRenderedOutput( + , + ); ReactNoop.render(); expect(Scheduler).toFlushAndYield(['Scrolling down: true']); - expect(ReactNoop.getChildren()).toEqual([span('Scrolling down: true')]); + expect(ReactNoop).toMatchRenderedOutput( + , + ); ReactNoop.render(); expect(Scheduler).toFlushAndYield(['Scrolling down: true']); - expect(ReactNoop.getChildren()).toEqual([span('Scrolling down: true')]); + expect(ReactNoop).toMatchRenderedOutput( + , + ); ReactNoop.render(); expect(Scheduler).toFlushAndYield(['Scrolling down: false']); - expect(ReactNoop.getChildren()).toEqual([span('Scrolling down: false')]); + expect(ReactNoop).toMatchRenderedOutput( + , + ); ReactNoop.render(); expect(Scheduler).toFlushAndYield(['Scrolling down: false']); - expect(ReactNoop.getChildren()).toEqual([span('Scrolling down: false')]); + expect(ReactNoop).toMatchRenderedOutput( + , + ); }); it('warns about render phase update on a different component', async () => { @@ -517,7 +525,7 @@ describe('ReactHooksWithNoopRenderer', () => { 'Render: 3', 3, ]); - expect(ReactNoop.getChildren()).toEqual([span(3)]); + expect(ReactNoop).toMatchRenderedOutput(); }); it('updates multiple times within same render function', () => { @@ -542,7 +550,7 @@ describe('ReactHooksWithNoopRenderer', () => { 'Render: 12', 12, ]); - expect(ReactNoop.getChildren()).toEqual([span(12)]); + expect(ReactNoop).toMatchRenderedOutput(); }); it('throws after too many iterations', () => { @@ -580,7 +588,7 @@ describe('ReactHooksWithNoopRenderer', () => { 'Render: 3', 3, ]); - expect(ReactNoop.getChildren()).toEqual([span(3)]); + expect(ReactNoop).toMatchRenderedOutput(); }); it('uses reducer passed at time of render, not time of dispatch', () => { @@ -632,7 +640,7 @@ describe('ReactHooksWithNoopRenderer', () => { 'Render: 21', 21, ]); - expect(ReactNoop.getChildren()).toEqual([span(21)]); + expect(ReactNoop).toMatchRenderedOutput(); // Test that it works on update, too. This time the log is a bit different // because we started with reducerB instead of reducerA. @@ -648,7 +656,7 @@ describe('ReactHooksWithNoopRenderer', () => { 'Render: 22', 22, ]); - expect(ReactNoop.getChildren()).toEqual([span(22)]); + expect(ReactNoop).toMatchRenderedOutput(); }); it('discards render phase updates if something suspends', async () => { @@ -873,11 +881,11 @@ describe('ReactHooksWithNoopRenderer', () => { const counter = React.createRef(null); ReactNoop.render(); expect(Scheduler).toFlushAndYield(['Count: 0']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); + expect(ReactNoop).toMatchRenderedOutput(); act(() => counter.current.dispatch(INCREMENT)); expect(Scheduler).toHaveYielded(['Count: 1']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]); + expect(ReactNoop).toMatchRenderedOutput(); act(() => { counter.current.dispatch(DECREMENT); counter.current.dispatch(DECREMENT); @@ -885,7 +893,7 @@ describe('ReactHooksWithNoopRenderer', () => { }); expect(Scheduler).toHaveYielded(['Count: -2']); - expect(ReactNoop.getChildren()).toEqual([span('Count: -2')]); + expect(ReactNoop).toMatchRenderedOutput(); }); it('lazy init', () => { @@ -915,11 +923,11 @@ describe('ReactHooksWithNoopRenderer', () => { const counter = React.createRef(null); ReactNoop.render(); expect(Scheduler).toFlushAndYield(['Init', 'Count: 10']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 10')]); + expect(ReactNoop).toMatchRenderedOutput(); act(() => counter.current.dispatch(INCREMENT)); expect(Scheduler).toHaveYielded(['Count: 11']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 11')]); + expect(ReactNoop).toMatchRenderedOutput(); act(() => { counter.current.dispatch(DECREMENT); @@ -928,7 +936,7 @@ describe('ReactHooksWithNoopRenderer', () => { }); expect(Scheduler).toHaveYielded(['Count: 8']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 8')]); + expect(ReactNoop).toMatchRenderedOutput(); }); // Regression test for https://github.com/facebook/react/issues/14360 @@ -950,7 +958,7 @@ describe('ReactHooksWithNoopRenderer', () => { ReactNoop.render(); expect(Scheduler).toFlushAndYield(['Count: 0']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); + expect(ReactNoop).toMatchRenderedOutput(); ReactNoop.batchedUpdates(() => { counter.current.dispatch(INCREMENT); @@ -963,12 +971,12 @@ describe('ReactHooksWithNoopRenderer', () => { }); if (gate(flags => flags.enableUnifiedSyncLane)) { expect(Scheduler).toHaveYielded(['Count: 4']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 4')]); + expect(ReactNoop).toMatchRenderedOutput(); } else { expect(Scheduler).toHaveYielded(['Count: 1']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]); + expect(ReactNoop).toMatchRenderedOutput(); expect(Scheduler).toFlushAndYield(['Count: 4']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 4')]); + expect(ReactNoop).toMatchRenderedOutput(); } }); }); @@ -986,7 +994,7 @@ describe('ReactHooksWithNoopRenderer', () => { Scheduler.unstable_yieldValue('Sync effect'), ); expect(Scheduler).toFlushAndYieldThrough(['Count: 0', 'Sync effect']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); + expect(ReactNoop).toMatchRenderedOutput(); // Effects are deferred until after the commit expect(Scheduler).toFlushAndYield(['Passive effect [0]']); }); @@ -996,7 +1004,7 @@ describe('ReactHooksWithNoopRenderer', () => { Scheduler.unstable_yieldValue('Sync effect'), ); expect(Scheduler).toFlushAndYieldThrough(['Count: 1', 'Sync effect']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]); + expect(ReactNoop).toMatchRenderedOutput(); // Effects are deferred until after the commit expect(Scheduler).toFlushAndYield(['Passive effect [1]']); }); @@ -1023,15 +1031,17 @@ describe('ReactHooksWithNoopRenderer', () => { 'Passive', 'Layout effect', ]); - expect(ReactNoop.getChildren()).toEqual([ - span('Layout'), - span('Passive'), - ]); + expect(ReactNoop).toMatchRenderedOutput( + <> + + + , + ); // Destroying the first child shouldn't prevent the passive effect from // being executed ReactNoop.render([passive]); expect(Scheduler).toFlushAndYield(['Passive effect']); - expect(ReactNoop.getChildren()).toEqual([span('Passive')]); + expect(ReactNoop).toMatchRenderedOutput(); }); // exiting act calls flushPassiveEffects(), but there are none left to flush. expect(Scheduler).toHaveYielded([]); @@ -1069,10 +1079,12 @@ describe('ReactHooksWithNoopRenderer', () => { ]); }); - expect(ReactNoop.getChildren()).toEqual([ - span('Passive'), - span('Layout'), - ]); + expect(ReactNoop).toMatchRenderedOutput( + <> + + + , + ); }); it('flushes passive effects even if siblings schedule a new root', () => { @@ -1099,10 +1111,12 @@ describe('ReactHooksWithNoopRenderer', () => { 'Passive effect', 'New Root', ]); - expect(ReactNoop.getChildren()).toEqual([ - span('Passive'), - span('Layout'), - ]); + expect(ReactNoop).toMatchRenderedOutput( + <> + + + , + ); }); }); @@ -1111,11 +1125,11 @@ describe('ReactHooksWithNoopRenderer', () => { "new ones, if they haven't already fired", () => { function getCommittedText() { - const children = ReactNoop.getChildren(); + const children = ReactNoop.getChildrenAsJSX(); if (children === null) { return null; } - return children[0].prop; + return children.props.prop; } function Counter(props) { @@ -1131,7 +1145,7 @@ describe('ReactHooksWithNoopRenderer', () => { Scheduler.unstable_yieldValue('Sync effect'), ); expect(Scheduler).toFlushAndYieldThrough([0, 'Sync effect']); - expect(ReactNoop.getChildren()).toEqual([span(0)]); + expect(ReactNoop).toMatchRenderedOutput(); // Before the effects have a chance to flush, schedule another update ReactNoop.render(, () => Scheduler.unstable_yieldValue('Sync effect'), @@ -1142,7 +1156,7 @@ describe('ReactHooksWithNoopRenderer', () => { 1, 'Sync effect', ]); - expect(ReactNoop.getChildren()).toEqual([span(1)]); + expect(ReactNoop).toMatchRenderedOutput(); }); expect(Scheduler).toHaveYielded([ @@ -1622,7 +1636,7 @@ describe('ReactHooksWithNoopRenderer', () => { 'Count: (empty)', 'Sync effect', ]); - expect(ReactNoop.getChildren()).toEqual([span('Count: (empty)')]); + expect(ReactNoop).toMatchRenderedOutput(); ReactNoop.flushPassiveEffects(); expect(Scheduler).toHaveYielded(['Schedule update [0]']); expect(Scheduler).toFlushAndYield(['Count: 0']); @@ -1633,7 +1647,7 @@ describe('ReactHooksWithNoopRenderer', () => { Scheduler.unstable_yieldValue('Sync effect'), ); expect(Scheduler).toFlushAndYieldThrough(['Count: 0', 'Sync effect']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); + expect(ReactNoop).toMatchRenderedOutput(); ReactNoop.flushPassiveEffects(); expect(Scheduler).toHaveYielded(['Schedule update [1]']); expect(Scheduler).toFlushAndYield(['Count: 1']); @@ -1657,7 +1671,7 @@ describe('ReactHooksWithNoopRenderer', () => { 'Count: (empty)', 'Sync effect', ]); - expect(ReactNoop.getChildren()).toEqual([span('Count: (empty)')]); + expect(ReactNoop).toMatchRenderedOutput(); // Rendering again should flush the previous commit's effects if (gate(flags => flags.enableSyncDefaultUpdates)) { @@ -1678,7 +1692,7 @@ describe('ReactHooksWithNoopRenderer', () => { ]); if (gate(flags => flags.enableSyncDefaultUpdates)) { - expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); + expect(ReactNoop).toMatchRenderedOutput(); expect(Scheduler).toFlushAndYieldThrough([ 'Count: 0', 'Sync effect', @@ -1686,16 +1700,18 @@ describe('ReactHooksWithNoopRenderer', () => { 'Count: 1', ]); } else { - expect(ReactNoop.getChildren()).toEqual([span('Count: (empty)')]); + expect(ReactNoop).toMatchRenderedOutput( + , + ); expect(Scheduler).toFlushAndYieldThrough(['Sync effect']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); + expect(ReactNoop).toMatchRenderedOutput(); ReactNoop.flushPassiveEffects(); expect(Scheduler).toHaveYielded(['Schedule update [1]']); expect(Scheduler).toFlushAndYield(['Count: 1']); } - expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]); + expect(ReactNoop).toMatchRenderedOutput(); }); }); @@ -1715,7 +1731,7 @@ describe('ReactHooksWithNoopRenderer', () => { Scheduler.unstable_yieldValue('Sync effect'), ); expect(Scheduler).toFlushAndYieldThrough(['Count: 0', 'Sync effect']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); + expect(ReactNoop).toMatchRenderedOutput(); // A flush sync doesn't cause the passive effects to fire. // So we haven't added the other update yet. act(() => { @@ -1737,7 +1753,7 @@ describe('ReactHooksWithNoopRenderer', () => { ]); } - expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]); + expect(ReactNoop).toMatchRenderedOutput(); }); it( @@ -1765,14 +1781,16 @@ describe('ReactHooksWithNoopRenderer', () => { // Even in legacy mode, effects are deferred until after paint expect(Scheduler).toHaveYielded(['Count: (empty)']); - expect(ReactNoop.getChildren()).toEqual([span('Count: (empty)')]); + expect(ReactNoop).toMatchRenderedOutput( + , + ); }); // effects get forced on exiting act() // There were multiple updates, but there should only be a // single render expect(Scheduler).toHaveYielded(['Count: 0']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); + expect(ReactNoop).toMatchRenderedOutput(); }, ); @@ -1784,10 +1802,11 @@ describe('ReactHooksWithNoopRenderer', () => { ReactNoop.flushSync(() => { updateCount(props.count); }); + expect(Scheduler).toHaveYielded([`Schedule update [${props.count}]`]); // This shouldn't flush synchronously. - expect(ReactNoop.getChildren()).not.toEqual([ - span('Count: ' + props.count), - ]); + expect(ReactNoop).not.toMatchRenderedOutput( + , + ); }, [props.count]); return ; } @@ -1800,10 +1819,13 @@ describe('ReactHooksWithNoopRenderer', () => { 'Count: (empty)', 'Sync effect', ]); - expect(ReactNoop.getChildren()).toEqual([span('Count: (empty)')]); + expect(ReactNoop).toMatchRenderedOutput( + , + ); }); }).toErrorDev('flushSync was called from inside a lifecycle method'); - expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); + expect(Scheduler).toHaveYielded([`Count: 0`]); + expect(ReactNoop).toMatchRenderedOutput(); }); it('unmounts previous effect', () => { @@ -1821,7 +1843,7 @@ describe('ReactHooksWithNoopRenderer', () => { Scheduler.unstable_yieldValue('Sync effect'), ); expect(Scheduler).toFlushAndYieldThrough(['Count: 0', 'Sync effect']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); + expect(ReactNoop).toMatchRenderedOutput(); }); expect(Scheduler).toHaveYielded(['Did create [0]']); @@ -1831,7 +1853,7 @@ describe('ReactHooksWithNoopRenderer', () => { Scheduler.unstable_yieldValue('Sync effect'), ); expect(Scheduler).toFlushAndYieldThrough(['Count: 1', 'Sync effect']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]); + expect(ReactNoop).toMatchRenderedOutput(); }); expect(Scheduler).toHaveYielded(['Did destroy [0]', 'Did create [1]']); @@ -1852,14 +1874,14 @@ describe('ReactHooksWithNoopRenderer', () => { Scheduler.unstable_yieldValue('Sync effect'), ); expect(Scheduler).toFlushAndYieldThrough(['Count: 0', 'Sync effect']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); + expect(ReactNoop).toMatchRenderedOutput(); }); expect(Scheduler).toHaveYielded(['Did create [0]']); ReactNoop.render(null); expect(Scheduler).toFlushAndYield(['Did destroy [0]']); - expect(ReactNoop.getChildren()).toEqual([]); + expect(ReactNoop).toMatchRenderedOutput(null); }); it('unmounts on deletion after skipped effect', () => { @@ -1877,7 +1899,7 @@ describe('ReactHooksWithNoopRenderer', () => { Scheduler.unstable_yieldValue('Sync effect'), ); expect(Scheduler).toFlushAndYieldThrough(['Count: 0', 'Sync effect']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); + expect(ReactNoop).toMatchRenderedOutput(); }); expect(Scheduler).toHaveYielded(['Did create [0]']); @@ -1887,14 +1909,14 @@ describe('ReactHooksWithNoopRenderer', () => { Scheduler.unstable_yieldValue('Sync effect'), ); expect(Scheduler).toFlushAndYieldThrough(['Count: 1', 'Sync effect']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]); + expect(ReactNoop).toMatchRenderedOutput(); }); expect(Scheduler).toHaveYielded([]); ReactNoop.render(null); expect(Scheduler).toFlushAndYield(['Did destroy [0]']); - expect(ReactNoop.getChildren()).toEqual([]); + expect(ReactNoop).toMatchRenderedOutput(null); }); it('always fires effects if no dependencies are provided', () => { @@ -1913,7 +1935,7 @@ describe('ReactHooksWithNoopRenderer', () => { Scheduler.unstable_yieldValue('Sync effect'), ); expect(Scheduler).toFlushAndYieldThrough(['Count: 0', 'Sync effect']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); + expect(ReactNoop).toMatchRenderedOutput(); }); expect(Scheduler).toHaveYielded(['Did create']); @@ -1923,14 +1945,14 @@ describe('ReactHooksWithNoopRenderer', () => { Scheduler.unstable_yieldValue('Sync effect'), ); expect(Scheduler).toFlushAndYieldThrough(['Count: 1', 'Sync effect']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]); + expect(ReactNoop).toMatchRenderedOutput(); }); expect(Scheduler).toHaveYielded(['Did destroy', 'Did create']); ReactNoop.render(null); expect(Scheduler).toFlushAndYield(['Did destroy']); - expect(ReactNoop.getChildren()).toEqual([]); + expect(ReactNoop).toMatchRenderedOutput(null); }); it('skips effect if inputs have not changed', () => { @@ -1952,7 +1974,7 @@ describe('ReactHooksWithNoopRenderer', () => { }); expect(Scheduler).toHaveYielded(['Did create [Count: 0]']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); + expect(ReactNoop).toMatchRenderedOutput(); act(() => { ReactNoop.render(, () => @@ -1960,7 +1982,7 @@ describe('ReactHooksWithNoopRenderer', () => { ); // Count changed expect(Scheduler).toFlushAndYieldThrough(['Count: 1', 'Sync effect']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]); + expect(ReactNoop).toMatchRenderedOutput(); }); expect(Scheduler).toHaveYielded([ @@ -1977,7 +1999,7 @@ describe('ReactHooksWithNoopRenderer', () => { }); expect(Scheduler).toHaveYielded([]); - expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]); + expect(ReactNoop).toMatchRenderedOutput(); act(() => { ReactNoop.render(, () => @@ -1985,7 +2007,7 @@ describe('ReactHooksWithNoopRenderer', () => { ); // Label changed expect(Scheduler).toFlushAndYieldThrough(['Total: 1', 'Sync effect']); - expect(ReactNoop.getChildren()).toEqual([span('Total: 1')]); + expect(ReactNoop).toMatchRenderedOutput(); }); expect(Scheduler).toHaveYielded([ @@ -2009,7 +2031,7 @@ describe('ReactHooksWithNoopRenderer', () => { Scheduler.unstable_yieldValue('Sync effect'), ); expect(Scheduler).toFlushAndYieldThrough(['Count: 0', 'Sync effect']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); + expect(ReactNoop).toMatchRenderedOutput(); }); expect(Scheduler).toHaveYielded(['Did commit 1 [0]', 'Did commit 2 [0]']); @@ -2019,7 +2041,7 @@ describe('ReactHooksWithNoopRenderer', () => { Scheduler.unstable_yieldValue('Sync effect'), ); expect(Scheduler).toFlushAndYieldThrough(['Count: 1', 'Sync effect']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]); + expect(ReactNoop).toMatchRenderedOutput(); }); expect(Scheduler).toHaveYielded(['Did commit 1 [1]', 'Did commit 2 [1]']); }); @@ -2045,7 +2067,7 @@ describe('ReactHooksWithNoopRenderer', () => { Scheduler.unstable_yieldValue('Sync effect'), ); expect(Scheduler).toFlushAndYieldThrough(['Count: 0', 'Sync effect']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); + expect(ReactNoop).toMatchRenderedOutput(); }); expect(Scheduler).toHaveYielded(['Mount A [0]', 'Mount B [0]']); @@ -2055,7 +2077,7 @@ describe('ReactHooksWithNoopRenderer', () => { Scheduler.unstable_yieldValue('Sync effect'), ); expect(Scheduler).toFlushAndYieldThrough(['Count: 1', 'Sync effect']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]); + expect(ReactNoop).toMatchRenderedOutput(); }); expect(Scheduler).toHaveYielded([ 'Unmount A [0]', @@ -2084,7 +2106,12 @@ describe('ReactHooksWithNoopRenderer', () => { () => Scheduler.unstable_yieldValue('Sync effect'), ); expect(Scheduler).toFlushAndYieldThrough(['A 0', 'B 0', 'Sync effect']); - expect(ReactNoop.getChildren()).toEqual([span('A 0'), span('B 0')]); + expect(ReactNoop).toMatchRenderedOutput( + <> + + + , + ); }); expect(Scheduler).toHaveYielded(['Mount A [0]', 'Mount B [0]']); @@ -2098,7 +2125,12 @@ describe('ReactHooksWithNoopRenderer', () => { () => Scheduler.unstable_yieldValue('Sync effect'), ); expect(Scheduler).toFlushAndYieldThrough(['A 1', 'B 1', 'Sync effect']); - expect(ReactNoop.getChildren()).toEqual([span('A 1'), span('B 1')]); + expect(ReactNoop).toMatchRenderedOutput( + <> + + + , + ); }); expect(Scheduler).toHaveYielded([ 'Unmount A [0]', @@ -2116,7 +2148,12 @@ describe('ReactHooksWithNoopRenderer', () => { () => Scheduler.unstable_yieldValue('Sync effect'), ); expect(Scheduler).toFlushAndYieldThrough(['B 2', 'C 0', 'Sync effect']); - expect(ReactNoop.getChildren()).toEqual([span('B 2'), span('C 0')]); + expect(ReactNoop).toMatchRenderedOutput( + <> + + + , + ); }); expect(Scheduler).toHaveYielded([ 'Unmount A [1]', @@ -2150,7 +2187,7 @@ describe('ReactHooksWithNoopRenderer', () => { Scheduler.unstable_yieldValue('Sync effect'), ); expect(Scheduler).toFlushAndYieldThrough(['Count: 0', 'Sync effect']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); + expect(ReactNoop).toMatchRenderedOutput(); expect(() => ReactNoop.flushPassiveEffects()).toThrow('Oops'); }); @@ -2161,7 +2198,7 @@ describe('ReactHooksWithNoopRenderer', () => { // never mounted. 'Unmount A [0]', ]); - expect(ReactNoop.getChildren()).toEqual([]); + expect(ReactNoop).toMatchRenderedOutput(null); }); it('handles errors in create on update', () => { @@ -2189,7 +2226,7 @@ describe('ReactHooksWithNoopRenderer', () => { Scheduler.unstable_yieldValue('Sync effect'), ); expect(Scheduler).toFlushAndYieldThrough(['Count: 0', 'Sync effect']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); + expect(ReactNoop).toMatchRenderedOutput(); ReactNoop.flushPassiveEffects(); expect(Scheduler).toHaveYielded(['Mount A [0]', 'Mount B [0]']); }); @@ -2200,7 +2237,7 @@ describe('ReactHooksWithNoopRenderer', () => { Scheduler.unstable_yieldValue('Sync effect'), ); expect(Scheduler).toFlushAndYieldThrough(['Count: 1', 'Sync effect']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]); + expect(ReactNoop).toMatchRenderedOutput(); expect(() => ReactNoop.flushPassiveEffects()).toThrow('Oops'); expect(Scheduler).toHaveYielded([ 'Unmount A [0]', @@ -2208,7 +2245,7 @@ describe('ReactHooksWithNoopRenderer', () => { 'Mount A [1]', 'Oops!', ]); - expect(ReactNoop.getChildren()).toEqual([]); + expect(ReactNoop).toMatchRenderedOutput(null); }); expect(Scheduler).toHaveYielded([ // Clean up effect A runs passively on unmount. @@ -2242,7 +2279,7 @@ describe('ReactHooksWithNoopRenderer', () => { Scheduler.unstable_yieldValue('Sync effect'), ); expect(Scheduler).toFlushAndYieldThrough(['Count: 0', 'Sync effect']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); + expect(ReactNoop).toMatchRenderedOutput(); ReactNoop.flushPassiveEffects(); expect(Scheduler).toHaveYielded(['Mount A [0]', 'Mount B [0]']); }); @@ -2253,7 +2290,7 @@ describe('ReactHooksWithNoopRenderer', () => { Scheduler.unstable_yieldValue('Sync effect'), ); expect(Scheduler).toFlushAndYieldThrough(['Count: 1', 'Sync effect']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]); + expect(ReactNoop).toMatchRenderedOutput(); expect(() => ReactNoop.flushPassiveEffects()).toThrow('Oops'); // This branch enables a feature flag that flushes all passive destroys in a @@ -2272,7 +2309,7 @@ describe('ReactHooksWithNoopRenderer', () => { // The remaining destroy functions are run later on unmount, since they're passive. // In this case, one of them throws again (because of how the test is written). expect(Scheduler).toHaveYielded(['Oops!', 'Unmount B [1]']); - expect(ReactNoop.getChildren()).toEqual([]); + expect(ReactNoop).toMatchRenderedOutput(null); }); it('works with memo', () => { @@ -2293,7 +2330,7 @@ describe('ReactHooksWithNoopRenderer', () => { 'Mount: 0', 'Sync effect', ]); - expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); + expect(ReactNoop).toMatchRenderedOutput(); ReactNoop.render(, () => Scheduler.unstable_yieldValue('Sync effect'), @@ -2304,11 +2341,11 @@ describe('ReactHooksWithNoopRenderer', () => { 'Mount: 1', 'Sync effect', ]); - expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]); + expect(ReactNoop).toMatchRenderedOutput(); ReactNoop.render(null); expect(Scheduler).toFlushAndYieldThrough(['Unmount: 1']); - expect(ReactNoop.getChildren()).toEqual([]); + expect(ReactNoop).toMatchRenderedOutput(null); }); describe('errors thrown in passive destroy function within unmounted trees', () => { @@ -2473,9 +2510,9 @@ describe('ReactHooksWithNoopRenderer', () => { 'ErrorBoundary componentDidCatch', ]); - expect(ReactNoop.getChildren()).toEqual([ - span('ErrorBoundary fallback'), - ]); + expect(ReactNoop).toMatchRenderedOutput( + , + ); }); // @gate skipUnmountedBoundaries @@ -2511,7 +2548,7 @@ describe('ReactHooksWithNoopRenderer', () => { 'BrokenUseEffectCleanup useEffect destroy', ]); - expect(ReactNoop.getChildren()).toEqual([]); + expect(ReactNoop).toMatchRenderedOutput(null); }); }); @@ -3191,12 +3228,12 @@ describe('ReactHooksWithNoopRenderer', () => { it('fires layout effects after the host has been mutated', () => { function getCommittedText() { const yields = Scheduler.unstable_clearYields(); - const children = ReactNoop.getChildren(); + const children = ReactNoop.getChildrenAsJSX(); Scheduler.unstable_yieldValue(yields); if (children === null) { return null; } - return children[0].prop; + return children.props.prop; } function Counter(props) { @@ -3214,7 +3251,7 @@ describe('ReactHooksWithNoopRenderer', () => { 'Current: 0', 'Sync effect', ]); - expect(ReactNoop.getChildren()).toEqual([span(0)]); + expect(ReactNoop).toMatchRenderedOutput(); ReactNoop.render(, () => Scheduler.unstable_yieldValue('Sync effect'), @@ -3224,7 +3261,7 @@ describe('ReactHooksWithNoopRenderer', () => { 'Current: 1', 'Sync effect', ]); - expect(ReactNoop.getChildren()).toEqual([span(1)]); + expect(ReactNoop).toMatchRenderedOutput(); }); it('force flushes passive effects before firing new layout effects', () => { @@ -3341,10 +3378,12 @@ describe('ReactHooksWithNoopRenderer', () => { 'InnerBoundary render success', 'BrokenLayoutEffectDestroy render', ]); - expect(ReactNoop.getChildren()).toEqual([ - span('sibling'), - span('broken'), - ]); + expect(ReactNoop).toMatchRenderedOutput( + <> + + + , + ); ReactNoop.render( @@ -3361,7 +3400,7 @@ describe('ReactHooksWithNoopRenderer', () => { 'OuterBoundary render error', 'Component render OuterFallback', ]); - expect(ReactNoop.getChildren()).toEqual([span('OuterFallback')]); + expect(ReactNoop).toMatchRenderedOutput(); }); it('assumes layout effect destroy function is either a function or undefined', () => { @@ -3441,10 +3480,12 @@ describe('ReactHooksWithNoopRenderer', () => { const button = React.createRef(null); ReactNoop.render(); expect(Scheduler).toFlushAndYield(['Increment', 'Count: 0']); - expect(ReactNoop.getChildren()).toEqual([ - span('Increment'), - span('Count: 0'), - ]); + expect(ReactNoop).toMatchRenderedOutput( + <> + + + , + ); act(button.current.increment); expect(Scheduler).toHaveYielded([ @@ -3452,10 +3493,12 @@ describe('ReactHooksWithNoopRenderer', () => { // 'Increment', 'Count: 1', ]); - expect(ReactNoop.getChildren()).toEqual([ - span('Increment'), - span('Count: 1'), - ]); + expect(ReactNoop).toMatchRenderedOutput( + <> + + + , + ); // Increase the increment amount ReactNoop.render(); @@ -3464,18 +3507,22 @@ describe('ReactHooksWithNoopRenderer', () => { 'Increment', 'Count: 1', ]); - expect(ReactNoop.getChildren()).toEqual([ - span('Increment'), - span('Count: 1'), - ]); + expect(ReactNoop).toMatchRenderedOutput( + <> + + + , + ); // Callback should have updated act(button.current.increment); expect(Scheduler).toHaveYielded(['Count: 11']); - expect(ReactNoop.getChildren()).toEqual([ - span('Increment'), - span('Count: 11'), - ]); + expect(ReactNoop).toMatchRenderedOutput( + <> + + + , + ); }); }); @@ -3492,19 +3539,19 @@ describe('ReactHooksWithNoopRenderer', () => { ReactNoop.render(); expect(Scheduler).toFlushAndYield(["Capitalize 'hello'", 'HELLO']); - expect(ReactNoop.getChildren()).toEqual([span('HELLO')]); + expect(ReactNoop).toMatchRenderedOutput(); ReactNoop.render(); expect(Scheduler).toFlushAndYield(["Capitalize 'hi'", 'HI']); - expect(ReactNoop.getChildren()).toEqual([span('HI')]); + expect(ReactNoop).toMatchRenderedOutput(); ReactNoop.render(); expect(Scheduler).toFlushAndYield(['HI']); - expect(ReactNoop.getChildren()).toEqual([span('HI')]); + expect(ReactNoop).toMatchRenderedOutput(); ReactNoop.render(); expect(Scheduler).toFlushAndYield(["Capitalize 'goodbye'", 'GOODBYE']); - expect(ReactNoop.getChildren()).toEqual([span('GOODBYE')]); + expect(ReactNoop).toMatchRenderedOutput(); }); it('always re-computes if no inputs are provided', () => { @@ -3583,14 +3630,14 @@ describe('ReactHooksWithNoopRenderer', () => { const counter = React.createRef(null); ReactNoop.render(); expect(Scheduler).toFlushAndYield(['Count: 0']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); + expect(ReactNoop).toMatchRenderedOutput(); expect(counter.current.count).toBe(0); act(() => { counter.current.dispatch(INCREMENT); }); expect(Scheduler).toHaveYielded(['Count: 1']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]); + expect(ReactNoop).toMatchRenderedOutput(); // Intentionally not updated because of [] deps: expect(counter.current.count).toBe(0); }); @@ -3613,14 +3660,14 @@ describe('ReactHooksWithNoopRenderer', () => { const counter = React.createRef(null); ReactNoop.render(); expect(Scheduler).toFlushAndYield(['Count: 0']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); + expect(ReactNoop).toMatchRenderedOutput(); expect(counter.current.count).toBe(0); act(() => { counter.current.dispatch(INCREMENT); }); expect(Scheduler).toHaveYielded(['Count: 1']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]); + expect(ReactNoop).toMatchRenderedOutput(); expect(counter.current.count).toBe(1); }); @@ -3649,7 +3696,7 @@ describe('ReactHooksWithNoopRenderer', () => { const counter = React.createRef(null); ReactNoop.render(); expect(Scheduler).toFlushAndYield(['Count: 0']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); + expect(ReactNoop).toMatchRenderedOutput(); expect(counter.current.count).toBe(0); expect(totalRefUpdates).toBe(1); @@ -3657,14 +3704,14 @@ describe('ReactHooksWithNoopRenderer', () => { counter.current.dispatch(INCREMENT); }); expect(Scheduler).toHaveYielded(['Count: 1']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]); + expect(ReactNoop).toMatchRenderedOutput(); expect(counter.current.count).toBe(1); expect(totalRefUpdates).toBe(2); // Update that doesn't change the ref dependencies ReactNoop.render(); expect(Scheduler).toFlushAndYield(['Count: 1']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]); + expect(ReactNoop).toMatchRenderedOutput(); expect(counter.current.count).toBe(1); expect(totalRefUpdates).toBe(2); // Should not increase since last time }); @@ -3694,9 +3741,9 @@ describe('ReactHooksWithNoopRenderer', () => { } ReactNoop.render(); expect(Scheduler).toFlushAndYield(['Before... Pending: false']); - expect(ReactNoop.getChildren()).toEqual([ - span('Before... Pending: false'), - ]); + expect(ReactNoop).toMatchRenderedOutput( + , + ); await act(async () => { transition(); @@ -3706,27 +3753,27 @@ describe('ReactHooksWithNoopRenderer', () => { 'Suspend! [After... Pending: false]', 'Loading... Pending: false', ]); - expect(ReactNoop.getChildren()).toEqual([ - span('Before... Pending: true'), - ]); + expect(ReactNoop).toMatchRenderedOutput( + , + ); Scheduler.unstable_advanceTime(500); await advanceTimers(500); // Even after a long amount of time, we still don't show a placeholder. Scheduler.unstable_advanceTime(100000); await advanceTimers(100000); - expect(ReactNoop.getChildren()).toEqual([ - span('Before... Pending: true'), - ]); + expect(ReactNoop).toMatchRenderedOutput( + , + ); await resolveText('After... Pending: false'); expect(Scheduler).toHaveYielded([ 'Promise resolved [After... Pending: false]', ]); expect(Scheduler).toFlushAndYield(['After... Pending: false']); - expect(ReactNoop.getChildren()).toEqual([ - span('After... Pending: false'), - ]); + expect(ReactNoop).toMatchRenderedOutput( + , + ); }); }); }); @@ -3759,12 +3806,22 @@ describe('ReactHooksWithNoopRenderer', () => { }); expect(Scheduler).toHaveYielded(['A', 'Suspend! [A]', 'Loading']); - expect(ReactNoop.getChildren()).toEqual([span('A'), span('Loading')]); + expect(ReactNoop).toMatchRenderedOutput( + <> + + + , + ); await resolveText('A'); expect(Scheduler).toHaveYielded(['Promise resolved [A]']); expect(Scheduler).toFlushAndYield(['A']); - expect(ReactNoop.getChildren()).toEqual([span('A'), span('A')]); + expect(ReactNoop).toMatchRenderedOutput( + <> + + + , + ); await act(async () => { _setText('B'); @@ -3776,7 +3833,12 @@ describe('ReactHooksWithNoopRenderer', () => { 'Loading', ]); expect(Scheduler).toFlushAndYield([]); - expect(ReactNoop.getChildren()).toEqual([span('B'), span('A')]); + expect(ReactNoop).toMatchRenderedOutput( + <> + + + , + ); }); await act(async () => { @@ -3784,19 +3846,34 @@ describe('ReactHooksWithNoopRenderer', () => { await advanceTimers(250); }); expect(Scheduler).toHaveYielded([]); - expect(ReactNoop.getChildren()).toEqual([span('B'), span('A')]); + expect(ReactNoop).toMatchRenderedOutput( + <> + + + , + ); // Even after a long amount of time, we don't show a fallback Scheduler.unstable_advanceTime(100000); await advanceTimers(100000); expect(Scheduler).toFlushAndYield([]); - expect(ReactNoop.getChildren()).toEqual([span('B'), span('A')]); + expect(ReactNoop).toMatchRenderedOutput( + <> + + + , + ); await act(async () => { await resolveText('B'); }); expect(Scheduler).toHaveYielded(['Promise resolved [B]', 'B', 'B']); - expect(ReactNoop.getChildren()).toEqual([span('B'), span('B')]); + expect(ReactNoop).toMatchRenderedOutput( + <> + + + , + ); }); }); @@ -3824,9 +3901,9 @@ describe('ReactHooksWithNoopRenderer', () => { ReactNoop.render(); expect(Scheduler).toFlushAndYield(['A: 0, B: 0, C: [not loaded]']); - expect(ReactNoop.getChildren()).toEqual([ - span('A: 0, B: 0, C: [not loaded]'), - ]); + expect(ReactNoop).toMatchRenderedOutput( + , + ); act(() => { updateA(2); @@ -3834,9 +3911,9 @@ describe('ReactHooksWithNoopRenderer', () => { }); expect(Scheduler).toHaveYielded(['A: 2, B: 3, C: [not loaded]']); - expect(ReactNoop.getChildren()).toEqual([ - span('A: 2, B: 3, C: [not loaded]'), - ]); + expect(ReactNoop).toMatchRenderedOutput( + , + ); ReactNoop.render(); expect(() => { @@ -3856,11 +3933,11 @@ describe('ReactHooksWithNoopRenderer', () => { ]); // Uncomment if/when we support this again - // expect(ReactNoop.getChildren()).toEqual([span('A: 2, B: 3, C: 0')]); + // expect(ReactNoop).toMatchRenderedOutput(]); // updateC(4); // expect(Scheduler).toFlushAndYield(['A: 2, B: 3, C: 4']); - // expect(ReactNoop.getChildren()).toEqual([span('A: 2, B: 3, C: 4')]); + // expect(ReactNoop).toMatchRenderedOutput(]); }); it('unmount state', () => { @@ -3888,14 +3965,14 @@ describe('ReactHooksWithNoopRenderer', () => { ReactNoop.render(); expect(Scheduler).toFlushAndYield(['A: 0, B: 0, C: 0']); - expect(ReactNoop.getChildren()).toEqual([span('A: 0, B: 0, C: 0')]); + expect(ReactNoop).toMatchRenderedOutput(); act(() => { updateA(2); updateB(3); updateC(4); }); expect(Scheduler).toHaveYielded(['A: 2, B: 3, C: 4']); - expect(ReactNoop.getChildren()).toEqual([span('A: 2, B: 3, C: 4')]); + expect(ReactNoop).toMatchRenderedOutput(); ReactNoop.render(); expect(Scheduler).toFlushAndThrow( 'Rendered fewer hooks than expected. This may be caused by an ' + diff --git a/packages/react-reconciler/src/__tests__/ReactIncrementalErrorHandling-test.internal.js b/packages/react-reconciler/src/__tests__/ReactIncrementalErrorHandling-test.internal.js index 80f1779e119a4..37869121ba17c 100644 --- a/packages/react-reconciler/src/__tests__/ReactIncrementalErrorHandling-test.internal.js +++ b/packages/react-reconciler/src/__tests__/ReactIncrementalErrorHandling-test.internal.js @@ -29,15 +29,6 @@ describe('ReactIncrementalErrorHandling', () => { act = require('jest-react').act; }); - function div(...children) { - children = children.map(c => (typeof c === 'string' ? {text: c} : c)); - return {type: 'div', children, prop: undefined, hidden: false}; - } - - function span(prop) { - return {type: 'span', children: [], prop, hidden: false}; - } - function normalizeCodeLocInfo(str) { return ( str && @@ -149,7 +140,7 @@ describe('ReactIncrementalErrorHandling', () => { // Since the error was thrown during an async render, React won't commit // the result yet. - expect(ReactNoop.getChildren()).toEqual([]); + expect(ReactNoop).toMatchRenderedOutput(null); // Instead, it will try rendering one more time, synchronously, in case that // happens to fix the error. @@ -169,7 +160,9 @@ describe('ReactIncrementalErrorHandling', () => { 'ErrorMessage', ]); - expect(ReactNoop.getChildren()).toEqual([span('Caught an error: oops!')]); + expect(ReactNoop).toMatchRenderedOutput( + , + ); }); it('recovers from errors asynchronously (legacy, no getDerivedStateFromError)', () => { @@ -267,7 +260,9 @@ describe('ReactIncrementalErrorHandling', () => { 'ErrorBoundary (catch)', 'ErrorMessage', ]); - expect(ReactNoop.getChildren()).toEqual([span('Caught an error: oops!')]); + expect(ReactNoop).toMatchRenderedOutput( + , + ); }); it("retries at a lower priority if there's additional pending work", async () => { @@ -307,7 +302,9 @@ describe('ReactIncrementalErrorHandling', () => { 'commit', 'commit', ]); - expect(ReactNoop.getChildren()).toEqual([span('Everything is fine.')]); + expect(ReactNoop).toMatchRenderedOutput( + , + ); }); // @gate www @@ -424,7 +421,7 @@ describe('ReactIncrementalErrorHandling', () => { 'Sibling', 'commit', ]); - expect(ReactNoop.getChildren()).toEqual([]); + expect(ReactNoop).toMatchRenderedOutput(null); }); it('retries one more time if an error occurs during a render that expires midway through the tree', async () => { @@ -484,7 +481,7 @@ describe('ReactIncrementalErrorHandling', () => { 'C', 'D', ]); - expect(ReactNoop.getChildren()).toEqual([]); + expect(ReactNoop).toMatchRenderedOutput(null); }); it('calls componentDidCatch multiple times for multiple errors', () => { @@ -531,7 +528,9 @@ describe('ReactIncrementalErrorHandling', () => { 'componentDidCatch: Error 2', 'componentDidCatch: Error 3', ]); - expect(ReactNoop.getChildren()).toEqual([span('Number of errors: 3')]); + expect(ReactNoop).toMatchRenderedOutput( + , + ); }); it('catches render error in a boundary during full deferred mounting', () => { @@ -560,7 +559,9 @@ describe('ReactIncrementalErrorHandling', () => { , ); expect(Scheduler).toFlushWithoutYielding(); - expect(ReactNoop.getChildren()).toEqual([span('Caught an error: Hello.')]); + expect(ReactNoop).toMatchRenderedOutput( + , + ); }); it('catches render error in a boundary during partial deferred mounting', () => { @@ -604,7 +605,7 @@ describe('ReactIncrementalErrorHandling', () => { } expect(Scheduler).toFlushAndYieldThrough(['ErrorBoundary render success']); - expect(ReactNoop.getChildren()).toEqual([]); + expect(ReactNoop).toMatchRenderedOutput(null); expect(Scheduler).toFlushAndYield([ 'BrokenRender', @@ -616,7 +617,9 @@ describe('ReactIncrementalErrorHandling', () => { 'ErrorBoundary componentDidCatch', 'ErrorBoundary render error', ]); - expect(ReactNoop.getChildren()).toEqual([span('Caught an error: Hello.')]); + expect(ReactNoop).toMatchRenderedOutput( + , + ); }); it('catches render error in a boundary during synchronous mounting', () => { @@ -663,7 +666,9 @@ describe('ReactIncrementalErrorHandling', () => { 'ErrorBoundary componentDidCatch', 'ErrorBoundary render error', ]); - expect(ReactNoop.getChildren()).toEqual([span('Caught an error: Hello.')]); + expect(ReactNoop).toMatchRenderedOutput( + , + ); }); it('catches render error in a boundary during batched mounting', () => { @@ -711,7 +716,9 @@ describe('ReactIncrementalErrorHandling', () => { 'ErrorBoundary componentDidCatch', 'ErrorBoundary render error', ]); - expect(ReactNoop.getChildren()).toEqual([span('Caught an error: Hello.')]); + expect(ReactNoop).toMatchRenderedOutput( + , + ); }); it('propagates an error from a noop error boundary during full deferred mounting', () => { @@ -750,7 +757,7 @@ describe('ReactIncrementalErrorHandling', () => { 'RethrowErrorBoundary componentDidCatch', ]); }).toThrow('Hello'); - expect(ReactNoop.getChildren()).toEqual([]); + expect(ReactNoop.getChildrenAsJSX()).toEqual(null); }); it('propagates an error from a noop error boundary during partial deferred mounting', () => { @@ -801,7 +808,7 @@ describe('ReactIncrementalErrorHandling', () => { // Errored again on retry. Now handle it. 'RethrowErrorBoundary componentDidCatch', ]); - expect(ReactNoop.getChildren()).toEqual([]); + expect(ReactNoop).toMatchRenderedOutput(null); }); it('propagates an error from a noop error boundary during synchronous mounting', () => { @@ -841,7 +848,7 @@ describe('ReactIncrementalErrorHandling', () => { // Errored again on retry. Now handle it. 'RethrowErrorBoundary componentDidCatch', ]); - expect(ReactNoop.getChildren()).toEqual([]); + expect(ReactNoop).toMatchRenderedOutput(null); }); it('propagates an error from a noop error boundary during batched mounting', () => { @@ -884,7 +891,7 @@ describe('ReactIncrementalErrorHandling', () => { // Errored again on retry. Now handle it. 'RethrowErrorBoundary componentDidCatch', ]); - expect(ReactNoop.getChildren()).toEqual([]); + expect(ReactNoop).toMatchRenderedOutput(null); }); it('applies batched updates regardless despite errors in scheduling', () => { @@ -897,7 +904,7 @@ describe('ReactIncrementalErrorHandling', () => { }); }).toThrow('Hello'); expect(Scheduler).toFlushWithoutYielding(); - expect(ReactNoop.getChildren()).toEqual([span('a:3')]); + expect(ReactNoop).toMatchRenderedOutput(); }); it('applies nested batched updates despite errors in scheduling', () => { @@ -914,7 +921,7 @@ describe('ReactIncrementalErrorHandling', () => { }); }).toThrow('Hello'); expect(Scheduler).toFlushWithoutYielding(); - expect(ReactNoop.getChildren()).toEqual([span('a:5')]); + expect(ReactNoop).toMatchRenderedOutput(); }); // TODO: Is this a breaking change? @@ -930,7 +937,7 @@ describe('ReactIncrementalErrorHandling', () => { }); }).toThrow('Hello'); Scheduler.unstable_flushAll(); - expect(ReactNoop.getChildren()).toEqual([span('a:3')]); + expect(ReactNoop).toMatchRenderedOutput(); }); it('can schedule updates after uncaught error in render on mount', () => { @@ -1127,11 +1134,11 @@ describe('ReactIncrementalErrorHandling', () => { ); ReactNoop.renderToRootWithID(, 'b'); expect(Scheduler).toFlushWithoutYielding(); - expect(ReactNoop.getChildren('a')).toEqual([ - span('Caught an error: Hello.'), - ]); + expect(ReactNoop.getChildrenAsJSX('a')).toEqual( + , + ); expect(Scheduler).toFlushWithoutYielding(); - expect(ReactNoop.getChildren('b')).toEqual([span('b:1')]); + expect(ReactNoop.getChildrenAsJSX('b')).toEqual(); }); it('continues work on other roots despite uncaught errors', () => { @@ -1143,7 +1150,7 @@ describe('ReactIncrementalErrorHandling', () => { expect(() => { expect(Scheduler).toFlushWithoutYielding(); }).toThrow('a'); - expect(ReactNoop.getChildren('a')).toEqual([]); + expect(ReactNoop.getChildrenAsJSX('a')).toEqual(null); ReactNoop.renderToRootWithID(, 'a'); ReactNoop.renderToRootWithID(, 'b'); @@ -1152,16 +1159,16 @@ describe('ReactIncrementalErrorHandling', () => { }).toThrow('a'); expect(Scheduler).toFlushWithoutYielding(); - expect(ReactNoop.getChildren('a')).toEqual([]); - expect(ReactNoop.getChildren('b')).toEqual([span('b:2')]); + expect(ReactNoop.getChildrenAsJSX('a')).toEqual(null); + expect(ReactNoop.getChildrenAsJSX('b')).toEqual(); ReactNoop.renderToRootWithID(, 'a'); ReactNoop.renderToRootWithID(, 'b'); expect(() => { expect(Scheduler).toFlushWithoutYielding(); }).toThrow('b'); - expect(ReactNoop.getChildren('a')).toEqual([span('a:3')]); - expect(ReactNoop.getChildren('b')).toEqual([]); + expect(ReactNoop.getChildrenAsJSX('a')).toEqual(); + expect(ReactNoop.getChildrenAsJSX('b')).toEqual(null); ReactNoop.renderToRootWithID(, 'a'); ReactNoop.renderToRootWithID(, 'b'); @@ -1170,9 +1177,9 @@ describe('ReactIncrementalErrorHandling', () => { expect(Scheduler).toFlushWithoutYielding(); }).toThrow('b'); expect(Scheduler).toFlushWithoutYielding(); - expect(ReactNoop.getChildren('a')).toEqual([span('a:4')]); - expect(ReactNoop.getChildren('b')).toEqual([]); - expect(ReactNoop.getChildren('c')).toEqual([span('c:4')]); + expect(ReactNoop.getChildrenAsJSX('a')).toEqual(); + expect(ReactNoop.getChildrenAsJSX('b')).toEqual(null); + expect(ReactNoop.getChildrenAsJSX('c')).toEqual(); ReactNoop.renderToRootWithID(, 'a'); ReactNoop.renderToRootWithID(, 'b'); @@ -1183,11 +1190,11 @@ describe('ReactIncrementalErrorHandling', () => { expect(Scheduler).toFlushWithoutYielding(); }).toThrow('e'); expect(Scheduler).toFlushWithoutYielding(); - expect(ReactNoop.getChildren('a')).toEqual([span('a:5')]); - expect(ReactNoop.getChildren('b')).toEqual([span('b:5')]); - expect(ReactNoop.getChildren('c')).toEqual([span('c:5')]); - expect(ReactNoop.getChildren('d')).toEqual([span('d:5')]); - expect(ReactNoop.getChildren('e')).toEqual([]); + expect(ReactNoop.getChildrenAsJSX('a')).toEqual(); + expect(ReactNoop.getChildrenAsJSX('b')).toEqual(); + expect(ReactNoop.getChildrenAsJSX('c')).toEqual(); + expect(ReactNoop.getChildrenAsJSX('d')).toEqual(); + expect(ReactNoop.getChildrenAsJSX('e')).toEqual(null); ReactNoop.renderToRootWithID(, 'a'); ReactNoop.renderToRootWithID(, 'b'); @@ -1207,12 +1214,12 @@ describe('ReactIncrementalErrorHandling', () => { }).toThrow('e'); expect(Scheduler).toFlushWithoutYielding(); - expect(ReactNoop.getChildren('a')).toEqual([]); - expect(ReactNoop.getChildren('b')).toEqual([span('b:6')]); - expect(ReactNoop.getChildren('c')).toEqual([]); - expect(ReactNoop.getChildren('d')).toEqual([span('d:6')]); - expect(ReactNoop.getChildren('e')).toEqual([]); - expect(ReactNoop.getChildren('f')).toEqual([span('f:6')]); + expect(ReactNoop.getChildrenAsJSX('a')).toEqual(null); + expect(ReactNoop.getChildrenAsJSX('b')).toEqual(); + expect(ReactNoop.getChildrenAsJSX('c')).toEqual(null); + expect(ReactNoop.getChildrenAsJSX('d')).toEqual(); + expect(ReactNoop.getChildrenAsJSX('e')).toEqual(null); + expect(ReactNoop.getChildrenAsJSX('f')).toEqual(); ReactNoop.unmountRootWithID('a'); ReactNoop.unmountRootWithID('b'); @@ -1221,12 +1228,12 @@ describe('ReactIncrementalErrorHandling', () => { ReactNoop.unmountRootWithID('e'); ReactNoop.unmountRootWithID('f'); expect(Scheduler).toFlushWithoutYielding(); - expect(ReactNoop.getChildren('a')).toEqual(null); - expect(ReactNoop.getChildren('b')).toEqual(null); - expect(ReactNoop.getChildren('c')).toEqual(null); - expect(ReactNoop.getChildren('d')).toEqual(null); - expect(ReactNoop.getChildren('e')).toEqual(null); - expect(ReactNoop.getChildren('f')).toEqual(null); + expect(ReactNoop.getChildrenAsJSX('a')).toEqual(null); + expect(ReactNoop.getChildrenAsJSX('b')).toEqual(null); + expect(ReactNoop.getChildrenAsJSX('c')).toEqual(null); + expect(ReactNoop.getChildrenAsJSX('d')).toEqual(null); + expect(ReactNoop.getChildrenAsJSX('e')).toEqual(null); + expect(ReactNoop.getChildrenAsJSX('f')).toEqual(null); }); it('unwinds the context stack correctly on error', () => { @@ -1284,7 +1291,7 @@ describe('ReactIncrementalErrorHandling', () => { expect(Scheduler).toFlushWithoutYielding(); // If the context stack does not unwind, span will get 'abcde' - expect(ReactNoop.getChildren()).toEqual([span('a')]); + expect(ReactNoop).toMatchRenderedOutput(); }); it('catches reconciler errors in a boundary during mounting', () => { @@ -1315,17 +1322,19 @@ describe('ReactIncrementalErrorHandling', () => { // React retries once on error 'Warning: React.createElement: type is invalid -- expected a string', ]); - expect(ReactNoop.getChildren()).toEqual([ - span( - 'Element type is invalid: expected a string (for built-in components) or ' + + expect(ReactNoop).toMatchRenderedOutput( + , + ); }); it('catches reconciler errors in a boundary during update', () => { @@ -1364,17 +1373,19 @@ describe('ReactIncrementalErrorHandling', () => { // React retries once on error 'Warning: React.createElement: type is invalid -- expected a string', ]); - expect(ReactNoop.getChildren()).toEqual([ - span( - 'Element type is invalid: expected a string (for built-in components) or ' + + expect(ReactNoop).toMatchRenderedOutput( + , + ); }); it('recovers from uncaught reconciler errors', () => { @@ -1394,7 +1405,7 @@ describe('ReactIncrementalErrorHandling', () => { ReactNoop.render(); expect(Scheduler).toFlushWithoutYielding(); - expect(ReactNoop.getChildren()).toEqual([span('hi')]); + expect(ReactNoop).toMatchRenderedOutput(); }); it('unmounts components with uncaught errors', () => { @@ -1447,7 +1458,7 @@ describe('ReactIncrementalErrorHandling', () => { 'Parent componentWillUnmount [!]', 'BrokenRenderAndUnmount componentWillUnmount', ]); - expect(ReactNoop.getChildren()).toEqual([]); + expect(ReactNoop).toMatchRenderedOutput(null); expect(() => { ReactNoop.flushSync(); @@ -1478,7 +1489,11 @@ describe('ReactIncrementalErrorHandling', () => { ReactNoop.render(); expect(Scheduler).toFlushAndYield(['barRef attach']); - expect(ReactNoop.getChildren()).toEqual([div(span('Bar'))]); + expect(ReactNoop).toMatchRenderedOutput( +
+ +
, + ); // Unmount ReactNoop.render(); @@ -1489,7 +1504,7 @@ describe('ReactIncrementalErrorHandling', () => { 'Bar unmount', ]); // Because there was an error, entire tree should unmount - expect(ReactNoop.getChildren()).toEqual([]); + expect(ReactNoop).toMatchRenderedOutput(null); }); it('handles error thrown by host config while working on failed root', () => { @@ -1562,7 +1577,9 @@ describe('ReactIncrementalErrorHandling', () => { 'componentDidCatch', 'ErrorBoundary (catch)', ]); - expect(ReactNoop.getChildren()).toEqual([span('Caught an error: oops')]); + expect(ReactNoop).toMatchRenderedOutput( + , + ); if (__DEV__) { expect(console.error).toHaveBeenCalledTimes(1); @@ -1635,7 +1652,9 @@ describe('ReactIncrementalErrorHandling', () => { 'ErrorBoundary (catch)', 'ErrorMessage', ]); - expect(ReactNoop.getChildren()).toEqual([span('Caught an error: oops!')]); + expect(ReactNoop).toMatchRenderedOutput( + , + ); }); it('calls the correct lifecycles on the error boundary after catching an error (mixed)', () => { @@ -1676,7 +1695,9 @@ describe('ReactIncrementalErrorHandling', () => { 'render error message', 'did update', ]); - expect(ReactNoop.getChildren()).toEqual([span('Caught an error: oops!')]); + expect(ReactNoop).toMatchRenderedOutput( + , + ); }); it('provides component stack to the error boundary with componentDidCatch', () => { @@ -1710,13 +1731,15 @@ describe('ReactIncrementalErrorHandling', () => { , ); expect(Scheduler).toFlushAndYield(['render error message']); - expect(ReactNoop.getChildren()).toEqual([ - span( - 'Caught an error:\n' + + expect(ReactNoop).toMatchRenderedOutput( + , + ); }); it('does not provide component stack to the error boundary with getDerivedStateFromError', () => { @@ -1744,7 +1767,9 @@ describe('ReactIncrementalErrorHandling', () => { , ); expect(Scheduler).toFlushWithoutYielding(); - expect(ReactNoop.getChildren()).toEqual([span('Caught an error: Hello')]); + expect(ReactNoop).toMatchRenderedOutput( + , + ); }); it('provides component stack even if overriding prepareStackTrace', () => { @@ -1794,13 +1819,15 @@ describe('ReactIncrementalErrorHandling', () => { expect(Scheduler).toFlushAndYield(['render error message']); Error.prepareStackTrace = undefined; - expect(ReactNoop.getChildren()).toEqual([ - span( - 'Caught an error:\n' + + expect(ReactNoop).toMatchRenderedOutput( + , + ); }); if (!ReactFeatureFlags.disableModulePatternComponents) { diff --git a/packages/react-reconciler/src/__tests__/ReactIncrementalScheduling-test.js b/packages/react-reconciler/src/__tests__/ReactIncrementalScheduling-test.js index 247ef5f406823..3cc006dad1c52 100644 --- a/packages/react-reconciler/src/__tests__/ReactIncrementalScheduling-test.js +++ b/packages/react-reconciler/src/__tests__/ReactIncrementalScheduling-test.js @@ -25,13 +25,9 @@ describe('ReactIncrementalScheduling', () => { act = require('jest-react').act; }); - function span(prop) { - return {type: 'span', children: [], prop, hidden: false}; - } - it('schedules and flushes deferred work', () => { ReactNoop.render(); - expect(ReactNoop.getChildren()).toEqual([]); + expect(ReactNoop).toMatchRenderedOutput(null); expect(Scheduler).toFlushWithoutYielding(); expect(ReactNoop).toMatchRenderedOutput(); @@ -44,9 +40,9 @@ describe('ReactIncrementalScheduling', () => { expect(Scheduler).toFlushWithoutYielding(); - expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]); - expect(ReactNoop.getChildren('b')).toEqual([span('b:1')]); - expect(ReactNoop.getChildren('c')).toEqual([span('c:1')]); + expect(ReactNoop.getChildrenAsJSX('a')).toEqual(); + expect(ReactNoop.getChildrenAsJSX('b')).toEqual(); + expect(ReactNoop.getChildrenAsJSX('c')).toEqual(); }); it('schedules top-level updates in order of priority', () => { diff --git a/packages/react-reconciler/src/__tests__/ReactIncrementalSideEffects-test.js b/packages/react-reconciler/src/__tests__/ReactIncrementalSideEffects-test.js index bb5f2e597007e..3e4cd2e4f55b5 100644 --- a/packages/react-reconciler/src/__tests__/ReactIncrementalSideEffects-test.js +++ b/packages/react-reconciler/src/__tests__/ReactIncrementalSideEffects-test.js @@ -23,21 +23,6 @@ describe('ReactIncrementalSideEffects', () => { Scheduler = require('scheduler'); }); - function div(...children) { - children = children.map(c => - typeof c === 'string' ? {text: c, hidden: false} : c, - ); - return {type: 'div', children, prop: undefined, hidden: false}; - } - - function span(prop) { - return {type: 'span', children: [], prop, hidden: false}; - } - - function text(t) { - return {text: t, hidden: false}; - } - // Note: This is based on a similar component we use in www. We can delete // once the extra div wrapper is no longer necessary. function LegacyHiddenDiv({children, mode}) { @@ -67,11 +52,20 @@ describe('ReactIncrementalSideEffects', () => { ReactNoop.render(); expect(Scheduler).toFlushWithoutYielding(); - expect(ReactNoop.getChildren()).toEqual([div(span())]); + expect(ReactNoop).toMatchRenderedOutput( +
+ Hello +
, + ); ReactNoop.render(); expect(Scheduler).toFlushWithoutYielding(); - expect(ReactNoop.getChildren()).toEqual([div(span(), span())]); + expect(ReactNoop).toMatchRenderedOutput( +
+ World + World +
, + ); }); it('can update child nodes of a fragment', function () { @@ -95,19 +89,34 @@ describe('ReactIncrementalSideEffects', () => { ReactNoop.render(); expect(Scheduler).toFlushWithoutYielding(); - expect(ReactNoop.getChildren()).toEqual([div(span(), span('test'))]); + expect(ReactNoop).toMatchRenderedOutput( +
+ Hello + +
, + ); ReactNoop.render(); expect(Scheduler).toFlushWithoutYielding(); - expect(ReactNoop.getChildren()).toEqual([ - div(span(), span(), div(), span('test')), - ]); + expect(ReactNoop).toMatchRenderedOutput( +
+ World + World +
+ +
, + ); ReactNoop.render(); expect(Scheduler).toFlushWithoutYielding(); - expect(ReactNoop.getChildren()).toEqual([ - div(span(), div(), span(), span('test')), - ]); + expect(ReactNoop).toMatchRenderedOutput( +
+ Hi +
+ Hi + +
, + ); }); it('can update child nodes rendering into text nodes', function () { @@ -128,11 +137,11 @@ describe('ReactIncrementalSideEffects', () => { ReactNoop.render(); expect(Scheduler).toFlushWithoutYielding(); - expect(ReactNoop.getChildren()).toEqual([div('Hello')]); + expect(ReactNoop).toMatchRenderedOutput(
Hello
); ReactNoop.render(); expect(Scheduler).toFlushWithoutYielding(); - expect(ReactNoop.getChildren()).toEqual([div('World', 'World', '!')]); + expect(ReactNoop).toMatchRenderedOutput(
WorldWorld!
); }); it('can deletes children either components, host or text', function () { @@ -152,13 +161,17 @@ describe('ReactIncrementalSideEffects', () => { ReactNoop.render(); expect(Scheduler).toFlushWithoutYielding(); - expect(ReactNoop.getChildren()).toEqual([ - div(div(), span('Hello'), 'World'), - ]); + expect(ReactNoop).toMatchRenderedOutput( +
+
+ + World +
, + ); ReactNoop.render(); expect(Scheduler).toFlushWithoutYielding(); - expect(ReactNoop.getChildren()).toEqual([div()]); + expect(ReactNoop).toMatchRenderedOutput(
); }); it('can delete a child that changes type - implicit keys', function () { @@ -194,23 +207,33 @@ describe('ReactIncrementalSideEffects', () => { ReactNoop.render(); expect(Scheduler).toFlushWithoutYielding(); - expect(ReactNoop.getChildren()).toEqual([div(span('Class'), 'Trail')]); + expect(ReactNoop).toMatchRenderedOutput( +
+ + Trail +
, + ); expect(unmounted).toBe(false); ReactNoop.render(); expect(Scheduler).toFlushWithoutYielding(); - expect(ReactNoop.getChildren()).toEqual([div(span('Function'), 'Trail')]); + expect(ReactNoop).toMatchRenderedOutput( +
+ + Trail +
, + ); expect(unmounted).toBe(true); ReactNoop.render(); expect(Scheduler).toFlushWithoutYielding(); - expect(ReactNoop.getChildren()).toEqual([div('Text', 'Trail')]); + expect(ReactNoop).toMatchRenderedOutput(
TextTrail
); ReactNoop.render(); expect(Scheduler).toFlushWithoutYielding(); - expect(ReactNoop.getChildren()).toEqual([div('Trail')]); + expect(ReactNoop).toMatchRenderedOutput(
Trail
); }); it('can delete a child that changes type - explicit keys', function () { @@ -244,19 +267,29 @@ describe('ReactIncrementalSideEffects', () => { ReactNoop.render(); expect(Scheduler).toFlushWithoutYielding(); - expect(ReactNoop.getChildren()).toEqual([div(span('Class'), 'Trail')]); + expect(ReactNoop).toMatchRenderedOutput( +
+ + Trail +
, + ); expect(unmounted).toBe(false); ReactNoop.render(); expect(Scheduler).toFlushWithoutYielding(); - expect(ReactNoop.getChildren()).toEqual([div(span('Function'), 'Trail')]); + expect(ReactNoop).toMatchRenderedOutput( +
+ + Trail +
, + ); expect(unmounted).toBe(true); ReactNoop.render(); expect(Scheduler).toFlushWithoutYielding(); - expect(ReactNoop.getChildren()).toEqual([div('Trail')]); + expect(ReactNoop).toMatchRenderedOutput(
Trail
); }); it('can delete a child when it unmounts inside a portal', () => { @@ -280,12 +313,14 @@ describe('ReactIncrementalSideEffects', () => {
, ); expect(Scheduler).toFlushWithoutYielding(); - expect(ReactNoop.getChildren()).toEqual([div()]); - expect(ReactNoop.getChildren('portalContainer')).toEqual([ - div(), - span('Hello'), - text('World'), - ]); + expect(ReactNoop).toMatchRenderedOutput(
); + expect(ReactNoop.getChildrenAsJSX('portalContainer')).toEqual( + <> +
+ + World + , + ); ReactNoop.render(
@@ -293,8 +328,8 @@ describe('ReactIncrementalSideEffects', () => {
, ); expect(Scheduler).toFlushWithoutYielding(); - expect(ReactNoop.getChildren()).toEqual([div()]); - expect(ReactNoop.getChildren('portalContainer')).toEqual([]); + expect(ReactNoop).toMatchRenderedOutput(
); + expect(ReactNoop.getChildrenAsJSX('portalContainer')).toEqual(null); ReactNoop.render(
@@ -302,36 +337,40 @@ describe('ReactIncrementalSideEffects', () => {
, ); expect(Scheduler).toFlushWithoutYielding(); - expect(ReactNoop.getChildren()).toEqual([div()]); - expect(ReactNoop.getChildren('portalContainer')).toEqual([ - div(), - span('Hello'), - text('World'), - ]); + expect(ReactNoop).toMatchRenderedOutput(
); + expect(ReactNoop.getChildrenAsJSX('portalContainer')).toEqual( + <> +
+ + World + , + ); ReactNoop.render(null); expect(Scheduler).toFlushWithoutYielding(); - expect(ReactNoop.getChildren()).toEqual([]); - expect(ReactNoop.getChildren('portalContainer')).toEqual([]); + expect(ReactNoop).toMatchRenderedOutput(null); + expect(ReactNoop.getChildrenAsJSX('portalContainer')).toEqual(null); ReactNoop.render(); expect(Scheduler).toFlushWithoutYielding(); - expect(ReactNoop.getChildren()).toEqual([]); - expect(ReactNoop.getChildren('portalContainer')).toEqual([]); + expect(ReactNoop).toMatchRenderedOutput(null); + expect(ReactNoop.getChildrenAsJSX('portalContainer')).toEqual(null); ReactNoop.render(); expect(Scheduler).toFlushWithoutYielding(); - expect(ReactNoop.getChildren()).toEqual([]); - expect(ReactNoop.getChildren('portalContainer')).toEqual([ - div(), - span('Hello'), - text('World'), - ]); + expect(ReactNoop).toMatchRenderedOutput(null); + expect(ReactNoop.getChildrenAsJSX('portalContainer')).toEqual( + <> +
+ + World + , + ); ReactNoop.render(null); expect(Scheduler).toFlushWithoutYielding(); - expect(ReactNoop.getChildren()).toEqual([]); - expect(ReactNoop.getChildren('portalContainer')).toEqual([]); + expect(ReactNoop).toMatchRenderedOutput(null); + expect(ReactNoop.getChildrenAsJSX('portalContainer')).toEqual(null); }); it('can delete a child when it unmounts with a portal', () => { @@ -355,31 +394,35 @@ describe('ReactIncrementalSideEffects', () => {
, ); expect(Scheduler).toFlushWithoutYielding(); - expect(ReactNoop.getChildren()).toEqual([div()]); - expect(ReactNoop.getChildren('portalContainer')).toEqual([ - div(), - span('Hello'), - text('World'), - ]); + expect(ReactNoop).toMatchRenderedOutput(
); + expect(ReactNoop.getChildrenAsJSX('portalContainer')).toEqual( + <> +
+ + World + , + ); ReactNoop.render(null); expect(Scheduler).toFlushWithoutYielding(); - expect(ReactNoop.getChildren()).toEqual([]); - expect(ReactNoop.getChildren('portalContainer')).toEqual([]); + expect(ReactNoop).toMatchRenderedOutput(null); + expect(ReactNoop.getChildrenAsJSX('portalContainer')).toEqual(null); ReactNoop.render(); expect(Scheduler).toFlushWithoutYielding(); - expect(ReactNoop.getChildren()).toEqual([]); - expect(ReactNoop.getChildren('portalContainer')).toEqual([ - div(), - span('Hello'), - text('World'), - ]); + expect(ReactNoop).toMatchRenderedOutput(null); + expect(ReactNoop.getChildrenAsJSX('portalContainer')).toEqual( + <> +
+ + World + , + ); ReactNoop.render(null); expect(Scheduler).toFlushWithoutYielding(); - expect(ReactNoop.getChildren()).toEqual([]); - expect(ReactNoop.getChildren('portalContainer')).toEqual([]); + expect(ReactNoop).toMatchRenderedOutput(null); + expect(ReactNoop.getChildrenAsJSX('portalContainer')).toEqual(null); }); it('does not update child nodes if a flush is aborted', () => { @@ -403,9 +446,15 @@ describe('ReactIncrementalSideEffects', () => { ReactNoop.render(); expect(Scheduler).toFlushAndYield(['Foo', 'Bar', 'Bar', 'Bar']); - expect(ReactNoop.getChildren()).toEqual([ - div(div(span('Hello'), span('Hello')), span('Yo')), - ]); + expect(ReactNoop).toMatchRenderedOutput( +
+
+ + +
+ +
, + ); if (gate(flags => flags.enableSyncDefaultUpdates)) { React.startTransition(() => { @@ -417,9 +466,15 @@ describe('ReactIncrementalSideEffects', () => { // Flush some of the work without committing expect(Scheduler).toFlushAndYieldThrough(['Foo', 'Bar']); - expect(ReactNoop.getChildren()).toEqual([ - div(div(span('Hello'), span('Hello')), span('Yo')), - ]); + expect(ReactNoop).toMatchRenderedOutput( +
+
+ + +
+ +
, + ); }); // @gate www @@ -729,55 +784,61 @@ describe('ReactIncrementalSideEffects', () => { } ReactNoop.render(); ReactNoop.flushDeferredPri(40 + 25); - expect(ReactNoop.getChildren()).toEqual([ - div( - span(0), - div(/*the spans are down-prioritized and not rendered yet*/), - ), - ]); + expect(ReactNoop).toMatchRenderedOutput( +
+ +
+
, + ); ReactNoop.render(); ReactNoop.flushDeferredPri(35 + 25); - expect(ReactNoop.getChildren()).toEqual([ - div(span(1), div(/*still not rendered yet*/)), - ]); + expect(ReactNoop).toMatchRenderedOutput( +
+ +
{/*still not rendered yet*/}
+
, + ); ReactNoop.flushDeferredPri(30 + 25); - expect(ReactNoop.getChildren()).toEqual([ - div( - span(1), - div( - // Now we had enough time to finish the spans. - span(0), - span(1), - ), - ), - ]); - const innerSpanA = ReactNoop.getChildren()[0].children[1].children[1]; + expect(ReactNoop).toMatchRenderedOutput( +
+ +
+ {/* Now we had enough time to finish the spans. */} + + +
+ , +
, + ); + const innerSpanA = + ReactNoop.dangerouslyGetChildren()[0].children[1].children[1]; ReactNoop.render(); ReactNoop.flushDeferredPri(30 + 25); - expect(ReactNoop.getChildren()).toEqual([ - div( - span(2), - div( - // Still same old numbers. - span(0), - span(1), - ), - ), - ]); + expect(ReactNoop).toMatchRenderedOutput( +
+ +
+ {/* Still same old numbers. */} + + +
+
, + ); ReactNoop.render(); expect(Scheduler).toFlushWithoutYielding(); - expect(ReactNoop.getChildren()).toEqual([ - div( - span(3), - div( - // New numbers. - span(1), - span(2), - ), - ), - ]); + expect(ReactNoop).toMatchRenderedOutput( +
+ +
+ {/* New numbers. */} + + +
+
, + ); - const innerSpanB = ReactNoop.getChildren()[0].children[1].children[1]; + const innerSpanB = + ReactNoop.dangerouslyGetChildren()[0].children[1].children[1]; // This should have been an update to an existing instance, not recreation. // We verify that by ensuring that the child instance was the same as // before. @@ -823,39 +884,44 @@ describe('ReactIncrementalSideEffects', () => { } ReactNoop.render(); ReactNoop.flushDeferredPri(65 + 5); - expect(ReactNoop.getChildren()).toEqual([ - div( - span(0), - div(/*the spans are down-prioritized and not rendered yet*/), - ), - ]); + expect(ReactNoop).toMatchRenderedOutput( +
+ + {/*the spans are down-prioritized and not rendered yet*/} +
+
, + ); expect(ops).toEqual(['Foo', 'Baz', 'Bar']); ops = []; ReactNoop.render(); ReactNoop.flushDeferredPri(70); - expect(ReactNoop.getChildren()).toEqual([ - div(span(1), div(/*still not rendered yet*/)), - ]); + expect(ReactNoop).toMatchRenderedOutput( +
+ + {/*still not rendered yet*/} +
+
, + ); expect(ops).toEqual(['Foo']); ops = []; expect(Scheduler).toFlushWithoutYielding(); - expect(ReactNoop.getChildren()).toEqual([ - div( - span(1), - div( - // Now we had enough time to finish the spans. - span(0), - span(0), - span(0), - span(0), - span(0), - span(0), - ), - ), + expect(ReactNoop).toMatchRenderedOutput([ +
+ , +
+ {/* Now we had enough time to finish the spans. */} + , + , + , + , + , + , +
+
, ]); expect(ops).toEqual(['Bar', 'Baz', 'Bar', 'Bar', 'Baz', 'Bar', 'Bar']); @@ -865,20 +931,20 @@ describe('ReactIncrementalSideEffects', () => { // way through. ReactNoop.render(); ReactNoop.flushDeferredPri(95); - expect(ReactNoop.getChildren()).toEqual([ - div( - span(2), - div( - // Still same old numbers. - span(0), - span(0), - span(0), - span(0), - span(0), - span(0), - ), - ), - ]); + expect(ReactNoop).toMatchRenderedOutput( +
+ , +
+ {/* Still same old numbers. */} + + + + + + +
+
, + ); // We let it finish half way through. That means we'll have one fully // completed Baz, one half-way completed Baz and one fully incomplete Baz. @@ -889,20 +955,20 @@ describe('ReactIncrementalSideEffects', () => { // way through. ReactNoop.render(); ReactNoop.flushDeferredPri(50); - expect(ReactNoop.getChildren()).toEqual([ - div( - span(3), - div( - // Old numbers. - span(0), - span(0), - span(0), - span(0), - span(0), - span(0), - ), - ), - ]); + expect(ReactNoop).toMatchRenderedOutput( +
+ +
+ {/* Old numbers. */} + + + + + + +
+
, + ); expect(ops).toEqual(['Foo']); ops = []; @@ -910,19 +976,19 @@ describe('ReactIncrementalSideEffects', () => { // We should now be able to reuse some of the work we've already done // and replay those side-effects. expect(Scheduler).toFlushWithoutYielding(); - expect(ReactNoop.getChildren()).toEqual([ - div( - span(3), - div( - // New numbers. - span(1), - span(1), - span(1), - span(1), - span(1), - span(1), - ), - ), + expect(ReactNoop).toMatchRenderedOutput([ +
+ , +
+ {/* New numbers. */} + + + + + + +
+
, ]); expect(ops).toEqual(['Bar', 'Baz', 'Bar', 'Bar']); @@ -1039,10 +1105,10 @@ describe('ReactIncrementalSideEffects', () => { ReactNoop.render(); expect(Scheduler).toFlushWithoutYielding(); - expect(ReactNoop.getChildren()).toEqual([span('foo')]); + expect(ReactNoop).toMatchRenderedOutput(); let called = false; instance.setState({text: 'bar'}, () => { - expect(ReactNoop.getChildren()).toEqual([span('bar')]); + expect(ReactNoop).toMatchRenderedOutput(); called = true; }); expect(Scheduler).toFlushWithoutYielding(); @@ -1067,7 +1133,7 @@ describe('ReactIncrementalSideEffects', () => { ReactNoop.render(); expect(Scheduler).toFlushWithoutYielding(); - expect(ReactNoop.getChildren()).toEqual([span('foo')]); + expect(ReactNoop).toMatchRenderedOutput(); let called = false; instance.setState({}, () => { called = true; @@ -1253,7 +1319,7 @@ describe('ReactIncrementalSideEffects', () => { expect(ops).toEqual([ classInstance, // no call for function components - div(), + {type: 'div', children: [], prop: undefined, hidden: false}, ]); ops = []; @@ -1267,7 +1333,7 @@ describe('ReactIncrementalSideEffects', () => { null, // reattach as a separate phase classInstance, - div(), + {type: 'div', children: [], prop: undefined, hidden: false}, ]); ops = []; diff --git a/packages/react-reconciler/src/__tests__/ReactIncrementalTriangle-test.js b/packages/react-reconciler/src/__tests__/ReactIncrementalTriangle-test.js index effeea5ff5ec7..07bf3af6e1211 100644 --- a/packages/react-reconciler/src/__tests__/ReactIncrementalTriangle-test.js +++ b/packages/react-reconciler/src/__tests__/ReactIncrementalTriangle-test.js @@ -316,7 +316,8 @@ describe('ReactIncrementalTriangle', () => { reset(); function assertConsistentTree(activeTriangleIndices = new Set(), counter) { - const children = ReactNoop.getChildren(rootID); + const childrenJSX = ReactNoop.getPendingChildrenAsJSX(rootID); + const children = childrenJSX === null ? [] : childrenJSX.props.children; if (children.length !== TOTAL_CHILDREN) { throw new Error('Wrong number of children.'); @@ -327,7 +328,7 @@ describe('ReactIncrementalTriangle', () => { for (let i = 0; i < children.length; i++) { const child = children[i]; - const output = JSON.parse(child.prop); + const output = JSON.parse(child.props.prop); const prop = output.prop; const isActive = output.isActive; const counterContext = output.counterContext; diff --git a/packages/react-reconciler/src/__tests__/ReactIncrementalUpdates-test.js b/packages/react-reconciler/src/__tests__/ReactIncrementalUpdates-test.js index f5f480ecca90f..02c849f6ed57a 100644 --- a/packages/react-reconciler/src/__tests__/ReactIncrementalUpdates-test.js +++ b/packages/react-reconciler/src/__tests__/ReactIncrementalUpdates-test.js @@ -28,10 +28,6 @@ describe('ReactIncrementalUpdates', () => { require('react-reconciler/constants').ContinuousEventPriority; }); - function span(prop) { - return {type: 'span', children: [], prop, hidden: false}; - } - function flushNextRenderIfExpired() { // This will start rendering the next level of work. If the work hasn't // expired yet, React will exit without doing anything. If it has expired, @@ -174,7 +170,7 @@ describe('ReactIncrementalUpdates', () => { // Begin the updates but don't flush them yet expect(Scheduler).toFlushAndYieldThrough(['a', 'b', 'c']); - expect(ReactNoop.getChildren()).toEqual([span('')]); + expect(ReactNoop).toMatchRenderedOutput(); // Schedule some more updates at different priorities instance.setState(createUpdate('d')); @@ -193,11 +189,11 @@ describe('ReactIncrementalUpdates', () => { ) ) { expect(Scheduler).toHaveYielded(['d', 'e', 'f']); - expect(ReactNoop.getChildren()).toEqual([span('def')]); + expect(ReactNoop).toMatchRenderedOutput(); } else { // Update d was dropped and replaced by e. expect(Scheduler).toHaveYielded(['e', 'f']); - expect(ReactNoop.getChildren()).toEqual([span('ef')]); + expect(ReactNoop).toMatchRenderedOutput(); } // Now flush the remaining work. Even though e and f were already processed, @@ -235,7 +231,7 @@ describe('ReactIncrementalUpdates', () => { 'g', ]); } - expect(ReactNoop.getChildren()).toEqual([span('abcdefg')]); + expect(ReactNoop).toMatchRenderedOutput(); }); it('can abort an update, schedule a replaceState, and resume', () => { @@ -279,7 +275,7 @@ describe('ReactIncrementalUpdates', () => { // Begin the updates but don't flush them yet expect(Scheduler).toFlushAndYieldThrough(['a', 'b', 'c']); - expect(ReactNoop.getChildren()).toEqual([span('')]); + expect(ReactNoop).toMatchRenderedOutput(); // Schedule some more updates at different priorities instance.setState(createUpdate('d')); @@ -305,7 +301,7 @@ describe('ReactIncrementalUpdates', () => { // Update d was dropped and replaced by e. expect(Scheduler).toHaveYielded(['e', 'f']); } - expect(ReactNoop.getChildren()).toEqual([span('f')]); + expect(ReactNoop).toMatchRenderedOutput(); // Now flush the remaining work. Even though e and f were already processed, // they should be processed again, to ensure that the terminal state @@ -342,7 +338,7 @@ describe('ReactIncrementalUpdates', () => { 'g', ]); } - expect(ReactNoop.getChildren()).toEqual([span('fg')]); + expect(ReactNoop).toMatchRenderedOutput(); }); it('passes accumulation of previous updates to replaceState updater function', () => { @@ -537,7 +533,7 @@ describe('ReactIncrementalUpdates', () => { ReactNoop.flushSync(() => { ReactNoop.render(); }); - expect(ReactNoop.getChildren()).toEqual([span('derived state')]); + expect(ReactNoop).toMatchRenderedOutput(); ReactNoop.flushSync(() => { // Triggers getDerivedStateFromProps again @@ -546,12 +542,12 @@ describe('ReactIncrementalUpdates', () => { // led to this bug. Removing it causes it to "accidentally" work. foo.setState({value: 'update state'}, function noop() {}); }); - expect(ReactNoop.getChildren()).toEqual([span('derived state')]); + expect(ReactNoop).toMatchRenderedOutput(); ReactNoop.flushSync(() => { bar.setState({}); }); - expect(ReactNoop.getChildren()).toEqual([span('derived state')]); + expect(ReactNoop).toMatchRenderedOutput(); }); it('regression: does not expire soon due to layout effects in the last batch', () => { diff --git a/packages/react-reconciler/src/__tests__/ReactMemo-test.js b/packages/react-reconciler/src/__tests__/ReactMemo-test.js index bd26d03f0582c..1ca0cf2e99dde 100644 --- a/packages/react-reconciler/src/__tests__/ReactMemo-test.js +++ b/packages/react-reconciler/src/__tests__/ReactMemo-test.js @@ -31,10 +31,6 @@ describe('memo', () => { ({Suspense} = React); }); - function span(prop) { - return {type: 'span', children: [], prop, hidden: false}; - } - function Text(props) { Scheduler.unstable_yieldValue(props.text); return ; @@ -112,7 +108,7 @@ describe('memo', () => { expect(Scheduler).toFlushAndYield(['Loading...']); await Promise.resolve(); expect(Scheduler).toFlushAndYield([0]); - expect(ReactNoop.getChildren()).toEqual([span(0)]); + expect(ReactNoop).toMatchRenderedOutput(); // Should bail out because props have not changed ReactNoop.render( @@ -121,7 +117,7 @@ describe('memo', () => { , ); expect(Scheduler).toFlushAndYield([]); - expect(ReactNoop.getChildren()).toEqual([span(0)]); + expect(ReactNoop).toMatchRenderedOutput(); // Should update because count prop changed ReactNoop.render( @@ -130,7 +126,7 @@ describe('memo', () => { , ); expect(Scheduler).toFlushAndYield([1]); - expect(ReactNoop.getChildren()).toEqual([span(1)]); + expect(ReactNoop).toMatchRenderedOutput(); }); it("does not bail out if there's a context change", async () => { @@ -167,17 +163,17 @@ describe('memo', () => { expect(Scheduler).toFlushAndYield(['Loading...']); await Promise.resolve(); expect(Scheduler).toFlushAndYield(['Count: 0']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); + expect(ReactNoop).toMatchRenderedOutput(); // Should bail out because props have not changed ReactNoop.render(); expect(Scheduler).toFlushAndYield([]); - expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); + expect(ReactNoop).toMatchRenderedOutput(); // Should update because there was a context change parent.current.setState({count: 1}); expect(Scheduler).toFlushAndYield(['Count: 1']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]); + expect(ReactNoop).toMatchRenderedOutput(); }); it('consistent behavior for reusing props object across different function component types', async () => { @@ -352,7 +348,7 @@ describe('memo', () => { expect(Scheduler).toFlushAndYield(['Loading...']); await Promise.resolve(); expect(Scheduler).toFlushAndYield([0]); - expect(ReactNoop.getChildren()).toEqual([span(0)]); + expect(ReactNoop).toMatchRenderedOutput(); // Should bail out because props have not changed ReactNoop.render( @@ -361,7 +357,7 @@ describe('memo', () => { , ); expect(Scheduler).toFlushAndYield(['Old count: 0, New count: 0']); - expect(ReactNoop.getChildren()).toEqual([span(0)]); + expect(ReactNoop).toMatchRenderedOutput(); // Should update because count prop changed ReactNoop.render( @@ -370,7 +366,7 @@ describe('memo', () => { , ); expect(Scheduler).toFlushAndYield(['Old count: 0, New count: 1', 1]); - expect(ReactNoop.getChildren()).toEqual([span(1)]); + expect(ReactNoop).toMatchRenderedOutput(); }); it('supports non-pure class components', async () => { @@ -390,7 +386,7 @@ describe('memo', () => { expect(Scheduler).toFlushAndYield(['Loading...']); await Promise.resolve(); expect(Scheduler).toFlushAndYield(['0!']); - expect(ReactNoop.getChildren()).toEqual([span('0!')]); + expect(ReactNoop).toMatchRenderedOutput(); // Should bail out because props have not changed ReactNoop.render( @@ -399,7 +395,7 @@ describe('memo', () => { , ); expect(Scheduler).toFlushAndYield([]); - expect(ReactNoop.getChildren()).toEqual([span('0!')]); + expect(ReactNoop).toMatchRenderedOutput(); // Should update because count prop changed ReactNoop.render( @@ -408,7 +404,7 @@ describe('memo', () => { , ); expect(Scheduler).toFlushAndYield(['1!']); - expect(ReactNoop.getChildren()).toEqual([span('1!')]); + expect(ReactNoop).toMatchRenderedOutput(); }); it('supports defaultProps defined on the memo() return value', async () => { @@ -447,7 +443,7 @@ describe('memo', () => { }).toErrorDev([ 'Counter: Support for defaultProps will be removed from memo components in a future major release. Use JavaScript default parameters instead.', ]); - expect(ReactNoop.getChildren()).toEqual([span(15)]); + expect(ReactNoop).toMatchRenderedOutput(); // Should bail out because props have not changed ReactNoop.render( @@ -456,7 +452,7 @@ describe('memo', () => { , ); expect(Scheduler).toFlushAndYield([]); - expect(ReactNoop.getChildren()).toEqual([span(15)]); + expect(ReactNoop).toMatchRenderedOutput(); // Should update because count prop changed ReactNoop.render( @@ -465,7 +461,7 @@ describe('memo', () => { , ); expect(Scheduler).toFlushAndYield([20]); - expect(ReactNoop.getChildren()).toEqual([span(20)]); + expect(ReactNoop).toMatchRenderedOutput(); }); it('warns if the first argument is undefined', () => { diff --git a/packages/react-reconciler/src/__tests__/ReactNewContext-test.js b/packages/react-reconciler/src/__tests__/ReactNewContext-test.js index bc77687451743..25da1d13a6b2b 100644 --- a/packages/react-reconciler/src/__tests__/ReactNewContext-test.js +++ b/packages/react-reconciler/src/__tests__/ReactNewContext-test.js @@ -128,12 +128,12 @@ describe('ReactNewContext', () => { ReactNoop.render(); expect(Scheduler).toFlushWithoutYielding(); - expect(ReactNoop.getChildren()).toEqual([span('Result: 2')]); + expect(ReactNoop).toMatchRenderedOutput(); // Update ReactNoop.render(); expect(Scheduler).toFlushWithoutYielding(); - expect(ReactNoop.getChildren()).toEqual([span('Result: 3')]); + expect(ReactNoop).toMatchRenderedOutput(); }); it('propagates through shouldComponentUpdate false', () => { @@ -193,7 +193,7 @@ describe('ReactNewContext', () => { 'Consumer', 'Consumer render prop', ]); - expect(ReactNoop.getChildren()).toEqual([span('Result: 2')]); + expect(ReactNoop).toMatchRenderedOutput(); // Update ReactNoop.render(); @@ -202,7 +202,7 @@ describe('ReactNewContext', () => { 'Provider', 'Consumer render prop', ]); - expect(ReactNoop.getChildren()).toEqual([span('Result: 3')]); + expect(ReactNoop).toMatchRenderedOutput(); }); it('consumers bail out if context value is the same', () => { @@ -262,7 +262,7 @@ describe('ReactNewContext', () => { 'Consumer', 'Consumer render prop', ]); - expect(ReactNoop.getChildren()).toEqual([span('Result: 2')]); + expect(ReactNoop).toMatchRenderedOutput(); // Update with the same context value ReactNoop.render(); @@ -271,7 +271,7 @@ describe('ReactNewContext', () => { 'Provider', // Don't call render prop again ]); - expect(ReactNoop.getChildren()).toEqual([span('Result: 2')]); + expect(ReactNoop).toMatchRenderedOutput(); }); it('nested providers', () => { @@ -322,12 +322,12 @@ describe('ReactNewContext', () => { ReactNoop.render(); expect(Scheduler).toFlushWithoutYielding(); - expect(ReactNoop.getChildren()).toEqual([span('Result: 8')]); + expect(ReactNoop).toMatchRenderedOutput(); // Update ReactNoop.render(); expect(Scheduler).toFlushWithoutYielding(); - expect(ReactNoop.getChildren()).toEqual([span('Result: 12')]); + expect(ReactNoop).toMatchRenderedOutput(); }); it('should provide the correct (default) values to consumers outside of a provider', () => { @@ -417,26 +417,32 @@ describe('ReactNewContext', () => { ReactNoop.render(); expect(Scheduler).toFlushWithoutYielding(); - expect(ReactNoop.getChildren()).toEqual([ - span('Result: 4'), - span('Result: 2'), - ]); + expect(ReactNoop).toMatchRenderedOutput( + <> + + + , + ); // Update ReactNoop.render(); expect(Scheduler).toFlushWithoutYielding(); - expect(ReactNoop.getChildren()).toEqual([ - span('Result: 6'), - span('Result: 3'), - ]); + expect(ReactNoop).toMatchRenderedOutput( + <> + + + , + ); // Another update ReactNoop.render(); expect(Scheduler).toFlushWithoutYielding(); - expect(ReactNoop.getChildren()).toEqual([ - span('Result: 8'), - span('Result: 4'), - ]); + expect(ReactNoop).toMatchRenderedOutput( + <> + + + , + ); }); it('compares context values with Object.is semantics', () => { @@ -496,7 +502,7 @@ describe('ReactNewContext', () => { 'Consumer', 'Consumer render prop', ]); - expect(ReactNoop.getChildren()).toEqual([span('Result: NaN')]); + expect(ReactNoop).toMatchRenderedOutput(); // Update ReactNoop.render(); @@ -506,7 +512,7 @@ describe('ReactNewContext', () => { // Consumer should not re-render again // 'Consumer render prop', ]); - expect(ReactNoop.getChildren()).toEqual([span('Result: NaN')]); + expect(ReactNoop).toMatchRenderedOutput(); }); it('context unwinds when interrupted', () => { @@ -555,10 +561,10 @@ describe('ReactNewContext', () => { ReactNoop.render(); expect(Scheduler).toFlushWithoutYielding(); - expect(ReactNoop.getChildren()).toEqual([ + expect(ReactNoop).toMatchRenderedOutput( // The second provider should use the default value. - span('Result: Does not unwind'), - ]); + , + ); }); it("does not re-render if there's an update in a child", () => { @@ -594,11 +600,15 @@ describe('ReactNewContext', () => { // Initial mount ReactNoop.render(); expect(Scheduler).toFlushAndYield(['Consumer render prop', 'Child']); - expect(ReactNoop.getChildren()).toEqual([span('Context: 1, Step: 0')]); + expect(ReactNoop).toMatchRenderedOutput( + , + ); child.setState({step: 1}); expect(Scheduler).toFlushAndYield(['Child']); - expect(ReactNoop.getChildren()).toEqual([span('Context: 1, Step: 1')]); + expect(ReactNoop).toMatchRenderedOutput( + , + ); }); it('consumer bails out if value is unchanged and something above bailed out', () => { @@ -654,17 +664,32 @@ describe('ReactNewContext', () => { 'ChildWithCachedRenderCallback', 'Consumer', ]); - expect(ReactNoop.getChildren()).toEqual([span(1), span(1)]); + expect(ReactNoop).toMatchRenderedOutput( + <> + + + , + ); // Update (bailout) ReactNoop.render(); expect(Scheduler).toFlushAndYield(['App']); - expect(ReactNoop.getChildren()).toEqual([span(1), span(1)]); + expect(ReactNoop).toMatchRenderedOutput( + <> + + + , + ); // Update (no bailout) ReactNoop.render(); expect(Scheduler).toFlushAndYield(['App', 'Consumer', 'Consumer']); - expect(ReactNoop.getChildren()).toEqual([span(2), span(2)]); + expect(ReactNoop).toMatchRenderedOutput( + <> + + + , + ); }); // @gate www @@ -788,26 +813,32 @@ describe('ReactNewContext', () => { let inst; ReactNoop.render( (inst = ref)} />); expect(Scheduler).toFlushAndYield(['App']); - expect(ReactNoop.getChildren()).toEqual([ - span('static 1'), - span('static 2'), - ]); + expect(ReactNoop).toMatchRenderedOutput( + <> + + + , + ); // Update the first time inst.setState({step: 1}); expect(Scheduler).toFlushAndYield(['App', 'Consumer']); - expect(ReactNoop.getChildren()).toEqual([ - span('static 1'), - span('static 2'), - span(1), - ]); + expect(ReactNoop).toMatchRenderedOutput( + <> + + + + , + ); // Update the second time inst.setState({step: 2}); expect(Scheduler).toFlushAndYield(['App', 'Consumer']); - expect(ReactNoop.getChildren()).toEqual([ - span('static 1'), - span('static 2'), - span(2), - ]); + expect(ReactNoop).toMatchRenderedOutput( + <> + + + + , + ); }); }); } @@ -936,7 +967,7 @@ describe('ReactNewContext', () => { // Initial mount ReactNoop.render(); expect(Scheduler).toFlushAndYield(['App', 'Child']); - expect(ReactNoop.getChildren()).toEqual([span('Child')]); + expect(ReactNoop).toMatchRenderedOutput(); // Update ReactNoop.render(); @@ -944,7 +975,7 @@ describe('ReactNewContext', () => { 'App', // Child does not re-render ]); - expect(ReactNoop.getChildren()).toEqual([span('Child')]); + expect(ReactNoop).toMatchRenderedOutput(); }); it('provider does not bail out if legacy context changed above', () => { @@ -995,22 +1026,22 @@ describe('ReactNewContext', () => { , ); expect(Scheduler).toFlushAndYield(['LegacyProvider', 'App', 'Child']); - expect(ReactNoop.getChildren()).toEqual([span('Child')]); + expect(ReactNoop).toMatchRenderedOutput(); // Update App with same value (should bail out) appRef.current.setState({value: 1}); expect(Scheduler).toFlushAndYield(['App']); - expect(ReactNoop.getChildren()).toEqual([span('Child')]); + expect(ReactNoop).toMatchRenderedOutput(); // Update LegacyProvider (should not bail out) legacyProviderRef.current.setState({value: 1}); expect(Scheduler).toFlushAndYield(['LegacyProvider', 'App', 'Child']); - expect(ReactNoop.getChildren()).toEqual([span('Child')]); + expect(ReactNoop).toMatchRenderedOutput(); // Update App with same value (should bail out) appRef.current.setState({value: 1}); expect(Scheduler).toFlushAndYield(['App']); - expect(ReactNoop.getChildren()).toEqual([span('Child')]); + expect(ReactNoop).toMatchRenderedOutput(); }); }); @@ -1066,17 +1097,17 @@ describe('ReactNewContext', () => { ReactNoop.render(); expect(Scheduler).toFlushAndYield(['Foo: 1, Bar: 1']); - expect(ReactNoop.getChildren()).toEqual([span('Foo: 1, Bar: 1')]); + expect(ReactNoop).toMatchRenderedOutput(); // Update foo ReactNoop.render(); expect(Scheduler).toFlushAndYield(['Foo: 2, Bar: 1']); - expect(ReactNoop.getChildren()).toEqual([span('Foo: 2, Bar: 1')]); + expect(ReactNoop).toMatchRenderedOutput(); // Update bar ReactNoop.render(); expect(Scheduler).toFlushAndYield(['Foo: 2, Bar: 2']); - expect(ReactNoop.getChildren()).toEqual([span('Foo: 2, Bar: 2')]); + expect(ReactNoop).toMatchRenderedOutput(); }); // Context consumer bails out on propagating "deep" updates when `value` hasn't changed. @@ -1112,12 +1143,12 @@ describe('ReactNewContext', () => { let inst; ReactNoop.render( (inst = ref)} />); expect(Scheduler).toFlushAndYield(['App', 'App#renderConsumer']); - expect(ReactNoop.getChildren()).toEqual([span('hello')]); + expect(ReactNoop).toMatchRenderedOutput(); // Update inst.setState({text: 'goodbye'}); expect(Scheduler).toFlushAndYield(['App', 'App#renderConsumer']); - expect(ReactNoop.getChildren()).toEqual([span('goodbye')]); + expect(ReactNoop).toMatchRenderedOutput(); }); }); @@ -1186,33 +1217,33 @@ describe('ReactNewContext', () => { ReactNoop.render(); expect(Scheduler).toFlushAndYield(['Foo: 1, Bar: 1', 'Baz: 1']); - expect(ReactNoop.getChildren()).toEqual([ - span('Foo: 1, Bar: 1'), - span('Baz: 1'), + expect(ReactNoop).toMatchRenderedOutput([ + , + , ]); // Update only foo ReactNoop.render(); expect(Scheduler).toFlushAndYield(['Foo: 2, Bar: 1']); - expect(ReactNoop.getChildren()).toEqual([ - span('Foo: 2, Bar: 1'), - span('Baz: 1'), + expect(ReactNoop).toMatchRenderedOutput([ + , + , ]); // Update only bar ReactNoop.render(); expect(Scheduler).toFlushAndYield(['Foo: 2, Bar: 2']); - expect(ReactNoop.getChildren()).toEqual([ - span('Foo: 2, Bar: 2'), - span('Baz: 1'), + expect(ReactNoop).toMatchRenderedOutput([ + , + , ]); // Update only baz ReactNoop.render(); expect(Scheduler).toFlushAndYield(['Baz: 2']); - expect(ReactNoop.getChildren()).toEqual([ - span('Foo: 2, Bar: 2'), - span('Baz: 2'), + expect(ReactNoop).toMatchRenderedOutput([ + , + , ]); }); @@ -1255,12 +1286,12 @@ describe('ReactNewContext', () => { let inst; ReactNoop.render( (inst = ref)} />); expect(Scheduler).toFlushAndYield(['App', 'App#renderConsumer']); - expect(ReactNoop.getChildren()).toEqual([span('hello')]); + expect(ReactNoop).toMatchRenderedOutput(); // Update inst.setState({text: 'goodbye'}); expect(Scheduler).toFlushAndYield(['App', 'App#renderConsumer']); - expect(ReactNoop.getChildren()).toEqual([span('goodbye')]); + expect(ReactNoop).toMatchRenderedOutput(); }); it('warns when reading context inside render phase class setState updater', () => { @@ -1366,12 +1397,12 @@ describe('ReactNewContext', () => { let inst; ReactNoop.render( (inst = ref)} />); expect(Scheduler).toFlushAndYield(['App', 'App#renderConsumer']); - expect(ReactNoop.getChildren()).toEqual([span('hello')]); + expect(ReactNoop).toMatchRenderedOutput(); // Update inst.setState({text: 'goodbye'}); expect(Scheduler).toFlushAndYield(['App', 'App#renderConsumer']); - expect(ReactNoop.getChildren()).toEqual([span('goodbye')]); + expect(ReactNoop).toMatchRenderedOutput(); }); }); @@ -1393,7 +1424,7 @@ describe('ReactNewContext', () => { , ); expect(Scheduler).toFlushWithoutYielding(); - expect(ReactNoop.getChildren()).toEqual([span(10)]); + expect(ReactNoop).toMatchRenderedOutput(); }); describe('fuzz test', () => { @@ -1518,9 +1549,10 @@ describe('ReactNewContext', () => { ); function assertConsistentTree(expectedValues = {}) { - const children = ReactNoop.getChildren(); + const jsx = ReactNoop.getChildrenAsJSX(); + const children = jsx === null ? [] : jsx.props.children; children.forEach(child => { - const text = child.prop; + const text = child.props.prop; const key = text[0]; const value = parseInt(text[2], 10); const expectedValue = expectedValues[key]; diff --git a/packages/react-reconciler/src/__tests__/ReactNoopRendererAct-test.js b/packages/react-reconciler/src/__tests__/ReactNoopRendererAct-test.js index bf660226fd2a9..b6f20c509cbc1 100644 --- a/packages/react-reconciler/src/__tests__/ReactNoopRendererAct-test.js +++ b/packages/react-reconciler/src/__tests__/ReactNoopRendererAct-test.js @@ -58,6 +58,6 @@ describe('internal act()', () => { }); expect(Scheduler).toHaveYielded(['stage 1', 'stage 2']); expect(Scheduler).toFlushWithoutYielding(); - expect(ReactNoop.getChildren()).toEqual([{text: '1', hidden: false}]); + expect(ReactNoop).toMatchRenderedOutput('1'); }); }); diff --git a/packages/react-reconciler/src/__tests__/ReactPersistent-test.js b/packages/react-reconciler/src/__tests__/ReactPersistent-test.js index e8306f852db96..f53f45e30d68c 100644 --- a/packages/react-reconciler/src/__tests__/ReactPersistent-test.js +++ b/packages/react-reconciler/src/__tests__/ReactPersistent-test.js @@ -49,8 +49,12 @@ describe('ReactPersistent', () => { return {type: 'span', children: [], prop, hidden: false}; } - function getChildren() { - return ReactNoopPersistent.getChildren(); + // For persistent renderers we have to mix deep equality and reference equality checks + // for which we need the actual children. + // None of the tests are gated and the underlying implementation is rarely touch + // so it's unlikely we deal with failing `toEqual` checks which cause bad performance. + function dangerouslyGetChildren() { + return ReactNoopPersistent.dangerouslyGetChildren(); } it('can update child nodes of a host instance', () => { @@ -69,12 +73,12 @@ describe('ReactPersistent', () => { render(); expect(Scheduler).toFlushWithoutYielding(); - const originalChildren = getChildren(); + const originalChildren = dangerouslyGetChildren(); expect(originalChildren).toEqual([div(span())]); render(); expect(Scheduler).toFlushWithoutYielding(); - const newChildren = getChildren(); + const newChildren = dangerouslyGetChildren(); expect(newChildren).toEqual([div(span(), span())]); expect(originalChildren).toEqual([div(span())]); @@ -103,12 +107,12 @@ describe('ReactPersistent', () => { render(); expect(Scheduler).toFlushWithoutYielding(); - const originalChildren = getChildren(); + const originalChildren = dangerouslyGetChildren(); expect(originalChildren).toEqual([div(span('Hello'))]); render(); expect(Scheduler).toFlushWithoutYielding(); - const newChildren = getChildren(); + const newChildren = dangerouslyGetChildren(); expect(newChildren).toEqual([div(span('Hello'), span('World'))]); expect(originalChildren).toEqual([div(span('Hello'))]); @@ -129,12 +133,12 @@ describe('ReactPersistent', () => { render(); expect(Scheduler).toFlushWithoutYielding(); - const originalChildren = getChildren(); + const originalChildren = dangerouslyGetChildren(); expect(originalChildren).toEqual([div('Hello', span())]); render(); expect(Scheduler).toFlushWithoutYielding(); - const newChildren = getChildren(); + const newChildren = dangerouslyGetChildren(); expect(newChildren).toEqual([div('World', span())]); expect(originalChildren).toEqual([div('Hello', span())]); @@ -173,7 +177,7 @@ describe('ReactPersistent', () => { expect(emptyPortalChildSet).toEqual([]); - const originalChildren = getChildren(); + const originalChildren = dangerouslyGetChildren(); expect(originalChildren).toEqual([div()]); const originalPortalChildren = portalContainer.children; expect(originalPortalChildren).toEqual([div(span())]); @@ -185,7 +189,7 @@ describe('ReactPersistent', () => { ); expect(Scheduler).toFlushWithoutYielding(); - const newChildren = getChildren(); + const newChildren = dangerouslyGetChildren(); expect(newChildren).toEqual([div()]); const newPortalChildren = portalContainer.children; expect(newPortalChildren).toEqual([div(span(), 'Hello ', 'World')]); diff --git a/packages/react-reconciler/src/__tests__/ReactSuspenseCallback-test.js b/packages/react-reconciler/src/__tests__/ReactSuspenseCallback-test.js index afa9ab499dca1..b14f7494a572c 100644 --- a/packages/react-reconciler/src/__tests__/ReactSuspenseCallback-test.js +++ b/packages/react-reconciler/src/__tests__/ReactSuspenseCallback-test.js @@ -22,10 +22,6 @@ describe('ReactSuspense', () => { Scheduler = require('scheduler'); }); - function text(t) { - return {text: t, hidden: false}; - } - function createThenable() { let completed = false; let resolve; @@ -88,13 +84,13 @@ describe('ReactSuspense', () => { ReactNoop.render(element); expect(Scheduler).toFlushWithoutYielding(); - expect(ReactNoop.getChildren()).toEqual([text('Waiting')]); + expect(ReactNoop).toMatchRenderedOutput('Waiting'); expect(ops).toEqual([new Set([promise])]); ops = []; await resolve(); expect(Scheduler).toFlushWithoutYielding(); - expect(ReactNoop.getChildren()).toEqual([text('Done')]); + expect(ReactNoop).toMatchRenderedOutput('Done'); expect(ops).toEqual([]); }); @@ -127,21 +123,21 @@ describe('ReactSuspense', () => { ReactNoop.render(element); expect(Scheduler).toFlushWithoutYielding(); - expect(ReactNoop.getChildren()).toEqual([text('Waiting Tier 1')]); + expect(ReactNoop).toMatchRenderedOutput('Waiting Tier 1'); expect(ops).toEqual([new Set([promise1, promise2])]); ops = []; await resolve1(); ReactNoop.render(element); expect(Scheduler).toFlushWithoutYielding(); - expect(ReactNoop.getChildren()).toEqual([text('Waiting Tier 1')]); + expect(ReactNoop).toMatchRenderedOutput('Waiting Tier 1'); expect(ops).toEqual([new Set([promise2])]); ops = []; await resolve2(); ReactNoop.render(element); expect(Scheduler).toFlushWithoutYielding(); - expect(ReactNoop.getChildren()).toEqual([text('Done'), text('Done')]); + expect(ReactNoop).toMatchRenderedOutput('DoneDone'); expect(ops).toEqual([]); }); @@ -172,7 +168,7 @@ describe('ReactSuspense', () => { ReactNoop.render(element); expect(Scheduler).toFlushWithoutYielding(); - expect(ReactNoop.getChildren()).toEqual([text('Waiting Tier 2')]); + expect(ReactNoop).toMatchRenderedOutput('Waiting Tier 2'); expect(ops1).toEqual([]); expect(ops2).toEqual([new Set([promise])]); }); @@ -214,7 +210,7 @@ describe('ReactSuspense', () => { ReactNoop.render(element); expect(Scheduler).toFlushWithoutYielding(); - expect(ReactNoop.getChildren()).toEqual([text('Waiting Tier 1')]); + expect(ReactNoop).toMatchRenderedOutput('Waiting Tier 1'); expect(ops1).toEqual([new Set([promise1])]); expect(ops2).toEqual([]); ops1 = []; @@ -228,10 +224,7 @@ describe('ReactSuspense', () => { // TODO: Should be able to use `act` here. jest.runAllTimers(); - expect(ReactNoop.getChildren()).toEqual([ - text('Waiting Tier 2'), - text('Done'), - ]); + expect(ReactNoop).toMatchRenderedOutput('Waiting Tier 2Done'); expect(ops1).toEqual([]); expect(ops2).toEqual([new Set([promise2])]); ops1 = []; @@ -240,7 +233,7 @@ describe('ReactSuspense', () => { await resolve2(); ReactNoop.render(element); expect(Scheduler).toFlushWithoutYielding(); - expect(ReactNoop.getChildren()).toEqual([text('Done'), text('Done')]); + expect(ReactNoop).toMatchRenderedOutput('DoneDone'); expect(ops1).toEqual([]); expect(ops2).toEqual([]); }); diff --git a/packages/react-reconciler/src/__tests__/ReactSuspenseEffectsSemantics-test.js b/packages/react-reconciler/src/__tests__/ReactSuspenseEffectsSemantics-test.js index 863c9e878a2a9..76a6b7794be61 100644 --- a/packages/react-reconciler/src/__tests__/ReactSuspenseEffectsSemantics-test.js +++ b/packages/react-reconciler/src/__tests__/ReactSuspenseEffectsSemantics-test.js @@ -185,14 +185,6 @@ describe('ReactSuspenseEffectsSemantics', () => { const resolveText = resolveMostRecentTextCache; - function span(prop, children = []) { - return {type: 'span', children, prop, hidden: false}; - } - - function spanHidden(prop, children = []) { - return {type: 'span', children, prop, hidden: true}; - } - function advanceTimers(ms) { // Note: This advances Jest's virtual time but not React's. Use // ReactNoop.expire for that. @@ -278,10 +270,12 @@ describe('ReactSuspenseEffectsSemantics', () => { 'Text:Outside create passive', 'App create passive', ]); - expect(ReactNoop.getChildren()).toEqual([ - span('Fallback'), - span('Outside'), - ]); + expect(ReactNoop).toMatchRenderedOutput( + <> + + + , + ); // Resolving the suspended resource should await act(async () => { @@ -299,12 +293,14 @@ describe('ReactSuspenseEffectsSemantics', () => { 'Text:Inside:Before create passive', 'AsyncText:Async create passive', ]); - expect(ReactNoop.getChildren()).toEqual([ - span('Inside:Before'), - span('Async'), - span('Inside:After'), - span('Outside'), - ]); + expect(ReactNoop).toMatchRenderedOutput( + <> + + + + + , + ); await act(async () => { ReactNoop.render(null); @@ -320,7 +316,7 @@ describe('ReactSuspenseEffectsSemantics', () => { 'AsyncText:Async destroy passive', 'Text:Outside destroy passive', ]); - expect(ReactNoop.getChildren()).toEqual([]); + expect(ReactNoop).toMatchRenderedOutput(null); }); // @gate enableLegacyCache @@ -398,12 +394,14 @@ describe('ReactSuspenseEffectsSemantics', () => { 'Text:Outside create passive', 'App create passive', ]); - expect(ReactNoop.getChildren()).toEqual([ - spanHidden('Inside:Before'), - spanHidden('Inside:After'), - span('Fallback'), - span('Outside'), - ]); + expect(ReactNoop).toMatchRenderedOutput( + <> +