2017-01-12 03:19:32 +08:00
|
|
|
/**
|
2018-09-08 06:11:23 +08:00
|
|
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
2017-01-12 03:19:32 +08:00
|
|
|
*
|
2017-09-25 04:48:13 +08:00
|
|
|
* This source code is licensed under the MIT license found in the
|
|
|
|
|
* LICENSE file in the root directory of this source tree.
|
2017-01-12 03:19:32 +08:00
|
|
|
*
|
|
|
|
|
* @flow
|
|
|
|
|
*/
|
|
|
|
|
|
Split cross-package types from implementation
Some of our internal reconciler types have leaked into other packages.
Usually, these types are treated as opaque; we don't read and write
to its fields. This is good.
However, the type is often passed back to a reconciler method. For
example, React DOM creates a FiberRoot with `createContainer`, then
passes that root to `updateContainer`. It doesn't do anything with the
root except pass it through, but because `updateContainer` expects a
full FiberRoot, React DOM is still coupled to all its fields.
I don't know if there's an idiomatic way to handle this in Flow. Opaque
types are simlar, but those only work within a single file. AFAIK,
there's no way to use a package as the boundary for opaqueness.
The immediate problem this presents is that the reconciler refactor will
involve changes to our internal data structures. I don't want to have to
fork every single package that happens to pass through a Fiber or
FiberRoot, or access any one of its fields. So my current plan is to
share the same Flow type across both forks. The shared type will be a
superset of each implementation's type, e.g. Fiber will have both an
`expirationTime` field and a `lanes` field. The implementations will
diverge, but not the types.
To do this, I lifted the type definitions into a separate module.
2020-04-09 14:48:24 +08:00
|
|
|
import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
|
|
|
|
|
import type {FiberRoot} from 'react-reconciler/src/ReactInternalTypes';
|
2018-05-15 08:12:28 +08:00
|
|
|
import type {Instance, TextInstance} from './ReactTestHostConfig';
|
2017-10-26 02:07:54 +08:00
|
|
|
|
`act` should work without mock Scheduler (#21714)
Currently, in a React 18 root, `act` only works if you mock the
Scheduler package. This was because we didn't want to add additional
checks at runtime.
But now that the `act` testing API is dev-only, we can simplify its
implementation.
Now when an update is wrapped with `act`, React will bypass Scheduler
entirely and push its tasks onto a special internal queue. Then, when
the outermost `act` scope exists, we'll flush that queue.
I also removed the "wrong act" warning, because the plan is to move
`act` to an isomorphic entry point, simlar to `startTransition`. That's
not directly related to this PR, but I didn't want to bother
re-implementing that warning only to immediately remove it.
I'll add the isomorphic API in a follow up.
Note that the internal version of `act` that we use in our own tests
still depends on mocking the Scheduler package, because it needs to work
in production. I'm planning to move that implementation to a shared
(internal) module, too.
2021-06-23 05:25:07 +08:00
|
|
|
import * as React from 'react';
|
2019-03-01 02:50:38 +08:00
|
|
|
import * as Scheduler from 'scheduler/unstable_mock';
|
2018-11-20 07:32:54 +08:00
|
|
|
import {
|
|
|
|
|
getPublicRootInstance,
|
|
|
|
|
createContainer,
|
|
|
|
|
updateContainer,
|
|
|
|
|
flushSync,
|
|
|
|
|
injectIntoDevTools,
|
2019-02-06 00:10:16 +08:00
|
|
|
batchedUpdates,
|
2020-03-07 08:20:42 +08:00
|
|
|
} from 'react-reconciler/src/ReactFiberReconciler';
|
|
|
|
|
import {findCurrentFiberUsingSlowPath} from 'react-reconciler/src/ReactFiberTreeReflection';
|
2017-11-03 03:50:03 +08:00
|
|
|
import {
|
2017-08-08 07:47:22 +08:00
|
|
|
Fragment,
|
2018-10-05 05:44:46 +08:00
|
|
|
FunctionComponent,
|
2017-02-10 04:55:00 +08:00
|
|
|
ClassComponent,
|
|
|
|
|
HostComponent,
|
2018-02-06 00:55:48 +08:00
|
|
|
HostPortal,
|
2017-02-10 04:55:00 +08:00
|
|
|
HostText,
|
|
|
|
|
HostRoot,
|
2018-02-17 03:27:20 +08:00
|
|
|
ContextConsumer,
|
|
|
|
|
ContextProvider,
|
|
|
|
|
Mode,
|
2018-03-17 02:18:50 +08:00
|
|
|
ForwardRef,
|
2018-05-11 06:25:32 +08:00
|
|
|
Profiler,
|
2018-10-21 00:46:23 +08:00
|
|
|
MemoComponent,
|
|
|
|
|
SimpleMemoComponent,
|
2018-10-24 02:36:56 +08:00
|
|
|
IncompleteClassComponent,
|
2019-08-31 01:27:14 +08:00
|
|
|
ScopeComponent,
|
2020-03-22 06:22:01 +08:00
|
|
|
} from 'react-reconciler/src/ReactWorkTags';
|
2021-04-07 22:57:43 +08:00
|
|
|
import isArray from 'shared/isArray';
|
2021-03-06 05:02:02 +08:00
|
|
|
import getComponentNameFromType from 'shared/getComponentNameFromType';
|
2018-06-21 00:24:52 +08:00
|
|
|
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';
|
2017-01-12 03:19:32 +08:00
|
|
|
|
2018-11-20 07:32:54 +08:00
|
|
|
import {getPublicInstance} from './ReactTestHostConfig';
|
2020-03-22 06:22:01 +08:00
|
|
|
import {ConcurrentRoot, LegacyRoot} from 'react-reconciler/src/ReactRootTags';
|
2021-04-29 04:09:30 +08:00
|
|
|
import {allowConcurrentByDefault} from 'shared/ReactFeatureFlags';
|
2018-05-15 08:12:28 +08:00
|
|
|
|
2021-07-13 08:15:20 +08:00
|
|
|
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
|
|
|
|
2021-06-23 05:29:35 +08:00
|
|
|
// TODO: Remove from public bundle
|
|
|
|
|
|
2017-06-15 05:10:33 +08:00
|
|
|
type TestRendererOptions = {
|
2017-08-29 05:39:58 +08:00
|
|
|
createNodeMock: (element: React$Element<any>) => any,
|
2018-09-27 00:13:02 +08:00
|
|
|
unstable_isConcurrent: boolean,
|
2021-05-05 03:42:48 +08:00
|
|
|
unstable_strictMode: boolean,
|
2021-04-29 04:09:30 +08:00
|
|
|
unstable_concurrentUpdatesByDefault: boolean,
|
2020-01-09 22:50:44 +08:00
|
|
|
...
|
2017-06-15 05:10:33 +08:00
|
|
|
};
|
|
|
|
|
|
2017-01-12 03:19:32 +08:00
|
|
|
type ReactTestRendererJSON = {|
|
2017-03-14 08:05:18 +08:00
|
|
|
type: string,
|
2020-01-09 22:50:44 +08:00
|
|
|
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().
|
2017-01-12 03:19:32 +08:00
|
|
|
|};
|
|
|
|
|
type ReactTestRendererNode = ReactTestRendererJSON | string;
|
|
|
|
|
|
2017-08-08 07:47:22 +08:00
|
|
|
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,
|
2020-01-09 22:50:44 +08:00
|
|
|
...
|
2017-08-08 07:47:22 +08:00
|
|
|
}>;
|
|
|
|
|
|
|
|
|
|
export type Predicate = (node: ReactTestInstance) => ?boolean;
|
|
|
|
|
|
2017-12-01 08:01:45 +08:00
|
|
|
const defaultTestOptions = {
|
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;
|
|
|
|
|
}
|
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': {
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-01-12 03:19:32 +08:00
|
|
|
}
|
2017-03-14 08:05:18 +08:00
|
|
|
const json: ReactTestRendererJSON = {
|
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'),
|
|
|
|
|
});
|
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
|
|
|
}
|
2017-01-12 03:19:32 +08:00
|
|
|
default:
|
|
|
|
|
throw new Error(`Unexpected node type in toJSON: ${inst.tag}`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-06 00:55:48 +08:00
|
|
|
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) {
|
2017-12-01 08:01:45 +08:00
|
|
|
const array = [];
|
|
|
|
|
let node = nodeWithSibling;
|
2017-02-10 04:55:00 +08:00
|
|
|
while (node != null) {
|
|
|
|
|
array.push(node);
|
|
|
|
|
node = node.sibling;
|
|
|
|
|
}
|
2018-02-06 00:55:48 +08:00
|
|
|
return array;
|
2017-09-15 05:15:43 +08:00
|
|
|
}
|
|
|
|
|
|
2018-02-06 00:55:48 +08:00
|
|
|
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;
|
2021-04-07 22:57:43 +08:00
|
|
|
if (isArray(el)) {
|
2018-02-06 00:55:48 +08:00
|
|
|
stack.push(n);
|
|
|
|
|
stack.push({i: 0, array: el});
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
result.push(el);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return result;
|
2017-02-10 04:55:00 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function toTree(node: ?Fiber) {
|
|
|
|
|
if (node == null) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
switch (node.tag) {
|
2018-02-06 00:55:48 +08:00
|
|
|
case HostRoot:
|
|
|
|
|
return childrenToTree(node.child);
|
|
|
|
|
case HostPortal:
|
|
|
|
|
return childrenToTree(node.child);
|
2017-02-10 04:55:00 +08:00
|
|
|
case ClassComponent:
|
|
|
|
|
return {
|
|
|
|
|
nodeType: 'component',
|
|
|
|
|
type: node.type,
|
2017-03-14 08:05:18 +08:00
|
|
|
props: {...node.memoizedProps},
|
2017-02-10 04:55:00 +08:00
|
|
|
instance: node.stateNode,
|
2018-02-06 00:55:48 +08:00
|
|
|
rendered: childrenToTree(node.child),
|
2017-02-10 04:55:00 +08:00
|
|
|
};
|
2018-10-05 05:44:46 +08:00
|
|
|
case FunctionComponent:
|
2018-10-21 00:46:23 +08:00
|
|
|
case SimpleMemoComponent:
|
2017-02-10 04:55:00 +08:00
|
|
|
return {
|
|
|
|
|
nodeType: 'component',
|
|
|
|
|
type: node.type,
|
2017-03-14 08:05:18 +08:00
|
|
|
props: {...node.memoizedProps},
|
2017-02-10 04:55:00 +08:00
|
|
|
instance: null,
|
2018-02-06 00:55:48 +08:00
|
|
|
rendered: childrenToTree(node.child),
|
2017-02-10 04:55:00 +08:00
|
|
|
};
|
2018-02-06 00:55:48 +08:00
|
|
|
case HostComponent: {
|
2017-02-10 04:55:00 +08:00
|
|
|
return {
|
|
|
|
|
nodeType: 'host',
|
|
|
|
|
type: node.type,
|
2017-03-14 08:05:18 +08:00
|
|
|
props: {...node.memoizedProps},
|
2017-02-10 04:55:00 +08:00
|
|
|
instance: null, // TODO: use createNodeMock here somehow?
|
2018-02-06 00:55:48 +08:00
|
|
|
rendered: flatten(nodeAndSiblingsArray(node.child).map(toTree)),
|
2017-02-10 04:55:00 +08:00
|
|
|
};
|
2018-02-06 00:55:48 +08:00
|
|
|
}
|
|
|
|
|
case HostText:
|
2017-02-10 04:55:00 +08:00
|
|
|
return node.stateNode.text;
|
2018-02-06 00:55:48 +08:00
|
|
|
case Fragment:
|
2018-02-17 03:27:20 +08:00
|
|
|
case ContextProvider:
|
|
|
|
|
case ContextConsumer:
|
|
|
|
|
case Mode:
|
2018-05-11 06:25:32 +08:00
|
|
|
case Profiler:
|
2018-03-17 02:18:50 +08:00
|
|
|
case ForwardRef:
|
2018-10-21 00:46:23 +08:00
|
|
|
case MemoComponent:
|
2018-10-24 02:36:56 +08:00
|
|
|
case IncompleteClassComponent:
|
2019-08-31 01:27:14 +08:00
|
|
|
case ScopeComponent:
|
2018-02-06 00:55:48 +08:00
|
|
|
return childrenToTree(node.child);
|
2017-02-10 04:55:00 +08:00
|
|
|
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}`,
|
2017-02-10 04:55:00 +08:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-08 07:47:22 +08:00
|
|
|
const validWrapperTypes = new Set([
|
2018-10-05 05:44:46 +08:00
|
|
|
FunctionComponent,
|
2017-08-08 07:47:22 +08:00
|
|
|
ClassComponent,
|
|
|
|
|
HostComponent,
|
2018-05-02 02:55:06 +08:00
|
|
|
ForwardRef,
|
2018-10-21 00:46:23 +08:00
|
|
|
MemoComponent,
|
2018-11-06 02:02:59 +08:00
|
|
|
SimpleMemoComponent,
|
2018-06-12 03:03:51 +08:00
|
|
|
// Normally skipped, but used when there's more than one root child.
|
|
|
|
|
HostRoot,
|
2017-08-08 07:47:22 +08:00
|
|
|
]);
|
|
|
|
|
|
2018-06-12 03:03:51 +08:00
|
|
|
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');
|
|
|
|
|
}
|
2018-06-12 03:03:51 +08:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-08 07:47:22 +08:00
|
|
|
class ReactTestInstance {
|
|
|
|
|
_fiber: Fiber;
|
|
|
|
|
|
|
|
|
|
_currentFiber(): Fiber {
|
|
|
|
|
// Throws if this component has been unmounted.
|
2017-11-03 03:50:03 +08:00
|
|
|
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.',
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-08 07:47:22 +08:00
|
|
|
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.',
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-08 07:47:22 +08:00
|
|
|
this._fiber = fiber;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
get instance() {
|
|
|
|
|
if (this._fiber.tag === HostComponent) {
|
2018-11-20 07:32:54 +08:00
|
|
|
return getPublicInstance(this._fiber.stateNode);
|
2017-08-08 07:47:22 +08:00
|
|
|
} else {
|
|
|
|
|
return this._fiber.stateNode;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
get type() {
|
|
|
|
|
return this._fiber.type;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
get props(): Object {
|
|
|
|
|
return this._currentFiber().memoizedProps;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
get parent(): ?ReactTestInstance {
|
2018-05-15 18:11:19 +08:00
|
|
|
let parent = this._fiber.return;
|
|
|
|
|
while (parent !== null) {
|
|
|
|
|
if (validWrapperTypes.has(parent.tag)) {
|
2018-06-12 03:03:51 +08:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-05-15 18:11:19 +08:00
|
|
|
return wrapFiber(parent);
|
|
|
|
|
}
|
|
|
|
|
parent = parent.return;
|
|
|
|
|
}
|
|
|
|
|
return null;
|
2017-08-08 07:47:22 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
get children(): Array<ReactTestInstance | string> {
|
2018-06-12 03:03:51 +08:00
|
|
|
return getChildren(this._currentFiber());
|
2017-08-08 07:47:22 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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}),
|
2021-03-06 05:02:02 +08:00
|
|
|
`with node type: "${getComponentNameFromType(type) || 'Unknown'}"`,
|
2017-08-08 07:47:22 +08:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-10 00:11:22 +08:00
|
|
|
root.children.forEach(child => {
|
2017-08-08 07:47:22 +08:00
|
|
|
if (typeof child === 'string') {
|
2018-02-10 00:11:22 +08:00
|
|
|
return;
|
2017-08-08 07:47:22 +08:00
|
|
|
}
|
|
|
|
|
results.push(...findAll(child, predicate, options));
|
2018-02-10 00:11:22 +08:00
|
|
|
});
|
2017-08-08 07:47:22 +08:00
|
|
|
|
|
|
|
|
return results;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function expectOne(
|
|
|
|
|
all: Array<ReactTestInstance>,
|
|
|
|
|
message: string,
|
|
|
|
|
): ReactTestInstance {
|
|
|
|
|
if (all.length === 1) {
|
|
|
|
|
return all[0];
|
|
|
|
|
}
|
|
|
|
|
|
2017-11-08 02:09:33 +08:00
|
|
|
const prefix =
|
|
|
|
|
all.length === 0
|
|
|
|
|
? 'No instances found '
|
|
|
|
|
: `Expected 1 but found ${all.length} instances `;
|
2017-08-08 07:47:22 +08:00
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-27 10:04:32 +08:00
|
|
|
function create(element: React$Element<any>, options: TestRendererOptions) {
|
|
|
|
|
let createNodeMock = defaultTestOptions.createNodeMock;
|
|
|
|
|
let isConcurrent = false;
|
2021-05-05 03:42:48 +08:00
|
|
|
let isStrictMode = false;
|
2021-04-29 04:09:30 +08:00
|
|
|
let concurrentUpdatesByDefault = null;
|
2020-02-27 10:04:32 +08:00
|
|
|
if (typeof options === 'object' && options !== null) {
|
|
|
|
|
if (typeof options.createNodeMock === 'function') {
|
|
|
|
|
createNodeMock = options.createNodeMock;
|
|
|
|
|
}
|
|
|
|
|
if (options.unstable_isConcurrent === true) {
|
|
|
|
|
isConcurrent = true;
|
|
|
|
|
}
|
2021-05-05 03:42:48 +08:00
|
|
|
if (options.unstable_strictMode === true) {
|
|
|
|
|
isStrictMode = true;
|
|
|
|
|
}
|
2021-04-29 04:09:30 +08:00
|
|
|
if (allowConcurrentByDefault) {
|
|
|
|
|
if (options.unstable_concurrentUpdatesByDefault !== undefined) {
|
|
|
|
|
concurrentUpdatesByDefault =
|
|
|
|
|
options.unstable_concurrentUpdatesByDefault;
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-02-27 10:04:32 +08:00
|
|
|
}
|
|
|
|
|
let container = {
|
|
|
|
|
children: [],
|
|
|
|
|
createNodeMock,
|
|
|
|
|
tag: 'CONTAINER',
|
|
|
|
|
};
|
|
|
|
|
let root: FiberRoot | null = createContainer(
|
|
|
|
|
container,
|
|
|
|
|
isConcurrent ? ConcurrentRoot : LegacyRoot,
|
|
|
|
|
false,
|
|
|
|
|
null,
|
2021-05-05 03:42:48 +08:00
|
|
|
isStrictMode,
|
2021-04-29 04:09:30 +08:00
|
|
|
concurrentUpdatesByDefault,
|
2020-02-27 10:04:32 +08:00
|
|
|
);
|
[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');
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-27 10:04:32 +08:00
|
|
|
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;
|
2018-03-29 05:57:25 +08:00
|
|
|
}
|
2020-02-27 10:04:32 +08:00
|
|
|
if (container.children.length === 0) {
|
|
|
|
|
return null;
|
2018-03-29 05:57:25 +08:00
|
|
|
}
|
2020-02-27 10:04:32 +08:00
|
|
|
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
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-02-27 10:04:32 +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,
|
2020-02-27 10:04:32 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Object.defineProperty(
|
|
|
|
|
entry,
|
|
|
|
|
'root',
|
|
|
|
|
({
|
|
|
|
|
configurable: true,
|
|
|
|
|
enumerable: true,
|
|
|
|
|
get: function() {
|
|
|
|
|
if (root === null) {
|
|
|
|
|
throw new Error("Can't access .root on unmounted test renderer");
|
2017-01-12 03:19:32 +08:00
|
|
|
}
|
2020-02-27 10:04: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);
|
2017-01-12 03:19:32 +08:00
|
|
|
}
|
2018-05-11 06:25:32 +08:00
|
|
|
},
|
2020-02-27 10:04:32 +08:00
|
|
|
}: Object),
|
|
|
|
|
);
|
2017-01-12 03:19:32 +08:00
|
|
|
|
2020-02-27 10:04:32 +08:00
|
|
|
return entry;
|
|
|
|
|
}
|
2019-02-06 00:10:16 +08:00
|
|
|
|
2018-09-10 23:15:18 +08:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2018-06-21 00:24:52 +08:00
|
|
|
// Enable ReactTestRenderer to be used to test DevTools integration.
|
2018-11-20 07:32:54 +08:00
|
|
|
injectIntoDevTools({
|
2018-06-21 00:24:52 +08:00
|
|
|
findFiberByHostInstance: (() => {
|
|
|
|
|
throw new Error('TestRenderer does not support findFiberByHostInstance()');
|
|
|
|
|
}: any),
|
|
|
|
|
bundleType: __DEV__ ? 1 : 0,
|
|
|
|
|
version: ReactVersion,
|
|
|
|
|
rendererPackageName: 'react-test-renderer',
|
|
|
|
|
});
|
|
|
|
|
|
2020-02-27 10:04:32 +08:00
|
|
|
export {
|
|
|
|
|
Scheduler as _Scheduler,
|
|
|
|
|
create,
|
|
|
|
|
/* eslint-disable-next-line camelcase */
|
|
|
|
|
batchedUpdates as unstable_batchedUpdates,
|
|
|
|
|
act,
|
|
|
|
|
};
|