react/packages/react-test-renderer/src/ReactTestRenderer.js

609 lines
16 KiB
JavaScript
Raw Normal View History

[Fiber] Implement test renderer (#8628) * ReactTestRenderer move current impl to stack dir * ReactTestRenderer on fiber: commence! * ReactTestRenderer: most non-ref/non-public-instance tests are passing * Move ReactTestFiberComponent functions from Renderer to Component file * test renderer: get rid of private root containers and root Maps * TestRenderer: switch impl based on ReactDOMFeatureFlag.useFiber * ReactTestRenderer: inline component creation * ReactTestRenderer: return to pristine original glory (+ Fiber for error order difference) * TestRendererFiber: use a simple class as TestComponentInstances * Add `getPublicInstance` to support TestRenderer `createNodeMock` * Rename files to end. Update for `mountContainer->createContainer` change * test renderer return same object to prevent unnecessary context pushing/popping * Fiber HostConfig add getPublicInstance. This should be the identity fn everywhere except the test renderer * appease flow * Initial cleanup from sleepy work * unstable_batchedUpdates * Stack test renderer: cache nodeMock to not call on unmount * add public instance type parameter to the reconciler * test renderer: set _nodeMock when mounted * More cleanup * Add test cases for root fragments and (maybe?) root text nodes * Fix the npm package build Explicitly require the Stack version by default. Add a separate entry point for Fiber. We don't add fiber.js to the package yet since it's considered internal until React 16. * Relax the ref type from Object to mixed This seems like the most straightforward way to support getPublicInstance for test renderer. * Remove accidental newline * test renderer: unify TestComponent and TestContainer, handle root updates * Remove string/number serialization attempts since Fiber ensures all textInstances are strings * Return full fragments in toJSON * Test Renderer remove TestComponent instances for simple objects * Update babylon for exact object type syntax * Use $$typeof because clarity > punching ducks. * Minor Flow annotation tweaks * Tweak style, types, and naming * Fix typo
2017-01-12 03:19:32 +08:00
/**
* Copyright (c) Facebook, Inc. and its affiliates.
[Fiber] Implement test renderer (#8628) * ReactTestRenderer move current impl to stack dir * ReactTestRenderer on fiber: commence! * ReactTestRenderer: most non-ref/non-public-instance tests are passing * Move ReactTestFiberComponent functions from Renderer to Component file * test renderer: get rid of private root containers and root Maps * TestRenderer: switch impl based on ReactDOMFeatureFlag.useFiber * ReactTestRenderer: inline component creation * ReactTestRenderer: return to pristine original glory (+ Fiber for error order difference) * TestRendererFiber: use a simple class as TestComponentInstances * Add `getPublicInstance` to support TestRenderer `createNodeMock` * Rename files to end. Update for `mountContainer->createContainer` change * test renderer return same object to prevent unnecessary context pushing/popping * Fiber HostConfig add getPublicInstance. This should be the identity fn everywhere except the test renderer * appease flow * Initial cleanup from sleepy work * unstable_batchedUpdates * Stack test renderer: cache nodeMock to not call on unmount * add public instance type parameter to the reconciler * test renderer: set _nodeMock when mounted * More cleanup * Add test cases for root fragments and (maybe?) root text nodes * Fix the npm package build Explicitly require the Stack version by default. Add a separate entry point for Fiber. We don't add fiber.js to the package yet since it's considered internal until React 16. * Relax the ref type from Object to mixed This seems like the most straightforward way to support getPublicInstance for test renderer. * Remove accidental newline * test renderer: unify TestComponent and TestContainer, handle root updates * Remove string/number serialization attempts since Fiber ensures all textInstances are strings * Return full fragments in toJSON * Test Renderer remove TestComponent instances for simple objects * Update babylon for exact object type syntax * Use $$typeof because clarity > punching ducks. * Minor Flow annotation tweaks * Tweak style, types, and naming * Fix typo
2017-01-12 03:19:32 +08:00
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
[Fiber] Implement test renderer (#8628) * ReactTestRenderer move current impl to stack dir * ReactTestRenderer on fiber: commence! * ReactTestRenderer: most non-ref/non-public-instance tests are passing * Move ReactTestFiberComponent functions from Renderer to Component file * test renderer: get rid of private root containers and root Maps * TestRenderer: switch impl based on ReactDOMFeatureFlag.useFiber * ReactTestRenderer: inline component creation * ReactTestRenderer: return to pristine original glory (+ Fiber for error order difference) * TestRendererFiber: use a simple class as TestComponentInstances * Add `getPublicInstance` to support TestRenderer `createNodeMock` * Rename files to end. Update for `mountContainer->createContainer` change * test renderer return same object to prevent unnecessary context pushing/popping * Fiber HostConfig add getPublicInstance. This should be the identity fn everywhere except the test renderer * appease flow * Initial cleanup from sleepy work * unstable_batchedUpdates * Stack test renderer: cache nodeMock to not call on unmount * add public instance type parameter to the reconciler * test renderer: set _nodeMock when mounted * More cleanup * Add test cases for root fragments and (maybe?) root text nodes * Fix the npm package build Explicitly require the Stack version by default. Add a separate entry point for Fiber. We don't add fiber.js to the package yet since it's considered internal until React 16. * Relax the ref type from Object to mixed This seems like the most straightforward way to support getPublicInstance for test renderer. * Remove accidental newline * test renderer: unify TestComponent and TestContainer, handle root updates * Remove string/number serialization attempts since Fiber ensures all textInstances are strings * Return full fragments in toJSON * Test Renderer remove TestComponent instances for simple objects * Update babylon for exact object type syntax * Use $$typeof because clarity > punching ducks. * Minor Flow annotation tweaks * Tweak style, types, and naming * Fix typo
2017-01-12 03:19:32 +08:00
*
* @flow
*/
import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
import type {FiberRoot} from 'react-reconciler/src/ReactInternalTypes';
import type {Instance, TextInstance} from './ReactTestHostConfig';
import * as React from 'react';
import * as Scheduler from 'scheduler/unstable_mock';
import {
getPublicRootInstance,
createContainer,
updateContainer,
flushSync,
injectIntoDevTools,
batchedUpdates,
} from 'react-reconciler/src/ReactFiberReconciler';
import {findCurrentFiberUsingSlowPath} from 'react-reconciler/src/ReactFiberTreeReflection';
import {
Fragment,
FunctionComponent,
ClassComponent,
HostComponent,
HostPortal,
HostText,
HostRoot,
ContextConsumer,
ContextProvider,
Mode,
ForwardRef,
Profiler,
2018-10-21 00:46:23 +08:00
MemoComponent,
SimpleMemoComponent,
IncompleteClassComponent,
ScopeComponent,
} from 'react-reconciler/src/ReactWorkTags';
import isArray from 'shared/isArray';
import getComponentNameFromType from 'shared/getComponentNameFromType';
import ReactVersion from 'shared/ReactVersion';
Improve DEV errors if string coercion throws (Temporal.*, Symbol, etc.) (#22064) * Revise ESLint rules for string coercion Currently, react uses `'' + value` to coerce mixed values to strings. This code will throw for Temporal objects or symbols. To make string-coercion safer and to improve user-facing error messages, This commit adds a new ESLint rule called `safe-string-coercion`. This rule has two modes: a production mode and a non-production mode. * If the `isProductionUserAppCode` option is true, then `'' + value` coercions are allowed (because they're faster, although they may throw) and `String(value)` coercions are disallowed. Exception: when building error messages or running DEV-only code in prod files, `String()` should be used because it won't throw. * If the `isProductionUserAppCode` option is false, then `'' + value` coercions are disallowed (because they may throw, and in non-prod code it's not worth the risk) and `String(value)` are allowed. Production mode is used for all files which will be bundled with developers' userland apps. Non-prod mode is used for all other React code: tests, DEV blocks, devtools extension, etc. In production mode, in addiiton to flagging `String(value)` calls, the rule will also flag `'' + value` or `value + ''` coercions that may throw. The rule is smart enough to silence itself in the following "will never throw" cases: * When the coercion is wrapped in a `typeof` test that restricts to safe (non-symbol, non-object) types. Example: if (typeof value === 'string' || typeof value === 'number') { thisWontReport('' + value); } * When what's being coerced is a unary function result, because unary functions never return an object or a symbol. * When the coerced value is a commonly-used numeric identifier: `i`, `idx`, or `lineNumber`. * When the statement immeidately before the coercion is a DEV-only call to a function from shared/CheckStringCoercion.js. This call is a no-op in production, but in DEV it will show a console error explaining the problem, then will throw right after a long explanatory code comment so that debugger users will have an idea what's going on. The check function call must be in the following format: if (__DEV__) { checkXxxxxStringCoercion(value); }; Manually disabling the rule is usually not necessary because almost all prod use of the `'' + value` pattern falls into one of the categories above. But in the rare cases where the rule isn't smart enough to detect safe usage (e.g. when a coercion is inside a nested ternary operator), manually disabling the rule will be needed. The rule should also be manually disabled in prod error handling code where `String(value)` should be used for coercions, because it'd be bad to throw while building an error message or stack trace! The prod and non-prod modes have differentiated error messages to explain how to do a proper coercion in that mode. If a production check call is needed but is missing or incorrect (e.g. not in a DEV block or not immediately before the coercion), then a context-sensitive error message will be reported so that developers can figure out what's wrong and how to fix the problem. Because string coercions are now handled by the `safe-string-coercion` rule, the `no-primitive-constructor` rule no longer flags `String()` usage. It still flags `new String(value)` because that usage is almost always a bug. * Add DEV-only string coercion check functions This commit adds DEV-only functions to check whether coercing values to strings using the `'' + value` pattern will throw. If it will throw, these functions will: 1. Display a console error with a friendly error message describing the problem and the developer can fix it. 2. Perform the coercion, which will throw. Right before the line where the throwing happens, there's a long code comment that will help debugger users (or others looking at the exception call stack) figure out what happened and how to fix the problem. One of these check functions should be called before all string coercion of user-provided values, except when the the coercion is guaranteed not to throw, e.g. * if inside a typeof check like `if (typeof value === 'string')` * if coercing the result of a unary function like `+value` or `value++` * if coercing a variable named in a whitelist of numeric identifiers: `i`, `idx`, or `lineNumber`. The new `safe-string-coercion` internal ESLint rule enforces that these check functions are called when they are required. Only use these check functions in production code that will be bundled with user apps. For non-prod code (and for production error-handling code), use `String(value)` instead which may be a little slower but will never throw. * Add failing tests for string coercion Added failing tests to verify: * That input, select, and textarea elements with value and defaultValue set to Temporal-like objects which will throw when coerced to string using the `'' + value` pattern. * That text elements will throw for Temporal-like objects * That dangerouslySetInnerHTML will *not* throw for Temporal-like objects because this value is not cast to a string before passing to the DOM. * That keys that are Temporal-like objects will throw All tests above validate the friendly error messages thrown. * Use `String(value)` for coercion in non-prod files This commit switches non-production code from `'' + value` (which throws for Temporal objects and symbols) to instead use `String(value)` which won't throw for these or other future plus-phobic types. "Non-produciton code" includes anything not bundled into user apps: * Tests and test utilities. Note that I didn't change legacy React test fixtures because I assumed it was good for those files to act just like old React, including coercion behavior. * Build scripts * Dev tools package - In addition to switching to `String`, I also removed special-case code for coercing symbols which is now unnecessary. * Add DEV-only string coercion checks to prod files This commit adds DEV-only function calls to to check if string coercion using `'' + value` will throw, which it will if the value is a Temporal object or a symbol because those types can't be added with `+`. If it will throw, then in DEV these checks will show a console error to help the user undertsand what went wrong and how to fix the problem. After emitting the console error, the check functions will retry the coercion which will throw with a call stack that's easy (or at least easier!) to troubleshoot because the exception happens right after a long comment explaining the issue. So whether the user is in a debugger, looking at the browser console, or viewing the in-browser DEV call stack, it should be easy to understand and fix the problem. In most cases, the safe-string-coercion ESLint rule is smart enough to detect when a coercion is safe. But in rare cases (e.g. when a coercion is inside a ternary) this rule will have to be manually disabled. This commit also switches error-handling code to use `String(value)` for coercion, because it's bad to crash when you're trying to build an error message or a call stack! Because `String()` is usually disallowed by the `safe-string-coercion` ESLint rule in production code, the rule must be disabled when `String()` is used.
2021-09-28 01:05:07 +08:00
import {checkPropStringCoercion} from 'shared/CheckStringCoercion';
[Fiber] Implement test renderer (#8628) * ReactTestRenderer move current impl to stack dir * ReactTestRenderer on fiber: commence! * ReactTestRenderer: most non-ref/non-public-instance tests are passing * Move ReactTestFiberComponent functions from Renderer to Component file * test renderer: get rid of private root containers and root Maps * TestRenderer: switch impl based on ReactDOMFeatureFlag.useFiber * ReactTestRenderer: inline component creation * ReactTestRenderer: return to pristine original glory (+ Fiber for error order difference) * TestRendererFiber: use a simple class as TestComponentInstances * Add `getPublicInstance` to support TestRenderer `createNodeMock` * Rename files to end. Update for `mountContainer->createContainer` change * test renderer return same object to prevent unnecessary context pushing/popping * Fiber HostConfig add getPublicInstance. This should be the identity fn everywhere except the test renderer * appease flow * Initial cleanup from sleepy work * unstable_batchedUpdates * Stack test renderer: cache nodeMock to not call on unmount * add public instance type parameter to the reconciler * test renderer: set _nodeMock when mounted * More cleanup * Add test cases for root fragments and (maybe?) root text nodes * Fix the npm package build Explicitly require the Stack version by default. Add a separate entry point for Fiber. We don't add fiber.js to the package yet since it's considered internal until React 16. * Relax the ref type from Object to mixed This seems like the most straightforward way to support getPublicInstance for test renderer. * Remove accidental newline * test renderer: unify TestComponent and TestContainer, handle root updates * Remove string/number serialization attempts since Fiber ensures all textInstances are strings * Return full fragments in toJSON * Test Renderer remove TestComponent instances for simple objects * Update babylon for exact object type syntax * Use $$typeof because clarity > punching ducks. * Minor Flow annotation tweaks * Tweak style, types, and naming * Fix typo
2017-01-12 03:19:32 +08:00
import {getPublicInstance} from './ReactTestHostConfig';
import {ConcurrentRoot, LegacyRoot} from 'react-reconciler/src/ReactRootTags';
import {allowConcurrentByDefault} from 'shared/ReactFeatureFlags';
const act = React.unstable_act;
Decouple public, internal act implementation (#19745) In the next major release, we intend to drop support for using the `act` testing helper in production. (It already fires a warning.) The rationale is that, in order for `act` to work, you must either mock the testing environment or add extra logic at runtime. Mocking the testing environment isn't ideal because it requires extra set up for the user. Extra logic at runtime is fine only in development mode — we don't want to slow down the production builds. Since most people only run their tests in development mode, dropping support for production should be fine; if there's demand, we can add it back later using a special testing build that is identical to the production build except for the additional testing logic. One blocker for removing production support is that we currently use `act` to test React itself. We must test React in both development and production modes. So, the solution is to fork `act` into separate public and internal implementations: - *public implementation of `act`* – exposed to users, only works in development mode, uses special runtime logic, does not support partial rendering - *internal implementation of `act`* – private, works in both development and productionm modes, only used by the React Core test suite, uses no special runtime logic, supports partial rendering (i.e. `toFlushAndYieldThrough`) The internal implementation should mostly match the public implementation's behavior, but since it's a private API, it doesn't have to match exactly. It works by mocking the test environment: it uses a mock build of Scheduler to flush rendering tasks, and Jest's mock timers to flush Suspense placeholders. --- In this first commit, I've added the internal forks of `act` and migrated our tests to use them. The public `act` implementation is unaffected for now; I will leave refactoring/clean-up for a later step.
2020-09-08 23:11:45 +08:00
// TODO: Remove from public bundle
type TestRendererOptions = {
createNodeMock: (element: React$Element<any>) => any,
unstable_isConcurrent: boolean,
unstable_strictMode: boolean,
unstable_concurrentUpdatesByDefault: boolean,
...
};
[Fiber] Implement test renderer (#8628) * ReactTestRenderer move current impl to stack dir * ReactTestRenderer on fiber: commence! * ReactTestRenderer: most non-ref/non-public-instance tests are passing * Move ReactTestFiberComponent functions from Renderer to Component file * test renderer: get rid of private root containers and root Maps * TestRenderer: switch impl based on ReactDOMFeatureFlag.useFiber * ReactTestRenderer: inline component creation * ReactTestRenderer: return to pristine original glory (+ Fiber for error order difference) * TestRendererFiber: use a simple class as TestComponentInstances * Add `getPublicInstance` to support TestRenderer `createNodeMock` * Rename files to end. Update for `mountContainer->createContainer` change * test renderer return same object to prevent unnecessary context pushing/popping * Fiber HostConfig add getPublicInstance. This should be the identity fn everywhere except the test renderer * appease flow * Initial cleanup from sleepy work * unstable_batchedUpdates * Stack test renderer: cache nodeMock to not call on unmount * add public instance type parameter to the reconciler * test renderer: set _nodeMock when mounted * More cleanup * Add test cases for root fragments and (maybe?) root text nodes * Fix the npm package build Explicitly require the Stack version by default. Add a separate entry point for Fiber. We don't add fiber.js to the package yet since it's considered internal until React 16. * Relax the ref type from Object to mixed This seems like the most straightforward way to support getPublicInstance for test renderer. * Remove accidental newline * test renderer: unify TestComponent and TestContainer, handle root updates * Remove string/number serialization attempts since Fiber ensures all textInstances are strings * Return full fragments in toJSON * Test Renderer remove TestComponent instances for simple objects * Update babylon for exact object type syntax * Use $$typeof because clarity > punching ducks. * Minor Flow annotation tweaks * Tweak style, types, and naming * Fix typo
2017-01-12 03:19:32 +08:00
type ReactTestRendererJSON = {|
2017-03-14 08:05:18 +08:00
type: string,
props: {[propName: string]: any, ...},
2017-03-14 08:05:18 +08:00
children: null | Array<ReactTestRendererNode>,
$$typeof?: Symbol, // Optional because we add it with defineProperty().
[Fiber] Implement test renderer (#8628) * ReactTestRenderer move current impl to stack dir * ReactTestRenderer on fiber: commence! * ReactTestRenderer: most non-ref/non-public-instance tests are passing * Move ReactTestFiberComponent functions from Renderer to Component file * test renderer: get rid of private root containers and root Maps * TestRenderer: switch impl based on ReactDOMFeatureFlag.useFiber * ReactTestRenderer: inline component creation * ReactTestRenderer: return to pristine original glory (+ Fiber for error order difference) * TestRendererFiber: use a simple class as TestComponentInstances * Add `getPublicInstance` to support TestRenderer `createNodeMock` * Rename files to end. Update for `mountContainer->createContainer` change * test renderer return same object to prevent unnecessary context pushing/popping * Fiber HostConfig add getPublicInstance. This should be the identity fn everywhere except the test renderer * appease flow * Initial cleanup from sleepy work * unstable_batchedUpdates * Stack test renderer: cache nodeMock to not call on unmount * add public instance type parameter to the reconciler * test renderer: set _nodeMock when mounted * More cleanup * Add test cases for root fragments and (maybe?) root text nodes * Fix the npm package build Explicitly require the Stack version by default. Add a separate entry point for Fiber. We don't add fiber.js to the package yet since it's considered internal until React 16. * Relax the ref type from Object to mixed This seems like the most straightforward way to support getPublicInstance for test renderer. * Remove accidental newline * test renderer: unify TestComponent and TestContainer, handle root updates * Remove string/number serialization attempts since Fiber ensures all textInstances are strings * Return full fragments in toJSON * Test Renderer remove TestComponent instances for simple objects * Update babylon for exact object type syntax * Use $$typeof because clarity > punching ducks. * Minor Flow annotation tweaks * Tweak style, types, and naming * Fix typo
2017-01-12 03:19:32 +08:00
|};
type ReactTestRendererNode = ReactTestRendererJSON | string;
type FindOptions = $Shape<{
// performs a "greedy" search: if a matching node is found, will continue
// to search within the matching node's children. (default: true)
deep: boolean,
...
}>;
export type Predicate = (node: ReactTestInstance) => ?boolean;
const defaultTestOptions = {
[Fiber] Implement test renderer (#8628) * ReactTestRenderer move current impl to stack dir * ReactTestRenderer on fiber: commence! * ReactTestRenderer: most non-ref/non-public-instance tests are passing * Move ReactTestFiberComponent functions from Renderer to Component file * test renderer: get rid of private root containers and root Maps * TestRenderer: switch impl based on ReactDOMFeatureFlag.useFiber * ReactTestRenderer: inline component creation * ReactTestRenderer: return to pristine original glory (+ Fiber for error order difference) * TestRendererFiber: use a simple class as TestComponentInstances * Add `getPublicInstance` to support TestRenderer `createNodeMock` * Rename files to end. Update for `mountContainer->createContainer` change * test renderer return same object to prevent unnecessary context pushing/popping * Fiber HostConfig add getPublicInstance. This should be the identity fn everywhere except the test renderer * appease flow * Initial cleanup from sleepy work * unstable_batchedUpdates * Stack test renderer: cache nodeMock to not call on unmount * add public instance type parameter to the reconciler * test renderer: set _nodeMock when mounted * More cleanup * Add test cases for root fragments and (maybe?) root text nodes * Fix the npm package build Explicitly require the Stack version by default. Add a separate entry point for Fiber. We don't add fiber.js to the package yet since it's considered internal until React 16. * Relax the ref type from Object to mixed This seems like the most straightforward way to support getPublicInstance for test renderer. * Remove accidental newline * test renderer: unify TestComponent and TestContainer, handle root updates * Remove string/number serialization attempts since Fiber ensures all textInstances are strings * Return full fragments in toJSON * Test Renderer remove TestComponent instances for simple objects * Update babylon for exact object type syntax * Use $$typeof because clarity > punching ducks. * Minor Flow annotation tweaks * Tweak style, types, and naming * Fix typo
2017-01-12 03:19:32 +08:00
createNodeMock: function() {
return null;
},
};
Hide timed-out children instead of deleting them so their state is preserved (#13823) * Store the start time on `updateQueue` instead of `stateNode` Originally I did this to free the `stateNode` field to store a second set of children. I don't we'll need this anymore, since we use fragment fibers instead. But I still think using `updateQueue` makes more sense so I'll leave this in. * Use fragment fibers to keep the primary and fallback children separate If the children timeout, we switch to showing the fallback children in place of the "primary" children. However, we don't want to delete the primary children because then their state will be lost (both the React state and the host state, e.g. uncontrolled form inputs). Instead we keep them mounted and hide them. Both the fallback children AND the primary children are rendered at the same time. Once the primary children are un-suspended, we can delete the fallback children — don't need to preserve their state. The two sets of children are siblings in the host environment, but semantically, for purposes of reconciliation, they are two separate sets. So we store them using two fragment fibers. However, we want to avoid allocating extra fibers for every placeholder. They're only necessary when the children time out, because that's the only time when both sets are mounted. So, the extra fragment fibers are only used if the children time out. Otherwise, we render the primary children directly. This requires some custom reconciliation logic to preserve the state of the primary children. It's essentially a very basic form of re-parenting. * Use `memoizedState` to store various pieces of SuspenseComponent's state SuspenseComponent has three pieces of state: - alreadyCaptured: Whether a component in the child subtree already suspended. If true, subsequent suspends should bubble up to the next boundary. - didTimeout: Whether the boundary renders the primary or fallback children. This is separate from `alreadyCaptured` because outside of strict mode, when a boundary times out, the first commit renders the primary children in an incomplete state, then performs a second commit to switch the fallback. In that first commit, `alreadyCaptured` is false and `didTimeout` is true. - timedOutAt: The time at which the boundary timed out. This is separate from `didTimeout` because it's not set unless the boundary actually commits. These were previously spread across several fields. This happens to make the non-strict case a bit less hacky; the logic for that special case is now mostly localized to the UnwindWork module. * Hide timed-out Suspense children When a subtree takes too long to load, we swap its contents out for a fallback to unblock the rest of the tree. Because we don't want to lose the state of the timed out view, we shouldn't actually delete the nodes from the tree. Instead, we'll keep them mounted and hide them visually. When the subtree is unblocked, we un-hide it, having preserved the existing state. Adds additional host config methods. For mutation mode: - hideInstance - hideTextInstance - unhideInstance - unhideTextInstance For persistent mode: - cloneHiddenInstance - cloneUnhiddenInstance - createHiddenTextInstance I've only implemented the new methods in the noop and test renderers. I'll implement them in the other renderers in subsequent commits. * Include `hidden` prop in noop renderer's output This will be used in subsequent commits to test that timed-out children are properly hidden. Also adds getChildrenAsJSX() method as an alternative to using getChildren(). (Ideally all our tests would use test renderer #oneday.) * Implement hide/unhide host config methods for DOM renderer For DOM nodes, we hide using `el.style.display = 'none'`. Text nodes don't have style, so we hide using `text.textContent = ''`. * Implement hide/unhide host config methods for Art renderer * Create DOM fixture that tests state preservation of timed out content * Account for class components that suspend outside concurrent mode Need to distinguish mount from update. An unfortunate edge case :( * Fork appendAllChildren between persistent and mutation mode * Remove redundant check for existence of el.style * Schedule placement effect on indeterminate components In non-concurrent mode, indeterminate fibers may commit in an inconsistent state. But when they update, we should throw out the old fiber and start fresh. Which means the new fiber needs a placement effect. * Pass null instead of current everywhere in mountIndeterminateComponent
2018-10-19 06:37:16 +08:00
function toJSON(inst: Instance | TextInstance): ReactTestRendererNode | null {
if (inst.isHidden) {
// Omit timed out children from output entirely. This seems like the least
// surprising behavior. We could perhaps add a separate API that includes
// them, if it turns out people need it.
return null;
}
[Fiber] Implement test renderer (#8628) * ReactTestRenderer move current impl to stack dir * ReactTestRenderer on fiber: commence! * ReactTestRenderer: most non-ref/non-public-instance tests are passing * Move ReactTestFiberComponent functions from Renderer to Component file * test renderer: get rid of private root containers and root Maps * TestRenderer: switch impl based on ReactDOMFeatureFlag.useFiber * ReactTestRenderer: inline component creation * ReactTestRenderer: return to pristine original glory (+ Fiber for error order difference) * TestRendererFiber: use a simple class as TestComponentInstances * Add `getPublicInstance` to support TestRenderer `createNodeMock` * Rename files to end. Update for `mountContainer->createContainer` change * test renderer return same object to prevent unnecessary context pushing/popping * Fiber HostConfig add getPublicInstance. This should be the identity fn everywhere except the test renderer * appease flow * Initial cleanup from sleepy work * unstable_batchedUpdates * Stack test renderer: cache nodeMock to not call on unmount * add public instance type parameter to the reconciler * test renderer: set _nodeMock when mounted * More cleanup * Add test cases for root fragments and (maybe?) root text nodes * Fix the npm package build Explicitly require the Stack version by default. Add a separate entry point for Fiber. We don't add fiber.js to the package yet since it's considered internal until React 16. * Relax the ref type from Object to mixed This seems like the most straightforward way to support getPublicInstance for test renderer. * Remove accidental newline * test renderer: unify TestComponent and TestContainer, handle root updates * Remove string/number serialization attempts since Fiber ensures all textInstances are strings * Return full fragments in toJSON * Test Renderer remove TestComponent instances for simple objects * Update babylon for exact object type syntax * Use $$typeof because clarity > punching ducks. * Minor Flow annotation tweaks * Tweak style, types, and naming * Fix typo
2017-01-12 03:19:32 +08:00
switch (inst.tag) {
case 'TEXT':
return inst.text;
Hide timed-out children instead of deleting them so their state is preserved (#13823) * Store the start time on `updateQueue` instead of `stateNode` Originally I did this to free the `stateNode` field to store a second set of children. I don't we'll need this anymore, since we use fragment fibers instead. But I still think using `updateQueue` makes more sense so I'll leave this in. * Use fragment fibers to keep the primary and fallback children separate If the children timeout, we switch to showing the fallback children in place of the "primary" children. However, we don't want to delete the primary children because then their state will be lost (both the React state and the host state, e.g. uncontrolled form inputs). Instead we keep them mounted and hide them. Both the fallback children AND the primary children are rendered at the same time. Once the primary children are un-suspended, we can delete the fallback children — don't need to preserve their state. The two sets of children are siblings in the host environment, but semantically, for purposes of reconciliation, they are two separate sets. So we store them using two fragment fibers. However, we want to avoid allocating extra fibers for every placeholder. They're only necessary when the children time out, because that's the only time when both sets are mounted. So, the extra fragment fibers are only used if the children time out. Otherwise, we render the primary children directly. This requires some custom reconciliation logic to preserve the state of the primary children. It's essentially a very basic form of re-parenting. * Use `memoizedState` to store various pieces of SuspenseComponent's state SuspenseComponent has three pieces of state: - alreadyCaptured: Whether a component in the child subtree already suspended. If true, subsequent suspends should bubble up to the next boundary. - didTimeout: Whether the boundary renders the primary or fallback children. This is separate from `alreadyCaptured` because outside of strict mode, when a boundary times out, the first commit renders the primary children in an incomplete state, then performs a second commit to switch the fallback. In that first commit, `alreadyCaptured` is false and `didTimeout` is true. - timedOutAt: The time at which the boundary timed out. This is separate from `didTimeout` because it's not set unless the boundary actually commits. These were previously spread across several fields. This happens to make the non-strict case a bit less hacky; the logic for that special case is now mostly localized to the UnwindWork module. * Hide timed-out Suspense children When a subtree takes too long to load, we swap its contents out for a fallback to unblock the rest of the tree. Because we don't want to lose the state of the timed out view, we shouldn't actually delete the nodes from the tree. Instead, we'll keep them mounted and hide them visually. When the subtree is unblocked, we un-hide it, having preserved the existing state. Adds additional host config methods. For mutation mode: - hideInstance - hideTextInstance - unhideInstance - unhideTextInstance For persistent mode: - cloneHiddenInstance - cloneUnhiddenInstance - createHiddenTextInstance I've only implemented the new methods in the noop and test renderers. I'll implement them in the other renderers in subsequent commits. * Include `hidden` prop in noop renderer's output This will be used in subsequent commits to test that timed-out children are properly hidden. Also adds getChildrenAsJSX() method as an alternative to using getChildren(). (Ideally all our tests would use test renderer #oneday.) * Implement hide/unhide host config methods for DOM renderer For DOM nodes, we hide using `el.style.display = 'none'`. Text nodes don't have style, so we hide using `text.textContent = ''`. * Implement hide/unhide host config methods for Art renderer * Create DOM fixture that tests state preservation of timed out content * Account for class components that suspend outside concurrent mode Need to distinguish mount from update. An unfortunate edge case :( * Fork appendAllChildren between persistent and mutation mode * Remove redundant check for existence of el.style * Schedule placement effect on indeterminate components In non-concurrent mode, indeterminate fibers may commit in an inconsistent state. But when they update, we should throw out the old fiber and start fresh. Which means the new fiber needs a placement effect. * Pass null instead of current everywhere in mountIndeterminateComponent
2018-10-19 06:37:16 +08:00
case 'INSTANCE': {
[Fiber] Implement test renderer (#8628) * ReactTestRenderer move current impl to stack dir * ReactTestRenderer on fiber: commence! * ReactTestRenderer: most non-ref/non-public-instance tests are passing * Move ReactTestFiberComponent functions from Renderer to Component file * test renderer: get rid of private root containers and root Maps * TestRenderer: switch impl based on ReactDOMFeatureFlag.useFiber * ReactTestRenderer: inline component creation * ReactTestRenderer: return to pristine original glory (+ Fiber for error order difference) * TestRendererFiber: use a simple class as TestComponentInstances * Add `getPublicInstance` to support TestRenderer `createNodeMock` * Rename files to end. Update for `mountContainer->createContainer` change * test renderer return same object to prevent unnecessary context pushing/popping * Fiber HostConfig add getPublicInstance. This should be the identity fn everywhere except the test renderer * appease flow * Initial cleanup from sleepy work * unstable_batchedUpdates * Stack test renderer: cache nodeMock to not call on unmount * add public instance type parameter to the reconciler * test renderer: set _nodeMock when mounted * More cleanup * Add test cases for root fragments and (maybe?) root text nodes * Fix the npm package build Explicitly require the Stack version by default. Add a separate entry point for Fiber. We don't add fiber.js to the package yet since it's considered internal until React 16. * Relax the ref type from Object to mixed This seems like the most straightforward way to support getPublicInstance for test renderer. * Remove accidental newline * test renderer: unify TestComponent and TestContainer, handle root updates * Remove string/number serialization attempts since Fiber ensures all textInstances are strings * Return full fragments in toJSON * Test Renderer remove TestComponent instances for simple objects * Update babylon for exact object type syntax * Use $$typeof because clarity > punching ducks. * Minor Flow annotation tweaks * Tweak style, types, and naming * Fix typo
2017-01-12 03:19:32 +08:00
/* eslint-disable no-unused-vars */
// We don't include the `children` prop in JSON.
// Instead, we will include the actual rendered children.
const {children, ...props} = inst.props;
/* eslint-enable */
let renderedChildren = null;
if (inst.children && inst.children.length) {
Hide timed-out children instead of deleting them so their state is preserved (#13823) * Store the start time on `updateQueue` instead of `stateNode` Originally I did this to free the `stateNode` field to store a second set of children. I don't we'll need this anymore, since we use fragment fibers instead. But I still think using `updateQueue` makes more sense so I'll leave this in. * Use fragment fibers to keep the primary and fallback children separate If the children timeout, we switch to showing the fallback children in place of the "primary" children. However, we don't want to delete the primary children because then their state will be lost (both the React state and the host state, e.g. uncontrolled form inputs). Instead we keep them mounted and hide them. Both the fallback children AND the primary children are rendered at the same time. Once the primary children are un-suspended, we can delete the fallback children — don't need to preserve their state. The two sets of children are siblings in the host environment, but semantically, for purposes of reconciliation, they are two separate sets. So we store them using two fragment fibers. However, we want to avoid allocating extra fibers for every placeholder. They're only necessary when the children time out, because that's the only time when both sets are mounted. So, the extra fragment fibers are only used if the children time out. Otherwise, we render the primary children directly. This requires some custom reconciliation logic to preserve the state of the primary children. It's essentially a very basic form of re-parenting. * Use `memoizedState` to store various pieces of SuspenseComponent's state SuspenseComponent has three pieces of state: - alreadyCaptured: Whether a component in the child subtree already suspended. If true, subsequent suspends should bubble up to the next boundary. - didTimeout: Whether the boundary renders the primary or fallback children. This is separate from `alreadyCaptured` because outside of strict mode, when a boundary times out, the first commit renders the primary children in an incomplete state, then performs a second commit to switch the fallback. In that first commit, `alreadyCaptured` is false and `didTimeout` is true. - timedOutAt: The time at which the boundary timed out. This is separate from `didTimeout` because it's not set unless the boundary actually commits. These were previously spread across several fields. This happens to make the non-strict case a bit less hacky; the logic for that special case is now mostly localized to the UnwindWork module. * Hide timed-out Suspense children When a subtree takes too long to load, we swap its contents out for a fallback to unblock the rest of the tree. Because we don't want to lose the state of the timed out view, we shouldn't actually delete the nodes from the tree. Instead, we'll keep them mounted and hide them visually. When the subtree is unblocked, we un-hide it, having preserved the existing state. Adds additional host config methods. For mutation mode: - hideInstance - hideTextInstance - unhideInstance - unhideTextInstance For persistent mode: - cloneHiddenInstance - cloneUnhiddenInstance - createHiddenTextInstance I've only implemented the new methods in the noop and test renderers. I'll implement them in the other renderers in subsequent commits. * Include `hidden` prop in noop renderer's output This will be used in subsequent commits to test that timed-out children are properly hidden. Also adds getChildrenAsJSX() method as an alternative to using getChildren(). (Ideally all our tests would use test renderer #oneday.) * Implement hide/unhide host config methods for DOM renderer For DOM nodes, we hide using `el.style.display = 'none'`. Text nodes don't have style, so we hide using `text.textContent = ''`. * Implement hide/unhide host config methods for Art renderer * Create DOM fixture that tests state preservation of timed out content * Account for class components that suspend outside concurrent mode Need to distinguish mount from update. An unfortunate edge case :( * Fork appendAllChildren between persistent and mutation mode * Remove redundant check for existence of el.style * Schedule placement effect on indeterminate components In non-concurrent mode, indeterminate fibers may commit in an inconsistent state. But when they update, we should throw out the old fiber and start fresh. Which means the new fiber needs a placement effect. * Pass null instead of current everywhere in mountIndeterminateComponent
2018-10-19 06:37:16 +08:00
for (let i = 0; i < inst.children.length; i++) {
const renderedChild = toJSON(inst.children[i]);
if (renderedChild !== null) {
if (renderedChildren === null) {
renderedChildren = [renderedChild];
} else {
renderedChildren.push(renderedChild);
}
}
}
[Fiber] Implement test renderer (#8628) * ReactTestRenderer move current impl to stack dir * ReactTestRenderer on fiber: commence! * ReactTestRenderer: most non-ref/non-public-instance tests are passing * Move ReactTestFiberComponent functions from Renderer to Component file * test renderer: get rid of private root containers and root Maps * TestRenderer: switch impl based on ReactDOMFeatureFlag.useFiber * ReactTestRenderer: inline component creation * ReactTestRenderer: return to pristine original glory (+ Fiber for error order difference) * TestRendererFiber: use a simple class as TestComponentInstances * Add `getPublicInstance` to support TestRenderer `createNodeMock` * Rename files to end. Update for `mountContainer->createContainer` change * test renderer return same object to prevent unnecessary context pushing/popping * Fiber HostConfig add getPublicInstance. This should be the identity fn everywhere except the test renderer * appease flow * Initial cleanup from sleepy work * unstable_batchedUpdates * Stack test renderer: cache nodeMock to not call on unmount * add public instance type parameter to the reconciler * test renderer: set _nodeMock when mounted * More cleanup * Add test cases for root fragments and (maybe?) root text nodes * Fix the npm package build Explicitly require the Stack version by default. Add a separate entry point for Fiber. We don't add fiber.js to the package yet since it's considered internal until React 16. * Relax the ref type from Object to mixed This seems like the most straightforward way to support getPublicInstance for test renderer. * Remove accidental newline * test renderer: unify TestComponent and TestContainer, handle root updates * Remove string/number serialization attempts since Fiber ensures all textInstances are strings * Return full fragments in toJSON * Test Renderer remove TestComponent instances for simple objects * Update babylon for exact object type syntax * Use $$typeof because clarity > punching ducks. * Minor Flow annotation tweaks * Tweak style, types, and naming * Fix typo
2017-01-12 03:19:32 +08:00
}
2017-03-14 08:05:18 +08:00
const json: ReactTestRendererJSON = {
[Fiber] Implement test renderer (#8628) * ReactTestRenderer move current impl to stack dir * ReactTestRenderer on fiber: commence! * ReactTestRenderer: most non-ref/non-public-instance tests are passing * Move ReactTestFiberComponent functions from Renderer to Component file * test renderer: get rid of private root containers and root Maps * TestRenderer: switch impl based on ReactDOMFeatureFlag.useFiber * ReactTestRenderer: inline component creation * ReactTestRenderer: return to pristine original glory (+ Fiber for error order difference) * TestRendererFiber: use a simple class as TestComponentInstances * Add `getPublicInstance` to support TestRenderer `createNodeMock` * Rename files to end. Update for `mountContainer->createContainer` change * test renderer return same object to prevent unnecessary context pushing/popping * Fiber HostConfig add getPublicInstance. This should be the identity fn everywhere except the test renderer * appease flow * Initial cleanup from sleepy work * unstable_batchedUpdates * Stack test renderer: cache nodeMock to not call on unmount * add public instance type parameter to the reconciler * test renderer: set _nodeMock when mounted * More cleanup * Add test cases for root fragments and (maybe?) root text nodes * Fix the npm package build Explicitly require the Stack version by default. Add a separate entry point for Fiber. We don't add fiber.js to the package yet since it's considered internal until React 16. * Relax the ref type from Object to mixed This seems like the most straightforward way to support getPublicInstance for test renderer. * Remove accidental newline * test renderer: unify TestComponent and TestContainer, handle root updates * Remove string/number serialization attempts since Fiber ensures all textInstances are strings * Return full fragments in toJSON * Test Renderer remove TestComponent instances for simple objects * Update babylon for exact object type syntax * Use $$typeof because clarity > punching ducks. * Minor Flow annotation tweaks * Tweak style, types, and naming * Fix typo
2017-01-12 03:19:32 +08:00
type: inst.type,
props: props,
children: renderedChildren,
};
2017-03-14 08:05:18 +08:00
Object.defineProperty(json, '$$typeof', {
value: Symbol.for('react.test.json'),
});
[Fiber] Implement test renderer (#8628) * ReactTestRenderer move current impl to stack dir * ReactTestRenderer on fiber: commence! * ReactTestRenderer: most non-ref/non-public-instance tests are passing * Move ReactTestFiberComponent functions from Renderer to Component file * test renderer: get rid of private root containers and root Maps * TestRenderer: switch impl based on ReactDOMFeatureFlag.useFiber * ReactTestRenderer: inline component creation * ReactTestRenderer: return to pristine original glory (+ Fiber for error order difference) * TestRendererFiber: use a simple class as TestComponentInstances * Add `getPublicInstance` to support TestRenderer `createNodeMock` * Rename files to end. Update for `mountContainer->createContainer` change * test renderer return same object to prevent unnecessary context pushing/popping * Fiber HostConfig add getPublicInstance. This should be the identity fn everywhere except the test renderer * appease flow * Initial cleanup from sleepy work * unstable_batchedUpdates * Stack test renderer: cache nodeMock to not call on unmount * add public instance type parameter to the reconciler * test renderer: set _nodeMock when mounted * More cleanup * Add test cases for root fragments and (maybe?) root text nodes * Fix the npm package build Explicitly require the Stack version by default. Add a separate entry point for Fiber. We don't add fiber.js to the package yet since it's considered internal until React 16. * Relax the ref type from Object to mixed This seems like the most straightforward way to support getPublicInstance for test renderer. * Remove accidental newline * test renderer: unify TestComponent and TestContainer, handle root updates * Remove string/number serialization attempts since Fiber ensures all textInstances are strings * Return full fragments in toJSON * Test Renderer remove TestComponent instances for simple objects * Update babylon for exact object type syntax * Use $$typeof because clarity > punching ducks. * Minor Flow annotation tweaks * Tweak style, types, and naming * Fix typo
2017-01-12 03:19:32 +08:00
return json;
Hide timed-out children instead of deleting them so their state is preserved (#13823) * Store the start time on `updateQueue` instead of `stateNode` Originally I did this to free the `stateNode` field to store a second set of children. I don't we'll need this anymore, since we use fragment fibers instead. But I still think using `updateQueue` makes more sense so I'll leave this in. * Use fragment fibers to keep the primary and fallback children separate If the children timeout, we switch to showing the fallback children in place of the "primary" children. However, we don't want to delete the primary children because then their state will be lost (both the React state and the host state, e.g. uncontrolled form inputs). Instead we keep them mounted and hide them. Both the fallback children AND the primary children are rendered at the same time. Once the primary children are un-suspended, we can delete the fallback children — don't need to preserve their state. The two sets of children are siblings in the host environment, but semantically, for purposes of reconciliation, they are two separate sets. So we store them using two fragment fibers. However, we want to avoid allocating extra fibers for every placeholder. They're only necessary when the children time out, because that's the only time when both sets are mounted. So, the extra fragment fibers are only used if the children time out. Otherwise, we render the primary children directly. This requires some custom reconciliation logic to preserve the state of the primary children. It's essentially a very basic form of re-parenting. * Use `memoizedState` to store various pieces of SuspenseComponent's state SuspenseComponent has three pieces of state: - alreadyCaptured: Whether a component in the child subtree already suspended. If true, subsequent suspends should bubble up to the next boundary. - didTimeout: Whether the boundary renders the primary or fallback children. This is separate from `alreadyCaptured` because outside of strict mode, when a boundary times out, the first commit renders the primary children in an incomplete state, then performs a second commit to switch the fallback. In that first commit, `alreadyCaptured` is false and `didTimeout` is true. - timedOutAt: The time at which the boundary timed out. This is separate from `didTimeout` because it's not set unless the boundary actually commits. These were previously spread across several fields. This happens to make the non-strict case a bit less hacky; the logic for that special case is now mostly localized to the UnwindWork module. * Hide timed-out Suspense children When a subtree takes too long to load, we swap its contents out for a fallback to unblock the rest of the tree. Because we don't want to lose the state of the timed out view, we shouldn't actually delete the nodes from the tree. Instead, we'll keep them mounted and hide them visually. When the subtree is unblocked, we un-hide it, having preserved the existing state. Adds additional host config methods. For mutation mode: - hideInstance - hideTextInstance - unhideInstance - unhideTextInstance For persistent mode: - cloneHiddenInstance - cloneUnhiddenInstance - createHiddenTextInstance I've only implemented the new methods in the noop and test renderers. I'll implement them in the other renderers in subsequent commits. * Include `hidden` prop in noop renderer's output This will be used in subsequent commits to test that timed-out children are properly hidden. Also adds getChildrenAsJSX() method as an alternative to using getChildren(). (Ideally all our tests would use test renderer #oneday.) * Implement hide/unhide host config methods for DOM renderer For DOM nodes, we hide using `el.style.display = 'none'`. Text nodes don't have style, so we hide using `text.textContent = ''`. * Implement hide/unhide host config methods for Art renderer * Create DOM fixture that tests state preservation of timed out content * Account for class components that suspend outside concurrent mode Need to distinguish mount from update. An unfortunate edge case :( * Fork appendAllChildren between persistent and mutation mode * Remove redundant check for existence of el.style * Schedule placement effect on indeterminate components In non-concurrent mode, indeterminate fibers may commit in an inconsistent state. But when they update, we should throw out the old fiber and start fresh. Which means the new fiber needs a placement effect. * Pass null instead of current everywhere in mountIndeterminateComponent
2018-10-19 06:37:16 +08:00
}
[Fiber] Implement test renderer (#8628) * ReactTestRenderer move current impl to stack dir * ReactTestRenderer on fiber: commence! * ReactTestRenderer: most non-ref/non-public-instance tests are passing * Move ReactTestFiberComponent functions from Renderer to Component file * test renderer: get rid of private root containers and root Maps * TestRenderer: switch impl based on ReactDOMFeatureFlag.useFiber * ReactTestRenderer: inline component creation * ReactTestRenderer: return to pristine original glory (+ Fiber for error order difference) * TestRendererFiber: use a simple class as TestComponentInstances * Add `getPublicInstance` to support TestRenderer `createNodeMock` * Rename files to end. Update for `mountContainer->createContainer` change * test renderer return same object to prevent unnecessary context pushing/popping * Fiber HostConfig add getPublicInstance. This should be the identity fn everywhere except the test renderer * appease flow * Initial cleanup from sleepy work * unstable_batchedUpdates * Stack test renderer: cache nodeMock to not call on unmount * add public instance type parameter to the reconciler * test renderer: set _nodeMock when mounted * More cleanup * Add test cases for root fragments and (maybe?) root text nodes * Fix the npm package build Explicitly require the Stack version by default. Add a separate entry point for Fiber. We don't add fiber.js to the package yet since it's considered internal until React 16. * Relax the ref type from Object to mixed This seems like the most straightforward way to support getPublicInstance for test renderer. * Remove accidental newline * test renderer: unify TestComponent and TestContainer, handle root updates * Remove string/number serialization attempts since Fiber ensures all textInstances are strings * Return full fragments in toJSON * Test Renderer remove TestComponent instances for simple objects * Update babylon for exact object type syntax * Use $$typeof because clarity > punching ducks. * Minor Flow annotation tweaks * Tweak style, types, and naming * Fix typo
2017-01-12 03:19:32 +08:00
default:
throw new Error(`Unexpected node type in toJSON: ${inst.tag}`);
}
}
function childrenToTree(node) {
if (!node) {
return null;
}
const children = nodeAndSiblingsArray(node);
if (children.length === 0) {
return null;
} else if (children.length === 1) {
return toTree(children[0]);
}
return flatten(children.map(toTree));
}
function nodeAndSiblingsArray(nodeWithSibling) {
const array = [];
let node = nodeWithSibling;
while (node != null) {
array.push(node);
node = node.sibling;
}
return array;
}
function flatten(arr) {
const result = [];
const stack = [{i: 0, array: arr}];
while (stack.length) {
const n = stack.pop();
while (n.i < n.array.length) {
const el = n.array[n.i];
n.i += 1;
if (isArray(el)) {
stack.push(n);
stack.push({i: 0, array: el});
break;
}
result.push(el);
}
}
return result;
}
function toTree(node: ?Fiber) {
if (node == null) {
return null;
}
switch (node.tag) {
case HostRoot:
return childrenToTree(node.child);
case HostPortal:
return childrenToTree(node.child);
case ClassComponent:
return {
nodeType: 'component',
type: node.type,
2017-03-14 08:05:18 +08:00
props: {...node.memoizedProps},
instance: node.stateNode,
rendered: childrenToTree(node.child),
};
case FunctionComponent:
2018-10-21 00:46:23 +08:00
case SimpleMemoComponent:
return {
nodeType: 'component',
type: node.type,
2017-03-14 08:05:18 +08:00
props: {...node.memoizedProps},
instance: null,
rendered: childrenToTree(node.child),
};
case HostComponent: {
return {
nodeType: 'host',
type: node.type,
2017-03-14 08:05:18 +08:00
props: {...node.memoizedProps},
instance: null, // TODO: use createNodeMock here somehow?
rendered: flatten(nodeAndSiblingsArray(node.child).map(toTree)),
};
}
case HostText:
return node.stateNode.text;
case Fragment:
case ContextProvider:
case ContextConsumer:
case Mode:
case Profiler:
case ForwardRef:
2018-10-21 00:46:23 +08:00
case MemoComponent:
case IncompleteClassComponent:
case ScopeComponent:
return childrenToTree(node.child);
default:
[RFC] Codemod invariant -> throw new Error (#22435) * Hoist error codes import to module scope When this code was written, the error codes map (`codes.json`) was created on-the-fly, so we had to lazily require from inside the visitor. Because `codes.json` is now checked into source, we can import it a single time in module scope. * Minify error constructors in production We use a script to minify our error messages in production. Each message is assigned an error code, defined in `scripts/error-codes/codes.json`. Then our build script replaces the messages with a link to our error decoder page, e.g. https://reactjs.org/docs/error-decoder.html/?invariant=92 This enables us to write helpful error messages without increasing the bundle size. Right now, the script only works for `invariant` calls. It does not work if you throw an Error object. This is an old Facebookism that we don't really need, other than the fact that our error minification script relies on it. So, I've updated the script to minify error constructors, too: Input: Error(`A ${adj} message that contains ${noun}`); Output: Error(formatProdErrorMessage(ERR_CODE, adj, noun)); It only works for constructors that are literally named Error, though we could add support for other names, too. As a next step, I will add a lint rule to enforce that errors written this way must have a corresponding error code. * Minify "no fallback UI specified" error in prod This error message wasn't being minified because it doesn't use invariant. The reason it didn't use invariant is because this particular error is created without begin thrown — it doesn't need to be thrown because it's located inside the error handling part of the runtime. Now that the error minification script supports Error constructors, we can minify it by assigning it a production error code in `scripts/error-codes/codes.json`. To support the use of Error constructors more generally, I will add a lint rule that enforces each message has a corresponding error code. * Lint rule to detect unminified errors Adds a lint rule that detects when an Error constructor is used without a corresponding production error code. We already have this for `invariant`, but not for regular errors, i.e. `throw new Error(msg)`. There's also nothing that enforces the use of `invariant` besides convention. There are some packages where we don't care to minify errors. These are packages that run in environments where bundle size is not a concern, like react-pg. I added an override in the ESLint config to ignore these. * Temporarily add invariant codemod script I'm adding this codemod to the repo temporarily, but I'll revert it in the same PR. That way we don't have to check it in but it's still accessible (via the PR) if we need it later. * [Automated] Codemod invariant -> Error This commit contains only automated changes: npx jscodeshift -t scripts/codemod-invariant.js packages --ignore-pattern="node_modules/**/*" yarn linc --fix yarn prettier I will do any manual touch ups in separate commits so they're easier to review. * Remove temporary codemod script This reverts the codemod script and ESLint config I added temporarily in order to perform the invariant codemod. * Manual touch ups A few manual changes I made after the codemod ran. * Enable error code transform per package Currently we're not consistent about which packages should have their errors minified in production and which ones should. This adds a field to the bundle configuration to control whether to apply the transform. We should decide what the criteria is going forward. I think it's probably a good idea to minify any package that gets sent over the network. So yes to modules that run in the browser, and no to modules that run on the server and during development only.
2021-10-01 03:01:28 +08:00
throw new Error(
`toTree() does not yet know how to handle nodes with tag=${node.tag}`,
);
}
}
const validWrapperTypes = new Set([
FunctionComponent,
ClassComponent,
HostComponent,
ForwardRef,
2018-10-21 00:46:23 +08:00
MemoComponent,
SimpleMemoComponent,
// Normally skipped, but used when there's more than one root child.
HostRoot,
]);
function getChildren(parent: Fiber) {
const children = [];
const startingNode = parent;
let node: Fiber = startingNode;
if (node.child === null) {
return children;
}
node.child.return = node;
node = node.child;
outer: while (true) {
let descend = false;
if (validWrapperTypes.has(node.tag)) {
children.push(wrapFiber(node));
} else if (node.tag === HostText) {
Improve DEV errors if string coercion throws (Temporal.*, Symbol, etc.) (#22064) * Revise ESLint rules for string coercion Currently, react uses `'' + value` to coerce mixed values to strings. This code will throw for Temporal objects or symbols. To make string-coercion safer and to improve user-facing error messages, This commit adds a new ESLint rule called `safe-string-coercion`. This rule has two modes: a production mode and a non-production mode. * If the `isProductionUserAppCode` option is true, then `'' + value` coercions are allowed (because they're faster, although they may throw) and `String(value)` coercions are disallowed. Exception: when building error messages or running DEV-only code in prod files, `String()` should be used because it won't throw. * If the `isProductionUserAppCode` option is false, then `'' + value` coercions are disallowed (because they may throw, and in non-prod code it's not worth the risk) and `String(value)` are allowed. Production mode is used for all files which will be bundled with developers' userland apps. Non-prod mode is used for all other React code: tests, DEV blocks, devtools extension, etc. In production mode, in addiiton to flagging `String(value)` calls, the rule will also flag `'' + value` or `value + ''` coercions that may throw. The rule is smart enough to silence itself in the following "will never throw" cases: * When the coercion is wrapped in a `typeof` test that restricts to safe (non-symbol, non-object) types. Example: if (typeof value === 'string' || typeof value === 'number') { thisWontReport('' + value); } * When what's being coerced is a unary function result, because unary functions never return an object or a symbol. * When the coerced value is a commonly-used numeric identifier: `i`, `idx`, or `lineNumber`. * When the statement immeidately before the coercion is a DEV-only call to a function from shared/CheckStringCoercion.js. This call is a no-op in production, but in DEV it will show a console error explaining the problem, then will throw right after a long explanatory code comment so that debugger users will have an idea what's going on. The check function call must be in the following format: if (__DEV__) { checkXxxxxStringCoercion(value); }; Manually disabling the rule is usually not necessary because almost all prod use of the `'' + value` pattern falls into one of the categories above. But in the rare cases where the rule isn't smart enough to detect safe usage (e.g. when a coercion is inside a nested ternary operator), manually disabling the rule will be needed. The rule should also be manually disabled in prod error handling code where `String(value)` should be used for coercions, because it'd be bad to throw while building an error message or stack trace! The prod and non-prod modes have differentiated error messages to explain how to do a proper coercion in that mode. If a production check call is needed but is missing or incorrect (e.g. not in a DEV block or not immediately before the coercion), then a context-sensitive error message will be reported so that developers can figure out what's wrong and how to fix the problem. Because string coercions are now handled by the `safe-string-coercion` rule, the `no-primitive-constructor` rule no longer flags `String()` usage. It still flags `new String(value)` because that usage is almost always a bug. * Add DEV-only string coercion check functions This commit adds DEV-only functions to check whether coercing values to strings using the `'' + value` pattern will throw. If it will throw, these functions will: 1. Display a console error with a friendly error message describing the problem and the developer can fix it. 2. Perform the coercion, which will throw. Right before the line where the throwing happens, there's a long code comment that will help debugger users (or others looking at the exception call stack) figure out what happened and how to fix the problem. One of these check functions should be called before all string coercion of user-provided values, except when the the coercion is guaranteed not to throw, e.g. * if inside a typeof check like `if (typeof value === 'string')` * if coercing the result of a unary function like `+value` or `value++` * if coercing a variable named in a whitelist of numeric identifiers: `i`, `idx`, or `lineNumber`. The new `safe-string-coercion` internal ESLint rule enforces that these check functions are called when they are required. Only use these check functions in production code that will be bundled with user apps. For non-prod code (and for production error-handling code), use `String(value)` instead which may be a little slower but will never throw. * Add failing tests for string coercion Added failing tests to verify: * That input, select, and textarea elements with value and defaultValue set to Temporal-like objects which will throw when coerced to string using the `'' + value` pattern. * That text elements will throw for Temporal-like objects * That dangerouslySetInnerHTML will *not* throw for Temporal-like objects because this value is not cast to a string before passing to the DOM. * That keys that are Temporal-like objects will throw All tests above validate the friendly error messages thrown. * Use `String(value)` for coercion in non-prod files This commit switches non-production code from `'' + value` (which throws for Temporal objects and symbols) to instead use `String(value)` which won't throw for these or other future plus-phobic types. "Non-produciton code" includes anything not bundled into user apps: * Tests and test utilities. Note that I didn't change legacy React test fixtures because I assumed it was good for those files to act just like old React, including coercion behavior. * Build scripts * Dev tools package - In addition to switching to `String`, I also removed special-case code for coercing symbols which is now unnecessary. * Add DEV-only string coercion checks to prod files This commit adds DEV-only function calls to to check if string coercion using `'' + value` will throw, which it will if the value is a Temporal object or a symbol because those types can't be added with `+`. If it will throw, then in DEV these checks will show a console error to help the user undertsand what went wrong and how to fix the problem. After emitting the console error, the check functions will retry the coercion which will throw with a call stack that's easy (or at least easier!) to troubleshoot because the exception happens right after a long comment explaining the issue. So whether the user is in a debugger, looking at the browser console, or viewing the in-browser DEV call stack, it should be easy to understand and fix the problem. In most cases, the safe-string-coercion ESLint rule is smart enough to detect when a coercion is safe. But in rare cases (e.g. when a coercion is inside a ternary) this rule will have to be manually disabled. This commit also switches error-handling code to use `String(value)` for coercion, because it's bad to crash when you're trying to build an error message or a call stack! Because `String()` is usually disallowed by the `safe-string-coercion` ESLint rule in production code, the rule must be disabled when `String()` is used.
2021-09-28 01:05:07 +08:00
if (__DEV__) {
checkPropStringCoercion(node.memoizedProps, 'memoizedProps');
}
children.push('' + node.memoizedProps);
} else {
descend = true;
}
if (descend && node.child !== null) {
node.child.return = node;
node = node.child;
continue;
}
while (node.sibling === null) {
if (node.return === startingNode) {
break outer;
}
node = (node.return: any);
}
(node.sibling: any).return = node.return;
node = (node.sibling: any);
}
return children;
}
class ReactTestInstance {
_fiber: Fiber;
_currentFiber(): Fiber {
// Throws if this component has been unmounted.
const fiber = findCurrentFiberUsingSlowPath(this._fiber);
[RFC] Codemod invariant -> throw new Error (#22435) * Hoist error codes import to module scope When this code was written, the error codes map (`codes.json`) was created on-the-fly, so we had to lazily require from inside the visitor. Because `codes.json` is now checked into source, we can import it a single time in module scope. * Minify error constructors in production We use a script to minify our error messages in production. Each message is assigned an error code, defined in `scripts/error-codes/codes.json`. Then our build script replaces the messages with a link to our error decoder page, e.g. https://reactjs.org/docs/error-decoder.html/?invariant=92 This enables us to write helpful error messages without increasing the bundle size. Right now, the script only works for `invariant` calls. It does not work if you throw an Error object. This is an old Facebookism that we don't really need, other than the fact that our error minification script relies on it. So, I've updated the script to minify error constructors, too: Input: Error(`A ${adj} message that contains ${noun}`); Output: Error(formatProdErrorMessage(ERR_CODE, adj, noun)); It only works for constructors that are literally named Error, though we could add support for other names, too. As a next step, I will add a lint rule to enforce that errors written this way must have a corresponding error code. * Minify "no fallback UI specified" error in prod This error message wasn't being minified because it doesn't use invariant. The reason it didn't use invariant is because this particular error is created without begin thrown — it doesn't need to be thrown because it's located inside the error handling part of the runtime. Now that the error minification script supports Error constructors, we can minify it by assigning it a production error code in `scripts/error-codes/codes.json`. To support the use of Error constructors more generally, I will add a lint rule that enforces each message has a corresponding error code. * Lint rule to detect unminified errors Adds a lint rule that detects when an Error constructor is used without a corresponding production error code. We already have this for `invariant`, but not for regular errors, i.e. `throw new Error(msg)`. There's also nothing that enforces the use of `invariant` besides convention. There are some packages where we don't care to minify errors. These are packages that run in environments where bundle size is not a concern, like react-pg. I added an override in the ESLint config to ignore these. * Temporarily add invariant codemod script I'm adding this codemod to the repo temporarily, but I'll revert it in the same PR. That way we don't have to check it in but it's still accessible (via the PR) if we need it later. * [Automated] Codemod invariant -> Error This commit contains only automated changes: npx jscodeshift -t scripts/codemod-invariant.js packages --ignore-pattern="node_modules/**/*" yarn linc --fix yarn prettier I will do any manual touch ups in separate commits so they're easier to review. * Remove temporary codemod script This reverts the codemod script and ESLint config I added temporarily in order to perform the invariant codemod. * Manual touch ups A few manual changes I made after the codemod ran. * Enable error code transform per package Currently we're not consistent about which packages should have their errors minified in production and which ones should. This adds a field to the bundle configuration to control whether to apply the transform. We should decide what the criteria is going forward. I think it's probably a good idea to minify any package that gets sent over the network. So yes to modules that run in the browser, and no to modules that run on the server and during development only.
2021-10-01 03:01:28 +08:00
if (fiber === null) {
throw new Error(
"Can't read from currently-mounting component. This error is likely " +
'caused by a bug in React. Please file an issue.',
);
}
return fiber;
}
constructor(fiber: Fiber) {
[RFC] Codemod invariant -> throw new Error (#22435) * Hoist error codes import to module scope When this code was written, the error codes map (`codes.json`) was created on-the-fly, so we had to lazily require from inside the visitor. Because `codes.json` is now checked into source, we can import it a single time in module scope. * Minify error constructors in production We use a script to minify our error messages in production. Each message is assigned an error code, defined in `scripts/error-codes/codes.json`. Then our build script replaces the messages with a link to our error decoder page, e.g. https://reactjs.org/docs/error-decoder.html/?invariant=92 This enables us to write helpful error messages without increasing the bundle size. Right now, the script only works for `invariant` calls. It does not work if you throw an Error object. This is an old Facebookism that we don't really need, other than the fact that our error minification script relies on it. So, I've updated the script to minify error constructors, too: Input: Error(`A ${adj} message that contains ${noun}`); Output: Error(formatProdErrorMessage(ERR_CODE, adj, noun)); It only works for constructors that are literally named Error, though we could add support for other names, too. As a next step, I will add a lint rule to enforce that errors written this way must have a corresponding error code. * Minify "no fallback UI specified" error in prod This error message wasn't being minified because it doesn't use invariant. The reason it didn't use invariant is because this particular error is created without begin thrown — it doesn't need to be thrown because it's located inside the error handling part of the runtime. Now that the error minification script supports Error constructors, we can minify it by assigning it a production error code in `scripts/error-codes/codes.json`. To support the use of Error constructors more generally, I will add a lint rule that enforces each message has a corresponding error code. * Lint rule to detect unminified errors Adds a lint rule that detects when an Error constructor is used without a corresponding production error code. We already have this for `invariant`, but not for regular errors, i.e. `throw new Error(msg)`. There's also nothing that enforces the use of `invariant` besides convention. There are some packages where we don't care to minify errors. These are packages that run in environments where bundle size is not a concern, like react-pg. I added an override in the ESLint config to ignore these. * Temporarily add invariant codemod script I'm adding this codemod to the repo temporarily, but I'll revert it in the same PR. That way we don't have to check it in but it's still accessible (via the PR) if we need it later. * [Automated] Codemod invariant -> Error This commit contains only automated changes: npx jscodeshift -t scripts/codemod-invariant.js packages --ignore-pattern="node_modules/**/*" yarn linc --fix yarn prettier I will do any manual touch ups in separate commits so they're easier to review. * Remove temporary codemod script This reverts the codemod script and ESLint config I added temporarily in order to perform the invariant codemod. * Manual touch ups A few manual changes I made after the codemod ran. * Enable error code transform per package Currently we're not consistent about which packages should have their errors minified in production and which ones should. This adds a field to the bundle configuration to control whether to apply the transform. We should decide what the criteria is going forward. I think it's probably a good idea to minify any package that gets sent over the network. So yes to modules that run in the browser, and no to modules that run on the server and during development only.
2021-10-01 03:01:28 +08:00
if (!validWrapperTypes.has(fiber.tag)) {
throw new Error(
`Unexpected object passed to ReactTestInstance constructor (tag: ${fiber.tag}). ` +
'This is probably a bug in React.',
);
}
this._fiber = fiber;
}
get instance() {
if (this._fiber.tag === HostComponent) {
return getPublicInstance(this._fiber.stateNode);
} else {
return this._fiber.stateNode;
}
}
get type() {
return this._fiber.type;
}
get props(): Object {
return this._currentFiber().memoizedProps;
}
get parent(): ?ReactTestInstance {
let parent = this._fiber.return;
while (parent !== null) {
if (validWrapperTypes.has(parent.tag)) {
if (parent.tag === HostRoot) {
// Special case: we only "materialize" instances for roots
// if they have more than a single child. So we'll check that now.
if (getChildren(parent).length < 2) {
return null;
}
}
return wrapFiber(parent);
}
parent = parent.return;
}
return null;
}
get children(): Array<ReactTestInstance | string> {
return getChildren(this._currentFiber());
}
// Custom search functions
find(predicate: Predicate): ReactTestInstance {
return expectOne(
this.findAll(predicate, {deep: false}),
`matching custom predicate: ${predicate.toString()}`,
);
}
findByType(type: any): ReactTestInstance {
return expectOne(
this.findAllByType(type, {deep: false}),
`with node type: "${getComponentNameFromType(type) || 'Unknown'}"`,
);
}
findByProps(props: Object): ReactTestInstance {
return expectOne(
this.findAllByProps(props, {deep: false}),
`with props: ${JSON.stringify(props)}`,
);
}
findAll(
predicate: Predicate,
options: ?FindOptions = null,
): Array<ReactTestInstance> {
return findAll(this, predicate, options);
}
findAllByType(
type: any,
options: ?FindOptions = null,
): Array<ReactTestInstance> {
return findAll(this, node => node.type === type, options);
}
findAllByProps(
props: Object,
options: ?FindOptions = null,
): Array<ReactTestInstance> {
return findAll(
this,
node => node.props && propsMatch(node.props, props),
options,
);
}
}
function findAll(
root: ReactTestInstance,
predicate: Predicate,
options: ?FindOptions,
): Array<ReactTestInstance> {
const deep = options ? options.deep : true;
const results = [];
if (predicate(root)) {
results.push(root);
if (!deep) {
return results;
}
}
root.children.forEach(child => {
if (typeof child === 'string') {
return;
}
results.push(...findAll(child, predicate, options));
});
return results;
}
function expectOne(
all: Array<ReactTestInstance>,
message: string,
): ReactTestInstance {
if (all.length === 1) {
return all[0];
}
const prefix =
all.length === 0
? 'No instances found '
: `Expected 1 but found ${all.length} instances `;
throw new Error(prefix + message);
}
function propsMatch(props: Object, filter: Object): boolean {
for (const key in filter) {
if (props[key] !== filter[key]) {
return false;
}
}
return true;
}
function create(element: React$Element<any>, options: TestRendererOptions) {
let createNodeMock = defaultTestOptions.createNodeMock;
let isConcurrent = false;
let isStrictMode = false;
let concurrentUpdatesByDefault = null;
if (typeof options === 'object' && options !== null) {
if (typeof options.createNodeMock === 'function') {
createNodeMock = options.createNodeMock;
}
if (options.unstable_isConcurrent === true) {
isConcurrent = true;
}
if (options.unstable_strictMode === true) {
isStrictMode = true;
}
if (allowConcurrentByDefault) {
if (options.unstable_concurrentUpdatesByDefault !== undefined) {
concurrentUpdatesByDefault =
options.unstable_concurrentUpdatesByDefault;
}
}
}
let container = {
children: [],
createNodeMock,
tag: 'CONTAINER',
};
let root: FiberRoot | null = createContainer(
container,
isConcurrent ? ConcurrentRoot : LegacyRoot,
false,
null,
isStrictMode,
concurrentUpdatesByDefault,
);
[RFC] Codemod invariant -> throw new Error (#22435) * Hoist error codes import to module scope When this code was written, the error codes map (`codes.json`) was created on-the-fly, so we had to lazily require from inside the visitor. Because `codes.json` is now checked into source, we can import it a single time in module scope. * Minify error constructors in production We use a script to minify our error messages in production. Each message is assigned an error code, defined in `scripts/error-codes/codes.json`. Then our build script replaces the messages with a link to our error decoder page, e.g. https://reactjs.org/docs/error-decoder.html/?invariant=92 This enables us to write helpful error messages without increasing the bundle size. Right now, the script only works for `invariant` calls. It does not work if you throw an Error object. This is an old Facebookism that we don't really need, other than the fact that our error minification script relies on it. So, I've updated the script to minify error constructors, too: Input: Error(`A ${adj} message that contains ${noun}`); Output: Error(formatProdErrorMessage(ERR_CODE, adj, noun)); It only works for constructors that are literally named Error, though we could add support for other names, too. As a next step, I will add a lint rule to enforce that errors written this way must have a corresponding error code. * Minify "no fallback UI specified" error in prod This error message wasn't being minified because it doesn't use invariant. The reason it didn't use invariant is because this particular error is created without begin thrown — it doesn't need to be thrown because it's located inside the error handling part of the runtime. Now that the error minification script supports Error constructors, we can minify it by assigning it a production error code in `scripts/error-codes/codes.json`. To support the use of Error constructors more generally, I will add a lint rule that enforces each message has a corresponding error code. * Lint rule to detect unminified errors Adds a lint rule that detects when an Error constructor is used without a corresponding production error code. We already have this for `invariant`, but not for regular errors, i.e. `throw new Error(msg)`. There's also nothing that enforces the use of `invariant` besides convention. There are some packages where we don't care to minify errors. These are packages that run in environments where bundle size is not a concern, like react-pg. I added an override in the ESLint config to ignore these. * Temporarily add invariant codemod script I'm adding this codemod to the repo temporarily, but I'll revert it in the same PR. That way we don't have to check it in but it's still accessible (via the PR) if we need it later. * [Automated] Codemod invariant -> Error This commit contains only automated changes: npx jscodeshift -t scripts/codemod-invariant.js packages --ignore-pattern="node_modules/**/*" yarn linc --fix yarn prettier I will do any manual touch ups in separate commits so they're easier to review. * Remove temporary codemod script This reverts the codemod script and ESLint config I added temporarily in order to perform the invariant codemod. * Manual touch ups A few manual changes I made after the codemod ran. * Enable error code transform per package Currently we're not consistent about which packages should have their errors minified in production and which ones should. This adds a field to the bundle configuration to control whether to apply the transform. We should decide what the criteria is going forward. I think it's probably a good idea to minify any package that gets sent over the network. So yes to modules that run in the browser, and no to modules that run on the server and during development only.
2021-10-01 03:01:28 +08:00
if (root == null) {
throw new Error('something went wrong');
}
updateContainer(element, root, null, null);
const entry = {
_Scheduler: Scheduler,
root: undefined, // makes flow happy
// we define a 'getter' for 'root' below using 'Object.defineProperty'
toJSON(): Array<ReactTestRendererNode> | ReactTestRendererNode | null {
if (root == null || root.current == null || container == null) {
return null;
}
if (container.children.length === 0) {
return null;
}
if (container.children.length === 1) {
return toJSON(container.children[0]);
}
if (
container.children.length === 2 &&
container.children[0].isHidden === true &&
container.children[1].isHidden === false
) {
// Omit timed out children from output entirely, including the fact that we
// temporarily wrap fallback and timed out children in an array.
return toJSON(container.children[1]);
}
let renderedChildren = null;
if (container.children && container.children.length) {
for (let i = 0; i < container.children.length; i++) {
const renderedChild = toJSON(container.children[i]);
if (renderedChild !== null) {
if (renderedChildren === null) {
renderedChildren = [renderedChild];
} else {
renderedChildren.push(renderedChild);
Hide timed-out children instead of deleting them so their state is preserved (#13823) * Store the start time on `updateQueue` instead of `stateNode` Originally I did this to free the `stateNode` field to store a second set of children. I don't we'll need this anymore, since we use fragment fibers instead. But I still think using `updateQueue` makes more sense so I'll leave this in. * Use fragment fibers to keep the primary and fallback children separate If the children timeout, we switch to showing the fallback children in place of the "primary" children. However, we don't want to delete the primary children because then their state will be lost (both the React state and the host state, e.g. uncontrolled form inputs). Instead we keep them mounted and hide them. Both the fallback children AND the primary children are rendered at the same time. Once the primary children are un-suspended, we can delete the fallback children — don't need to preserve their state. The two sets of children are siblings in the host environment, but semantically, for purposes of reconciliation, they are two separate sets. So we store them using two fragment fibers. However, we want to avoid allocating extra fibers for every placeholder. They're only necessary when the children time out, because that's the only time when both sets are mounted. So, the extra fragment fibers are only used if the children time out. Otherwise, we render the primary children directly. This requires some custom reconciliation logic to preserve the state of the primary children. It's essentially a very basic form of re-parenting. * Use `memoizedState` to store various pieces of SuspenseComponent's state SuspenseComponent has three pieces of state: - alreadyCaptured: Whether a component in the child subtree already suspended. If true, subsequent suspends should bubble up to the next boundary. - didTimeout: Whether the boundary renders the primary or fallback children. This is separate from `alreadyCaptured` because outside of strict mode, when a boundary times out, the first commit renders the primary children in an incomplete state, then performs a second commit to switch the fallback. In that first commit, `alreadyCaptured` is false and `didTimeout` is true. - timedOutAt: The time at which the boundary timed out. This is separate from `didTimeout` because it's not set unless the boundary actually commits. These were previously spread across several fields. This happens to make the non-strict case a bit less hacky; the logic for that special case is now mostly localized to the UnwindWork module. * Hide timed-out Suspense children When a subtree takes too long to load, we swap its contents out for a fallback to unblock the rest of the tree. Because we don't want to lose the state of the timed out view, we shouldn't actually delete the nodes from the tree. Instead, we'll keep them mounted and hide them visually. When the subtree is unblocked, we un-hide it, having preserved the existing state. Adds additional host config methods. For mutation mode: - hideInstance - hideTextInstance - unhideInstance - unhideTextInstance For persistent mode: - cloneHiddenInstance - cloneUnhiddenInstance - createHiddenTextInstance I've only implemented the new methods in the noop and test renderers. I'll implement them in the other renderers in subsequent commits. * Include `hidden` prop in noop renderer's output This will be used in subsequent commits to test that timed-out children are properly hidden. Also adds getChildrenAsJSX() method as an alternative to using getChildren(). (Ideally all our tests would use test renderer #oneday.) * Implement hide/unhide host config methods for DOM renderer For DOM nodes, we hide using `el.style.display = 'none'`. Text nodes don't have style, so we hide using `text.textContent = ''`. * Implement hide/unhide host config methods for Art renderer * Create DOM fixture that tests state preservation of timed out content * Account for class components that suspend outside concurrent mode Need to distinguish mount from update. An unfortunate edge case :( * Fork appendAllChildren between persistent and mutation mode * Remove redundant check for existence of el.style * Schedule placement effect on indeterminate components In non-concurrent mode, indeterminate fibers may commit in an inconsistent state. But when they update, we should throw out the old fiber and start fresh. Which means the new fiber needs a placement effect. * Pass null instead of current everywhere in mountIndeterminateComponent
2018-10-19 06:37:16 +08:00
}
}
}
}
return renderedChildren;
},
toTree() {
if (root == null || root.current == null) {
return null;
}
return toTree(root.current);
},
update(newElement: React$Element<any>) {
if (root == null || root.current == null) {
return;
}
updateContainer(newElement, root, null, null);
},
unmount() {
if (root == null || root.current == null) {
return;
}
updateContainer(null, root, null, null);
container = null;
root = null;
},
getInstance() {
if (root == null || root.current == null) {
return null;
}
return getPublicRootInstance(root);
},
2021-07-16 22:37:57 +08:00
unstable_flushSync: flushSync,
};
Object.defineProperty(
entry,
'root',
({
configurable: true,
enumerable: true,
get: function() {
if (root === null) {
throw new Error("Can't access .root on unmounted test renderer");
[Fiber] Implement test renderer (#8628) * ReactTestRenderer move current impl to stack dir * ReactTestRenderer on fiber: commence! * ReactTestRenderer: most non-ref/non-public-instance tests are passing * Move ReactTestFiberComponent functions from Renderer to Component file * test renderer: get rid of private root containers and root Maps * TestRenderer: switch impl based on ReactDOMFeatureFlag.useFiber * ReactTestRenderer: inline component creation * ReactTestRenderer: return to pristine original glory (+ Fiber for error order difference) * TestRendererFiber: use a simple class as TestComponentInstances * Add `getPublicInstance` to support TestRenderer `createNodeMock` * Rename files to end. Update for `mountContainer->createContainer` change * test renderer return same object to prevent unnecessary context pushing/popping * Fiber HostConfig add getPublicInstance. This should be the identity fn everywhere except the test renderer * appease flow * Initial cleanup from sleepy work * unstable_batchedUpdates * Stack test renderer: cache nodeMock to not call on unmount * add public instance type parameter to the reconciler * test renderer: set _nodeMock when mounted * More cleanup * Add test cases for root fragments and (maybe?) root text nodes * Fix the npm package build Explicitly require the Stack version by default. Add a separate entry point for Fiber. We don't add fiber.js to the package yet since it's considered internal until React 16. * Relax the ref type from Object to mixed This seems like the most straightforward way to support getPublicInstance for test renderer. * Remove accidental newline * test renderer: unify TestComponent and TestContainer, handle root updates * Remove string/number serialization attempts since Fiber ensures all textInstances are strings * Return full fragments in toJSON * Test Renderer remove TestComponent instances for simple objects * Update babylon for exact object type syntax * Use $$typeof because clarity > punching ducks. * Minor Flow annotation tweaks * Tweak style, types, and naming * Fix typo
2017-01-12 03:19:32 +08:00
}
const children = getChildren(root.current);
if (children.length === 0) {
throw new Error("Can't access .root on unmounted test renderer");
} else if (children.length === 1) {
// Normally, we skip the root and just give you the child.
return children[0];
} else {
// However, we give you the root if there's more than one root child.
// We could make this the behavior for all cases but it would be a breaking change.
return wrapFiber(root.current);
[Fiber] Implement test renderer (#8628) * ReactTestRenderer move current impl to stack dir * ReactTestRenderer on fiber: commence! * ReactTestRenderer: most non-ref/non-public-instance tests are passing * Move ReactTestFiberComponent functions from Renderer to Component file * test renderer: get rid of private root containers and root Maps * TestRenderer: switch impl based on ReactDOMFeatureFlag.useFiber * ReactTestRenderer: inline component creation * ReactTestRenderer: return to pristine original glory (+ Fiber for error order difference) * TestRendererFiber: use a simple class as TestComponentInstances * Add `getPublicInstance` to support TestRenderer `createNodeMock` * Rename files to end. Update for `mountContainer->createContainer` change * test renderer return same object to prevent unnecessary context pushing/popping * Fiber HostConfig add getPublicInstance. This should be the identity fn everywhere except the test renderer * appease flow * Initial cleanup from sleepy work * unstable_batchedUpdates * Stack test renderer: cache nodeMock to not call on unmount * add public instance type parameter to the reconciler * test renderer: set _nodeMock when mounted * More cleanup * Add test cases for root fragments and (maybe?) root text nodes * Fix the npm package build Explicitly require the Stack version by default. Add a separate entry point for Fiber. We don't add fiber.js to the package yet since it's considered internal until React 16. * Relax the ref type from Object to mixed This seems like the most straightforward way to support getPublicInstance for test renderer. * Remove accidental newline * test renderer: unify TestComponent and TestContainer, handle root updates * Remove string/number serialization attempts since Fiber ensures all textInstances are strings * Return full fragments in toJSON * Test Renderer remove TestComponent instances for simple objects * Update babylon for exact object type syntax * Use $$typeof because clarity > punching ducks. * Minor Flow annotation tweaks * Tweak style, types, and naming * Fix typo
2017-01-12 03:19:32 +08:00
}
},
}: Object),
);
[Fiber] Implement test renderer (#8628) * ReactTestRenderer move current impl to stack dir * ReactTestRenderer on fiber: commence! * ReactTestRenderer: most non-ref/non-public-instance tests are passing * Move ReactTestFiberComponent functions from Renderer to Component file * test renderer: get rid of private root containers and root Maps * TestRenderer: switch impl based on ReactDOMFeatureFlag.useFiber * ReactTestRenderer: inline component creation * ReactTestRenderer: return to pristine original glory (+ Fiber for error order difference) * TestRendererFiber: use a simple class as TestComponentInstances * Add `getPublicInstance` to support TestRenderer `createNodeMock` * Rename files to end. Update for `mountContainer->createContainer` change * test renderer return same object to prevent unnecessary context pushing/popping * Fiber HostConfig add getPublicInstance. This should be the identity fn everywhere except the test renderer * appease flow * Initial cleanup from sleepy work * unstable_batchedUpdates * Stack test renderer: cache nodeMock to not call on unmount * add public instance type parameter to the reconciler * test renderer: set _nodeMock when mounted * More cleanup * Add test cases for root fragments and (maybe?) root text nodes * Fix the npm package build Explicitly require the Stack version by default. Add a separate entry point for Fiber. We don't add fiber.js to the package yet since it's considered internal until React 16. * Relax the ref type from Object to mixed This seems like the most straightforward way to support getPublicInstance for test renderer. * Remove accidental newline * test renderer: unify TestComponent and TestContainer, handle root updates * Remove string/number serialization attempts since Fiber ensures all textInstances are strings * Return full fragments in toJSON * Test Renderer remove TestComponent instances for simple objects * Update babylon for exact object type syntax * Use $$typeof because clarity > punching ducks. * Minor Flow annotation tweaks * Tweak style, types, and naming * Fix typo
2017-01-12 03:19:32 +08:00
return entry;
}
const fiberToWrapper = new WeakMap();
function wrapFiber(fiber: Fiber): ReactTestInstance {
let wrapper = fiberToWrapper.get(fiber);
if (wrapper === undefined && fiber.alternate !== null) {
wrapper = fiberToWrapper.get(fiber.alternate);
}
if (wrapper === undefined) {
wrapper = new ReactTestInstance(fiber);
fiberToWrapper.set(fiber, wrapper);
}
return wrapper;
}
// Enable ReactTestRenderer to be used to test DevTools integration.
injectIntoDevTools({
findFiberByHostInstance: (() => {
throw new Error('TestRenderer does not support findFiberByHostInstance()');
}: any),
bundleType: __DEV__ ? 1 : 0,
version: ReactVersion,
rendererPackageName: 'react-test-renderer',
});
export {
Scheduler as _Scheduler,
create,
/* eslint-disable-next-line camelcase */
batchedUpdates as unstable_batchedUpdates,
act,
};