Compare commits
197 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
487f4bf2ee | ||
|
|
297165f1e1 | ||
|
|
cd5b8950a0 | ||
|
|
f00be84b81 | ||
|
|
e0c2c56dfd | ||
|
|
aa8736a3a9 | ||
|
|
d9fa20d52f | ||
|
|
84cc8a31fa | ||
|
|
f9e41e3a51 | ||
|
|
fb572afc14 | ||
|
|
a0a2e846ce | ||
|
|
1fc13e4b35 | ||
|
|
b5cb9d345c | ||
|
|
342fa78ed4 | ||
|
|
62f5d4a057 | ||
|
|
6b86a6e039 | ||
|
|
8f7335875c | ||
|
|
d822d4bbe7 | ||
|
|
13a3788c54 | ||
|
|
d8a73b5eb6 | ||
|
|
741aa17a33 | ||
|
|
95c2b49543 | ||
|
|
55cf14f98e | ||
|
|
29b7b775f2 | ||
|
|
b668168d4d | ||
|
|
619cdfc624 | ||
|
|
3e55560438 | ||
|
|
dfabb77a97 | ||
|
|
c555c008b6 | ||
|
|
ff188d666b | ||
|
|
c4d8ef6430 | ||
|
|
08e9554357 | ||
|
|
0e4135e8c2 | ||
|
|
6d4038f0a6 | ||
|
|
fa6205d522 | ||
|
|
c6bee765ba | ||
|
|
3ae94e1885 | ||
|
|
f3a14951ab | ||
|
|
f24a0da6e0 | ||
|
|
1fecba9230 | ||
|
|
e15542ee0f | ||
|
|
c11015ff4f | ||
|
|
3e295edd52 | ||
|
|
1d48b4a684 | ||
|
|
aa9423701e | ||
|
|
45fc46bfa0 | ||
|
|
b7cc6b2e6f | ||
|
|
f2e2637c8e | ||
|
|
1107b9673c | ||
|
|
0975ea3278 | ||
|
|
d827445a50 | ||
|
|
bc9818f24d | ||
|
|
b5398a9130 | ||
|
|
ce6ecd3fbf | ||
|
|
008a2ab9cd | ||
|
|
d1326f466a | ||
|
|
267ed98146 | ||
|
|
fb3f7bfde9 | ||
|
|
81470a0027 | ||
|
|
e602b5291c | ||
|
|
e489c3f9c1 | ||
|
|
c21c41ecfa | ||
|
|
fec00a869c | ||
|
|
66eb293742 | ||
|
|
51c07912ac | ||
|
|
70d4075832 | ||
|
|
ba6477aa3c | ||
|
|
cb1ff430e8 | ||
|
|
9d483dcfd6 | ||
|
|
6d5ea54eee | ||
|
|
e19c9e1064 | ||
|
|
f11a9c1cb0 | ||
|
|
e679a4b6e2 | ||
|
|
8bcc88f2e7 | ||
|
|
6cb26774e2 | ||
|
|
73962c3664 | ||
|
|
9944392285 | ||
|
|
f0befae657 | ||
|
|
a129259ad6 | ||
|
|
c068d31cca | ||
|
|
db695c4d32 | ||
|
|
38247cba36 | ||
|
|
3f0bcaf0db | ||
|
|
ecd919a2f9 | ||
|
|
3fbebb2a0b | ||
|
|
5fce6488ce | ||
|
|
fe2ecd276e | ||
|
|
8f45a7fdc4 | ||
|
|
1fcbd22431 | ||
|
|
2a084f51a9 | ||
|
|
b5a3df6e88 | ||
|
|
9c146e6751 | ||
|
|
baa6d40fc8 | ||
|
|
a1414e8949 | ||
|
|
10a7a5b5ce | ||
|
|
a2fa6eb98d | ||
|
|
9120f6c2d8 | ||
|
|
b66e6e41e6 | ||
|
|
8c1614a2fd | ||
|
|
177fb76353 | ||
|
|
d17d0b99c1 | ||
|
|
4f332885a2 | ||
|
|
e1cd83e49d | ||
|
|
be457ca685 | ||
|
|
17d70df919 | ||
|
|
4846809370 | ||
|
|
4feab7fc92 | ||
|
|
1454a8be03 | ||
|
|
71b64d5211 | ||
|
|
790c8ef041 | ||
|
|
8a120095bf | ||
|
|
7ab8a8e979 | ||
|
|
4392e3821d | ||
|
|
153a0b5980 | ||
|
|
0ca628b216 | ||
|
|
7ad9806d11 | ||
|
|
0fc1547513 | ||
|
|
edb1f59564 | ||
|
|
3e15b1c690 | ||
|
|
0005d1e3f5 | ||
|
|
f290138d32 | ||
|
|
b4ad8e9471 | ||
|
|
ab03e3d651 | ||
|
|
19ef0ec116 | ||
|
|
a9b035b0c2 | ||
|
|
547e059f0b | ||
|
|
3494ee57e6 | ||
|
|
fef40c061e | ||
|
|
659c13963e | ||
|
|
84b86471ea | ||
|
|
c695b2384f | ||
|
|
1c5aa2f23a | ||
|
|
653bc582f9 | ||
|
|
8bfef0da55 | ||
|
|
4a1072194f | ||
|
|
535804f5c8 | ||
|
|
2743fb7b23 | ||
|
|
7325ebe4d6 | ||
|
|
a22880e5e5 | ||
|
|
947bddd5cf | ||
|
|
ce43a8cd07 | ||
|
|
f64906fba1 | ||
|
|
8df4d59be5 | ||
|
|
5bb4ad7376 | ||
|
|
98eb5ae531 | ||
|
|
39489e7674 | ||
|
|
1dc108e582 | ||
|
|
d9871729c8 | ||
|
|
6bf5e85986 | ||
|
|
7a48c900b7 | ||
|
|
e382b0ba95 | ||
|
|
8482cbe22d | ||
|
|
f00c2755b9 | ||
|
|
52bea95cfc | ||
|
|
1d25aa5787 | ||
|
|
f1bf281605 | ||
|
|
16e120438c | ||
|
|
d14ba87b1b | ||
|
|
88ada98198 | ||
|
|
4f964f09c1 | ||
|
|
c2a2d8a539 | ||
|
|
48f1e5b3ce | ||
|
|
f93f3402f7 | ||
|
|
ee3ef3a079 | ||
|
|
33f6f5e532 | ||
|
|
409066a0a1 | ||
|
|
a7f270c550 | ||
|
|
ed4c4a51cd | ||
|
|
686f1060ad | ||
|
|
7475120ce7 | ||
|
|
0c7189d923 | ||
|
|
14be29b2b9 | ||
|
|
dc0dd4bbff | ||
|
|
dd8205cef9 | ||
|
|
1da310809e | ||
|
|
a9fdf8a326 | ||
|
|
327cf0ee33 | ||
|
|
c954efa70f | ||
|
|
ccb14e270c | ||
|
|
0e9cb3f5d0 | ||
|
|
592676503c | ||
|
|
9b2fb24f99 | ||
|
|
a22fabc2a1 | ||
|
|
21d5f7d32d | ||
|
|
5f06576f51 | ||
|
|
8feeed10d8 | ||
|
|
5bce0ef10a | ||
|
|
d7fd679a31 | ||
|
|
3cd89daed9 | ||
|
|
85f5a81eb7 | ||
|
|
f55795c8ee | ||
|
|
d204747bef | ||
|
|
b98adb648a | ||
|
|
f8bfd58680 | ||
|
|
961eb65b4b | ||
|
|
1a6ab1e9b5 | ||
|
|
8ae867e6b5 |
@@ -40,3 +40,9 @@ jobs:
|
||||
key: v1-node-{{ arch }}-{{ .Branch }}-{{ checksum "yarn.lock" }}
|
||||
paths:
|
||||
- node_modules
|
||||
|
||||
- store_artifacts:
|
||||
path: ./node_modules.tgz
|
||||
|
||||
- store_artifacts:
|
||||
path: ./scripts/error-codes/codes.json
|
||||
137
CHANGELOG.md
137
CHANGELOG.md
@@ -6,12 +6,145 @@
|
||||
</summary>
|
||||
</details>
|
||||
|
||||
## 16.8.6 (March 27, 2019)
|
||||
|
||||
### React DOM
|
||||
|
||||
* Fix an incorrect bailout in `useReducer()`. ([@acdlite](https://github.com/acdlite) in [#15124](https://github.com/facebook/react/pull/15124))
|
||||
* Fix iframe warnings in Safari DevTools. ([@renanvalentin](https://github.com/renanvalentin) in [#15099](https://github.com/facebook/react/pull/15099))
|
||||
* Warn if `contextType` is set to `Context.Consumer` instead of `Context`. ([@aweary](https://github.com/aweary) in [#14831](https://github.com/facebook/react/pull/14831))
|
||||
* Warn if `contextType` is set to invalid values. ([@gaearon](https://github.com/gaearon) in [#15142](https://github.com/facebook/react/pull/15142))
|
||||
|
||||
## 16.8.5 (March 22, 2019)
|
||||
|
||||
### React DOM
|
||||
|
||||
* Don't set the first option as selected in select tag with `size` attribute. ([@kulek1](https://github.com/kulek1) in [#14242](https://github.com/facebook/react/pull/14242))
|
||||
* Improve the `useEffect(async () => ...)` warning message. ([@gaearon](https://github.com/gaearon) in [#15118](https://github.com/facebook/react/pull/15118))
|
||||
* Improve the error message sometimes caused by duplicate React. ([@jaredpalmer](https://github.com/jaredpalmer) in [#15139](https://github.com/facebook/react/pull/15139))
|
||||
|
||||
### React DOM Server
|
||||
|
||||
* Improve the `useLayoutEffect` warning message when server rendering. ([@gaearon](https://github.com/gaearon) in [#15158](https://github.com/facebook/react/pull/15158))
|
||||
|
||||
### React Shallow Renderer
|
||||
|
||||
* Fix `setState` in shallow renderer to work with Hooks. ([@gaearon](https://github.com/gaearon) in [#15120](https://github.com/facebook/react/pull/15120))
|
||||
* Fix shallow renderer to support `React.memo`. ([@aweary](https://github.com/aweary) in [#14816](https://github.com/facebook/react/pull/14816))
|
||||
* Fix shallow renderer to support Hooks inside `forwardRef`. ([@eps1lon](https://github.com/eps1lon) in [#15100](https://github.com/facebook/react/pull/15100))
|
||||
|
||||
## 16.8.4 (March 5, 2019)
|
||||
|
||||
### React DOM and other renderers
|
||||
|
||||
- Fix a bug where DevTools caused a runtime error when inspecting a component that used a `useContext` hook. ([@bvaughn](https://github.com/bvaughn) in [#14940](https://github.com/facebook/react/pull/14940))
|
||||
|
||||
## 16.8.3 (February 21, 2019)
|
||||
|
||||
### React DOM
|
||||
|
||||
- Fix a bug that caused inputs to behave incorrectly in UMD builds. ([@gaearon](https://github.com/gaearon) in [#14914](https://github.com/facebook/react/pull/14914))
|
||||
- Fix a bug that caused render phase updates to be discarded. ([@gaearon](https://github.com/gaearon) in [#14852](https://github.com/facebook/react/pull/14852))
|
||||
|
||||
### React DOM Server
|
||||
- Unwind the context stack when a stream is destroyed without completing, to prevent incorrect values during a subsequent render. ([@overlookmotel](https://github.com/overlookmotel) in [#14706](https://github.com/facebook/react/pull/14706/))
|
||||
|
||||
## 16.8.2 (February 14, 2019)
|
||||
|
||||
### React DOM
|
||||
|
||||
* Fix `ReactDOM.render` being ignored inside `useEffect`. ([@gaearon](https://github.com/gaearon) in [#14799](https://github.com/facebook/react/pull/14799))
|
||||
* Fix a crash when unmounting empty portals. ([@gaearon](https://github.com/gaearon) in [#14820](https://github.com/facebook/react/pull/14820))
|
||||
* Fix `useImperativeHandle` to work correctly when no deps are specified. ([@gaearon](https://github.com/gaearon) in [#14801](https://github.com/facebook/react/pull/14801))
|
||||
* Fix `crossOrigin` attribute to work in SVG `image` elements. ([@aweary](https://github.com/aweary) in [#14832](https://github.com/facebook/react/pull/14832))
|
||||
* Fix a false positive warning when using Suspense with Hooks. ([@gaearon](https://github.com/gaearon) in [#14821](https://github.com/facebook/react/pull/14821))
|
||||
|
||||
### React Test Utils and React Test Renderer
|
||||
|
||||
* Include component stack into the `act()` warning. ([@threepointone](https://github.com/threepointone) in [#14855](https://github.com/facebook/react/pull/14855))
|
||||
|
||||
## 16.8.1 (February 6, 2019)
|
||||
|
||||
### React DOM and React Test Renderer
|
||||
|
||||
* Fix a crash when used together with an older version of React. ([@bvaughn](https://github.com/bvaughn) in [#14770](https://github.com/facebook/react/pull/14770))
|
||||
|
||||
### React Test Utils
|
||||
|
||||
* Fix a crash in Node environment. ([@threepointone](https://github.com/threepointone) in [#14768](https://github.com/facebook/react/pull/14768))
|
||||
|
||||
## 16.8.0 (February 6, 2019)
|
||||
|
||||
### React
|
||||
|
||||
* Add [Hooks](https://reactjs.org/docs/hooks-intro.html) — a way to use state and other React features without writing a class. ([@acdlite](https://github.com/acdlite) et al. in [#13968](https://github.com/facebook/react/pull/13968))
|
||||
* Improve the `useReducer` Hook lazy initialization API. ([@acdlite](https://github.com/acdlite) in [#14723](https://github.com/facebook/react/pull/14723))
|
||||
|
||||
### React DOM
|
||||
|
||||
* Bail out of rendering on identical values for `useState` and `useReducer` Hooks. ([@acdlite](https://github.com/acdlite) in [#14569](https://github.com/facebook/react/pull/14569))
|
||||
* Use `Object.is` algorithm for comparing `useState` and `useReducer` values. ([@Jessidhia](https://github.com/Jessidhia) in [#14752](https://github.com/facebook/react/pull/14752))
|
||||
* Don’t compare the first argument passed to `useEffect`/`useMemo`/`useCallback` Hooks. ([@acdlite](https://github.com/acdlite) in [#14594](https://github.com/facebook/react/pull/14594))
|
||||
* Support synchronous thenables passed to `React.lazy()`. ([@gaearon](https://github.com/gaearon) in [#14626](https://github.com/facebook/react/pull/14626))
|
||||
* Render components with Hooks twice in Strict Mode (DEV-only) to match class behavior. ([@gaearon](https://github.com/gaearon) in [#14654](https://github.com/facebook/react/pull/14654))
|
||||
* Warn about mismatching Hook order in development. ([@threepointone](https://github.com/threepointone) in [#14585](https://github.com/facebook/react/pull/14585) and [@acdlite](https://github.com/acdlite) in [#14591](https://github.com/facebook/react/pull/14591))
|
||||
* Effect clean-up functions must return either `undefined` or a function. All other values, including `null`, are not allowed. [@acdlite](https://github.com/acdlite) in [#14119](https://github.com/facebook/react/pull/14119)
|
||||
|
||||
### React Test Renderer and Test Utils
|
||||
|
||||
* Support Hooks in the shallow renderer. ([@trueadm](https://github.com/trueadm) in [#14567](https://github.com/facebook/react/pull/14567))
|
||||
* Fix wrong state in `shouldComponentUpdate` in the presence of `getDerivedStateFromProps` for Shallow Renderer. ([@chenesan](https://github.com/chenesan) in [#14613](https://github.com/facebook/react/pull/14613))
|
||||
* Add `ReactTestRenderer.act()` and `ReactTestUtils.act()` for batching updates so that tests more closely match real behavior. ([@threepointone](https://github.com/threepointone) in [#14744](https://github.com/facebook/react/pull/14744))
|
||||
|
||||
|
||||
### ESLint Plugin: React Hooks
|
||||
|
||||
* Initial [release](https://www.npmjs.com/package/eslint-plugin-react-hooks). ([@calebmer](https://github.com/calebmer) in [#13968](https://github.com/facebook/react/pull/13968))
|
||||
* Fix reporting after encountering a loop. ([@calebmer](https://github.com/calebmer) and [@Yurickh](https://github.com/Yurickh) in [#14661](https://github.com/facebook/react/pull/14661))
|
||||
* Don't consider throwing to be a rule violation. ([@sophiebits](https://github.com/sophiebits) in [#14040](https://github.com/facebook/react/pull/14040))
|
||||
|
||||
## 16.7.0 (December 19, 2018)
|
||||
|
||||
### React DOM
|
||||
|
||||
* Fix performance of `React.lazy` for large numbers of lazily-loaded components. ([@acdlite](http://github.com/acdlite) in [#14429](https://github.com/facebook/react/pull/14429))
|
||||
* Clear fields on unmount to avoid memory leaks. ([@trueadm](http://github.com/trueadm) in [#14276](https://github.com/facebook/react/pull/14276))
|
||||
* Fix bug with SSR and context when mixing `react-dom/server@16.6` and `react@<16.6`. ([@gaearon](http://github.com/gaearon) in [#14291](https://github.com/facebook/react/pull/14291))
|
||||
* Fix a performance regression in profiling mode. ([@bvaughn](http://github.com/bvaughn) in [#14383](https://github.com/facebook/react/pull/14383))
|
||||
|
||||
### Scheduler (Experimental)
|
||||
|
||||
* Post to MessageChannel instead of window. ([@acdlite](http://github.com/acdlite) in [#14234](https://github.com/facebook/react/pull/14234))
|
||||
* Reduce serialization overhead. ([@developit](http://github.com/developit) in [#14249](https://github.com/facebook/react/pull/14249))
|
||||
* Fix fallback to `setTimeout` in testing environments. ([@bvaughn](http://github.com/bvaughn) in [#14358](https://github.com/facebook/react/pull/14358))
|
||||
* Add methods for debugging. ([@mrkev](http://github.com/mrkev) in [#14053](https://github.com/facebook/react/pull/14053))
|
||||
|
||||
|
||||
## 16.6.3 (November 12, 2018)
|
||||
|
||||
### React DOM
|
||||
|
||||
* Fix bugs in `Suspense` and `lazy`. ([@acdlite](https://github.com/acdlite) in [#14133](https://github.com/facebook/react/pull/14133), [#14157](https://github.com/facebook/react/pull/14157), and [#14164](https://github.com/facebook/react/pull/14164))
|
||||
* Fix highlighting of `React.memo` updates in React DevTools. ([@bvaughn](https://github.com/bvaughn) in [#14141](https://github.com/facebook/react/pull/14141))
|
||||
* Fix interaction of Suspense with the React Profiler. ([@bvaughn](https://github.com/bvaughn) in [#14065](https://github.com/facebook/react/pull/14065))
|
||||
* Fix a false positive warning when using Suspense. ([@acdlite](https://github.com/acdlite) in [#14158](https://github.com/facebook/react/pull/14158))
|
||||
|
||||
### React DOM Server
|
||||
|
||||
* Fix incorrect sharing of context state between `renderToNodeStream()` calls. ([@sebmarkbage](https://github.com/sebmarkbage) in [#14182](https://github.com/facebook/react/pull/14182))
|
||||
* Add a warning about incorrect usage of the context API. ([@trueadm](https://github.com/trueadm) in [#14033](https://github.com/facebook/react/pull/14033))
|
||||
|
||||
## 16.6.2 (November 12, 2018)
|
||||
|
||||
This release was published in a broken state and should be skipped.
|
||||
|
||||
## 16.6.1 (November 6, 2018)
|
||||
|
||||
### React DOM
|
||||
|
||||
* Fallback should not remount every time a promise resolves. ([@acdlite](https://github.com/acdlite) in [#14083](https://github.com/facebook/react/pull/14083))
|
||||
* Fix bug where Suspense keeps showing fallback even after everything finishes loading. ([@acdlite](https://github.com/acdlite) in [#14083](https://github.com/facebook/react/pull/14083))
|
||||
* Fix a crash when Suspense finishes loading in IE11. ([@sophiebits](https://github.com/sophiebits) in [#14126](https://github.com/facebook/react/pull/14126))
|
||||
* Fix unresolved default props in lifecycle methods of a lazy component. ([@gaearon](https://github.com/gaearon) in [#14112](https://github.com/facebook/react/pull/14112))
|
||||
* Fix bug when recovering from an error thrown during complete phase. ([@gaearon](https://github.com/gaearon) in [#14104](https://github.com/facebook/react/pull/14104))
|
||||
|
||||
@@ -107,7 +240,7 @@
|
||||
* Fix incorrect data in `compositionend` event when typing Korean on IE11 ([@crux153](https://github.com/crux153) in [#12563](https://github.com/facebook/react/issues/12563))
|
||||
* Fix a crash when using dynamic `children` in the `<option>` tag ([@Slowyn](https://github.com/Slowyn) in [#13261](https://github.com/facebook/react/issues/13261), [@gaearon](https://github.com/gaearon) in [#13465](https://github.com/facebook/react/pull/13465))
|
||||
* Fix the `checked` attribute not getting initially set on the `input` ([@dilidili](https://github.com/dilidili) in [#13114](https://github.com/facebook/react/issues/13114))
|
||||
* Fix hydration of `dangerouslySetInnerHTML` when `__html` is not a string ([@gaearon](https://github.com/gaearon) in [#13353](https://github.com/facebook/react/issues/13353))
|
||||
* Fix hydration of `dangerouslySetInnerHTML` when `__html` is not a string ([@gaearon](https://github.com/gaearon) in [#13353](https://github.com/facebook/react/issues/13353))
|
||||
* Fix a warning about missing controlled `onChange` to fire on falsy values too ([@nicolevy](https://github.com/nicolevy) in [#12628](https://github.com/facebook/react/issues/12628))
|
||||
* Fix `submit` and `reset` buttons getting an empty label ([@ellsclytn](https://github.com/ellsclytn) in [#12780](https://github.com/facebook/react/issues/12780))
|
||||
* Fix the `onSelect` event not being triggered after drag and drop ([@gaearon](https://github.com/gaearon) in [#13422](https://github.com/facebook/react/issues/13422))
|
||||
@@ -298,7 +431,7 @@
|
||||
* Deduplicate warning messages about invalid callback. ([@yenshih](https://github.com/yenshih) in [#11833](https://github.com/facebook/react/pull/11833))
|
||||
* Deprecate `ReactDOM.unstable_createPortal()` in favor of `ReactDOM.createPortal()`. ([@prometheansacrifice](https://github.com/prometheansacrifice) in [#11747](https://github.com/facebook/react/pull/11747))
|
||||
* Don't emit User Timing entries for context types. ([@abhaynikam](https://github.com/abhaynikam) in [#12250](https://github.com/facebook/react/pull/12250))
|
||||
* Improve the error message when context consumer child isn't a function. ([@raunofreiberg](https://github.com/raunofreiberg) in [#12267](https://github.com/facebook/react/pull/12267))
|
||||
* Improve the error message when context consumer child isn't a function. ([@raunofreiberg](https://github.com/raunofreiberg) in [#12267](https://github.com/facebook/react/pull/12267))
|
||||
* Improve the error message when adding a ref to a functional component. ([@skiritsis](https://github.com/skiritsis) in [#11782](https://github.com/facebook/react/pull/11782))
|
||||
|
||||
### React DOM Server
|
||||
|
||||
10
README.md
10
README.md
@@ -40,10 +40,8 @@ You can improve it by sending pull requests to [this repository](https://github.
|
||||
We have several examples [on the website](https://reactjs.org/). Here is the first one to get you started:
|
||||
|
||||
```jsx
|
||||
class HelloMessage extends React.Component {
|
||||
render() {
|
||||
return <div>Hello {this.props.name}</div>;
|
||||
}
|
||||
function HelloMessage({ name }) {
|
||||
return <div>Hello {name}</div>;
|
||||
}
|
||||
|
||||
ReactDOM.render(
|
||||
@@ -60,9 +58,9 @@ You'll notice that we used an HTML-like syntax; [we call it JSX](https://reactjs
|
||||
|
||||
The main purpose of this repository is to continue to evolve React core, making it faster and easier to use. Development of React happens in the open on GitHub, and we are grateful to the community for contributing bugfixes and improvements. Read below to learn how you can take part in improving React.
|
||||
|
||||
### [Code of Conduct](https://code.facebook.com/codeofconduct)
|
||||
### [Code of Conduct](https://code.fb.com/codeofconduct)
|
||||
|
||||
Facebook has adopted a Code of Conduct that we expect project participants to adhere to. Please read [the full text](https://code.facebook.com/codeofconduct) so that you can understand what actions will and will not be tolerated.
|
||||
Facebook has adopted a Code of Conduct that we expect project participants to adhere to. Please read [the full text](https://code.fb.com/codeofconduct) so that you can understand what actions will and will not be tolerated.
|
||||
|
||||
### [Contributing Guide](https://reactjs.org/contributing/how-to-contribute.html)
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
//
|
||||
// You'll need a GitHub token, you can re-use this one:
|
||||
//
|
||||
// e622517d9f1136ea8900 07c6373666312cdfaa69
|
||||
// 0a7d5c3cad9a6dbec2d9 9a5222cf49062a4c1ef7
|
||||
//
|
||||
// (Just remove the space)
|
||||
//
|
||||
@@ -29,9 +29,16 @@ const {markdown, danger} = require('danger');
|
||||
const fetch = require('node-fetch');
|
||||
|
||||
const {generateResultsArray} = require('./scripts/rollup/stats');
|
||||
const {readFileSync} = require('fs');
|
||||
const {existsSync, readFileSync} = require('fs');
|
||||
const {exec} = require('child_process');
|
||||
|
||||
if (!existsSync('./scripts/rollup/results.json')) {
|
||||
// This indicates the build failed previously.
|
||||
// In that case, there's nothing for the Dangerfile to do.
|
||||
// Exit early to avoid leaving a redundant (and potentially confusing) PR comment.
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const currentBuildResults = JSON.parse(
|
||||
readFileSync('./scripts/rollup/results.json')
|
||||
);
|
||||
|
||||
@@ -32,7 +32,7 @@ class VectorWidget extends React.Component {
|
||||
/**
|
||||
* When the component is mounted into the document - this is similar to a
|
||||
* constructor, but invoked when the instance is actually mounted into the
|
||||
* document. Here's, we'll just set up an animation loop that invokes our
|
||||
* document. Here, we'll just set up an animation loop that invokes our
|
||||
* method. Binding of `this.onTick` is not needed because all React methods
|
||||
* are automatically bound before being mounted.
|
||||
*/
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
},
|
||||
"scripts": {
|
||||
"prestart":
|
||||
"cp ../../build/dist/react.development.js public/ && cp ../../build/dist/react-dom.development.js public/ && cp ../../build/dist/react-dom-server.browser.development.js public/",
|
||||
"cp ../../build/node_modules/react/umd/react.development.js public/ && cp ../../build/node_modules/react-dom/umd/react-dom.development.js public/ && cp ../../build/node_modules/react-dom/umd/react-dom-server.browser.development.js public/",
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test --env=jsdom",
|
||||
|
||||
3
fixtures/dom/.gitignore
vendored
3
fixtures/dom/.gitignore
vendored
@@ -9,8 +9,11 @@ coverage
|
||||
# production
|
||||
build
|
||||
public/react.development.js
|
||||
public/react.production.min.js
|
||||
public/react-dom.development.js
|
||||
public/react-dom.production.min.js
|
||||
public/react-dom-server.browser.development.js
|
||||
public/react-dom-server.browser.production.min.js
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"prestart": "cp ../../build/dist/react.development.js ../../build/dist/react-dom.development.js ../../build/dist/react-dom-server.browser.development.js public/",
|
||||
"prestart": "cp ../../build/node_modules/react/umd/react.development.js ../../build/node_modules/react-dom/umd/react-dom.development.js ../../build/node_modules/react/umd/react.production.min.js ../../build/node_modules/react-dom/umd/react-dom.production.min.js ../../build/node_modules/react-dom/umd/react-dom-server.browser.development.js ../../build/node_modules/react-dom/umd/react-dom-server.browser.production.min.js public/",
|
||||
"build": "react-scripts build && cp build/index.html build/200.html",
|
||||
"test": "react-scripts test --env=jsdom",
|
||||
"eject": "react-scripts eject"
|
||||
|
||||
@@ -7,8 +7,9 @@ class Header extends React.Component {
|
||||
super(props, context);
|
||||
const query = parse(window.location.search);
|
||||
const version = query.version || 'local';
|
||||
const production = query.production || false;
|
||||
const versions = [version];
|
||||
this.state = {version, versions};
|
||||
this.state = {version, versions, production};
|
||||
}
|
||||
componentWillMount() {
|
||||
getVersionTags().then(tags => {
|
||||
@@ -25,6 +26,14 @@ class Header extends React.Component {
|
||||
}
|
||||
window.location.search = stringify(query);
|
||||
}
|
||||
handleProductionChange(event) {
|
||||
const query = parse(window.location.search);
|
||||
query.production = event.target.checked;
|
||||
if (!query.production) {
|
||||
delete query.production;
|
||||
}
|
||||
window.location.search = stringify(query);
|
||||
}
|
||||
handleFixtureChange(event) {
|
||||
window.location.pathname = event.target.value;
|
||||
}
|
||||
@@ -43,6 +52,16 @@ class Header extends React.Component {
|
||||
</span>
|
||||
|
||||
<div className="header-controls">
|
||||
<input
|
||||
id="react_production"
|
||||
className="header__checkbox"
|
||||
type="checkbox"
|
||||
checked={this.state.production}
|
||||
onChange={this.handleProductionChange}
|
||||
/>
|
||||
<label htmlFor="react_production" className="header__label">
|
||||
Production
|
||||
</label>
|
||||
<label htmlFor="example">
|
||||
<span className="sr-only">Select an example</span>
|
||||
<select
|
||||
|
||||
@@ -202,6 +202,34 @@ class SelectFixture extends React.Component {
|
||||
</select>
|
||||
</div>
|
||||
</TestCase>
|
||||
|
||||
<TestCase
|
||||
title="A select with the size attribute should not set first option as selected"
|
||||
relatedIssues="14239"
|
||||
introducedIn="16.0.0">
|
||||
<TestCase.ExpectedResult>
|
||||
No options should be selected.
|
||||
</TestCase.ExpectedResult>
|
||||
|
||||
<div className="test-fixture">
|
||||
<select size="3">
|
||||
<option>0</option>
|
||||
<option>1</option>
|
||||
<option>2</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<p className="footnote">
|
||||
<b>Notes:</b> This happens if <code>size</code> is assigned after
|
||||
options are selected. The select element picks the first item by
|
||||
default, then it is expanded to show more options when{' '}
|
||||
<code>size</code> is assigned, preserving the default selection.
|
||||
</p>
|
||||
<p className="footnote">
|
||||
This was introduced in React 16.0.0 when options were added before
|
||||
select attribute assignment.
|
||||
</p>
|
||||
</TestCase>
|
||||
</FixtureSet>
|
||||
);
|
||||
}
|
||||
|
||||
36
fixtures/dom/src/react-loader.js
vendored
36
fixtures/dom/src/react-loader.js
vendored
@@ -37,12 +37,15 @@ function loadScript(src) {
|
||||
}
|
||||
|
||||
export function reactPaths() {
|
||||
let reactPath = 'react.development.js';
|
||||
let reactDOMPath = 'react-dom.development.js';
|
||||
let reactDOMServerPath = 'react-dom-server.browser.development.js';
|
||||
|
||||
let query = parseQuery(window.location.search);
|
||||
let version = query.version || 'local';
|
||||
let isProduction = query.production === 'true';
|
||||
|
||||
let environment = isProduction ? 'production.min' : 'development';
|
||||
|
||||
let reactPath = 'react.' + environment + '.js';
|
||||
let reactDOMPath = 'react-dom.' + environment + '.js';
|
||||
let reactDOMServerPath = 'react-dom-server.browser.' + environment + '.js';
|
||||
|
||||
if (version !== 'local') {
|
||||
const {major, minor, prerelease} = semver(version);
|
||||
@@ -51,21 +54,34 @@ export function reactPaths() {
|
||||
// Load the old module location for anything less than 16 RC
|
||||
if (major >= 16 && !(minor === 0 && preReleaseStage === 'alpha')) {
|
||||
reactPath =
|
||||
'https://unpkg.com/react@' + version + '/umd/react.development.js';
|
||||
'https://unpkg.com/react@' +
|
||||
version +
|
||||
'/umd/react.' +
|
||||
environment +
|
||||
'.js';
|
||||
reactDOMPath =
|
||||
'https://unpkg.com/react-dom@' +
|
||||
version +
|
||||
'/umd/react-dom.development.js';
|
||||
'/umd/react-dom.' +
|
||||
environment +
|
||||
'.js';
|
||||
reactDOMServerPath =
|
||||
'https://unpkg.com/react-dom@' +
|
||||
version +
|
||||
'/umd/react-dom-server.browser.development';
|
||||
'/umd/react-dom-server.browser.' +
|
||||
environment +
|
||||
'.js';
|
||||
} else {
|
||||
reactPath = 'https://unpkg.com/react@' + version + '/dist/react.js';
|
||||
let suffix = isProduction ? '.min.js' : '.js';
|
||||
|
||||
reactPath = 'https://unpkg.com/react@' + version + '/dist/react' + suffix;
|
||||
reactDOMPath =
|
||||
'https://unpkg.com/react-dom@' + version + '/dist/react-dom.js';
|
||||
'https://unpkg.com/react-dom@' + version + '/dist/react-dom' + suffix;
|
||||
reactDOMServerPath =
|
||||
'https://unpkg.com/react-dom@' + version + '/dist/react-dom-server.js';
|
||||
'https://unpkg.com/react-dom@' +
|
||||
version +
|
||||
'/dist/react-dom-server' +
|
||||
suffix;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -126,6 +126,15 @@ textarea {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.header__checkbox {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.header__label {
|
||||
font-size: 12px;
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.sr-only {
|
||||
clip: rect(0, 0, 0, 0);
|
||||
height: 0;
|
||||
|
||||
14
fixtures/eslint/.eslintrc.json
Normal file
14
fixtures/eslint/.eslintrc.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"root": true,
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 6,
|
||||
"sourceType": "module",
|
||||
"ecmaFeatures": {
|
||||
"jsx": true
|
||||
}
|
||||
},
|
||||
"plugins": ["react-hooks"],
|
||||
"rules": {
|
||||
"react-hooks/rules-of-hooks": 2
|
||||
}
|
||||
}
|
||||
7
fixtures/eslint/README.md
Normal file
7
fixtures/eslint/README.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# ESLint Playground Fixture
|
||||
|
||||
This is an internal playground for quick iteration on our lint rules inside an IDE like VSCode.
|
||||
|
||||
See instructions in `./index.js` in this directory.
|
||||
|
||||

|
||||
11
fixtures/eslint/index.js
Normal file
11
fixtures/eslint/index.js
Normal file
@@ -0,0 +1,11 @@
|
||||
// This is a testing playground for our lint rules.
|
||||
|
||||
// 1. Run yarn && yarn start
|
||||
// 2. "File > Add Folder to Workspace" this specific folder in VSCode with ESLint extension
|
||||
// 3. Changes to the rule source should get picked up without restarting ESLint server
|
||||
|
||||
function Foo() {
|
||||
if (condition) {
|
||||
useEffect(() => {});
|
||||
}
|
||||
}
|
||||
12
fixtures/eslint/package.json
Normal file
12
fixtures/eslint/package.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "eslint-playground",
|
||||
"dependencies": {
|
||||
"eslint": "4.1.0",
|
||||
"eslint-plugin-react-hooks": "link:./proxy"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "./watch.sh",
|
||||
"lint": "eslint index.js"
|
||||
}
|
||||
}
|
||||
35
fixtures/eslint/proxy/index.js
Normal file
35
fixtures/eslint/proxy/index.js
Normal file
@@ -0,0 +1,35 @@
|
||||
'use strict';
|
||||
|
||||
// This file is a proxy for our rule definition that will
|
||||
// load the latest built version on every check. This makes
|
||||
// it convenient to test inside IDEs (which would otherwise
|
||||
// load a version of our rule once and never restart the server).
|
||||
// See instructions in ../index.js playground.
|
||||
|
||||
let build;
|
||||
reload();
|
||||
|
||||
function reload() {
|
||||
for (let id in require.cache) {
|
||||
if (/eslint-plugin-react-hooks/.test(id)) {
|
||||
delete require.cache[id];
|
||||
}
|
||||
}
|
||||
// Point to the built version.
|
||||
build = require('../../../build/node_modules/eslint-plugin-react-hooks');
|
||||
}
|
||||
|
||||
let rules = {};
|
||||
for (let key in build.rules) {
|
||||
if (build.rules.hasOwnProperty(key)) {
|
||||
rules[key] = Object.assign({}, build.rules, {
|
||||
create() {
|
||||
// Reload changes to the built rule
|
||||
reload();
|
||||
return build.rules[key].create.apply(this, arguments);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {rules};
|
||||
4
fixtures/eslint/proxy/package.json
Normal file
4
fixtures/eslint/proxy/package.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"private": true,
|
||||
"version": "0.0.0"
|
||||
}
|
||||
3
fixtures/eslint/watch.sh
Executable file
3
fixtures/eslint/watch.sh
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
(cd ../.. && yarn build eslint --type=NODE_DEV)
|
||||
(cd ../.. && watchman-make --make 'yarn build eslint --type=NODE_DEV' -p 'packages/eslint-plugin-*/**/*' -t ignored)
|
||||
853
fixtures/eslint/yarn.lock
Normal file
853
fixtures/eslint/yarn.lock
Normal file
@@ -0,0 +1,853 @@
|
||||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
acorn-jsx@^3.0.0:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-3.0.1.tgz#afdf9488fb1ecefc8348f6fb22f464e32a58b36b"
|
||||
integrity sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=
|
||||
dependencies:
|
||||
acorn "^3.0.4"
|
||||
|
||||
acorn@^3.0.4:
|
||||
version "3.3.0"
|
||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a"
|
||||
integrity sha1-ReN/s56No/JbruP/U2niu18iAXo=
|
||||
|
||||
acorn@^5.5.0:
|
||||
version "5.7.3"
|
||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.3.tgz#67aa231bf8812974b85235a96771eb6bd07ea279"
|
||||
integrity sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==
|
||||
|
||||
ajv-keywords@^3.0.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.2.0.tgz#e86b819c602cf8821ad637413698f1dec021847a"
|
||||
integrity sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo=
|
||||
|
||||
ajv@^6.0.1:
|
||||
version "6.7.0"
|
||||
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.7.0.tgz#e3ce7bb372d6577bb1839f1dfdfcbf5ad2948d96"
|
||||
integrity sha512-RZXPviBTtfmtka9n9sy1N5M5b82CbxWIR6HIis4s3WQTXDJamc/0gpCWNGz6EWdWp4DOfjzJfhz/AS9zVPjjWg==
|
||||
dependencies:
|
||||
fast-deep-equal "^2.0.1"
|
||||
fast-json-stable-stringify "^2.0.0"
|
||||
json-schema-traverse "^0.4.1"
|
||||
uri-js "^4.2.2"
|
||||
|
||||
ansi-escapes@^3.0.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.1.0.tgz#f73207bb81207d75fd6c83f125af26eea378ca30"
|
||||
integrity sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw==
|
||||
|
||||
ansi-regex@^2.0.0:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df"
|
||||
integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8=
|
||||
|
||||
ansi-regex@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998"
|
||||
integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=
|
||||
|
||||
ansi-styles@^2.2.1:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
|
||||
integrity sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=
|
||||
|
||||
ansi-styles@^3.2.1:
|
||||
version "3.2.1"
|
||||
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
|
||||
integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==
|
||||
dependencies:
|
||||
color-convert "^1.9.0"
|
||||
|
||||
argparse@^1.0.7:
|
||||
version "1.0.10"
|
||||
resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911"
|
||||
integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==
|
||||
dependencies:
|
||||
sprintf-js "~1.0.2"
|
||||
|
||||
babel-code-frame@^6.22.0:
|
||||
version "6.26.0"
|
||||
resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b"
|
||||
integrity sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=
|
||||
dependencies:
|
||||
chalk "^1.1.3"
|
||||
esutils "^2.0.2"
|
||||
js-tokens "^3.0.2"
|
||||
|
||||
balanced-match@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
|
||||
integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
|
||||
|
||||
brace-expansion@^1.1.7:
|
||||
version "1.1.11"
|
||||
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
|
||||
integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
|
||||
dependencies:
|
||||
balanced-match "^1.0.0"
|
||||
concat-map "0.0.1"
|
||||
|
||||
buffer-from@^1.0.0:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
|
||||
integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==
|
||||
|
||||
caller-path@^0.1.0:
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-0.1.0.tgz#94085ef63581ecd3daa92444a8fe94e82577751f"
|
||||
integrity sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=
|
||||
dependencies:
|
||||
callsites "^0.2.0"
|
||||
|
||||
callsites@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/callsites/-/callsites-0.2.0.tgz#afab96262910a7f33c19a5775825c69f34e350ca"
|
||||
integrity sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=
|
||||
|
||||
chalk@^1.1.3:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
|
||||
integrity sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=
|
||||
dependencies:
|
||||
ansi-styles "^2.2.1"
|
||||
escape-string-regexp "^1.0.2"
|
||||
has-ansi "^2.0.0"
|
||||
strip-ansi "^3.0.0"
|
||||
supports-color "^2.0.0"
|
||||
|
||||
chalk@^2.0.0, chalk@^2.1.0:
|
||||
version "2.4.2"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
|
||||
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
|
||||
dependencies:
|
||||
ansi-styles "^3.2.1"
|
||||
escape-string-regexp "^1.0.5"
|
||||
supports-color "^5.3.0"
|
||||
|
||||
chardet@^0.4.0:
|
||||
version "0.4.2"
|
||||
resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.4.2.tgz#b5473b33dc97c424e5d98dc87d55d4d8a29c8bf2"
|
||||
integrity sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=
|
||||
|
||||
circular-json@^0.3.1:
|
||||
version "0.3.3"
|
||||
resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.3.tgz#815c99ea84f6809529d2f45791bdf82711352d66"
|
||||
integrity sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==
|
||||
|
||||
cli-cursor@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5"
|
||||
integrity sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=
|
||||
dependencies:
|
||||
restore-cursor "^2.0.0"
|
||||
|
||||
cli-width@^2.0.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639"
|
||||
integrity sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=
|
||||
|
||||
color-convert@^1.9.0:
|
||||
version "1.9.3"
|
||||
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
|
||||
integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==
|
||||
dependencies:
|
||||
color-name "1.1.3"
|
||||
|
||||
color-name@1.1.3:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
|
||||
integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=
|
||||
|
||||
concat-map@0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
|
||||
integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
|
||||
|
||||
concat-stream@^1.6.0:
|
||||
version "1.6.2"
|
||||
resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34"
|
||||
integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==
|
||||
dependencies:
|
||||
buffer-from "^1.0.0"
|
||||
inherits "^2.0.3"
|
||||
readable-stream "^2.2.2"
|
||||
typedarray "^0.0.6"
|
||||
|
||||
core-util-is@~1.0.0:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
|
||||
integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
|
||||
|
||||
debug@^2.6.8:
|
||||
version "2.6.9"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
|
||||
integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
|
||||
dependencies:
|
||||
ms "2.0.0"
|
||||
|
||||
deep-is@~0.1.3:
|
||||
version "0.1.3"
|
||||
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34"
|
||||
integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=
|
||||
|
||||
doctrine@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d"
|
||||
integrity sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==
|
||||
dependencies:
|
||||
esutils "^2.0.2"
|
||||
|
||||
escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
|
||||
integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
|
||||
|
||||
"eslint-plugin-react-hooks@link:./proxy":
|
||||
version "0.0.0"
|
||||
uid ""
|
||||
|
||||
eslint-scope@^3.7.1:
|
||||
version "3.7.3"
|
||||
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-3.7.3.tgz#bb507200d3d17f60247636160b4826284b108535"
|
||||
integrity sha512-W+B0SvF4gamyCTmUc+uITPY0989iXVfKvhwtmJocTaYoc/3khEHmEmvfY/Gn9HA9VV75jrQECsHizkNw1b68FA==
|
||||
dependencies:
|
||||
esrecurse "^4.1.0"
|
||||
estraverse "^4.1.1"
|
||||
|
||||
eslint@4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint/-/eslint-4.1.0.tgz#bbb55a28220ee08b69da9554d45a6b2ebfd7d913"
|
||||
integrity sha1-u7VaKCIO4Itp2pVU1FprLr/X2RM=
|
||||
dependencies:
|
||||
babel-code-frame "^6.22.0"
|
||||
chalk "^1.1.3"
|
||||
concat-stream "^1.6.0"
|
||||
debug "^2.6.8"
|
||||
doctrine "^2.0.0"
|
||||
eslint-scope "^3.7.1"
|
||||
espree "^3.4.3"
|
||||
esquery "^1.0.0"
|
||||
estraverse "^4.2.0"
|
||||
esutils "^2.0.2"
|
||||
file-entry-cache "^2.0.0"
|
||||
glob "^7.1.2"
|
||||
globals "^9.17.0"
|
||||
ignore "^3.3.3"
|
||||
imurmurhash "^0.1.4"
|
||||
inquirer "^3.0.6"
|
||||
is-my-json-valid "^2.16.0"
|
||||
is-resolvable "^1.0.0"
|
||||
js-yaml "^3.8.4"
|
||||
json-stable-stringify "^1.0.1"
|
||||
levn "^0.3.0"
|
||||
lodash "^4.17.4"
|
||||
minimatch "^3.0.2"
|
||||
mkdirp "^0.5.1"
|
||||
natural-compare "^1.4.0"
|
||||
optionator "^0.8.2"
|
||||
path-is-inside "^1.0.2"
|
||||
pluralize "^4.0.0"
|
||||
progress "^2.0.0"
|
||||
require-uncached "^1.0.3"
|
||||
strip-json-comments "~2.0.1"
|
||||
table "^4.0.1"
|
||||
text-table "~0.2.0"
|
||||
|
||||
espree@^3.4.3:
|
||||
version "3.5.4"
|
||||
resolved "https://registry.yarnpkg.com/espree/-/espree-3.5.4.tgz#b0f447187c8a8bed944b815a660bddf5deb5d1a7"
|
||||
integrity sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==
|
||||
dependencies:
|
||||
acorn "^5.5.0"
|
||||
acorn-jsx "^3.0.0"
|
||||
|
||||
esprima@^4.0.0:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
|
||||
integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==
|
||||
|
||||
esquery@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.0.1.tgz#406c51658b1f5991a5f9b62b1dc25b00e3e5c708"
|
||||
integrity sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==
|
||||
dependencies:
|
||||
estraverse "^4.0.0"
|
||||
|
||||
esrecurse@^4.1.0:
|
||||
version "4.2.1"
|
||||
resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.2.1.tgz#007a3b9fdbc2b3bb87e4879ea19c92fdbd3942cf"
|
||||
integrity sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==
|
||||
dependencies:
|
||||
estraverse "^4.1.0"
|
||||
|
||||
estraverse@^4.0.0, estraverse@^4.1.0, estraverse@^4.1.1, estraverse@^4.2.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13"
|
||||
integrity sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=
|
||||
|
||||
esutils@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b"
|
||||
integrity sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=
|
||||
|
||||
external-editor@^2.0.4:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-2.2.0.tgz#045511cfd8d133f3846673d1047c154e214ad3d5"
|
||||
integrity sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==
|
||||
dependencies:
|
||||
chardet "^0.4.0"
|
||||
iconv-lite "^0.4.17"
|
||||
tmp "^0.0.33"
|
||||
|
||||
fast-deep-equal@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49"
|
||||
integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=
|
||||
|
||||
fast-json-stable-stringify@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2"
|
||||
integrity sha1-1RQsDK7msRifh9OnYREGT4bIu/I=
|
||||
|
||||
fast-levenshtein@~2.0.4:
|
||||
version "2.0.6"
|
||||
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
|
||||
integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=
|
||||
|
||||
figures@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962"
|
||||
integrity sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=
|
||||
dependencies:
|
||||
escape-string-regexp "^1.0.5"
|
||||
|
||||
file-entry-cache@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-2.0.0.tgz#c392990c3e684783d838b8c84a45d8a048458361"
|
||||
integrity sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=
|
||||
dependencies:
|
||||
flat-cache "^1.2.1"
|
||||
object-assign "^4.0.1"
|
||||
|
||||
flat-cache@^1.2.1:
|
||||
version "1.3.4"
|
||||
resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-1.3.4.tgz#2c2ef77525cc2929007dfffa1dd314aa9c9dee6f"
|
||||
integrity sha512-VwyB3Lkgacfik2vhqR4uv2rvebqmDvFu4jlN/C1RzWoJEo8I7z4Q404oiqYCkq41mni8EzQnm95emU9seckwtg==
|
||||
dependencies:
|
||||
circular-json "^0.3.1"
|
||||
graceful-fs "^4.1.2"
|
||||
rimraf "~2.6.2"
|
||||
write "^0.2.1"
|
||||
|
||||
fs.realpath@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
|
||||
integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
|
||||
|
||||
generate-function@^2.0.0:
|
||||
version "2.3.1"
|
||||
resolved "https://registry.yarnpkg.com/generate-function/-/generate-function-2.3.1.tgz#f069617690c10c868e73b8465746764f97c3479f"
|
||||
integrity sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==
|
||||
dependencies:
|
||||
is-property "^1.0.2"
|
||||
|
||||
generate-object-property@^1.1.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/generate-object-property/-/generate-object-property-1.2.0.tgz#9c0e1c40308ce804f4783618b937fa88f99d50d0"
|
||||
integrity sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=
|
||||
dependencies:
|
||||
is-property "^1.0.0"
|
||||
|
||||
glob@^7.1.2, glob@^7.1.3:
|
||||
version "7.1.3"
|
||||
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1"
|
||||
integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==
|
||||
dependencies:
|
||||
fs.realpath "^1.0.0"
|
||||
inflight "^1.0.4"
|
||||
inherits "2"
|
||||
minimatch "^3.0.4"
|
||||
once "^1.3.0"
|
||||
path-is-absolute "^1.0.0"
|
||||
|
||||
globals@^9.17.0:
|
||||
version "9.18.0"
|
||||
resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a"
|
||||
integrity sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==
|
||||
|
||||
graceful-fs@^4.1.2:
|
||||
version "4.1.15"
|
||||
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00"
|
||||
integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==
|
||||
|
||||
has-ansi@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91"
|
||||
integrity sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=
|
||||
dependencies:
|
||||
ansi-regex "^2.0.0"
|
||||
|
||||
has-flag@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
|
||||
integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0=
|
||||
|
||||
iconv-lite@^0.4.17:
|
||||
version "0.4.24"
|
||||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
|
||||
integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
|
||||
dependencies:
|
||||
safer-buffer ">= 2.1.2 < 3"
|
||||
|
||||
ignore@^3.3.3:
|
||||
version "3.3.10"
|
||||
resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.10.tgz#0a97fb876986e8081c631160f8f9f389157f0043"
|
||||
integrity sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==
|
||||
|
||||
imurmurhash@^0.1.4:
|
||||
version "0.1.4"
|
||||
resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
|
||||
integrity sha1-khi5srkoojixPcT7a21XbyMUU+o=
|
||||
|
||||
inflight@^1.0.4:
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
|
||||
integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=
|
||||
dependencies:
|
||||
once "^1.3.0"
|
||||
wrappy "1"
|
||||
|
||||
inherits@2, inherits@^2.0.3, inherits@~2.0.3:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
|
||||
integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
|
||||
|
||||
inquirer@^3.0.6:
|
||||
version "3.3.0"
|
||||
resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-3.3.0.tgz#9dd2f2ad765dcab1ff0443b491442a20ba227dc9"
|
||||
integrity sha512-h+xtnyk4EwKvFWHrUYsWErEVR+igKtLdchu+o0Z1RL7VU/jVMFbYir2bp6bAj8efFNxWqHX0dIss6fJQ+/+qeQ==
|
||||
dependencies:
|
||||
ansi-escapes "^3.0.0"
|
||||
chalk "^2.0.0"
|
||||
cli-cursor "^2.1.0"
|
||||
cli-width "^2.0.0"
|
||||
external-editor "^2.0.4"
|
||||
figures "^2.0.0"
|
||||
lodash "^4.3.0"
|
||||
mute-stream "0.0.7"
|
||||
run-async "^2.2.0"
|
||||
rx-lite "^4.0.8"
|
||||
rx-lite-aggregates "^4.0.8"
|
||||
string-width "^2.1.0"
|
||||
strip-ansi "^4.0.0"
|
||||
through "^2.3.6"
|
||||
|
||||
is-fullwidth-code-point@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f"
|
||||
integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=
|
||||
|
||||
is-my-ip-valid@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz#7b351b8e8edd4d3995d4d066680e664d94696824"
|
||||
integrity sha512-gmh/eWXROncUzRnIa1Ubrt5b8ep/MGSnfAUI3aRp+sqTCs1tv1Isl8d8F6JmkN3dXKc3ehZMrtiPN9eL03NuaQ==
|
||||
|
||||
is-my-json-valid@^2.16.0:
|
||||
version "2.19.0"
|
||||
resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.19.0.tgz#8fd6e40363cd06b963fa877d444bfb5eddc62175"
|
||||
integrity sha512-mG0f/unGX1HZ5ep4uhRaPOS8EkAY8/j6mDRMJrutq4CqhoJWYp7qAlonIPy3TV7p3ju4TK9fo/PbnoksWmsp5Q==
|
||||
dependencies:
|
||||
generate-function "^2.0.0"
|
||||
generate-object-property "^1.1.0"
|
||||
is-my-ip-valid "^1.0.0"
|
||||
jsonpointer "^4.0.0"
|
||||
xtend "^4.0.0"
|
||||
|
||||
is-promise@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa"
|
||||
integrity sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=
|
||||
|
||||
is-property@^1.0.0, is-property@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84"
|
||||
integrity sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=
|
||||
|
||||
is-resolvable@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88"
|
||||
integrity sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==
|
||||
|
||||
isarray@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
|
||||
integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
|
||||
|
||||
js-tokens@^3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"
|
||||
integrity sha1-mGbfOVECEw449/mWvOtlRDIJwls=
|
||||
|
||||
js-yaml@^3.8.4:
|
||||
version "3.12.1"
|
||||
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.12.1.tgz#295c8632a18a23e054cf5c9d3cecafe678167600"
|
||||
integrity sha512-um46hB9wNOKlwkHgiuyEVAybXBjwFUV0Z/RaHJblRd9DXltue9FTYvzCr9ErQrK9Adz5MU4gHWVaNUfdmrC8qA==
|
||||
dependencies:
|
||||
argparse "^1.0.7"
|
||||
esprima "^4.0.0"
|
||||
|
||||
json-schema-traverse@^0.4.1:
|
||||
version "0.4.1"
|
||||
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
|
||||
integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
|
||||
|
||||
json-stable-stringify@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af"
|
||||
integrity sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=
|
||||
dependencies:
|
||||
jsonify "~0.0.0"
|
||||
|
||||
jsonify@~0.0.0:
|
||||
version "0.0.0"
|
||||
resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73"
|
||||
integrity sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=
|
||||
|
||||
jsonpointer@^4.0.0:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9"
|
||||
integrity sha1-T9kss04OnbPInIYi7PUfm5eMbLk=
|
||||
|
||||
levn@^0.3.0, levn@~0.3.0:
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee"
|
||||
integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=
|
||||
dependencies:
|
||||
prelude-ls "~1.1.2"
|
||||
type-check "~0.3.2"
|
||||
|
||||
lodash@^4.17.4, lodash@^4.3.0:
|
||||
version "4.17.11"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d"
|
||||
integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==
|
||||
|
||||
mimic-fn@^1.0.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022"
|
||||
integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==
|
||||
|
||||
minimatch@^3.0.2, minimatch@^3.0.4:
|
||||
version "3.0.4"
|
||||
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
|
||||
integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
|
||||
dependencies:
|
||||
brace-expansion "^1.1.7"
|
||||
|
||||
minimist@0.0.8:
|
||||
version "0.0.8"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
|
||||
integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=
|
||||
|
||||
mkdirp@^0.5.1:
|
||||
version "0.5.1"
|
||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
|
||||
integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=
|
||||
dependencies:
|
||||
minimist "0.0.8"
|
||||
|
||||
ms@2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
||||
integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
|
||||
|
||||
mute-stream@0.0.7:
|
||||
version "0.0.7"
|
||||
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab"
|
||||
integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=
|
||||
|
||||
natural-compare@^1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
|
||||
integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=
|
||||
|
||||
object-assign@^4.0.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
|
||||
integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
|
||||
|
||||
once@^1.3.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
|
||||
integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
|
||||
dependencies:
|
||||
wrappy "1"
|
||||
|
||||
onetime@^2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4"
|
||||
integrity sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=
|
||||
dependencies:
|
||||
mimic-fn "^1.0.0"
|
||||
|
||||
optionator@^0.8.2:
|
||||
version "0.8.2"
|
||||
resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64"
|
||||
integrity sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=
|
||||
dependencies:
|
||||
deep-is "~0.1.3"
|
||||
fast-levenshtein "~2.0.4"
|
||||
levn "~0.3.0"
|
||||
prelude-ls "~1.1.2"
|
||||
type-check "~0.3.2"
|
||||
wordwrap "~1.0.0"
|
||||
|
||||
os-tmpdir@~1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
|
||||
integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=
|
||||
|
||||
path-is-absolute@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
|
||||
integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
|
||||
|
||||
path-is-inside@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53"
|
||||
integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=
|
||||
|
||||
pluralize@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-4.0.0.tgz#59b708c1c0190a2f692f1c7618c446b052fd1762"
|
||||
integrity sha1-WbcIwcAZCi9pLxx2GMRGsFL9F2I=
|
||||
|
||||
prelude-ls@~1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
|
||||
integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=
|
||||
|
||||
process-nextick-args@~2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa"
|
||||
integrity sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==
|
||||
|
||||
progress@^2.0.0:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
|
||||
integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==
|
||||
|
||||
punycode@^2.1.0:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
|
||||
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
|
||||
|
||||
readable-stream@^2.2.2:
|
||||
version "2.3.6"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf"
|
||||
integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==
|
||||
dependencies:
|
||||
core-util-is "~1.0.0"
|
||||
inherits "~2.0.3"
|
||||
isarray "~1.0.0"
|
||||
process-nextick-args "~2.0.0"
|
||||
safe-buffer "~5.1.1"
|
||||
string_decoder "~1.1.1"
|
||||
util-deprecate "~1.0.1"
|
||||
|
||||
require-uncached@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/require-uncached/-/require-uncached-1.0.3.tgz#4e0d56d6c9662fd31e43011c4b95aa49955421d3"
|
||||
integrity sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=
|
||||
dependencies:
|
||||
caller-path "^0.1.0"
|
||||
resolve-from "^1.0.0"
|
||||
|
||||
resolve-from@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-1.0.1.tgz#26cbfe935d1aeeeabb29bc3fe5aeb01e93d44226"
|
||||
integrity sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=
|
||||
|
||||
restore-cursor@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf"
|
||||
integrity sha1-n37ih/gv0ybU/RYpI9YhKe7g368=
|
||||
dependencies:
|
||||
onetime "^2.0.0"
|
||||
signal-exit "^3.0.2"
|
||||
|
||||
rimraf@~2.6.2:
|
||||
version "2.6.3"
|
||||
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab"
|
||||
integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==
|
||||
dependencies:
|
||||
glob "^7.1.3"
|
||||
|
||||
run-async@^2.2.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0"
|
||||
integrity sha1-A3GrSuC91yDUFm19/aZP96RFpsA=
|
||||
dependencies:
|
||||
is-promise "^2.1.0"
|
||||
|
||||
rx-lite-aggregates@^4.0.8:
|
||||
version "4.0.8"
|
||||
resolved "https://registry.yarnpkg.com/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz#753b87a89a11c95467c4ac1626c4efc4e05c67be"
|
||||
integrity sha1-dTuHqJoRyVRnxKwWJsTvxOBcZ74=
|
||||
dependencies:
|
||||
rx-lite "*"
|
||||
|
||||
rx-lite@*, rx-lite@^4.0.8:
|
||||
version "4.0.8"
|
||||
resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-4.0.8.tgz#0b1e11af8bc44836f04a6407e92da42467b79444"
|
||||
integrity sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ=
|
||||
|
||||
safe-buffer@~5.1.0, safe-buffer@~5.1.1:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
|
||||
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
|
||||
|
||||
"safer-buffer@>= 2.1.2 < 3":
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
|
||||
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
|
||||
|
||||
signal-exit@^3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"
|
||||
integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=
|
||||
|
||||
slice-ansi@1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-1.0.0.tgz#044f1a49d8842ff307aad6b505ed178bd950134d"
|
||||
integrity sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg==
|
||||
dependencies:
|
||||
is-fullwidth-code-point "^2.0.0"
|
||||
|
||||
sprintf-js@~1.0.2:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
|
||||
integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=
|
||||
|
||||
string-width@^2.1.0, string-width@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e"
|
||||
integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==
|
||||
dependencies:
|
||||
is-fullwidth-code-point "^2.0.0"
|
||||
strip-ansi "^4.0.0"
|
||||
|
||||
string_decoder@~1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
|
||||
integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==
|
||||
dependencies:
|
||||
safe-buffer "~5.1.0"
|
||||
|
||||
strip-ansi@^3.0.0:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf"
|
||||
integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=
|
||||
dependencies:
|
||||
ansi-regex "^2.0.0"
|
||||
|
||||
strip-ansi@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f"
|
||||
integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8=
|
||||
dependencies:
|
||||
ansi-regex "^3.0.0"
|
||||
|
||||
strip-json-comments@~2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
|
||||
integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo=
|
||||
|
||||
supports-color@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
|
||||
integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=
|
||||
|
||||
supports-color@^5.3.0:
|
||||
version "5.5.0"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
|
||||
integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
|
||||
dependencies:
|
||||
has-flag "^3.0.0"
|
||||
|
||||
table@^4.0.1:
|
||||
version "4.0.3"
|
||||
resolved "https://registry.yarnpkg.com/table/-/table-4.0.3.tgz#00b5e2b602f1794b9acaf9ca908a76386a7813bc"
|
||||
integrity sha512-S7rnFITmBH1EnyKcvxBh1LjYeQMmnZtCXSEbHcH6S0NoKit24ZuFO/T1vDcLdYsLQkM188PVVhQmzKIuThNkKg==
|
||||
dependencies:
|
||||
ajv "^6.0.1"
|
||||
ajv-keywords "^3.0.0"
|
||||
chalk "^2.1.0"
|
||||
lodash "^4.17.4"
|
||||
slice-ansi "1.0.0"
|
||||
string-width "^2.1.1"
|
||||
|
||||
text-table@~0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
|
||||
integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=
|
||||
|
||||
through@^2.3.6:
|
||||
version "2.3.8"
|
||||
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
|
||||
integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=
|
||||
|
||||
tmp@^0.0.33:
|
||||
version "0.0.33"
|
||||
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
|
||||
integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==
|
||||
dependencies:
|
||||
os-tmpdir "~1.0.2"
|
||||
|
||||
type-check@~0.3.2:
|
||||
version "0.3.2"
|
||||
resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72"
|
||||
integrity sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=
|
||||
dependencies:
|
||||
prelude-ls "~1.1.2"
|
||||
|
||||
typedarray@^0.0.6:
|
||||
version "0.0.6"
|
||||
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
|
||||
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
|
||||
|
||||
uri-js@^4.2.2:
|
||||
version "4.2.2"
|
||||
resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0"
|
||||
integrity sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==
|
||||
dependencies:
|
||||
punycode "^2.1.0"
|
||||
|
||||
util-deprecate@~1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
|
||||
integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
|
||||
|
||||
wordwrap@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb"
|
||||
integrity sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=
|
||||
|
||||
wrappy@1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
|
||||
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
|
||||
|
||||
write@^0.2.1:
|
||||
version "0.2.1"
|
||||
resolved "https://registry.yarnpkg.com/write/-/write-0.2.1.tgz#5fc03828e264cea3fe91455476f7a3c566cb0757"
|
||||
integrity sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=
|
||||
dependencies:
|
||||
mkdirp "^0.5.1"
|
||||
|
||||
xtend@^4.0.0:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"
|
||||
integrity sha1-pcbVMr5lbiPbgg77lDofBJmNY68=
|
||||
@@ -9,7 +9,7 @@
|
||||
},
|
||||
"scripts": {
|
||||
"prestart":
|
||||
"cp ../../build/dist/react.development.js public/ && cp ../../build/dist/react-dom.development.js public/",
|
||||
"cp ../../build/node_modules/react/umd/react.development.js public/ && cp ../../build/node_modules/react-dom/umd/react-dom.development.js public/",
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test --env=jsdom",
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
If you checked out the source from GitHub make sure to run <code>npm run build</code>.
|
||||
</p>
|
||||
</div>
|
||||
<script src="../../build/dist/react.development.js"></script>
|
||||
<script src="../../build/dist/react-dom.development.js"></script>
|
||||
<script src="../../build/node_modules/react/umd/react.development.js"></script>
|
||||
<script src="../../build/node_modules/react-dom/umd/react-dom.development.js"></script>
|
||||
<script src="https://unpkg.com/babel-standalone@6/babel.js"></script>
|
||||
<script type="text/babel">
|
||||
var dotStyle = {
|
||||
|
||||
40
fixtures/fizz-ssr-browser/index.html
Normal file
40
fixtures/fizz-ssr-browser/index.html
Normal file
@@ -0,0 +1,40 @@
|
||||
<!DOCTYPE html>
|
||||
<html style="width: 100%; height: 100%; overflow: hidden">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Fizz Example</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Fizz Example</h1>
|
||||
<div id="container">
|
||||
<p>
|
||||
To install React, follow the instructions on
|
||||
<a href="https://github.com/facebook/react/">GitHub</a>.
|
||||
</p>
|
||||
<p>
|
||||
If you can see this, React is <strong>not</strong> working right.
|
||||
If you checked out the source from GitHub make sure to run <code>npm run build</code>.
|
||||
</p>
|
||||
</div>
|
||||
<script src="../../build/dist/react.development.js"></script>
|
||||
<script src="../../build/dist/react-dom-unstable-fizz.browser.development.js"></script>
|
||||
<script src="https://unpkg.com/babel-standalone@6/babel.js"></script>
|
||||
<script type="text/babel">
|
||||
let stream = ReactDOMFizzServer.renderToReadableStream(<body>Success</body>);
|
||||
let response = new Response(stream, {
|
||||
headers: {'Content-Type': 'text/html'},
|
||||
});
|
||||
display(response);
|
||||
|
||||
async function display(responseToDisplay) {
|
||||
let blob = await responseToDisplay.blob();
|
||||
let url = URL.createObjectURL(blob);
|
||||
let iframe = document.createElement('iframe');
|
||||
iframe.src = url;
|
||||
let container = document.getElementById('container');
|
||||
container.innerHTML = '';
|
||||
container.appendChild(iframe);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -15,28 +15,18 @@ First, build React and the fixtures:
|
||||
```
|
||||
cd react
|
||||
npm run build
|
||||
|
||||
cd fixtures/packaging
|
||||
node build-all.js
|
||||
node fixtures/packaging/build-all.js
|
||||
```
|
||||
|
||||
Then run a local server at the root of the repo, e.g.
|
||||
Then run a local server, e.g.
|
||||
|
||||
```
|
||||
npm i -g pushstate-server
|
||||
cd ../..
|
||||
pushstate-server .
|
||||
npx pushstate-server .
|
||||
```
|
||||
|
||||
(Too complicated? Send a PR to simplify this :-)).
|
||||
|
||||
Then open the following URL in your browser:
|
||||
|
||||
```
|
||||
open http://localhost:9000/fixtures/packaging/index.html
|
||||
```
|
||||
and open the following URL in your browser: [http://localhost:9000/fixtures/packaging/index.html](http://localhost:9000/fixtures/packaging/index.html)
|
||||
|
||||
You should see two things:
|
||||
|
||||
* "Hello World" is rendered in each iframe.
|
||||
* A number of iframes (corresponding to various builds), with "Hello World" rendered in each iframe.
|
||||
* No errors in the console.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<html>
|
||||
<body>
|
||||
<script src="../../../build/dist/react.development.js"></script>
|
||||
<script src="../../../build/dist/react-dom.development.js"></script>
|
||||
<script src="../../../build/node_modules/react/umd/react.development.js"></script>
|
||||
<script src="../../../build/node_modules/react-dom/umd/react-dom.development.js"></script>
|
||||
<script src="https://unpkg.com/babel-standalone@6/babel.js"></script>
|
||||
<div id="container"></div>
|
||||
<script type="text/babel">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<html>
|
||||
<body>
|
||||
<script src="../../../build/dist/react.development.js"></script>
|
||||
<script src="../../../build/dist/react-dom.development.js"></script>
|
||||
<script src="../../../build/node_modules/react/umd/react.development.js"></script>
|
||||
<script src="../../../build/node_modules/react-dom/umd/react-dom.development.js"></script>
|
||||
<div id="container"></div>
|
||||
<script>
|
||||
ReactDOM.render(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<html>
|
||||
<body>
|
||||
<script src="../../../build/dist/react.production.min.js"></script>
|
||||
<script src="../../../build/dist/react-dom.production.min.js"></script>
|
||||
<script src="../../../build/node_modules/react/umd/react.production.min.js"></script>
|
||||
<script src="../../../build/node_modules/react-dom/umd/react-dom.production.min.js"></script>
|
||||
<div id="container"></div>
|
||||
<script>
|
||||
ReactDOM.render(
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
<script>
|
||||
requirejs.config({
|
||||
paths: {
|
||||
react: '../../../build/dist/react.development',
|
||||
'react-dom': '../../../build/dist/react-dom.development'
|
||||
react: '../../../build/node_modules/react/umd/react.development',
|
||||
'react-dom': '../../../build/node_modules/react-dom/umd/react-dom.development'
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
<script>
|
||||
requirejs.config({
|
||||
paths: {
|
||||
react: '../../../build/dist/react.production.min',
|
||||
'react-dom': '../../../build/dist/react-dom.production.min'
|
||||
react: '../../../build/node_modules/react/umd/react.production.min',
|
||||
'react-dom': '../../../build/node_modules/react-dom/umd/react-dom.production.min'
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -4,8 +4,9 @@ module.exports = {
|
||||
out: 'output.js',
|
||||
optimize: 'none',
|
||||
paths: {
|
||||
react: '../../../../build/dist/react.development',
|
||||
'react-dom': '../../../../build/dist/react-dom.development',
|
||||
react: '../../../../build/node_modules/react/umd/react.development',
|
||||
'react-dom':
|
||||
'../../../../build/node_modules/react-dom/umd/react-dom.development',
|
||||
schedule: '../../../../build/dist/schedule.development',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -4,8 +4,9 @@ module.exports = {
|
||||
out: 'output.js',
|
||||
optimize: 'none',
|
||||
paths: {
|
||||
react: '../../../../build/dist/react.production.min',
|
||||
'react-dom': '../../../../build/dist/react-dom.production.min',
|
||||
react: '../../../../build/node_modules/react/umd/react.production.min',
|
||||
'react-dom':
|
||||
'../../../../build/node_modules/react-dom/umd/react-dom.production.min',
|
||||
schedule: '../../../../build/dist/schedule.development',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
System.config({
|
||||
paths: {
|
||||
react: '../../../../build/dist/react.development.js',
|
||||
'react-dom': '../../../../build/dist/react-dom.development.js',
|
||||
react: '../../../../build/node_modules/react/umd/react.development.js',
|
||||
'react-dom':
|
||||
'../../../../build/node_modules/react-dom/umd/react-dom.development.js',
|
||||
schedule: '../../../../build/dist/schedule.development',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
System.config({
|
||||
paths: {
|
||||
react: '../../../../build/dist/react.production.min.js',
|
||||
'react-dom': '../../../../build/dist/react-dom.production.min.js',
|
||||
react: '../../../../build/node_modules/react/umd/react.production.min.js',
|
||||
'react-dom':
|
||||
'../../../../build/node_modules/react-dom/umd/react-dom.production.min.js',
|
||||
schedule: '../../../../build/dist/schedule.development',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
<script>
|
||||
System.config({
|
||||
paths: {
|
||||
react: '../../../build/dist/react.development.js',
|
||||
'react-dom': '../../../build/dist/react-dom.development.js'
|
||||
react: '../../../build/node_modules/react/umd/react.development.js',
|
||||
'react-dom': '../../../build/node_modules/react-dom/umd/react-dom.development.js'
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
<script>
|
||||
System.config({
|
||||
paths: {
|
||||
react: '../../../build/dist/react.production.min.js',
|
||||
'react-dom': '../../../build/dist/react-dom.production.min.js'
|
||||
react: '../../../build/node_modules/react/umd/react.production.min.js',
|
||||
'react-dom': '../../../build/node_modules/react-dom/umd/react-dom.production.min.js'
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -90,15 +90,33 @@
|
||||
</div>
|
||||
<div> If the counter advanced while you were away from this tab, it's correct.</div>
|
||||
</li>
|
||||
<li>
|
||||
<p>Can pause execution, dump scheduled callbacks, and continue where it left off</p>
|
||||
<button onClick="runTestEight()">Run Test 8</button>
|
||||
<div><b>Click the button above, press "continue" to finish the test after it pauses:</b></div>
|
||||
<button onClick="continueTestEight()">continue</button>
|
||||
<div><b>Expected:</b></div>
|
||||
<div id="test-8-expected">
|
||||
</div>
|
||||
<div> -------------------------------------------------</div>
|
||||
<div> If the test didn't progress until you hit "continue" and </div>
|
||||
<div> you see the same above and below afterwards it's correct.
|
||||
<div> -------------------------------------------------</div>
|
||||
<div><b>Actual:</b></div>
|
||||
<div id="test-8"></div>
|
||||
</li>
|
||||
</ol>
|
||||
<script src="../../build/dist/react.development.js"></script>
|
||||
<script src="../../build/node_modules/react/umd/react.development.js"></script>
|
||||
<script src="../../build/node_modules/scheduler/umd/scheduler.development.js"></script>
|
||||
<script src="https://unpkg.com/babel-standalone@6/babel.js"></script>
|
||||
<script type="text/babel">
|
||||
const {
|
||||
unstable_scheduleCallback: scheduleCallback,
|
||||
unstable_cancelCallback: cancelCallback,
|
||||
unstable_now: now
|
||||
unstable_now: now,
|
||||
unstable_getFirstCallbackNode: getFirstCallbackNode,
|
||||
unstable_pauseExecution: pauseExecution,
|
||||
unstable_continueExecution: continueExecution,
|
||||
} = Scheduler;
|
||||
function displayTestResult(testNumber) {
|
||||
const expectationNode = document.getElementById('test-' + testNumber + '-expected');
|
||||
@@ -215,6 +233,16 @@ const expectedResults = [
|
||||
[
|
||||
// ... TODO
|
||||
],
|
||||
[],
|
||||
[],
|
||||
// Test 8
|
||||
[
|
||||
'Queue size: 0.',
|
||||
'Pausing... press continue to resume.',
|
||||
'Queue size: 2.',
|
||||
'Finishing...',
|
||||
'Done!',
|
||||
],
|
||||
];
|
||||
function runTestOne() {
|
||||
// Test 1
|
||||
@@ -496,6 +524,51 @@ function runTestSeven() {
|
||||
}
|
||||
scheduleCallback(incrementCounterAndScheduleNextCallback);
|
||||
}
|
||||
|
||||
function runTestEight() {
|
||||
// Test 8
|
||||
// Pauses execution, dumps the queue, and continues execution
|
||||
clearTestResult(8);
|
||||
|
||||
function countNodesInStack(firstCallbackNode) {
|
||||
var node = firstCallbackNode;
|
||||
var count = 0;
|
||||
if (node !== null) {
|
||||
do {
|
||||
count = count + 1;
|
||||
node = node.next;
|
||||
} while (node !== firstCallbackNode);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
scheduleCallback(() => {
|
||||
|
||||
// size should be 0
|
||||
updateTestResult(8, `Queue size: ${countNodesInStack(getFirstCallbackNode())}.`);
|
||||
updateTestResult(8, 'Pausing... press continue to resume.');
|
||||
pauseExecution();
|
||||
|
||||
scheduleCallback(function () {
|
||||
updateTestResult(8, 'Finishing...');
|
||||
displayTestResult(8);
|
||||
})
|
||||
scheduleCallback(function () {
|
||||
updateTestResult(8, 'Done!');
|
||||
displayTestResult(8);
|
||||
checkTestResult(8);
|
||||
})
|
||||
|
||||
// new size should be 2 now
|
||||
updateTestResult(8, `Queue size: ${countNodesInStack(getFirstCallbackNode())}.`);
|
||||
displayTestResult(8);
|
||||
});
|
||||
}
|
||||
|
||||
function continueTestEight() {
|
||||
continueExecution();
|
||||
}
|
||||
|
||||
</script type="text/babel">
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,17 +1,32 @@
|
||||
import React, {Component} from 'react';
|
||||
import React, {useContext, useState, Suspense} from 'react';
|
||||
|
||||
import Chrome from './Chrome';
|
||||
import Page from './Page';
|
||||
import Page2 from './Page2';
|
||||
import Theme from './Theme';
|
||||
|
||||
export default class App extends Component {
|
||||
render() {
|
||||
return (
|
||||
<Chrome title="Hello World" assets={this.props.assets}>
|
||||
<div>
|
||||
<h1>Hello World</h1>
|
||||
<Page />
|
||||
</div>
|
||||
</Chrome>
|
||||
);
|
||||
}
|
||||
function LoadingIndicator() {
|
||||
let theme = useContext(Theme);
|
||||
return <div className={theme + '-loading'}>Loading...</div>;
|
||||
}
|
||||
|
||||
export default function App({assets}) {
|
||||
let [CurrentPage, switchPage] = useState(() => Page);
|
||||
return (
|
||||
<Chrome title="Hello World" assets={assets}>
|
||||
<div>
|
||||
<h1>Hello World</h1>
|
||||
<a className="link" onClick={() => switchPage(() => Page)}>
|
||||
Page 1
|
||||
</a>
|
||||
{' | '}
|
||||
<a className="link" onClick={() => switchPage(() => Page2)}>
|
||||
Page 2
|
||||
</a>
|
||||
<Suspense fallback={<LoadingIndicator />}>
|
||||
<CurrentPage />
|
||||
</Suspense>
|
||||
</div>
|
||||
</Chrome>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,3 +3,27 @@ body {
|
||||
padding: 0;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
body.light {
|
||||
background-color: #FFFFFF;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
body.dark {
|
||||
background-color: #000000;
|
||||
color: #CCCCCC;
|
||||
}
|
||||
|
||||
.light-loading {
|
||||
margin: 10px 0;
|
||||
padding: 10px;
|
||||
background-color: #CCCCCC;
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
.dark-loading {
|
||||
margin: 10px 0;
|
||||
padding: 10px;
|
||||
background-color: #333333;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import React, {Component} from 'react';
|
||||
|
||||
import Theme, {ThemeToggleButton} from './Theme';
|
||||
|
||||
import './Chrome.css';
|
||||
|
||||
export default class Chrome extends Component {
|
||||
state = {theme: 'light'};
|
||||
render() {
|
||||
const assets = this.props.assets;
|
||||
return (
|
||||
@@ -14,13 +17,18 @@ export default class Chrome extends Component {
|
||||
<link rel="stylesheet" href={assets['main.css']} />
|
||||
<title>{this.props.title}</title>
|
||||
</head>
|
||||
<body>
|
||||
<body className={this.state.theme}>
|
||||
<noscript
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `<b>Enable JavaScript to run this app.</b>`,
|
||||
}}
|
||||
/>
|
||||
{this.props.children}
|
||||
<Theme.Provider value={this.state.theme}>
|
||||
{this.props.children}
|
||||
<div>
|
||||
<ThemeToggleButton onChange={theme => this.setState({theme})} />
|
||||
</div>
|
||||
</Theme.Provider>
|
||||
<script
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `assetManifest = ${JSON.stringify(assets)};`,
|
||||
|
||||
@@ -1,3 +1,16 @@
|
||||
.bold {
|
||||
.link {
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
}
|
||||
.light-box {
|
||||
margin: 10px 0;
|
||||
padding: 10px;
|
||||
background-color: #CCCCCC;
|
||||
color: #333333;
|
||||
}
|
||||
.dark-box {
|
||||
margin: 10px 0;
|
||||
padding: 10px;
|
||||
background-color: #333333;
|
||||
color: #CCCCCC;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import React, {Component} from 'react';
|
||||
|
||||
import Theme from './Theme';
|
||||
import Suspend from './Suspend';
|
||||
|
||||
import './Page.css';
|
||||
|
||||
const autofocusedInputs = [
|
||||
@@ -14,17 +17,22 @@ export default class Page extends Component {
|
||||
};
|
||||
render() {
|
||||
const link = (
|
||||
<a className="bold" onClick={this.handleClick}>
|
||||
<a className="link" onClick={this.handleClick}>
|
||||
Click Here
|
||||
</a>
|
||||
);
|
||||
return (
|
||||
<div>
|
||||
<p suppressHydrationWarning={true}>A random number: {Math.random()}</p>
|
||||
<p>Autofocus on page load: {autofocusedInputs}</p>
|
||||
<p>{!this.state.active ? link : 'Thanks!'}</p>
|
||||
{this.state.active && <p>Autofocus on update: {autofocusedInputs}</p>}
|
||||
<div className={this.context + '-box'}>
|
||||
<Suspend>
|
||||
<p suppressHydrationWarning={true}>
|
||||
A random number: {Math.random()}
|
||||
</p>
|
||||
<p>Autofocus on page load: {autofocusedInputs}</p>
|
||||
<p>{!this.state.active ? link : 'Thanks!'}</p>
|
||||
{this.state.active && <p>Autofocus on update: {autofocusedInputs}</p>}
|
||||
</Suspend>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
Page.contextType = Theme;
|
||||
|
||||
15
fixtures/ssr/src/components/Page2.js
Normal file
15
fixtures/ssr/src/components/Page2.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import React, {useContext} from 'react';
|
||||
|
||||
import Theme from './Theme';
|
||||
import Suspend from './Suspend';
|
||||
|
||||
import './Page.css';
|
||||
|
||||
export default function Page2() {
|
||||
let theme = useContext(Theme);
|
||||
return (
|
||||
<div className={theme + '-box'}>
|
||||
<Suspend>Content of a different page</Suspend>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
21
fixtures/ssr/src/components/Suspend.js
Normal file
21
fixtures/ssr/src/components/Suspend.js
Normal file
@@ -0,0 +1,21 @@
|
||||
let promise = null;
|
||||
let isResolved = false;
|
||||
|
||||
export default function Suspend({children}) {
|
||||
// This will suspend the content from rendering but only on the client.
|
||||
// This is used to demo a slow loading app.
|
||||
if (typeof window === 'object') {
|
||||
if (!isResolved) {
|
||||
if (promise === null) {
|
||||
promise = new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
isResolved = true;
|
||||
resolve();
|
||||
}, 6000);
|
||||
});
|
||||
}
|
||||
throw promise;
|
||||
}
|
||||
}
|
||||
return children;
|
||||
}
|
||||
25
fixtures/ssr/src/components/Theme.js
Normal file
25
fixtures/ssr/src/components/Theme.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import React, {createContext, useContext, useState} from 'react';
|
||||
|
||||
const Theme = createContext('light');
|
||||
|
||||
export default Theme;
|
||||
|
||||
export function ThemeToggleButton({onChange}) {
|
||||
let theme = useContext(Theme);
|
||||
let [targetTheme, setTargetTheme] = useState(theme);
|
||||
function toggleTheme() {
|
||||
let newTheme = theme === 'light' ? 'dark' : 'light';
|
||||
// High pri, responsive update.
|
||||
setTargetTheme(newTheme);
|
||||
// Perform the actual theme change in a separate update.
|
||||
setTimeout(() => onChange(newTheme), 0);
|
||||
}
|
||||
if (targetTheme !== theme) {
|
||||
return 'Switching to ' + targetTheme + '...';
|
||||
}
|
||||
return (
|
||||
<a className="link" onClick={toggleTheme}>
|
||||
Switch to {theme === 'light' ? 'Dark' : 'Light'} theme
|
||||
</a>
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import {hydrate} from 'react-dom';
|
||||
import {unstable_createRoot} from 'react-dom';
|
||||
|
||||
import App from './components/App';
|
||||
|
||||
hydrate(<App assets={window.assetManifest} />, document);
|
||||
let root = unstable_createRoot(document, {hydrate: true});
|
||||
root.render(<App assets={window.assetManifest} />);
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
</p>
|
||||
<h3>
|
||||
Tests
|
||||
<button onClick="runAllTests()">Run all tests</button>
|
||||
<button id="run-test-button" onClick="runAllTests()">Run all tests</button>
|
||||
</h3>
|
||||
<ol>
|
||||
<li id="checkSchedulerAPI" data-value="...">
|
||||
@@ -62,6 +62,17 @@
|
||||
<strong>Test end-to-end integration</strong>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<script>
|
||||
if (window.location.search.includes('puppeteer=true')) {
|
||||
// Colocated calls to performance.now() often yield different values in Puppeteer.
|
||||
// This causes the Scheduler API test to fail.
|
||||
// For the purposes of our automated release scripts,
|
||||
// Coerce tests to use Date.now() instead to reduce the chances of a false positive.
|
||||
window.performance = {now: Date.now};
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Load the tracing API before react to test that it's lazily evaluated -->
|
||||
<script src="../../build/node_modules/scheduler/umd/scheduler.development.js"></script>
|
||||
<script src="../../build/node_modules/scheduler/umd/scheduler-tracing.development.js"></script>
|
||||
|
||||
@@ -20,7 +20,7 @@ Follow these steps:
|
||||
# 1: Build react from source
|
||||
cd /path/to/react
|
||||
yarn
|
||||
yarn build dom-client,core,react-cache,scheduler --type=NODE
|
||||
yarn build react-dom/index,react/index,react-cache,scheduler --type=NODE
|
||||
|
||||
# 2: Install fixture dependencies
|
||||
cd fixtures/unstable-async/suspense/
|
||||
|
||||
@@ -18,7 +18,7 @@ There are also known bugs and inefficiencies in master so **don't use this fixtu
|
||||
# 1: Build react from source
|
||||
cd /path/to/react
|
||||
yarn
|
||||
yarn build dom-client,core,react-cache,scheduler --type=NODE
|
||||
yarn build react-dom/index,react/index,react-cache,scheduler --type=NODE
|
||||
|
||||
# 2: Install fixture dependencies
|
||||
cd fixtures/unstable-async/time-slicing/
|
||||
|
||||
14
fixtures/unstable-fire/index.html
Normal file
14
fixtures/unstable-fire/index.html
Normal file
@@ -0,0 +1,14 @@
|
||||
<html>
|
||||
<body>
|
||||
<script src="../../build/node_modules/react/umd/react.development.js"></script>
|
||||
<script src="../../build/node_modules/react-dom/umd/react-dom-unstable-fire.development.js"></script>
|
||||
<script src="https://unpkg.com/babel-standalone@6/babel.js"></script>
|
||||
<div id="container"></div>
|
||||
<script type="text/babel">
|
||||
ReactFire.render(
|
||||
<h1>Hello World!</h1>,
|
||||
document.getElementById('container')
|
||||
);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -58,7 +58,7 @@
|
||||
"flow-bin": "^0.72.0",
|
||||
"glob": "^6.0.4",
|
||||
"glob-stream": "^6.1.0",
|
||||
"google-closure-compiler": "20180506.0.0",
|
||||
"google-closure-compiler": "20190106.0.0",
|
||||
"gzip-size": "^3.0.0",
|
||||
"jasmine-check": "^1.0.0-rc.0",
|
||||
"jest": "^23.1.0",
|
||||
@@ -84,16 +84,17 @@
|
||||
"targz": "^1.0.1",
|
||||
"through2": "^2.0.0",
|
||||
"tmp": "~0.0.28",
|
||||
"typescript": "~1.8.10"
|
||||
"typescript": "~1.8.10",
|
||||
"@mattiasbuelens/web-streams-polyfill": "0.1.0"
|
||||
},
|
||||
"devEngines": {
|
||||
"node": "8.x || 9.x || 10.x"
|
||||
"node": "8.x || 9.x || 10.x || 11.x"
|
||||
},
|
||||
"jest": {
|
||||
"testRegex": "/scripts/jest/dont-run-jest-directly\\.js$"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "npm run version-check && node ./scripts/rollup/build.js",
|
||||
"build": "node ./scripts/rollup/build.js",
|
||||
"linc": "node ./scripts/tasks/linc.js",
|
||||
"lint": "node ./scripts/tasks/eslint.js",
|
||||
"lint-build": "node ./scripts/rollup/validate/index.js",
|
||||
|
||||
@@ -36,7 +36,7 @@ This abstraction can handle a variety of subscription types, including:
|
||||
yarn add create-subscription
|
||||
|
||||
# NPM
|
||||
npm install create-subscription --save
|
||||
npm install create-subscription
|
||||
```
|
||||
|
||||
# Usage
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
{
|
||||
"name": "create-subscription",
|
||||
"description": "utility for subscribing to external data sources inside React components",
|
||||
"version": "16.6.1",
|
||||
"repository": "facebook/react",
|
||||
"version": "16.8.6",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/facebook/react.git",
|
||||
"directory": "packages/create-subscription"
|
||||
},
|
||||
"files": [
|
||||
"LICENSE",
|
||||
"README.md",
|
||||
"build-info.json",
|
||||
"index.js",
|
||||
"cjs/"
|
||||
],
|
||||
|
||||
@@ -148,7 +148,7 @@ describe('createSubscription', () => {
|
||||
|
||||
// Test a promise that resolves before render
|
||||
// Note that this will require an extra render anyway,
|
||||
// Because there is no way to syncrhonously get a Promise's value
|
||||
// Because there is no way to synchronously get a Promise's value
|
||||
rejectB(false);
|
||||
ReactNoop.render(<Subscription source={promiseB}>{render}</Subscription>);
|
||||
expect(ReactNoop.flush()).toEqual(['loading']);
|
||||
@@ -431,7 +431,7 @@ describe('createSubscription', () => {
|
||||
'Parent.componentDidUpdate',
|
||||
]);
|
||||
|
||||
// Updates from the new subsribable should be ignored.
|
||||
// Updates from the new subscribable should be ignored.
|
||||
observableB.next('b-1');
|
||||
expect(ReactNoop.flush()).toEqual([]);
|
||||
expect(log).toEqual([
|
||||
|
||||
@@ -2,26 +2,20 @@
|
||||
|
||||
This ESLint plugin enforces the [Rules of Hooks](https://reactjs.org/docs/hooks-rules.html).
|
||||
|
||||
It is a part of the [Hooks proposal](https://reactjs.org/docs/hooks-intro.html) for React.
|
||||
|
||||
## Experimental Status
|
||||
|
||||
This is an experimental release and is intended to be used for testing the Hooks proposal with React 16.7 alpha. The exact heuristics it uses may be adjusted.
|
||||
|
||||
The [Rules of Hooks](https://reactjs.org/docs/hooks-rules.html) documentation contains a link to the technical RFC. Please leave a comment on the RFC if you have concerns or ideas about how this plugin should work.
|
||||
It is a part of the [Hooks API](https://reactjs.org/docs/hooks-intro.html) for React.
|
||||
|
||||
## Installation
|
||||
|
||||
**Note: If you're using Create React App, please wait for a corresponding experimental release of `react-scripts` that includes this rule instead of adding it directly.**
|
||||
**Note: If you're using Create React App, please wait for a corresponding release of `react-scripts` that includes this rule instead of adding it directly.**
|
||||
|
||||
Assuming you already have ESLint installed, run:
|
||||
|
||||
```sh
|
||||
# npm
|
||||
npm install eslint-plugin-react-hooks@next --save-dev
|
||||
npm install eslint-plugin-react-hooks --save-dev
|
||||
|
||||
# yarn
|
||||
yarn add eslint-plugin-react-hooks@next --dev
|
||||
yarn add eslint-plugin-react-hooks --dev
|
||||
```
|
||||
|
||||
Then add it to your ESLint configuration:
|
||||
|
||||
@@ -270,6 +270,75 @@ eslintTester.run('react-hooks', ReactHooksESLintRule, {
|
||||
useState();
|
||||
}
|
||||
`,
|
||||
`
|
||||
// Valid because the loop doesn't change the order of hooks calls.
|
||||
function RegressionTest() {
|
||||
const res = [];
|
||||
const additionalCond = true;
|
||||
for (let i = 0; i !== 10 && additionalCond; ++i ) {
|
||||
res.push(i);
|
||||
}
|
||||
React.useLayoutEffect(() => {});
|
||||
}
|
||||
`,
|
||||
`
|
||||
// Is valid but hard to compute by brute-forcing
|
||||
function MyComponent() {
|
||||
// 40 conditions
|
||||
if (c) {} else {}
|
||||
if (c) {} else {}
|
||||
if (c) {} else {}
|
||||
if (c) {} else {}
|
||||
if (c) {} else {}
|
||||
if (c) {} else {}
|
||||
if (c) {} else {}
|
||||
if (c) {} else {}
|
||||
if (c) {} else {}
|
||||
if (c) {} else {}
|
||||
if (c) {} else {}
|
||||
if (c) {} else {}
|
||||
if (c) {} else {}
|
||||
if (c) {} else {}
|
||||
if (c) {} else {}
|
||||
if (c) {} else {}
|
||||
if (c) {} else {}
|
||||
if (c) {} else {}
|
||||
if (c) {} else {}
|
||||
if (c) {} else {}
|
||||
if (c) {} else {}
|
||||
if (c) {} else {}
|
||||
if (c) {} else {}
|
||||
if (c) {} else {}
|
||||
if (c) {} else {}
|
||||
if (c) {} else {}
|
||||
if (c) {} else {}
|
||||
if (c) {} else {}
|
||||
if (c) {} else {}
|
||||
if (c) {} else {}
|
||||
if (c) {} else {}
|
||||
if (c) {} else {}
|
||||
if (c) {} else {}
|
||||
if (c) {} else {}
|
||||
if (c) {} else {}
|
||||
if (c) {} else {}
|
||||
if (c) {} else {}
|
||||
if (c) {} else {}
|
||||
if (c) {} else {}
|
||||
if (c) {} else {}
|
||||
|
||||
// 10 hooks
|
||||
useHook();
|
||||
useHook();
|
||||
useHook();
|
||||
useHook();
|
||||
useHook();
|
||||
useHook();
|
||||
useHook();
|
||||
useHook();
|
||||
useHook();
|
||||
useHook();
|
||||
}
|
||||
`,
|
||||
],
|
||||
invalid: [
|
||||
{
|
||||
|
||||
@@ -2,11 +2,16 @@
|
||||
"private": true,
|
||||
"name": "eslint-plugin-react-hooks",
|
||||
"description": "ESLint rules for React Hooks",
|
||||
"version": "0.0.0",
|
||||
"repository": "facebook/react",
|
||||
"version": "1.1.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/facebook/react.git",
|
||||
"directory": "packages/eslint-plugin-react-hooks"
|
||||
},
|
||||
"files": [
|
||||
"LICENSE",
|
||||
"README.md",
|
||||
"build-info.json",
|
||||
"index.js",
|
||||
"cjs"
|
||||
],
|
||||
|
||||
@@ -149,7 +149,14 @@ export default {
|
||||
paths += countPathsFromStart(prevSegment);
|
||||
}
|
||||
}
|
||||
cache.set(segment.id, paths);
|
||||
|
||||
// If our segment is reachable then there should be at least one path
|
||||
// to it from the start of our code path.
|
||||
if (segment.reachable && paths === 0) {
|
||||
cache.delete(segment.id);
|
||||
} else {
|
||||
cache.set(segment.id, paths);
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
@@ -286,7 +293,7 @@ export default {
|
||||
// hook functions.
|
||||
const codePathFunctionName = getFunctionName(codePathNode);
|
||||
|
||||
// This is a valid code path for React hooks if we are direcly in a React
|
||||
// This is a valid code path for React hooks if we are directly in a React
|
||||
// function component or we are in a hook function.
|
||||
const isSomewhereInsideComponentOrHook = isInsideComponentOrHook(
|
||||
codePathNode,
|
||||
@@ -374,13 +381,14 @@ export default {
|
||||
for (const hook of reactHooks) {
|
||||
// Report an error if a hook may be called more then once.
|
||||
if (cycled) {
|
||||
context.report(
|
||||
hook,
|
||||
`React Hook "${context.getSource(hook)}" may be executed ` +
|
||||
context.report({
|
||||
node: hook,
|
||||
message:
|
||||
`React Hook "${context.getSource(hook)}" may be executed ` +
|
||||
'more than once. Possibly because it is called in a loop. ' +
|
||||
'React Hooks must be called in the exact same order in ' +
|
||||
'every component render.',
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// If this is not a valid code path for React hooks then we need to
|
||||
@@ -394,16 +402,15 @@ export default {
|
||||
//
|
||||
// Special case when we think there might be an early return.
|
||||
if (!cycled && pathsFromStartToEnd !== allPathsFromStartToEnd) {
|
||||
context.report(
|
||||
hook,
|
||||
const message =
|
||||
`React Hook "${context.getSource(hook)}" is called ` +
|
||||
'conditionally. React Hooks must be called in the exact ' +
|
||||
'same order in every component render.' +
|
||||
(possiblyHasEarlyReturn
|
||||
? ' Did you accidentally call a React Hook after an' +
|
||||
' early return?'
|
||||
: ''),
|
||||
);
|
||||
'conditionally. React Hooks must be called in the exact ' +
|
||||
'same order in every component render.' +
|
||||
(possiblyHasEarlyReturn
|
||||
? ' Did you accidentally call a React Hook after an' +
|
||||
' early return?'
|
||||
: '');
|
||||
context.report({node: hook, message});
|
||||
}
|
||||
} else if (
|
||||
codePathNode.parent &&
|
||||
@@ -415,16 +422,15 @@ export default {
|
||||
// false positives due to feature flag checks. We're less
|
||||
// sensitive to them in classes because hooks would produce
|
||||
// runtime errors in classes anyway, and because a use*()
|
||||
// call in a class, if it works, is unambigously *not* a hook.
|
||||
// call in a class, if it works, is unambiguously *not* a hook.
|
||||
} else if (codePathFunctionName) {
|
||||
// Custom message if we found an invalid function name.
|
||||
context.report(
|
||||
hook,
|
||||
const message =
|
||||
`React Hook "${context.getSource(hook)}" is called in ` +
|
||||
`function "${context.getSource(codePathFunctionName)}" ` +
|
||||
'which is neither a React function component or a custom ' +
|
||||
'React Hook function.',
|
||||
);
|
||||
`function "${context.getSource(codePathFunctionName)}" ` +
|
||||
'which is neither a React function component or a custom ' +
|
||||
'React Hook function.';
|
||||
context.report({node: hook, message});
|
||||
} else if (codePathNode.type === 'Program') {
|
||||
// For now, ignore if it's in top level scope.
|
||||
// We could warn here but there are false positives related
|
||||
@@ -436,12 +442,11 @@ export default {
|
||||
// enough in the common case that the incorrect message in
|
||||
// uncommon cases doesn't matter.
|
||||
if (isSomewhereInsideComponentOrHook) {
|
||||
context.report(
|
||||
hook,
|
||||
const message =
|
||||
`React Hook "${context.getSource(hook)}" cannot be called ` +
|
||||
'inside a callback. React Hooks must be called in a ' +
|
||||
'React function component or a custom React Hook function.',
|
||||
);
|
||||
'inside a callback. React Hooks must be called in a ' +
|
||||
'React function component or a custom React Hook function.';
|
||||
context.report({node: hook, message});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -471,7 +476,7 @@ export default {
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets tbe static name of a function AST node. For function declarations it is
|
||||
* Gets the static name of a function AST node. For function declarations it is
|
||||
* easy. For anonymous function expressions it is much harder. If you search for
|
||||
* `IsAnonymousFunctionDefinition()` in the ECMAScript spec you'll find places
|
||||
* where JS gives anonymous function expressions names. We roughly detect the
|
||||
|
||||
5
packages/jest-mock-scheduler/README.md
Normal file
5
packages/jest-mock-scheduler/README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# `jest-mock-scheduler`
|
||||
|
||||
Jest matchers and utilities for testing the `scheduler` package.
|
||||
|
||||
This package is experimental. APIs may change between releases.
|
||||
8
packages/jest-mock-scheduler/index.js
Normal file
8
packages/jest-mock-scheduler/index.js
Normal file
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
export * from './src/JestMockScheduler';
|
||||
7
packages/jest-mock-scheduler/npm/index.js
Normal file
7
packages/jest-mock-scheduler/npm/index.js
Normal file
@@ -0,0 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
module.exports = require('./cjs/jest-mock-scheduler.production.min.js');
|
||||
} else {
|
||||
module.exports = require('./cjs/jest-mock-scheduler.development.js');
|
||||
}
|
||||
32
packages/jest-mock-scheduler/package.json
Normal file
32
packages/jest-mock-scheduler/package.json
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"name": "jest-mock-scheduler",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"description": "Jest matchers and utilities for testing the scheduler package.",
|
||||
"main": "index.js",
|
||||
"repository": {
|
||||
"type" : "git",
|
||||
"url" : "https://github.com/facebook/react.git",
|
||||
"directory": "packages/jest-mock-scheduler"
|
||||
},
|
||||
"keywords": [
|
||||
"jest",
|
||||
"scheduler"
|
||||
],
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/facebook/react/issues"
|
||||
},
|
||||
"homepage": "https://reactjs.org/",
|
||||
"peerDependencies": {
|
||||
"jest": "^23.0.1",
|
||||
"scheduler": "^0.11.0"
|
||||
},
|
||||
"files": [
|
||||
"LICENSE",
|
||||
"README.md",
|
||||
"build-info.json",
|
||||
"index.js",
|
||||
"cjs/"
|
||||
]
|
||||
}
|
||||
61
packages/jest-mock-scheduler/src/JestMockScheduler.js
Normal file
61
packages/jest-mock-scheduler/src/JestMockScheduler.js
Normal file
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
// Max 31 bit integer. The max integer size in V8 for 32-bit systems.
|
||||
// Math.pow(2, 30) - 1
|
||||
// 0b111111111111111111111111111111
|
||||
const maxSigned31BitInt = 1073741823;
|
||||
|
||||
export function mockRestore() {
|
||||
delete global._schedMock;
|
||||
}
|
||||
|
||||
let callback = null;
|
||||
let currentTime = -1;
|
||||
|
||||
function flushCallback(didTimeout, ms) {
|
||||
if (callback !== null) {
|
||||
let cb = callback;
|
||||
callback = null;
|
||||
try {
|
||||
currentTime = ms;
|
||||
cb(didTimeout);
|
||||
} finally {
|
||||
currentTime = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function requestHostCallback(cb, ms) {
|
||||
if (currentTime !== -1) {
|
||||
// Protect against re-entrancy.
|
||||
setTimeout(requestHostCallback, 0, cb, ms);
|
||||
} else {
|
||||
callback = cb;
|
||||
setTimeout(flushCallback, ms, true, ms);
|
||||
setTimeout(flushCallback, maxSigned31BitInt, false, maxSigned31BitInt);
|
||||
}
|
||||
}
|
||||
|
||||
function cancelHostCallback() {
|
||||
callback = null;
|
||||
}
|
||||
|
||||
function shouldYieldToHost() {
|
||||
return false;
|
||||
}
|
||||
|
||||
function getCurrentTime() {
|
||||
return currentTime === -1 ? 0 : currentTime;
|
||||
}
|
||||
|
||||
global._schedMock = [
|
||||
requestHostCallback,
|
||||
cancelHostCallback,
|
||||
shouldYieldToHost,
|
||||
getCurrentTime,
|
||||
];
|
||||
@@ -1,9 +1,13 @@
|
||||
{
|
||||
"name": "jest-react",
|
||||
"version": "0.4.0",
|
||||
"version": "0.6.6",
|
||||
"description": "Jest matchers and utilities for testing React components.",
|
||||
"main": "index.js",
|
||||
"repository": "facebook/react",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/facebook/react.git",
|
||||
"directory": "packages/jest-react"
|
||||
},
|
||||
"keywords": [
|
||||
"react",
|
||||
"jest",
|
||||
@@ -22,6 +26,7 @@
|
||||
"files": [
|
||||
"LICENSE",
|
||||
"README.md",
|
||||
"build-info.json",
|
||||
"index.js",
|
||||
"cjs/"
|
||||
]
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
{
|
||||
"name": "react-art",
|
||||
"description": "React ART is a JavaScript library for drawing vector graphics using React. It provides declarative and reactive bindings to the ART library. Using the same declarative API you can render the output to either Canvas, SVG or VML (IE8).",
|
||||
"version": "16.6.1",
|
||||
"version": "16.8.6",
|
||||
"main": "index.js",
|
||||
"repository": "facebook/react",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/facebook/react.git",
|
||||
"directory": "packages/react-art"
|
||||
},
|
||||
"keywords": [
|
||||
"react",
|
||||
"art",
|
||||
@@ -23,7 +27,7 @@
|
||||
"loose-envify": "^1.1.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"prop-types": "^15.6.2",
|
||||
"scheduler": "^0.11.0"
|
||||
"scheduler": "^0.13.6"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.0.0"
|
||||
@@ -31,6 +35,7 @@
|
||||
"files": [
|
||||
"LICENSE",
|
||||
"README.md",
|
||||
"build-info.json",
|
||||
"index.js",
|
||||
"cjs/",
|
||||
"umd/",
|
||||
|
||||
16
packages/react-art/src/ReactART.js
vendored
16
packages/react-art/src/ReactART.js
vendored
@@ -7,7 +7,11 @@
|
||||
|
||||
import React from 'react';
|
||||
import ReactVersion from 'shared/ReactVersion';
|
||||
import * as ARTRenderer from 'react-reconciler/inline.art';
|
||||
import {
|
||||
createContainer,
|
||||
updateContainer,
|
||||
injectIntoDevTools,
|
||||
} from 'react-reconciler/inline.art';
|
||||
import Transform from 'art/core/transform';
|
||||
import Mode from 'art/modes/current';
|
||||
import FastNoSideEffects from 'art/modes/fast-noSideEffects';
|
||||
@@ -61,8 +65,8 @@ class Surface extends React.Component {
|
||||
|
||||
this._surface = Mode.Surface(+width, +height, this._tagRef);
|
||||
|
||||
this._mountNode = ARTRenderer.createContainer(this._surface);
|
||||
ARTRenderer.updateContainer(this.props.children, this._mountNode, this);
|
||||
this._mountNode = createContainer(this._surface);
|
||||
updateContainer(this.props.children, this._mountNode, this);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
@@ -72,7 +76,7 @@ class Surface extends React.Component {
|
||||
this._surface.resize(+props.width, +props.height);
|
||||
}
|
||||
|
||||
ARTRenderer.updateContainer(this.props.children, this._mountNode, this);
|
||||
updateContainer(this.props.children, this._mountNode, this);
|
||||
|
||||
if (this._surface.render) {
|
||||
this._surface.render();
|
||||
@@ -80,7 +84,7 @@ class Surface extends React.Component {
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
ARTRenderer.updateContainer(null, this._mountNode, this);
|
||||
updateContainer(null, this._mountNode, this);
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -132,7 +136,7 @@ class Text extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
ARTRenderer.injectIntoDevTools({
|
||||
injectIntoDevTools({
|
||||
findFiberByHostInstance: () => null,
|
||||
bundleType: __DEV__ ? 1 : 0,
|
||||
version: ReactVersion,
|
||||
|
||||
6
packages/react-art/src/ReactARTHostConfig.js
vendored
6
packages/react-art/src/ReactARTHostConfig.js
vendored
@@ -5,6 +5,10 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {
|
||||
unstable_scheduleCallback as scheduleDeferredCallback,
|
||||
unstable_cancelCallback as cancelDeferredCallback,
|
||||
} from 'scheduler';
|
||||
export {
|
||||
unstable_now as now,
|
||||
unstable_scheduleCallback as scheduleDeferredCallback,
|
||||
@@ -337,6 +341,8 @@ export function getChildHostContext() {
|
||||
export const scheduleTimeout = setTimeout;
|
||||
export const cancelTimeout = clearTimeout;
|
||||
export const noTimeout = -1;
|
||||
export const schedulePassiveEffects = scheduleDeferredCallback;
|
||||
export const cancelPassiveEffects = cancelDeferredCallback;
|
||||
|
||||
export function shouldSetTextContent(type, props) {
|
||||
return (
|
||||
|
||||
@@ -3,10 +3,15 @@
|
||||
"name": "react-cache",
|
||||
"description": "A basic cache for React applications",
|
||||
"version": "2.0.0-alpha.0",
|
||||
"repository": "facebook/react",
|
||||
"repository": {
|
||||
"type" : "git",
|
||||
"url" : "https://github.com/facebook/react.git",
|
||||
"directory": "packages/react-cache"
|
||||
},
|
||||
"files": [
|
||||
"LICENSE",
|
||||
"README.md",
|
||||
"build-info.json",
|
||||
"index.js",
|
||||
"cjs/",
|
||||
"umd/"
|
||||
|
||||
7
packages/react-cache/src/ReactCache.js
vendored
7
packages/react-cache/src/ReactCache.js
vendored
@@ -46,11 +46,12 @@ const Pending = 0;
|
||||
const Resolved = 1;
|
||||
const Rejected = 2;
|
||||
|
||||
const currentOwner =
|
||||
React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner;
|
||||
const ReactCurrentDispatcher =
|
||||
React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
|
||||
.ReactCurrentDispatcher;
|
||||
|
||||
function readContext(Context, observedBits) {
|
||||
const dispatcher = currentOwner.currentDispatcher;
|
||||
const dispatcher = ReactCurrentDispatcher.current;
|
||||
if (dispatcher === null) {
|
||||
throw new Error(
|
||||
'react-cache: read and preload may only be called from within a ' +
|
||||
|
||||
@@ -24,6 +24,8 @@ describe('ReactCache', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
|
||||
let currentPriorityLevel = 3;
|
||||
|
||||
jest.mock('scheduler', () => {
|
||||
let callbacks = [];
|
||||
return {
|
||||
@@ -38,6 +40,26 @@ describe('ReactCache', () => {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
|
||||
unstable_ImmediatePriority: 1,
|
||||
unstable_UserBlockingPriority: 2,
|
||||
unstable_NormalPriority: 3,
|
||||
unstable_LowPriority: 4,
|
||||
unstable_IdlePriority: 5,
|
||||
|
||||
unstable_runWithPriority(priorityLevel, fn) {
|
||||
const prevPriorityLevel = currentPriorityLevel;
|
||||
currentPriorityLevel = priorityLevel;
|
||||
try {
|
||||
return fn();
|
||||
} finally {
|
||||
currentPriorityLevel = prevPriorityLevel;
|
||||
}
|
||||
},
|
||||
|
||||
unstable_getCurrentPriorityLevel() {
|
||||
return currentPriorityLevel;
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -12,11 +12,16 @@
|
||||
"files": [
|
||||
"LICENSE",
|
||||
"README.md",
|
||||
"build-info.json",
|
||||
"index.js",
|
||||
"cjs/"
|
||||
],
|
||||
"main": "index.js",
|
||||
"repository": "facebook/react",
|
||||
"repository": {
|
||||
"type" : "git",
|
||||
"url" : "https://github.com/facebook/react.git",
|
||||
"directory": "packages/react-debug-tools"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
},
|
||||
|
||||
134
packages/react-debug-tools/src/ReactDebugHooks.js
vendored
134
packages/react-debug-tools/src/ReactDebugHooks.js
vendored
@@ -10,6 +10,7 @@
|
||||
import type {ReactContext, ReactProviderType} from 'shared/ReactTypes';
|
||||
import type {Fiber} from 'react-reconciler/src/ReactFiber';
|
||||
import type {Hook} from 'react-reconciler/src/ReactFiberHooks';
|
||||
import type {Dispatcher as DispatcherType} from 'react-reconciler/src/ReactFiberHooks';
|
||||
|
||||
import ErrorStackParser from 'error-stack-parser';
|
||||
import ReactSharedInternals from 'shared/ReactSharedInternals';
|
||||
@@ -20,7 +21,7 @@ import {
|
||||
ForwardRef,
|
||||
} from 'shared/ReactWorkTags';
|
||||
|
||||
const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
|
||||
type CurrentDispatcherRef = typeof ReactSharedInternals.ReactCurrentDispatcher;
|
||||
|
||||
// Used to track hooks called during a render
|
||||
|
||||
@@ -52,10 +53,10 @@ function getPrimitiveStackCache(): Map<string, Array<any>> {
|
||||
Dispatcher.useState(null);
|
||||
Dispatcher.useReducer((s, a) => s, null);
|
||||
Dispatcher.useRef(null);
|
||||
Dispatcher.useMutationEffect(() => {});
|
||||
Dispatcher.useLayoutEffect(() => {});
|
||||
Dispatcher.useEffect(() => {});
|
||||
Dispatcher.useImperativeMethods(undefined, () => null);
|
||||
Dispatcher.useImperativeHandle(undefined, () => null);
|
||||
Dispatcher.useDebugValue(null);
|
||||
Dispatcher.useCallback(() => {});
|
||||
Dispatcher.useMemo(() => null);
|
||||
} finally {
|
||||
@@ -114,13 +115,18 @@ function useState<S>(
|
||||
return [state, (action: BasicStateAction<S>) => {}];
|
||||
}
|
||||
|
||||
function useReducer<S, A>(
|
||||
function useReducer<S, I, A>(
|
||||
reducer: (S, A) => S,
|
||||
initialState: S,
|
||||
initialAction: A | void | null,
|
||||
initialArg: I,
|
||||
init?: I => S,
|
||||
): [S, Dispatch<A>] {
|
||||
let hook = nextHook();
|
||||
let state = hook !== null ? hook.memoizedState : initialState;
|
||||
let state;
|
||||
if (hook !== null) {
|
||||
state = hook.memoizedState;
|
||||
} else {
|
||||
state = init !== undefined ? init(initialArg) : ((initialArg: any): S);
|
||||
}
|
||||
hookLog.push({
|
||||
primitive: 'Reducer',
|
||||
stackError: new Error(),
|
||||
@@ -140,20 +146,8 @@ function useRef<T>(initialValue: T): {current: T} {
|
||||
return ref;
|
||||
}
|
||||
|
||||
function useMutationEffect(
|
||||
create: () => mixed,
|
||||
inputs: Array<mixed> | void | null,
|
||||
): void {
|
||||
nextHook();
|
||||
hookLog.push({
|
||||
primitive: 'MutationEffect',
|
||||
stackError: new Error(),
|
||||
value: create,
|
||||
});
|
||||
}
|
||||
|
||||
function useLayoutEffect(
|
||||
create: () => mixed,
|
||||
create: () => (() => void) | void,
|
||||
inputs: Array<mixed> | void | null,
|
||||
): void {
|
||||
nextHook();
|
||||
@@ -165,14 +159,14 @@ function useLayoutEffect(
|
||||
}
|
||||
|
||||
function useEffect(
|
||||
create: () => mixed,
|
||||
create: () => (() => void) | void,
|
||||
inputs: Array<mixed> | void | null,
|
||||
): void {
|
||||
nextHook();
|
||||
hookLog.push({primitive: 'Effect', stackError: new Error(), value: create});
|
||||
}
|
||||
|
||||
function useImperativeMethods<T>(
|
||||
function useImperativeHandle<T>(
|
||||
ref: {current: T | null} | ((inst: T | null) => mixed) | null | void,
|
||||
create: () => T,
|
||||
inputs: Array<mixed> | void | null,
|
||||
@@ -187,12 +181,20 @@ function useImperativeMethods<T>(
|
||||
instance = ref.current;
|
||||
}
|
||||
hookLog.push({
|
||||
primitive: 'ImperativeMethods',
|
||||
primitive: 'ImperativeHandle',
|
||||
stackError: new Error(),
|
||||
value: instance,
|
||||
});
|
||||
}
|
||||
|
||||
function useDebugValue(value: any, formatterFn: ?(value: any) => any) {
|
||||
hookLog.push({
|
||||
primitive: 'DebugValue',
|
||||
stackError: new Error(),
|
||||
value: typeof formatterFn === 'function' ? formatterFn(value) : value,
|
||||
});
|
||||
}
|
||||
|
||||
function useCallback<T>(callback: T, inputs: Array<mixed> | void | null): T {
|
||||
let hook = nextHook();
|
||||
hookLog.push({
|
||||
@@ -213,15 +215,15 @@ function useMemo<T>(
|
||||
return value;
|
||||
}
|
||||
|
||||
const Dispatcher = {
|
||||
const Dispatcher: DispatcherType = {
|
||||
readContext,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useImperativeMethods,
|
||||
useImperativeHandle,
|
||||
useDebugValue,
|
||||
useLayoutEffect,
|
||||
useMemo,
|
||||
useMutationEffect,
|
||||
useReducer,
|
||||
useRef,
|
||||
useState,
|
||||
@@ -402,7 +404,7 @@ function buildTree(rootStack, readHookLog): HooksTree {
|
||||
let children = [];
|
||||
levelChildren.push({
|
||||
name: parseCustomHookName(stack[j - 1].functionName),
|
||||
value: undefined, // TODO: Support custom inspectable values.
|
||||
value: undefined,
|
||||
subHooks: children,
|
||||
});
|
||||
stackOfChildren.push(levelChildren);
|
||||
@@ -416,16 +418,61 @@ function buildTree(rootStack, readHookLog): HooksTree {
|
||||
subHooks: [],
|
||||
});
|
||||
}
|
||||
|
||||
// Associate custom hook values (useDebugValue() hook entries) with the correct hooks.
|
||||
processDebugValues(rootChildren, null);
|
||||
|
||||
return rootChildren;
|
||||
}
|
||||
|
||||
// Custom hooks support user-configurable labels (via the special useDebugValue() hook).
|
||||
// That hook adds user-provided values to the hooks tree,
|
||||
// but these values aren't intended to appear alongside of the other hooks.
|
||||
// Instead they should be attributed to their parent custom hook.
|
||||
// This method walks the tree and assigns debug values to their custom hook owners.
|
||||
function processDebugValues(
|
||||
hooksTree: HooksTree,
|
||||
parentHooksNode: HooksNode | null,
|
||||
): void {
|
||||
let debugValueHooksNodes: Array<HooksNode> = [];
|
||||
|
||||
for (let i = 0; i < hooksTree.length; i++) {
|
||||
const hooksNode = hooksTree[i];
|
||||
if (hooksNode.name === 'DebugValue' && hooksNode.subHooks.length === 0) {
|
||||
hooksTree.splice(i, 1);
|
||||
i--;
|
||||
debugValueHooksNodes.push(hooksNode);
|
||||
} else {
|
||||
processDebugValues(hooksNode.subHooks, hooksNode);
|
||||
}
|
||||
}
|
||||
|
||||
// Bubble debug value labels to their custom hook owner.
|
||||
// If there is no parent hook, just ignore them for now.
|
||||
// (We may warn about this in the future.)
|
||||
if (parentHooksNode !== null) {
|
||||
if (debugValueHooksNodes.length === 1) {
|
||||
parentHooksNode.value = debugValueHooksNodes[0].value;
|
||||
} else if (debugValueHooksNodes.length > 1) {
|
||||
parentHooksNode.value = debugValueHooksNodes.map(({value}) => value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function inspectHooks<Props>(
|
||||
renderFunction: Props => React$Node,
|
||||
props: Props,
|
||||
currentDispatcher: ?CurrentDispatcherRef,
|
||||
): HooksTree {
|
||||
let previousDispatcher = ReactCurrentOwner.currentDispatcher;
|
||||
// DevTools will pass the current renderer's injected dispatcher.
|
||||
// Other apps might compile debug hooks as part of their app though.
|
||||
if (currentDispatcher == null) {
|
||||
currentDispatcher = ReactSharedInternals.ReactCurrentDispatcher;
|
||||
}
|
||||
|
||||
let previousDispatcher = currentDispatcher.current;
|
||||
let readHookLog;
|
||||
ReactCurrentOwner.currentDispatcher = Dispatcher;
|
||||
currentDispatcher.current = Dispatcher;
|
||||
let ancestorStackError;
|
||||
try {
|
||||
ancestorStackError = new Error();
|
||||
@@ -433,7 +480,7 @@ export function inspectHooks<Props>(
|
||||
} finally {
|
||||
readHookLog = hookLog;
|
||||
hookLog = [];
|
||||
ReactCurrentOwner.currentDispatcher = previousDispatcher;
|
||||
currentDispatcher.current = previousDispatcher;
|
||||
}
|
||||
let rootStack = ErrorStackParser.parse(ancestorStackError);
|
||||
return buildTree(rootStack, readHookLog);
|
||||
@@ -464,10 +511,11 @@ function inspectHooksOfForwardRef<Props, Ref>(
|
||||
renderFunction: (Props, Ref) => React$Node,
|
||||
props: Props,
|
||||
ref: Ref,
|
||||
currentDispatcher: CurrentDispatcherRef,
|
||||
): HooksTree {
|
||||
let previousDispatcher = ReactCurrentOwner.currentDispatcher;
|
||||
let previousDispatcher = currentDispatcher.current;
|
||||
let readHookLog;
|
||||
ReactCurrentOwner.currentDispatcher = Dispatcher;
|
||||
currentDispatcher.current = Dispatcher;
|
||||
let ancestorStackError;
|
||||
try {
|
||||
ancestorStackError = new Error();
|
||||
@@ -475,7 +523,7 @@ function inspectHooksOfForwardRef<Props, Ref>(
|
||||
} finally {
|
||||
readHookLog = hookLog;
|
||||
hookLog = [];
|
||||
ReactCurrentOwner.currentDispatcher = previousDispatcher;
|
||||
currentDispatcher.current = previousDispatcher;
|
||||
}
|
||||
let rootStack = ErrorStackParser.parse(ancestorStackError);
|
||||
return buildTree(rootStack, readHookLog);
|
||||
@@ -496,7 +544,16 @@ function resolveDefaultProps(Component, baseProps) {
|
||||
return baseProps;
|
||||
}
|
||||
|
||||
export function inspectHooksOfFiber(fiber: Fiber) {
|
||||
export function inspectHooksOfFiber(
|
||||
fiber: Fiber,
|
||||
currentDispatcher: ?CurrentDispatcherRef,
|
||||
) {
|
||||
// DevTools will pass the current renderer's injected dispatcher.
|
||||
// Other apps might compile debug hooks as part of their app though.
|
||||
if (currentDispatcher == null) {
|
||||
currentDispatcher = ReactSharedInternals.ReactCurrentDispatcher;
|
||||
}
|
||||
|
||||
if (
|
||||
fiber.tag !== FunctionComponent &&
|
||||
fiber.tag !== SimpleMemoComponent &&
|
||||
@@ -520,9 +577,14 @@ export function inspectHooksOfFiber(fiber: Fiber) {
|
||||
try {
|
||||
setupContexts(contextMap, fiber);
|
||||
if (fiber.tag === ForwardRef) {
|
||||
return inspectHooksOfForwardRef(type.render, props, fiber.ref);
|
||||
return inspectHooksOfForwardRef(
|
||||
type.render,
|
||||
props,
|
||||
fiber.ref,
|
||||
currentDispatcher,
|
||||
);
|
||||
}
|
||||
return inspectHooks(type, props);
|
||||
return inspectHooks(type, props, currentDispatcher);
|
||||
} finally {
|
||||
currentHook = null;
|
||||
restoreContexts(contextMap);
|
||||
|
||||
@@ -16,9 +16,6 @@ let ReactDebugTools;
|
||||
describe('ReactHooksInspection', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
let ReactFeatureFlags = require('shared/ReactFeatureFlags');
|
||||
// TODO: Switch this test to non-internal once the flag is on by default.
|
||||
ReactFeatureFlags.enableHooks = true;
|
||||
React = require('react');
|
||||
ReactDebugTools = require('react-debug-tools');
|
||||
});
|
||||
@@ -41,6 +38,7 @@ describe('ReactHooksInspection', () => {
|
||||
it('should inspect a simple custom hook', () => {
|
||||
function useCustom(value) {
|
||||
let [state] = React.useState(value);
|
||||
React.useDebugValue('custom hook label');
|
||||
return state;
|
||||
}
|
||||
function Foo(props) {
|
||||
@@ -51,7 +49,7 @@ describe('ReactHooksInspection', () => {
|
||||
expect(tree).toEqual([
|
||||
{
|
||||
name: 'Custom',
|
||||
value: undefined,
|
||||
value: __DEV__ ? 'custom hook label' : undefined,
|
||||
subHooks: [
|
||||
{
|
||||
name: 'State',
|
||||
@@ -129,7 +127,7 @@ describe('ReactHooksInspection', () => {
|
||||
return result;
|
||||
}
|
||||
function useBaz(value) {
|
||||
React.useMutationEffect(effect);
|
||||
React.useLayoutEffect(effect);
|
||||
let result = useCustom(value);
|
||||
return result;
|
||||
}
|
||||
@@ -176,7 +174,7 @@ describe('ReactHooksInspection', () => {
|
||||
value: undefined,
|
||||
subHooks: [
|
||||
{
|
||||
name: 'MutationEffect',
|
||||
name: 'LayoutEffect',
|
||||
value: effect,
|
||||
subHooks: [],
|
||||
},
|
||||
@@ -216,4 +214,72 @@ describe('ReactHooksInspection', () => {
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should support an injected dispatcher', () => {
|
||||
function Foo(props) {
|
||||
let [state] = React.useState('hello world');
|
||||
return <div>{state}</div>;
|
||||
}
|
||||
|
||||
let initial = {};
|
||||
let current = initial;
|
||||
let getterCalls = 0;
|
||||
let setterCalls = [];
|
||||
let FakeDispatcherRef = {
|
||||
get current() {
|
||||
getterCalls++;
|
||||
return current;
|
||||
},
|
||||
set current(value) {
|
||||
setterCalls.push(value);
|
||||
current = value;
|
||||
},
|
||||
};
|
||||
|
||||
expect(() => {
|
||||
ReactDebugTools.inspectHooks(Foo, {}, FakeDispatcherRef);
|
||||
}).toThrow(
|
||||
'Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for' +
|
||||
' one of the following reasons:\n' +
|
||||
'1. You might have mismatching versions of React and the renderer (such as React DOM)\n' +
|
||||
'2. You might be breaking the Rules of Hooks\n' +
|
||||
'3. You might have more than one copy of React in the same app\n' +
|
||||
'See https://fb.me/react-invalid-hook-call for tips about how to debug and fix this problem.',
|
||||
);
|
||||
|
||||
expect(getterCalls).toBe(1);
|
||||
expect(setterCalls).toHaveLength(2);
|
||||
expect(setterCalls[0]).not.toBe(initial);
|
||||
expect(setterCalls[1]).toBe(initial);
|
||||
});
|
||||
|
||||
describe('useDebugValue', () => {
|
||||
it('should be ignored when called outside of a custom hook', () => {
|
||||
function Foo(props) {
|
||||
React.useDebugValue('this is invalid');
|
||||
return null;
|
||||
}
|
||||
let tree = ReactDebugTools.inspectHooks(Foo, {});
|
||||
expect(tree).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should support an optional formatter function param', () => {
|
||||
function useCustom() {
|
||||
React.useDebugValue({bar: 123}, object => `bar:${object.bar}`);
|
||||
React.useState(0);
|
||||
}
|
||||
function Foo(props) {
|
||||
useCustom();
|
||||
return null;
|
||||
}
|
||||
let tree = ReactDebugTools.inspectHooks(Foo, {});
|
||||
expect(tree).toEqual([
|
||||
{
|
||||
name: 'Custom',
|
||||
value: __DEV__ ? 'bar:123' : undefined,
|
||||
subHooks: [{name: 'State', subHooks: [], value: 0}],
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,247 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @emails react-core
|
||||
* @jest-environment node
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
let React;
|
||||
let ReactTestRenderer;
|
||||
let ReactDebugTools;
|
||||
|
||||
describe('ReactHooksInspectionIntergration', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
let ReactFeatureFlags = require('shared/ReactFeatureFlags');
|
||||
// TODO: Switch this test to non-internal once the flag is on by default.
|
||||
ReactFeatureFlags.enableHooks = true;
|
||||
React = require('react');
|
||||
ReactTestRenderer = require('react-test-renderer');
|
||||
ReactDebugTools = require('react-debug-tools');
|
||||
});
|
||||
|
||||
it('should inspect the current state of useState hooks', () => {
|
||||
let useState = React.useState;
|
||||
function Foo(props) {
|
||||
let [state1, setState1] = useState('hello');
|
||||
let [state2, setState2] = useState('world');
|
||||
return (
|
||||
<div onMouseDown={setState1} onMouseUp={setState2}>
|
||||
{state1} {state2}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
let renderer = ReactTestRenderer.create(<Foo prop="prop" />);
|
||||
|
||||
let childFiber = renderer.root.findByType(Foo)._currentFiber();
|
||||
let tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
|
||||
expect(tree).toEqual([
|
||||
{name: 'State', value: 'hello', subHooks: []},
|
||||
{name: 'State', value: 'world', subHooks: []},
|
||||
]);
|
||||
|
||||
let {
|
||||
onMouseDown: setStateA,
|
||||
onMouseUp: setStateB,
|
||||
} = renderer.root.findByType('div').props;
|
||||
|
||||
setStateA('Hi');
|
||||
|
||||
childFiber = renderer.root.findByType(Foo)._currentFiber();
|
||||
tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
|
||||
|
||||
expect(tree).toEqual([
|
||||
{name: 'State', value: 'Hi', subHooks: []},
|
||||
{name: 'State', value: 'world', subHooks: []},
|
||||
]);
|
||||
|
||||
setStateB('world!');
|
||||
|
||||
childFiber = renderer.root.findByType(Foo)._currentFiber();
|
||||
tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
|
||||
|
||||
expect(tree).toEqual([
|
||||
{name: 'State', value: 'Hi', subHooks: []},
|
||||
{name: 'State', value: 'world!', subHooks: []},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should inspect the current state of all stateful hooks', () => {
|
||||
let outsideRef = React.createRef();
|
||||
function effect() {}
|
||||
function Foo(props) {
|
||||
let [state1, setState] = React.useState('a');
|
||||
let [state2, dispatch] = React.useReducer((s, a) => a.value, 'b');
|
||||
let ref = React.useRef('c');
|
||||
|
||||
React.useMutationEffect(effect);
|
||||
React.useLayoutEffect(effect);
|
||||
React.useEffect(effect);
|
||||
|
||||
React.useImperativeMethods(
|
||||
outsideRef,
|
||||
() => {
|
||||
// Return a function so that jest treats them as non-equal.
|
||||
return function Instance() {};
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
React.useMemo(() => state1 + state2, [state1]);
|
||||
|
||||
function update() {
|
||||
setState('A');
|
||||
dispatch({value: 'B'});
|
||||
ref.current = 'C';
|
||||
}
|
||||
let memoizedUpdate = React.useCallback(update, []);
|
||||
return (
|
||||
<div onClick={memoizedUpdate}>
|
||||
{state1} {state2}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
let renderer = ReactTestRenderer.create(<Foo prop="prop" />);
|
||||
|
||||
let childFiber = renderer.root.findByType(Foo)._currentFiber();
|
||||
|
||||
let {onClick: updateStates} = renderer.root.findByType('div').props;
|
||||
|
||||
let tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
|
||||
expect(tree).toEqual([
|
||||
{name: 'State', value: 'a', subHooks: []},
|
||||
{name: 'Reducer', value: 'b', subHooks: []},
|
||||
{name: 'Ref', value: 'c', subHooks: []},
|
||||
{name: 'MutationEffect', value: effect, subHooks: []},
|
||||
{name: 'LayoutEffect', value: effect, subHooks: []},
|
||||
{name: 'Effect', value: effect, subHooks: []},
|
||||
{name: 'ImperativeMethods', value: outsideRef.current, subHooks: []},
|
||||
{name: 'Memo', value: 'ab', subHooks: []},
|
||||
{name: 'Callback', value: updateStates, subHooks: []},
|
||||
]);
|
||||
|
||||
updateStates();
|
||||
|
||||
childFiber = renderer.root.findByType(Foo)._currentFiber();
|
||||
tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
|
||||
|
||||
expect(tree).toEqual([
|
||||
{name: 'State', value: 'A', subHooks: []},
|
||||
{name: 'Reducer', value: 'B', subHooks: []},
|
||||
{name: 'Ref', value: 'C', subHooks: []},
|
||||
{name: 'MutationEffect', value: effect, subHooks: []},
|
||||
{name: 'LayoutEffect', value: effect, subHooks: []},
|
||||
{name: 'Effect', value: effect, subHooks: []},
|
||||
{name: 'ImperativeMethods', value: outsideRef.current, subHooks: []},
|
||||
{name: 'Memo', value: 'Ab', subHooks: []},
|
||||
{name: 'Callback', value: updateStates, subHooks: []},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should inspect the value of the current provider in useContext', () => {
|
||||
let MyContext = React.createContext('default');
|
||||
function Foo(props) {
|
||||
let value = React.useContext(MyContext);
|
||||
return <div>{value}</div>;
|
||||
}
|
||||
let renderer = ReactTestRenderer.create(
|
||||
<MyContext.Provider value="contextual">
|
||||
<Foo prop="prop" />
|
||||
</MyContext.Provider>,
|
||||
);
|
||||
let childFiber = renderer.root.findByType(Foo)._currentFiber();
|
||||
let tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
|
||||
expect(tree).toEqual([
|
||||
{
|
||||
name: 'Context',
|
||||
value: 'contextual',
|
||||
subHooks: [],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should inspect forwardRef', () => {
|
||||
let obj = function() {};
|
||||
let Foo = React.forwardRef(function(props, ref) {
|
||||
React.useImperativeMethods(ref, () => obj);
|
||||
return <div />;
|
||||
});
|
||||
let ref = React.createRef();
|
||||
let renderer = ReactTestRenderer.create(<Foo ref={ref} />);
|
||||
|
||||
let childFiber = renderer.root.findByType(Foo)._currentFiber();
|
||||
let tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
|
||||
expect(tree).toEqual([
|
||||
{name: 'ImperativeMethods', value: obj, subHooks: []},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should inspect memo', () => {
|
||||
function InnerFoo(props) {
|
||||
let [value] = React.useState('hello');
|
||||
return <div>{value}</div>;
|
||||
}
|
||||
let Foo = React.memo(InnerFoo);
|
||||
let renderer = ReactTestRenderer.create(<Foo />);
|
||||
// TODO: Test renderer findByType is broken for memo. Have to search for the inner.
|
||||
let childFiber = renderer.root.findByType(InnerFoo)._currentFiber();
|
||||
let tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
|
||||
expect(tree).toEqual([{name: 'State', value: 'hello', subHooks: []}]);
|
||||
});
|
||||
|
||||
it('should inspect custom hooks', () => {
|
||||
function useCustom() {
|
||||
let [value] = React.useState('hello');
|
||||
return value;
|
||||
}
|
||||
function Foo(props) {
|
||||
let value = useCustom();
|
||||
return <div>{value}</div>;
|
||||
}
|
||||
let renderer = ReactTestRenderer.create(<Foo />);
|
||||
let childFiber = renderer.root.findByType(Foo)._currentFiber();
|
||||
let tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
|
||||
expect(tree).toEqual([
|
||||
{
|
||||
name: 'Custom',
|
||||
value: undefined,
|
||||
subHooks: [{name: 'State', value: 'hello', subHooks: []}],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should support defaultProps and lazy', async () => {
|
||||
let Suspense = React.Suspense;
|
||||
|
||||
function Foo(props) {
|
||||
let [value] = React.useState(props.defaultValue.substr(0, 3));
|
||||
return <div>{value}</div>;
|
||||
}
|
||||
Foo.defaultProps = {
|
||||
defaultValue: 'default',
|
||||
};
|
||||
|
||||
async function fakeImport(result) {
|
||||
return {default: result};
|
||||
}
|
||||
|
||||
let LazyFoo = React.lazy(() => fakeImport(Foo));
|
||||
|
||||
let renderer = ReactTestRenderer.create(
|
||||
<Suspense fallback="Loading...">
|
||||
<LazyFoo />
|
||||
</Suspense>,
|
||||
);
|
||||
|
||||
await LazyFoo;
|
||||
|
||||
let childFiber = renderer.root._currentFiber();
|
||||
let tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
|
||||
expect(tree).toEqual([{name: 'State', value: 'def', subHooks: []}]);
|
||||
});
|
||||
});
|
||||
473
packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js
vendored
Normal file
473
packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js
vendored
Normal file
@@ -0,0 +1,473 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @emails react-core
|
||||
* @jest-environment node
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
let React;
|
||||
let ReactTestRenderer;
|
||||
let ReactDebugTools;
|
||||
let act;
|
||||
|
||||
describe('ReactHooksInspectionIntegration', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
React = require('react');
|
||||
ReactTestRenderer = require('react-test-renderer');
|
||||
act = ReactTestRenderer.act;
|
||||
ReactDebugTools = require('react-debug-tools');
|
||||
});
|
||||
|
||||
it('should inspect the current state of useState hooks', () => {
|
||||
let useState = React.useState;
|
||||
function Foo(props) {
|
||||
let [state1, setState1] = useState('hello');
|
||||
let [state2, setState2] = useState('world');
|
||||
return (
|
||||
<div onMouseDown={setState1} onMouseUp={setState2}>
|
||||
{state1} {state2}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
let renderer = ReactTestRenderer.create(<Foo prop="prop" />);
|
||||
|
||||
let childFiber = renderer.root.findByType(Foo)._currentFiber();
|
||||
let tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
|
||||
expect(tree).toEqual([
|
||||
{name: 'State', value: 'hello', subHooks: []},
|
||||
{name: 'State', value: 'world', subHooks: []},
|
||||
]);
|
||||
|
||||
let {
|
||||
onMouseDown: setStateA,
|
||||
onMouseUp: setStateB,
|
||||
} = renderer.root.findByType('div').props;
|
||||
|
||||
act(() => setStateA('Hi'));
|
||||
|
||||
childFiber = renderer.root.findByType(Foo)._currentFiber();
|
||||
tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
|
||||
|
||||
expect(tree).toEqual([
|
||||
{name: 'State', value: 'Hi', subHooks: []},
|
||||
{name: 'State', value: 'world', subHooks: []},
|
||||
]);
|
||||
|
||||
act(() => setStateB('world!'));
|
||||
|
||||
childFiber = renderer.root.findByType(Foo)._currentFiber();
|
||||
tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
|
||||
|
||||
expect(tree).toEqual([
|
||||
{name: 'State', value: 'Hi', subHooks: []},
|
||||
{name: 'State', value: 'world!', subHooks: []},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should inspect the current state of all stateful hooks', () => {
|
||||
let outsideRef = React.createRef();
|
||||
function effect() {}
|
||||
function Foo(props) {
|
||||
let [state1, setState] = React.useState('a');
|
||||
let [state2, dispatch] = React.useReducer((s, a) => a.value, 'b');
|
||||
let ref = React.useRef('c');
|
||||
|
||||
React.useLayoutEffect(effect);
|
||||
React.useEffect(effect);
|
||||
|
||||
React.useImperativeHandle(
|
||||
outsideRef,
|
||||
() => {
|
||||
// Return a function so that jest treats them as non-equal.
|
||||
return function Instance() {};
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
React.useMemo(() => state1 + state2, [state1]);
|
||||
|
||||
function update() {
|
||||
act(() => {
|
||||
setState('A');
|
||||
});
|
||||
act(() => {
|
||||
dispatch({value: 'B'});
|
||||
});
|
||||
ref.current = 'C';
|
||||
}
|
||||
let memoizedUpdate = React.useCallback(update, []);
|
||||
return (
|
||||
<div onClick={memoizedUpdate}>
|
||||
{state1} {state2}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
let renderer = ReactTestRenderer.create(<Foo prop="prop" />);
|
||||
|
||||
let childFiber = renderer.root.findByType(Foo)._currentFiber();
|
||||
|
||||
let {onClick: updateStates} = renderer.root.findByType('div').props;
|
||||
|
||||
let tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
|
||||
expect(tree).toEqual([
|
||||
{name: 'State', value: 'a', subHooks: []},
|
||||
{name: 'Reducer', value: 'b', subHooks: []},
|
||||
{name: 'Ref', value: 'c', subHooks: []},
|
||||
{name: 'LayoutEffect', value: effect, subHooks: []},
|
||||
{name: 'Effect', value: effect, subHooks: []},
|
||||
{name: 'ImperativeHandle', value: outsideRef.current, subHooks: []},
|
||||
{name: 'Memo', value: 'ab', subHooks: []},
|
||||
{name: 'Callback', value: updateStates, subHooks: []},
|
||||
]);
|
||||
|
||||
updateStates();
|
||||
|
||||
childFiber = renderer.root.findByType(Foo)._currentFiber();
|
||||
tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
|
||||
|
||||
expect(tree).toEqual([
|
||||
{name: 'State', value: 'A', subHooks: []},
|
||||
{name: 'Reducer', value: 'B', subHooks: []},
|
||||
{name: 'Ref', value: 'C', subHooks: []},
|
||||
{name: 'LayoutEffect', value: effect, subHooks: []},
|
||||
{name: 'Effect', value: effect, subHooks: []},
|
||||
{name: 'ImperativeHandle', value: outsideRef.current, subHooks: []},
|
||||
{name: 'Memo', value: 'Ab', subHooks: []},
|
||||
{name: 'Callback', value: updateStates, subHooks: []},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should inspect the value of the current provider in useContext', () => {
|
||||
let MyContext = React.createContext('default');
|
||||
function Foo(props) {
|
||||
let value = React.useContext(MyContext);
|
||||
return <div>{value}</div>;
|
||||
}
|
||||
let renderer = ReactTestRenderer.create(
|
||||
<MyContext.Provider value="contextual">
|
||||
<Foo prop="prop" />
|
||||
</MyContext.Provider>,
|
||||
);
|
||||
let childFiber = renderer.root.findByType(Foo)._currentFiber();
|
||||
let tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
|
||||
expect(tree).toEqual([
|
||||
{
|
||||
name: 'Context',
|
||||
value: 'contextual',
|
||||
subHooks: [],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should inspect forwardRef', () => {
|
||||
let obj = function() {};
|
||||
let Foo = React.forwardRef(function(props, ref) {
|
||||
React.useImperativeHandle(ref, () => obj);
|
||||
return <div />;
|
||||
});
|
||||
let ref = React.createRef();
|
||||
let renderer = ReactTestRenderer.create(<Foo ref={ref} />);
|
||||
|
||||
let childFiber = renderer.root.findByType(Foo)._currentFiber();
|
||||
let tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
|
||||
expect(tree).toEqual([
|
||||
{name: 'ImperativeHandle', value: obj, subHooks: []},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should inspect memo', () => {
|
||||
function InnerFoo(props) {
|
||||
let [value] = React.useState('hello');
|
||||
return <div>{value}</div>;
|
||||
}
|
||||
let Foo = React.memo(InnerFoo);
|
||||
let renderer = ReactTestRenderer.create(<Foo />);
|
||||
// TODO: Test renderer findByType is broken for memo. Have to search for the inner.
|
||||
let childFiber = renderer.root.findByType(InnerFoo)._currentFiber();
|
||||
let tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
|
||||
expect(tree).toEqual([{name: 'State', value: 'hello', subHooks: []}]);
|
||||
});
|
||||
|
||||
it('should inspect custom hooks', () => {
|
||||
function useCustom() {
|
||||
let [value] = React.useState('hello');
|
||||
return value;
|
||||
}
|
||||
function Foo(props) {
|
||||
let value = useCustom();
|
||||
return <div>{value}</div>;
|
||||
}
|
||||
let renderer = ReactTestRenderer.create(<Foo />);
|
||||
let childFiber = renderer.root.findByType(Foo)._currentFiber();
|
||||
let tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
|
||||
expect(tree).toEqual([
|
||||
{
|
||||
name: 'Custom',
|
||||
value: undefined,
|
||||
subHooks: [{name: 'State', value: 'hello', subHooks: []}],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
describe('useDebugValue', () => {
|
||||
it('should support inspectable values for multiple custom hooks', () => {
|
||||
function useLabeledValue(label) {
|
||||
let [value] = React.useState(label);
|
||||
React.useDebugValue(`custom label ${label}`);
|
||||
return value;
|
||||
}
|
||||
function useAnonymous(label) {
|
||||
let [value] = React.useState(label);
|
||||
return value;
|
||||
}
|
||||
function Example() {
|
||||
useLabeledValue('a');
|
||||
React.useState('b');
|
||||
useAnonymous('c');
|
||||
useLabeledValue('d');
|
||||
return null;
|
||||
}
|
||||
let renderer = ReactTestRenderer.create(<Example />);
|
||||
let childFiber = renderer.root.findByType(Example)._currentFiber();
|
||||
let tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
|
||||
expect(tree).toEqual([
|
||||
{
|
||||
name: 'LabeledValue',
|
||||
value: __DEV__ ? 'custom label a' : undefined,
|
||||
subHooks: [{name: 'State', value: 'a', subHooks: []}],
|
||||
},
|
||||
{
|
||||
name: 'State',
|
||||
value: 'b',
|
||||
subHooks: [],
|
||||
},
|
||||
{
|
||||
name: 'Anonymous',
|
||||
value: undefined,
|
||||
subHooks: [{name: 'State', value: 'c', subHooks: []}],
|
||||
},
|
||||
{
|
||||
name: 'LabeledValue',
|
||||
value: __DEV__ ? 'custom label d' : undefined,
|
||||
subHooks: [{name: 'State', value: 'd', subHooks: []}],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should support inspectable values for nested custom hooks', () => {
|
||||
function useInner() {
|
||||
React.useDebugValue('inner');
|
||||
React.useState(0);
|
||||
}
|
||||
function useOuter() {
|
||||
React.useDebugValue('outer');
|
||||
useInner();
|
||||
}
|
||||
function Example() {
|
||||
useOuter();
|
||||
return null;
|
||||
}
|
||||
let renderer = ReactTestRenderer.create(<Example />);
|
||||
let childFiber = renderer.root.findByType(Example)._currentFiber();
|
||||
let tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
|
||||
expect(tree).toEqual([
|
||||
{
|
||||
name: 'Outer',
|
||||
value: __DEV__ ? 'outer' : undefined,
|
||||
subHooks: [
|
||||
{
|
||||
name: 'Inner',
|
||||
value: __DEV__ ? 'inner' : undefined,
|
||||
subHooks: [{name: 'State', value: 0, subHooks: []}],
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should support multiple inspectable values per custom hooks', () => {
|
||||
function useMultiLabelCustom() {
|
||||
React.useDebugValue('one');
|
||||
React.useDebugValue('two');
|
||||
React.useDebugValue('three');
|
||||
React.useState(0);
|
||||
}
|
||||
function useSingleLabelCustom(value) {
|
||||
React.useDebugValue(`single ${value}`);
|
||||
React.useState(0);
|
||||
}
|
||||
function Example() {
|
||||
useSingleLabelCustom('one');
|
||||
useMultiLabelCustom();
|
||||
useSingleLabelCustom('two');
|
||||
return null;
|
||||
}
|
||||
let renderer = ReactTestRenderer.create(<Example />);
|
||||
let childFiber = renderer.root.findByType(Example)._currentFiber();
|
||||
let tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
|
||||
expect(tree).toEqual([
|
||||
{
|
||||
name: 'SingleLabelCustom',
|
||||
value: __DEV__ ? 'single one' : undefined,
|
||||
subHooks: [{name: 'State', value: 0, subHooks: []}],
|
||||
},
|
||||
{
|
||||
name: 'MultiLabelCustom',
|
||||
value: __DEV__ ? ['one', 'two', 'three'] : undefined,
|
||||
subHooks: [{name: 'State', value: 0, subHooks: []}],
|
||||
},
|
||||
{
|
||||
name: 'SingleLabelCustom',
|
||||
value: __DEV__ ? 'single two' : undefined,
|
||||
subHooks: [{name: 'State', value: 0, subHooks: []}],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should ignore useDebugValue() made outside of a custom hook', () => {
|
||||
function Example() {
|
||||
React.useDebugValue('this is invalid');
|
||||
return null;
|
||||
}
|
||||
let renderer = ReactTestRenderer.create(<Example />);
|
||||
let childFiber = renderer.root.findByType(Example)._currentFiber();
|
||||
let tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
|
||||
expect(tree).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should support an optional formatter function param', () => {
|
||||
function useCustom() {
|
||||
React.useDebugValue({bar: 123}, object => `bar:${object.bar}`);
|
||||
React.useState(0);
|
||||
}
|
||||
function Example() {
|
||||
useCustom();
|
||||
return null;
|
||||
}
|
||||
let renderer = ReactTestRenderer.create(<Example />);
|
||||
let childFiber = renderer.root.findByType(Example)._currentFiber();
|
||||
let tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
|
||||
expect(tree).toEqual([
|
||||
{
|
||||
name: 'Custom',
|
||||
value: __DEV__ ? 'bar:123' : undefined,
|
||||
subHooks: [{name: 'State', subHooks: [], value: 0}],
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
it('should support defaultProps and lazy', async () => {
|
||||
let Suspense = React.Suspense;
|
||||
|
||||
function Foo(props) {
|
||||
let [value] = React.useState(props.defaultValue.substr(0, 3));
|
||||
return <div>{value}</div>;
|
||||
}
|
||||
Foo.defaultProps = {
|
||||
defaultValue: 'default',
|
||||
};
|
||||
|
||||
async function fakeImport(result) {
|
||||
return {default: result};
|
||||
}
|
||||
|
||||
let LazyFoo = React.lazy(() => fakeImport(Foo));
|
||||
|
||||
let renderer = ReactTestRenderer.create(
|
||||
<Suspense fallback="Loading...">
|
||||
<LazyFoo />
|
||||
</Suspense>,
|
||||
);
|
||||
|
||||
await LazyFoo;
|
||||
|
||||
let childFiber = renderer.root._currentFiber();
|
||||
let tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
|
||||
expect(tree).toEqual([{name: 'State', value: 'def', subHooks: []}]);
|
||||
});
|
||||
|
||||
it('should support an injected dispatcher', () => {
|
||||
function Foo(props) {
|
||||
let [state] = React.useState('hello world');
|
||||
return <div>{state}</div>;
|
||||
}
|
||||
|
||||
let initial = {};
|
||||
let current = initial;
|
||||
let getterCalls = 0;
|
||||
let setterCalls = [];
|
||||
let FakeDispatcherRef = {
|
||||
get current() {
|
||||
getterCalls++;
|
||||
return current;
|
||||
},
|
||||
set current(value) {
|
||||
setterCalls.push(value);
|
||||
current = value;
|
||||
},
|
||||
};
|
||||
|
||||
let renderer = ReactTestRenderer.create(<Foo />);
|
||||
let childFiber = renderer.root._currentFiber();
|
||||
expect(() => {
|
||||
ReactDebugTools.inspectHooksOfFiber(childFiber, FakeDispatcherRef);
|
||||
}).toThrow(
|
||||
'Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for' +
|
||||
' one of the following reasons:\n' +
|
||||
'1. You might have mismatching versions of React and the renderer (such as React DOM)\n' +
|
||||
'2. You might be breaking the Rules of Hooks\n' +
|
||||
'3. You might have more than one copy of React in the same app\n' +
|
||||
'See https://fb.me/react-invalid-hook-call for tips about how to debug and fix this problem.',
|
||||
);
|
||||
|
||||
expect(getterCalls).toBe(1);
|
||||
expect(setterCalls).toHaveLength(2);
|
||||
expect(setterCalls[0]).not.toBe(initial);
|
||||
expect(setterCalls[1]).toBe(initial);
|
||||
});
|
||||
|
||||
// This test case is based on an open source bug report:
|
||||
// facebookincubator/redux-react-hook/issues/34#issuecomment-466693787
|
||||
it('should properly advance the current hook for useContext', () => {
|
||||
const MyContext = React.createContext(1);
|
||||
|
||||
let incrementCount;
|
||||
|
||||
function Foo(props) {
|
||||
const context = React.useContext(MyContext);
|
||||
const [data, setData] = React.useState({count: context});
|
||||
|
||||
incrementCount = () => setData(({count}) => ({count: count + 1}));
|
||||
|
||||
return <div>count: {data.count}</div>;
|
||||
}
|
||||
|
||||
const renderer = ReactTestRenderer.create(<Foo />);
|
||||
expect(renderer.toJSON()).toEqual({
|
||||
type: 'div',
|
||||
props: {},
|
||||
children: ['count: ', '1'],
|
||||
});
|
||||
|
||||
act(incrementCount);
|
||||
expect(renderer.toJSON()).toEqual({
|
||||
type: 'div',
|
||||
props: {},
|
||||
children: ['count: ', '2'],
|
||||
});
|
||||
|
||||
const childFiber = renderer.root._currentFiber();
|
||||
const tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
|
||||
expect(tree).toEqual([
|
||||
{name: 'Context', value: 1, subHooks: []},
|
||||
{name: 'State', value: {count: 2}, subHooks: []},
|
||||
]);
|
||||
});
|
||||
});
|
||||
7
packages/react-dom/npm/unstable-fizz.browser.js
Normal file
7
packages/react-dom/npm/unstable-fizz.browser.js
Normal file
@@ -0,0 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
module.exports = require('./cjs/react-dom-unstable-fizz.browser.production.min.js');
|
||||
} else {
|
||||
module.exports = require('./cjs/react-dom-unstable-fizz.browser.development.js');
|
||||
}
|
||||
3
packages/react-dom/npm/unstable-fizz.js
vendored
Normal file
3
packages/react-dom/npm/unstable-fizz.js
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = require('./unstable-fizz.node');
|
||||
7
packages/react-dom/npm/unstable-fizz.node.js
Normal file
7
packages/react-dom/npm/unstable-fizz.node.js
Normal file
@@ -0,0 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
module.exports = require('./cjs/react-dom-unstable-fizz.node.production.min.js');
|
||||
} else {
|
||||
module.exports = require('./cjs/react-dom-unstable-fizz.node.development.js');
|
||||
}
|
||||
@@ -1,9 +1,13 @@
|
||||
{
|
||||
"name": "react-dom",
|
||||
"version": "16.6.1",
|
||||
"version": "16.8.6",
|
||||
"description": "React package for working with the DOM.",
|
||||
"main": "index.js",
|
||||
"repository": "facebook/react",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/facebook/react.git",
|
||||
"directory": "packages/react-dom"
|
||||
},
|
||||
"keywords": [
|
||||
"react"
|
||||
],
|
||||
@@ -16,7 +20,7 @@
|
||||
"loose-envify": "^1.1.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"prop-types": "^15.6.2",
|
||||
"scheduler": "^0.11.0"
|
||||
"scheduler": "^0.13.6"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.0.0"
|
||||
@@ -24,18 +28,24 @@
|
||||
"files": [
|
||||
"LICENSE",
|
||||
"README.md",
|
||||
"build-info.json",
|
||||
"index.js",
|
||||
"profiling.js",
|
||||
"server.js",
|
||||
"server.browser.js",
|
||||
"server.node.js",
|
||||
"test-utils.js",
|
||||
"unstable-fire.js",
|
||||
"unstable-fizz.js",
|
||||
"unstable-fizz.browser.js",
|
||||
"unstable-fizz.node.js",
|
||||
"unstable-native-dependencies.js",
|
||||
"cjs/",
|
||||
"umd/"
|
||||
],
|
||||
"browser": {
|
||||
"./server.js": "./server.browser.js"
|
||||
"./server.js": "./server.browser.js",
|
||||
"./unstable-fizz.js": "./unstable-fizz.browser.js"
|
||||
},
|
||||
"browserify": {
|
||||
"transform": [
|
||||
|
||||
@@ -1201,6 +1201,40 @@ describe('ReactComponentLifeCycle', () => {
|
||||
expect(log).toEqual([]);
|
||||
});
|
||||
|
||||
it('should pass previous state to shouldComponentUpdate even with getDerivedStateFromProps', () => {
|
||||
const divRef = React.createRef();
|
||||
class SimpleComponent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
value: props.value,
|
||||
};
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps(nextProps, prevState) {
|
||||
if (nextProps.value === prevState.value) {
|
||||
return null;
|
||||
}
|
||||
return {value: nextProps.value};
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
return nextState.value !== this.state.value;
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div ref={divRef}>value: {this.state.value}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
const div = document.createElement('div');
|
||||
|
||||
ReactDOM.render(<SimpleComponent value="initial" />, div);
|
||||
expect(divRef.current.textContent).toBe('value: initial');
|
||||
ReactDOM.render(<SimpleComponent value="updated" />, div);
|
||||
expect(divRef.current.textContent).toBe('value: updated');
|
||||
});
|
||||
|
||||
it('should call getSnapshotBeforeUpdate before mutations are committed', () => {
|
||||
const log = [];
|
||||
|
||||
|
||||
@@ -1743,6 +1743,25 @@ describe('ReactCompositeComponent', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should warn about reassigning this.props while rendering', () => {
|
||||
class Bad extends React.Component {
|
||||
componentDidMount() {}
|
||||
componentDidUpdate() {}
|
||||
render() {
|
||||
this.props = {...this.props};
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const container = document.createElement('div');
|
||||
expect(() => {
|
||||
ReactDOM.render(<Bad />, container);
|
||||
}).toWarnDev(
|
||||
'It looks like Bad is reassigning its own `this.props` while rendering. ' +
|
||||
'This is not supported and can lead to confusing bugs.',
|
||||
);
|
||||
});
|
||||
|
||||
it('should return error if render is not defined', () => {
|
||||
class RenderTestUndefinedRender extends React.Component {}
|
||||
|
||||
|
||||
@@ -450,22 +450,6 @@ describe('ReactDOM', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('warns when requestAnimationFrame is not polyfilled', () => {
|
||||
const previousRAF = global.requestAnimationFrame;
|
||||
try {
|
||||
delete global.requestAnimationFrame;
|
||||
jest.resetModules();
|
||||
spyOnDevAndProd(console, 'error');
|
||||
require('react-dom');
|
||||
expect(console.error.calls.count()).toEqual(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toMatch(
|
||||
"This browser doesn't support requestAnimationFrame.",
|
||||
);
|
||||
} finally {
|
||||
global.requestAnimationFrame = previousRAF;
|
||||
}
|
||||
});
|
||||
|
||||
it('reports stacks with re-entrant renderToString() calls on the client', () => {
|
||||
function Child2(props) {
|
||||
return <span ariaTypo3="no">{props.children}</span>;
|
||||
|
||||
@@ -461,6 +461,40 @@ describe('ReactDOMFiber', () => {
|
||||
expect(container.innerHTML).toBe('<div></div>');
|
||||
});
|
||||
|
||||
it('should unmount empty portal component wherever it appears', () => {
|
||||
const portalContainer = document.createElement('div');
|
||||
|
||||
class Wrapper extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
show: true,
|
||||
};
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
{this.state.show && (
|
||||
<React.Fragment>
|
||||
{ReactDOM.createPortal(null, portalContainer)}
|
||||
<div>child</div>
|
||||
</React.Fragment>
|
||||
)}
|
||||
<div>parent</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const instance = ReactDOM.render(<Wrapper />, container);
|
||||
expect(container.innerHTML).toBe(
|
||||
'<div><div>child</div><div>parent</div></div>',
|
||||
);
|
||||
instance.setState({show: false});
|
||||
expect(instance.state.show).toBe(false);
|
||||
expect(container.innerHTML).toBe('<div><div>parent</div></div>');
|
||||
});
|
||||
|
||||
it('should keep track of namespace across portals (simple)', () => {
|
||||
assertNamespacesMatch(
|
||||
<svg {...expectSVG}>
|
||||
|
||||
@@ -491,5 +491,191 @@ describe('ReactDOMFiberAsync', () => {
|
||||
expect(container.textContent).toEqual('1');
|
||||
expect(returnValue).toBe(undefined);
|
||||
});
|
||||
|
||||
it('ignores discrete events on a pending removed element', () => {
|
||||
const disableButtonRef = React.createRef();
|
||||
const submitButtonRef = React.createRef();
|
||||
|
||||
let formSubmitted = false;
|
||||
|
||||
class Form extends React.Component {
|
||||
state = {active: true};
|
||||
disableForm = () => {
|
||||
this.setState({active: false});
|
||||
};
|
||||
submitForm = () => {
|
||||
formSubmitted = true; // This should not get invoked
|
||||
};
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<button onClick={this.disableForm} ref={disableButtonRef}>
|
||||
Disable
|
||||
</button>
|
||||
{this.state.active ? (
|
||||
<button onClick={this.submitForm} ref={submitButtonRef}>
|
||||
Submit
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const root = ReactDOM.unstable_createRoot(container);
|
||||
root.render(<Form />);
|
||||
// Flush
|
||||
jest.runAllTimers();
|
||||
|
||||
let disableButton = disableButtonRef.current;
|
||||
expect(disableButton.tagName).toBe('BUTTON');
|
||||
|
||||
// Dispatch a click event on the Disable-button.
|
||||
let firstEvent = document.createEvent('Event');
|
||||
firstEvent.initEvent('click', true, true);
|
||||
disableButton.dispatchEvent(firstEvent);
|
||||
|
||||
// There should now be a pending update to disable the form.
|
||||
|
||||
// This should not have flushed yet since it's in concurrent mode.
|
||||
let submitButton = submitButtonRef.current;
|
||||
expect(submitButton.tagName).toBe('BUTTON');
|
||||
|
||||
// In the meantime, we can dispatch a new client event on the submit button.
|
||||
let secondEvent = document.createEvent('Event');
|
||||
secondEvent.initEvent('click', true, true);
|
||||
// This should force the pending update to flush which disables the submit button before the event is invoked.
|
||||
submitButton.dispatchEvent(secondEvent);
|
||||
|
||||
// Therefore the form should never have been submitted.
|
||||
expect(formSubmitted).toBe(false);
|
||||
|
||||
expect(submitButtonRef.current).toBe(null);
|
||||
});
|
||||
|
||||
it('ignores discrete events on a pending removed event listener', () => {
|
||||
const disableButtonRef = React.createRef();
|
||||
const submitButtonRef = React.createRef();
|
||||
|
||||
let formSubmitted = false;
|
||||
|
||||
class Form extends React.Component {
|
||||
state = {active: true};
|
||||
disableForm = () => {
|
||||
this.setState({active: false});
|
||||
};
|
||||
submitForm = () => {
|
||||
formSubmitted = true; // This should not get invoked
|
||||
};
|
||||
disabledSubmitForm = () => {
|
||||
// The form is disabled.
|
||||
};
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<button onClick={this.disableForm} ref={disableButtonRef}>
|
||||
Disable
|
||||
</button>
|
||||
<button
|
||||
onClick={
|
||||
this.state.active ? this.submitForm : this.disabledSubmitForm
|
||||
}
|
||||
ref={submitButtonRef}>
|
||||
Submit
|
||||
</button>{' '}
|
||||
: null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const root = ReactDOM.unstable_createRoot(container);
|
||||
root.render(<Form />);
|
||||
// Flush
|
||||
jest.runAllTimers();
|
||||
|
||||
let disableButton = disableButtonRef.current;
|
||||
expect(disableButton.tagName).toBe('BUTTON');
|
||||
|
||||
// Dispatch a click event on the Disable-button.
|
||||
let firstEvent = document.createEvent('Event');
|
||||
firstEvent.initEvent('click', true, true);
|
||||
disableButton.dispatchEvent(firstEvent);
|
||||
|
||||
// There should now be a pending update to disable the form.
|
||||
|
||||
// This should not have flushed yet since it's in concurrent mode.
|
||||
let submitButton = submitButtonRef.current;
|
||||
expect(submitButton.tagName).toBe('BUTTON');
|
||||
|
||||
// In the meantime, we can dispatch a new client event on the submit button.
|
||||
let secondEvent = document.createEvent('Event');
|
||||
secondEvent.initEvent('click', true, true);
|
||||
// This should force the pending update to flush which disables the submit button before the event is invoked.
|
||||
submitButton.dispatchEvent(secondEvent);
|
||||
|
||||
// Therefore the form should never have been submitted.
|
||||
expect(formSubmitted).toBe(false);
|
||||
});
|
||||
|
||||
it('uses the newest discrete events on a pending changed event listener', () => {
|
||||
const enableButtonRef = React.createRef();
|
||||
const submitButtonRef = React.createRef();
|
||||
|
||||
let formSubmitted = false;
|
||||
|
||||
class Form extends React.Component {
|
||||
state = {active: false};
|
||||
enableForm = () => {
|
||||
this.setState({active: true});
|
||||
};
|
||||
submitForm = () => {
|
||||
formSubmitted = true; // This should happen
|
||||
};
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<button onClick={this.enableForm} ref={enableButtonRef}>
|
||||
Enable
|
||||
</button>
|
||||
<button
|
||||
onClick={this.state.active ? this.submitForm : null}
|
||||
ref={submitButtonRef}>
|
||||
Submit
|
||||
</button>{' '}
|
||||
: null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const root = ReactDOM.unstable_createRoot(container);
|
||||
root.render(<Form />);
|
||||
// Flush
|
||||
jest.runAllTimers();
|
||||
|
||||
let enableButton = enableButtonRef.current;
|
||||
expect(enableButton.tagName).toBe('BUTTON');
|
||||
|
||||
// Dispatch a click event on the Enable-button.
|
||||
let firstEvent = document.createEvent('Event');
|
||||
firstEvent.initEvent('click', true, true);
|
||||
enableButton.dispatchEvent(firstEvent);
|
||||
|
||||
// There should now be a pending update to enable the form.
|
||||
|
||||
// This should not have flushed yet since it's in concurrent mode.
|
||||
let submitButton = submitButtonRef.current;
|
||||
expect(submitButton.tagName).toBe('BUTTON');
|
||||
|
||||
// In the meantime, we can dispatch a new client event on the submit button.
|
||||
let secondEvent = document.createEvent('Event');
|
||||
secondEvent.initEvent('click', true, true);
|
||||
// This should force the pending update to flush which enables the submit button before the event is invoked.
|
||||
submitButton.dispatchEvent(secondEvent);
|
||||
|
||||
// Therefore the form should have been submitted.
|
||||
expect(formSubmitted).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
45
packages/react-dom/src/__tests__/ReactDOMFizzServerBrowser-test.js
vendored
Normal file
45
packages/react-dom/src/__tests__/ReactDOMFizzServerBrowser-test.js
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @emails react-core
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
// Polyfills for test environment
|
||||
global.ReadableStream = require('@mattiasbuelens/web-streams-polyfill/ponyfill/es6').ReadableStream;
|
||||
global.TextEncoder = require('util').TextEncoder;
|
||||
|
||||
let React;
|
||||
let ReactDOMFizzServer;
|
||||
|
||||
describe('ReactDOMFizzServer', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
React = require('react');
|
||||
ReactDOMFizzServer = require('react-dom/unstable-fizz.browser');
|
||||
});
|
||||
|
||||
async function readResult(stream) {
|
||||
let reader = stream.getReader();
|
||||
let result = '';
|
||||
while (true) {
|
||||
let {done, value} = await reader.read();
|
||||
if (done) {
|
||||
return result;
|
||||
}
|
||||
result += Buffer.from(value).toString('utf8');
|
||||
}
|
||||
}
|
||||
|
||||
it('should call renderToReadableStream', async () => {
|
||||
let stream = ReactDOMFizzServer.renderToReadableStream(
|
||||
<div>hello world</div>,
|
||||
);
|
||||
let result = await readResult(stream);
|
||||
expect(result).toBe('<div>hello world</div>');
|
||||
});
|
||||
});
|
||||
39
packages/react-dom/src/__tests__/ReactDOMFizzServerNode-test.js
vendored
Normal file
39
packages/react-dom/src/__tests__/ReactDOMFizzServerNode-test.js
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @emails react-core
|
||||
* @jest-environment node
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
let Stream;
|
||||
let React;
|
||||
let ReactDOMFizzServer;
|
||||
|
||||
describe('ReactDOMFizzServer', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
React = require('react');
|
||||
ReactDOMFizzServer = require('react-dom/unstable-fizz');
|
||||
Stream = require('stream');
|
||||
});
|
||||
|
||||
function getTestWritable() {
|
||||
let writable = new Stream.PassThrough();
|
||||
writable.setEncoding('utf8');
|
||||
writable.result = '';
|
||||
writable.on('data', chunk => (writable.result += chunk));
|
||||
return writable;
|
||||
}
|
||||
|
||||
it('should call pipeToNodeWritable', () => {
|
||||
let writable = getTestWritable();
|
||||
ReactDOMFizzServer.pipeToNodeWritable(<div>hello world</div>, writable);
|
||||
jest.runAllTimers();
|
||||
expect(writable.result).toBe('<div>hello world</div>');
|
||||
});
|
||||
});
|
||||
180
packages/react-dom/src/__tests__/ReactDOMHooks-test.js
vendored
Normal file
180
packages/react-dom/src/__tests__/ReactDOMHooks-test.js
vendored
Normal file
@@ -0,0 +1,180 @@
|
||||
/**
|
||||
* Copyright (c) 2013-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @emails react-core
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
let React;
|
||||
let ReactDOM;
|
||||
|
||||
describe('ReactDOMHooks', () => {
|
||||
let container;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
|
||||
React = require('react');
|
||||
ReactDOM = require('react-dom');
|
||||
|
||||
container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
document.body.removeChild(container);
|
||||
});
|
||||
|
||||
it('can ReactDOM.render() from useEffect', () => {
|
||||
let container2 = document.createElement('div');
|
||||
let container3 = document.createElement('div');
|
||||
|
||||
function Example1({n}) {
|
||||
React.useEffect(() => {
|
||||
ReactDOM.render(<Example2 n={n} />, container2);
|
||||
});
|
||||
return 1 * n;
|
||||
}
|
||||
|
||||
function Example2({n}) {
|
||||
React.useEffect(() => {
|
||||
ReactDOM.render(<Example3 n={n} />, container3);
|
||||
});
|
||||
return 2 * n;
|
||||
}
|
||||
|
||||
function Example3({n}) {
|
||||
return 3 * n;
|
||||
}
|
||||
|
||||
ReactDOM.render(<Example1 n={1} />, container);
|
||||
expect(container.textContent).toBe('1');
|
||||
expect(container2.textContent).toBe('');
|
||||
expect(container3.textContent).toBe('');
|
||||
jest.runAllTimers();
|
||||
expect(container.textContent).toBe('1');
|
||||
expect(container2.textContent).toBe('2');
|
||||
expect(container3.textContent).toBe('3');
|
||||
|
||||
ReactDOM.render(<Example1 n={2} />, container);
|
||||
expect(container.textContent).toBe('2');
|
||||
expect(container2.textContent).toBe('2'); // Not flushed yet
|
||||
expect(container3.textContent).toBe('3'); // Not flushed yet
|
||||
jest.runAllTimers();
|
||||
expect(container.textContent).toBe('2');
|
||||
expect(container2.textContent).toBe('4');
|
||||
expect(container3.textContent).toBe('6');
|
||||
});
|
||||
|
||||
it('can batch synchronous work inside effects with other work', () => {
|
||||
let otherContainer = document.createElement('div');
|
||||
|
||||
let calledA = false;
|
||||
function A() {
|
||||
calledA = true;
|
||||
return 'A';
|
||||
}
|
||||
|
||||
let calledB = false;
|
||||
function B() {
|
||||
calledB = true;
|
||||
return 'B';
|
||||
}
|
||||
|
||||
let _set;
|
||||
function Foo() {
|
||||
_set = React.useState(0)[1];
|
||||
React.useEffect(() => {
|
||||
ReactDOM.render(<A />, otherContainer);
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
ReactDOM.render(<Foo />, container);
|
||||
ReactDOM.unstable_batchedUpdates(() => {
|
||||
_set(0); // Forces the effect to be flushed
|
||||
expect(otherContainer.textContent).toBe('');
|
||||
ReactDOM.render(<B />, otherContainer);
|
||||
expect(otherContainer.textContent).toBe('');
|
||||
});
|
||||
expect(otherContainer.textContent).toBe('B');
|
||||
expect(calledA).toBe(false); // It was in a batch
|
||||
expect(calledB).toBe(true);
|
||||
});
|
||||
|
||||
it('should not bail out when an update is scheduled from within an event handler', () => {
|
||||
const {createRef, useCallback, useState} = React;
|
||||
|
||||
const Example = ({inputRef, labelRef}) => {
|
||||
const [text, setText] = useState('');
|
||||
const handleInput = useCallback(event => {
|
||||
setText(event.target.value);
|
||||
});
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<input ref={inputRef} onInput={handleInput} />
|
||||
<label ref={labelRef}>{text}</label>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const inputRef = createRef();
|
||||
const labelRef = createRef();
|
||||
|
||||
ReactDOM.render(
|
||||
<Example inputRef={inputRef} labelRef={labelRef} />,
|
||||
container,
|
||||
);
|
||||
|
||||
inputRef.current.value = 'abc';
|
||||
inputRef.current.dispatchEvent(
|
||||
new Event('input', {bubbles: true, cancelable: true}),
|
||||
);
|
||||
|
||||
expect(labelRef.current.innerHTML).toBe('abc');
|
||||
});
|
||||
|
||||
it('should not bail out when an update is scheduled from within an event handler in ConcurrentMode', () => {
|
||||
const {createRef, useCallback, useState} = React;
|
||||
|
||||
const Example = ({inputRef, labelRef}) => {
|
||||
const [text, setText] = useState('');
|
||||
const handleInput = useCallback(event => {
|
||||
setText(event.target.value);
|
||||
});
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<input ref={inputRef} onInput={handleInput} />
|
||||
<label ref={labelRef}>{text}</label>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const inputRef = createRef();
|
||||
const labelRef = createRef();
|
||||
|
||||
const root = ReactDOM.unstable_createRoot(container);
|
||||
root.render(
|
||||
<React.unstable_ConcurrentMode>
|
||||
<Example inputRef={inputRef} labelRef={labelRef} />
|
||||
</React.unstable_ConcurrentMode>,
|
||||
);
|
||||
|
||||
jest.runAllTimers();
|
||||
|
||||
inputRef.current.value = 'abc';
|
||||
inputRef.current.dispatchEvent(
|
||||
new Event('input', {bubbles: true, cancelable: true}),
|
||||
);
|
||||
|
||||
jest.runAllTimers();
|
||||
|
||||
expect(labelRef.current.innerHTML).toBe('abc');
|
||||
});
|
||||
});
|
||||
@@ -374,4 +374,104 @@ describe('ReactDOMRoot', () => {
|
||||
'unstable_createRoot(...): Target container is not a DOM element.',
|
||||
);
|
||||
});
|
||||
|
||||
it('warns when rendering with legacy API into createRoot() container', () => {
|
||||
const root = ReactDOM.unstable_createRoot(container);
|
||||
root.render(<div>Hi</div>);
|
||||
jest.runAllTimers();
|
||||
expect(container.textContent).toEqual('Hi');
|
||||
expect(() => {
|
||||
ReactDOM.render(<div>Bye</div>, container);
|
||||
}).toWarnDev(
|
||||
[
|
||||
// We care about this warning:
|
||||
'You are calling ReactDOM.render() on a container that was previously ' +
|
||||
'passed to ReactDOM.unstable_createRoot(). This is not supported. ' +
|
||||
'Did you mean to call root.render(element)?',
|
||||
// This is more of a symptom but restructuring the code to avoid it isn't worth it:
|
||||
'Replacing React-rendered children with a new root component.',
|
||||
],
|
||||
{withoutStack: true},
|
||||
);
|
||||
jest.runAllTimers();
|
||||
// This works now but we could disallow it:
|
||||
expect(container.textContent).toEqual('Bye');
|
||||
});
|
||||
|
||||
it('warns when hydrating with legacy API into createRoot() container', () => {
|
||||
const root = ReactDOM.unstable_createRoot(container);
|
||||
root.render(<div>Hi</div>);
|
||||
jest.runAllTimers();
|
||||
expect(container.textContent).toEqual('Hi');
|
||||
expect(() => {
|
||||
ReactDOM.hydrate(<div>Hi</div>, container);
|
||||
}).toWarnDev(
|
||||
[
|
||||
// We care about this warning:
|
||||
'You are calling ReactDOM.hydrate() on a container that was previously ' +
|
||||
'passed to ReactDOM.unstable_createRoot(). This is not supported. ' +
|
||||
'Did you mean to call createRoot(container, {hydrate: true}).render(element)?',
|
||||
// This is more of a symptom but restructuring the code to avoid it isn't worth it:
|
||||
'Replacing React-rendered children with a new root component.',
|
||||
],
|
||||
{withoutStack: true},
|
||||
);
|
||||
});
|
||||
|
||||
it('warns when unmounting with legacy API (no previous content)', () => {
|
||||
const root = ReactDOM.unstable_createRoot(container);
|
||||
root.render(<div>Hi</div>);
|
||||
jest.runAllTimers();
|
||||
expect(container.textContent).toEqual('Hi');
|
||||
let unmounted = false;
|
||||
expect(() => {
|
||||
unmounted = ReactDOM.unmountComponentAtNode(container);
|
||||
}).toWarnDev(
|
||||
[
|
||||
// We care about this warning:
|
||||
'You are calling ReactDOM.unmountComponentAtNode() on a container that was previously ' +
|
||||
'passed to ReactDOM.unstable_createRoot(). This is not supported. Did you mean to call root.unmount()?',
|
||||
// This is more of a symptom but restructuring the code to avoid it isn't worth it:
|
||||
"The node you're attempting to unmount was rendered by React and is not a top-level container.",
|
||||
],
|
||||
{withoutStack: true},
|
||||
);
|
||||
expect(unmounted).toBe(false);
|
||||
jest.runAllTimers();
|
||||
expect(container.textContent).toEqual('Hi');
|
||||
root.unmount();
|
||||
jest.runAllTimers();
|
||||
expect(container.textContent).toEqual('');
|
||||
});
|
||||
|
||||
it('warns when unmounting with legacy API (has previous content)', () => {
|
||||
// Currently createRoot().render() doesn't clear this.
|
||||
container.appendChild(document.createElement('div'));
|
||||
// The rest is the same as test above.
|
||||
const root = ReactDOM.unstable_createRoot(container);
|
||||
root.render(<div>Hi</div>);
|
||||
jest.runAllTimers();
|
||||
expect(container.textContent).toEqual('Hi');
|
||||
let unmounted = false;
|
||||
expect(() => {
|
||||
unmounted = ReactDOM.unmountComponentAtNode(container);
|
||||
}).toWarnDev('Did you mean to call root.unmount()?', {withoutStack: true});
|
||||
expect(unmounted).toBe(false);
|
||||
jest.runAllTimers();
|
||||
expect(container.textContent).toEqual('Hi');
|
||||
root.unmount();
|
||||
jest.runAllTimers();
|
||||
expect(container.textContent).toEqual('');
|
||||
});
|
||||
|
||||
it('warns when passing legacy container to createRoot()', () => {
|
||||
ReactDOM.render(<div>Hi</div>, container);
|
||||
expect(() => {
|
||||
ReactDOM.unstable_createRoot(container);
|
||||
}).toWarnDev(
|
||||
'You are calling ReactDOM.unstable_createRoot() on a container that was previously ' +
|
||||
'passed to ReactDOM.render(). This is not supported.',
|
||||
{withoutStack: true},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -362,6 +362,32 @@ describe('ReactDOMSelect', () => {
|
||||
expect(node.options[2].selected).toBe(true); // gorilla
|
||||
});
|
||||
|
||||
it('does not select an item when size is initially set to greater than 1', () => {
|
||||
const stub = (
|
||||
<select size="2">
|
||||
<option value="monkey">A monkey!</option>
|
||||
<option value="giraffe">A giraffe!</option>
|
||||
<option value="gorilla">A gorilla!</option>
|
||||
</select>
|
||||
);
|
||||
const container = document.createElement('div');
|
||||
const select = ReactDOM.render(stub, container);
|
||||
|
||||
expect(select.options[0].selected).toBe(false);
|
||||
expect(select.options[1].selected).toBe(false);
|
||||
expect(select.options[2].selected).toBe(false);
|
||||
|
||||
// Note: There is an inconsistency between JSDOM and Chrome where
|
||||
// Chrome reports an empty string when no value is selected for a
|
||||
// single-select with a size greater than 0. JSDOM reports the first
|
||||
// value
|
||||
//
|
||||
// This assertion exists only for clarity of JSDOM behavior:
|
||||
expect(select.value).toBe('monkey'); // "" in Chrome
|
||||
// Despite this, the selection index is correct:
|
||||
expect(select.selectedIndex).toBe(-1);
|
||||
});
|
||||
|
||||
it('should remember value when switching to uncontrolled', () => {
|
||||
let stub = (
|
||||
<select value={'giraffe'} onChange={noop}>
|
||||
|
||||
@@ -24,9 +24,9 @@ let useContext;
|
||||
let useCallback;
|
||||
let useMemo;
|
||||
let useRef;
|
||||
let useImperativeMethods;
|
||||
let useMutationEffect;
|
||||
let useImperativeHandle;
|
||||
let useLayoutEffect;
|
||||
let useDebugValue;
|
||||
let forwardRef;
|
||||
let yieldedValues;
|
||||
let yieldValue;
|
||||
@@ -38,7 +38,6 @@ function initModules() {
|
||||
|
||||
ReactFeatureFlags = require('shared/ReactFeatureFlags');
|
||||
ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
|
||||
ReactFeatureFlags.enableHooks = true;
|
||||
React = require('react');
|
||||
ReactDOM = require('react-dom');
|
||||
ReactDOMServer = require('react-dom/server');
|
||||
@@ -49,8 +48,8 @@ function initModules() {
|
||||
useCallback = React.useCallback;
|
||||
useMemo = React.useMemo;
|
||||
useRef = React.useRef;
|
||||
useImperativeMethods = React.useImperativeMethods;
|
||||
useMutationEffect = React.useMutationEffect;
|
||||
useDebugValue = React.useDebugValue;
|
||||
useImperativeHandle = React.useImperativeHandle;
|
||||
useLayoutEffect = React.useLayoutEffect;
|
||||
forwardRef = React.forwardRef;
|
||||
|
||||
@@ -145,7 +144,12 @@ describe('ReactDOMServerHooks', () => {
|
||||
|
||||
return render(<Counter />);
|
||||
},
|
||||
'Hooks can only be called inside the body of a function component.',
|
||||
'Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for' +
|
||||
' one of the following reasons:\n' +
|
||||
'1. You might have mismatching versions of React and the renderer (such as React DOM)\n' +
|
||||
'2. You might be breaking the Rules of Hooks\n' +
|
||||
'3. You might have more than one copy of React in the same app\n' +
|
||||
'See https://fb.me/react-invalid-hook-call for tips about how to debug and fix this problem.',
|
||||
);
|
||||
|
||||
itRenders('multiple times when an updater is called', async render => {
|
||||
@@ -209,12 +213,12 @@ describe('ReactDOMServerHooks', () => {
|
||||
expect(domNode.textContent).toEqual('0');
|
||||
});
|
||||
|
||||
itRenders('lazy initialization with initialAction', async render => {
|
||||
itRenders('lazy initialization', async render => {
|
||||
function reducer(state, action) {
|
||||
return action === 'increment' ? state + 1 : state;
|
||||
}
|
||||
function Counter() {
|
||||
let [count] = useReducer(reducer, 0, 'increment');
|
||||
let [count] = useReducer(reducer, 0, c => c + 1);
|
||||
yieldValue('Render: ' + count);
|
||||
return <Text text={count} />;
|
||||
}
|
||||
@@ -419,6 +423,52 @@ describe('ReactDOMServerHooks', () => {
|
||||
expect(domNode.textContent).toEqual('HELLO, WORLD.');
|
||||
},
|
||||
);
|
||||
|
||||
itRenders('with a warning for useState inside useMemo', async render => {
|
||||
function App() {
|
||||
useMemo(() => {
|
||||
useState();
|
||||
return 0;
|
||||
});
|
||||
return 'hi';
|
||||
}
|
||||
|
||||
const domNode = await render(<App />, 1);
|
||||
expect(domNode.textContent).toEqual('hi');
|
||||
});
|
||||
|
||||
itThrowsWhenRendering(
|
||||
'with a warning for useRef inside useReducer',
|
||||
async render => {
|
||||
function App() {
|
||||
const [value, dispatch] = useReducer((state, action) => {
|
||||
useRef(0);
|
||||
return state + 1;
|
||||
}, 0);
|
||||
if (value === 0) {
|
||||
dispatch();
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
const domNode = await render(<App />, 1);
|
||||
expect(domNode.textContent).toEqual('1');
|
||||
},
|
||||
'Rendered more hooks than during the previous render',
|
||||
);
|
||||
|
||||
itRenders('with a warning for useRef inside useState', async render => {
|
||||
function App() {
|
||||
const [value] = useState(() => {
|
||||
useRef(0);
|
||||
return 0;
|
||||
});
|
||||
return value;
|
||||
}
|
||||
|
||||
const domNode = await render(<App />, 1);
|
||||
expect(domNode.textContent).toEqual('0');
|
||||
});
|
||||
});
|
||||
|
||||
describe('useRef', () => {
|
||||
@@ -516,12 +566,25 @@ describe('ReactDOMServerHooks', () => {
|
||||
expect(domNode.tagName).toEqual('SPAN');
|
||||
expect(domNode.textContent).toEqual('Count: 0');
|
||||
});
|
||||
|
||||
itRenders('should support render time callbacks', async render => {
|
||||
function Counter(props) {
|
||||
const renderCount = useCallback(increment => {
|
||||
return 'Count: ' + (props.count + increment);
|
||||
});
|
||||
return <Text text={renderCount(3)} />;
|
||||
}
|
||||
const domNode = await render(<Counter count={2} />);
|
||||
expect(clearYields()).toEqual(['Count: 5']);
|
||||
expect(domNode.tagName).toEqual('SPAN');
|
||||
expect(domNode.textContent).toEqual('Count: 5');
|
||||
});
|
||||
});
|
||||
|
||||
describe('useImperativeMethods', () => {
|
||||
describe('useImperativeHandle', () => {
|
||||
it('should not be invoked on the server', async () => {
|
||||
function Counter(props, ref) {
|
||||
useImperativeMethods(ref, () => {
|
||||
useImperativeHandle(ref, () => {
|
||||
throw new Error('should not be invoked');
|
||||
});
|
||||
return <Text text={props.label + ': ' + ref.current} />;
|
||||
@@ -538,22 +601,6 @@ describe('ReactDOMServerHooks', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('useMutationEffect', () => {
|
||||
it('should warn when invoked during render', async () => {
|
||||
function Counter() {
|
||||
useMutationEffect(() => {
|
||||
throw new Error('should not be invoked');
|
||||
});
|
||||
|
||||
return <Text text="Count: 0" />;
|
||||
}
|
||||
const domNode = await serverRender(<Counter />, 1);
|
||||
expect(clearYields()).toEqual(['Count: 0']);
|
||||
expect(domNode.tagName).toEqual('SPAN');
|
||||
expect(domNode.textContent).toEqual('Count: 0');
|
||||
});
|
||||
});
|
||||
|
||||
describe('useLayoutEffect', () => {
|
||||
it('should warn when invoked during render', async () => {
|
||||
function Counter() {
|
||||
@@ -571,8 +618,119 @@ describe('ReactDOMServerHooks', () => {
|
||||
});
|
||||
|
||||
describe('useContext', () => {
|
||||
itThrowsWhenRendering(
|
||||
'if used inside a class component',
|
||||
async render => {
|
||||
const Context = React.createContext({}, () => {});
|
||||
class Counter extends React.Component {
|
||||
render() {
|
||||
let [count] = useContext(Context);
|
||||
return <Text text={count} />;
|
||||
}
|
||||
}
|
||||
|
||||
return render(<Counter />);
|
||||
},
|
||||
'Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for' +
|
||||
' one of the following reasons:\n' +
|
||||
'1. You might have mismatching versions of React and the renderer (such as React DOM)\n' +
|
||||
'2. You might be breaking the Rules of Hooks\n' +
|
||||
'3. You might have more than one copy of React in the same app\n' +
|
||||
'See https://fb.me/react-invalid-hook-call for tips about how to debug and fix this problem.',
|
||||
);
|
||||
});
|
||||
|
||||
itRenders(
|
||||
'can use the same context multiple times in the same function',
|
||||
async render => {
|
||||
const Context = React.createContext({foo: 0, bar: 0, baz: 0});
|
||||
|
||||
function Provider(props) {
|
||||
return (
|
||||
<Context.Provider
|
||||
value={{foo: props.foo, bar: props.bar, baz: props.baz}}>
|
||||
{props.children}
|
||||
</Context.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
function FooAndBar() {
|
||||
const {foo} = useContext(Context);
|
||||
const {bar} = useContext(Context);
|
||||
return <Text text={`Foo: ${foo}, Bar: ${bar}`} />;
|
||||
}
|
||||
|
||||
function Baz() {
|
||||
const {baz} = useContext(Context);
|
||||
return <Text text={'Baz: ' + baz} />;
|
||||
}
|
||||
|
||||
class Indirection extends React.Component {
|
||||
render() {
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
function App(props) {
|
||||
return (
|
||||
<div>
|
||||
<Provider foo={props.foo} bar={props.bar} baz={props.baz}>
|
||||
<Indirection>
|
||||
<Indirection>
|
||||
<FooAndBar />
|
||||
</Indirection>
|
||||
<Indirection>
|
||||
<Baz />
|
||||
</Indirection>
|
||||
</Indirection>
|
||||
</Provider>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const domNode = await render(<App foo={1} bar={3} baz={5} />);
|
||||
expect(clearYields()).toEqual(['Foo: 1, Bar: 3', 'Baz: 5']);
|
||||
expect(domNode.childNodes.length).toBe(2);
|
||||
expect(domNode.firstChild.tagName).toEqual('SPAN');
|
||||
expect(domNode.firstChild.textContent).toEqual('Foo: 1, Bar: 3');
|
||||
expect(domNode.lastChild.tagName).toEqual('SPAN');
|
||||
expect(domNode.lastChild.textContent).toEqual('Baz: 5');
|
||||
},
|
||||
);
|
||||
|
||||
itRenders('warns when bitmask is passed to useContext', async render => {
|
||||
let Context = React.createContext('Hi');
|
||||
|
||||
function Foo() {
|
||||
return <span>{useContext(Context, 1)}</span>;
|
||||
}
|
||||
|
||||
const domNode = await render(<Foo />, 1);
|
||||
expect(domNode.textContent).toBe('Hi');
|
||||
});
|
||||
|
||||
describe('useDebugValue', () => {
|
||||
itRenders('is a noop', async render => {
|
||||
function Counter(props) {
|
||||
const debugValue = useDebugValue(123);
|
||||
return <Text text={typeof debugValue} />;
|
||||
}
|
||||
|
||||
const domNode = await render(<Counter />);
|
||||
expect(domNode.textContent).toEqual('undefined');
|
||||
});
|
||||
});
|
||||
|
||||
describe('readContext', () => {
|
||||
function readContext(Context, observedBits) {
|
||||
const dispatcher =
|
||||
React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
|
||||
.ReactCurrentDispatcher.current;
|
||||
return dispatcher.readContext(Context, observedBits);
|
||||
}
|
||||
|
||||
itRenders(
|
||||
'can use the same context multiple times in the same function',
|
||||
'can read the same context multiple times in the same function',
|
||||
async render => {
|
||||
const Context = React.createContext(
|
||||
{foo: 0, bar: 0, baz: 0},
|
||||
@@ -601,13 +759,13 @@ describe('ReactDOMServerHooks', () => {
|
||||
}
|
||||
|
||||
function FooAndBar() {
|
||||
const {foo} = useContext(Context, 0b001);
|
||||
const {bar} = useContext(Context, 0b010);
|
||||
const {foo} = readContext(Context, 0b001);
|
||||
const {bar} = readContext(Context, 0b010);
|
||||
return <Text text={`Foo: ${foo}, Bar: ${bar}`} />;
|
||||
}
|
||||
|
||||
function Baz() {
|
||||
const {baz} = useContext(Context, 0b100);
|
||||
const {baz} = readContext(Context, 0b100);
|
||||
return <Text text={'Baz: ' + baz} />;
|
||||
}
|
||||
|
||||
@@ -647,20 +805,27 @@ describe('ReactDOMServerHooks', () => {
|
||||
},
|
||||
);
|
||||
|
||||
itThrowsWhenRendering(
|
||||
'if used inside a class component',
|
||||
async render => {
|
||||
const Context = React.createContext({}, () => {});
|
||||
class Counter extends React.Component {
|
||||
render() {
|
||||
let [count] = useContext(Context);
|
||||
return <Text text={count} />;
|
||||
}
|
||||
}
|
||||
itRenders('with a warning inside useMemo and useReducer', async render => {
|
||||
const Context = React.createContext(42);
|
||||
|
||||
return render(<Counter />);
|
||||
},
|
||||
'Hooks can only be called inside the body of a function component.',
|
||||
);
|
||||
function ReadInMemo(props) {
|
||||
let count = React.useMemo(() => readContext(Context), []);
|
||||
return <Text text={count} />;
|
||||
}
|
||||
|
||||
function ReadInReducer(props) {
|
||||
let [count, dispatch] = React.useReducer(() => readContext(Context));
|
||||
if (count !== 42) {
|
||||
dispatch();
|
||||
}
|
||||
return <Text text={count} />;
|
||||
}
|
||||
|
||||
const domNode1 = await render(<ReadInMemo />, 1);
|
||||
expect(domNode1.textContent).toEqual('42');
|
||||
|
||||
const domNode2 = await render(<ReadInReducer />, 1);
|
||||
expect(domNode2.textContent).toEqual('42');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -37,9 +37,9 @@ describe('ReactDOMServerIntegration', () => {
|
||||
});
|
||||
|
||||
describe('context', function() {
|
||||
let PurpleContext, RedContext, Consumer;
|
||||
let Context, PurpleContextProvider, RedContextProvider, Consumer;
|
||||
beforeEach(() => {
|
||||
let Context = React.createContext('none');
|
||||
Context = React.createContext('none');
|
||||
|
||||
class Parent extends React.Component {
|
||||
render() {
|
||||
@@ -51,8 +51,12 @@ describe('ReactDOMServerIntegration', () => {
|
||||
}
|
||||
}
|
||||
Consumer = Context.Consumer;
|
||||
PurpleContext = props => <Parent text="purple">{props.children}</Parent>;
|
||||
RedContext = props => <Parent text="red">{props.children}</Parent>;
|
||||
PurpleContextProvider = props => (
|
||||
<Parent text="purple">{props.children}</Parent>
|
||||
);
|
||||
RedContextProvider = props => (
|
||||
<Parent text="red">{props.children}</Parent>
|
||||
);
|
||||
});
|
||||
|
||||
itRenders('class child with context', async render => {
|
||||
@@ -67,9 +71,9 @@ describe('ReactDOMServerIntegration', () => {
|
||||
}
|
||||
|
||||
const e = await render(
|
||||
<PurpleContext>
|
||||
<PurpleContextProvider>
|
||||
<ClassChildWithContext />
|
||||
</PurpleContext>,
|
||||
</PurpleContextProvider>,
|
||||
);
|
||||
expect(e.textContent).toBe('purple');
|
||||
});
|
||||
@@ -80,9 +84,9 @@ describe('ReactDOMServerIntegration', () => {
|
||||
}
|
||||
|
||||
const e = await render(
|
||||
<PurpleContext>
|
||||
<PurpleContextProvider>
|
||||
<FunctionChildWithContext />
|
||||
</PurpleContext>,
|
||||
</PurpleContextProvider>,
|
||||
);
|
||||
expect(e.textContent).toBe('purple');
|
||||
});
|
||||
@@ -127,9 +131,9 @@ describe('ReactDOMServerIntegration', () => {
|
||||
const Child = props => <Grandchild />;
|
||||
|
||||
const e = await render(
|
||||
<PurpleContext>
|
||||
<PurpleContextProvider>
|
||||
<Child />
|
||||
</PurpleContext>,
|
||||
</PurpleContextProvider>,
|
||||
);
|
||||
expect(e.textContent).toBe('purple');
|
||||
});
|
||||
@@ -144,15 +148,54 @@ describe('ReactDOMServerIntegration', () => {
|
||||
};
|
||||
|
||||
const e = await render(
|
||||
<PurpleContext>
|
||||
<RedContext>
|
||||
<PurpleContextProvider>
|
||||
<RedContextProvider>
|
||||
<Grandchild />
|
||||
</RedContext>
|
||||
</PurpleContext>,
|
||||
</RedContextProvider>
|
||||
</PurpleContextProvider>,
|
||||
);
|
||||
expect(e.textContent).toBe('red');
|
||||
});
|
||||
|
||||
itRenders('readContext() in different components', async render => {
|
||||
function readContext(Ctx, observedBits) {
|
||||
const dispatcher =
|
||||
React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
|
||||
.ReactCurrentDispatcher.current;
|
||||
return dispatcher.readContext(Ctx, observedBits);
|
||||
}
|
||||
|
||||
class Cls extends React.Component {
|
||||
render() {
|
||||
return readContext(Context);
|
||||
}
|
||||
}
|
||||
function Fn() {
|
||||
return readContext(Context);
|
||||
}
|
||||
const Memo = React.memo(() => {
|
||||
return readContext(Context);
|
||||
});
|
||||
const FwdRef = React.forwardRef((props, ref) => {
|
||||
return readContext(Context);
|
||||
});
|
||||
|
||||
const e = await render(
|
||||
<PurpleContextProvider>
|
||||
<RedContextProvider>
|
||||
<span>
|
||||
<Fn />
|
||||
<Cls />
|
||||
<Memo />
|
||||
<FwdRef />
|
||||
<Consumer>{() => readContext(Context)}</Consumer>
|
||||
</span>
|
||||
</RedContextProvider>
|
||||
</PurpleContextProvider>,
|
||||
);
|
||||
expect(e.textContent).toBe('redredredredred');
|
||||
});
|
||||
|
||||
itRenders('multiple contexts', async render => {
|
||||
const Theme = React.createContext('dark');
|
||||
const Language = React.createContext('french');
|
||||
@@ -348,5 +391,189 @@ describe('ReactDOMServerIntegration', () => {
|
||||
await render(<App />, 1);
|
||||
},
|
||||
);
|
||||
|
||||
it('does not pollute parallel node streams', () => {
|
||||
const LoggedInUser = React.createContext();
|
||||
|
||||
const AppWithUser = user => (
|
||||
<LoggedInUser.Provider value={user}>
|
||||
<header>
|
||||
<LoggedInUser.Consumer>{whoAmI => whoAmI}</LoggedInUser.Consumer>
|
||||
</header>
|
||||
<footer>
|
||||
<LoggedInUser.Consumer>{whoAmI => whoAmI}</LoggedInUser.Consumer>
|
||||
</footer>
|
||||
</LoggedInUser.Provider>
|
||||
);
|
||||
|
||||
const streamAmy = ReactDOMServer.renderToNodeStream(
|
||||
AppWithUser('Amy'),
|
||||
).setEncoding('utf8');
|
||||
const streamBob = ReactDOMServer.renderToNodeStream(
|
||||
AppWithUser('Bob'),
|
||||
).setEncoding('utf8');
|
||||
|
||||
// Testing by filling the buffer using internal _read() with a small
|
||||
// number of bytes to avoid a test case which needs to align to a
|
||||
// highWaterMark boundary of 2^14 chars.
|
||||
streamAmy._read(20);
|
||||
streamBob._read(20);
|
||||
streamAmy._read(20);
|
||||
streamBob._read(20);
|
||||
|
||||
expect(streamAmy.read()).toBe('<header>Amy</header><footer>Amy</footer>');
|
||||
expect(streamBob.read()).toBe('<header>Bob</header><footer>Bob</footer>');
|
||||
});
|
||||
|
||||
it('does not pollute parallel node streams when many are used', () => {
|
||||
const CurrentIndex = React.createContext();
|
||||
|
||||
const NthRender = index => (
|
||||
<CurrentIndex.Provider value={index}>
|
||||
<header>
|
||||
<CurrentIndex.Consumer>{idx => idx}</CurrentIndex.Consumer>
|
||||
</header>
|
||||
<footer>
|
||||
<CurrentIndex.Consumer>{idx => idx}</CurrentIndex.Consumer>
|
||||
</footer>
|
||||
</CurrentIndex.Provider>
|
||||
);
|
||||
|
||||
let streams = [];
|
||||
|
||||
// Test with more than 32 streams to test that growing the thread count
|
||||
// works properly.
|
||||
let streamCount = 34;
|
||||
|
||||
for (let i = 0; i < streamCount; i++) {
|
||||
streams[i] = ReactDOMServer.renderToNodeStream(
|
||||
NthRender(i % 2 === 0 ? 'Expected to be recreated' : i),
|
||||
).setEncoding('utf8');
|
||||
}
|
||||
|
||||
// Testing by filling the buffer using internal _read() with a small
|
||||
// number of bytes to avoid a test case which needs to align to a
|
||||
// highWaterMark boundary of 2^14 chars.
|
||||
for (let i = 0; i < streamCount; i++) {
|
||||
streams[i]._read(20);
|
||||
}
|
||||
|
||||
// Early destroy every other stream
|
||||
for (let i = 0; i < streamCount; i += 2) {
|
||||
streams[i].destroy();
|
||||
}
|
||||
|
||||
// Recreate those same streams.
|
||||
for (let i = 0; i < streamCount; i += 2) {
|
||||
streams[i] = ReactDOMServer.renderToNodeStream(
|
||||
NthRender(i),
|
||||
).setEncoding('utf8');
|
||||
}
|
||||
|
||||
// Read a bit from all streams again.
|
||||
for (let i = 0; i < streamCount; i++) {
|
||||
streams[i]._read(20);
|
||||
}
|
||||
|
||||
// Assert that all stream rendered the expected output.
|
||||
for (let i = 0; i < streamCount; i++) {
|
||||
expect(streams[i].read()).toBe(
|
||||
'<header>' + i + '</header><footer>' + i + '</footer>',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// Regression test for https://github.com/facebook/react/issues/14705
|
||||
it('does not pollute later renders when stream destroyed', () => {
|
||||
const LoggedInUser = React.createContext('default');
|
||||
|
||||
const AppWithUser = user => (
|
||||
<LoggedInUser.Provider value={user}>
|
||||
<header>
|
||||
<LoggedInUser.Consumer>{whoAmI => whoAmI}</LoggedInUser.Consumer>
|
||||
</header>
|
||||
</LoggedInUser.Provider>
|
||||
);
|
||||
|
||||
const stream = ReactDOMServer.renderToNodeStream(
|
||||
AppWithUser('Amy'),
|
||||
).setEncoding('utf8');
|
||||
|
||||
// This is an implementation detail because we test a memory leak
|
||||
const {threadID} = stream.partialRenderer;
|
||||
|
||||
// Read enough to render Provider but not enough for it to be exited
|
||||
stream._read(10);
|
||||
expect(LoggedInUser[threadID]).toBe('Amy');
|
||||
|
||||
stream.destroy();
|
||||
|
||||
const AppWithUserNoProvider = () => (
|
||||
<LoggedInUser.Consumer>{whoAmI => whoAmI}</LoggedInUser.Consumer>
|
||||
);
|
||||
|
||||
const stream2 = ReactDOMServer.renderToNodeStream(
|
||||
AppWithUserNoProvider(),
|
||||
).setEncoding('utf8');
|
||||
|
||||
// Sanity check to ensure 2nd render has same threadID as 1st render,
|
||||
// otherwise this test is not testing what it's meant to
|
||||
expect(stream2.partialRenderer.threadID).toBe(threadID);
|
||||
|
||||
const markup = stream2.read(Infinity);
|
||||
|
||||
expect(markup).toBe('default');
|
||||
});
|
||||
|
||||
// Regression test for https://github.com/facebook/react/issues/14705
|
||||
it('frees context value reference when stream destroyed', () => {
|
||||
const LoggedInUser = React.createContext('default');
|
||||
|
||||
const AppWithUser = user => (
|
||||
<LoggedInUser.Provider value={user}>
|
||||
<header>
|
||||
<LoggedInUser.Consumer>{whoAmI => whoAmI}</LoggedInUser.Consumer>
|
||||
</header>
|
||||
</LoggedInUser.Provider>
|
||||
);
|
||||
|
||||
const stream = ReactDOMServer.renderToNodeStream(
|
||||
AppWithUser('Amy'),
|
||||
).setEncoding('utf8');
|
||||
|
||||
// This is an implementation detail because we test a memory leak
|
||||
const {threadID} = stream.partialRenderer;
|
||||
|
||||
// Read enough to render Provider but not enough for it to be exited
|
||||
stream._read(10);
|
||||
expect(LoggedInUser[threadID]).toBe('Amy');
|
||||
|
||||
stream.destroy();
|
||||
expect(LoggedInUser[threadID]).toBe('default');
|
||||
});
|
||||
|
||||
it('does not pollute sync renders after an error', () => {
|
||||
const LoggedInUser = React.createContext('default');
|
||||
const Crash = () => {
|
||||
throw new Error('Boo!');
|
||||
};
|
||||
const AppWithUser = user => (
|
||||
<LoggedInUser.Provider value={user}>
|
||||
<LoggedInUser.Consumer>{whoAmI => whoAmI}</LoggedInUser.Consumer>
|
||||
<Crash />
|
||||
</LoggedInUser.Provider>
|
||||
);
|
||||
|
||||
expect(() => {
|
||||
ReactDOMServer.renderToString(AppWithUser('Casper'));
|
||||
}).toThrow('Boo');
|
||||
|
||||
// Should not report a value from failed render
|
||||
expect(
|
||||
ReactDOMServer.renderToString(
|
||||
<LoggedInUser.Consumer>{whoAmI => whoAmI}</LoggedInUser.Consumer>,
|
||||
),
|
||||
).toBe('default');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,637 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @emails react-core
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
let React;
|
||||
let ReactDOM;
|
||||
let ReactDOMServer;
|
||||
let ReactFeatureFlags;
|
||||
let Suspense;
|
||||
let act;
|
||||
|
||||
describe('ReactDOMServerPartialHydration', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModuleRegistry();
|
||||
|
||||
ReactFeatureFlags = require('shared/ReactFeatureFlags');
|
||||
ReactFeatureFlags.enableSuspenseServerRenderer = true;
|
||||
|
||||
React = require('react');
|
||||
ReactDOM = require('react-dom');
|
||||
act = require('react-dom/test-utils').act;
|
||||
ReactDOMServer = require('react-dom/server');
|
||||
Suspense = React.Suspense;
|
||||
});
|
||||
|
||||
it('hydrates a parent even if a child Suspense boundary is blocked', async () => {
|
||||
let suspend = false;
|
||||
let resolve;
|
||||
let promise = new Promise(resolvePromise => (resolve = resolvePromise));
|
||||
let ref = React.createRef();
|
||||
|
||||
function Child() {
|
||||
if (suspend) {
|
||||
throw promise;
|
||||
} else {
|
||||
return 'Hello';
|
||||
}
|
||||
}
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<div>
|
||||
<Suspense fallback="Loading...">
|
||||
<span ref={ref}>
|
||||
<Child />
|
||||
</span>
|
||||
</Suspense>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// First we render the final HTML. With the streaming renderer
|
||||
// this may have suspense points on the server but here we want
|
||||
// to test the completed HTML. Don't suspend on the server.
|
||||
suspend = false;
|
||||
let finalHTML = ReactDOMServer.renderToString(<App />);
|
||||
|
||||
let container = document.createElement('div');
|
||||
container.innerHTML = finalHTML;
|
||||
|
||||
let span = container.getElementsByTagName('span')[0];
|
||||
|
||||
// On the client we don't have all data yet but we want to start
|
||||
// hydrating anyway.
|
||||
suspend = true;
|
||||
let root = ReactDOM.unstable_createRoot(container, {hydrate: true});
|
||||
root.render(<App />);
|
||||
jest.runAllTimers();
|
||||
|
||||
expect(ref.current).toBe(null);
|
||||
|
||||
// Resolving the promise should continue hydration
|
||||
suspend = false;
|
||||
resolve();
|
||||
await promise;
|
||||
jest.runAllTimers();
|
||||
|
||||
// We should now have hydrated with a ref on the existing span.
|
||||
expect(ref.current).toBe(span);
|
||||
});
|
||||
|
||||
it('can insert siblings before the dehydrated boundary', () => {
|
||||
let suspend = false;
|
||||
let promise = new Promise(() => {});
|
||||
let showSibling;
|
||||
|
||||
function Child() {
|
||||
if (suspend) {
|
||||
throw promise;
|
||||
} else {
|
||||
return 'Second';
|
||||
}
|
||||
}
|
||||
|
||||
function Sibling() {
|
||||
let [visible, setVisibilty] = React.useState(false);
|
||||
showSibling = () => setVisibilty(true);
|
||||
if (visible) {
|
||||
return <div>First</div>;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<div>
|
||||
<Sibling />
|
||||
<Suspense fallback="Loading...">
|
||||
<span>
|
||||
<Child />
|
||||
</span>
|
||||
</Suspense>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
suspend = false;
|
||||
let finalHTML = ReactDOMServer.renderToString(<App />);
|
||||
let container = document.createElement('div');
|
||||
container.innerHTML = finalHTML;
|
||||
|
||||
// On the client we don't have all data yet but we want to start
|
||||
// hydrating anyway.
|
||||
suspend = true;
|
||||
|
||||
act(() => {
|
||||
ReactDOM.hydrate(<App />, container);
|
||||
});
|
||||
|
||||
expect(container.firstChild.firstChild.tagName).not.toBe('DIV');
|
||||
|
||||
// In this state, we can still update the siblings.
|
||||
act(() => showSibling());
|
||||
|
||||
expect(container.firstChild.firstChild.tagName).toBe('DIV');
|
||||
expect(container.firstChild.firstChild.textContent).toBe('First');
|
||||
});
|
||||
|
||||
it('can delete the dehydrated boundary before it is hydrated', () => {
|
||||
let suspend = false;
|
||||
let promise = new Promise(() => {});
|
||||
let hideMiddle;
|
||||
|
||||
function Child() {
|
||||
if (suspend) {
|
||||
throw promise;
|
||||
} else {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div>Middle</div>
|
||||
Some text
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function App() {
|
||||
let [visible, setVisibilty] = React.useState(true);
|
||||
hideMiddle = () => setVisibilty(false);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div>Before</div>
|
||||
{visible ? (
|
||||
<Suspense fallback="Loading...">
|
||||
<Child />
|
||||
</Suspense>
|
||||
) : null}
|
||||
<div>After</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
suspend = false;
|
||||
let finalHTML = ReactDOMServer.renderToString(<App />);
|
||||
let container = document.createElement('div');
|
||||
container.innerHTML = finalHTML;
|
||||
|
||||
// On the client we don't have all data yet but we want to start
|
||||
// hydrating anyway.
|
||||
suspend = true;
|
||||
act(() => {
|
||||
ReactDOM.hydrate(<App />, container);
|
||||
});
|
||||
|
||||
expect(container.firstChild.children[1].textContent).toBe('Middle');
|
||||
|
||||
// In this state, we can still delete the boundary.
|
||||
act(() => hideMiddle());
|
||||
|
||||
expect(container.firstChild.children[1].textContent).toBe('After');
|
||||
});
|
||||
|
||||
it('regenerates the content if props have changed before hydration completes', async () => {
|
||||
let suspend = false;
|
||||
let resolve;
|
||||
let promise = new Promise(resolvePromise => (resolve = resolvePromise));
|
||||
let ref = React.createRef();
|
||||
|
||||
function Child({text}) {
|
||||
if (suspend) {
|
||||
throw promise;
|
||||
} else {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
function App({text, className}) {
|
||||
return (
|
||||
<div>
|
||||
<Suspense fallback="Loading...">
|
||||
<span ref={ref} className={className}>
|
||||
<Child text={text} />
|
||||
</span>
|
||||
</Suspense>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
suspend = false;
|
||||
let finalHTML = ReactDOMServer.renderToString(
|
||||
<App text="Hello" className="hello" />,
|
||||
);
|
||||
let container = document.createElement('div');
|
||||
container.innerHTML = finalHTML;
|
||||
|
||||
let span = container.getElementsByTagName('span')[0];
|
||||
|
||||
// On the client we don't have all data yet but we want to start
|
||||
// hydrating anyway.
|
||||
suspend = true;
|
||||
let root = ReactDOM.unstable_createRoot(container, {hydrate: true});
|
||||
root.render(<App text="Hello" className="hello" />);
|
||||
jest.runAllTimers();
|
||||
|
||||
expect(ref.current).toBe(null);
|
||||
expect(span.textContent).toBe('Hello');
|
||||
|
||||
// Render an update, which will be higher or the same priority as pinging the hydration.
|
||||
root.render(<App text="Hi" className="hi" />);
|
||||
|
||||
// At the same time, resolving the promise so that rendering can complete.
|
||||
suspend = false;
|
||||
resolve();
|
||||
await promise;
|
||||
|
||||
// Flushing both of these in the same batch won't be able to hydrate so we'll
|
||||
// probably throw away the existing subtree.
|
||||
jest.runAllTimers();
|
||||
|
||||
// Pick up the new span. In an ideal implementation this might be the same span
|
||||
// but patched up. At the time of writing, this will be a new span though.
|
||||
span = container.getElementsByTagName('span')[0];
|
||||
|
||||
// We should now have fully rendered with a ref on the new span.
|
||||
expect(ref.current).toBe(span);
|
||||
expect(span.textContent).toBe('Hi');
|
||||
// If we ended up hydrating the existing content, we won't have properly
|
||||
// patched up the tree, which might mean we haven't patched the className.
|
||||
expect(span.className).toBe('hi');
|
||||
});
|
||||
|
||||
it('shows the fallback if props have changed before hydration completes and is still suspended', async () => {
|
||||
let suspend = false;
|
||||
let resolve;
|
||||
let promise = new Promise(resolvePromise => (resolve = resolvePromise));
|
||||
let ref = React.createRef();
|
||||
|
||||
function Child({text}) {
|
||||
if (suspend) {
|
||||
throw promise;
|
||||
} else {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
function App({text, className}) {
|
||||
return (
|
||||
<div>
|
||||
<Suspense fallback="Loading...">
|
||||
<span ref={ref} className={className}>
|
||||
<Child text={text} />
|
||||
</span>
|
||||
</Suspense>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
suspend = false;
|
||||
let finalHTML = ReactDOMServer.renderToString(
|
||||
<App text="Hello" className="hello" />,
|
||||
);
|
||||
let container = document.createElement('div');
|
||||
container.innerHTML = finalHTML;
|
||||
|
||||
// On the client we don't have all data yet but we want to start
|
||||
// hydrating anyway.
|
||||
suspend = true;
|
||||
let root = ReactDOM.unstable_createRoot(container, {hydrate: true});
|
||||
root.render(<App text="Hello" className="hello" />);
|
||||
jest.runAllTimers();
|
||||
|
||||
expect(ref.current).toBe(null);
|
||||
|
||||
// Render an update, but leave it still suspended.
|
||||
root.render(<App text="Hi" className="hi" />);
|
||||
|
||||
// Flushing now should delete the existing content and show the fallback.
|
||||
jest.runAllTimers();
|
||||
|
||||
expect(container.getElementsByTagName('span').length).toBe(0);
|
||||
expect(ref.current).toBe(null);
|
||||
expect(container.textContent).toBe('Loading...');
|
||||
|
||||
// Unsuspending shows the content.
|
||||
suspend = false;
|
||||
resolve();
|
||||
await promise;
|
||||
|
||||
jest.runAllTimers();
|
||||
|
||||
let span = container.getElementsByTagName('span')[0];
|
||||
expect(span.textContent).toBe('Hi');
|
||||
expect(span.className).toBe('hi');
|
||||
expect(ref.current).toBe(span);
|
||||
expect(container.textContent).toBe('Hi');
|
||||
});
|
||||
|
||||
it('shows the fallback of the outer if fallback is missing', async () => {
|
||||
// This is the same exact test as above but with a nested Suspense without a fallback.
|
||||
// This should be a noop.
|
||||
let suspend = false;
|
||||
let resolve;
|
||||
let promise = new Promise(resolvePromise => (resolve = resolvePromise));
|
||||
let ref = React.createRef();
|
||||
|
||||
function Child({text}) {
|
||||
if (suspend) {
|
||||
throw promise;
|
||||
} else {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
function App({text, className}) {
|
||||
return (
|
||||
<div>
|
||||
<Suspense fallback="Loading...">
|
||||
<span ref={ref} className={className}>
|
||||
<Suspense maxDuration={200}>
|
||||
<Child text={text} />
|
||||
</Suspense>
|
||||
</span>
|
||||
</Suspense>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
suspend = false;
|
||||
let finalHTML = ReactDOMServer.renderToString(
|
||||
<App text="Hello" className="hello" />,
|
||||
);
|
||||
let container = document.createElement('div');
|
||||
container.innerHTML = finalHTML;
|
||||
|
||||
// On the client we don't have all data yet but we want to start
|
||||
// hydrating anyway.
|
||||
suspend = true;
|
||||
let root = ReactDOM.unstable_createRoot(container, {hydrate: true});
|
||||
root.render(<App text="Hello" className="hello" />);
|
||||
jest.runAllTimers();
|
||||
|
||||
expect(ref.current).toBe(null);
|
||||
|
||||
// Render an update, but leave it still suspended.
|
||||
root.render(<App text="Hi" className="hi" />);
|
||||
|
||||
// Flushing now should delete the existing content and show the fallback.
|
||||
jest.runAllTimers();
|
||||
|
||||
expect(container.getElementsByTagName('span').length).toBe(0);
|
||||
expect(ref.current).toBe(null);
|
||||
expect(container.textContent).toBe('Loading...');
|
||||
|
||||
// Unsuspending shows the content.
|
||||
suspend = false;
|
||||
resolve();
|
||||
await promise;
|
||||
|
||||
jest.runAllTimers();
|
||||
|
||||
let span = container.getElementsByTagName('span')[0];
|
||||
expect(span.textContent).toBe('Hi');
|
||||
expect(span.className).toBe('hi');
|
||||
expect(ref.current).toBe(span);
|
||||
expect(container.textContent).toBe('Hi');
|
||||
});
|
||||
|
||||
it('clears nested suspense boundaries if they did not hydrate yet', async () => {
|
||||
let suspend = false;
|
||||
let resolve;
|
||||
let promise = new Promise(resolvePromise => (resolve = resolvePromise));
|
||||
let ref = React.createRef();
|
||||
|
||||
function Child({text}) {
|
||||
if (suspend) {
|
||||
throw promise;
|
||||
} else {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
function App({text, className}) {
|
||||
return (
|
||||
<div>
|
||||
<Suspense fallback="Loading...">
|
||||
<Suspense fallback="Never happens">
|
||||
<Child text={text} />
|
||||
</Suspense>{' '}
|
||||
<span ref={ref} className={className}>
|
||||
<Child text={text} />
|
||||
</span>
|
||||
</Suspense>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
suspend = false;
|
||||
let finalHTML = ReactDOMServer.renderToString(
|
||||
<App text="Hello" className="hello" />,
|
||||
);
|
||||
let container = document.createElement('div');
|
||||
container.innerHTML = finalHTML;
|
||||
|
||||
// On the client we don't have all data yet but we want to start
|
||||
// hydrating anyway.
|
||||
suspend = true;
|
||||
let root = ReactDOM.unstable_createRoot(container, {hydrate: true});
|
||||
root.render(<App text="Hello" className="hello" />);
|
||||
jest.runAllTimers();
|
||||
|
||||
expect(ref.current).toBe(null);
|
||||
|
||||
// Render an update, but leave it still suspended.
|
||||
root.render(<App text="Hi" className="hi" />);
|
||||
|
||||
// Flushing now should delete the existing content and show the fallback.
|
||||
jest.runAllTimers();
|
||||
|
||||
expect(container.getElementsByTagName('span').length).toBe(0);
|
||||
expect(ref.current).toBe(null);
|
||||
expect(container.textContent).toBe('Loading...');
|
||||
|
||||
// Unsuspending shows the content.
|
||||
suspend = false;
|
||||
resolve();
|
||||
await promise;
|
||||
|
||||
jest.runAllTimers();
|
||||
|
||||
let span = container.getElementsByTagName('span')[0];
|
||||
expect(span.textContent).toBe('Hi');
|
||||
expect(span.className).toBe('hi');
|
||||
expect(ref.current).toBe(span);
|
||||
expect(container.textContent).toBe('Hi Hi');
|
||||
});
|
||||
|
||||
it('regenerates the content if context has changed before hydration completes', async () => {
|
||||
let suspend = false;
|
||||
let resolve;
|
||||
let promise = new Promise(resolvePromise => (resolve = resolvePromise));
|
||||
let ref = React.createRef();
|
||||
let Context = React.createContext(null);
|
||||
|
||||
function Child() {
|
||||
let {text, className} = React.useContext(Context);
|
||||
if (suspend) {
|
||||
throw promise;
|
||||
} else {
|
||||
return (
|
||||
<span ref={ref} className={className}>
|
||||
{text}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const App = React.memo(function App() {
|
||||
return (
|
||||
<div>
|
||||
<Suspense fallback="Loading...">
|
||||
<Child />
|
||||
</Suspense>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
suspend = false;
|
||||
let finalHTML = ReactDOMServer.renderToString(
|
||||
<Context.Provider value={{text: 'Hello', className: 'hello'}}>
|
||||
<App />
|
||||
</Context.Provider>,
|
||||
);
|
||||
let container = document.createElement('div');
|
||||
container.innerHTML = finalHTML;
|
||||
|
||||
let span = container.getElementsByTagName('span')[0];
|
||||
|
||||
// On the client we don't have all data yet but we want to start
|
||||
// hydrating anyway.
|
||||
suspend = true;
|
||||
let root = ReactDOM.unstable_createRoot(container, {hydrate: true});
|
||||
root.render(
|
||||
<Context.Provider value={{text: 'Hello', className: 'hello'}}>
|
||||
<App />
|
||||
</Context.Provider>,
|
||||
);
|
||||
jest.runAllTimers();
|
||||
|
||||
expect(ref.current).toBe(null);
|
||||
expect(span.textContent).toBe('Hello');
|
||||
|
||||
// Render an update, which will be higher or the same priority as pinging the hydration.
|
||||
root.render(
|
||||
<Context.Provider value={{text: 'Hi', className: 'hi'}}>
|
||||
<App />
|
||||
</Context.Provider>,
|
||||
);
|
||||
|
||||
// At the same time, resolving the promise so that rendering can complete.
|
||||
suspend = false;
|
||||
resolve();
|
||||
await promise;
|
||||
|
||||
// Flushing both of these in the same batch won't be able to hydrate so we'll
|
||||
// probably throw away the existing subtree.
|
||||
jest.runAllTimers();
|
||||
|
||||
// Pick up the new span. In an ideal implementation this might be the same span
|
||||
// but patched up. At the time of writing, this will be a new span though.
|
||||
span = container.getElementsByTagName('span')[0];
|
||||
|
||||
// We should now have fully rendered with a ref on the new span.
|
||||
expect(ref.current).toBe(span);
|
||||
expect(span.textContent).toBe('Hi');
|
||||
// If we ended up hydrating the existing content, we won't have properly
|
||||
// patched up the tree, which might mean we haven't patched the className.
|
||||
expect(span.className).toBe('hi');
|
||||
});
|
||||
|
||||
it('shows the fallback if context has changed before hydration completes and is still suspended', async () => {
|
||||
let suspend = false;
|
||||
let resolve;
|
||||
let promise = new Promise(resolvePromise => (resolve = resolvePromise));
|
||||
let ref = React.createRef();
|
||||
let Context = React.createContext(null);
|
||||
|
||||
function Child() {
|
||||
let {text, className} = React.useContext(Context);
|
||||
if (suspend) {
|
||||
throw promise;
|
||||
} else {
|
||||
return (
|
||||
<span ref={ref} className={className}>
|
||||
{text}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const App = React.memo(function App() {
|
||||
return (
|
||||
<div>
|
||||
<Suspense fallback="Loading...">
|
||||
<Child />
|
||||
</Suspense>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
suspend = false;
|
||||
let finalHTML = ReactDOMServer.renderToString(
|
||||
<Context.Provider value={{text: 'Hello', className: 'hello'}}>
|
||||
<App />
|
||||
</Context.Provider>,
|
||||
);
|
||||
let container = document.createElement('div');
|
||||
container.innerHTML = finalHTML;
|
||||
|
||||
// On the client we don't have all data yet but we want to start
|
||||
// hydrating anyway.
|
||||
suspend = true;
|
||||
let root = ReactDOM.unstable_createRoot(container, {hydrate: true});
|
||||
root.render(
|
||||
<Context.Provider value={{text: 'Hello', className: 'hello'}}>
|
||||
<App />
|
||||
</Context.Provider>,
|
||||
);
|
||||
jest.runAllTimers();
|
||||
|
||||
expect(ref.current).toBe(null);
|
||||
|
||||
// Render an update, but leave it still suspended.
|
||||
root.render(
|
||||
<Context.Provider value={{text: 'Hi', className: 'hi'}}>
|
||||
<App />
|
||||
</Context.Provider>,
|
||||
);
|
||||
|
||||
// Flushing now should delete the existing content and show the fallback.
|
||||
jest.runAllTimers();
|
||||
|
||||
expect(container.getElementsByTagName('span').length).toBe(0);
|
||||
expect(ref.current).toBe(null);
|
||||
expect(container.textContent).toBe('Loading...');
|
||||
|
||||
// Unsuspending shows the content.
|
||||
suspend = false;
|
||||
resolve();
|
||||
await promise;
|
||||
|
||||
jest.runAllTimers();
|
||||
|
||||
let span = container.getElementsByTagName('span')[0];
|
||||
expect(span.textContent).toBe('Hi');
|
||||
expect(span.className).toBe('hi');
|
||||
expect(ref.current).toBe(span);
|
||||
expect(container.textContent).toBe('Hi');
|
||||
});
|
||||
});
|
||||
@@ -52,38 +52,47 @@ describe('ReactDOMServerSuspense', () => {
|
||||
}
|
||||
|
||||
it('should render the children when no promise is thrown', async () => {
|
||||
const e = await serverRender(
|
||||
<React.Suspense fallback={<Text text="Fallback" />}>
|
||||
<Text text="Children" />
|
||||
</React.Suspense>,
|
||||
const c = await serverRender(
|
||||
<div>
|
||||
<React.Suspense fallback={<Text text="Fallback" />}>
|
||||
<Text text="Children" />
|
||||
</React.Suspense>
|
||||
</div>,
|
||||
);
|
||||
const e = c.children[0];
|
||||
|
||||
expect(e.tagName).toBe('DIV');
|
||||
expect(e.textContent).toBe('Children');
|
||||
});
|
||||
|
||||
it('should render the fallback when a promise thrown', async () => {
|
||||
const e = await serverRender(
|
||||
<React.Suspense fallback={<Text text="Fallback" />}>
|
||||
<AsyncText text="Children" />
|
||||
</React.Suspense>,
|
||||
const c = await serverRender(
|
||||
<div>
|
||||
<React.Suspense fallback={<Text text="Fallback" />}>
|
||||
<AsyncText text="Children" />
|
||||
</React.Suspense>
|
||||
</div>,
|
||||
);
|
||||
const e = c.children[0];
|
||||
|
||||
expect(e.tagName).toBe('DIV');
|
||||
expect(e.textContent).toBe('Fallback');
|
||||
});
|
||||
|
||||
it('should work with nested suspense components', async () => {
|
||||
const e = await serverRender(
|
||||
<React.Suspense fallback={<Text text="Fallback" />}>
|
||||
<div>
|
||||
<Text text="Children" />
|
||||
<React.Suspense fallback={<Text text="Fallback" />}>
|
||||
<AsyncText text="Children" />
|
||||
</React.Suspense>
|
||||
</div>
|
||||
</React.Suspense>,
|
||||
const c = await serverRender(
|
||||
<div>
|
||||
<React.Suspense fallback={<Text text="Fallback" />}>
|
||||
<div>
|
||||
<Text text="Children" />
|
||||
<React.Suspense fallback={<Text text="Fallback" />}>
|
||||
<AsyncText text="Children" />
|
||||
</React.Suspense>
|
||||
</div>
|
||||
</React.Suspense>
|
||||
</div>,
|
||||
);
|
||||
const e = c.children[0];
|
||||
|
||||
expect(e.innerHTML).toBe('<div>Children</div><div>Fallback</div>');
|
||||
});
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @emails react-core
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
describe('ReactDOMShorthandCSSPropertyCollision', () => {
|
||||
let ReactFeatureFlags;
|
||||
let React;
|
||||
let ReactDOM;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
ReactFeatureFlags = require('shared/ReactFeatureFlags');
|
||||
ReactFeatureFlags.warnAboutShorthandPropertyCollision = true;
|
||||
React = require('react');
|
||||
ReactDOM = require('react-dom');
|
||||
});
|
||||
|
||||
it('should warn for conflicting CSS shorthand updates', () => {
|
||||
const container = document.createElement('div');
|
||||
ReactDOM.render(<div style={{font: 'foo', fontStyle: 'bar'}} />, container);
|
||||
expect(() =>
|
||||
ReactDOM.render(<div style={{font: 'foo'}} />, container),
|
||||
).toWarnDev(
|
||||
'Warning: Removing a style property during rerender (fontStyle) ' +
|
||||
'when a conflicting property is set (font) can lead to styling ' +
|
||||
"bugs. To avoid this, don't mix shorthand and non-shorthand " +
|
||||
'properties for the same value; instead, replace the shorthand ' +
|
||||
'with separate values.' +
|
||||
'\n in div (at **)',
|
||||
);
|
||||
|
||||
// These updates are OK and don't warn:
|
||||
ReactDOM.render(<div style={{font: 'qux', fontStyle: 'bar'}} />, container);
|
||||
ReactDOM.render(<div style={{font: 'foo', fontStyle: 'baz'}} />, container);
|
||||
|
||||
expect(() =>
|
||||
ReactDOM.render(
|
||||
<div style={{font: 'qux', fontStyle: 'baz'}} />,
|
||||
container,
|
||||
),
|
||||
).toWarnDev(
|
||||
'Warning: Updating a style property during rerender (font) when ' +
|
||||
'a conflicting property is set (fontStyle) can lead to styling ' +
|
||||
"bugs. To avoid this, don't mix shorthand and non-shorthand " +
|
||||
'properties for the same value; instead, replace the shorthand ' +
|
||||
'with separate values.' +
|
||||
'\n in div (at **)',
|
||||
);
|
||||
expect(() =>
|
||||
ReactDOM.render(<div style={{fontStyle: 'baz'}} />, container),
|
||||
).toWarnDev(
|
||||
'Warning: Removing a style property during rerender (font) when ' +
|
||||
'a conflicting property is set (fontStyle) can lead to styling ' +
|
||||
"bugs. To avoid this, don't mix shorthand and non-shorthand " +
|
||||
'properties for the same value; instead, replace the shorthand ' +
|
||||
'with separate values.' +
|
||||
'\n in div (at **)',
|
||||
);
|
||||
|
||||
// A bit of a special case: backgroundPosition isn't technically longhand
|
||||
// (it expands to backgroundPosition{X,Y} but so does background)
|
||||
ReactDOM.render(
|
||||
<div style={{background: 'yellow', backgroundPosition: 'center'}} />,
|
||||
container,
|
||||
);
|
||||
expect(() =>
|
||||
ReactDOM.render(<div style={{background: 'yellow'}} />, container),
|
||||
).toWarnDev(
|
||||
'Warning: Removing a style property during rerender ' +
|
||||
'(backgroundPosition) when a conflicting property is set ' +
|
||||
"(background) can lead to styling bugs. To avoid this, don't mix " +
|
||||
'shorthand and non-shorthand properties for the same value; ' +
|
||||
'instead, replace the shorthand with separate values.' +
|
||||
'\n in div (at **)',
|
||||
);
|
||||
ReactDOM.render(
|
||||
<div style={{background: 'yellow', backgroundPosition: 'center'}} />,
|
||||
container,
|
||||
);
|
||||
// But setting them at the same time is OK:
|
||||
ReactDOM.render(
|
||||
<div style={{background: 'green', backgroundPosition: 'top'}} />,
|
||||
container,
|
||||
);
|
||||
expect(() =>
|
||||
ReactDOM.render(<div style={{backgroundPosition: 'top'}} />, container),
|
||||
).toWarnDev(
|
||||
'Warning: Removing a style property during rerender (background) ' +
|
||||
'when a conflicting property is set (backgroundPosition) can lead ' +
|
||||
"to styling bugs. To avoid this, don't mix shorthand and " +
|
||||
'non-shorthand properties for the same value; instead, replace the ' +
|
||||
'shorthand with separate values.' +
|
||||
'\n in div (at **)',
|
||||
);
|
||||
|
||||
// A bit of an even more special case: borderLeft and borderStyle overlap.
|
||||
ReactDOM.render(
|
||||
<div style={{borderStyle: 'dotted', borderLeft: '1px solid red'}} />,
|
||||
container,
|
||||
);
|
||||
expect(() =>
|
||||
ReactDOM.render(<div style={{borderLeft: '1px solid red'}} />, container),
|
||||
).toWarnDev(
|
||||
'Warning: Removing a style property during rerender (borderStyle) ' +
|
||||
'when a conflicting property is set (borderLeft) can lead to ' +
|
||||
"styling bugs. To avoid this, don't mix shorthand and " +
|
||||
'non-shorthand properties for the same value; instead, replace the ' +
|
||||
'shorthand with separate values.' +
|
||||
'\n in div (at **)',
|
||||
);
|
||||
expect(() =>
|
||||
ReactDOM.render(
|
||||
<div style={{borderStyle: 'dashed', borderLeft: '1px solid red'}} />,
|
||||
container,
|
||||
),
|
||||
).toWarnDev(
|
||||
'Warning: Updating a style property during rerender (borderStyle) ' +
|
||||
'when a conflicting property is set (borderLeft) can lead to ' +
|
||||
"styling bugs. To avoid this, don't mix shorthand and " +
|
||||
'non-shorthand properties for the same value; instead, replace the ' +
|
||||
'shorthand with separate values.' +
|
||||
'\n in div (at **)',
|
||||
);
|
||||
// But setting them at the same time is OK:
|
||||
ReactDOM.render(
|
||||
<div style={{borderStyle: 'dotted', borderLeft: '2px solid red'}} />,
|
||||
container,
|
||||
);
|
||||
expect(() =>
|
||||
ReactDOM.render(<div style={{borderStyle: 'dotted'}} />, container),
|
||||
).toWarnDev(
|
||||
'Warning: Removing a style property during rerender (borderLeft) ' +
|
||||
'when a conflicting property is set (borderStyle) can lead to ' +
|
||||
"styling bugs. To avoid this, don't mix shorthand and " +
|
||||
'non-shorthand properties for the same value; instead, replace the ' +
|
||||
'shorthand with separate values.' +
|
||||
'\n in div (at **)',
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -9,25 +9,27 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
let ReactFeatureFlags;
|
||||
let React;
|
||||
let ReactDOM;
|
||||
let Suspense;
|
||||
let ReactCache;
|
||||
let ReactTestUtils;
|
||||
let TextResource;
|
||||
let act;
|
||||
|
||||
describe('ReactDOMSuspensePlaceholder', () => {
|
||||
let container;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
ReactFeatureFlags = require('shared/ReactFeatureFlags');
|
||||
ReactFeatureFlags.enableHooks = true;
|
||||
React = require('react');
|
||||
ReactDOM = require('react-dom');
|
||||
ReactCache = require('react-cache');
|
||||
ReactTestUtils = require('react-dom/test-utils');
|
||||
act = ReactTestUtils.act;
|
||||
Suspense = React.Suspense;
|
||||
container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
|
||||
TextResource = ReactCache.unstable_createResource(([text, ms = 0]) => {
|
||||
return new Promise((resolve, reject) =>
|
||||
@@ -38,6 +40,10 @@ describe('ReactDOMSuspensePlaceholder', () => {
|
||||
}, ([text, ms]) => text);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
document.body.removeChild(container);
|
||||
});
|
||||
|
||||
function advanceTimers(ms) {
|
||||
// Note: This advances Jest's virtual time but not React's. Use
|
||||
// ReactNoop.expire for that.
|
||||
@@ -140,12 +146,14 @@ describe('ReactDOMSuspensePlaceholder', () => {
|
||||
);
|
||||
}
|
||||
|
||||
ReactDOM.render(<App />, container);
|
||||
act(() => {
|
||||
ReactDOM.render(<App />, container);
|
||||
});
|
||||
expect(container.innerHTML).toEqual(
|
||||
'<span style="display: none;">Sibling</span><span style="display: none;"></span>Loading...',
|
||||
);
|
||||
|
||||
setIsVisible(true);
|
||||
act(() => setIsVisible(true));
|
||||
expect(container.innerHTML).toEqual(
|
||||
'<span style="display: none;">Sibling</span><span style="display: none;"></span>Loading...',
|
||||
);
|
||||
@@ -157,4 +165,64 @@ describe('ReactDOMSuspensePlaceholder', () => {
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
// Regression test for https://github.com/facebook/react/issues/14188
|
||||
it('can call findDOMNode() in a suspended component commit phase', async () => {
|
||||
const log = [];
|
||||
const Lazy = React.lazy(
|
||||
() =>
|
||||
new Promise(resolve =>
|
||||
resolve({
|
||||
default() {
|
||||
return 'lazy';
|
||||
},
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
class Child extends React.Component {
|
||||
componentDidMount() {
|
||||
log.push('cDM ' + this.props.id);
|
||||
ReactDOM.findDOMNode(this);
|
||||
}
|
||||
componentDidUpdate() {
|
||||
log.push('cDU ' + this.props.id);
|
||||
ReactDOM.findDOMNode(this);
|
||||
}
|
||||
render() {
|
||||
return 'child';
|
||||
}
|
||||
}
|
||||
|
||||
const buttonRef = React.createRef();
|
||||
class App extends React.Component {
|
||||
state = {
|
||||
suspend: false,
|
||||
};
|
||||
handleClick = () => {
|
||||
this.setState({suspend: true});
|
||||
};
|
||||
render() {
|
||||
return (
|
||||
<React.Suspense fallback="Loading">
|
||||
<Child id="first" />
|
||||
<button ref={buttonRef} onClick={this.handleClick}>
|
||||
Suspend
|
||||
</button>
|
||||
<Child id="second" />
|
||||
{this.state.suspend && <Lazy />}
|
||||
</React.Suspense>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ReactDOM.render(<App />, container);
|
||||
|
||||
expect(log).toEqual(['cDM first', 'cDM second']);
|
||||
log.length = 0;
|
||||
|
||||
buttonRef.current.dispatchEvent(new MouseEvent('click', {bubbles: true}));
|
||||
await Lazy;
|
||||
expect(log).toEqual(['cDU first', 'cDU second']);
|
||||
});
|
||||
});
|
||||
@@ -28,6 +28,8 @@ describe('ReactErrorBoundaries', () => {
|
||||
let BrokenComponentWillMountErrorBoundary;
|
||||
let BrokenComponentDidMountErrorBoundary;
|
||||
let BrokenRender;
|
||||
let BrokenUseEffect;
|
||||
let BrokenUseLayoutEffect;
|
||||
let ErrorBoundary;
|
||||
let ErrorMessage;
|
||||
let NoopErrorBoundary;
|
||||
@@ -35,6 +37,7 @@ describe('ReactErrorBoundaries', () => {
|
||||
let Normal;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers();
|
||||
jest.resetModules();
|
||||
PropTypes = require('prop-types');
|
||||
ReactFeatureFlags = require('shared/ReactFeatureFlags');
|
||||
@@ -386,6 +389,28 @@ describe('ReactErrorBoundaries', () => {
|
||||
}
|
||||
};
|
||||
|
||||
BrokenUseEffect = props => {
|
||||
log.push('BrokenUseEffect render');
|
||||
|
||||
React.useEffect(() => {
|
||||
log.push('BrokenUseEffect useEffect [!]');
|
||||
throw new Error('Hello');
|
||||
});
|
||||
|
||||
return props.children;
|
||||
};
|
||||
|
||||
BrokenUseLayoutEffect = props => {
|
||||
log.push('BrokenUseLayoutEffect render');
|
||||
|
||||
React.useLayoutEffect(() => {
|
||||
log.push('BrokenUseLayoutEffect useLayoutEffect [!]');
|
||||
throw new Error('Hello');
|
||||
});
|
||||
|
||||
return props.children;
|
||||
};
|
||||
|
||||
NoopErrorBoundary = class extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
@@ -1795,6 +1820,67 @@ describe('ReactErrorBoundaries', () => {
|
||||
expect(log).toEqual(['ErrorBoundary componentWillUnmount']);
|
||||
});
|
||||
|
||||
it('catches errors in useEffect', () => {
|
||||
const container = document.createElement('div');
|
||||
ReactDOM.render(
|
||||
<ErrorBoundary>
|
||||
<BrokenUseEffect>Initial value</BrokenUseEffect>
|
||||
</ErrorBoundary>,
|
||||
container,
|
||||
);
|
||||
expect(log).toEqual([
|
||||
'ErrorBoundary constructor',
|
||||
'ErrorBoundary componentWillMount',
|
||||
'ErrorBoundary render success',
|
||||
'BrokenUseEffect render',
|
||||
'ErrorBoundary componentDidMount',
|
||||
]);
|
||||
|
||||
expect(container.firstChild.textContent).toBe('Initial value');
|
||||
log.length = 0;
|
||||
|
||||
jest.runAllTimers();
|
||||
|
||||
// Flush passive effects and handle the error
|
||||
expect(log).toEqual([
|
||||
'BrokenUseEffect useEffect [!]',
|
||||
// Handle the error
|
||||
'ErrorBoundary static getDerivedStateFromError',
|
||||
'ErrorBoundary componentWillUpdate',
|
||||
'ErrorBoundary render error',
|
||||
'ErrorBoundary componentDidUpdate',
|
||||
]);
|
||||
|
||||
expect(container.firstChild.textContent).toBe('Caught an error: Hello.');
|
||||
});
|
||||
|
||||
it('catches errors in useLayoutEffect', () => {
|
||||
const container = document.createElement('div');
|
||||
ReactDOM.render(
|
||||
<ErrorBoundary>
|
||||
<BrokenUseLayoutEffect>Initial value</BrokenUseLayoutEffect>
|
||||
</ErrorBoundary>,
|
||||
container,
|
||||
);
|
||||
expect(log).toEqual([
|
||||
'ErrorBoundary constructor',
|
||||
'ErrorBoundary componentWillMount',
|
||||
'ErrorBoundary render success',
|
||||
'BrokenUseLayoutEffect render',
|
||||
'BrokenUseLayoutEffect useLayoutEffect [!]',
|
||||
// Fiber proceeds with the hooks
|
||||
'ErrorBoundary componentDidMount',
|
||||
// The error propagates to the higher boundary
|
||||
'ErrorBoundary static getDerivedStateFromError',
|
||||
// Fiber retries from the root
|
||||
'ErrorBoundary componentWillUpdate',
|
||||
'ErrorBoundary render error',
|
||||
'ErrorBoundary componentDidUpdate',
|
||||
]);
|
||||
|
||||
expect(container.firstChild.textContent).toBe('Caught an error: Hello.');
|
||||
});
|
||||
|
||||
it('propagates errors inside boundary during componentDidMount', () => {
|
||||
const container = document.createElement('div');
|
||||
ReactDOM.render(
|
||||
|
||||
@@ -192,7 +192,9 @@ describe('ReactFunctionComponent', () => {
|
||||
ReactTestUtils.renderIntoDocument(<ParentUsingStringRef />),
|
||||
).toWarnDev(
|
||||
'Warning: Function components cannot be given refs. ' +
|
||||
'Attempts to access this ref will fail.\n\nCheck the render method ' +
|
||||
'Attempts to access this ref will fail. ' +
|
||||
'Did you mean to use React.forwardRef()?\n\n' +
|
||||
'Check the render method ' +
|
||||
'of `ParentUsingStringRef`.\n' +
|
||||
' in FunctionComponent (at **)\n' +
|
||||
' in div (at **)\n' +
|
||||
@@ -228,7 +230,9 @@ describe('ReactFunctionComponent', () => {
|
||||
ReactTestUtils.renderIntoDocument(<ParentUsingFunctionRef />),
|
||||
).toWarnDev(
|
||||
'Warning: Function components cannot be given refs. ' +
|
||||
'Attempts to access this ref will fail.\n\nCheck the render method ' +
|
||||
'Attempts to access this ref will fail. ' +
|
||||
'Did you mean to use React.forwardRef()?\n\n' +
|
||||
'Check the render method ' +
|
||||
'of `ParentUsingFunctionRef`.\n' +
|
||||
' in FunctionComponent (at **)\n' +
|
||||
' in div (at **)\n' +
|
||||
@@ -332,7 +336,9 @@ describe('ReactFunctionComponent', () => {
|
||||
|
||||
expect(() => ReactTestUtils.renderIntoDocument(<Parent />)).toWarnDev(
|
||||
'Warning: Function components cannot be given refs. ' +
|
||||
'Attempts to access this ref will fail.\n\nCheck the render method ' +
|
||||
'Attempts to access this ref will fail. ' +
|
||||
'Did you mean to use React.forwardRef()?\n\n' +
|
||||
'Check the render method ' +
|
||||
'of `Parent`.\n' +
|
||||
' in Child (at **)\n' +
|
||||
' in Parent (at **)',
|
||||
@@ -354,7 +360,7 @@ describe('ReactFunctionComponent', () => {
|
||||
}
|
||||
|
||||
expect(() => ReactTestUtils.renderIntoDocument(<Child />)).toWarnDev(
|
||||
'Each child in an array or iterator should have a unique "key" prop.\n\n' +
|
||||
'Each child in a list should have a unique "key" prop.\n\n' +
|
||||
'Check the render method of `Child`.',
|
||||
);
|
||||
});
|
||||
|
||||
@@ -162,8 +162,8 @@ describe('ReactMultiChildText', () => {
|
||||
['', 'foo', <div>{true}{<div />}{1.2}{''}</div>, 'foo'], ['', 'foo', <div />, 'foo'],
|
||||
]);
|
||||
}).toWarnDev([
|
||||
'Warning: Each child in an array or iterator should have a unique "key" prop.',
|
||||
'Warning: Each child in an array or iterator should have a unique "key" prop.',
|
||||
'Warning: Each child in a list should have a unique "key" prop.',
|
||||
'Warning: Each child in a list should have a unique "key" prop.',
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
let React;
|
||||
let ReactDOMServer;
|
||||
let PropTypes;
|
||||
let ReactCurrentOwner;
|
||||
let ReactCurrentDispatcher;
|
||||
|
||||
function normalizeCodeLocInfo(str) {
|
||||
return str && str.replace(/\(at .+?:\d+\)/g, '(at **)');
|
||||
@@ -25,9 +25,9 @@ describe('ReactDOMServer', () => {
|
||||
React = require('react');
|
||||
PropTypes = require('prop-types');
|
||||
ReactDOMServer = require('react-dom/server');
|
||||
ReactCurrentOwner =
|
||||
ReactCurrentDispatcher =
|
||||
React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
|
||||
.ReactCurrentOwner;
|
||||
.ReactCurrentDispatcher;
|
||||
});
|
||||
|
||||
describe('renderToString', () => {
|
||||
@@ -439,7 +439,7 @@ describe('ReactDOMServer', () => {
|
||||
const Context = React.createContext(0);
|
||||
|
||||
function readContext(context) {
|
||||
return ReactCurrentOwner.currentDispatcher.readContext(context);
|
||||
return ReactCurrentDispatcher.current.readContext(context);
|
||||
}
|
||||
|
||||
function Consumer(props) {
|
||||
@@ -556,6 +556,52 @@ describe('ReactDOMServer', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('renderToNodeStream', () => {
|
||||
it('should generate simple markup', () => {
|
||||
const SuccessfulElement = React.createElement(() => <img />);
|
||||
const response = ReactDOMServer.renderToNodeStream(SuccessfulElement);
|
||||
expect(response.read().toString()).toMatch(
|
||||
new RegExp('<img data-reactroot=""' + '/>'),
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle errors correctly', () => {
|
||||
const FailingElement = React.createElement(() => {
|
||||
throw new Error('An Error');
|
||||
});
|
||||
const response = ReactDOMServer.renderToNodeStream(FailingElement);
|
||||
return new Promise(resolve => {
|
||||
response.once('error', () => {
|
||||
resolve();
|
||||
});
|
||||
expect(response.read()).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('renderToStaticNodeStream', () => {
|
||||
it('should generate simple markup', () => {
|
||||
const SuccessfulElement = React.createElement(() => <img />);
|
||||
const response = ReactDOMServer.renderToStaticNodeStream(
|
||||
SuccessfulElement,
|
||||
);
|
||||
expect(response.read().toString()).toMatch(new RegExp('<img' + '/>'));
|
||||
});
|
||||
|
||||
it('should handle errors correctly', () => {
|
||||
const FailingElement = React.createElement(() => {
|
||||
throw new Error('An Error');
|
||||
});
|
||||
const response = ReactDOMServer.renderToStaticNodeStream(FailingElement);
|
||||
return new Promise(resolve => {
|
||||
response.once('error', () => {
|
||||
resolve();
|
||||
});
|
||||
expect(response.read()).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('warns with a no-op when an async setState is triggered', () => {
|
||||
class Foo extends React.Component {
|
||||
UNSAFE_componentWillMount() {
|
||||
@@ -858,4 +904,125 @@ describe('ReactDOMServer', () => {
|
||||
' in App (at **)',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should warn if an invalid contextType is defined', () => {
|
||||
const Context = React.createContext();
|
||||
|
||||
class ComponentA extends React.Component {
|
||||
// It should warn for both Context.Consumer and Context.Provider
|
||||
static contextType = Context.Consumer;
|
||||
render() {
|
||||
return <div />;
|
||||
}
|
||||
}
|
||||
class ComponentB extends React.Component {
|
||||
static contextType = Context.Provider;
|
||||
render() {
|
||||
return <div />;
|
||||
}
|
||||
}
|
||||
|
||||
expect(() => {
|
||||
ReactDOMServer.renderToString(<ComponentA />);
|
||||
}).toWarnDev(
|
||||
'Warning: ComponentA defines an invalid contextType. ' +
|
||||
'contextType should point to the Context object returned by React.createContext(). ' +
|
||||
'Did you accidentally pass the Context.Consumer instead?',
|
||||
{withoutStack: true},
|
||||
);
|
||||
|
||||
// Warnings should be deduped by component type
|
||||
ReactDOMServer.renderToString(<ComponentA />);
|
||||
|
||||
expect(() => {
|
||||
ReactDOMServer.renderToString(<ComponentB />);
|
||||
}).toWarnDev(
|
||||
'Warning: ComponentB defines an invalid contextType. ' +
|
||||
'contextType should point to the Context object returned by React.createContext(). ' +
|
||||
'Did you accidentally pass the Context.Provider instead?',
|
||||
{withoutStack: true},
|
||||
);
|
||||
});
|
||||
|
||||
it('should not warn when class contextType is null', () => {
|
||||
class Foo extends React.Component {
|
||||
static contextType = null; // Handy for conditional declaration
|
||||
render() {
|
||||
return this.context.hello.world;
|
||||
}
|
||||
}
|
||||
|
||||
expect(() => {
|
||||
ReactDOMServer.renderToString(<Foo />);
|
||||
}).toThrow("Cannot read property 'world' of undefined");
|
||||
});
|
||||
|
||||
it('should warn when class contextType is undefined', () => {
|
||||
class Foo extends React.Component {
|
||||
// This commonly happens with circular deps
|
||||
// https://github.com/facebook/react/issues/13969
|
||||
static contextType = undefined;
|
||||
render() {
|
||||
return this.context.hello.world;
|
||||
}
|
||||
}
|
||||
|
||||
expect(() => {
|
||||
expect(() => {
|
||||
ReactDOMServer.renderToString(<Foo />);
|
||||
}).toThrow("Cannot read property 'world' of undefined");
|
||||
}).toWarnDev(
|
||||
'Foo defines an invalid contextType. ' +
|
||||
'contextType should point to the Context object returned by React.createContext(). ' +
|
||||
'However, it is set to undefined. ' +
|
||||
'This can be caused by a typo or by mixing up named and default imports. ' +
|
||||
'This can also happen due to a circular dependency, ' +
|
||||
'so try moving the createContext() call to a separate file.',
|
||||
{withoutStack: true},
|
||||
);
|
||||
});
|
||||
|
||||
it('should warn when class contextType is an object', () => {
|
||||
class Foo extends React.Component {
|
||||
// Can happen due to a typo
|
||||
static contextType = {
|
||||
x: 42,
|
||||
y: 'hello',
|
||||
};
|
||||
render() {
|
||||
return this.context.hello.world;
|
||||
}
|
||||
}
|
||||
|
||||
expect(() => {
|
||||
expect(() => {
|
||||
ReactDOMServer.renderToString(<Foo />);
|
||||
}).toThrow("Cannot read property 'hello' of undefined");
|
||||
}).toWarnDev(
|
||||
'Foo defines an invalid contextType. ' +
|
||||
'contextType should point to the Context object returned by React.createContext(). ' +
|
||||
'However, it is set to an object with keys {x, y}.',
|
||||
{withoutStack: true},
|
||||
);
|
||||
});
|
||||
|
||||
it('should warn when class contextType is a primitive', () => {
|
||||
class Foo extends React.Component {
|
||||
static contextType = 'foo';
|
||||
render() {
|
||||
return this.context.hello.world;
|
||||
}
|
||||
}
|
||||
|
||||
expect(() => {
|
||||
expect(() => {
|
||||
ReactDOMServer.renderToString(<Foo />);
|
||||
}).toThrow("Cannot read property 'world' of undefined");
|
||||
}).toWarnDev(
|
||||
'Foo defines an invalid contextType. ' +
|
||||
'contextType should point to the Context object returned by React.createContext(). ' +
|
||||
'However, it is set to a string.',
|
||||
{withoutStack: true},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,6 +14,7 @@ let React;
|
||||
let ReactDOM;
|
||||
let ReactDOMServer;
|
||||
let ReactTestUtils;
|
||||
let act;
|
||||
|
||||
function getTestDocument(markup) {
|
||||
const doc = document.implementation.createHTMLDocument('');
|
||||
@@ -33,6 +34,7 @@ describe('ReactTestUtils', () => {
|
||||
ReactDOM = require('react-dom');
|
||||
ReactDOMServer = require('react-dom/server');
|
||||
ReactTestUtils = require('react-dom/test-utils');
|
||||
act = ReactTestUtils.act;
|
||||
});
|
||||
|
||||
it('Simulate should have locally attached media events', () => {
|
||||
@@ -515,4 +517,173 @@ describe('ReactTestUtils', () => {
|
||||
ReactTestUtils.renderIntoDocument(<Component />);
|
||||
expect(mockArgs.length).toEqual(0);
|
||||
});
|
||||
|
||||
it('can use act to batch effects', () => {
|
||||
function App(props) {
|
||||
React.useEffect(props.callback);
|
||||
return null;
|
||||
}
|
||||
const container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
|
||||
try {
|
||||
let called = false;
|
||||
act(() => {
|
||||
ReactDOM.render(
|
||||
<App
|
||||
callback={() => {
|
||||
called = true;
|
||||
}}
|
||||
/>,
|
||||
container,
|
||||
);
|
||||
});
|
||||
|
||||
expect(called).toBe(true);
|
||||
} finally {
|
||||
document.body.removeChild(container);
|
||||
}
|
||||
});
|
||||
|
||||
it('flushes effects on every call', () => {
|
||||
function App(props) {
|
||||
let [ctr, setCtr] = React.useState(0);
|
||||
React.useEffect(() => {
|
||||
props.callback(ctr);
|
||||
});
|
||||
return (
|
||||
<button id="button" onClick={() => setCtr(x => x + 1)}>
|
||||
click me!
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
const container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
let calledCtr = 0;
|
||||
act(() => {
|
||||
ReactDOM.render(
|
||||
<App
|
||||
callback={val => {
|
||||
calledCtr = val;
|
||||
}}
|
||||
/>,
|
||||
container,
|
||||
);
|
||||
});
|
||||
const button = document.getElementById('button');
|
||||
function click() {
|
||||
button.dispatchEvent(new MouseEvent('click', {bubbles: true}));
|
||||
}
|
||||
|
||||
act(() => {
|
||||
click();
|
||||
click();
|
||||
click();
|
||||
});
|
||||
expect(calledCtr).toBe(3);
|
||||
act(click);
|
||||
expect(calledCtr).toBe(4);
|
||||
act(click);
|
||||
expect(calledCtr).toBe(5);
|
||||
|
||||
document.body.removeChild(container);
|
||||
});
|
||||
|
||||
it('can use act to batch effects on updates too', () => {
|
||||
function App() {
|
||||
let [ctr, setCtr] = React.useState(0);
|
||||
return (
|
||||
<button id="button" onClick={() => setCtr(x => x + 1)}>
|
||||
{ctr}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
const container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
let button;
|
||||
act(() => {
|
||||
ReactDOM.render(<App />, container);
|
||||
});
|
||||
button = document.getElementById('button');
|
||||
expect(button.innerHTML).toBe('0');
|
||||
act(() => {
|
||||
button.dispatchEvent(new MouseEvent('click', {bubbles: true}));
|
||||
});
|
||||
expect(button.innerHTML).toBe('1');
|
||||
document.body.removeChild(container);
|
||||
});
|
||||
|
||||
it('detects setState being called outside of act(...)', () => {
|
||||
let setValueRef = null;
|
||||
function App() {
|
||||
let [value, setValue] = React.useState(0);
|
||||
setValueRef = setValue;
|
||||
return (
|
||||
<button id="button" onClick={() => setValue(2)}>
|
||||
{value}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
const container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
let button;
|
||||
act(() => {
|
||||
ReactDOM.render(<App />, container);
|
||||
button = container.querySelector('#button');
|
||||
button.dispatchEvent(new MouseEvent('click', {bubbles: true}));
|
||||
});
|
||||
expect(button.innerHTML).toBe('2');
|
||||
expect(() => setValueRef(1)).toWarnDev([
|
||||
'An update to App inside a test was not wrapped in act(...).',
|
||||
]);
|
||||
document.body.removeChild(container);
|
||||
});
|
||||
|
||||
it('lets a ticker update', () => {
|
||||
function App() {
|
||||
let [toggle, setToggle] = React.useState(0);
|
||||
React.useEffect(() => {
|
||||
let timeout = setTimeout(() => {
|
||||
setToggle(1);
|
||||
}, 200);
|
||||
return () => clearTimeout(timeout);
|
||||
});
|
||||
return toggle;
|
||||
}
|
||||
const container = document.createElement('div');
|
||||
|
||||
act(() => {
|
||||
act(() => {
|
||||
ReactDOM.render(<App />, container);
|
||||
});
|
||||
jest.advanceTimersByTime(250);
|
||||
});
|
||||
|
||||
expect(container.innerHTML).toBe('1');
|
||||
});
|
||||
|
||||
it('warns if you return a value inside act', () => {
|
||||
expect(() => act(() => null)).toWarnDev(
|
||||
[
|
||||
'The callback passed to ReactTestUtils.act(...) function must not return anything.',
|
||||
],
|
||||
{withoutStack: true},
|
||||
);
|
||||
expect(() => act(() => 123)).toWarnDev(
|
||||
[
|
||||
'The callback passed to ReactTestUtils.act(...) function must not return anything.',
|
||||
],
|
||||
{withoutStack: true},
|
||||
);
|
||||
});
|
||||
|
||||
it('warns if you try to await an .act call', () => {
|
||||
expect(act(() => {}).then).toWarnDev(
|
||||
[
|
||||
'Do not await the result of calling ReactTestUtils.act(...), it is not a Promise.',
|
||||
],
|
||||
{withoutStack: true},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
207
packages/react-dom/src/client/ReactDOM.js
vendored
207
packages/react-dom/src/client/ReactDOM.js
vendored
@@ -14,20 +14,46 @@ import type {
|
||||
FiberRoot,
|
||||
Batch as FiberRootBatch,
|
||||
} from 'react-reconciler/src/ReactFiberRoot';
|
||||
import type {Container} from './ReactDOMHostConfig';
|
||||
|
||||
import '../shared/checkReact';
|
||||
import './ReactDOMClientInjection';
|
||||
|
||||
import * as DOMRenderer from 'react-reconciler/inline.dom';
|
||||
import * as ReactPortal from 'shared/ReactPortal';
|
||||
import {
|
||||
computeUniqueAsyncExpiration,
|
||||
findHostInstanceWithNoPortals,
|
||||
updateContainerAtExpirationTime,
|
||||
flushRoot,
|
||||
createContainer,
|
||||
updateContainer,
|
||||
batchedUpdates,
|
||||
unbatchedUpdates,
|
||||
interactiveUpdates,
|
||||
flushInteractiveUpdates,
|
||||
flushSync,
|
||||
flushControlled,
|
||||
injectIntoDevTools,
|
||||
getPublicRootInstance,
|
||||
findHostInstance,
|
||||
findHostInstanceWithWarning,
|
||||
} from 'react-reconciler/inline.dom';
|
||||
import {createPortal as createPortalImpl} from 'shared/ReactPortal';
|
||||
import {canUseDOM} from 'shared/ExecutionEnvironment';
|
||||
import * as ReactGenericBatching from 'events/ReactGenericBatching';
|
||||
import * as ReactControlledComponent from 'events/ReactControlledComponent';
|
||||
import * as EventPluginHub from 'events/EventPluginHub';
|
||||
import * as EventPluginRegistry from 'events/EventPluginRegistry';
|
||||
import * as EventPropagators from 'events/EventPropagators';
|
||||
import * as ReactInstanceMap from 'shared/ReactInstanceMap';
|
||||
import {setBatchingImplementation} from 'events/ReactGenericBatching';
|
||||
import {
|
||||
setRestoreImplementation,
|
||||
enqueueStateRestore,
|
||||
restoreStateIfNeeded,
|
||||
} from 'events/ReactControlledComponent';
|
||||
import {
|
||||
injection as EventPluginHubInjection,
|
||||
runEventsInBatch,
|
||||
} from 'events/EventPluginHub';
|
||||
import {eventNameDispatchConfigs} from 'events/EventPluginRegistry';
|
||||
import {
|
||||
accumulateTwoPhaseDispatches,
|
||||
accumulateDirectDispatches,
|
||||
} from 'events/EventPropagators';
|
||||
import {has as hasInstance} from 'shared/ReactInstanceMap';
|
||||
import ReactVersion from 'shared/ReactVersion';
|
||||
import ReactSharedInternals from 'shared/ReactSharedInternals';
|
||||
import getComponentName from 'shared/getComponentName';
|
||||
@@ -36,9 +62,14 @@ import lowPriorityWarning from 'shared/lowPriorityWarning';
|
||||
import warningWithoutStack from 'shared/warningWithoutStack';
|
||||
import {enableStableConcurrentModeAPIs} from 'shared/ReactFeatureFlags';
|
||||
|
||||
import * as ReactDOMComponentTree from './ReactDOMComponentTree';
|
||||
import {
|
||||
getInstanceFromNode,
|
||||
getNodeFromInstance,
|
||||
getFiberCurrentPropsFromNode,
|
||||
getClosestInstanceFromNode,
|
||||
} from './ReactDOMComponentTree';
|
||||
import {restoreControlledState} from './ReactDOMComponent';
|
||||
import * as ReactDOMEventListener from '../events/ReactDOMEventListener';
|
||||
import {dispatchEvent} from '../events/ReactDOMEventListener';
|
||||
import {
|
||||
ELEMENT_NODE,
|
||||
COMMENT_NODE,
|
||||
@@ -74,7 +105,7 @@ if (__DEV__) {
|
||||
|
||||
topLevelUpdateWarnings = (container: DOMContainer) => {
|
||||
if (container._reactRootContainer && container.nodeType !== COMMENT_NODE) {
|
||||
const hostInstance = DOMRenderer.findHostInstanceWithNoPortals(
|
||||
const hostInstance = findHostInstanceWithNoPortals(
|
||||
container._reactRootContainer._internalRoot.current,
|
||||
);
|
||||
if (hostInstance) {
|
||||
@@ -90,9 +121,7 @@ if (__DEV__) {
|
||||
|
||||
const isRootRenderedBySomeReact = !!container._reactRootContainer;
|
||||
const rootEl = getReactRootElementInContainer(container);
|
||||
const hasNonRootReactChild = !!(
|
||||
rootEl && ReactDOMComponentTree.getInstanceFromNode(rootEl)
|
||||
);
|
||||
const hasNonRootReactChild = !!(rootEl && getInstanceFromNode(rootEl));
|
||||
|
||||
warningWithoutStack(
|
||||
!hasNonRootReactChild || isRootRenderedBySomeReact,
|
||||
@@ -125,14 +154,16 @@ if (__DEV__) {
|
||||
};
|
||||
}
|
||||
|
||||
ReactControlledComponent.setRestoreImplementation(restoreControlledState);
|
||||
setRestoreImplementation(restoreControlledState);
|
||||
|
||||
export type DOMContainer =
|
||||
| (Element & {
|
||||
_reactRootContainer: ?Root,
|
||||
_reactHasBeenPassedToCreateRootDEV: ?boolean,
|
||||
})
|
||||
| (Document & {
|
||||
_reactRootContainer: ?Root,
|
||||
_reactHasBeenPassedToCreateRootDEV: ?boolean,
|
||||
});
|
||||
|
||||
type Batch = FiberRootBatch & {
|
||||
@@ -165,7 +196,7 @@ type Root = {
|
||||
};
|
||||
|
||||
function ReactBatch(root: ReactRoot) {
|
||||
const expirationTime = DOMRenderer.computeUniqueAsyncExpiration();
|
||||
const expirationTime = computeUniqueAsyncExpiration();
|
||||
this._expirationTime = expirationTime;
|
||||
this._root = root;
|
||||
this._next = null;
|
||||
@@ -185,7 +216,7 @@ ReactBatch.prototype.render = function(children: ReactNodeList) {
|
||||
const internalRoot = this._root._internalRoot;
|
||||
const expirationTime = this._expirationTime;
|
||||
const work = new ReactWork();
|
||||
DOMRenderer.updateContainerAtExpirationTime(
|
||||
updateContainerAtExpirationTime(
|
||||
children,
|
||||
internalRoot,
|
||||
null,
|
||||
@@ -256,7 +287,7 @@ ReactBatch.prototype.commit = function() {
|
||||
|
||||
// Synchronously flush all the work up to this batch's expiration time.
|
||||
this._defer = false;
|
||||
DOMRenderer.flushRoot(internalRoot, expirationTime);
|
||||
flushRoot(internalRoot, expirationTime);
|
||||
|
||||
// Pop the batch from the list.
|
||||
const next = this._next;
|
||||
@@ -332,11 +363,11 @@ ReactWork.prototype._onCommit = function(): void {
|
||||
};
|
||||
|
||||
function ReactRoot(
|
||||
container: Container,
|
||||
container: DOMContainer,
|
||||
isConcurrent: boolean,
|
||||
hydrate: boolean,
|
||||
) {
|
||||
const root = DOMRenderer.createContainer(container, isConcurrent, hydrate);
|
||||
const root = createContainer(container, isConcurrent, hydrate);
|
||||
this._internalRoot = root;
|
||||
}
|
||||
ReactRoot.prototype.render = function(
|
||||
@@ -352,7 +383,7 @@ ReactRoot.prototype.render = function(
|
||||
if (callback !== null) {
|
||||
work.then(callback);
|
||||
}
|
||||
DOMRenderer.updateContainer(children, root, null, work._onCommit);
|
||||
updateContainer(children, root, null, work._onCommit);
|
||||
return work;
|
||||
};
|
||||
ReactRoot.prototype.unmount = function(callback: ?() => mixed): Work {
|
||||
@@ -365,7 +396,7 @@ ReactRoot.prototype.unmount = function(callback: ?() => mixed): Work {
|
||||
if (callback !== null) {
|
||||
work.then(callback);
|
||||
}
|
||||
DOMRenderer.updateContainer(null, root, null, work._onCommit);
|
||||
updateContainer(null, root, null, work._onCommit);
|
||||
return work;
|
||||
};
|
||||
ReactRoot.prototype.legacy_renderSubtreeIntoContainer = function(
|
||||
@@ -382,7 +413,7 @@ ReactRoot.prototype.legacy_renderSubtreeIntoContainer = function(
|
||||
if (callback !== null) {
|
||||
work.then(callback);
|
||||
}
|
||||
DOMRenderer.updateContainer(children, root, parentComponent, work._onCommit);
|
||||
updateContainer(children, root, parentComponent, work._onCommit);
|
||||
return work;
|
||||
};
|
||||
ReactRoot.prototype.createBatch = function(): Batch {
|
||||
@@ -453,10 +484,10 @@ function shouldHydrateDueToLegacyHeuristic(container) {
|
||||
);
|
||||
}
|
||||
|
||||
ReactGenericBatching.setBatchingImplementation(
|
||||
DOMRenderer.batchedUpdates,
|
||||
DOMRenderer.interactiveUpdates,
|
||||
DOMRenderer.flushInteractiveUpdates,
|
||||
setBatchingImplementation(
|
||||
batchedUpdates,
|
||||
interactiveUpdates,
|
||||
flushInteractiveUpdates,
|
||||
);
|
||||
|
||||
let warnedAboutHydrateAPI = false;
|
||||
@@ -513,12 +544,6 @@ function legacyRenderSubtreeIntoContainer(
|
||||
forceHydrate: boolean,
|
||||
callback: ?Function,
|
||||
) {
|
||||
// TODO: Ensure all entry points contain this check
|
||||
invariant(
|
||||
isValidContainer(container),
|
||||
'Target container is not a DOM element.',
|
||||
);
|
||||
|
||||
if (__DEV__) {
|
||||
topLevelUpdateWarnings(container);
|
||||
}
|
||||
@@ -535,12 +560,12 @@ function legacyRenderSubtreeIntoContainer(
|
||||
if (typeof callback === 'function') {
|
||||
const originalCallback = callback;
|
||||
callback = function() {
|
||||
const instance = DOMRenderer.getPublicRootInstance(root._internalRoot);
|
||||
const instance = getPublicRootInstance(root._internalRoot);
|
||||
originalCallback.call(instance);
|
||||
};
|
||||
}
|
||||
// Initial mount should not be batched.
|
||||
DOMRenderer.unbatchedUpdates(() => {
|
||||
unbatchedUpdates(() => {
|
||||
if (parentComponent != null) {
|
||||
root.legacy_renderSubtreeIntoContainer(
|
||||
parentComponent,
|
||||
@@ -555,7 +580,7 @@ function legacyRenderSubtreeIntoContainer(
|
||||
if (typeof callback === 'function') {
|
||||
const originalCallback = callback;
|
||||
callback = function() {
|
||||
const instance = DOMRenderer.getPublicRootInstance(root._internalRoot);
|
||||
const instance = getPublicRootInstance(root._internalRoot);
|
||||
originalCallback.call(instance);
|
||||
};
|
||||
}
|
||||
@@ -570,7 +595,7 @@ function legacyRenderSubtreeIntoContainer(
|
||||
root.render(children, callback);
|
||||
}
|
||||
}
|
||||
return DOMRenderer.getPublicRootInstance(root._internalRoot);
|
||||
return getPublicRootInstance(root._internalRoot);
|
||||
}
|
||||
|
||||
function createPortal(
|
||||
@@ -583,7 +608,7 @@ function createPortal(
|
||||
'Target container is not a DOM element.',
|
||||
);
|
||||
// TODO: pass ReactDOM portal implementation as third argument
|
||||
return ReactPortal.createPortal(children, container, null, key);
|
||||
return createPortalImpl(children, container, null, key);
|
||||
}
|
||||
|
||||
const ReactDOM: Object = {
|
||||
@@ -616,15 +641,25 @@ const ReactDOM: Object = {
|
||||
return (componentOrElement: any);
|
||||
}
|
||||
if (__DEV__) {
|
||||
return DOMRenderer.findHostInstanceWithWarning(
|
||||
componentOrElement,
|
||||
'findDOMNode',
|
||||
);
|
||||
return findHostInstanceWithWarning(componentOrElement, 'findDOMNode');
|
||||
}
|
||||
return DOMRenderer.findHostInstance(componentOrElement);
|
||||
return findHostInstance(componentOrElement);
|
||||
},
|
||||
|
||||
hydrate(element: React$Node, container: DOMContainer, callback: ?Function) {
|
||||
invariant(
|
||||
isValidContainer(container),
|
||||
'Target container is not a DOM element.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
warningWithoutStack(
|
||||
!container._reactHasBeenPassedToCreateRootDEV,
|
||||
'You are calling ReactDOM.hydrate() on a container that was previously ' +
|
||||
'passed to ReactDOM.%s(). This is not supported. ' +
|
||||
'Did you mean to call createRoot(container, {hydrate: true}).render(element)?',
|
||||
enableStableConcurrentModeAPIs ? 'createRoot' : 'unstable_createRoot',
|
||||
);
|
||||
}
|
||||
// TODO: throw or warn if we couldn't hydrate?
|
||||
return legacyRenderSubtreeIntoContainer(
|
||||
null,
|
||||
@@ -640,6 +675,19 @@ const ReactDOM: Object = {
|
||||
container: DOMContainer,
|
||||
callback: ?Function,
|
||||
) {
|
||||
invariant(
|
||||
isValidContainer(container),
|
||||
'Target container is not a DOM element.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
warningWithoutStack(
|
||||
!container._reactHasBeenPassedToCreateRootDEV,
|
||||
'You are calling ReactDOM.render() on a container that was previously ' +
|
||||
'passed to ReactDOM.%s(). This is not supported. ' +
|
||||
'Did you mean to call root.render(element)?',
|
||||
enableStableConcurrentModeAPIs ? 'createRoot' : 'unstable_createRoot',
|
||||
);
|
||||
}
|
||||
return legacyRenderSubtreeIntoContainer(
|
||||
null,
|
||||
element,
|
||||
@@ -656,7 +704,11 @@ const ReactDOM: Object = {
|
||||
callback: ?Function,
|
||||
) {
|
||||
invariant(
|
||||
parentComponent != null && ReactInstanceMap.has(parentComponent),
|
||||
isValidContainer(containerNode),
|
||||
'Target container is not a DOM element.',
|
||||
);
|
||||
invariant(
|
||||
parentComponent != null && hasInstance(parentComponent),
|
||||
'parentComponent must be a valid React Component',
|
||||
);
|
||||
return legacyRenderSubtreeIntoContainer(
|
||||
@@ -674,11 +726,19 @@ const ReactDOM: Object = {
|
||||
'unmountComponentAtNode(...): Target container is not a DOM element.',
|
||||
);
|
||||
|
||||
if (__DEV__) {
|
||||
warningWithoutStack(
|
||||
!container._reactHasBeenPassedToCreateRootDEV,
|
||||
'You are calling ReactDOM.unmountComponentAtNode() on a container that was previously ' +
|
||||
'passed to ReactDOM.%s(). This is not supported. Did you mean to call root.unmount()?',
|
||||
enableStableConcurrentModeAPIs ? 'createRoot' : 'unstable_createRoot',
|
||||
);
|
||||
}
|
||||
|
||||
if (container._reactRootContainer) {
|
||||
if (__DEV__) {
|
||||
const rootEl = getReactRootElementInContainer(container);
|
||||
const renderedByDifferentReact =
|
||||
rootEl && !ReactDOMComponentTree.getInstanceFromNode(rootEl);
|
||||
const renderedByDifferentReact = rootEl && !getInstanceFromNode(rootEl);
|
||||
warningWithoutStack(
|
||||
!renderedByDifferentReact,
|
||||
"unmountComponentAtNode(): The node you're attempting to unmount " +
|
||||
@@ -687,7 +747,7 @@ const ReactDOM: Object = {
|
||||
}
|
||||
|
||||
// Unmount should not be batched.
|
||||
DOMRenderer.unbatchedUpdates(() => {
|
||||
unbatchedUpdates(() => {
|
||||
legacyRenderSubtreeIntoContainer(null, null, container, false, () => {
|
||||
container._reactRootContainer = null;
|
||||
});
|
||||
@@ -698,9 +758,7 @@ const ReactDOM: Object = {
|
||||
} else {
|
||||
if (__DEV__) {
|
||||
const rootEl = getReactRootElementInContainer(container);
|
||||
const hasNonRootReactChild = !!(
|
||||
rootEl && ReactDOMComponentTree.getInstanceFromNode(rootEl)
|
||||
);
|
||||
const hasNonRootReactChild = !!(rootEl && getInstanceFromNode(rootEl));
|
||||
|
||||
// Check if the container itself is a React root node.
|
||||
const isContainerReactRoot =
|
||||
@@ -740,29 +798,30 @@ const ReactDOM: Object = {
|
||||
return createPortal(...args);
|
||||
},
|
||||
|
||||
unstable_batchedUpdates: DOMRenderer.batchedUpdates,
|
||||
unstable_batchedUpdates: batchedUpdates,
|
||||
|
||||
unstable_interactiveUpdates: DOMRenderer.interactiveUpdates,
|
||||
unstable_interactiveUpdates: interactiveUpdates,
|
||||
|
||||
flushSync: DOMRenderer.flushSync,
|
||||
flushSync: flushSync,
|
||||
|
||||
unstable_flushControlled: DOMRenderer.flushControlled,
|
||||
unstable_createRoot: createRoot,
|
||||
unstable_flushControlled: flushControlled,
|
||||
|
||||
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: {
|
||||
// Keep in sync with ReactDOMUnstableNativeDependencies.js
|
||||
// and ReactTestUtils.js. This is an array for better minification.
|
||||
Events: [
|
||||
ReactDOMComponentTree.getInstanceFromNode,
|
||||
ReactDOMComponentTree.getNodeFromInstance,
|
||||
ReactDOMComponentTree.getFiberCurrentPropsFromNode,
|
||||
EventPluginHub.injection.injectEventPluginsByName,
|
||||
EventPluginRegistry.eventNameDispatchConfigs,
|
||||
EventPropagators.accumulateTwoPhaseDispatches,
|
||||
EventPropagators.accumulateDirectDispatches,
|
||||
ReactControlledComponent.enqueueStateRestore,
|
||||
ReactControlledComponent.restoreStateIfNeeded,
|
||||
ReactDOMEventListener.dispatchEvent,
|
||||
EventPluginHub.runEventsInBatch,
|
||||
getInstanceFromNode,
|
||||
getNodeFromInstance,
|
||||
getFiberCurrentPropsFromNode,
|
||||
EventPluginHubInjection.injectEventPluginsByName,
|
||||
eventNameDispatchConfigs,
|
||||
accumulateTwoPhaseDispatches,
|
||||
accumulateDirectDispatches,
|
||||
enqueueStateRestore,
|
||||
restoreStateIfNeeded,
|
||||
dispatchEvent,
|
||||
runEventsInBatch,
|
||||
],
|
||||
},
|
||||
};
|
||||
@@ -780,18 +839,26 @@ function createRoot(container: DOMContainer, options?: RootOptions): ReactRoot {
|
||||
'%s(...): Target container is not a DOM element.',
|
||||
functionName,
|
||||
);
|
||||
if (__DEV__) {
|
||||
warningWithoutStack(
|
||||
!container._reactRootContainer,
|
||||
'You are calling ReactDOM.%s() on a container that was previously ' +
|
||||
'passed to ReactDOM.render(). This is not supported.',
|
||||
enableStableConcurrentModeAPIs ? 'createRoot' : 'unstable_createRoot',
|
||||
);
|
||||
container._reactHasBeenPassedToCreateRootDEV = true;
|
||||
}
|
||||
const hydrate = options != null && options.hydrate === true;
|
||||
return new ReactRoot(container, true, hydrate);
|
||||
}
|
||||
|
||||
if (enableStableConcurrentModeAPIs) {
|
||||
ReactDOM.createRoot = createRoot;
|
||||
} else {
|
||||
ReactDOM.unstable_createRoot = createRoot;
|
||||
ReactDOM.unstable_createRoot = undefined;
|
||||
}
|
||||
|
||||
const foundDevTools = DOMRenderer.injectIntoDevTools({
|
||||
findFiberByHostInstance: ReactDOMComponentTree.getClosestInstanceFromNode,
|
||||
const foundDevTools = injectIntoDevTools({
|
||||
findFiberByHostInstance: getClosestInstanceFromNode,
|
||||
bundleType: __DEV__ ? 1 : 0,
|
||||
version: ReactVersion,
|
||||
rendererPackageName: 'react-dom',
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user