Compare commits
196 Commits
v16.3.2
...
is-rendera
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
efe8e670af | ||
|
|
3297102de6 | ||
|
|
f4b6a9f8ee | ||
|
|
3eedcb1fda | ||
|
|
6d6de6011c | ||
|
|
71a60ddb16 | ||
|
|
9e6c99ca2e | ||
|
|
baff5cc2f6 | ||
|
|
6a530e3baa | ||
|
|
c35a1e7483 | ||
|
|
076bbeace7 | ||
|
|
da5c87bdfa | ||
|
|
a960d18bc7 | ||
|
|
5b3d17a5f7 | ||
|
|
b0f60895f7 | ||
|
|
ae8c6dd534 | ||
|
|
0fcf92d06d | ||
|
|
97af3e1f3a | ||
|
|
4fe6eec15b | ||
|
|
8e87c139b4 | ||
|
|
aeda7b745d | ||
|
|
b1b3acbd6b | ||
|
|
ae14317d68 | ||
|
|
72434a7686 | ||
|
|
64c54edea4 | ||
|
|
9bd4d1fae2 | ||
|
|
9bda7b28f3 | ||
|
|
2e75779075 | ||
|
|
bc963f353d | ||
|
|
051637da61 | ||
|
|
2a8085980f | ||
|
|
e0c78344e2 | ||
|
|
74b1723df1 | ||
|
|
9725065eb4 | ||
|
|
a5957bf296 | ||
|
|
0b87b27906 | ||
|
|
65eb6b94ac | ||
|
|
c469e3b422 | ||
|
|
036ae3c6e2 | ||
|
|
945fc1bfce | ||
|
|
392530104c | ||
|
|
1594409fab | ||
|
|
d5c11193e2 | ||
|
|
ec60457bcd | ||
|
|
30bc8ef792 | ||
|
|
d480782c41 | ||
|
|
4ac6f133af | ||
|
|
23be4102df | ||
|
|
d0d4280640 | ||
|
|
c78957eac8 | ||
|
|
bfb12ebb52 | ||
|
|
394b17eede | ||
|
|
188c4252a2 | ||
|
|
d3e0a3aaf3 | ||
|
|
9cf3733a9a | ||
|
|
52fbe7612e | ||
|
|
c5a733e1e3 | ||
|
|
36546b5137 | ||
|
|
65ab53694f | ||
|
|
15767a8f8f | ||
|
|
3118ed9d64 | ||
|
|
524a743313 | ||
|
|
ae57b125c7 | ||
|
|
e0a03c1b4d | ||
|
|
79a740c6e3 | ||
|
|
ff724d3c28 | ||
|
|
001f9ef471 | ||
|
|
83f76e4db9 | ||
|
|
4f1f909b5b | ||
|
|
8aeea5afa2 | ||
|
|
aa85b0fd5f | ||
|
|
a32f857ac7 | ||
|
|
61777a78f6 | ||
|
|
e7bd3d59a9 | ||
|
|
f35d989bea | ||
|
|
5578700671 | ||
|
|
76e07071a1 | ||
|
|
345e0a71ac | ||
|
|
fa7fa812c7 | ||
|
|
8765d60893 | ||
|
|
d31e753f89 | ||
|
|
d427a563d5 | ||
|
|
eca59ec1b3 | ||
|
|
53852a887b | ||
|
|
fe747a51c1 | ||
|
|
3df157480a | ||
|
|
6f4fb4a059 | ||
|
|
d1be01f079 | ||
|
|
c601f7a646 | ||
|
|
7350358374 | ||
|
|
e885791842 | ||
|
|
7c0aca289d | ||
|
|
33289b530c | ||
|
|
ad27845ccd | ||
|
|
60853f09f3 | ||
|
|
dd5fad2961 | ||
|
|
13003654e7 | ||
|
|
12c8a88cd9 | ||
|
|
dc3b144f41 | ||
|
|
0442e8275f | ||
|
|
089d2deb20 | ||
|
|
d7b9b4921b | ||
|
|
9bed4a6aee | ||
|
|
397d6115b7 | ||
|
|
47b003a828 | ||
|
|
c0fe8d6f69 | ||
|
|
5e80d81f37 | ||
|
|
17908c8ac9 | ||
|
|
96992f2a6c | ||
|
|
972d209dcc | ||
|
|
40addbd110 | ||
|
|
40ea053bac | ||
|
|
c5a8dae025 | ||
|
|
9d71ef26c3 | ||
|
|
1a0afed771 | ||
|
|
b245795de3 | ||
|
|
7ccb37161f | ||
|
|
4b8510be0f | ||
|
|
2d20dc47a3 | ||
|
|
d4123b4784 | ||
|
|
2ace49362a | ||
|
|
2da155a4c3 | ||
|
|
d6f304e889 | ||
|
|
8227e54ccf | ||
|
|
ef294ed6fc | ||
|
|
49979bbf52 | ||
|
|
de84d5c107 | ||
|
|
f792275972 | ||
|
|
a5184b215d | ||
|
|
103503eb69 | ||
|
|
9097f3cdf0 | ||
|
|
d758960116 | ||
|
|
025d867dce | ||
|
|
fe7890d569 | ||
|
|
7ba1abecaa | ||
|
|
f2252a2ad4 | ||
|
|
b998357f9d | ||
|
|
7631024722 | ||
|
|
bb44feb05d | ||
|
|
7dc1a176b5 | ||
|
|
e96dc14059 | ||
|
|
1047980dca | ||
|
|
bde4b1659f | ||
|
|
73f59e6f31 | ||
|
|
8c747d01cb | ||
|
|
369dd4fb17 | ||
|
|
45b90d4866 | ||
|
|
b2d16047ae | ||
|
|
c802d29bd1 | ||
|
|
c5d3104fc0 | ||
|
|
0ba63aa141 | ||
|
|
c4abfa4015 | ||
|
|
2a4d2ca7fc | ||
|
|
72542030cf | ||
|
|
37d12e2916 | ||
|
|
0470854f55 | ||
|
|
d430e13582 | ||
|
|
8506062975 | ||
|
|
7b19f93ab9 | ||
|
|
4b2e65d32e | ||
|
|
4f459bb144 | ||
|
|
b0726e9947 | ||
|
|
6565795377 | ||
|
|
42a1262375 | ||
|
|
fc3777b1fe | ||
|
|
a9abd27e4f | ||
|
|
3fb8be5c30 | ||
|
|
0bf24cc83e | ||
|
|
25dda90c1e | ||
|
|
ad7cd68667 | ||
|
|
200357596a | ||
|
|
e0ca51a85d | ||
|
|
7dd4ca2911 | ||
|
|
9a9f54720f | ||
|
|
dcc854bcc3 | ||
|
|
045d4f166d | ||
|
|
7c39328571 | ||
|
|
d883d59863 | ||
|
|
ec57d29941 | ||
|
|
9c77ffb444 | ||
|
|
09a14eacd4 | ||
|
|
1673485720 | ||
|
|
1e3cd332a0 | ||
|
|
149a34f735 | ||
|
|
b548b3cd64 | ||
|
|
5dcf93d146 | ||
|
|
c040bcbea8 | ||
|
|
999b656ed1 | ||
|
|
f80bbf88e5 | ||
|
|
920f30ef77 | ||
|
|
0887c7d56c | ||
|
|
039695cc01 | ||
|
|
b05e67e36a | ||
|
|
77ebeb1b09 | ||
|
|
b85c5cd188 | ||
|
|
01402f4ad9 |
4
.babelrc
4
.babelrc
@@ -18,8 +18,6 @@
|
||||
["transform-es2015-spread", { "loose": true }],
|
||||
"transform-es2015-parameters",
|
||||
["transform-es2015-destructuring", { "loose": true }],
|
||||
["transform-es2015-block-scoping", { "throwIfClosureRequired": true }],
|
||||
"transform-es3-member-expression-literals",
|
||||
"transform-es3-property-literals"
|
||||
["transform-es2015-block-scoping", { "throwIfClosureRequired": true }]
|
||||
]
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ jobs:
|
||||
build:
|
||||
|
||||
docker:
|
||||
- image: circleci/node:8
|
||||
- image: circleci/openjdk:8-jdk-node-browsers
|
||||
|
||||
environment:
|
||||
TZ: /usr/share/zoneinfo/America/Los_Angeles
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
|
||||
- run:
|
||||
name: Install Packages
|
||||
command: yarn install
|
||||
command: yarn install --frozen-lockfile
|
||||
|
||||
- run:
|
||||
name: Test Packages
|
||||
|
||||
@@ -81,5 +81,6 @@ module.exports = {
|
||||
spyOnDev: true,
|
||||
spyOnDevAndProd: true,
|
||||
spyOnProd: true,
|
||||
__PROFILE__: true,
|
||||
},
|
||||
};
|
||||
|
||||
42
.flowconfig
42
.flowconfig
@@ -1,42 +0,0 @@
|
||||
[ignore]
|
||||
|
||||
<PROJECT_ROOT>/fixtures/.*
|
||||
<PROJECT_ROOT>/build/.*
|
||||
<PROJECT_ROOT>/scripts/bench/.*
|
||||
|
||||
# These shims are copied into external projects:
|
||||
<PROJECT_ROOT>/scripts/rollup/shims/facebook-www/.*
|
||||
<PROJECT_ROOT>/scripts/rollup/shims/react-native/.*
|
||||
|
||||
<PROJECT_ROOT>/.*/node_modules/y18n/.*
|
||||
<PROJECT_ROOT>/node_modules/chrome-devtools-frontend/.*
|
||||
<PROJECT_ROOT>/node_modules/devtools-timeline-model/.*
|
||||
<PROJECT_ROOT>/node_modules/create-react-class/.*
|
||||
<PROJECT_ROOT>/.*/__mocks__/.*
|
||||
<PROJECT_ROOT>/.*/__tests__/.*
|
||||
|
||||
[include]
|
||||
|
||||
[libs]
|
||||
./node_modules/fbjs/flow/lib/dev.js
|
||||
./scripts/flow
|
||||
|
||||
[options]
|
||||
esproposal.class_static_fields=enable
|
||||
esproposal.class_instance_fields=enable
|
||||
unsafe.enable_getters_and_setters=true
|
||||
|
||||
munge_underscores=false
|
||||
|
||||
suppress_type=$FlowIssue
|
||||
suppress_type=$FlowFixMe
|
||||
suppress_type=$FixMe
|
||||
suppress_type=$FlowExpectedError
|
||||
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError
|
||||
|
||||
[version]
|
||||
^0.61.0
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,5 +1,6 @@
|
||||
.DS_STORE
|
||||
node_modules
|
||||
scripts/flow/*/.flowconfig
|
||||
*~
|
||||
*.pyc
|
||||
.grunt
|
||||
@@ -21,4 +22,4 @@ chrome-user-data
|
||||
*.iml
|
||||
.vscode
|
||||
*.swp
|
||||
*.swo
|
||||
*.swo
|
||||
|
||||
82
CHANGELOG.md
82
CHANGELOG.md
@@ -4,8 +4,90 @@
|
||||
Changes that have landed in master but are not yet released.
|
||||
Click to see more.
|
||||
</summary>
|
||||
|
||||
</details>
|
||||
|
||||
## 16.4.1 (June 13, 2018)
|
||||
|
||||
### React
|
||||
|
||||
* You can now assign `propTypes` to components returned by `React.ForwardRef`. ([@bvaughn](https://github.com/bvaughn) in [#12911](https://github.com/facebook/react/pull/12911))
|
||||
|
||||
### React DOM
|
||||
|
||||
* Fix a crash when the input `type` changes from some other types to `text`. ([@spirosikmd](https://github.com/spirosikmd) in [#12135](https://github.com/facebook/react/pull/12135))
|
||||
* Fix a crash in IE11 when restoring focus to an SVG element. ([@ThaddeusJiang](https://github.com/ThaddeusJiang) in [#12996](https://github.com/facebook/react/pull/12996))
|
||||
* Fix a range input not updating in some cases. ([@Illu](https://github.com/Illu) in [#12939](https://github.com/facebook/react/pull/12939))
|
||||
* Fix input validation triggering unnecessarily in Firefox. ([@nhunzaker](https://github.com/nhunzaker) in [#12925](https://github.com/facebook/react/pull/12925))
|
||||
* Fix an incorrect `event.target` value for the `onChange` event in IE9. ([@nhunzaker](https://github.com/nhunzaker) in [#12976](https://github.com/facebook/react/pull/12976))
|
||||
* Fix a false positive error when returning an empty `<React.Fragment />` from a component. ([@philipp-spiess](https://github.com/philipp-spiess) in [#12966](https://github.com/facebook/react/pull/12966))
|
||||
|
||||
### React DOM Server
|
||||
|
||||
* Fix an incorrect value being provided by new context API. ([@ericsoderberghp](https://github.com/ericsoderberghp) in [#12985](https://github.com/facebook/react/pull/12985), [@gaearon](https://github.com/gaearon) in [#13019](https://github.com/facebook/react/pull/13019))
|
||||
|
||||
### React Test Renderer
|
||||
|
||||
* Allow multiple root children in test renderer traversal API. ([@gaearon](https://github.com/gaearon) in [#13017](https://github.com/facebook/react/pull/13017))
|
||||
* Fix `getDerivedStateFromProps()` in the shallow renderer to not discard the pending state. ([@fatfisz](https://github.com/fatfisz) in [#13030](https://github.com/facebook/react/pull/13030))
|
||||
|
||||
## 16.4.0 (May 23, 2018)
|
||||
|
||||
### React
|
||||
|
||||
* Add a new [experimental](https://github.com/reactjs/rfcs/pull/51) `React.unstable_Profiler` component for measuring performance. ([@bvaughn](https://github.com/bvaughn) in [#12745](https://github.com/facebook/react/pull/12745))
|
||||
|
||||
### React DOM
|
||||
|
||||
* Add support for the Pointer Events specification. ([@philipp-spiess](https://github.com/philipp-spiess) in [#12507](https://github.com/facebook/react/pull/12507))
|
||||
* Properly call `getDerivedStateFromProps()` regardless of the reason for re-rendering. ([@acdlite](https://github.com/acdlite) in [#12600](https://github.com/facebook/react/pull/12600) and [#12802](https://github.com/facebook/react/pull/12802))
|
||||
* Fix a bug that prevented context propagation in some cases. ([@gaearon](https://github.com/gaearon) in [#12708](https://github.com/facebook/react/pull/12708))
|
||||
* Fix re-rendering of components using `forwardRef()` on a deeper `setState()`. ([@gaearon](https://github.com/gaearon) in [#12690](https://github.com/facebook/react/pull/12690))
|
||||
* Fix some attributes incorrectly getting removed from custom element nodes. ([@airamrguez](https://github.com/airamrguez) in [#12702](https://github.com/facebook/react/pull/12702))
|
||||
* Fix context providers to not bail out on children if there's a legacy context provider above. ([@gaearon](https://github.com/gaearon) in [#12586](https://github.com/facebook/react/pull/12586))
|
||||
* Add the ability to specify `propTypes` on a context provider component. ([@nicolevy](https://github.com/nicolevy) in [#12658](https://github.com/facebook/react/pull/12658))
|
||||
* Fix a false positive warning when using `react-lifecycles-compat` in `<StrictMode>`. ([@bvaughn](https://github.com/bvaughn) in [#12644](https://github.com/facebook/react/pull/12644))
|
||||
* Warn when the `forwardRef()` render function has `propTypes` or `defaultProps`. ([@bvaughn](https://github.com/bvaughn) in [#12644](https://github.com/facebook/react/pull/12644))
|
||||
* Improve how `forwardRef()` and context consumers are displayed in the component stack. ([@sophiebits](https://github.com/sophiebits) in [#12777](https://github.com/facebook/react/pull/12777))
|
||||
* Change internal event names. This can break third-party packages that rely on React internals in unsupported ways. ([@philipp-spiess](https://github.com/philipp-spiess) in [#12629](https://github.com/facebook/react/pull/12629))
|
||||
|
||||
### React Test Renderer
|
||||
|
||||
* Fix the `getDerivedStateFromProps()` support to match the new React DOM behavior. ([@koba04](https://github.com/koba04) in [#12676](https://github.com/facebook/react/pull/12676))
|
||||
* Fix a `testInstance.parent` crash when the parent is a fragment or another special node. ([@gaearon](https://github.com/gaearon) in [#12813](https://github.com/facebook/react/pull/12813))
|
||||
* `forwardRef()` components are now discoverable by the test renderer traversal methods. ([@gaearon](https://github.com/gaearon) in [#12725](https://github.com/facebook/react/pull/12725))
|
||||
* Shallow renderer now ignores `setState()` updaters that return `null` or `undefined`. ([@koba04](https://github.com/koba04) in [#12756](https://github.com/facebook/react/pull/12756))
|
||||
|
||||
### React ART
|
||||
|
||||
* Fix reading context provided from the tree managed by React DOM. ([@acdlite](https://github.com/acdlite) in [#12779](https://github.com/facebook/react/pull/12779))
|
||||
|
||||
### React Call Return (Experimental)
|
||||
|
||||
* This experiment was deleted because it was affecting the bundle size and the API wasn't good enough. It's likely to come back in the future in some other form. ([@gaearon](https://github.com/gaearon) in [#12820](https://github.com/facebook/react/pull/12820))
|
||||
|
||||
### React Reconciler (Experimental)
|
||||
|
||||
* The [new host config shape](https://github.com/facebook/react/blob/c601f7a64640290af85c9f0e33c78480656b46bc/packages/react-noop-renderer/src/createReactNoop.js#L82-L285) is flat and doesn't use nested objects. ([@gaearon](https://github.com/gaearon) in [#12792](https://github.com/facebook/react/pull/12792))
|
||||
|
||||
## 16.3.2 (April 16, 2018)
|
||||
|
||||
### React
|
||||
|
||||
* Improve the error message when passing `null` or `undefined` to `React.cloneElement`. ([@nicolevy](https://github.com/nicolevy) in [#12534](https://github.com/facebook/react/pull/12534))
|
||||
|
||||
### React DOM
|
||||
|
||||
* Fix an IE crash in development when using `<StrictMode>`. ([@bvaughn](https://github.com/bvaughn) in [#12546](https://github.com/facebook/react/pull/12546))
|
||||
* Fix labels in User Timing measurements for new component types. ([@bvaughn](https://github.com/bvaughn) in [#12609](https://github.com/facebook/react/pull/12609))
|
||||
* Improve the warning about wrong component type casing. ([@nicolevy](https://github.com/nicolevy) in [#12533](https://github.com/facebook/react/pull/12533))
|
||||
* Improve general performance in development mode. ([@gaearon](https://github.com/gaearon) in [#12537](https://github.com/facebook/react/pull/12537))
|
||||
* Improve performance of the experimental `unstable_observedBits` API with nesting. ([@gaearon](https://github.com/gaearon) in [#12543](https://github.com/facebook/react/pull/12543))
|
||||
|
||||
### React Test Renderer
|
||||
|
||||
* Add a UMD build. ([@bvaughn](https://github.com/bvaughn) in [#12594](https://github.com/facebook/react/pull/12594))
|
||||
|
||||
## 16.3.1 (April 3, 2018)
|
||||
|
||||
### React
|
||||
|
||||
37
README.md
37
README.md
@@ -8,15 +8,28 @@ React is a JavaScript library for building user interfaces.
|
||||
|
||||
[Learn how to use React in your own project](https://reactjs.org/docs/getting-started.html).
|
||||
|
||||
## Installation
|
||||
|
||||
React has been designed for gradual adoption from the start, and **you can use as little or as much React as you need**:
|
||||
|
||||
* Use [Online Playgrounds](https://reactjs.org/docs/getting-started.html#online-playgrounds) to get a taste of React.
|
||||
* [Add React to a Website](https://reactjs.org/docs/add-react-to-a-website.html) as a `<script>` tag in one minute.
|
||||
* [Create a New React App](https://reactjs.org/docs/create-a-new-react-app.html) if you're looking for a powerful JavaScript toolchain.
|
||||
|
||||
You can use React as a `<script>` tag from a [CDN](https://reactjs.org/docs/cdn-links.html), or as a `react` package on [npm](https://www.npmjs.com/).
|
||||
|
||||
## Documentation
|
||||
|
||||
You can find the React documentation [on the website](https://reactjs.org/docs).
|
||||
It is divided into several sections:
|
||||
|
||||
* [Quick Start](https://reactjs.org/docs/hello-world.html)
|
||||
Check out the [Getting Started](https://reactjs.org/docs/getting-started.html) page for a quick overview.
|
||||
|
||||
The documentation is divided into several sections:
|
||||
|
||||
* [Tutorial](https://reactjs.org/tutorial/tutorial.html)
|
||||
* [Main Concepts](https://reactjs.org/docs/hello-world.html)
|
||||
* [Advanced Guides](https://reactjs.org/docs/jsx-in-depth.html)
|
||||
* [API Reference](https://reactjs.org/docs/react-api.html)
|
||||
* [Tutorial](https://reactjs.org/tutorial/tutorial.html)
|
||||
* [Where to Get Support](https://reactjs.org/community/support.html)
|
||||
* [Contributing Guide](https://reactjs.org/docs/how-to-contribute.html)
|
||||
|
||||
@@ -34,26 +47,14 @@ class HelloMessage extends React.Component {
|
||||
}
|
||||
|
||||
ReactDOM.render(
|
||||
<HelloMessage name="John" />,
|
||||
<HelloMessage name="Taylor" />,
|
||||
document.getElementById('container')
|
||||
);
|
||||
```
|
||||
|
||||
This example will render "Hello John" into a container on the page.
|
||||
This example will render "Hello Taylor" into a container on the page.
|
||||
|
||||
You'll notice that we used an HTML-like syntax; [we call it JSX](https://reactjs.org/docs/introducing-jsx.html). JSX is not required to use React, but it makes code more readable, and writing it feels like writing HTML. We recommend using [Babel](https://babeljs.io/) with a [React preset](https://babeljs.io/docs/plugins/preset-react/) to convert JSX into native JavaScript for browsers to digest.
|
||||
|
||||
## Installation
|
||||
|
||||
React is available as the `react` package on [npm](https://www.npmjs.com/). It is also available on a [CDN](https://reactjs.org/docs/cdn-links.html).
|
||||
|
||||
React is flexible and can be used in a variety of projects. You can create new apps with it, but you can also gradually introduce it into an existing codebase without doing a rewrite.
|
||||
|
||||
The recommended way to install React depends on your project. Here you can find short guides for the most common scenarios:
|
||||
|
||||
* [Trying Out React](https://reactjs.org/docs/try-react.html)
|
||||
* [Creating a New Application](https://reactjs.org/docs/add-react-to-a-new-app.html)
|
||||
* [Adding React to an Existing Application](https://reactjs.org/docs/add-react-to-an-existing-app.html)
|
||||
You'll notice that we used an HTML-like syntax; [we call it JSX](https://reactjs.org/docs/introducing-jsx.html). JSX is not required to use React, but it makes code more readable, and writing it feels like writing HTML.
|
||||
|
||||
## Contributing
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@ init:
|
||||
- git config --global core.autocrlf input
|
||||
|
||||
environment:
|
||||
matrix:
|
||||
- nodejs_version: 8
|
||||
nodejs_version: 8
|
||||
JAVA_HOME: C:\Program Files\Java\jdk1.8.0
|
||||
|
||||
# Finish on first failed build
|
||||
matrix:
|
||||
@@ -25,12 +25,12 @@ deploy: off
|
||||
|
||||
install:
|
||||
- ps: Install-Product node $env:nodejs_version $env:platform
|
||||
- yarn install
|
||||
- yarn install --frozen-lockfile
|
||||
|
||||
test_script:
|
||||
- node --version
|
||||
- yarn lint
|
||||
- yarn flow
|
||||
# - yarn flow-ci
|
||||
- yarn build
|
||||
- yarn test
|
||||
- yarn prettier
|
||||
|
||||
@@ -53,7 +53,8 @@ function generateMDTable(headers, body) {
|
||||
|
||||
/**
|
||||
* Generates a user-readable string from a percentage change
|
||||
* @param {string[]} headers
|
||||
* @param {number} change
|
||||
* @param {boolean} includeEmoji
|
||||
*/
|
||||
function addPercent(change, includeEmoji) {
|
||||
if (!isFinite(change)) {
|
||||
|
||||
@@ -64,6 +64,8 @@ class Header extends React.Component {
|
||||
<option value="/event-pooling">Event Pooling</option>
|
||||
<option value="/custom-elements">Custom Elements</option>
|
||||
<option value="/media-events">Media Events</option>
|
||||
<option value="/pointer-events">Pointer Events</option>
|
||||
<option value="/mouse-events">Mouse Events</option>
|
||||
</select>
|
||||
</label>
|
||||
<label htmlFor="react_version">
|
||||
|
||||
@@ -11,6 +11,8 @@ import ErrorHandling from './error-handling';
|
||||
import EventPooling from './event-pooling';
|
||||
import CustomElementFixtures from './custom-elements';
|
||||
import MediaEventsFixtures from './media-events';
|
||||
import PointerEventsFixtures from './pointer-events';
|
||||
import MouseEventsFixtures from './mouse-events';
|
||||
|
||||
const React = window.React;
|
||||
|
||||
@@ -46,6 +48,10 @@ function FixturesPage() {
|
||||
return <CustomElementFixtures />;
|
||||
case '/media-events':
|
||||
return <MediaEventsFixtures />;
|
||||
case '/pointer-events':
|
||||
return <PointerEventsFixtures />;
|
||||
case '/mouse-events':
|
||||
return <MouseEventsFixtures />;
|
||||
default:
|
||||
return <p>Please select a test fixture.</p>;
|
||||
}
|
||||
|
||||
16
fixtures/dom/src/components/fixtures/mouse-events/index.js
Normal file
16
fixtures/dom/src/components/fixtures/mouse-events/index.js
Normal file
@@ -0,0 +1,16 @@
|
||||
import FixtureSet from '../../FixtureSet';
|
||||
import MouseMovement from './mouse-movement';
|
||||
|
||||
const React = window.React;
|
||||
|
||||
class MouseEvents extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<FixtureSet title="Mouse Events" description="">
|
||||
<MouseMovement />
|
||||
</FixtureSet>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default MouseEvents;
|
||||
@@ -0,0 +1,48 @@
|
||||
import TestCase from '../../TestCase';
|
||||
|
||||
const React = window.React;
|
||||
|
||||
class MouseMovement extends React.Component {
|
||||
state = {
|
||||
movement: {x: 0, y: 0},
|
||||
};
|
||||
|
||||
onMove = event => {
|
||||
this.setState({x: event.movementX, y: event.movementY});
|
||||
};
|
||||
|
||||
render() {
|
||||
const {x, y} = this.state;
|
||||
|
||||
const boxStyle = {
|
||||
padding: '10px 20px',
|
||||
border: '1px solid #d9d9d9',
|
||||
margin: '10px 0 20px',
|
||||
};
|
||||
|
||||
return (
|
||||
<TestCase
|
||||
title="Mouse Movement"
|
||||
description="We polyfill the movementX and movementY fields."
|
||||
affectedBrowsers="IE, Safari">
|
||||
<TestCase.Steps>
|
||||
<li>Mouse over the box below</li>
|
||||
</TestCase.Steps>
|
||||
|
||||
<TestCase.ExpectedResult>
|
||||
The reported values should equal the pixel (integer) difference
|
||||
between mouse movements positions.
|
||||
</TestCase.ExpectedResult>
|
||||
|
||||
<div style={boxStyle} onMouseMove={this.onMove}>
|
||||
<p>Trace your mouse over this box.</p>
|
||||
<p>
|
||||
Last movement: {x},{y}
|
||||
</p>
|
||||
</div>
|
||||
</TestCase>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default MouseMovement;
|
||||
@@ -15,7 +15,7 @@ function NumberInputs() {
|
||||
`}
|
||||
affectedBrowsers="IE Edge, IE 11">
|
||||
<TestCase.Steps>
|
||||
<li>Type any string (not an actual password</li>
|
||||
<li>Type any string (not an actual password)</li>
|
||||
</TestCase.Steps>
|
||||
|
||||
<TestCase.ExpectedResult>
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
const React = window.React;
|
||||
|
||||
const CIRCLE_SIZE = 80;
|
||||
|
||||
class DragBox extends React.Component {
|
||||
state = {
|
||||
hasCapture: false,
|
||||
circleLeft: 80,
|
||||
circleTop: 80,
|
||||
};
|
||||
isDragging = false;
|
||||
previousLeft = 0;
|
||||
previousTop = 0;
|
||||
|
||||
onDown = event => {
|
||||
this.isDragging = true;
|
||||
event.target.setPointerCapture(event.pointerId);
|
||||
|
||||
// We store the initial coordinates to be able to calculate the changes
|
||||
// later on.
|
||||
this.extractPositionDelta(event);
|
||||
};
|
||||
|
||||
onMove = event => {
|
||||
if (!this.isDragging) {
|
||||
return;
|
||||
}
|
||||
const {left, top} = this.extractPositionDelta(event);
|
||||
|
||||
this.setState(({circleLeft, circleTop}) => ({
|
||||
circleLeft: circleLeft + left,
|
||||
circleTop: circleTop + top,
|
||||
}));
|
||||
};
|
||||
|
||||
onUp = event => (this.isDragging = false);
|
||||
onGotCapture = event => this.setState({hasCapture: true});
|
||||
onLostCapture = event => this.setState({hasCapture: false});
|
||||
|
||||
extractPositionDelta = event => {
|
||||
const left = event.pageX;
|
||||
const top = event.pageY;
|
||||
const delta = {
|
||||
left: left - this.previousLeft,
|
||||
top: top - this.previousTop,
|
||||
};
|
||||
this.previousLeft = left;
|
||||
this.previousTop = top;
|
||||
return delta;
|
||||
};
|
||||
|
||||
render() {
|
||||
const {hasCapture, circleLeft, circleTop} = this.state;
|
||||
|
||||
const boxStyle = {
|
||||
border: '1px solid #d9d9d9',
|
||||
margin: '10px 0 20px',
|
||||
minHeight: 400,
|
||||
width: '100%',
|
||||
position: 'relative',
|
||||
};
|
||||
|
||||
const circleStyle = {
|
||||
width: CIRCLE_SIZE,
|
||||
height: CIRCLE_SIZE,
|
||||
borderRadius: CIRCLE_SIZE / 2,
|
||||
position: 'absolute',
|
||||
left: circleLeft,
|
||||
top: circleTop,
|
||||
backgroundColor: hasCapture ? 'blue' : 'green',
|
||||
touchAction: 'none',
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={boxStyle}>
|
||||
<div
|
||||
style={circleStyle}
|
||||
onPointerDown={this.onDown}
|
||||
onPointerMove={this.onMove}
|
||||
onPointerUp={this.onUp}
|
||||
onPointerCancel={this.onUp}
|
||||
onGotPointerCapture={this.onGotCapture}
|
||||
onLostPointerCapture={this.onLostCapture}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default DragBox;
|
||||
25
fixtures/dom/src/components/fixtures/pointer-events/drag.js
Normal file
25
fixtures/dom/src/components/fixtures/pointer-events/drag.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import TestCase from '../../TestCase';
|
||||
import DragBox from './drag-box';
|
||||
|
||||
const React = window.React;
|
||||
|
||||
class Drag extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<TestCase title="Drag" description="">
|
||||
<TestCase.Steps>
|
||||
<li>Drag the circle below with any pointer tool</li>
|
||||
</TestCase.Steps>
|
||||
|
||||
<TestCase.ExpectedResult>
|
||||
While dragging, the circle must have turn blue to indicate that a
|
||||
pointer capture was received.
|
||||
</TestCase.ExpectedResult>
|
||||
|
||||
<DragBox />
|
||||
</TestCase>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Drag;
|
||||
@@ -0,0 +1,34 @@
|
||||
const React = window.React;
|
||||
|
||||
class DrawBox extends React.Component {
|
||||
render() {
|
||||
const boxStyle = {
|
||||
border: '1px solid #d9d9d9',
|
||||
margin: '10px 0 20px',
|
||||
padding: '20px 20px',
|
||||
touchAction: 'none',
|
||||
};
|
||||
|
||||
const obstacleStyle = {
|
||||
border: '1px solid #d9d9d9',
|
||||
width: '25%',
|
||||
height: '200px',
|
||||
margin: '12.5%',
|
||||
display: 'inline-block',
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
style={boxStyle}
|
||||
onPointerOver={this.props.onOver}
|
||||
onPointerOut={this.props.onOut}
|
||||
onPointerEnter={this.props.onEnter}
|
||||
onPointerLeave={this.props.onLeave}>
|
||||
<div style={obstacleStyle} />
|
||||
<div style={obstacleStyle} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default DrawBox;
|
||||
51
fixtures/dom/src/components/fixtures/pointer-events/hover.js
Normal file
51
fixtures/dom/src/components/fixtures/pointer-events/hover.js
Normal file
@@ -0,0 +1,51 @@
|
||||
import TestCase from '../../TestCase';
|
||||
import HoverBox from './hover-box';
|
||||
|
||||
const React = window.React;
|
||||
|
||||
class Hover extends React.Component {
|
||||
state = {
|
||||
overs: 0,
|
||||
outs: 0,
|
||||
enters: 0,
|
||||
leaves: 0,
|
||||
};
|
||||
|
||||
onOver = () => this.setState({overs: this.state.overs + 1});
|
||||
onOut = () => this.setState({outs: this.state.outs + 1});
|
||||
onEnter = () => this.setState({enters: this.state.enters + 1});
|
||||
onLeave = () => this.setState({leaves: this.state.leaves + 1});
|
||||
|
||||
render() {
|
||||
const {overs, outs, enters, leaves} = this.state;
|
||||
|
||||
return (
|
||||
<TestCase title="Hover" description="">
|
||||
<TestCase.Steps>
|
||||
<li>Hover over the above box and the obstacles</li>
|
||||
</TestCase.Steps>
|
||||
|
||||
<TestCase.ExpectedResult>
|
||||
Overs and outs should increase when moving over the obstacles but
|
||||
enters and leaves should not.
|
||||
</TestCase.ExpectedResult>
|
||||
|
||||
<HoverBox
|
||||
onOver={this.onOver}
|
||||
onOut={this.onOut}
|
||||
onEnter={this.onEnter}
|
||||
onLeave={this.onLeave}
|
||||
/>
|
||||
|
||||
<p>
|
||||
Pointer Overs: <b>{overs}</b> <br />
|
||||
Pointer Outs: <b>{outs}</b> <br />
|
||||
Pointer Enters: <b>{enters}</b> <br />
|
||||
Pointer Leaves: <b>{leaves}</b> <br />
|
||||
</p>
|
||||
</TestCase>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Hover;
|
||||
20
fixtures/dom/src/components/fixtures/pointer-events/index.js
Normal file
20
fixtures/dom/src/components/fixtures/pointer-events/index.js
Normal file
@@ -0,0 +1,20 @@
|
||||
import FixtureSet from '../../FixtureSet';
|
||||
import Drag from './drag';
|
||||
import Hover from './hover';
|
||||
|
||||
const React = window.React;
|
||||
|
||||
class PointerEvents extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<FixtureSet
|
||||
title="Pointer Events"
|
||||
description="Pointer Events are not supported in every browser. The examples below might not work in every browser. To test pointer events, make sure to use Google Chrome, Firefox, Internet Explorer, or Edge.">
|
||||
<Drag />
|
||||
<Hover />
|
||||
</FixtureSet>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default PointerEvents;
|
||||
@@ -0,0 +1,40 @@
|
||||
import Fixture from '../../Fixture';
|
||||
|
||||
const React = window.React;
|
||||
|
||||
class ReplaceEmailInput extends React.Component {
|
||||
state = {
|
||||
formSubmitted: false,
|
||||
};
|
||||
|
||||
onReset = () => {
|
||||
this.setState({formSubmitted: false});
|
||||
};
|
||||
|
||||
onSubmit = event => {
|
||||
event.preventDefault();
|
||||
this.setState({formSubmitted: true});
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Fixture>
|
||||
<form className="control-box" onSubmit={this.onSubmit}>
|
||||
<fieldset>
|
||||
<legend>Email</legend>
|
||||
{!this.state.formSubmitted ? (
|
||||
<input type="email" />
|
||||
) : (
|
||||
<input type="text" disabled={true} />
|
||||
)}
|
||||
</fieldset>
|
||||
</form>
|
||||
<button type="button" onClick={this.onReset}>
|
||||
Reset
|
||||
</button>
|
||||
</Fixture>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ReplaceEmailInput;
|
||||
@@ -2,6 +2,7 @@ import Fixture from '../../Fixture';
|
||||
import FixtureSet from '../../FixtureSet';
|
||||
import TestCase from '../../TestCase';
|
||||
import InputTestCase from './InputTestCase';
|
||||
import ReplaceEmailInput from './ReplaceEmailInput';
|
||||
|
||||
const React = window.React;
|
||||
|
||||
@@ -64,21 +65,36 @@ class TextInputFixtures extends React.Component {
|
||||
<Fixture>
|
||||
<form className="control-box">
|
||||
<fieldset>
|
||||
<legend>Text</legend>
|
||||
<legend>Empty value prop string</legend>
|
||||
<input value="" required={true} />
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>No value prop</legend>
|
||||
<input required={true} />
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>Date</legend>
|
||||
<legend>Empty defaultValue prop string</legend>
|
||||
<input required={true} defaultValue="" />
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>No value prop date input</legend>
|
||||
<input type="date" required={true} />
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>Empty value prop date input</legend>
|
||||
<input type="date" value="" required={true} />
|
||||
</fieldset>
|
||||
</form>
|
||||
</Fixture>
|
||||
|
||||
<p className="footnote">
|
||||
Checking the date type is also important because of a prior fix for
|
||||
iOS Safari that involved assigning over value/defaultValue
|
||||
properties of the input to prevent a display bug. This also
|
||||
triggered input validation.
|
||||
properties of the input to prevent a display bug. This also triggers
|
||||
input validation.
|
||||
</p>
|
||||
<p className="footnote">
|
||||
The date inputs should be blank in iOS. This is not a bug.
|
||||
</p>
|
||||
</TestCase>
|
||||
|
||||
@@ -110,6 +126,21 @@ class TextInputFixtures extends React.Component {
|
||||
<InputTestCase type="url" defaultValue="" />
|
||||
</TestCase>
|
||||
|
||||
<TestCase
|
||||
title="Replacing email input with text disabled input"
|
||||
relatedIssues="12062">
|
||||
<TestCase.Steps>
|
||||
<li>Type "test@test.com"</li>
|
||||
<li>Press enter</li>
|
||||
</TestCase.Steps>
|
||||
|
||||
<TestCase.ExpectedResult>
|
||||
There should be no selection-related error in the console.
|
||||
</TestCase.ExpectedResult>
|
||||
|
||||
<ReplaceEmailInput />
|
||||
</TestCase>
|
||||
|
||||
<TestCase title="All inputs" description="General test of all inputs">
|
||||
<InputTestCase type="text" defaultValue="Text" />
|
||||
<InputTestCase type="email" defaultValue="user@example.com" />
|
||||
|
||||
494
fixtures/schedule/index.html
Normal file
494
fixtures/schedule/index.html
Normal file
@@ -0,0 +1,494 @@
|
||||
<!DOCTYPE html>
|
||||
<html style="width: 100%; height: 100%;">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Schedule Test Page</title>
|
||||
<style>
|
||||
.correct {
|
||||
border: solid green 2px;
|
||||
}
|
||||
.incorrect {
|
||||
border: dashed red 2px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Schedule Fixture</h1>
|
||||
<p>
|
||||
This fixture is for manual testing purposes, and the patterns used in
|
||||
implementing it should not be used as a model. This is mainly for anyone
|
||||
working on making changes to the `schedule` module.
|
||||
</p>
|
||||
<h2>Tests:</h2>
|
||||
<ol>
|
||||
<li>
|
||||
<button onClick="runTestOne()">Run Test 1</button>
|
||||
<p>Calls the callback within the frame when not blocked:</p>
|
||||
<div><b>Expected:</b></div>
|
||||
<div id="test-1-expected">
|
||||
</div>
|
||||
<div> -------------------------------------------------</div>
|
||||
<div> If you see the same above and below it's correct.
|
||||
<div> -------------------------------------------------</div>
|
||||
<div><b>Actual:</b></div>
|
||||
<div id="test-1"></div>
|
||||
</li>
|
||||
<li>
|
||||
<p>Accepts multiple callbacks and calls within frame when not blocked</p>
|
||||
<button onClick="runTestTwo()">Run Test 2</button>
|
||||
<div><b>Expected:</b></div>
|
||||
<div id="test-2-expected">
|
||||
</div>
|
||||
<div> -------------------------------------------------</div>
|
||||
<div> If you see the same above and below it's correct.
|
||||
<div> -------------------------------------------------</div>
|
||||
<div><b>Actual:</b></div>
|
||||
<div id="test-2"></div>
|
||||
</li>
|
||||
<li>
|
||||
<p>Schedules callbacks in correct order when they use scheduleWork to schedule themselves</p>
|
||||
<button onClick="runTestThree()">Run Test 3</button>
|
||||
<div><b>Expected:</b></div>
|
||||
<div id="test-3-expected">
|
||||
</div>
|
||||
<div> -------------------------------------------------</div>
|
||||
<div> If you see the same above and below it's correct.
|
||||
<div> -------------------------------------------------</div>
|
||||
<div><b>Actual:</b></div>
|
||||
<div id="test-3"></div>
|
||||
</li>
|
||||
<li>
|
||||
<p>Calls timed out callbacks and then any more pending callbacks, defers others if time runs out</p>
|
||||
<button onClick="runTestFour()">Run Test 4</button>
|
||||
<div><b>Expected:</b></div>
|
||||
<div id="test-4-expected">
|
||||
</div>
|
||||
<div> -------------------------------------------------</div>
|
||||
<div> If you see the same above and below it's correct.
|
||||
<div> -------------------------------------------------</div>
|
||||
<div><b>Actual:</b></div>
|
||||
<div id="test-4"></div>
|
||||
</li>
|
||||
<li>
|
||||
<p>When some callbacks throw errors, still calls them all within the same frame</p>
|
||||
<p><b>IMPORTANT:</b> Open the console when you run this! Inspect the logs there!</p>
|
||||
<button onClick="runTestFive()">Run Test 5</button>
|
||||
</li>
|
||||
<li>
|
||||
<p>When some callbacks throw errors <b> and some also time out</b>, still calls them all within the same frame</p>
|
||||
<p><b>IMPORTANT:</b> Open the console when you run this! Inspect the logs there!</p>
|
||||
<button onClick="runTestSix()">Run Test 6</button>
|
||||
</li>
|
||||
<li>
|
||||
<p>Continues calling callbacks even when user switches away from this tab</p>
|
||||
<button onClick="runTestSeven()">Run Test 7</button>
|
||||
<div><b>Click the button above, observe the counter, then switch to
|
||||
another tab and switch back:</b></div>
|
||||
<div id="test-7">
|
||||
</div>
|
||||
<div> If the counter advanced while you were away from this tab, it's correct.</div>
|
||||
</li>
|
||||
</ol>
|
||||
<script src="../../build/dist/react-scheduler.development.js"></script>
|
||||
<script src="https://unpkg.com/babel-standalone@6/babel.js"></script>
|
||||
<script type="text/babel">
|
||||
const {scheduleWork, cancelWork, now} = ReactScheduler;
|
||||
function displayTestResult(testNumber) {
|
||||
const expectationNode = document.getElementById('test-' + testNumber + '-expected');
|
||||
const resultNode = document.getElementById('test-' + testNumber);
|
||||
resultNode.innerHTML = latestResults[testNumber - 1].join('<br />');
|
||||
expectationNode.innerHTML = expectedResults[testNumber - 1].join('<br />');
|
||||
}
|
||||
function clearTestResult(testNumber) {
|
||||
const resultNode = document.getElementById('test-' + testNumber);
|
||||
resultNode.innerHTML = '';
|
||||
latestResults[testNumber - 1] = [];
|
||||
}
|
||||
function updateTestResult(testNumber, textToAddToResult) {
|
||||
latestResults[testNumber - 1].push(textToAddToResult);
|
||||
};
|
||||
function checkTestResult(testNumber) {
|
||||
|
||||
let correct = true;
|
||||
const expected = expectedResults[testNumber - 1]; // zero indexing
|
||||
const result = latestResults[testNumber - 1]; // zero indexing
|
||||
if (expected.length !== result.length) {
|
||||
correct = false;
|
||||
} else {
|
||||
for (let i = 0, len = expected.length; i < len; i++) {
|
||||
if (expected[i] !== result[i]) {
|
||||
correct = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
const currentClass = correct ? 'correct' : 'incorrect';
|
||||
const previousClass = correct ? 'incorrect' : 'correct';
|
||||
document.getElementById('test-' + testNumber).classList.remove(previousClass);
|
||||
document.getElementById('test-' + testNumber).classList.add(currentClass);
|
||||
}
|
||||
function logWhenFramesStart(testNumber, cb) {
|
||||
requestAnimationFrame(() => {
|
||||
updateTestResult(testNumber, 'frame 1 started');
|
||||
requestAnimationFrame(() => {
|
||||
updateTestResult(testNumber, 'frame 2 started');
|
||||
requestAnimationFrame(() => {
|
||||
updateTestResult(testNumber, 'frame 3 started... we stop counting now.');
|
||||
cb();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
// push in results when we run the test
|
||||
const latestResults = [
|
||||
// test 1
|
||||
[
|
||||
],
|
||||
// test 2
|
||||
[
|
||||
],
|
||||
// test 3
|
||||
[
|
||||
],
|
||||
// test 4
|
||||
[
|
||||
],
|
||||
// test 5
|
||||
[
|
||||
],
|
||||
];
|
||||
|
||||
const expectedResults = [
|
||||
// test 1
|
||||
[
|
||||
'scheduled Cb1',
|
||||
'frame 1 started',
|
||||
'cb1 called with argument of {"didTimeout":false}',
|
||||
'frame 2 started',
|
||||
'frame 3 started... we stop counting now.',
|
||||
],
|
||||
// test 2
|
||||
[
|
||||
'scheduled CbA',
|
||||
'scheduled CbB',
|
||||
'frame 1 started',
|
||||
'cbA called with argument of {"didTimeout":false}',
|
||||
'cbB called with argument of {"didTimeout":false}',
|
||||
'frame 2 started',
|
||||
'frame 3 started... we stop counting now.',
|
||||
],
|
||||
// test 3
|
||||
[
|
||||
'scheduled CbA',
|
||||
'scheduled CbB',
|
||||
'frame 1 started',
|
||||
'scheduled CbA again',
|
||||
'cbA0 called with argument of {"didTimeout":false}',
|
||||
'cbB called with argument of {"didTimeout":false}',
|
||||
'cbA1 called with argument of {"didTimeout":false}',
|
||||
'frame 2 started',
|
||||
'frame 3 started... we stop counting now.',
|
||||
],
|
||||
// test 4
|
||||
[
|
||||
'scheduled cbA',
|
||||
'scheduled cbB',
|
||||
'scheduled cbC',
|
||||
'scheduled cbD',
|
||||
'frame 1 started',
|
||||
'cbC called with argument of {"didTimeout":true}',
|
||||
'cbA called with argument of {"didTimeout":false}',
|
||||
'cbA running and taking some time',
|
||||
'frame 2 started',
|
||||
'cbB called with argument of {"didTimeout":false}',
|
||||
'cbD called with argument of {"didTimeout":false}',
|
||||
'frame 3 started... we stop counting now.',
|
||||
],
|
||||
// test 5
|
||||
[
|
||||
// ... TODO
|
||||
],
|
||||
];
|
||||
function runTestOne() {
|
||||
// Test 1
|
||||
// Calls the callback with the frame when not blocked
|
||||
clearTestResult(1);
|
||||
const test1Log = [];
|
||||
const cb1Arguments = [];
|
||||
const cb1 = (x) => {
|
||||
updateTestResult(1, 'cb1 called with argument of ' + JSON.stringify(x));
|
||||
}
|
||||
scheduleWork(cb1);
|
||||
updateTestResult(1, 'scheduled Cb1');
|
||||
logWhenFramesStart(1, () => {
|
||||
displayTestResult(1);
|
||||
checkTestResult(1);
|
||||
});
|
||||
};
|
||||
|
||||
function runTestTwo() {
|
||||
// Test 2
|
||||
// accepts multiple callbacks and calls within frame when not blocked
|
||||
clearTestResult(2);
|
||||
const cbA = (x) => {
|
||||
updateTestResult(2, 'cbA called with argument of ' + JSON.stringify(x));
|
||||
}
|
||||
const cbB = (x) => {
|
||||
updateTestResult(2, 'cbB called with argument of ' + JSON.stringify(x));
|
||||
}
|
||||
scheduleWork(cbA);
|
||||
updateTestResult(2, 'scheduled CbA');
|
||||
scheduleWork(cbB);
|
||||
updateTestResult(2, 'scheduled CbB');
|
||||
logWhenFramesStart(2, () => {
|
||||
displayTestResult(2);
|
||||
checkTestResult(2);
|
||||
});
|
||||
}
|
||||
|
||||
function runTestThree() {
|
||||
// Test 3
|
||||
// Schedules callbacks in correct order when they use scheduleWork to schedule themselves
|
||||
clearTestResult(3);
|
||||
let callbackAIterations = 0;
|
||||
const cbA = (x) => {
|
||||
if (callbackAIterations < 1) {
|
||||
scheduleWork(cbA);
|
||||
updateTestResult(3, 'scheduled CbA again');
|
||||
}
|
||||
updateTestResult(3, 'cbA' + callbackAIterations + ' called with argument of ' + JSON.stringify(x));
|
||||
callbackAIterations++;
|
||||
}
|
||||
const cbB = (x) => {
|
||||
updateTestResult(3, 'cbB called with argument of ' + JSON.stringify(x));
|
||||
}
|
||||
scheduleWork(cbA);
|
||||
updateTestResult(3, 'scheduled CbA');
|
||||
scheduleWork(cbB);
|
||||
updateTestResult(3, 'scheduled CbB');
|
||||
logWhenFramesStart(3, () => {
|
||||
displayTestResult(3);
|
||||
checkTestResult(3);
|
||||
});
|
||||
}
|
||||
|
||||
function waitForTimeToPass(timeInMs) {
|
||||
const startTime = Date.now();
|
||||
const endTime = startTime + timeInMs;
|
||||
while (Date.now() < endTime) {
|
||||
// wait...
|
||||
}
|
||||
}
|
||||
|
||||
function runTestFour() {
|
||||
// Test 4
|
||||
// Calls timed out callbacks and then any more pending callbacks, defers others if time runs out
|
||||
clearTestResult(4);
|
||||
const cbA = (x) => {
|
||||
updateTestResult(4, 'cbA called with argument of ' + JSON.stringify(x));
|
||||
updateTestResult(4, 'cbA running and taking some time');
|
||||
waitForTimeToPass(35);
|
||||
}
|
||||
const cbB = (x) => {
|
||||
updateTestResult(4, 'cbB called with argument of ' + JSON.stringify(x));
|
||||
}
|
||||
const cbC = (x) => {
|
||||
updateTestResult(4, 'cbC called with argument of ' + JSON.stringify(x));
|
||||
}
|
||||
const cbD = (x) => {
|
||||
updateTestResult(4, 'cbD called with argument of ' + JSON.stringify(x));
|
||||
}
|
||||
scheduleWork(cbA); // won't time out
|
||||
updateTestResult(4, 'scheduled cbA');
|
||||
scheduleWork(cbB, {timeout: 100}); // times out later
|
||||
updateTestResult(4, 'scheduled cbB');
|
||||
scheduleWork(cbC, {timeout: 1}); // will time out fast
|
||||
updateTestResult(4, 'scheduled cbC');
|
||||
scheduleWork(cbD); // won't time out
|
||||
updateTestResult(4, 'scheduled cbD');
|
||||
|
||||
// should have run in order of C, A, B, D
|
||||
|
||||
logWhenFramesStart(4, () => {
|
||||
displayTestResult(4);
|
||||
checkTestResult(4);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
// Error handling
|
||||
|
||||
function runTestFive() {
|
||||
// Test 5
|
||||
// When some callbacks throw errors, still calls them all within the same frame
|
||||
const cbA = (x) => {
|
||||
console.log('cbA called with argument of ' + JSON.stringify(x));
|
||||
}
|
||||
const cbB = (x) => {
|
||||
console.log('cbB called with argument of ' + JSON.stringify(x));
|
||||
console.log('cbB is about to throw an error!');
|
||||
throw new Error('error B');
|
||||
}
|
||||
const cbC = (x) => {
|
||||
console.log('cbC called with argument of ' + JSON.stringify(x));
|
||||
}
|
||||
const cbD = (x) => {
|
||||
console.log('cbD called with argument of ' + JSON.stringify(x));
|
||||
console.log('cbD is about to throw an error!');
|
||||
throw new Error('error D');
|
||||
}
|
||||
const cbE = (x) => {
|
||||
console.log('cbE called with argument of ' + JSON.stringify(x));
|
||||
console.log('This was the last callback! ------------------');
|
||||
}
|
||||
|
||||
console.log('We are aiming to roughly emulate the way ' +
|
||||
'`requestAnimationFrame` handles errors from callbacks.');
|
||||
|
||||
console.log('about to run the simulation of what it should look like...:');
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
console.log('frame 1 started');
|
||||
requestAnimationFrame(() => {
|
||||
console.log('frame 2 started');
|
||||
requestAnimationFrame(() => {
|
||||
console.log('frame 3 started... we stop counting now.');
|
||||
console.log('about to wait a moment and start this again but ' +
|
||||
'with the scheduler instead of requestAnimationFrame');
|
||||
setTimeout(runSchedulerCode, 1000);
|
||||
});
|
||||
});
|
||||
});
|
||||
requestAnimationFrame(cbA);
|
||||
console.log('scheduled cbA');
|
||||
requestAnimationFrame(cbB); // will throw error
|
||||
console.log('scheduled cbB');
|
||||
requestAnimationFrame(cbC);
|
||||
console.log('scheduled cbC');
|
||||
requestAnimationFrame(cbD); // will throw error
|
||||
console.log('scheduled cbD');
|
||||
requestAnimationFrame(cbE);
|
||||
console.log('scheduled cbE');
|
||||
|
||||
|
||||
function runSchedulerCode() {
|
||||
console.log('-------------------------------------------------------------');
|
||||
console.log('now lets see what it looks like using the scheduler...:');
|
||||
requestAnimationFrame(() => {
|
||||
console.log('frame 1 started');
|
||||
requestAnimationFrame(() => {
|
||||
console.log('frame 2 started');
|
||||
requestAnimationFrame(() => {
|
||||
console.log('frame 3 started... we stop counting now.');
|
||||
});
|
||||
});
|
||||
});
|
||||
scheduleWork(cbA);
|
||||
console.log('scheduled cbA');
|
||||
scheduleWork(cbB); // will throw error
|
||||
console.log('scheduled cbB');
|
||||
scheduleWork(cbC);
|
||||
console.log('scheduled cbC');
|
||||
scheduleWork(cbD); // will throw error
|
||||
console.log('scheduled cbD');
|
||||
scheduleWork(cbE);
|
||||
console.log('scheduled cbE');
|
||||
};
|
||||
}
|
||||
|
||||
function runTestSix() {
|
||||
// Test 6
|
||||
// When some callbacks throw errors, still calls them all within the same frame
|
||||
const cbA = (x) => {
|
||||
console.log('cbA called with argument of ' + JSON.stringify(x));
|
||||
console.log('cbA is about to throw an error!');
|
||||
throw new Error('error A');
|
||||
}
|
||||
const cbB = (x) => {
|
||||
console.log('cbB called with argument of ' + JSON.stringify(x));
|
||||
}
|
||||
const cbC = (x) => {
|
||||
console.log('cbC called with argument of ' + JSON.stringify(x));
|
||||
}
|
||||
const cbD = (x) => {
|
||||
console.log('cbD called with argument of ' + JSON.stringify(x));
|
||||
console.log('cbD is about to throw an error!');
|
||||
throw new Error('error D');
|
||||
}
|
||||
const cbE = (x) => {
|
||||
console.log('cbE called with argument of ' + JSON.stringify(x));
|
||||
console.log('This was the last callback! ------------------');
|
||||
}
|
||||
|
||||
console.log('We are aiming to roughly emulate the way ' +
|
||||
'`requestAnimationFrame` handles errors from callbacks.');
|
||||
|
||||
console.log('about to run the simulation of what it should look like...:');
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
console.log('frame 1 started');
|
||||
requestAnimationFrame(() => {
|
||||
console.log('frame 2 started');
|
||||
requestAnimationFrame(() => {
|
||||
console.log('frame 3 started... we stop counting now.');
|
||||
console.log('about to wait a moment and start this again but ' +
|
||||
'with the scheduler instead of requestAnimationFrame');
|
||||
setTimeout(runSchedulerCode, 1000);
|
||||
});
|
||||
});
|
||||
});
|
||||
requestAnimationFrame(cbC);
|
||||
console.log('scheduled cbC first; simulating timing out');
|
||||
requestAnimationFrame(cbD); // will throw error
|
||||
console.log('scheduled cbD first; simulating timing out');
|
||||
requestAnimationFrame(cbE);
|
||||
console.log('scheduled cbE first; simulating timing out');
|
||||
requestAnimationFrame(cbA);
|
||||
console.log('scheduled cbA'); // will throw error
|
||||
requestAnimationFrame(cbB);
|
||||
console.log('scheduled cbB');
|
||||
|
||||
|
||||
function runSchedulerCode() {
|
||||
console.log('-------------------------------------------------------------');
|
||||
console.log('now lets see what it looks like using the scheduler...:');
|
||||
requestAnimationFrame(() => {
|
||||
console.log('frame 1 started');
|
||||
requestAnimationFrame(() => {
|
||||
console.log('frame 2 started');
|
||||
requestAnimationFrame(() => {
|
||||
console.log('frame 3 started... we stop counting now.');
|
||||
});
|
||||
});
|
||||
});
|
||||
scheduleWork(cbA);
|
||||
console.log('scheduled cbA');
|
||||
scheduleWork(cbB); // will throw error
|
||||
console.log('scheduled cbB');
|
||||
scheduleWork(cbC, {timeout: 1});
|
||||
console.log('scheduled cbC');
|
||||
scheduleWork(cbD, {timeout: 1}); // will throw error
|
||||
console.log('scheduled cbD');
|
||||
scheduleWork(cbE, {timeout: 1});
|
||||
console.log('scheduled cbE');
|
||||
};
|
||||
}
|
||||
|
||||
function runTestSeven() {
|
||||
// Test 7
|
||||
// Calls callbacks, continues calling them even when this tab is in the
|
||||
// background
|
||||
clearTestResult(7);
|
||||
let counter = -1;
|
||||
function incrementCounterAndScheduleNextCallback() {
|
||||
const counterNode = document.getElementById('test-7');
|
||||
counter++;
|
||||
counterNode.innerHTML = counter;
|
||||
waitForTimeToPass(100);
|
||||
scheduleWork(incrementCounterAndScheduleNextCallback);
|
||||
}
|
||||
scheduleWork(incrementCounterAndScheduleNextCallback);
|
||||
}
|
||||
</script type="text/babel">
|
||||
</body>
|
||||
</html>
|
||||
9
netlify.toml
Normal file
9
netlify.toml
Normal file
@@ -0,0 +1,9 @@
|
||||
[build]
|
||||
base = ""
|
||||
publish = "fixtures/dom/build"
|
||||
command = "yarn build --type=UMD_DEV && cd fixtures/dom/ && yarn && yarn prestart && yarn build"
|
||||
|
||||
[[redirects]]
|
||||
from = "/*"
|
||||
to = "/index.html"
|
||||
status = 200
|
||||
29
package.json
29
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"private": true,
|
||||
"version": "16.3.2",
|
||||
"version": "16.4.1",
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
],
|
||||
@@ -11,7 +11,7 @@
|
||||
"babel-code-frame": "^6.26.0",
|
||||
"babel-core": "^6.0.0",
|
||||
"babel-eslint": "^8.0.0",
|
||||
"babel-jest": "^22.0.6",
|
||||
"babel-jest": "^23.0.1",
|
||||
"babel-plugin-check-es2015-constants": "^6.5.0",
|
||||
"babel-plugin-external-helpers": "^6.22.0",
|
||||
"babel-plugin-syntax-trailing-function-commas": "^6.5.0",
|
||||
@@ -31,14 +31,12 @@
|
||||
"babel-plugin-transform-es2015-shorthand-properties": "^6.5.0",
|
||||
"babel-plugin-transform-es2015-spread": "^6.5.2",
|
||||
"babel-plugin-transform-es2015-template-literals": "^6.5.2",
|
||||
"babel-plugin-transform-es3-member-expression-literals": "^6.5.0",
|
||||
"babel-plugin-transform-es3-property-literals": "^6.5.0",
|
||||
"babel-plugin-transform-object-rest-spread": "^6.6.5",
|
||||
"babel-plugin-transform-react-jsx-source": "^6.8.0",
|
||||
"babel-plugin-transform-regenerator": "^6.26.0",
|
||||
"babel-preset-react": "^6.5.0",
|
||||
"babel-traverse": "^6.9.0",
|
||||
"babylon": "6.15.0",
|
||||
"babylon": "6.18.0",
|
||||
"bundle-collapser": "^1.1.1",
|
||||
"chalk": "^1.1.3",
|
||||
"cli-table": "^0.3.1",
|
||||
@@ -59,19 +57,19 @@
|
||||
"eslint-plugin-no-for-of-loops": "^1.0.0",
|
||||
"eslint-plugin-react": "^6.7.1",
|
||||
"eslint-plugin-react-internal": "link:./scripts/eslint-rules/",
|
||||
"fbjs": "^0.8.16",
|
||||
"fbjs-scripts": "^0.6.0",
|
||||
"filesize": "^3.5.6",
|
||||
"flow-bin": "^0.61.0",
|
||||
"flow-bin": "^0.72.0",
|
||||
"flow-coverage-report": "^0.4.0",
|
||||
"git-branch": "^0.3.0",
|
||||
"glob": "^6.0.4",
|
||||
"glob-stream": "^6.1.0",
|
||||
"google-closure-compiler": "20180506.0.0",
|
||||
"gzip-js": "~0.3.2",
|
||||
"gzip-size": "^3.0.0",
|
||||
"jasmine-check": "^1.0.0-rc.0",
|
||||
"jest": "^22.0.6",
|
||||
"jest-diff": "^22.1.0",
|
||||
"jest": "^23.1.0",
|
||||
"jest-diff": "^23.0.1",
|
||||
"merge-stream": "^1.0.0",
|
||||
"minimatch": "^3.0.4",
|
||||
"minimist": "^1.2.0",
|
||||
@@ -79,14 +77,13 @@
|
||||
"ncp": "^2.0.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"platform": "^1.1.0",
|
||||
"prettier": "1.8.1",
|
||||
"prop-types": "^15.6.0",
|
||||
"prettier": "1.11.1",
|
||||
"prop-types": "^15.6.2",
|
||||
"random-seed": "^0.3.0",
|
||||
"react-lifecycles-compat": "^1.0.2",
|
||||
"react-lifecycles-compat": "^3.0.2",
|
||||
"rimraf": "^2.6.1",
|
||||
"rollup": "^0.52.1",
|
||||
"rollup-plugin-babel": "^3.0.1",
|
||||
"rollup-plugin-closure-compiler-js": "^1.0.6",
|
||||
"rollup-plugin-commonjs": "^8.2.6",
|
||||
"rollup-plugin-node-resolve": "^2.1.1",
|
||||
"rollup-plugin-prettier": "^0.3.0",
|
||||
@@ -103,13 +100,16 @@
|
||||
"devEngines": {
|
||||
"node": "8.x || 9.x"
|
||||
},
|
||||
"jest": {
|
||||
"testRegex": "/scripts/jest/dont-run-jest-directly\\.js$"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "npm run version-check && node ./scripts/rollup/build.js",
|
||||
"flow-coverage": "flow-coverage-report --config ./.flowcoverage",
|
||||
"linc": "node ./scripts/tasks/linc.js",
|
||||
"lint": "node ./scripts/tasks/eslint.js",
|
||||
"lint-build": "node ./scripts/rollup/validate/index.js",
|
||||
"postinstall": "node node_modules/fbjs-scripts/node/check-dev-engines.js package.json",
|
||||
"postinstall": "node node_modules/fbjs-scripts/node/check-dev-engines.js package.json && node ./scripts/flow/createFlowConfigs.js",
|
||||
"debug-test": "cross-env NODE_ENV=development node --inspect-brk node_modules/.bin/jest --config ./scripts/jest/config.source.js --runInBand",
|
||||
"test": "cross-env NODE_ENV=development jest --config ./scripts/jest/config.source.js",
|
||||
"test-prod": "cross-env NODE_ENV=production jest --config ./scripts/jest/config.source.js",
|
||||
@@ -117,6 +117,7 @@
|
||||
"test-build": "cross-env NODE_ENV=development jest --config ./scripts/jest/config.build.js",
|
||||
"test-build-prod": "cross-env NODE_ENV=production jest --config ./scripts/jest/config.build.js",
|
||||
"flow": "node ./scripts/tasks/flow.js",
|
||||
"flow-ci": "node ./scripts/tasks/flow-ci.js",
|
||||
"prettier": "node ./scripts/prettier/index.js write-changed",
|
||||
"prettier-all": "node ./scripts/prettier/index.js write",
|
||||
"version-check": "node ./scripts/tasks/version-check.js"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "create-subscription",
|
||||
"description": "utility for subscribing to external data sources inside React components",
|
||||
"version": "16.3.2",
|
||||
"version": "16.4.1",
|
||||
"repository": "facebook/react",
|
||||
"files": [
|
||||
"LICENSE",
|
||||
@@ -9,9 +9,6 @@
|
||||
"index.js",
|
||||
"cjs/"
|
||||
],
|
||||
"dependencies": {
|
||||
"fbjs": "^0.8.16"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.3.0"
|
||||
},
|
||||
|
||||
@@ -264,7 +264,6 @@ describe('createSubscription', () => {
|
||||
|
||||
it('should ignore values emitted by a new subscribable until the commit phase', () => {
|
||||
const log = [];
|
||||
let parentInstance;
|
||||
|
||||
function Child({value}) {
|
||||
ReactNoop.yield('Child: ' + value);
|
||||
@@ -301,8 +300,6 @@ describe('createSubscription', () => {
|
||||
}
|
||||
|
||||
render() {
|
||||
parentInstance = this;
|
||||
|
||||
return (
|
||||
<Subscription source={this.state.observed}>
|
||||
{(value = 'default') => {
|
||||
@@ -331,8 +328,8 @@ describe('createSubscription', () => {
|
||||
observableB.next('b-2');
|
||||
observableB.next('b-3');
|
||||
|
||||
// Mimic a higher-priority interruption
|
||||
parentInstance.setState({observed: observableA});
|
||||
// Update again
|
||||
ReactNoop.render(<Parent observed={observableA} />);
|
||||
|
||||
// Flush everything and ensure that the correct subscribable is used
|
||||
// We expect the last emitted update to be rendered (because of the commit phase value check)
|
||||
@@ -354,7 +351,6 @@ describe('createSubscription', () => {
|
||||
|
||||
it('should not drop values emitted between updates', () => {
|
||||
const log = [];
|
||||
let parentInstance;
|
||||
|
||||
function Child({value}) {
|
||||
ReactNoop.yield('Child: ' + value);
|
||||
@@ -391,8 +387,6 @@ describe('createSubscription', () => {
|
||||
}
|
||||
|
||||
render() {
|
||||
parentInstance = this;
|
||||
|
||||
return (
|
||||
<Subscription source={this.state.observed}>
|
||||
{(value = 'default') => {
|
||||
@@ -420,8 +414,8 @@ describe('createSubscription', () => {
|
||||
observableA.next('a-1');
|
||||
observableA.next('a-2');
|
||||
|
||||
// Mimic a higher-priority interruption
|
||||
parentInstance.setState({observed: observableA});
|
||||
// Update again
|
||||
ReactNoop.render(<Parent observed={observableA} />);
|
||||
|
||||
// Flush everything and ensure that the correct subscribable is used
|
||||
// We expect the new subscribable to finish rendering,
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import invariant from 'fbjs/lib/invariant';
|
||||
import warning from 'fbjs/lib/warning';
|
||||
import invariant from 'shared/invariant';
|
||||
import warning from 'shared/warning';
|
||||
|
||||
type Unsubscribe = () => void;
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
import ReactErrorUtils from 'shared/ReactErrorUtils';
|
||||
import invariant from 'fbjs/lib/invariant';
|
||||
import invariant from 'shared/invariant';
|
||||
|
||||
import {
|
||||
injectEventPluginOrder,
|
||||
@@ -25,6 +25,7 @@ import type {PluginModule} from './PluginModuleType';
|
||||
import type {ReactSyntheticEvent} from './ReactSyntheticEventType';
|
||||
import type {Fiber} from 'react-reconciler/src/ReactFiber';
|
||||
import type {AnyNativeEvent} from './PluginModuleType';
|
||||
import type {TopLevelType} from './TopLevelEventTypes';
|
||||
|
||||
/**
|
||||
* Internal queue of events that have accumulated their dispatches and are
|
||||
@@ -165,7 +166,7 @@ export function getListener(inst: Fiber, registrationName: string) {
|
||||
* @internal
|
||||
*/
|
||||
function extractEvents(
|
||||
topLevelType: string,
|
||||
topLevelType: TopLevelType,
|
||||
targetInst: Fiber,
|
||||
nativeEvent: AnyNativeEvent,
|
||||
nativeEventTarget: EventTarget,
|
||||
@@ -227,7 +228,7 @@ export function runEventsInBatch(
|
||||
}
|
||||
|
||||
export function runExtractedEventsInBatch(
|
||||
topLevelType: string,
|
||||
topLevelType: TopLevelType,
|
||||
targetInst: Fiber,
|
||||
nativeEvent: AnyNativeEvent,
|
||||
nativeEventTarget: EventTarget,
|
||||
|
||||
@@ -14,7 +14,7 @@ import type {
|
||||
PluginModule,
|
||||
} from './PluginModuleType';
|
||||
|
||||
import invariant from 'fbjs/lib/invariant';
|
||||
import invariant from 'shared/invariant';
|
||||
|
||||
type NamesToPlugins = {[key: PluginName]: PluginModule<AnyNativeEvent>};
|
||||
type EventPluginOrder = null | Array<PluginName>;
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
*/
|
||||
|
||||
import ReactErrorUtils from 'shared/ReactErrorUtils';
|
||||
import invariant from 'fbjs/lib/invariant';
|
||||
import warning from 'fbjs/lib/warning';
|
||||
import invariant from 'shared/invariant';
|
||||
import warning from 'shared/warning';
|
||||
|
||||
export let getFiberCurrentPropsFromNode = null;
|
||||
export let getInstanceFromNode = null;
|
||||
@@ -30,21 +30,6 @@ export const injection = {
|
||||
},
|
||||
};
|
||||
|
||||
export function isEndish(topLevelType) {
|
||||
return (
|
||||
topLevelType === 'topMouseUp' ||
|
||||
topLevelType === 'topTouchEnd' ||
|
||||
topLevelType === 'topTouchCancel'
|
||||
);
|
||||
}
|
||||
|
||||
export function isMoveish(topLevelType) {
|
||||
return topLevelType === 'topMouseMove' || topLevelType === 'topTouchMove';
|
||||
}
|
||||
export function isStartish(topLevelType) {
|
||||
return topLevelType === 'topMouseDown' || topLevelType === 'topTouchStart';
|
||||
}
|
||||
|
||||
let validateEventDispatches;
|
||||
if (__DEV__) {
|
||||
validateEventDispatches = function(event) {
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
traverseTwoPhase,
|
||||
traverseEnterLeave,
|
||||
} from 'shared/ReactTreeTraversal';
|
||||
import warning from 'fbjs/lib/warning';
|
||||
import warning from 'shared/warning';
|
||||
|
||||
import {getListener} from './EventPluginHub';
|
||||
import accumulateInto from './accumulateInto';
|
||||
|
||||
@@ -12,6 +12,7 @@ import type {
|
||||
DispatchConfig,
|
||||
ReactSyntheticEvent,
|
||||
} from './ReactSyntheticEventType';
|
||||
import type {TopLevelType} from './TopLevelEventTypes';
|
||||
|
||||
export type EventTypes = {[key: string]: DispatchConfig};
|
||||
|
||||
@@ -22,7 +23,7 @@ export type PluginName = string;
|
||||
export type PluginModule<NativeEvent> = {
|
||||
eventTypes: EventTypes,
|
||||
extractEvents: (
|
||||
topLevelType: string,
|
||||
topLevelType: TopLevelType,
|
||||
targetInst: Fiber,
|
||||
nativeTarget: NativeEvent,
|
||||
nativeEventTarget: EventTarget,
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import invariant from 'fbjs/lib/invariant';
|
||||
import invariant from 'shared/invariant';
|
||||
|
||||
import {
|
||||
getInstanceFromNode,
|
||||
|
||||
@@ -9,9 +9,10 @@
|
||||
*/
|
||||
|
||||
import type {Fiber} from 'react-reconciler/src/ReactFiber';
|
||||
import type {TopLevelType} from './TopLevelEventTypes';
|
||||
|
||||
export type DispatchConfig = {
|
||||
dependencies: Array<string>,
|
||||
dependencies: Array<TopLevelType>,
|
||||
phasedRegistrationNames?: {
|
||||
bubbled: string,
|
||||
captured: string,
|
||||
|
||||
@@ -8,9 +8,6 @@
|
||||
import {getLowestCommonAncestor, isAncestor} from 'shared/ReactTreeTraversal';
|
||||
|
||||
import {
|
||||
isStartish,
|
||||
isMoveish,
|
||||
isEndish,
|
||||
executeDirectDispatch,
|
||||
hasDispatches,
|
||||
executeDispatchesInOrderStopAtTrue,
|
||||
@@ -24,6 +21,17 @@ import {
|
||||
import ResponderSyntheticEvent from './ResponderSyntheticEvent';
|
||||
import ResponderTouchHistoryStore from './ResponderTouchHistoryStore';
|
||||
import accumulate from './accumulate';
|
||||
import {
|
||||
TOP_SCROLL,
|
||||
TOP_SELECTION_CHANGE,
|
||||
TOP_TOUCH_CANCEL,
|
||||
isStartish,
|
||||
isMoveish,
|
||||
isEndish,
|
||||
startDependencies,
|
||||
moveDependencies,
|
||||
endDependencies,
|
||||
} from './ResponderTopLevelEventTypes';
|
||||
|
||||
/**
|
||||
* Instance of element that should respond to touch/move types of interactions,
|
||||
@@ -37,11 +45,6 @@ let responderInst = null;
|
||||
*/
|
||||
let trackedTouchCount = 0;
|
||||
|
||||
/**
|
||||
* Last reported number of active touches.
|
||||
*/
|
||||
let previousActiveTouches = 0;
|
||||
|
||||
const changeResponder = function(nextResponderInst, blockHostResponder) {
|
||||
const oldResponderInst = responderInst;
|
||||
responderInst = nextResponderInst;
|
||||
@@ -64,6 +67,7 @@ const eventTypes = {
|
||||
bubbled: 'onStartShouldSetResponder',
|
||||
captured: 'onStartShouldSetResponderCapture',
|
||||
},
|
||||
dependencies: startDependencies,
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -80,6 +84,7 @@ const eventTypes = {
|
||||
bubbled: 'onScrollShouldSetResponder',
|
||||
captured: 'onScrollShouldSetResponderCapture',
|
||||
},
|
||||
dependencies: [TOP_SCROLL],
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -94,6 +99,7 @@ const eventTypes = {
|
||||
bubbled: 'onSelectionChangeShouldSetResponder',
|
||||
captured: 'onSelectionChangeShouldSetResponderCapture',
|
||||
},
|
||||
dependencies: [TOP_SELECTION_CHANGE],
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -105,21 +111,44 @@ const eventTypes = {
|
||||
bubbled: 'onMoveShouldSetResponder',
|
||||
captured: 'onMoveShouldSetResponderCapture',
|
||||
},
|
||||
dependencies: moveDependencies,
|
||||
},
|
||||
|
||||
/**
|
||||
* Direct responder events dispatched directly to responder. Do not bubble.
|
||||
*/
|
||||
responderStart: {registrationName: 'onResponderStart'},
|
||||
responderMove: {registrationName: 'onResponderMove'},
|
||||
responderEnd: {registrationName: 'onResponderEnd'},
|
||||
responderRelease: {registrationName: 'onResponderRelease'},
|
||||
responderStart: {
|
||||
registrationName: 'onResponderStart',
|
||||
dependencies: startDependencies,
|
||||
},
|
||||
responderMove: {
|
||||
registrationName: 'onResponderMove',
|
||||
dependencies: moveDependencies,
|
||||
},
|
||||
responderEnd: {
|
||||
registrationName: 'onResponderEnd',
|
||||
dependencies: endDependencies,
|
||||
},
|
||||
responderRelease: {
|
||||
registrationName: 'onResponderRelease',
|
||||
dependencies: endDependencies,
|
||||
},
|
||||
responderTerminationRequest: {
|
||||
registrationName: 'onResponderTerminationRequest',
|
||||
dependencies: [],
|
||||
},
|
||||
responderGrant: {
|
||||
registrationName: 'onResponderGrant',
|
||||
dependencies: [],
|
||||
},
|
||||
responderReject: {
|
||||
registrationName: 'onResponderReject',
|
||||
dependencies: [],
|
||||
},
|
||||
responderTerminate: {
|
||||
registrationName: 'onResponderTerminate',
|
||||
dependencies: [],
|
||||
},
|
||||
responderGrant: {registrationName: 'onResponderGrant'},
|
||||
responderReject: {registrationName: 'onResponderReject'},
|
||||
responderTerminate: {registrationName: 'onResponderTerminate'},
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -322,7 +351,7 @@ function setResponderAndExtractTransfer(
|
||||
? eventTypes.startShouldSetResponder
|
||||
: isMoveish(topLevelType)
|
||||
? eventTypes.moveShouldSetResponder
|
||||
: topLevelType === 'topSelectionChange'
|
||||
: topLevelType === TOP_SELECTION_CHANGE
|
||||
? eventTypes.selectionChangeShouldSetResponder
|
||||
: eventTypes.scrollShouldSetResponder;
|
||||
|
||||
@@ -427,8 +456,8 @@ function canTriggerTransfer(topLevelType, topLevelInst, nativeEvent) {
|
||||
// responderIgnoreScroll: We are trying to migrate away from specifically
|
||||
// tracking native scroll events here and responderIgnoreScroll indicates we
|
||||
// will send topTouchCancel to handle canceling touch events instead
|
||||
((topLevelType === 'topScroll' && !nativeEvent.responderIgnoreScroll) ||
|
||||
(trackedTouchCount > 0 && topLevelType === 'topSelectionChange') ||
|
||||
((topLevelType === TOP_SCROLL && !nativeEvent.responderIgnoreScroll) ||
|
||||
(trackedTouchCount > 0 && topLevelType === TOP_SELECTION_CHANGE) ||
|
||||
isStartish(topLevelType) ||
|
||||
isMoveish(topLevelType))
|
||||
);
|
||||
@@ -534,7 +563,7 @@ const ResponderEventPlugin = {
|
||||
}
|
||||
|
||||
const isResponderTerminate =
|
||||
responderInst && topLevelType === 'topTouchCancel';
|
||||
responderInst && topLevelType === TOP_TOUCH_CANCEL;
|
||||
const isResponderRelease =
|
||||
responderInst &&
|
||||
!isResponderTerminate &&
|
||||
@@ -556,23 +585,10 @@ const ResponderEventPlugin = {
|
||||
changeResponder(null);
|
||||
}
|
||||
|
||||
const numberActiveTouches =
|
||||
ResponderTouchHistoryStore.touchHistory.numberActiveTouches;
|
||||
if (
|
||||
ResponderEventPlugin.GlobalInteractionHandler &&
|
||||
numberActiveTouches !== previousActiveTouches
|
||||
) {
|
||||
ResponderEventPlugin.GlobalInteractionHandler.onChange(
|
||||
numberActiveTouches,
|
||||
);
|
||||
}
|
||||
previousActiveTouches = numberActiveTouches;
|
||||
|
||||
return extracted;
|
||||
},
|
||||
|
||||
GlobalResponderHandler: null,
|
||||
GlobalInteractionHandler: null,
|
||||
|
||||
injection: {
|
||||
/**
|
||||
@@ -580,17 +596,9 @@ const ResponderEventPlugin = {
|
||||
* Object that handles any change in responder. Use this to inject
|
||||
* integration with an existing touch handling system etc.
|
||||
*/
|
||||
injectGlobalResponderHandler: function(GlobalResponderHandler) {
|
||||
injectGlobalResponderHandler(GlobalResponderHandler) {
|
||||
ResponderEventPlugin.GlobalResponderHandler = GlobalResponderHandler;
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {{onChange: (numberActiveTouches) => void} GlobalInteractionHandler
|
||||
* Object that handles any change in the number of active touches.
|
||||
*/
|
||||
injectGlobalInteractionHandler: function(GlobalInteractionHandler) {
|
||||
ResponderEventPlugin.GlobalInteractionHandler = GlobalInteractionHandler;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
31
packages/events/ResponderTopLevelEventTypes.js
Normal file
31
packages/events/ResponderTopLevelEventTypes.js
Normal file
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
export const TOP_TOUCH_START = 'topTouchStart';
|
||||
export const TOP_TOUCH_MOVE = 'topTouchMove';
|
||||
export const TOP_TOUCH_END = 'topTouchEnd';
|
||||
export const TOP_TOUCH_CANCEL = 'topTouchCancel';
|
||||
export const TOP_SCROLL = 'topScroll';
|
||||
export const TOP_SELECTION_CHANGE = 'topSelectionChange';
|
||||
|
||||
export function isStartish(topLevelType: mixed): boolean {
|
||||
return topLevelType === TOP_TOUCH_START;
|
||||
}
|
||||
|
||||
export function isMoveish(topLevelType: mixed): boolean {
|
||||
return topLevelType === TOP_TOUCH_MOVE;
|
||||
}
|
||||
|
||||
export function isEndish(topLevelType: mixed): boolean {
|
||||
return topLevelType === TOP_TOUCH_END || topLevelType === TOP_TOUCH_CANCEL;
|
||||
}
|
||||
|
||||
export const startDependencies = [TOP_TOUCH_START];
|
||||
export const moveDependencies = [TOP_TOUCH_MOVE];
|
||||
export const endDependencies = [TOP_TOUCH_CANCEL, TOP_TOUCH_END];
|
||||
@@ -7,10 +7,10 @@
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import invariant from 'fbjs/lib/invariant';
|
||||
import warning from 'fbjs/lib/warning';
|
||||
import invariant from 'shared/invariant';
|
||||
import warning from 'shared/warning';
|
||||
|
||||
import {isEndish, isMoveish, isStartish} from './EventPluginUtils';
|
||||
import {isStartish, isMoveish, isEndish} from './ResponderTopLevelEventTypes';
|
||||
|
||||
/**
|
||||
* Tracks the position and time of each active touch by `touch.identifier`. We
|
||||
|
||||
@@ -7,9 +7,8 @@
|
||||
|
||||
/* eslint valid-typeof: 0 */
|
||||
|
||||
import emptyFunction from 'fbjs/lib/emptyFunction';
|
||||
import invariant from 'fbjs/lib/invariant';
|
||||
import warning from 'fbjs/lib/warning';
|
||||
import invariant from 'shared/invariant';
|
||||
import warning from 'shared/warning';
|
||||
|
||||
let didWarnForAddedNewProperty = false;
|
||||
const EVENT_POOL_SIZE = 10;
|
||||
@@ -32,7 +31,9 @@ const EventInterface = {
|
||||
type: null,
|
||||
target: null,
|
||||
// currentTarget is set when dispatching; no use in copying it here
|
||||
currentTarget: emptyFunction.thatReturnsNull,
|
||||
currentTarget: function() {
|
||||
return null;
|
||||
},
|
||||
eventPhase: null,
|
||||
bubbles: null,
|
||||
cancelable: null,
|
||||
@@ -43,6 +44,14 @@ const EventInterface = {
|
||||
isTrusted: null,
|
||||
};
|
||||
|
||||
function functionThatReturnsTrue() {
|
||||
return true;
|
||||
}
|
||||
|
||||
function functionThatReturnsFalse() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Synthetic events are dispatched by event plugins, typically in response to a
|
||||
* top-level event delegation handler.
|
||||
@@ -103,11 +112,11 @@ function SyntheticEvent(
|
||||
? nativeEvent.defaultPrevented
|
||||
: nativeEvent.returnValue === false;
|
||||
if (defaultPrevented) {
|
||||
this.isDefaultPrevented = emptyFunction.thatReturnsTrue;
|
||||
this.isDefaultPrevented = functionThatReturnsTrue;
|
||||
} else {
|
||||
this.isDefaultPrevented = emptyFunction.thatReturnsFalse;
|
||||
this.isDefaultPrevented = functionThatReturnsFalse;
|
||||
}
|
||||
this.isPropagationStopped = emptyFunction.thatReturnsFalse;
|
||||
this.isPropagationStopped = functionThatReturnsFalse;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -124,7 +133,7 @@ Object.assign(SyntheticEvent.prototype, {
|
||||
} else if (typeof event.returnValue !== 'unknown') {
|
||||
event.returnValue = false;
|
||||
}
|
||||
this.isDefaultPrevented = emptyFunction.thatReturnsTrue;
|
||||
this.isDefaultPrevented = functionThatReturnsTrue;
|
||||
},
|
||||
|
||||
stopPropagation: function() {
|
||||
@@ -144,7 +153,7 @@ Object.assign(SyntheticEvent.prototype, {
|
||||
event.cancelBubble = true;
|
||||
}
|
||||
|
||||
this.isPropagationStopped = emptyFunction.thatReturnsTrue;
|
||||
this.isPropagationStopped = functionThatReturnsTrue;
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -153,7 +162,7 @@ Object.assign(SyntheticEvent.prototype, {
|
||||
* won't be added back into the pool.
|
||||
*/
|
||||
persist: function() {
|
||||
this.isPersistent = emptyFunction.thatReturnsTrue;
|
||||
this.isPersistent = functionThatReturnsTrue;
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -161,7 +170,7 @@ Object.assign(SyntheticEvent.prototype, {
|
||||
*
|
||||
* @return {boolean} True if this should not be released, false otherwise.
|
||||
*/
|
||||
isPersistent: emptyFunction.thatReturnsFalse,
|
||||
isPersistent: functionThatReturnsFalse,
|
||||
|
||||
/**
|
||||
* `PooledClass` looks for `destructor` on each instance it releases.
|
||||
@@ -191,12 +200,12 @@ Object.assign(SyntheticEvent.prototype, {
|
||||
Object.defineProperty(
|
||||
this,
|
||||
'preventDefault',
|
||||
getPooledWarningPropertyDefinition('preventDefault', emptyFunction),
|
||||
getPooledWarningPropertyDefinition('preventDefault', () => {}),
|
||||
);
|
||||
Object.defineProperty(
|
||||
this,
|
||||
'stopPropagation',
|
||||
getPooledWarningPropertyDefinition('stopPropagation', emptyFunction),
|
||||
getPooledWarningPropertyDefinition('stopPropagation', () => {}),
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
39
packages/events/TopLevelEventTypes.js
Normal file
39
packages/events/TopLevelEventTypes.js
Normal file
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
export type RNTopLevelEventType =
|
||||
| 'topMouseDown'
|
||||
| 'topMouseMove'
|
||||
| 'topMouseUp'
|
||||
| 'topScroll'
|
||||
| 'topSelectionChange'
|
||||
| 'topTouchCancel'
|
||||
| 'topTouchEnd'
|
||||
| 'topTouchMove'
|
||||
| 'topTouchStart';
|
||||
|
||||
export opaque type DOMTopLevelEventType = string;
|
||||
|
||||
// Do not uses the below two methods directly!
|
||||
// Instead use constants exported from DOMTopLevelEventTypes in ReactDOM.
|
||||
// (It is the only module that is allowed to access these methods.)
|
||||
|
||||
export function unsafeCastStringToDOMTopLevelType(
|
||||
topLevelType: string,
|
||||
): DOMTopLevelEventType {
|
||||
return topLevelType;
|
||||
}
|
||||
|
||||
export function unsafeCastDOMTopLevelTypeToString(
|
||||
topLevelType: DOMTopLevelEventType,
|
||||
): string {
|
||||
return topLevelType;
|
||||
}
|
||||
|
||||
export type TopLevelType = DOMTopLevelEventType | RNTopLevelEventType;
|
||||
@@ -7,7 +7,7 @@
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import invariant from 'fbjs/lib/invariant';
|
||||
import invariant from 'shared/invariant';
|
||||
|
||||
/**
|
||||
* Accumulates items that must not be null or undefined.
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import invariant from 'fbjs/lib/invariant';
|
||||
import invariant from 'shared/invariant';
|
||||
|
||||
/**
|
||||
* Accumulates items that must not be null or undefined into the first one. This
|
||||
|
||||
41
packages/events/forks/ResponderTopLevelEventTypes.dom.js
Normal file
41
packages/events/forks/ResponderTopLevelEventTypes.dom.js
Normal file
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
// Note: ideally these would be imported from DOMTopLevelEventTypes,
|
||||
// but our build system currently doesn't let us do that from a fork.
|
||||
|
||||
export const TOP_TOUCH_START = 'touchstart';
|
||||
export const TOP_TOUCH_MOVE = 'touchmove';
|
||||
export const TOP_TOUCH_END = 'touchend';
|
||||
export const TOP_TOUCH_CANCEL = 'touchcancel';
|
||||
export const TOP_SCROLL = 'scroll';
|
||||
export const TOP_SELECTION_CHANGE = 'selectionchange';
|
||||
export const TOP_MOUSE_DOWN = 'mousedown';
|
||||
export const TOP_MOUSE_MOVE = 'mousemove';
|
||||
export const TOP_MOUSE_UP = 'mouseup';
|
||||
|
||||
export function isStartish(topLevelType: mixed): boolean {
|
||||
return topLevelType === TOP_TOUCH_START || topLevelType === TOP_MOUSE_DOWN;
|
||||
}
|
||||
|
||||
export function isMoveish(topLevelType: mixed): boolean {
|
||||
return topLevelType === TOP_TOUCH_MOVE || topLevelType === TOP_MOUSE_MOVE;
|
||||
}
|
||||
|
||||
export function isEndish(topLevelType: mixed): boolean {
|
||||
return (
|
||||
topLevelType === TOP_TOUCH_END ||
|
||||
topLevelType === TOP_TOUCH_CANCEL ||
|
||||
topLevelType === TOP_MOUSE_UP
|
||||
);
|
||||
}
|
||||
|
||||
export const startDependencies = [TOP_TOUCH_START, TOP_MOUSE_DOWN];
|
||||
export const moveDependencies = [TOP_TOUCH_MOVE, TOP_MOUSE_MOVE];
|
||||
export const endDependencies = [TOP_TOUCH_CANCEL, TOP_TOUCH_END, TOP_MOUSE_UP];
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"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.3.2",
|
||||
"version": "16.4.1",
|
||||
"main": "index.js",
|
||||
"repository": "facebook/react",
|
||||
"keywords": [
|
||||
@@ -20,10 +20,9 @@
|
||||
"dependencies": {
|
||||
"art": "^0.10.1",
|
||||
"create-react-class": "^15.6.2",
|
||||
"fbjs": "^0.8.16",
|
||||
"loose-envify": "^1.1.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"prop-types": "^15.6.0"
|
||||
"prop-types": "^15.6.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.0.0"
|
||||
|
||||
410
packages/react-art/src/ReactART.js
vendored
410
packages/react-art/src/ReactART.js
vendored
@@ -6,268 +6,18 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import ReactFiberReconciler from 'react-reconciler';
|
||||
import * as ReactDOMFrameScheduling from 'shared/ReactDOMFrameScheduling';
|
||||
import * as ARTRenderer from 'react-reconciler/inline.art';
|
||||
import Transform from 'art/core/transform';
|
||||
import Mode from 'art/modes/current';
|
||||
import FastNoSideEffects from 'art/modes/fast-noSideEffects';
|
||||
import Transform from 'art/core/transform';
|
||||
import invariant from 'fbjs/lib/invariant';
|
||||
import emptyObject from 'fbjs/lib/emptyObject';
|
||||
|
||||
import {TYPES, childrenAsString} from './ReactARTInternals';
|
||||
|
||||
Mode.setCurrent(
|
||||
// Change to 'art/modes/dom' for easier debugging via SVG
|
||||
FastNoSideEffects,
|
||||
);
|
||||
|
||||
const pooledTransform = new Transform();
|
||||
|
||||
const EVENT_TYPES = {
|
||||
onClick: 'click',
|
||||
onMouseMove: 'mousemove',
|
||||
onMouseOver: 'mouseover',
|
||||
onMouseOut: 'mouseout',
|
||||
onMouseUp: 'mouseup',
|
||||
onMouseDown: 'mousedown',
|
||||
};
|
||||
|
||||
const TYPES = {
|
||||
CLIPPING_RECTANGLE: 'ClippingRectangle',
|
||||
GROUP: 'Group',
|
||||
SHAPE: 'Shape',
|
||||
TEXT: 'Text',
|
||||
};
|
||||
|
||||
const UPDATE_SIGNAL = {};
|
||||
|
||||
/** Helper Methods */
|
||||
|
||||
function addEventListeners(instance, type, listener) {
|
||||
// We need to explicitly unregister before unmount.
|
||||
// For this reason we need to track subscriptions.
|
||||
if (!instance._listeners) {
|
||||
instance._listeners = {};
|
||||
instance._subscriptions = {};
|
||||
}
|
||||
|
||||
instance._listeners[type] = listener;
|
||||
|
||||
if (listener) {
|
||||
if (!instance._subscriptions[type]) {
|
||||
instance._subscriptions[type] = instance.subscribe(
|
||||
type,
|
||||
createEventHandler(instance),
|
||||
instance,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (instance._subscriptions[type]) {
|
||||
instance._subscriptions[type]();
|
||||
delete instance._subscriptions[type];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function childrenAsString(children) {
|
||||
if (!children) {
|
||||
return '';
|
||||
} else if (typeof children === 'string') {
|
||||
return children;
|
||||
} else if (children.length) {
|
||||
return children.join('');
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
function createEventHandler(instance) {
|
||||
return function handleEvent(event) {
|
||||
const listener = instance._listeners[event.type];
|
||||
|
||||
if (!listener) {
|
||||
// Noop
|
||||
} else if (typeof listener === 'function') {
|
||||
listener.call(instance, event);
|
||||
} else if (listener.handleEvent) {
|
||||
listener.handleEvent(event);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function destroyEventListeners(instance) {
|
||||
if (instance._subscriptions) {
|
||||
for (let type in instance._subscriptions) {
|
||||
instance._subscriptions[type]();
|
||||
}
|
||||
}
|
||||
|
||||
instance._subscriptions = null;
|
||||
instance._listeners = null;
|
||||
}
|
||||
|
||||
function getScaleX(props) {
|
||||
if (props.scaleX != null) {
|
||||
return props.scaleX;
|
||||
} else if (props.scale != null) {
|
||||
return props.scale;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
function getScaleY(props) {
|
||||
if (props.scaleY != null) {
|
||||
return props.scaleY;
|
||||
} else if (props.scale != null) {
|
||||
return props.scale;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
function isSameFont(oldFont, newFont) {
|
||||
if (oldFont === newFont) {
|
||||
return true;
|
||||
} else if (typeof newFont === 'string' || typeof oldFont === 'string') {
|
||||
return false;
|
||||
} else {
|
||||
return (
|
||||
newFont.fontSize === oldFont.fontSize &&
|
||||
newFont.fontStyle === oldFont.fontStyle &&
|
||||
newFont.fontVariant === oldFont.fontVariant &&
|
||||
newFont.fontWeight === oldFont.fontWeight &&
|
||||
newFont.fontFamily === oldFont.fontFamily
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/** Render Methods */
|
||||
|
||||
function applyClippingRectangleProps(instance, props, prevProps = {}) {
|
||||
applyNodeProps(instance, props, prevProps);
|
||||
|
||||
instance.width = props.width;
|
||||
instance.height = props.height;
|
||||
}
|
||||
|
||||
function applyGroupProps(instance, props, prevProps = {}) {
|
||||
applyNodeProps(instance, props, prevProps);
|
||||
|
||||
instance.width = props.width;
|
||||
instance.height = props.height;
|
||||
}
|
||||
|
||||
function applyNodeProps(instance, props, prevProps = {}) {
|
||||
const scaleX = getScaleX(props);
|
||||
const scaleY = getScaleY(props);
|
||||
|
||||
pooledTransform
|
||||
.transformTo(1, 0, 0, 1, 0, 0)
|
||||
.move(props.x || 0, props.y || 0)
|
||||
.rotate(props.rotation || 0, props.originX, props.originY)
|
||||
.scale(scaleX, scaleY, props.originX, props.originY);
|
||||
|
||||
if (props.transform != null) {
|
||||
pooledTransform.transform(props.transform);
|
||||
}
|
||||
|
||||
if (
|
||||
instance.xx !== pooledTransform.xx ||
|
||||
instance.yx !== pooledTransform.yx ||
|
||||
instance.xy !== pooledTransform.xy ||
|
||||
instance.yy !== pooledTransform.yy ||
|
||||
instance.x !== pooledTransform.x ||
|
||||
instance.y !== pooledTransform.y
|
||||
) {
|
||||
instance.transformTo(pooledTransform);
|
||||
}
|
||||
|
||||
if (props.cursor !== prevProps.cursor || props.title !== prevProps.title) {
|
||||
instance.indicate(props.cursor, props.title);
|
||||
}
|
||||
|
||||
if (instance.blend && props.opacity !== prevProps.opacity) {
|
||||
instance.blend(props.opacity == null ? 1 : props.opacity);
|
||||
}
|
||||
|
||||
if (props.visible !== prevProps.visible) {
|
||||
if (props.visible == null || props.visible) {
|
||||
instance.show();
|
||||
} else {
|
||||
instance.hide();
|
||||
}
|
||||
}
|
||||
|
||||
for (let type in EVENT_TYPES) {
|
||||
addEventListeners(instance, EVENT_TYPES[type], props[type]);
|
||||
}
|
||||
}
|
||||
|
||||
function applyRenderableNodeProps(instance, props, prevProps = {}) {
|
||||
applyNodeProps(instance, props, prevProps);
|
||||
|
||||
if (prevProps.fill !== props.fill) {
|
||||
if (props.fill && props.fill.applyFill) {
|
||||
props.fill.applyFill(instance);
|
||||
} else {
|
||||
instance.fill(props.fill);
|
||||
}
|
||||
}
|
||||
if (
|
||||
prevProps.stroke !== props.stroke ||
|
||||
prevProps.strokeWidth !== props.strokeWidth ||
|
||||
prevProps.strokeCap !== props.strokeCap ||
|
||||
prevProps.strokeJoin !== props.strokeJoin ||
|
||||
// TODO: Consider deep check of stokeDash; may benefit VML in IE.
|
||||
prevProps.strokeDash !== props.strokeDash
|
||||
) {
|
||||
instance.stroke(
|
||||
props.stroke,
|
||||
props.strokeWidth,
|
||||
props.strokeCap,
|
||||
props.strokeJoin,
|
||||
props.strokeDash,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function applyShapeProps(instance, props, prevProps = {}) {
|
||||
applyRenderableNodeProps(instance, props, prevProps);
|
||||
|
||||
const path = props.d || childrenAsString(props.children);
|
||||
|
||||
const prevDelta = instance._prevDelta;
|
||||
const prevPath = instance._prevPath;
|
||||
|
||||
if (
|
||||
path !== prevPath ||
|
||||
path.delta !== prevDelta ||
|
||||
prevProps.height !== props.height ||
|
||||
prevProps.width !== props.width
|
||||
) {
|
||||
instance.draw(path, props.width, props.height);
|
||||
|
||||
instance._prevDelta = path.delta;
|
||||
instance._prevPath = path;
|
||||
}
|
||||
}
|
||||
|
||||
function applyTextProps(instance, props, prevProps = {}) {
|
||||
applyRenderableNodeProps(instance, props, prevProps);
|
||||
|
||||
const string = props.children;
|
||||
|
||||
if (
|
||||
instance._currentString !== string ||
|
||||
!isSameFont(props.font, prevProps.font) ||
|
||||
props.alignment !== prevProps.alignment ||
|
||||
props.path !== prevProps.path
|
||||
) {
|
||||
instance.draw(string, props.font, props.alignment, props.path);
|
||||
|
||||
instance._currentString = string;
|
||||
}
|
||||
}
|
||||
|
||||
/** Declarative fill-type objects; API design not finalized */
|
||||
|
||||
const slice = Array.prototype.slice;
|
||||
@@ -381,158 +131,6 @@ class Text extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
/** ART Renderer */
|
||||
|
||||
const ARTRenderer = ReactFiberReconciler({
|
||||
appendInitialChild(parentInstance, child) {
|
||||
if (typeof child === 'string') {
|
||||
// Noop for string children of Text (eg <Text>{'foo'}{'bar'}</Text>)
|
||||
invariant(false, 'Text children should already be flattened.');
|
||||
return;
|
||||
}
|
||||
|
||||
child.inject(parentInstance);
|
||||
},
|
||||
|
||||
createInstance(type, props, internalInstanceHandle) {
|
||||
let instance;
|
||||
|
||||
switch (type) {
|
||||
case TYPES.CLIPPING_RECTANGLE:
|
||||
instance = Mode.ClippingRectangle();
|
||||
instance._applyProps = applyClippingRectangleProps;
|
||||
break;
|
||||
case TYPES.GROUP:
|
||||
instance = Mode.Group();
|
||||
instance._applyProps = applyGroupProps;
|
||||
break;
|
||||
case TYPES.SHAPE:
|
||||
instance = Mode.Shape();
|
||||
instance._applyProps = applyShapeProps;
|
||||
break;
|
||||
case TYPES.TEXT:
|
||||
instance = Mode.Text(
|
||||
props.children,
|
||||
props.font,
|
||||
props.alignment,
|
||||
props.path,
|
||||
);
|
||||
instance._applyProps = applyTextProps;
|
||||
break;
|
||||
}
|
||||
|
||||
invariant(instance, 'ReactART does not support the type "%s"', type);
|
||||
|
||||
instance._applyProps(instance, props);
|
||||
|
||||
return instance;
|
||||
},
|
||||
|
||||
createTextInstance(text, rootContainerInstance, internalInstanceHandle) {
|
||||
return text;
|
||||
},
|
||||
|
||||
finalizeInitialChildren(domElement, type, props) {
|
||||
return false;
|
||||
},
|
||||
|
||||
getPublicInstance(instance) {
|
||||
return instance;
|
||||
},
|
||||
|
||||
prepareForCommit() {
|
||||
// Noop
|
||||
},
|
||||
|
||||
prepareUpdate(domElement, type, oldProps, newProps) {
|
||||
return UPDATE_SIGNAL;
|
||||
},
|
||||
|
||||
resetAfterCommit() {
|
||||
// Noop
|
||||
},
|
||||
|
||||
resetTextContent(domElement) {
|
||||
// Noop
|
||||
},
|
||||
|
||||
shouldDeprioritizeSubtree(type, props) {
|
||||
return false;
|
||||
},
|
||||
|
||||
getRootHostContext() {
|
||||
return emptyObject;
|
||||
},
|
||||
|
||||
getChildHostContext() {
|
||||
return emptyObject;
|
||||
},
|
||||
|
||||
scheduleDeferredCallback: ReactDOMFrameScheduling.rIC,
|
||||
|
||||
shouldSetTextContent(type, props) {
|
||||
return (
|
||||
typeof props.children === 'string' || typeof props.children === 'number'
|
||||
);
|
||||
},
|
||||
|
||||
now: ReactDOMFrameScheduling.now,
|
||||
|
||||
mutation: {
|
||||
appendChild(parentInstance, child) {
|
||||
if (child.parentNode === parentInstance) {
|
||||
child.eject();
|
||||
}
|
||||
child.inject(parentInstance);
|
||||
},
|
||||
|
||||
appendChildToContainer(parentInstance, child) {
|
||||
if (child.parentNode === parentInstance) {
|
||||
child.eject();
|
||||
}
|
||||
child.inject(parentInstance);
|
||||
},
|
||||
|
||||
insertBefore(parentInstance, child, beforeChild) {
|
||||
invariant(
|
||||
child !== beforeChild,
|
||||
'ReactART: Can not insert node before itself',
|
||||
);
|
||||
child.injectBefore(beforeChild);
|
||||
},
|
||||
|
||||
insertInContainerBefore(parentInstance, child, beforeChild) {
|
||||
invariant(
|
||||
child !== beforeChild,
|
||||
'ReactART: Can not insert node before itself',
|
||||
);
|
||||
child.injectBefore(beforeChild);
|
||||
},
|
||||
|
||||
removeChild(parentInstance, child) {
|
||||
destroyEventListeners(child);
|
||||
child.eject();
|
||||
},
|
||||
|
||||
removeChildFromContainer(parentInstance, child) {
|
||||
destroyEventListeners(child);
|
||||
child.eject();
|
||||
},
|
||||
|
||||
commitTextUpdate(textInstance, oldText, newText) {
|
||||
// Noop
|
||||
},
|
||||
|
||||
commitMount(instance, type, newProps) {
|
||||
// Noop
|
||||
},
|
||||
|
||||
commitUpdate(instance, updatePayload, type, oldProps, newProps) {
|
||||
instance._applyProps(instance, newProps, oldProps);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
/** API */
|
||||
|
||||
export const ClippingRectangle = TYPES.CLIPPING_RECTANGLE;
|
||||
|
||||
404
packages/react-art/src/ReactARTHostConfig.js
vendored
Normal file
404
packages/react-art/src/ReactARTHostConfig.js
vendored
Normal file
@@ -0,0 +1,404 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import * as ReactScheduler from 'shared/ReactScheduler';
|
||||
import Transform from 'art/core/transform';
|
||||
import Mode from 'art/modes/current';
|
||||
import invariant from 'shared/invariant';
|
||||
|
||||
import {TYPES, EVENT_TYPES, childrenAsString} from './ReactARTInternals';
|
||||
|
||||
const pooledTransform = new Transform();
|
||||
|
||||
const NO_CONTEXT = {};
|
||||
const UPDATE_SIGNAL = {};
|
||||
if (__DEV__) {
|
||||
Object.freeze(NO_CONTEXT);
|
||||
Object.freeze(UPDATE_SIGNAL);
|
||||
}
|
||||
|
||||
/** Helper Methods */
|
||||
|
||||
function addEventListeners(instance, type, listener) {
|
||||
// We need to explicitly unregister before unmount.
|
||||
// For this reason we need to track subscriptions.
|
||||
if (!instance._listeners) {
|
||||
instance._listeners = {};
|
||||
instance._subscriptions = {};
|
||||
}
|
||||
|
||||
instance._listeners[type] = listener;
|
||||
|
||||
if (listener) {
|
||||
if (!instance._subscriptions[type]) {
|
||||
instance._subscriptions[type] = instance.subscribe(
|
||||
type,
|
||||
createEventHandler(instance),
|
||||
instance,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (instance._subscriptions[type]) {
|
||||
instance._subscriptions[type]();
|
||||
delete instance._subscriptions[type];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function createEventHandler(instance) {
|
||||
return function handleEvent(event) {
|
||||
const listener = instance._listeners[event.type];
|
||||
|
||||
if (!listener) {
|
||||
// Noop
|
||||
} else if (typeof listener === 'function') {
|
||||
listener.call(instance, event);
|
||||
} else if (listener.handleEvent) {
|
||||
listener.handleEvent(event);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function destroyEventListeners(instance) {
|
||||
if (instance._subscriptions) {
|
||||
for (let type in instance._subscriptions) {
|
||||
instance._subscriptions[type]();
|
||||
}
|
||||
}
|
||||
|
||||
instance._subscriptions = null;
|
||||
instance._listeners = null;
|
||||
}
|
||||
|
||||
function getScaleX(props) {
|
||||
if (props.scaleX != null) {
|
||||
return props.scaleX;
|
||||
} else if (props.scale != null) {
|
||||
return props.scale;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
function getScaleY(props) {
|
||||
if (props.scaleY != null) {
|
||||
return props.scaleY;
|
||||
} else if (props.scale != null) {
|
||||
return props.scale;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
function isSameFont(oldFont, newFont) {
|
||||
if (oldFont === newFont) {
|
||||
return true;
|
||||
} else if (typeof newFont === 'string' || typeof oldFont === 'string') {
|
||||
return false;
|
||||
} else {
|
||||
return (
|
||||
newFont.fontSize === oldFont.fontSize &&
|
||||
newFont.fontStyle === oldFont.fontStyle &&
|
||||
newFont.fontVariant === oldFont.fontVariant &&
|
||||
newFont.fontWeight === oldFont.fontWeight &&
|
||||
newFont.fontFamily === oldFont.fontFamily
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/** Render Methods */
|
||||
|
||||
function applyClippingRectangleProps(instance, props, prevProps = {}) {
|
||||
applyNodeProps(instance, props, prevProps);
|
||||
|
||||
instance.width = props.width;
|
||||
instance.height = props.height;
|
||||
}
|
||||
|
||||
function applyGroupProps(instance, props, prevProps = {}) {
|
||||
applyNodeProps(instance, props, prevProps);
|
||||
|
||||
instance.width = props.width;
|
||||
instance.height = props.height;
|
||||
}
|
||||
|
||||
function applyNodeProps(instance, props, prevProps = {}) {
|
||||
const scaleX = getScaleX(props);
|
||||
const scaleY = getScaleY(props);
|
||||
|
||||
pooledTransform
|
||||
.transformTo(1, 0, 0, 1, 0, 0)
|
||||
.move(props.x || 0, props.y || 0)
|
||||
.rotate(props.rotation || 0, props.originX, props.originY)
|
||||
.scale(scaleX, scaleY, props.originX, props.originY);
|
||||
|
||||
if (props.transform != null) {
|
||||
pooledTransform.transform(props.transform);
|
||||
}
|
||||
|
||||
if (
|
||||
instance.xx !== pooledTransform.xx ||
|
||||
instance.yx !== pooledTransform.yx ||
|
||||
instance.xy !== pooledTransform.xy ||
|
||||
instance.yy !== pooledTransform.yy ||
|
||||
instance.x !== pooledTransform.x ||
|
||||
instance.y !== pooledTransform.y
|
||||
) {
|
||||
instance.transformTo(pooledTransform);
|
||||
}
|
||||
|
||||
if (props.cursor !== prevProps.cursor || props.title !== prevProps.title) {
|
||||
instance.indicate(props.cursor, props.title);
|
||||
}
|
||||
|
||||
if (instance.blend && props.opacity !== prevProps.opacity) {
|
||||
instance.blend(props.opacity == null ? 1 : props.opacity);
|
||||
}
|
||||
|
||||
if (props.visible !== prevProps.visible) {
|
||||
if (props.visible == null || props.visible) {
|
||||
instance.show();
|
||||
} else {
|
||||
instance.hide();
|
||||
}
|
||||
}
|
||||
|
||||
for (let type in EVENT_TYPES) {
|
||||
addEventListeners(instance, EVENT_TYPES[type], props[type]);
|
||||
}
|
||||
}
|
||||
|
||||
function applyRenderableNodeProps(instance, props, prevProps = {}) {
|
||||
applyNodeProps(instance, props, prevProps);
|
||||
|
||||
if (prevProps.fill !== props.fill) {
|
||||
if (props.fill && props.fill.applyFill) {
|
||||
props.fill.applyFill(instance);
|
||||
} else {
|
||||
instance.fill(props.fill);
|
||||
}
|
||||
}
|
||||
if (
|
||||
prevProps.stroke !== props.stroke ||
|
||||
prevProps.strokeWidth !== props.strokeWidth ||
|
||||
prevProps.strokeCap !== props.strokeCap ||
|
||||
prevProps.strokeJoin !== props.strokeJoin ||
|
||||
// TODO: Consider deep check of stokeDash; may benefit VML in IE.
|
||||
prevProps.strokeDash !== props.strokeDash
|
||||
) {
|
||||
instance.stroke(
|
||||
props.stroke,
|
||||
props.strokeWidth,
|
||||
props.strokeCap,
|
||||
props.strokeJoin,
|
||||
props.strokeDash,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function applyShapeProps(instance, props, prevProps = {}) {
|
||||
applyRenderableNodeProps(instance, props, prevProps);
|
||||
|
||||
const path = props.d || childrenAsString(props.children);
|
||||
|
||||
const prevDelta = instance._prevDelta;
|
||||
const prevPath = instance._prevPath;
|
||||
|
||||
if (
|
||||
path !== prevPath ||
|
||||
path.delta !== prevDelta ||
|
||||
prevProps.height !== props.height ||
|
||||
prevProps.width !== props.width
|
||||
) {
|
||||
instance.draw(path, props.width, props.height);
|
||||
|
||||
instance._prevDelta = path.delta;
|
||||
instance._prevPath = path;
|
||||
}
|
||||
}
|
||||
|
||||
function applyTextProps(instance, props, prevProps = {}) {
|
||||
applyRenderableNodeProps(instance, props, prevProps);
|
||||
|
||||
const string = props.children;
|
||||
|
||||
if (
|
||||
instance._currentString !== string ||
|
||||
!isSameFont(props.font, prevProps.font) ||
|
||||
props.alignment !== prevProps.alignment ||
|
||||
props.path !== prevProps.path
|
||||
) {
|
||||
instance.draw(string, props.font, props.alignment, props.path);
|
||||
|
||||
instance._currentString = string;
|
||||
}
|
||||
}
|
||||
|
||||
export * from 'shared/HostConfigWithNoPersistence';
|
||||
export * from 'shared/HostConfigWithNoHydration';
|
||||
|
||||
export function appendInitialChild(parentInstance, child) {
|
||||
if (typeof child === 'string') {
|
||||
// Noop for string children of Text (eg <Text>{'foo'}{'bar'}</Text>)
|
||||
invariant(false, 'Text children should already be flattened.');
|
||||
return;
|
||||
}
|
||||
|
||||
child.inject(parentInstance);
|
||||
}
|
||||
|
||||
export function createInstance(type, props, internalInstanceHandle) {
|
||||
let instance;
|
||||
|
||||
switch (type) {
|
||||
case TYPES.CLIPPING_RECTANGLE:
|
||||
instance = Mode.ClippingRectangle();
|
||||
instance._applyProps = applyClippingRectangleProps;
|
||||
break;
|
||||
case TYPES.GROUP:
|
||||
instance = Mode.Group();
|
||||
instance._applyProps = applyGroupProps;
|
||||
break;
|
||||
case TYPES.SHAPE:
|
||||
instance = Mode.Shape();
|
||||
instance._applyProps = applyShapeProps;
|
||||
break;
|
||||
case TYPES.TEXT:
|
||||
instance = Mode.Text(
|
||||
props.children,
|
||||
props.font,
|
||||
props.alignment,
|
||||
props.path,
|
||||
);
|
||||
instance._applyProps = applyTextProps;
|
||||
break;
|
||||
}
|
||||
|
||||
invariant(instance, 'ReactART does not support the type "%s"', type);
|
||||
|
||||
instance._applyProps(instance, props);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
export function createTextInstance(
|
||||
text,
|
||||
rootContainerInstance,
|
||||
internalInstanceHandle,
|
||||
) {
|
||||
return text;
|
||||
}
|
||||
|
||||
export function finalizeInitialChildren(domElement, type, props) {
|
||||
return false;
|
||||
}
|
||||
|
||||
export function getPublicInstance(instance) {
|
||||
return instance;
|
||||
}
|
||||
|
||||
export function prepareForCommit() {
|
||||
// Noop
|
||||
}
|
||||
|
||||
export function prepareUpdate(domElement, type, oldProps, newProps) {
|
||||
return UPDATE_SIGNAL;
|
||||
}
|
||||
|
||||
export function resetAfterCommit() {
|
||||
// Noop
|
||||
}
|
||||
|
||||
export function resetTextContent(domElement) {
|
||||
// Noop
|
||||
}
|
||||
|
||||
export function shouldDeprioritizeSubtree(type, props) {
|
||||
return false;
|
||||
}
|
||||
|
||||
export function getRootHostContext() {
|
||||
return NO_CONTEXT;
|
||||
}
|
||||
|
||||
export function getChildHostContext() {
|
||||
return NO_CONTEXT;
|
||||
}
|
||||
|
||||
export const scheduleDeferredCallback = ReactScheduler.scheduleWork;
|
||||
export const cancelDeferredCallback = ReactScheduler.cancelScheduledWork;
|
||||
|
||||
export function shouldSetTextContent(type, props) {
|
||||
return (
|
||||
typeof props.children === 'string' || typeof props.children === 'number'
|
||||
);
|
||||
}
|
||||
|
||||
export const now = ReactScheduler.now;
|
||||
|
||||
// The ART renderer is secondary to the React DOM renderer.
|
||||
export const isPrimaryRenderer = false;
|
||||
|
||||
export const supportsMutation = true;
|
||||
|
||||
export function appendChild(parentInstance, child) {
|
||||
if (child.parentNode === parentInstance) {
|
||||
child.eject();
|
||||
}
|
||||
child.inject(parentInstance);
|
||||
}
|
||||
|
||||
export function appendChildToContainer(parentInstance, child) {
|
||||
if (child.parentNode === parentInstance) {
|
||||
child.eject();
|
||||
}
|
||||
child.inject(parentInstance);
|
||||
}
|
||||
|
||||
export function insertBefore(parentInstance, child, beforeChild) {
|
||||
invariant(
|
||||
child !== beforeChild,
|
||||
'ReactART: Can not insert node before itself',
|
||||
);
|
||||
child.injectBefore(beforeChild);
|
||||
}
|
||||
|
||||
export function insertInContainerBefore(parentInstance, child, beforeChild) {
|
||||
invariant(
|
||||
child !== beforeChild,
|
||||
'ReactART: Can not insert node before itself',
|
||||
);
|
||||
child.injectBefore(beforeChild);
|
||||
}
|
||||
|
||||
export function removeChild(parentInstance, child) {
|
||||
destroyEventListeners(child);
|
||||
child.eject();
|
||||
}
|
||||
|
||||
export function removeChildFromContainer(parentInstance, child) {
|
||||
destroyEventListeners(child);
|
||||
child.eject();
|
||||
}
|
||||
|
||||
export function commitTextUpdate(textInstance, oldText, newText) {
|
||||
// Noop
|
||||
}
|
||||
|
||||
export function commitMount(instance, type, newProps) {
|
||||
// Noop
|
||||
}
|
||||
|
||||
export function commitUpdate(
|
||||
instance,
|
||||
updatePayload,
|
||||
type,
|
||||
oldProps,
|
||||
newProps,
|
||||
) {
|
||||
instance._applyProps(instance, newProps, oldProps);
|
||||
}
|
||||
34
packages/react-art/src/ReactARTInternals.js
vendored
Normal file
34
packages/react-art/src/ReactARTInternals.js
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export const TYPES = {
|
||||
CLIPPING_RECTANGLE: 'ClippingRectangle',
|
||||
GROUP: 'Group',
|
||||
SHAPE: 'Shape',
|
||||
TEXT: 'Text',
|
||||
};
|
||||
|
||||
export const EVENT_TYPES = {
|
||||
onClick: 'click',
|
||||
onMouseMove: 'mousemove',
|
||||
onMouseOver: 'mouseover',
|
||||
onMouseOut: 'mouseout',
|
||||
onMouseUp: 'mouseup',
|
||||
onMouseDown: 'mousedown',
|
||||
};
|
||||
|
||||
export function childrenAsString(children) {
|
||||
if (!children) {
|
||||
return '';
|
||||
} else if (typeof children === 'string') {
|
||||
return children;
|
||||
} else if (children.length) {
|
||||
return children.join('');
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
114
packages/react-art/src/__tests__/ReactART-test.js
vendored
114
packages/react-art/src/__tests__/ReactART-test.js
vendored
@@ -15,6 +15,19 @@ const React = require('react');
|
||||
const ReactDOM = require('react-dom');
|
||||
const ReactTestUtils = require('react-dom/test-utils');
|
||||
|
||||
// Isolate test renderer.
|
||||
jest.resetModules();
|
||||
const ReactTestRenderer = require('react-test-renderer');
|
||||
|
||||
// Isolate ART renderer.
|
||||
jest.resetModules();
|
||||
const ReactART = require('react-art');
|
||||
const ARTSVGMode = require('art/modes/svg');
|
||||
const ARTCurrentMode = require('art/modes/current');
|
||||
const Circle = require('react-art/Circle');
|
||||
const Rectangle = require('react-art/Rectangle');
|
||||
const Wedge = require('react-art/Wedge');
|
||||
|
||||
let Group;
|
||||
let Shape;
|
||||
let Surface;
|
||||
@@ -22,15 +35,6 @@ let TestComponent;
|
||||
|
||||
const Missing = {};
|
||||
|
||||
const ReactART = require('react-art');
|
||||
const ARTSVGMode = require('art/modes/svg');
|
||||
const ARTCurrentMode = require('art/modes/current');
|
||||
|
||||
const renderer = require('react-test-renderer');
|
||||
const Circle = require('react-art/Circle');
|
||||
const Rectangle = require('react-art/Rectangle');
|
||||
const Wedge = require('react-art/Wedge');
|
||||
|
||||
function testDOMNodeStructure(domNode, expectedStructure) {
|
||||
expect(domNode).toBeDefined();
|
||||
expect(domNode.nodeName).toBe(expectedStructure.nodeName);
|
||||
@@ -67,6 +71,8 @@ describe('ReactART', () => {
|
||||
Surface = ReactART.Surface;
|
||||
|
||||
TestComponent = class extends React.Component {
|
||||
group = React.createRef();
|
||||
|
||||
render() {
|
||||
const a = (
|
||||
<Shape
|
||||
@@ -100,7 +106,7 @@ describe('ReactART', () => {
|
||||
|
||||
return (
|
||||
<Surface width={150} height={200}>
|
||||
<Group ref="group">
|
||||
<Group ref={this.group}>
|
||||
{this.props.flipped ? [b, a, c] : [a, b, c]}
|
||||
</Group>
|
||||
</Surface>
|
||||
@@ -117,7 +123,7 @@ describe('ReactART', () => {
|
||||
it('should have the correct lifecycle state', () => {
|
||||
let instance = <TestComponent />;
|
||||
instance = ReactTestUtils.renderIntoDocument(instance);
|
||||
const group = instance.refs.group;
|
||||
const group = instance.group.current;
|
||||
// Duck type test for an ART group
|
||||
expect(typeof group.indicate).toBe('function');
|
||||
});
|
||||
@@ -256,15 +262,17 @@ describe('ReactART', () => {
|
||||
let ref = null;
|
||||
|
||||
class Outer extends React.Component {
|
||||
test = React.createRef();
|
||||
|
||||
componentDidMount() {
|
||||
ref = this.refs.test;
|
||||
ref = this.test.current;
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Surface>
|
||||
<Group>
|
||||
<CustomShape ref="test" />
|
||||
<CustomShape ref={this.test} />
|
||||
</Group>
|
||||
</Surface>
|
||||
);
|
||||
@@ -285,26 +293,28 @@ describe('ReactART', () => {
|
||||
let ref = {};
|
||||
|
||||
class Outer extends React.Component {
|
||||
test = React.createRef();
|
||||
|
||||
componentDidMount() {
|
||||
ref = this.refs.test;
|
||||
ref = this.test.current;
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
ref = this.refs.test;
|
||||
ref = this.test.current;
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Surface>
|
||||
<Group>
|
||||
{this.props.mountCustomShape && <CustomShape ref="test" />}
|
||||
{this.props.mountCustomShape && <CustomShape ref={this.test} />}
|
||||
</Group>
|
||||
</Surface>
|
||||
);
|
||||
}
|
||||
}
|
||||
ReactDOM.render(<Outer />, container);
|
||||
expect(ref).not.toBeDefined();
|
||||
expect(ref).toBe(null);
|
||||
ReactDOM.render(<Outer mountCustomShape={true} />, container);
|
||||
expect(ref.constructor).toBe(CustomShape);
|
||||
});
|
||||
@@ -339,11 +349,65 @@ describe('ReactART', () => {
|
||||
doClick(instance);
|
||||
expect(onClick2).toBeCalled();
|
||||
});
|
||||
|
||||
it('can concurrently render with a "primary" renderer while sharing context', () => {
|
||||
const CurrentRendererContext = React.createContext(null);
|
||||
|
||||
function Yield(props) {
|
||||
testRenderer.unstable_yield(props.value);
|
||||
return null;
|
||||
}
|
||||
|
||||
let ops = [];
|
||||
function LogCurrentRenderer() {
|
||||
return (
|
||||
<CurrentRendererContext.Consumer>
|
||||
{currentRenderer => {
|
||||
ops.push(currentRenderer);
|
||||
return null;
|
||||
}}
|
||||
</CurrentRendererContext.Consumer>
|
||||
);
|
||||
}
|
||||
|
||||
// Using test renderer instead of the DOM renderer here because async
|
||||
// testing APIs for the DOM renderer don't exist.
|
||||
const testRenderer = ReactTestRenderer.create(
|
||||
<CurrentRendererContext.Provider value="Test">
|
||||
<Yield value="A" />
|
||||
<Yield value="B" />
|
||||
<LogCurrentRenderer />
|
||||
<Yield value="C" />
|
||||
</CurrentRendererContext.Provider>,
|
||||
{
|
||||
unstable_isAsync: true,
|
||||
},
|
||||
);
|
||||
|
||||
testRenderer.unstable_flushThrough(['A']);
|
||||
|
||||
ReactDOM.render(
|
||||
<Surface>
|
||||
<LogCurrentRenderer />
|
||||
<CurrentRendererContext.Provider value="ART">
|
||||
<LogCurrentRenderer />
|
||||
</CurrentRendererContext.Provider>
|
||||
</Surface>,
|
||||
container,
|
||||
);
|
||||
|
||||
expect(ops).toEqual([null, 'ART']);
|
||||
|
||||
ops = [];
|
||||
expect(testRenderer.unstable_flushAll()).toEqual(['B', 'C']);
|
||||
|
||||
expect(ops).toEqual(['Test']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ReactARTComponents', () => {
|
||||
it('should generate a <Shape> with props for drawing the Circle', () => {
|
||||
const circle = renderer.create(
|
||||
const circle = ReactTestRenderer.create(
|
||||
<Circle radius={10} stroke="green" strokeWidth={3} fill="blue" />,
|
||||
);
|
||||
expect(circle.toJSON()).toMatchSnapshot();
|
||||
@@ -351,7 +415,9 @@ describe('ReactARTComponents', () => {
|
||||
|
||||
it('should warn if radius is missing on a Circle component', () => {
|
||||
expect(() =>
|
||||
renderer.create(<Circle stroke="green" strokeWidth={3} fill="blue" />),
|
||||
ReactTestRenderer.create(
|
||||
<Circle stroke="green" strokeWidth={3} fill="blue" />,
|
||||
),
|
||||
).toWarnDev(
|
||||
'Warning: Failed prop type: The prop `radius` is marked as required in `Circle`, ' +
|
||||
'but its value is `undefined`.' +
|
||||
@@ -360,7 +426,7 @@ describe('ReactARTComponents', () => {
|
||||
});
|
||||
|
||||
it('should generate a <Shape> with props for drawing the Rectangle', () => {
|
||||
const rectangle = renderer.create(
|
||||
const rectangle = ReactTestRenderer.create(
|
||||
<Rectangle width={50} height={50} stroke="green" fill="blue" />,
|
||||
);
|
||||
expect(rectangle.toJSON()).toMatchSnapshot();
|
||||
@@ -368,7 +434,7 @@ describe('ReactARTComponents', () => {
|
||||
|
||||
it('should warn if width/height is missing on a Rectangle component', () => {
|
||||
expect(() =>
|
||||
renderer.create(<Rectangle stroke="green" fill="blue" />),
|
||||
ReactTestRenderer.create(<Rectangle stroke="green" fill="blue" />),
|
||||
).toWarnDev([
|
||||
'Warning: Failed prop type: The prop `width` is marked as required in `Rectangle`, ' +
|
||||
'but its value is `undefined`.' +
|
||||
@@ -380,21 +446,21 @@ describe('ReactARTComponents', () => {
|
||||
});
|
||||
|
||||
it('should generate a <Shape> with props for drawing the Wedge', () => {
|
||||
const wedge = renderer.create(
|
||||
const wedge = ReactTestRenderer.create(
|
||||
<Wedge outerRadius={50} startAngle={0} endAngle={360} fill="blue" />,
|
||||
);
|
||||
expect(wedge.toJSON()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should return null if startAngle equals to endAngle on Wedge', () => {
|
||||
const wedge = renderer.create(
|
||||
const wedge = ReactTestRenderer.create(
|
||||
<Wedge outerRadius={50} startAngle={0} endAngle={0} fill="blue" />,
|
||||
);
|
||||
expect(wedge.toJSON()).toBeNull();
|
||||
});
|
||||
|
||||
it('should warn if outerRadius/startAngle/endAngle is missing on a Wedge component', () => {
|
||||
expect(() => renderer.create(<Wedge fill="blue" />)).toWarnDev([
|
||||
expect(() => ReactTestRenderer.create(<Wedge fill="blue" />)).toWarnDev([
|
||||
'Warning: Failed prop type: The prop `outerRadius` is marked as required in `Wedge`, ' +
|
||||
'but its value is `undefined`.' +
|
||||
'\n in Wedge (at **)',
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
# react-call-return
|
||||
|
||||
This is an experimental package for multi-pass rendering in React.
|
||||
|
||||
**Its API is not as stable as that of React, React Native, or React DOM, and does not follow the common versioning scheme.**
|
||||
|
||||
**Use it at your own risk.**
|
||||
|
||||
# No, Really, It Is Unstable
|
||||
|
||||
This is **an experiment**.
|
||||
|
||||
We **will** replace this with a different API in the future.
|
||||
It can break between patch versions of React.
|
||||
|
||||
We also know that **it has bugs**.
|
||||
|
||||
Don't rely on this for anything except experiments.
|
||||
Even in experiments, make sure to lock the versions so that an update doesn't break your app.
|
||||
|
||||
Don't publish third party components relying on this unless you clearly mark them as experimental too.
|
||||
They will break.
|
||||
|
||||
Have fun! Let us know if you find interesting use cases for it.
|
||||
|
||||
# API
|
||||
|
||||
See the test case in `src/__tests__/ReactCallReturn.js` for an example.
|
||||
|
||||
# What and Why
|
||||
|
||||
The API is not very intuitive right now, but [this is a good overview](https://cdb.reacttraining.com/react-call-return-what-and-why-7e7761f81843) of why it might be useful in some cases. We are very open to better API ideas for this concept.
|
||||
7
packages/react-call-return/npm/index.js
vendored
7
packages/react-call-return/npm/index.js
vendored
@@ -1,7 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
module.exports = require('./cjs/react-call-return.production.min.js');
|
||||
} else {
|
||||
module.exports = require('./cjs/react-call-return.development.js');
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"name": "react-call-return",
|
||||
"description": "Experimental APIs for multi-pass rendering in React.",
|
||||
"version": "0.8.0",
|
||||
"repository": "facebook/react",
|
||||
"files": [
|
||||
"LICENSE",
|
||||
"README.md",
|
||||
"index.js",
|
||||
"cjs/"
|
||||
],
|
||||
"dependencies": {
|
||||
"fbjs": "^0.8.16",
|
||||
"object-assign": "^4.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.0.0"
|
||||
}
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2014-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.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import {
|
||||
REACT_CALL_TYPE,
|
||||
REACT_RETURN_TYPE,
|
||||
REACT_ELEMENT_TYPE,
|
||||
} from 'shared/ReactSymbols';
|
||||
|
||||
import type {ReactCall, ReactNodeList, ReactReturn} from 'shared/ReactTypes';
|
||||
|
||||
type CallHandler<T, V> = (props: T, returns: Array<V>) => ReactNodeList;
|
||||
|
||||
export function unstable_createCall<T, V>(
|
||||
children: ReactNodeList,
|
||||
handler: CallHandler<T, V>,
|
||||
props: T,
|
||||
key: ?string = null,
|
||||
): ReactCall<V> {
|
||||
const call = {
|
||||
// This tag allow us to uniquely identify this as a React Call
|
||||
$$typeof: REACT_ELEMENT_TYPE,
|
||||
type: REACT_CALL_TYPE,
|
||||
key: key == null ? null : '' + key,
|
||||
ref: null,
|
||||
props: {
|
||||
props,
|
||||
handler,
|
||||
children: children,
|
||||
},
|
||||
};
|
||||
|
||||
if (__DEV__) {
|
||||
// TODO: Add _store property for marking this as validated.
|
||||
if (Object.freeze) {
|
||||
Object.freeze(call.props);
|
||||
Object.freeze(call);
|
||||
}
|
||||
}
|
||||
|
||||
return call;
|
||||
}
|
||||
|
||||
export function unstable_createReturn<V>(value: V): ReactReturn<V> {
|
||||
const returnNode = {
|
||||
// This tag allow us to uniquely identify this as a React Call
|
||||
$$typeof: REACT_ELEMENT_TYPE,
|
||||
type: REACT_RETURN_TYPE,
|
||||
key: null,
|
||||
ref: null,
|
||||
props: {
|
||||
value,
|
||||
},
|
||||
};
|
||||
|
||||
if (__DEV__) {
|
||||
// TODO: Add _store property for marking this as validated.
|
||||
if (Object.freeze) {
|
||||
Object.freeze(returnNode);
|
||||
}
|
||||
}
|
||||
|
||||
return returnNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies the object is a call object.
|
||||
*/
|
||||
export function unstable_isCall(object: mixed): boolean {
|
||||
return (
|
||||
typeof object === 'object' &&
|
||||
object !== null &&
|
||||
object.type === REACT_CALL_TYPE
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies the object is a return object.
|
||||
*/
|
||||
export function unstable_isReturn(object: mixed): boolean {
|
||||
return (
|
||||
typeof object === 'object' &&
|
||||
object !== null &&
|
||||
object.type === REACT_RETURN_TYPE
|
||||
);
|
||||
}
|
||||
|
||||
export const unstable_REACT_RETURN_TYPE = REACT_RETURN_TYPE;
|
||||
export const unstable_REACT_CALL_TYPE = REACT_CALL_TYPE;
|
||||
@@ -1,326 +0,0 @@
|
||||
/**
|
||||
* 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 ReactFeatureFlags;
|
||||
let ReactNoop;
|
||||
let ReactCallReturn;
|
||||
|
||||
describe('ReactCallReturn', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
ReactFeatureFlags = require('shared/ReactFeatureFlags');
|
||||
ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
|
||||
React = require('react');
|
||||
ReactNoop = require('react-noop-renderer');
|
||||
ReactCallReturn = require('react-call-return');
|
||||
});
|
||||
|
||||
function div(...children) {
|
||||
children = children.map(c => (typeof c === 'string' ? {text: c} : c));
|
||||
return {type: 'div', children, prop: undefined};
|
||||
}
|
||||
|
||||
function span(prop) {
|
||||
return {type: 'span', children: [], prop};
|
||||
}
|
||||
|
||||
it('should render a call', () => {
|
||||
const ops = [];
|
||||
|
||||
function Continuation({isSame}) {
|
||||
ops.push(['Continuation', isSame]);
|
||||
return <span prop={isSame ? 'foo==bar' : 'foo!=bar'} />;
|
||||
}
|
||||
|
||||
// An alternative API could mark Continuation as something that needs
|
||||
// returning. E.g. Continuation.returnType = 123;
|
||||
function Child({bar}) {
|
||||
ops.push(['Child', bar]);
|
||||
return ReactCallReturn.unstable_createReturn({
|
||||
props: {
|
||||
bar: bar,
|
||||
},
|
||||
continuation: Continuation,
|
||||
});
|
||||
}
|
||||
|
||||
function Indirection() {
|
||||
ops.push('Indirection');
|
||||
return [<Child key="a" bar={true} />, <Child key="b" bar={false} />];
|
||||
}
|
||||
|
||||
function HandleReturns(props, returns) {
|
||||
ops.push('HandleReturns');
|
||||
return returns.map((y, i) => (
|
||||
<y.continuation key={i} isSame={props.foo === y.props.bar} />
|
||||
));
|
||||
}
|
||||
|
||||
// An alternative API could mark Parent as something that needs
|
||||
// returning. E.g. Parent.handler = HandleReturns;
|
||||
function Parent(props) {
|
||||
ops.push('Parent');
|
||||
return ReactCallReturn.unstable_createCall(
|
||||
props.children,
|
||||
HandleReturns,
|
||||
props,
|
||||
);
|
||||
}
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<div>
|
||||
<Parent foo={true}>
|
||||
<Indirection />
|
||||
</Parent>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
ReactNoop.render(<App />);
|
||||
ReactNoop.flush();
|
||||
|
||||
expect(ops).toEqual([
|
||||
'Parent',
|
||||
'Indirection',
|
||||
['Child', true],
|
||||
// Return
|
||||
['Child', false],
|
||||
// Return
|
||||
'HandleReturns',
|
||||
// Call continuations
|
||||
['Continuation', true],
|
||||
['Continuation', false],
|
||||
]);
|
||||
expect(ReactNoop.getChildren()).toEqual([
|
||||
div(span('foo==bar'), span('foo!=bar')),
|
||||
]);
|
||||
});
|
||||
|
||||
it('should update a call', () => {
|
||||
function Continuation({isSame}) {
|
||||
return <span prop={isSame ? 'foo==bar' : 'foo!=bar'} />;
|
||||
}
|
||||
|
||||
function Child({bar}) {
|
||||
return ReactCallReturn.unstable_createReturn({
|
||||
props: {
|
||||
bar: bar,
|
||||
},
|
||||
continuation: Continuation,
|
||||
});
|
||||
}
|
||||
|
||||
function Indirection() {
|
||||
return [<Child key="a" bar={true} />, <Child key="b" bar={false} />];
|
||||
}
|
||||
|
||||
function HandleReturns(props, returns) {
|
||||
return returns.map((y, i) => (
|
||||
<y.continuation key={i} isSame={props.foo === y.props.bar} />
|
||||
));
|
||||
}
|
||||
|
||||
function Parent(props) {
|
||||
return ReactCallReturn.unstable_createCall(
|
||||
props.children,
|
||||
HandleReturns,
|
||||
props,
|
||||
);
|
||||
}
|
||||
|
||||
function App(props) {
|
||||
return (
|
||||
<div>
|
||||
<Parent foo={props.foo}>
|
||||
<Indirection />
|
||||
</Parent>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
ReactNoop.render(<App foo={true} />);
|
||||
ReactNoop.flush();
|
||||
expect(ReactNoop.getChildren()).toEqual([
|
||||
div(span('foo==bar'), span('foo!=bar')),
|
||||
]);
|
||||
|
||||
ReactNoop.render(<App foo={false} />);
|
||||
ReactNoop.flush();
|
||||
expect(ReactNoop.getChildren()).toEqual([
|
||||
div(span('foo!=bar'), span('foo==bar')),
|
||||
]);
|
||||
});
|
||||
|
||||
it('should unmount a composite in a call', () => {
|
||||
let ops = [];
|
||||
|
||||
class Continuation extends React.Component {
|
||||
render() {
|
||||
ops.push('Continuation');
|
||||
return <div />;
|
||||
}
|
||||
componentWillUnmount() {
|
||||
ops.push('Unmount Continuation');
|
||||
}
|
||||
}
|
||||
|
||||
class Child extends React.Component {
|
||||
render() {
|
||||
ops.push('Child');
|
||||
return ReactCallReturn.unstable_createReturn(Continuation);
|
||||
}
|
||||
componentWillUnmount() {
|
||||
ops.push('Unmount Child');
|
||||
}
|
||||
}
|
||||
|
||||
function HandleReturns(props, returns) {
|
||||
ops.push('HandleReturns');
|
||||
return returns.map((ContinuationComponent, i) => (
|
||||
<ContinuationComponent key={i} />
|
||||
));
|
||||
}
|
||||
|
||||
class Parent extends React.Component {
|
||||
render() {
|
||||
ops.push('Parent');
|
||||
return ReactCallReturn.unstable_createCall(
|
||||
this.props.children,
|
||||
HandleReturns,
|
||||
this.props,
|
||||
);
|
||||
}
|
||||
componentWillUnmount() {
|
||||
ops.push('Unmount Parent');
|
||||
}
|
||||
}
|
||||
|
||||
ReactNoop.render(
|
||||
<Parent>
|
||||
<Child />
|
||||
</Parent>,
|
||||
);
|
||||
ReactNoop.flush();
|
||||
|
||||
expect(ops).toEqual(['Parent', 'Child', 'HandleReturns', 'Continuation']);
|
||||
|
||||
ops = [];
|
||||
|
||||
ReactNoop.render(<div />);
|
||||
ReactNoop.flush();
|
||||
|
||||
expect(ops).toEqual([
|
||||
'Unmount Parent',
|
||||
'Unmount Child',
|
||||
'Unmount Continuation',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle deep updates in call', () => {
|
||||
let instances = {};
|
||||
|
||||
class Counter extends React.Component {
|
||||
state = {value: 5};
|
||||
render() {
|
||||
instances[this.props.id] = this;
|
||||
return ReactCallReturn.unstable_createReturn(this.state.value);
|
||||
}
|
||||
}
|
||||
|
||||
function App(props) {
|
||||
return ReactCallReturn.unstable_createCall(
|
||||
[
|
||||
<Counter key="a" id="a" />,
|
||||
<Counter key="b" id="b" />,
|
||||
<Counter key="c" id="c" />,
|
||||
],
|
||||
(p, returns) => returns.map((y, i) => <span key={i} prop={y * 100} />),
|
||||
{},
|
||||
);
|
||||
}
|
||||
|
||||
ReactNoop.render(<App />);
|
||||
ReactNoop.flush();
|
||||
expect(ReactNoop.getChildren()).toEqual([span(500), span(500), span(500)]);
|
||||
|
||||
instances.a.setState({value: 1});
|
||||
instances.b.setState({value: 2});
|
||||
ReactNoop.flush();
|
||||
expect(ReactNoop.getChildren()).toEqual([span(100), span(200), span(500)]);
|
||||
});
|
||||
|
||||
it('should unmount and remount children', () => {
|
||||
let ops = [];
|
||||
|
||||
class Call extends React.Component {
|
||||
render() {
|
||||
return ReactCallReturn.unstable_createCall(
|
||||
this.props.children,
|
||||
(p, returns) => returns,
|
||||
{},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class Return extends React.Component {
|
||||
render() {
|
||||
ops.push(`Return ${this.props.value}`);
|
||||
return ReactCallReturn.unstable_createReturn(this.props.children);
|
||||
}
|
||||
|
||||
UNSAFE_componentWillMount() {
|
||||
ops.push(`Mount Return ${this.props.value}`);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
ops.push(`Unmount Return ${this.props.value}`);
|
||||
}
|
||||
}
|
||||
|
||||
ReactNoop.render(
|
||||
<Call>
|
||||
<Return value={1} />
|
||||
<Return value={2} />
|
||||
</Call>,
|
||||
);
|
||||
expect(ReactNoop.flush).toWarnDev(
|
||||
'componentWillMount: Please update the following components ' +
|
||||
'to use componentDidMount instead: Return',
|
||||
);
|
||||
|
||||
expect(ops).toEqual([
|
||||
'Mount Return 1',
|
||||
'Return 1',
|
||||
'Mount Return 2',
|
||||
'Return 2',
|
||||
]);
|
||||
|
||||
ops = [];
|
||||
|
||||
ReactNoop.render(<Call />);
|
||||
ReactNoop.flush();
|
||||
|
||||
expect(ops).toEqual(['Unmount Return 1', 'Unmount Return 2']);
|
||||
|
||||
ops = [];
|
||||
|
||||
ReactNoop.render(
|
||||
<Call>
|
||||
<Return value={3} />
|
||||
</Call>,
|
||||
);
|
||||
ReactNoop.flush();
|
||||
|
||||
expect(ops).toEqual(['Mount Return 3', 'Return 3']);
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "react-dom",
|
||||
"version": "16.3.2",
|
||||
"version": "16.4.1",
|
||||
"description": "React package for working with the DOM.",
|
||||
"main": "index.js",
|
||||
"repository": "facebook/react",
|
||||
@@ -13,10 +13,9 @@
|
||||
},
|
||||
"homepage": "https://reactjs.org/",
|
||||
"dependencies": {
|
||||
"fbjs": "^0.8.16",
|
||||
"loose-envify": "^1.1.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"prop-types": "^15.6.0"
|
||||
"prop-types": "^15.6.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.0.0"
|
||||
|
||||
@@ -123,7 +123,7 @@ describe('DOMPropertyOperations', () => {
|
||||
spyOnDevAndProd(container.firstChild, 'setAttribute');
|
||||
ReactDOM.render(<progress value={30} />, container);
|
||||
ReactDOM.render(<progress value="30" />, container);
|
||||
expect(container.firstChild.setAttribute.calls.count()).toBe(2);
|
||||
expect(container.firstChild.setAttribute).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -155,5 +155,11 @@ describe('DOMPropertyOperations', () => {
|
||||
expect(container.firstChild.getAttribute('value')).toBe('foo');
|
||||
expect(container.firstChild.value).toBe('foo');
|
||||
});
|
||||
|
||||
it('should not remove attributes for custom component tag', () => {
|
||||
const container = document.createElement('div');
|
||||
ReactDOM.render(<my-icon size="5px" />, container);
|
||||
expect(container.firstChild.getAttribute('size')).toBe('5px');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -42,6 +42,8 @@ let getListener;
|
||||
let putListener;
|
||||
let deleteAllListeners;
|
||||
|
||||
let container;
|
||||
|
||||
function registerSimpleTestHandler() {
|
||||
putListener(CHILD, ON_CLICK_KEY, LISTENER);
|
||||
const listener = getListener(CHILD, ON_CLICK_KEY);
|
||||
@@ -63,7 +65,8 @@ describe('ReactBrowserEventEmitter', () => {
|
||||
ReactBrowserEventEmitter = require('../events/ReactBrowserEventEmitter');
|
||||
ReactTestUtils = require('react-dom/test-utils');
|
||||
|
||||
const container = document.createElement('div');
|
||||
container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
|
||||
let GRANDPARENT_PROPS = {};
|
||||
let PARENT_PROPS = {};
|
||||
@@ -129,6 +132,11 @@ describe('ReactBrowserEventEmitter', () => {
|
||||
idCallOrder = [];
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
document.body.removeChild(container);
|
||||
container = null;
|
||||
});
|
||||
|
||||
it('should store a listener correctly', () => {
|
||||
registerSimpleTestHandler();
|
||||
const listener = getListener(CHILD, ON_CLICK_KEY);
|
||||
@@ -150,25 +158,25 @@ describe('ReactBrowserEventEmitter', () => {
|
||||
|
||||
it('should invoke a simple handler registered on a node', () => {
|
||||
registerSimpleTestHandler();
|
||||
ReactTestUtils.Simulate.click(CHILD);
|
||||
expect(LISTENER.mock.calls.length).toBe(1);
|
||||
CHILD.click();
|
||||
expect(LISTENER).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should not invoke handlers if ReactBrowserEventEmitter is disabled', () => {
|
||||
registerSimpleTestHandler();
|
||||
ReactBrowserEventEmitter.setEnabled(false);
|
||||
ReactTestUtils.SimulateNative.click(CHILD);
|
||||
expect(LISTENER.mock.calls.length).toBe(0);
|
||||
CHILD.click();
|
||||
expect(LISTENER).toHaveBeenCalledTimes(0);
|
||||
ReactBrowserEventEmitter.setEnabled(true);
|
||||
ReactTestUtils.SimulateNative.click(CHILD);
|
||||
expect(LISTENER.mock.calls.length).toBe(1);
|
||||
CHILD.click();
|
||||
expect(LISTENER).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should bubble simply', () => {
|
||||
putListener(CHILD, ON_CLICK_KEY, recordID.bind(null, CHILD));
|
||||
putListener(PARENT, ON_CLICK_KEY, recordID.bind(null, PARENT));
|
||||
putListener(GRANDPARENT, ON_CLICK_KEY, recordID.bind(null, GRANDPARENT));
|
||||
ReactTestUtils.Simulate.click(CHILD);
|
||||
CHILD.click();
|
||||
expect(idCallOrder.length).toBe(3);
|
||||
expect(idCallOrder[0]).toBe(CHILD);
|
||||
expect(idCallOrder[1]).toBe(PARENT);
|
||||
@@ -179,7 +187,7 @@ describe('ReactBrowserEventEmitter', () => {
|
||||
putListener(GRANDPARENT, ON_CLICK_KEY, recordID.bind(null, 'GRANDPARENT'));
|
||||
putListener(PARENT, ON_CLICK_KEY, recordID.bind(null, 'PARENT'));
|
||||
putListener(CHILD, ON_CLICK_KEY, recordID.bind(null, 'CHILD'));
|
||||
ReactTestUtils.Simulate.click(CHILD);
|
||||
CHILD.click();
|
||||
expect(idCallOrder).toEqual(['CHILD', 'PARENT', 'GRANDPARENT']);
|
||||
|
||||
idCallOrder = [];
|
||||
@@ -191,7 +199,7 @@ describe('ReactBrowserEventEmitter', () => {
|
||||
recordID.bind(null, 'UPDATED_GRANDPARENT'),
|
||||
);
|
||||
|
||||
ReactTestUtils.Simulate.click(CHILD);
|
||||
CHILD.click();
|
||||
expect(idCallOrder).toEqual(['CHILD', 'PARENT', 'UPDATED_GRANDPARENT']);
|
||||
});
|
||||
|
||||
@@ -224,7 +232,7 @@ describe('ReactBrowserEventEmitter', () => {
|
||||
recordID(GRANDPARENT);
|
||||
expect(event.currentTarget).toBe(GRANDPARENT);
|
||||
});
|
||||
ReactTestUtils.Simulate.click(CHILD);
|
||||
CHILD.click();
|
||||
expect(idCallOrder.length).toBe(3);
|
||||
expect(idCallOrder[0]).toBe(CHILD);
|
||||
expect(idCallOrder[1]).toBe(PARENT);
|
||||
@@ -239,7 +247,7 @@ describe('ReactBrowserEventEmitter', () => {
|
||||
recordIDAndStopPropagation.bind(null, PARENT),
|
||||
);
|
||||
putListener(GRANDPARENT, ON_CLICK_KEY, recordID.bind(null, GRANDPARENT));
|
||||
ReactTestUtils.Simulate.click(CHILD);
|
||||
CHILD.click();
|
||||
expect(idCallOrder.length).toBe(2);
|
||||
expect(idCallOrder[0]).toBe(CHILD);
|
||||
expect(idCallOrder[1]).toBe(PARENT);
|
||||
@@ -254,7 +262,7 @@ describe('ReactBrowserEventEmitter', () => {
|
||||
e.isPropagationStopped = () => true;
|
||||
});
|
||||
putListener(GRANDPARENT, ON_CLICK_KEY, recordID.bind(null, GRANDPARENT));
|
||||
ReactTestUtils.Simulate.click(CHILD);
|
||||
CHILD.click();
|
||||
expect(idCallOrder.length).toBe(2);
|
||||
expect(idCallOrder[0]).toBe(CHILD);
|
||||
expect(idCallOrder[1]).toBe(PARENT);
|
||||
@@ -268,7 +276,7 @@ describe('ReactBrowserEventEmitter', () => {
|
||||
);
|
||||
putListener(PARENT, ON_CLICK_KEY, recordID.bind(null, PARENT));
|
||||
putListener(GRANDPARENT, ON_CLICK_KEY, recordID.bind(null, GRANDPARENT));
|
||||
ReactTestUtils.Simulate.click(CHILD);
|
||||
CHILD.click();
|
||||
expect(idCallOrder.length).toBe(1);
|
||||
expect(idCallOrder[0]).toBe(CHILD);
|
||||
});
|
||||
@@ -277,7 +285,7 @@ describe('ReactBrowserEventEmitter', () => {
|
||||
putListener(CHILD, ON_CLICK_KEY, recordIDAndReturnFalse.bind(null, CHILD));
|
||||
putListener(PARENT, ON_CLICK_KEY, recordID.bind(null, PARENT));
|
||||
putListener(GRANDPARENT, ON_CLICK_KEY, recordID.bind(null, GRANDPARENT));
|
||||
ReactTestUtils.Simulate.click(CHILD);
|
||||
CHILD.click();
|
||||
expect(idCallOrder.length).toBe(3);
|
||||
expect(idCallOrder[0]).toBe(CHILD);
|
||||
expect(idCallOrder[1]).toBe(PARENT);
|
||||
@@ -300,8 +308,8 @@ describe('ReactBrowserEventEmitter', () => {
|
||||
};
|
||||
putListener(CHILD, ON_CLICK_KEY, handleChildClick);
|
||||
putListener(PARENT, ON_CLICK_KEY, handleParentClick);
|
||||
ReactTestUtils.Simulate.click(CHILD);
|
||||
expect(handleParentClick.mock.calls.length).toBe(1);
|
||||
CHILD.click();
|
||||
expect(handleParentClick).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should not invoke newly inserted handlers while bubbling', () => {
|
||||
@@ -310,8 +318,8 @@ describe('ReactBrowserEventEmitter', () => {
|
||||
putListener(PARENT, ON_CLICK_KEY, handleParentClick);
|
||||
};
|
||||
putListener(CHILD, ON_CLICK_KEY, handleChildClick);
|
||||
ReactTestUtils.Simulate.click(CHILD);
|
||||
expect(handleParentClick.mock.calls.length).toBe(0);
|
||||
CHILD.click();
|
||||
expect(handleParentClick).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it('should have mouse enter simulated by test utils', () => {
|
||||
@@ -325,7 +333,7 @@ describe('ReactBrowserEventEmitter', () => {
|
||||
spyOnDevAndProd(EventTarget.prototype, 'addEventListener');
|
||||
ReactBrowserEventEmitter.listenTo(ON_CLICK_KEY, document);
|
||||
ReactBrowserEventEmitter.listenTo(ON_CLICK_KEY, document);
|
||||
expect(EventTarget.prototype.addEventListener.calls.count()).toBe(1);
|
||||
expect(EventTarget.prototype.addEventListener).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should work with event plugins without dependencies', () => {
|
||||
|
||||
@@ -278,7 +278,7 @@ describe('ReactComponent', () => {
|
||||
componentDidMount() {
|
||||
// Check .props.title to make sure we got the right elements back
|
||||
expect(this.wrapperRef.getTitle()).toBe('wrapper');
|
||||
expect(ReactDOM.findDOMNode(this.innerRef).className).toBe('inner');
|
||||
expect(this.innerRef.className).toBe('inner');
|
||||
mounted = true;
|
||||
}
|
||||
}
|
||||
@@ -387,11 +387,11 @@ describe('ReactComponent', () => {
|
||||
const callback = jest.fn();
|
||||
const container = document.createElement('div');
|
||||
ReactDOM.render(<div />, container, callback);
|
||||
expect(callback.mock.calls.length).toBe(1);
|
||||
expect(callback).toHaveBeenCalledTimes(1);
|
||||
ReactDOM.render(<div className="foo" />, container, callback);
|
||||
expect(callback.mock.calls.length).toBe(2);
|
||||
expect(callback).toHaveBeenCalledTimes(2);
|
||||
ReactDOM.render(<span />, container, callback);
|
||||
expect(callback.mock.calls.length).toBe(3);
|
||||
expect(callback).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
it('throws usefully when rendering badly-typed elements', () => {
|
||||
|
||||
@@ -63,28 +63,9 @@ describe('ReactComponentLifeCycle', () => {
|
||||
});
|
||||
|
||||
describe('react-lifecycles-compat', () => {
|
||||
const polyfill = require('react-lifecycles-compat');
|
||||
|
||||
it('should not warn about deprecated cWM/cWRP for polyfilled components', () => {
|
||||
class PolyfilledComponent extends React.Component {
|
||||
state = {};
|
||||
static getDerivedStateFromProps() {
|
||||
return null;
|
||||
}
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
polyfill(PolyfilledComponent);
|
||||
|
||||
const container = document.createElement('div');
|
||||
ReactDOM.render(<PolyfilledComponent />, container);
|
||||
});
|
||||
|
||||
it('should not warn about unsafe lifecycles within "strict" tree for polyfilled components', () => {
|
||||
const {StrictMode} = React;
|
||||
const {polyfill} = require('react-lifecycles-compat');
|
||||
|
||||
it('should not warn for components with polyfilled getDerivedStateFromProps', () => {
|
||||
class PolyfilledComponent extends React.Component {
|
||||
state = {};
|
||||
static getDerivedStateFromProps() {
|
||||
@@ -99,9 +80,31 @@ describe('ReactComponentLifeCycle', () => {
|
||||
|
||||
const container = document.createElement('div');
|
||||
ReactDOM.render(
|
||||
<StrictMode>
|
||||
<React.StrictMode>
|
||||
<PolyfilledComponent />
|
||||
</StrictMode>,
|
||||
</React.StrictMode>,
|
||||
container,
|
||||
);
|
||||
});
|
||||
|
||||
it('should not warn for components with polyfilled getSnapshotBeforeUpdate', () => {
|
||||
class PolyfilledComponent extends React.Component {
|
||||
getSnapshotBeforeUpdate() {
|
||||
return null;
|
||||
}
|
||||
componentDidUpdate() {}
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
polyfill(PolyfilledComponent);
|
||||
|
||||
const container = document.createElement('div');
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<PolyfilledComponent />
|
||||
</React.StrictMode>,
|
||||
container,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1065,16 +1065,22 @@ describe('ReactComponentLifeCycle', () => {
|
||||
}
|
||||
}
|
||||
|
||||
ReactTestUtils.renderIntoDocument(<Parent />);
|
||||
expect(divRef.current.textContent).toBe('remote:0, local:0');
|
||||
const container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
try {
|
||||
ReactDOM.render(<Parent />, container);
|
||||
expect(divRef.current.textContent).toBe('remote:0, local:0');
|
||||
|
||||
// Trigger setState() calls
|
||||
childInstance.updateState();
|
||||
expect(divRef.current.textContent).toBe('remote:1, local:1');
|
||||
// Trigger setState() calls
|
||||
childInstance.updateState();
|
||||
expect(divRef.current.textContent).toBe('remote:1, local:1');
|
||||
|
||||
// Trigger batched setState() calls
|
||||
ReactTestUtils.Simulate.click(divRef.current);
|
||||
expect(divRef.current.textContent).toBe('remote:2, local:2');
|
||||
// Trigger batched setState() calls
|
||||
divRef.current.click();
|
||||
expect(divRef.current.textContent).toBe('remote:2, local:2');
|
||||
} finally {
|
||||
document.body.removeChild(container);
|
||||
}
|
||||
});
|
||||
|
||||
it('should pass the return value from getSnapshotBeforeUpdate to componentDidUpdate', () => {
|
||||
|
||||
@@ -17,10 +17,50 @@ let ReactDOMServer;
|
||||
let ReactCurrentOwner;
|
||||
let ReactTestUtils;
|
||||
let PropTypes;
|
||||
let shallowEqual;
|
||||
let shallowCompare;
|
||||
|
||||
describe('ReactCompositeComponent', () => {
|
||||
const hasOwnProperty = Object.prototype.hasOwnProperty;
|
||||
|
||||
/**
|
||||
* Performs equality by iterating through keys on an object and returning false
|
||||
* when any key has values which are not strictly equal between the arguments.
|
||||
* Returns true when the values of all keys are strictly equal.
|
||||
*/
|
||||
function shallowEqual(objA: mixed, objB: mixed): boolean {
|
||||
if (Object.is(objA, objB)) {
|
||||
return true;
|
||||
}
|
||||
if (
|
||||
typeof objA !== 'object' ||
|
||||
objA === null ||
|
||||
typeof objB !== 'object' ||
|
||||
objB === null
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
const keysA = Object.keys(objA);
|
||||
const keysB = Object.keys(objB);
|
||||
if (keysA.length !== keysB.length) {
|
||||
return false;
|
||||
}
|
||||
for (let i = 0; i < keysA.length; i++) {
|
||||
if (
|
||||
!hasOwnProperty.call(objB, keysA[i]) ||
|
||||
!Object.is(objA[keysA[i]], objB[keysA[i]])
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function shallowCompare(instance, nextProps, nextState) {
|
||||
return (
|
||||
!shallowEqual(instance.props, nextProps) ||
|
||||
!shallowEqual(instance.state, nextState)
|
||||
);
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
React = require('react');
|
||||
@@ -30,14 +70,6 @@ describe('ReactCompositeComponent', () => {
|
||||
.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner;
|
||||
ReactTestUtils = require('react-dom/test-utils');
|
||||
PropTypes = require('prop-types');
|
||||
shallowEqual = require('fbjs/lib/shallowEqual');
|
||||
|
||||
shallowCompare = function(instance, nextProps, nextState) {
|
||||
return (
|
||||
!shallowEqual(instance.props, nextProps) ||
|
||||
!shallowEqual(instance.state, nextState)
|
||||
);
|
||||
};
|
||||
|
||||
MorphingComponent = class extends React.Component {
|
||||
state = {activated: false};
|
||||
@@ -140,23 +172,28 @@ describe('ReactCompositeComponent', () => {
|
||||
});
|
||||
|
||||
it('should react to state changes from callbacks', () => {
|
||||
const instance = ReactTestUtils.renderIntoDocument(<MorphingComponent />);
|
||||
let el = ReactDOM.findDOMNode(instance);
|
||||
expect(el.tagName).toBe('A');
|
||||
|
||||
ReactTestUtils.Simulate.click(el);
|
||||
el = ReactDOM.findDOMNode(instance);
|
||||
expect(el.tagName).toBe('B');
|
||||
const container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
try {
|
||||
const instance = ReactDOM.render(<MorphingComponent />, container);
|
||||
let el = ReactDOM.findDOMNode(instance);
|
||||
expect(el.tagName).toBe('A');
|
||||
el.click();
|
||||
el = ReactDOM.findDOMNode(instance);
|
||||
expect(el.tagName).toBe('B');
|
||||
} finally {
|
||||
document.body.removeChild(container);
|
||||
}
|
||||
});
|
||||
|
||||
it('should rewire refs when rendering to different child types', () => {
|
||||
const instance = ReactTestUtils.renderIntoDocument(<MorphingComponent />);
|
||||
|
||||
expect(ReactDOM.findDOMNode(instance.refs.x).tagName).toBe('A');
|
||||
expect(instance.refs.x.tagName).toBe('A');
|
||||
instance._toggleActivatedState();
|
||||
expect(ReactDOM.findDOMNode(instance.refs.x).tagName).toBe('B');
|
||||
expect(instance.refs.x.tagName).toBe('B');
|
||||
instance._toggleActivatedState();
|
||||
expect(ReactDOM.findDOMNode(instance.refs.x).tagName).toBe('A');
|
||||
expect(instance.refs.x.tagName).toBe('A');
|
||||
});
|
||||
|
||||
it('should not cache old DOM nodes when switching constructors', () => {
|
||||
|
||||
@@ -11,13 +11,11 @@
|
||||
|
||||
let React;
|
||||
let ReactDOM;
|
||||
let ReactTestUtils;
|
||||
|
||||
describe('ReactCompositeComponentNestedState-state', () => {
|
||||
beforeEach(() => {
|
||||
React = require('react');
|
||||
ReactDOM = require('react-dom');
|
||||
ReactTestUtils = require('react-dom/test-utils');
|
||||
});
|
||||
|
||||
it('should provide up to date values for props', () => {
|
||||
@@ -102,7 +100,7 @@ describe('ReactCompositeComponentNestedState-state', () => {
|
||||
void ReactDOM.render(<ParentComponent logger={logger} />, container);
|
||||
|
||||
// click "light green"
|
||||
ReactTestUtils.Simulate.click(container.childNodes[0].childNodes[3]);
|
||||
container.childNodes[0].childNodes[3].click();
|
||||
|
||||
expect(logger.mock.calls).toEqual([
|
||||
['parent-render', 'blue'],
|
||||
|
||||
135
packages/react-dom/src/__tests__/ReactDOM-test.js
vendored
135
packages/react-dom/src/__tests__/ReactDOM-test.js
vendored
@@ -45,23 +45,21 @@ describe('ReactDOM', () => {
|
||||
|
||||
it('allows a DOM element to be used with a string', () => {
|
||||
const element = React.createElement('div', {className: 'foo'});
|
||||
const instance = ReactTestUtils.renderIntoDocument(element);
|
||||
expect(ReactDOM.findDOMNode(instance).tagName).toBe('DIV');
|
||||
const node = ReactTestUtils.renderIntoDocument(element);
|
||||
expect(node.tagName).toBe('DIV');
|
||||
});
|
||||
|
||||
it('should allow children to be passed as an argument', () => {
|
||||
const argDiv = ReactTestUtils.renderIntoDocument(
|
||||
const argNode = ReactTestUtils.renderIntoDocument(
|
||||
React.createElement('div', null, 'child'),
|
||||
);
|
||||
const argNode = ReactDOM.findDOMNode(argDiv);
|
||||
expect(argNode.innerHTML).toBe('child');
|
||||
});
|
||||
|
||||
it('should overwrite props.children with children argument', () => {
|
||||
const conflictDiv = ReactTestUtils.renderIntoDocument(
|
||||
const conflictNode = ReactTestUtils.renderIntoDocument(
|
||||
React.createElement('div', {children: 'fakechild'}, 'child'),
|
||||
);
|
||||
const conflictNode = ReactDOM.findDOMNode(conflictDiv);
|
||||
expect(conflictNode.innerHTML).toBe('child');
|
||||
});
|
||||
|
||||
@@ -103,8 +101,7 @@ describe('ReactDOM', () => {
|
||||
<div key="theBird" className="bird" />,
|
||||
</div>,
|
||||
);
|
||||
const root = ReactDOM.findDOMNode(myDiv);
|
||||
const dog = root.childNodes[0];
|
||||
const dog = myDiv.childNodes[0];
|
||||
expect(dog.className).toBe('bigdog');
|
||||
});
|
||||
|
||||
@@ -242,34 +239,37 @@ describe('ReactDOM', () => {
|
||||
const log = [];
|
||||
const container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
ReactDOM.render(<A showTwo={false} />, container);
|
||||
input.focus();
|
||||
try {
|
||||
ReactDOM.render(<A showTwo={false} />, container);
|
||||
input.focus();
|
||||
|
||||
// When the second input is added, let's simulate losing focus, which is
|
||||
// something that could happen when manipulating DOM nodes (but is hard to
|
||||
// deterministically force without relying intensely on React DOM
|
||||
// implementation details)
|
||||
const div = container.firstChild;
|
||||
['appendChild', 'insertBefore'].forEach(name => {
|
||||
const mutator = div[name];
|
||||
div[name] = function() {
|
||||
if (input) {
|
||||
input.blur();
|
||||
expect(document.activeElement.tagName).toBe('BODY');
|
||||
log.push('input2 inserted');
|
||||
}
|
||||
return mutator.apply(this, arguments);
|
||||
};
|
||||
});
|
||||
// When the second input is added, let's simulate losing focus, which is
|
||||
// something that could happen when manipulating DOM nodes (but is hard to
|
||||
// deterministically force without relying intensely on React DOM
|
||||
// implementation details)
|
||||
const div = container.firstChild;
|
||||
['appendChild', 'insertBefore'].forEach(name => {
|
||||
const mutator = div[name];
|
||||
div[name] = function() {
|
||||
if (input) {
|
||||
input.blur();
|
||||
expect(document.activeElement.tagName).toBe('BODY');
|
||||
log.push('input2 inserted');
|
||||
}
|
||||
return mutator.apply(this, arguments);
|
||||
};
|
||||
});
|
||||
|
||||
expect(document.activeElement.id).toBe('one');
|
||||
ReactDOM.render(<A showTwo={true} />, container);
|
||||
// input2 gets added, which causes input to get blurred. Then
|
||||
// componentDidUpdate focuses input2 and that should make it down to here,
|
||||
// not get overwritten by focus restoration.
|
||||
expect(document.activeElement.id).toBe('two');
|
||||
expect(log).toEqual(['input2 inserted', 'input2 focused']);
|
||||
document.body.removeChild(container);
|
||||
expect(document.activeElement.id).toBe('one');
|
||||
ReactDOM.render(<A showTwo={true} />, container);
|
||||
// input2 gets added, which causes input to get blurred. Then
|
||||
// componentDidUpdate focuses input2 and that should make it down to here,
|
||||
// not get overwritten by focus restoration.
|
||||
expect(document.activeElement.id).toBe('two');
|
||||
expect(log).toEqual(['input2 inserted', 'input2 focused']);
|
||||
} finally {
|
||||
document.body.removeChild(container);
|
||||
}
|
||||
});
|
||||
|
||||
it('calls focus() on autoFocus elements after they have been mounted to the DOM', () => {
|
||||
@@ -308,20 +308,9 @@ describe('ReactDOM', () => {
|
||||
it("shouldn't fire duplicate event handler while handling other nested dispatch", () => {
|
||||
const actual = [];
|
||||
|
||||
function click(node) {
|
||||
const fakeNativeEvent = function() {};
|
||||
fakeNativeEvent.target = node;
|
||||
fakeNativeEvent.path = [node, container];
|
||||
ReactTestUtils.simulateNativeEventOnNode(
|
||||
'topClick',
|
||||
node,
|
||||
fakeNativeEvent,
|
||||
);
|
||||
}
|
||||
|
||||
class Wrapper extends React.Component {
|
||||
componentDidMount() {
|
||||
click(this.ref1);
|
||||
this.ref1.click();
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -330,7 +319,7 @@ describe('ReactDOM', () => {
|
||||
<div
|
||||
onClick={() => {
|
||||
actual.push('1st node clicked');
|
||||
click(this.ref2);
|
||||
this.ref2.click();
|
||||
}}
|
||||
ref={ref => (this.ref1 = ref)}
|
||||
/>
|
||||
@@ -346,13 +335,18 @@ describe('ReactDOM', () => {
|
||||
}
|
||||
|
||||
const container = document.createElement('div');
|
||||
ReactDOM.render(<Wrapper />, container);
|
||||
document.body.appendChild(container);
|
||||
try {
|
||||
ReactDOM.render(<Wrapper />, container);
|
||||
|
||||
const expected = [
|
||||
'1st node clicked',
|
||||
"2nd node clicked imperatively from 1st's handler",
|
||||
];
|
||||
expect(actual).toEqual(expected);
|
||||
const expected = [
|
||||
'1st node clicked',
|
||||
"2nd node clicked imperatively from 1st's handler",
|
||||
];
|
||||
expect(actual).toEqual(expected);
|
||||
} finally {
|
||||
document.body.removeChild(container);
|
||||
}
|
||||
});
|
||||
|
||||
it('should not crash with devtools installed', () => {
|
||||
@@ -439,4 +433,39 @@ describe('ReactDOM', () => {
|
||||
Object.defineProperty(global, 'document', documentDescriptor);
|
||||
}
|
||||
});
|
||||
|
||||
it('warns when requestAnimationFrame is not polyfilled in the browser', () => {
|
||||
const previousRAF = global.requestAnimationFrame;
|
||||
try {
|
||||
global.requestAnimationFrame = undefined;
|
||||
jest.resetModules();
|
||||
expect(() => require('react-dom')).toWarnDev(
|
||||
"This browser doesn't support requestAnimationFrame.",
|
||||
);
|
||||
} finally {
|
||||
global.requestAnimationFrame = previousRAF;
|
||||
}
|
||||
});
|
||||
|
||||
// We're just testing importing, not using it.
|
||||
// It is important because even isomorphic components may import it.
|
||||
it('can import findDOMNode in Node environment', () => {
|
||||
const previousRAF = global.requestAnimationFrame;
|
||||
const previousRIC = global.requestIdleCallback;
|
||||
const prevWindow = global.window;
|
||||
try {
|
||||
global.requestAnimationFrame = undefined;
|
||||
global.requestIdleCallback = undefined;
|
||||
// Simulate the Node environment:
|
||||
delete global.window;
|
||||
jest.resetModules();
|
||||
expect(() => {
|
||||
require('react-dom');
|
||||
}).not.toThrow();
|
||||
} finally {
|
||||
global.requestAnimationFrame = previousRAF;
|
||||
global.requestIdleCallback = previousRIC;
|
||||
global.window = prevWindow;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -738,28 +738,28 @@ describe('ReactDOMComponent', () => {
|
||||
node.removeAttribute.mockImplementation(nodeRemoveAttribute);
|
||||
|
||||
ReactDOM.render(<div id="" />, container);
|
||||
expect(node.setAttribute.mock.calls.length).toBe(0);
|
||||
expect(node.removeAttribute.mock.calls.length).toBe(0);
|
||||
expect(node.setAttribute).toHaveBeenCalledTimes(0);
|
||||
expect(node.removeAttribute).toHaveBeenCalledTimes(0);
|
||||
|
||||
ReactDOM.render(<div id="foo" />, container);
|
||||
expect(node.setAttribute.mock.calls.length).toBe(1);
|
||||
expect(node.removeAttribute.mock.calls.length).toBe(0);
|
||||
expect(node.setAttribute).toHaveBeenCalledTimes(1);
|
||||
expect(node.removeAttribute).toHaveBeenCalledTimes(0);
|
||||
|
||||
ReactDOM.render(<div id="foo" />, container);
|
||||
expect(node.setAttribute.mock.calls.length).toBe(1);
|
||||
expect(node.removeAttribute.mock.calls.length).toBe(0);
|
||||
expect(node.setAttribute).toHaveBeenCalledTimes(1);
|
||||
expect(node.removeAttribute).toHaveBeenCalledTimes(0);
|
||||
|
||||
ReactDOM.render(<div />, container);
|
||||
expect(node.setAttribute.mock.calls.length).toBe(1);
|
||||
expect(node.removeAttribute.mock.calls.length).toBe(1);
|
||||
expect(node.setAttribute).toHaveBeenCalledTimes(1);
|
||||
expect(node.removeAttribute).toHaveBeenCalledTimes(1);
|
||||
|
||||
ReactDOM.render(<div id="" />, container);
|
||||
expect(node.setAttribute.mock.calls.length).toBe(2);
|
||||
expect(node.removeAttribute.mock.calls.length).toBe(1);
|
||||
expect(node.setAttribute).toHaveBeenCalledTimes(2);
|
||||
expect(node.removeAttribute).toHaveBeenCalledTimes(1);
|
||||
|
||||
ReactDOM.render(<div />, container);
|
||||
expect(node.setAttribute.mock.calls.length).toBe(2);
|
||||
expect(node.removeAttribute.mock.calls.length).toBe(2);
|
||||
expect(node.setAttribute).toHaveBeenCalledTimes(2);
|
||||
expect(node.removeAttribute).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('should not incur unnecessary DOM mutations for string properties', () => {
|
||||
@@ -768,7 +768,7 @@ describe('ReactDOMComponent', () => {
|
||||
|
||||
const node = container.firstChild;
|
||||
|
||||
const nodeValueSetter = jest.genMockFn();
|
||||
const nodeValueSetter = jest.fn();
|
||||
|
||||
const oldSetAttribute = node.setAttribute.bind(node);
|
||||
node.setAttribute = function(key, value) {
|
||||
@@ -777,22 +777,22 @@ describe('ReactDOMComponent', () => {
|
||||
};
|
||||
|
||||
ReactDOM.render(<div value="foo" />, container);
|
||||
expect(nodeValueSetter.mock.calls.length).toBe(1);
|
||||
expect(nodeValueSetter).toHaveBeenCalledTimes(1);
|
||||
|
||||
ReactDOM.render(<div value="foo" />, container);
|
||||
expect(nodeValueSetter.mock.calls.length).toBe(1);
|
||||
expect(nodeValueSetter).toHaveBeenCalledTimes(1);
|
||||
|
||||
ReactDOM.render(<div />, container);
|
||||
expect(nodeValueSetter.mock.calls.length).toBe(1);
|
||||
expect(nodeValueSetter).toHaveBeenCalledTimes(1);
|
||||
|
||||
ReactDOM.render(<div value={null} />, container);
|
||||
expect(nodeValueSetter.mock.calls.length).toBe(1);
|
||||
expect(nodeValueSetter).toHaveBeenCalledTimes(1);
|
||||
|
||||
ReactDOM.render(<div value="" />, container);
|
||||
expect(nodeValueSetter.mock.calls.length).toBe(2);
|
||||
expect(nodeValueSetter).toHaveBeenCalledTimes(2);
|
||||
|
||||
ReactDOM.render(<div />, container);
|
||||
expect(nodeValueSetter.mock.calls.length).toBe(2);
|
||||
expect(nodeValueSetter).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('should not incur unnecessary DOM mutations for boolean properties', () => {
|
||||
@@ -812,16 +812,16 @@ describe('ReactDOMComponent', () => {
|
||||
});
|
||||
|
||||
ReactDOM.render(<div checked={true} />, container);
|
||||
expect(nodeValueSetter.mock.calls.length).toBe(0);
|
||||
expect(nodeValueSetter).toHaveBeenCalledTimes(0);
|
||||
|
||||
ReactDOM.render(<div />, container);
|
||||
expect(nodeValueSetter.mock.calls.length).toBe(1);
|
||||
expect(nodeValueSetter).toHaveBeenCalledTimes(1);
|
||||
|
||||
ReactDOM.render(<div checked={false} />, container);
|
||||
expect(nodeValueSetter.mock.calls.length).toBe(2);
|
||||
expect(nodeValueSetter).toHaveBeenCalledTimes(2);
|
||||
|
||||
ReactDOM.render(<div checked={true} />, container);
|
||||
expect(nodeValueSetter.mock.calls.length).toBe(3);
|
||||
expect(nodeValueSetter).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
it('should ignore attribute whitelist for elements with the "is" attribute', () => {
|
||||
@@ -850,9 +850,9 @@ describe('ReactDOMComponent', () => {
|
||||
ReactDOM.render(<div dir={null} />, container);
|
||||
ReactDOM.render(<div dir={undefined} />, container);
|
||||
ReactDOM.render(<div />, container);
|
||||
expect(setter.mock.calls.length).toBe(0);
|
||||
expect(setter).toHaveBeenCalledTimes(0);
|
||||
ReactDOM.render(<div dir="ltr" />, container);
|
||||
expect(setter.mock.calls.length).toBe(1);
|
||||
expect(setter).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('handles multiple child updates without interference', () => {
|
||||
@@ -984,7 +984,7 @@ describe('ReactDOMComponent', () => {
|
||||
container.getElementsByTagName('source')[0].dispatchEvent(errorEvent);
|
||||
|
||||
if (__DEV__) {
|
||||
expect(console.log.calls.count()).toBe(1);
|
||||
expect(console.log).toHaveBeenCalledTimes(1);
|
||||
expect(console.log.calls.argsFor(0)[0]).toContain('onError called');
|
||||
}
|
||||
});
|
||||
@@ -1315,7 +1315,7 @@ describe('ReactDOMComponent', () => {
|
||||
container.getElementsByTagName('image')[0].dispatchEvent(loadEvent);
|
||||
|
||||
if (__DEV__) {
|
||||
expect(console.log.calls.count()).toBe(2);
|
||||
expect(console.log).toHaveBeenCalledTimes(2);
|
||||
expect(console.log.calls.argsFor(0)[0]).toContain('onError called');
|
||||
expect(console.log.calls.argsFor(1)[0]).toContain('onLoad called');
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ describe('ReactDOMEventListener', () => {
|
||||
childNode.dispatchEvent(nativeEvent);
|
||||
|
||||
expect(mouseOut).toBeCalled();
|
||||
expect(mouseOut.mock.calls.length).toBe(2);
|
||||
expect(mouseOut).toHaveBeenCalledTimes(2);
|
||||
expect(mouseOut.mock.calls[0][0]).toEqual(childNode);
|
||||
expect(mouseOut.mock.calls[1][0]).toEqual(parentNode);
|
||||
|
||||
@@ -97,7 +97,7 @@ describe('ReactDOMEventListener', () => {
|
||||
childNode.dispatchEvent(nativeEvent);
|
||||
|
||||
expect(mouseOut).toBeCalled();
|
||||
expect(mouseOut.mock.calls.length).toBe(3);
|
||||
expect(mouseOut).toHaveBeenCalledTimes(3);
|
||||
expect(mouseOut.mock.calls[0][0]).toEqual(childNode);
|
||||
expect(mouseOut.mock.calls[1][0]).toEqual(parentNode);
|
||||
expect(mouseOut.mock.calls[2][0]).toEqual(grandParentNode);
|
||||
@@ -169,7 +169,7 @@ describe('ReactDOMEventListener', () => {
|
||||
childNode.dispatchEvent(nativeEvent);
|
||||
|
||||
// Child and parent should both call from event handlers.
|
||||
expect(mock.mock.calls.length).toBe(2);
|
||||
expect(mock).toHaveBeenCalledTimes(2);
|
||||
// The first call schedules a render of '1' into the 'Child'.
|
||||
// However, we're batching so it isn't flushed yet.
|
||||
expect(mock.mock.calls[0][0]).toBe('Child');
|
||||
@@ -213,8 +213,169 @@ describe('ReactDOMEventListener', () => {
|
||||
instance.getInner().dispatchEvent(nativeEvent);
|
||||
|
||||
expect(mouseOut).toBeCalled();
|
||||
expect(mouseOut.mock.calls.length).toBe(1);
|
||||
expect(mouseOut).toHaveBeenCalledTimes(1);
|
||||
expect(mouseOut.mock.calls[0][0]).toEqual(instance.getInner());
|
||||
document.body.removeChild(container);
|
||||
});
|
||||
|
||||
// Regression test for https://github.com/facebook/react/pull/12877
|
||||
it('should not fire form events twice', () => {
|
||||
const container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
|
||||
const formRef = React.createRef();
|
||||
const inputRef = React.createRef();
|
||||
|
||||
const handleInvalid = jest.fn();
|
||||
const handleReset = jest.fn();
|
||||
const handleSubmit = jest.fn();
|
||||
ReactDOM.render(
|
||||
<form ref={formRef} onReset={handleReset} onSubmit={handleSubmit}>
|
||||
<input ref={inputRef} onInvalid={handleInvalid} />
|
||||
</form>,
|
||||
container,
|
||||
);
|
||||
|
||||
inputRef.current.dispatchEvent(
|
||||
new Event('invalid', {
|
||||
// https://developer.mozilla.org/en-US/docs/Web/Events/invalid
|
||||
bubbles: false,
|
||||
}),
|
||||
);
|
||||
expect(handleInvalid).toHaveBeenCalledTimes(1);
|
||||
|
||||
formRef.current.dispatchEvent(
|
||||
new Event('reset', {
|
||||
// https://developer.mozilla.org/en-US/docs/Web/Events/reset
|
||||
bubbles: true,
|
||||
}),
|
||||
);
|
||||
expect(handleReset).toHaveBeenCalledTimes(1);
|
||||
|
||||
formRef.current.dispatchEvent(
|
||||
new Event('submit', {
|
||||
// https://developer.mozilla.org/en-US/docs/Web/Events/submit
|
||||
bubbles: true,
|
||||
}),
|
||||
);
|
||||
expect(handleSubmit).toHaveBeenCalledTimes(1);
|
||||
|
||||
formRef.current.dispatchEvent(
|
||||
new Event('submit', {
|
||||
// Might happen on older browsers.
|
||||
bubbles: true,
|
||||
}),
|
||||
);
|
||||
expect(handleSubmit).toHaveBeenCalledTimes(2); // It already fired in this test.
|
||||
|
||||
document.body.removeChild(container);
|
||||
});
|
||||
|
||||
it('should dispatch loadstart only for media elements', () => {
|
||||
const container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
|
||||
const imgRef = React.createRef();
|
||||
const videoRef = React.createRef();
|
||||
|
||||
const handleImgLoadStart = jest.fn();
|
||||
const handleVideoLoadStart = jest.fn();
|
||||
ReactDOM.render(
|
||||
<div>
|
||||
<img ref={imgRef} onLoadStart={handleImgLoadStart} />
|
||||
<video ref={videoRef} onLoadStart={handleVideoLoadStart} />
|
||||
</div>,
|
||||
container,
|
||||
);
|
||||
|
||||
// Note for debugging: loadstart currently doesn't fire in Chrome.
|
||||
// https://bugs.chromium.org/p/chromium/issues/detail?id=458851
|
||||
imgRef.current.dispatchEvent(
|
||||
new ProgressEvent('loadstart', {
|
||||
bubbles: false,
|
||||
}),
|
||||
);
|
||||
// Historically, we happened to not support onLoadStart
|
||||
// on <img>, and this test documents that lack of support.
|
||||
// If we decide to support it in the future, we should change
|
||||
// this line to expect 1 call. Note that fixing this would
|
||||
// be simple but would require attaching a handler to each
|
||||
// <img>. So far nobody asked us for it.
|
||||
expect(handleImgLoadStart).toHaveBeenCalledTimes(0);
|
||||
|
||||
videoRef.current.dispatchEvent(
|
||||
new ProgressEvent('loadstart', {
|
||||
bubbles: false,
|
||||
}),
|
||||
);
|
||||
expect(handleVideoLoadStart).toHaveBeenCalledTimes(1);
|
||||
|
||||
document.body.removeChild(container);
|
||||
});
|
||||
|
||||
it('should not attempt to listen to unnecessary events on the top level', () => {
|
||||
const container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
|
||||
const videoRef = React.createRef();
|
||||
const handleVideoPlay = jest.fn(); // We'll test this one.
|
||||
const mediaEvents = {
|
||||
onAbort() {},
|
||||
onCanPlay() {},
|
||||
onCanPlayThrough() {},
|
||||
onDurationChange() {},
|
||||
onEmptied() {},
|
||||
onEncrypted() {},
|
||||
onEnded() {},
|
||||
onError() {},
|
||||
onLoadedData() {},
|
||||
onLoadedMetadata() {},
|
||||
onLoadStart() {},
|
||||
onPause() {},
|
||||
onPlay() {},
|
||||
onPlaying() {},
|
||||
onProgress() {},
|
||||
onRateChange() {},
|
||||
onSeeked() {},
|
||||
onSeeking() {},
|
||||
onStalled() {},
|
||||
onSuspend() {},
|
||||
onTimeUpdate() {},
|
||||
onVolumeChange() {},
|
||||
onWaiting() {},
|
||||
};
|
||||
|
||||
const originalAddEventListener = document.addEventListener;
|
||||
document.addEventListener = function(type) {
|
||||
throw new Error(
|
||||
`Did not expect to add a top-level listener for the "${type}" event.`,
|
||||
);
|
||||
};
|
||||
|
||||
try {
|
||||
// We expect that mounting this tree will
|
||||
// *not* attach handlers for any top-level events.
|
||||
ReactDOM.render(
|
||||
<div>
|
||||
<video ref={videoRef} {...mediaEvents} onPlay={handleVideoPlay} />
|
||||
<audio {...mediaEvents}>
|
||||
<source {...mediaEvents} />
|
||||
</audio>
|
||||
<form onReset={() => {}} onSubmit={() => {}} />
|
||||
</div>,
|
||||
container,
|
||||
);
|
||||
|
||||
// Also verify dispatching one of them works
|
||||
videoRef.current.dispatchEvent(
|
||||
new Event('play', {
|
||||
bubbles: false,
|
||||
}),
|
||||
);
|
||||
expect(handleVideoPlay).toHaveBeenCalledTimes(1);
|
||||
} finally {
|
||||
document.addEventListener = originalAddEventListener;
|
||||
document.body.removeChild(container);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
|
||||
const React = require('react');
|
||||
const ReactDOM = require('react-dom');
|
||||
const ReactTestUtils = require('react-dom/test-utils');
|
||||
const PropTypes = require('prop-types');
|
||||
|
||||
describe('ReactDOMFiber', () => {
|
||||
@@ -171,6 +170,31 @@ describe('ReactDOMFiber', () => {
|
||||
expect(firstNode.tagName).toBe('DIV');
|
||||
});
|
||||
|
||||
it('renders an empty fragment', () => {
|
||||
const Div = () => <div />;
|
||||
const EmptyFragment = () => <React.Fragment />;
|
||||
const NonEmptyFragment = () => (
|
||||
<React.Fragment>
|
||||
<Div />
|
||||
</React.Fragment>
|
||||
);
|
||||
|
||||
ReactDOM.render(<EmptyFragment />, container);
|
||||
expect(container.firstChild).toBe(null);
|
||||
|
||||
ReactDOM.render(<NonEmptyFragment />, container);
|
||||
expect(container.firstChild.tagName).toBe('DIV');
|
||||
|
||||
ReactDOM.render(<EmptyFragment />, container);
|
||||
expect(container.firstChild).toBe(null);
|
||||
|
||||
ReactDOM.render(<Div />, container);
|
||||
expect(container.firstChild.tagName).toBe('DIV');
|
||||
|
||||
ReactDOM.render(<EmptyFragment />, container);
|
||||
expect(container.firstChild).toBe(null);
|
||||
});
|
||||
|
||||
let svgEls, htmlEls, mathEls;
|
||||
const expectSVG = {ref: el => svgEls.push(el)};
|
||||
const expectHTML = {ref: el => htmlEls.push(el)};
|
||||
@@ -818,38 +842,39 @@ describe('ReactDOMFiber', () => {
|
||||
|
||||
it('should bubble events from the portal to the parent', () => {
|
||||
const portalContainer = document.createElement('div');
|
||||
document.body.appendChild(portalContainer);
|
||||
try {
|
||||
const ops = [];
|
||||
let portal = null;
|
||||
|
||||
const ops = [];
|
||||
let portal = null;
|
||||
ReactDOM.render(
|
||||
<div onClick={() => ops.push('parent clicked')}>
|
||||
{ReactDOM.createPortal(
|
||||
<div
|
||||
onClick={() => ops.push('portal clicked')}
|
||||
ref={n => (portal = n)}>
|
||||
portal
|
||||
</div>,
|
||||
portalContainer,
|
||||
)}
|
||||
</div>,
|
||||
container,
|
||||
);
|
||||
|
||||
ReactDOM.render(
|
||||
<div onClick={() => ops.push('parent clicked')}>
|
||||
{ReactDOM.createPortal(
|
||||
<div
|
||||
onClick={() => ops.push('portal clicked')}
|
||||
ref={n => (portal = n)}>
|
||||
portal
|
||||
</div>,
|
||||
portalContainer,
|
||||
)}
|
||||
</div>,
|
||||
container,
|
||||
);
|
||||
expect(portal.tagName).toBe('DIV');
|
||||
|
||||
expect(portal.tagName).toBe('DIV');
|
||||
portal.click();
|
||||
|
||||
const fakeNativeEvent = {};
|
||||
ReactTestUtils.simulateNativeEventOnNode(
|
||||
'topClick',
|
||||
portal,
|
||||
fakeNativeEvent,
|
||||
);
|
||||
|
||||
expect(ops).toEqual(['portal clicked', 'parent clicked']);
|
||||
expect(ops).toEqual(['portal clicked', 'parent clicked']);
|
||||
} finally {
|
||||
document.body.removeChild(portalContainer);
|
||||
}
|
||||
});
|
||||
|
||||
it('should not onMouseLeave when staying in the portal', () => {
|
||||
const portalContainer = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
document.body.appendChild(portalContainer);
|
||||
|
||||
let ops = [];
|
||||
let firstTarget = null;
|
||||
@@ -858,58 +883,69 @@ describe('ReactDOMFiber', () => {
|
||||
|
||||
function simulateMouseMove(from, to) {
|
||||
if (from) {
|
||||
ReactTestUtils.simulateNativeEventOnNode('topMouseOut', from, {
|
||||
target: from,
|
||||
relatedTarget: to,
|
||||
});
|
||||
from.dispatchEvent(
|
||||
new MouseEvent('mouseout', {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
relatedTarget: to,
|
||||
}),
|
||||
);
|
||||
}
|
||||
if (to) {
|
||||
ReactTestUtils.simulateNativeEventOnNode('topMouseOver', to, {
|
||||
target: to,
|
||||
relatedTarget: from,
|
||||
});
|
||||
to.dispatchEvent(
|
||||
new MouseEvent('mouseover', {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
relatedTarget: from,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ReactDOM.render(
|
||||
<div>
|
||||
<div
|
||||
onMouseEnter={() => ops.push('enter parent')}
|
||||
onMouseLeave={() => ops.push('leave parent')}>
|
||||
<div ref={n => (firstTarget = n)} />
|
||||
{ReactDOM.createPortal(
|
||||
<div
|
||||
onMouseEnter={() => ops.push('enter portal')}
|
||||
onMouseLeave={() => ops.push('leave portal')}
|
||||
ref={n => (secondTarget = n)}>
|
||||
portal
|
||||
</div>,
|
||||
portalContainer,
|
||||
)}
|
||||
</div>
|
||||
<div ref={n => (thirdTarget = n)} />
|
||||
</div>,
|
||||
container,
|
||||
);
|
||||
try {
|
||||
ReactDOM.render(
|
||||
<div>
|
||||
<div
|
||||
onMouseEnter={() => ops.push('enter parent')}
|
||||
onMouseLeave={() => ops.push('leave parent')}>
|
||||
<div ref={n => (firstTarget = n)} />
|
||||
{ReactDOM.createPortal(
|
||||
<div
|
||||
onMouseEnter={() => ops.push('enter portal')}
|
||||
onMouseLeave={() => ops.push('leave portal')}
|
||||
ref={n => (secondTarget = n)}>
|
||||
portal
|
||||
</div>,
|
||||
portalContainer,
|
||||
)}
|
||||
</div>
|
||||
<div ref={n => (thirdTarget = n)} />
|
||||
</div>,
|
||||
container,
|
||||
);
|
||||
|
||||
simulateMouseMove(null, firstTarget);
|
||||
expect(ops).toEqual(['enter parent']);
|
||||
simulateMouseMove(null, firstTarget);
|
||||
expect(ops).toEqual(['enter parent']);
|
||||
|
||||
ops = [];
|
||||
ops = [];
|
||||
|
||||
simulateMouseMove(firstTarget, secondTarget);
|
||||
expect(ops).toEqual([
|
||||
// Parent did not invoke leave because we're still inside the portal.
|
||||
'enter portal',
|
||||
]);
|
||||
simulateMouseMove(firstTarget, secondTarget);
|
||||
expect(ops).toEqual([
|
||||
// Parent did not invoke leave because we're still inside the portal.
|
||||
'enter portal',
|
||||
]);
|
||||
|
||||
ops = [];
|
||||
ops = [];
|
||||
|
||||
simulateMouseMove(secondTarget, thirdTarget);
|
||||
expect(ops).toEqual([
|
||||
'leave portal',
|
||||
'leave parent', // Only when we leave the portal does onMouseLeave fire.
|
||||
]);
|
||||
simulateMouseMove(secondTarget, thirdTarget);
|
||||
expect(ops).toEqual([
|
||||
'leave portal',
|
||||
'leave parent', // Only when we leave the portal does onMouseLeave fire.
|
||||
]);
|
||||
} finally {
|
||||
document.body.removeChild(container);
|
||||
document.body.removeChild(portalContainer);
|
||||
}
|
||||
});
|
||||
|
||||
it('should throw on bad createPortal argument', () => {
|
||||
@@ -949,86 +985,82 @@ describe('ReactDOMFiber', () => {
|
||||
});
|
||||
|
||||
it('should not update event handlers until commit', () => {
|
||||
let ops = [];
|
||||
const handlerA = () => ops.push('A');
|
||||
const handlerB = () => ops.push('B');
|
||||
document.body.appendChild(container);
|
||||
try {
|
||||
let ops = [];
|
||||
const handlerA = () => ops.push('A');
|
||||
const handlerB = () => ops.push('B');
|
||||
|
||||
class Example extends React.Component {
|
||||
state = {flip: false, count: 0};
|
||||
flip() {
|
||||
this.setState({flip: true, count: this.state.count + 1});
|
||||
class Example extends React.Component {
|
||||
state = {flip: false, count: 0};
|
||||
flip() {
|
||||
this.setState({flip: true, count: this.state.count + 1});
|
||||
}
|
||||
tick() {
|
||||
this.setState({count: this.state.count + 1});
|
||||
}
|
||||
render() {
|
||||
const useB = !this.props.forceA && this.state.flip;
|
||||
return <div onClick={useB ? handlerB : handlerA} />;
|
||||
}
|
||||
}
|
||||
tick() {
|
||||
this.setState({count: this.state.count + 1});
|
||||
}
|
||||
render() {
|
||||
const useB = !this.props.forceA && this.state.flip;
|
||||
return <div onClick={useB ? handlerB : handlerA} />;
|
||||
}
|
||||
}
|
||||
|
||||
class Click extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
click(node);
|
||||
class Click extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
node.click();
|
||||
}
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
let inst;
|
||||
ReactDOM.render([<Example key="a" ref={n => (inst = n)} />], container);
|
||||
const node = container.firstChild;
|
||||
expect(node.tagName).toEqual('DIV');
|
||||
let inst;
|
||||
ReactDOM.render([<Example key="a" ref={n => (inst = n)} />], container);
|
||||
const node = container.firstChild;
|
||||
expect(node.tagName).toEqual('DIV');
|
||||
|
||||
function click(target) {
|
||||
const fakeNativeEvent = {};
|
||||
ReactTestUtils.simulateNativeEventOnNode(
|
||||
'topClick',
|
||||
target,
|
||||
fakeNativeEvent,
|
||||
node.click();
|
||||
|
||||
expect(ops).toEqual(['A']);
|
||||
ops = [];
|
||||
|
||||
// Render with the other event handler.
|
||||
inst.flip();
|
||||
|
||||
node.click();
|
||||
|
||||
expect(ops).toEqual(['B']);
|
||||
ops = [];
|
||||
|
||||
// Rerender without changing any props.
|
||||
inst.tick();
|
||||
|
||||
node.click();
|
||||
|
||||
expect(ops).toEqual(['B']);
|
||||
ops = [];
|
||||
|
||||
// Render a flip back to the A handler. The second component invokes the
|
||||
// click handler during render to simulate a click during an aborted
|
||||
// render. I use this hack because at current time we don't have a way to
|
||||
// test aborted ReactDOM renders.
|
||||
ReactDOM.render(
|
||||
[<Example key="a" forceA={true} />, <Click key="b" />],
|
||||
container,
|
||||
);
|
||||
|
||||
// Because the new click handler has not yet committed, we should still
|
||||
// invoke B.
|
||||
expect(ops).toEqual(['B']);
|
||||
ops = [];
|
||||
|
||||
// Any click that happens after commit, should invoke A.
|
||||
node.click();
|
||||
expect(ops).toEqual(['A']);
|
||||
} finally {
|
||||
document.body.removeChild(container);
|
||||
}
|
||||
|
||||
click(node);
|
||||
|
||||
expect(ops).toEqual(['A']);
|
||||
ops = [];
|
||||
|
||||
// Render with the other event handler.
|
||||
inst.flip();
|
||||
|
||||
click(node);
|
||||
|
||||
expect(ops).toEqual(['B']);
|
||||
ops = [];
|
||||
|
||||
// Rerender without changing any props.
|
||||
inst.tick();
|
||||
|
||||
click(node);
|
||||
|
||||
expect(ops).toEqual(['B']);
|
||||
ops = [];
|
||||
|
||||
// Render a flip back to the A handler. The second component invokes the
|
||||
// click handler during render to simulate a click during an aborted
|
||||
// render. I use this hack because at current time we don't have a way to
|
||||
// test aborted ReactDOM renders.
|
||||
ReactDOM.render(
|
||||
[<Example key="a" forceA={true} />, <Click key="b" />],
|
||||
container,
|
||||
);
|
||||
|
||||
// Because the new click handler has not yet committed, we should still
|
||||
// invoke B.
|
||||
expect(ops).toEqual(['B']);
|
||||
ops = [];
|
||||
|
||||
// Any click that happens after commit, should invoke A.
|
||||
click(node);
|
||||
expect(ops).toEqual(['A']);
|
||||
});
|
||||
|
||||
it('should not crash encountering low-priority tree', () => {
|
||||
|
||||
@@ -20,6 +20,31 @@ describe('ReactDOMFiberAsync', () => {
|
||||
let container;
|
||||
|
||||
beforeEach(() => {
|
||||
// TODO pull this into helper method, reduce repetition.
|
||||
// mock the browser APIs which are used in react-scheduler:
|
||||
// - requestAnimationFrame should pass the DOMHighResTimeStamp argument
|
||||
// - calling 'window.postMessage' should actually fire postmessage handlers
|
||||
global.requestAnimationFrame = function(cb) {
|
||||
return setTimeout(() => {
|
||||
cb(Date.now());
|
||||
});
|
||||
};
|
||||
const originalAddEventListener = global.addEventListener;
|
||||
let postMessageCallback;
|
||||
global.addEventListener = function(eventName, callback, useCapture) {
|
||||
if (eventName === 'message') {
|
||||
postMessageCallback = callback;
|
||||
} else {
|
||||
originalAddEventListener(eventName, callback, useCapture);
|
||||
}
|
||||
};
|
||||
global.postMessage = function(messageKey, targetOrigin) {
|
||||
const postMessageEvent = {source: window, data: messageKey};
|
||||
if (postMessageCallback) {
|
||||
postMessageCallback(postMessageEvent);
|
||||
}
|
||||
};
|
||||
jest.resetModules();
|
||||
container = document.createElement('div');
|
||||
ReactDOM = require('react-dom');
|
||||
});
|
||||
|
||||
@@ -11,24 +11,22 @@
|
||||
|
||||
describe('ReactDOMIframe', () => {
|
||||
let React;
|
||||
let ReactDOM;
|
||||
let ReactTestUtils;
|
||||
|
||||
beforeEach(() => {
|
||||
React = require('react');
|
||||
ReactDOM = require('react-dom');
|
||||
ReactTestUtils = require('react-dom/test-utils');
|
||||
});
|
||||
|
||||
it('should trigger load events', () => {
|
||||
const onLoadSpy = jasmine.createSpy();
|
||||
const onLoadSpy = jest.fn();
|
||||
let iframe = React.createElement('iframe', {onLoad: onLoadSpy});
|
||||
iframe = ReactTestUtils.renderIntoDocument(iframe);
|
||||
|
||||
const loadEvent = document.createEvent('Event');
|
||||
loadEvent.initEvent('load', false, false);
|
||||
|
||||
ReactDOM.findDOMNode(iframe).dispatchEvent(loadEvent);
|
||||
iframe.dispatchEvent(loadEvent);
|
||||
|
||||
expect(onLoadSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
const emptyFunction = require('fbjs/lib/emptyFunction');
|
||||
function emptyFunction() {}
|
||||
|
||||
describe('ReactDOMInput', () => {
|
||||
let React;
|
||||
@@ -38,27 +38,27 @@ describe('ReactDOMInput', () => {
|
||||
|
||||
it('should properly control a value even if no event listener exists', () => {
|
||||
const container = document.createElement('div');
|
||||
let stub;
|
||||
let node;
|
||||
|
||||
expect(() => {
|
||||
stub = ReactDOM.render(<input type="text" value="lion" />, container);
|
||||
node = ReactDOM.render(<input type="text" value="lion" />, container);
|
||||
}).toWarnDev(
|
||||
'Failed prop type: You provided a `value` prop to a form field without an `onChange` handler.',
|
||||
);
|
||||
|
||||
document.body.appendChild(container);
|
||||
|
||||
const node = ReactDOM.findDOMNode(stub);
|
||||
try {
|
||||
setUntrackedValue.call(node, 'giraffe');
|
||||
|
||||
setUntrackedValue.call(node, 'giraffe');
|
||||
// This must use the native event dispatching. If we simulate, we will
|
||||
// bypass the lazy event attachment system so we won't actually test this.
|
||||
dispatchEventOnNode(node, 'change');
|
||||
|
||||
// This must use the native event dispatching. If we simulate, we will
|
||||
// bypass the lazy event attachment system so we won't actually test this.
|
||||
dispatchEventOnNode(node, 'change');
|
||||
|
||||
expect(node.value).toBe('lion');
|
||||
|
||||
document.body.removeChild(container);
|
||||
expect(node.value).toBe('lion');
|
||||
} finally {
|
||||
document.body.removeChild(container);
|
||||
}
|
||||
});
|
||||
|
||||
it('should control a value in reentrant events', () => {
|
||||
@@ -169,26 +169,24 @@ describe('ReactDOMInput', () => {
|
||||
|
||||
describe('switching text inputs between numeric and string numbers', () => {
|
||||
it('does change the number 2 to "2.0" with no change handler', () => {
|
||||
let stub = <input type="text" value={2} onChange={jest.fn()} />;
|
||||
stub = ReactTestUtils.renderIntoDocument(stub);
|
||||
const node = ReactDOM.findDOMNode(stub);
|
||||
const stub = <input type="text" value={2} onChange={jest.fn()} />;
|
||||
const node = ReactTestUtils.renderIntoDocument(stub);
|
||||
|
||||
node.value = '2.0';
|
||||
|
||||
ReactTestUtils.Simulate.change(stub);
|
||||
ReactTestUtils.Simulate.change(node);
|
||||
|
||||
expect(node.getAttribute('value')).toBe('2');
|
||||
expect(node.value).toBe('2');
|
||||
});
|
||||
|
||||
it('does change the string "2" to "2.0" with no change handler', () => {
|
||||
let stub = <input type="text" value={'2'} onChange={jest.fn()} />;
|
||||
stub = ReactTestUtils.renderIntoDocument(stub);
|
||||
const node = ReactDOM.findDOMNode(stub);
|
||||
const stub = <input type="text" value={'2'} onChange={jest.fn()} />;
|
||||
const node = ReactTestUtils.renderIntoDocument(stub);
|
||||
|
||||
node.value = '2.0';
|
||||
|
||||
ReactTestUtils.Simulate.change(stub);
|
||||
ReactTestUtils.Simulate.change(node);
|
||||
|
||||
expect(node.getAttribute('value')).toBe('2');
|
||||
expect(node.value).toBe('2');
|
||||
@@ -315,8 +313,7 @@ describe('ReactDOMInput', () => {
|
||||
|
||||
it('should display `defaultValue` of number 0', () => {
|
||||
let stub = <input type="text" defaultValue={0} />;
|
||||
stub = ReactTestUtils.renderIntoDocument(stub);
|
||||
const node = ReactDOM.findDOMNode(stub);
|
||||
const node = ReactTestUtils.renderIntoDocument(stub);
|
||||
|
||||
expect(node.getAttribute('value')).toBe('0');
|
||||
expect(node.value).toBe('0');
|
||||
@@ -348,16 +345,14 @@ describe('ReactDOMInput', () => {
|
||||
|
||||
it('should display "true" for `defaultValue` of `true`', () => {
|
||||
let stub = <input type="text" defaultValue={true} />;
|
||||
stub = ReactTestUtils.renderIntoDocument(stub);
|
||||
const node = ReactDOM.findDOMNode(stub);
|
||||
const node = ReactTestUtils.renderIntoDocument(stub);
|
||||
|
||||
expect(node.value).toBe('true');
|
||||
});
|
||||
|
||||
it('should display "false" for `defaultValue` of `false`', () => {
|
||||
let stub = <input type="text" defaultValue={false} />;
|
||||
stub = ReactTestUtils.renderIntoDocument(stub);
|
||||
const node = ReactDOM.findDOMNode(stub);
|
||||
const node = ReactTestUtils.renderIntoDocument(stub);
|
||||
|
||||
expect(node.value).toBe('false');
|
||||
});
|
||||
@@ -467,17 +462,15 @@ describe('ReactDOMInput', () => {
|
||||
},
|
||||
};
|
||||
|
||||
let stub = <input type="text" defaultValue={objToString} />;
|
||||
stub = ReactTestUtils.renderIntoDocument(stub);
|
||||
const node = ReactDOM.findDOMNode(stub);
|
||||
const stub = <input type="text" defaultValue={objToString} />;
|
||||
const node = ReactTestUtils.renderIntoDocument(stub);
|
||||
|
||||
expect(node.value).toBe('foobar');
|
||||
});
|
||||
|
||||
it('should display `value` of number 0', () => {
|
||||
let stub = <input type="text" value={0} />;
|
||||
stub = ReactTestUtils.renderIntoDocument(stub);
|
||||
const node = ReactDOM.findDOMNode(stub);
|
||||
const stub = <input type="text" value={0} />;
|
||||
const node = ReactTestUtils.renderIntoDocument(stub);
|
||||
|
||||
expect(node.value).toBe('0');
|
||||
});
|
||||
@@ -535,7 +528,7 @@ describe('ReactDOMInput', () => {
|
||||
|
||||
const node = container.firstChild;
|
||||
let nodeValue = 'a';
|
||||
const nodeValueSetter = jest.genMockFn();
|
||||
const nodeValueSetter = jest.fn();
|
||||
Object.defineProperty(node, 'value', {
|
||||
get: function() {
|
||||
return nodeValue;
|
||||
@@ -546,10 +539,10 @@ describe('ReactDOMInput', () => {
|
||||
});
|
||||
|
||||
ReactDOM.render(<input value="a" onChange={() => {}} />, container);
|
||||
expect(nodeValueSetter.mock.calls.length).toBe(0);
|
||||
expect(nodeValueSetter).toHaveBeenCalledTimes(0);
|
||||
|
||||
ReactDOM.render(<input value="b" onChange={() => {}} />, container);
|
||||
expect(nodeValueSetter.mock.calls.length).toBe(1);
|
||||
expect(nodeValueSetter).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should not incur unnecessary DOM mutations for numeric type conversion', () => {
|
||||
@@ -558,7 +551,7 @@ describe('ReactDOMInput', () => {
|
||||
|
||||
const node = container.firstChild;
|
||||
let nodeValue = '0';
|
||||
const nodeValueSetter = jest.genMockFn();
|
||||
const nodeValueSetter = jest.fn();
|
||||
Object.defineProperty(node, 'value', {
|
||||
get: function() {
|
||||
return nodeValue;
|
||||
@@ -569,7 +562,7 @@ describe('ReactDOMInput', () => {
|
||||
});
|
||||
|
||||
ReactDOM.render(<input value={0} onChange={() => {}} />, container);
|
||||
expect(nodeValueSetter.mock.calls.length).toBe(0);
|
||||
expect(nodeValueSetter).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it('should not incur unnecessary DOM mutations for the boolean type conversion', () => {
|
||||
@@ -578,7 +571,7 @@ describe('ReactDOMInput', () => {
|
||||
|
||||
const node = container.firstChild;
|
||||
let nodeValue = 'true';
|
||||
const nodeValueSetter = jest.genMockFn();
|
||||
const nodeValueSetter = jest.fn();
|
||||
Object.defineProperty(node, 'value', {
|
||||
get: function() {
|
||||
return nodeValue;
|
||||
@@ -589,13 +582,12 @@ describe('ReactDOMInput', () => {
|
||||
});
|
||||
|
||||
ReactDOM.render(<input value={true} onChange={() => {}} />, container);
|
||||
expect(nodeValueSetter.mock.calls.length).toBe(0);
|
||||
expect(nodeValueSetter).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it('should properly control a value of number `0`', () => {
|
||||
let stub = <input type="text" value={0} onChange={emptyFunction} />;
|
||||
stub = ReactTestUtils.renderIntoDocument(stub);
|
||||
const node = ReactDOM.findDOMNode(stub);
|
||||
const stub = <input type="text" value={0} onChange={emptyFunction} />;
|
||||
const node = ReactTestUtils.renderIntoDocument(stub);
|
||||
|
||||
node.value = 'giraffe';
|
||||
ReactTestUtils.Simulate.change(node);
|
||||
@@ -603,9 +595,8 @@ describe('ReactDOMInput', () => {
|
||||
});
|
||||
|
||||
it('should properly control 0.0 for a text input', () => {
|
||||
let stub = <input type="text" value={0} onChange={emptyFunction} />;
|
||||
stub = ReactTestUtils.renderIntoDocument(stub);
|
||||
const node = ReactDOM.findDOMNode(stub);
|
||||
const stub = <input type="text" value={0} onChange={emptyFunction} />;
|
||||
const node = ReactTestUtils.renderIntoDocument(stub);
|
||||
|
||||
node.value = '0.0';
|
||||
ReactTestUtils.Simulate.change(node, {target: {value: '0.0'}});
|
||||
@@ -613,9 +604,8 @@ describe('ReactDOMInput', () => {
|
||||
});
|
||||
|
||||
it('should properly control 0.0 for a number input', () => {
|
||||
let stub = <input type="number" value={0} onChange={emptyFunction} />;
|
||||
stub = ReactTestUtils.renderIntoDocument(stub);
|
||||
const node = ReactDOM.findDOMNode(stub);
|
||||
const stub = <input type="number" value={0} onChange={emptyFunction} />;
|
||||
const node = ReactTestUtils.renderIntoDocument(stub);
|
||||
|
||||
node.value = '0.0';
|
||||
ReactTestUtils.Simulate.change(node, {target: {value: '0.0'}});
|
||||
@@ -694,18 +684,16 @@ describe('ReactDOMInput', () => {
|
||||
|
||||
setUntrackedValue.call(node, 'giraffe');
|
||||
|
||||
const fakeNativeEvent = function() {};
|
||||
fakeNativeEvent.target = node;
|
||||
fakeNativeEvent.path = [node, container];
|
||||
ReactTestUtils.simulateNativeEventOnNode('topInput', node, fakeNativeEvent);
|
||||
ReactTestUtils.SimulateNative.input(node, {
|
||||
path: [node, container],
|
||||
});
|
||||
|
||||
expect(handled).toBe(true);
|
||||
});
|
||||
|
||||
it('should not set a value for submit buttons unnecessarily', () => {
|
||||
let stub = <input type="submit" />;
|
||||
stub = ReactTestUtils.renderIntoDocument(stub);
|
||||
const node = ReactDOM.findDOMNode(stub);
|
||||
const stub = <input type="submit" />;
|
||||
const node = ReactTestUtils.renderIntoDocument(stub);
|
||||
|
||||
// The value shouldn't be '', or else the button will have no text; it
|
||||
// should have the default "Submit" or "Submit Query" label. Most browsers
|
||||
@@ -1461,10 +1449,9 @@ describe('ReactDOMInput', () => {
|
||||
});
|
||||
|
||||
it('an uncontrolled number input will not update the value attribute on blur', () => {
|
||||
const stub = ReactTestUtils.renderIntoDocument(
|
||||
const node = ReactTestUtils.renderIntoDocument(
|
||||
<input type="number" defaultValue="1" />,
|
||||
);
|
||||
const node = ReactDOM.findDOMNode(stub);
|
||||
|
||||
node.value = 4;
|
||||
|
||||
@@ -1474,10 +1461,9 @@ describe('ReactDOMInput', () => {
|
||||
});
|
||||
|
||||
it('an uncontrolled text input will not update the value attribute on blur', () => {
|
||||
const stub = ReactTestUtils.renderIntoDocument(
|
||||
const node = ReactTestUtils.renderIntoDocument(
|
||||
<input type="text" defaultValue="1" />,
|
||||
);
|
||||
const node = ReactDOM.findDOMNode(stub);
|
||||
|
||||
node.value = 4;
|
||||
|
||||
|
||||
@@ -26,8 +26,7 @@ describe('ReactDOMOption', () => {
|
||||
{1} {'foo'}
|
||||
</option>
|
||||
);
|
||||
stub = ReactTestUtils.renderIntoDocument(stub);
|
||||
const node = ReactDOM.findDOMNode(stub);
|
||||
const node = ReactTestUtils.renderIntoDocument(stub);
|
||||
|
||||
expect(node.innerHTML).toBe('1 foo');
|
||||
});
|
||||
@@ -59,17 +58,15 @@ describe('ReactDOMOption', () => {
|
||||
{undefined} {2}
|
||||
</option>
|
||||
);
|
||||
stub = ReactTestUtils.renderIntoDocument(stub);
|
||||
const node = ReactTestUtils.renderIntoDocument(stub);
|
||||
|
||||
const node = ReactDOM.findDOMNode(stub);
|
||||
expect(node.innerHTML).toBe('1 2');
|
||||
});
|
||||
|
||||
it('should be able to use dangerouslySetInnerHTML on option', () => {
|
||||
let stub = <option dangerouslySetInnerHTML={{__html: 'foobar'}} />;
|
||||
stub = ReactTestUtils.renderIntoDocument(stub);
|
||||
const node = ReactTestUtils.renderIntoDocument(stub);
|
||||
|
||||
const node = ReactDOM.findDOMNode(stub);
|
||||
expect(node.innerHTML).toBe('foobar');
|
||||
});
|
||||
|
||||
@@ -95,8 +92,7 @@ describe('ReactDOMOption', () => {
|
||||
);
|
||||
const options = stub.props.children;
|
||||
const container = document.createElement('div');
|
||||
stub = ReactDOM.render(stub, container);
|
||||
const node = ReactDOM.findDOMNode(stub);
|
||||
const node = ReactDOM.render(stub, container);
|
||||
|
||||
expect(node.selectedIndex).toBe(1);
|
||||
|
||||
|
||||
@@ -17,52 +17,46 @@ let AsyncMode = React.unstable_AsyncMode;
|
||||
describe('ReactDOMRoot', () => {
|
||||
let container;
|
||||
|
||||
let scheduledCallback;
|
||||
let flush;
|
||||
let now;
|
||||
let expire;
|
||||
let advanceCurrentTime;
|
||||
|
||||
beforeEach(() => {
|
||||
container = document.createElement('div');
|
||||
|
||||
// Override requestIdleCallback
|
||||
scheduledCallback = null;
|
||||
flush = function(units = Infinity) {
|
||||
if (scheduledCallback !== null) {
|
||||
let didStop = false;
|
||||
while (scheduledCallback !== null && !didStop) {
|
||||
const cb = scheduledCallback;
|
||||
scheduledCallback = null;
|
||||
cb({
|
||||
timeRemaining() {
|
||||
if (units > 0) {
|
||||
return 999;
|
||||
}
|
||||
didStop = true;
|
||||
return 0;
|
||||
},
|
||||
});
|
||||
units--;
|
||||
}
|
||||
// TODO pull this into helper method, reduce repetition.
|
||||
// mock the browser APIs which are used in react-scheduler:
|
||||
// - requestAnimationFrame should pass the DOMHighResTimeStamp argument
|
||||
// - calling 'window.postMessage' should actually fire postmessage handlers
|
||||
// - must allow artificially changing time returned by Date.now
|
||||
// Performance.now is not supported in the test environment
|
||||
const originalDateNow = Date.now;
|
||||
let advancedTime = null;
|
||||
global.Date.now = function() {
|
||||
if (advancedTime) {
|
||||
return originalDateNow() + advancedTime;
|
||||
}
|
||||
return originalDateNow();
|
||||
};
|
||||
advanceCurrentTime = function(amount) {
|
||||
advancedTime = amount;
|
||||
};
|
||||
global.requestAnimationFrame = function(cb) {
|
||||
return setTimeout(() => {
|
||||
cb(Date.now());
|
||||
});
|
||||
};
|
||||
const originalAddEventListener = global.addEventListener;
|
||||
let postMessageCallback;
|
||||
global.addEventListener = function(eventName, callback, useCapture) {
|
||||
if (eventName === 'message') {
|
||||
postMessageCallback = callback;
|
||||
} else {
|
||||
originalAddEventListener(eventName, callback, useCapture);
|
||||
}
|
||||
};
|
||||
global.performance = {
|
||||
now() {
|
||||
return now;
|
||||
},
|
||||
};
|
||||
global.requestIdleCallback = function(cb) {
|
||||
scheduledCallback = cb;
|
||||
};
|
||||
|
||||
now = 0;
|
||||
expire = function(ms) {
|
||||
now += ms;
|
||||
};
|
||||
global.performance = {
|
||||
now() {
|
||||
return now;
|
||||
},
|
||||
global.postMessage = function(messageKey, targetOrigin) {
|
||||
const postMessageEvent = {source: window, data: messageKey};
|
||||
if (postMessageCallback) {
|
||||
postMessageCallback(postMessageEvent);
|
||||
}
|
||||
};
|
||||
|
||||
jest.resetModules();
|
||||
@@ -75,17 +69,17 @@ describe('ReactDOMRoot', () => {
|
||||
it('renders children', () => {
|
||||
const root = ReactDOM.unstable_createRoot(container);
|
||||
root.render(<div>Hi</div>);
|
||||
flush();
|
||||
jest.runAllTimers();
|
||||
expect(container.textContent).toEqual('Hi');
|
||||
});
|
||||
|
||||
it('unmounts children', () => {
|
||||
const root = ReactDOM.unstable_createRoot(container);
|
||||
root.render(<div>Hi</div>);
|
||||
flush();
|
||||
jest.runAllTimers();
|
||||
expect(container.textContent).toEqual('Hi');
|
||||
root.unmount();
|
||||
flush();
|
||||
jest.runAllTimers();
|
||||
expect(container.textContent).toEqual('');
|
||||
});
|
||||
|
||||
@@ -97,7 +91,7 @@ describe('ReactDOMRoot', () => {
|
||||
ops.push('inside callback: ' + container.textContent);
|
||||
});
|
||||
ops.push('before committing: ' + container.textContent);
|
||||
flush();
|
||||
jest.runAllTimers();
|
||||
ops.push('after committing: ' + container.textContent);
|
||||
expect(ops).toEqual([
|
||||
'before committing: ',
|
||||
@@ -110,7 +104,7 @@ describe('ReactDOMRoot', () => {
|
||||
it('resolves `work.then` callback synchronously if the work already committed', () => {
|
||||
const root = ReactDOM.unstable_createRoot(container);
|
||||
const work = root.render(<AsyncMode>Hi</AsyncMode>);
|
||||
flush();
|
||||
jest.runAllTimers();
|
||||
let ops = [];
|
||||
work.then(() => {
|
||||
ops.push('inside callback');
|
||||
@@ -138,7 +132,7 @@ describe('ReactDOMRoot', () => {
|
||||
<span />
|
||||
</div>,
|
||||
);
|
||||
flush();
|
||||
jest.runAllTimers();
|
||||
|
||||
// Accepts `hydrate` option
|
||||
const container2 = document.createElement('div');
|
||||
@@ -149,7 +143,7 @@ describe('ReactDOMRoot', () => {
|
||||
<span />
|
||||
</div>,
|
||||
);
|
||||
expect(flush).toWarnDev('Extra attributes');
|
||||
expect(jest.runAllTimers).toWarnDev('Extra attributes');
|
||||
});
|
||||
|
||||
it('does not clear existing children', async () => {
|
||||
@@ -161,7 +155,7 @@ describe('ReactDOMRoot', () => {
|
||||
<span>d</span>
|
||||
</div>,
|
||||
);
|
||||
flush();
|
||||
jest.runAllTimers();
|
||||
expect(container.textContent).toEqual('abcd');
|
||||
root.render(
|
||||
<div>
|
||||
@@ -169,7 +163,7 @@ describe('ReactDOMRoot', () => {
|
||||
<span>c</span>
|
||||
</div>,
|
||||
);
|
||||
flush();
|
||||
jest.runAllTimers();
|
||||
expect(container.textContent).toEqual('abdc');
|
||||
});
|
||||
|
||||
@@ -205,7 +199,7 @@ describe('ReactDOMRoot', () => {
|
||||
</AsyncMode>,
|
||||
);
|
||||
|
||||
flush();
|
||||
jest.runAllTimers();
|
||||
|
||||
// Hasn't updated yet
|
||||
expect(container.textContent).toEqual('');
|
||||
@@ -234,7 +228,7 @@ describe('ReactDOMRoot', () => {
|
||||
const batch = root.createBatch();
|
||||
batch.render(<Foo>Hi</Foo>);
|
||||
// Flush all async work.
|
||||
flush();
|
||||
jest.runAllTimers();
|
||||
// Root should complete without committing.
|
||||
expect(ops).toEqual(['Foo']);
|
||||
expect(container.textContent).toEqual('');
|
||||
@@ -252,7 +246,7 @@ describe('ReactDOMRoot', () => {
|
||||
const batch = root.createBatch();
|
||||
batch.render(<AsyncMode>Foo</AsyncMode>);
|
||||
|
||||
flush();
|
||||
jest.runAllTimers();
|
||||
|
||||
// Hasn't updated yet
|
||||
expect(container.textContent).toEqual('');
|
||||
@@ -292,7 +286,7 @@ describe('ReactDOMRoot', () => {
|
||||
const root = ReactDOM.unstable_createRoot(container);
|
||||
root.render(<AsyncMode>1</AsyncMode>);
|
||||
|
||||
expire(2000);
|
||||
advanceCurrentTime(2000);
|
||||
// This batch has a later expiration time than the earlier update.
|
||||
const batch = root.createBatch();
|
||||
|
||||
@@ -300,7 +294,7 @@ describe('ReactDOMRoot', () => {
|
||||
batch.commit();
|
||||
expect(container.textContent).toEqual('');
|
||||
|
||||
flush();
|
||||
jest.runAllTimers();
|
||||
expect(container.textContent).toEqual('1');
|
||||
});
|
||||
|
||||
@@ -327,7 +321,7 @@ describe('ReactDOMRoot', () => {
|
||||
batch1.render(1);
|
||||
|
||||
// This batch has a later expiration time
|
||||
expire(2000);
|
||||
advanceCurrentTime(2000);
|
||||
const batch2 = root.createBatch();
|
||||
batch2.render(2);
|
||||
|
||||
@@ -346,7 +340,7 @@ describe('ReactDOMRoot', () => {
|
||||
batch1.render(1);
|
||||
|
||||
// This batch has a later expiration time
|
||||
expire(2000);
|
||||
advanceCurrentTime(2000);
|
||||
const batch2 = root.createBatch();
|
||||
batch2.render(2);
|
||||
|
||||
@@ -356,7 +350,7 @@ describe('ReactDOMRoot', () => {
|
||||
expect(container.textContent).toEqual('2');
|
||||
|
||||
batch1.commit();
|
||||
flush();
|
||||
jest.runAllTimers();
|
||||
expect(container.textContent).toEqual('1');
|
||||
});
|
||||
|
||||
|
||||
@@ -35,8 +35,7 @@ describe('ReactDOMSelect', () => {
|
||||
);
|
||||
const options = stub.props.children;
|
||||
const container = document.createElement('div');
|
||||
stub = ReactDOM.render(stub, container);
|
||||
const node = ReactDOM.findDOMNode(stub);
|
||||
const node = ReactDOM.render(stub, container);
|
||||
|
||||
expect(node.value).toBe('giraffe');
|
||||
|
||||
@@ -65,8 +64,7 @@ describe('ReactDOMSelect', () => {
|
||||
</select>
|
||||
);
|
||||
const container = document.createElement('div');
|
||||
const stub = ReactDOM.render(el, container);
|
||||
const node = ReactDOM.findDOMNode(stub);
|
||||
const node = ReactDOM.render(el, container);
|
||||
|
||||
expect(node.value).toBe('giraffe');
|
||||
|
||||
@@ -86,8 +84,7 @@ describe('ReactDOMSelect', () => {
|
||||
);
|
||||
const options = stub.props.children;
|
||||
const container = document.createElement('div');
|
||||
stub = ReactDOM.render(stub, container);
|
||||
const node = ReactDOM.findDOMNode(stub);
|
||||
const node = ReactDOM.render(stub, container);
|
||||
|
||||
expect(node.options[0].selected).toBe(false); // monkey
|
||||
expect(node.options[1].selected).toBe(true); // giraffe
|
||||
@@ -116,8 +113,7 @@ describe('ReactDOMSelect', () => {
|
||||
);
|
||||
const options = stub.props.children;
|
||||
const container = document.createElement('div');
|
||||
stub = ReactDOM.render(stub, container);
|
||||
const node = ReactDOM.findDOMNode(stub);
|
||||
const node = ReactDOM.render(stub, container);
|
||||
|
||||
expect(node.value).toBe('giraffe');
|
||||
|
||||
@@ -141,8 +137,7 @@ describe('ReactDOMSelect', () => {
|
||||
</select>
|
||||
);
|
||||
const container = document.createElement('div');
|
||||
stub = ReactDOM.render(stub, container);
|
||||
const node = ReactDOM.findDOMNode(stub);
|
||||
const node = ReactDOM.render(stub, container);
|
||||
expect(node.options[0].selected).toBe(false);
|
||||
expect(node.options[2].selected).toBe(true);
|
||||
});
|
||||
@@ -157,8 +152,7 @@ describe('ReactDOMSelect', () => {
|
||||
);
|
||||
const options = stub.props.children;
|
||||
const container = document.createElement('div');
|
||||
stub = ReactDOM.render(stub, container);
|
||||
const node = ReactDOM.findDOMNode(stub);
|
||||
const node = ReactDOM.render(stub, container);
|
||||
|
||||
expect(node.value).toBe('__proto__');
|
||||
|
||||
@@ -190,8 +184,7 @@ describe('ReactDOMSelect', () => {
|
||||
);
|
||||
const options = stub.props.children;
|
||||
const container = document.createElement('div');
|
||||
stub = ReactDOM.render(stub, container);
|
||||
const node = ReactDOM.findDOMNode(stub);
|
||||
const node = ReactDOM.render(stub, container);
|
||||
|
||||
expect(node.options[0].selected).toBe(false); // monkey
|
||||
expect(node.options[1].selected).toBe(true); // giraffe
|
||||
@@ -220,8 +213,7 @@ describe('ReactDOMSelect', () => {
|
||||
);
|
||||
const options = stub.props.children;
|
||||
const container = document.createElement('div');
|
||||
stub = ReactDOM.render(stub, container);
|
||||
const node = ReactDOM.findDOMNode(stub);
|
||||
const node = ReactDOM.render(stub, container);
|
||||
|
||||
expect(node.options[0].selected).toBe(false); // monkey
|
||||
expect(node.options[1].selected).toBe(true); // __proto__
|
||||
@@ -248,8 +240,7 @@ describe('ReactDOMSelect', () => {
|
||||
<option value="12">twelve</option>
|
||||
</select>
|
||||
);
|
||||
stub = ReactTestUtils.renderIntoDocument(stub);
|
||||
const node = ReactDOM.findDOMNode(stub);
|
||||
const node = ReactTestUtils.renderIntoDocument(stub);
|
||||
|
||||
expect(node.options[0].selected).toBe(false); // one
|
||||
expect(node.options[1].selected).toBe(false); // two
|
||||
@@ -259,7 +250,7 @@ describe('ReactDOMSelect', () => {
|
||||
it('should reset child options selected when they are changed and `value` is set', () => {
|
||||
let stub = <select multiple={true} value={['a', 'b']} onChange={noop} />;
|
||||
const container = document.createElement('div');
|
||||
stub = ReactDOM.render(stub, container);
|
||||
const node = ReactDOM.render(stub, container);
|
||||
|
||||
ReactDOM.render(
|
||||
<select multiple={true} value={['a', 'b']} onChange={noop}>
|
||||
@@ -270,8 +261,6 @@ describe('ReactDOMSelect', () => {
|
||||
container,
|
||||
);
|
||||
|
||||
const node = ReactDOM.findDOMNode(stub);
|
||||
|
||||
expect(node.options[0].selected).toBe(true); // a
|
||||
expect(node.options[1].selected).toBe(true); // b
|
||||
expect(node.options[2].selected).toBe(false); // c
|
||||
@@ -293,8 +282,7 @@ describe('ReactDOMSelect', () => {
|
||||
</select>
|
||||
);
|
||||
const container = document.createElement('div');
|
||||
const stub = ReactDOM.render(el, container);
|
||||
const node = ReactDOM.findDOMNode(stub);
|
||||
const node = ReactDOM.render(el, container);
|
||||
|
||||
expect(node.options[0].selected).toBe(false); // monkey
|
||||
expect(node.options[1].selected).toBe(true); // giraffe
|
||||
@@ -327,8 +315,7 @@ describe('ReactDOMSelect', () => {
|
||||
);
|
||||
const options = stub.props.children;
|
||||
const container = document.createElement('div');
|
||||
stub = ReactDOM.render(stub, container);
|
||||
const node = ReactDOM.findDOMNode(stub);
|
||||
const node = ReactDOM.render(stub, container);
|
||||
|
||||
expect(node.options[0].selected).toBe(false); // monkey
|
||||
expect(node.options[1].selected).toBe(true); // giraffe
|
||||
@@ -357,8 +344,7 @@ describe('ReactDOMSelect', () => {
|
||||
);
|
||||
const options = stub.props.children;
|
||||
const container = document.createElement('div');
|
||||
stub = ReactDOM.render(stub, container);
|
||||
const node = ReactDOM.findDOMNode(stub);
|
||||
const node = ReactDOM.render(stub, container);
|
||||
|
||||
expect(node.options[0].selected).toBe(false); // monkey
|
||||
expect(node.options[1].selected).toBe(true); // giraffe
|
||||
@@ -386,8 +372,7 @@ describe('ReactDOMSelect', () => {
|
||||
);
|
||||
const options = stub.props.children;
|
||||
const container = document.createElement('div');
|
||||
stub = ReactDOM.render(stub, container);
|
||||
const node = ReactDOM.findDOMNode(stub);
|
||||
const node = ReactDOM.render(stub, container);
|
||||
|
||||
expect(node.options[0].selected).toBe(false); // monkey
|
||||
expect(node.options[1].selected).toBe(true); // giraffe
|
||||
@@ -410,8 +395,7 @@ describe('ReactDOMSelect', () => {
|
||||
);
|
||||
const options = stub.props.children;
|
||||
const container = document.createElement('div');
|
||||
stub = ReactDOM.render(stub, container);
|
||||
const node = ReactDOM.findDOMNode(stub);
|
||||
const node = ReactDOM.render(stub, container);
|
||||
|
||||
ReactDOM.render(
|
||||
<select value="gorilla" onChange={noop}>
|
||||
@@ -459,6 +443,37 @@ describe('ReactDOMSelect', () => {
|
||||
expect(markup).not.toContain('<option selected="" value="gorilla"');
|
||||
});
|
||||
|
||||
it('should support server-side rendering with dangerouslySetInnerHTML', () => {
|
||||
const stub = (
|
||||
<select defaultValue="giraffe">
|
||||
<option
|
||||
value="monkey"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: 'A monkey!',
|
||||
}}>
|
||||
{undefined}
|
||||
</option>
|
||||
<option
|
||||
value="giraffe"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: 'A giraffe!',
|
||||
}}>
|
||||
{null}
|
||||
</option>
|
||||
<option
|
||||
value="gorilla"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: 'A gorilla!',
|
||||
}}
|
||||
/>
|
||||
</select>
|
||||
);
|
||||
const markup = ReactDOMServer.renderToString(stub);
|
||||
expect(markup).toContain('<option selected="" value="giraffe"');
|
||||
expect(markup).not.toContain('<option selected="" value="monkey"');
|
||||
expect(markup).not.toContain('<option selected="" value="gorilla"');
|
||||
});
|
||||
|
||||
it('should support server-side rendering with multiple', () => {
|
||||
const stub = (
|
||||
<select multiple={true} value={['giraffe', 'gorilla']} onChange={noop}>
|
||||
@@ -476,7 +491,7 @@ describe('ReactDOMSelect', () => {
|
||||
it('should not control defaultValue if readding options', () => {
|
||||
const container = document.createElement('div');
|
||||
|
||||
const select = ReactDOM.render(
|
||||
const node = ReactDOM.render(
|
||||
<select multiple={true} defaultValue={['giraffe']}>
|
||||
<option key="monkey" value="monkey">
|
||||
A monkey!
|
||||
@@ -490,7 +505,6 @@ describe('ReactDOMSelect', () => {
|
||||
</select>,
|
||||
container,
|
||||
);
|
||||
const node = ReactDOM.findDOMNode(select);
|
||||
|
||||
expect(node.options[0].selected).toBe(false); // monkey
|
||||
expect(node.options[1].selected).toBe(true); // giraffe
|
||||
@@ -601,8 +615,7 @@ describe('ReactDOMSelect', () => {
|
||||
<option value="gorilla">A gorilla!</option>
|
||||
</select>
|
||||
);
|
||||
stub = ReactTestUtils.renderIntoDocument(stub);
|
||||
const node = ReactDOM.findDOMNode(stub);
|
||||
const node = ReactTestUtils.renderIntoDocument(stub);
|
||||
|
||||
ReactTestUtils.Simulate.change(node);
|
||||
|
||||
@@ -648,8 +661,7 @@ describe('ReactDOMSelect', () => {
|
||||
<option value="gorilla">A gorilla!</option>
|
||||
</select>
|
||||
);
|
||||
stub = ReactDOM.render(stub, container);
|
||||
const node = ReactDOM.findDOMNode(stub);
|
||||
const node = ReactDOM.render(stub, container);
|
||||
|
||||
expect(() => ReactTestUtils.Simulate.change(node)).not.toThrow();
|
||||
});
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
let React;
|
||||
let ReactDOM;
|
||||
let ReactDOMSelection;
|
||||
let invariant;
|
||||
|
||||
let getModernOffsetsFromPoints;
|
||||
|
||||
@@ -21,7 +20,6 @@ describe('ReactDOMSelection', () => {
|
||||
React = require('react');
|
||||
ReactDOM = require('react-dom');
|
||||
ReactDOMSelection = require('../client/ReactDOMSelection');
|
||||
invariant = require('fbjs/lib/invariant');
|
||||
|
||||
({getModernOffsetsFromPoints} = ReactDOMSelection);
|
||||
});
|
||||
@@ -68,10 +66,9 @@ describe('ReactDOMSelection', () => {
|
||||
}
|
||||
traverse(outerNode);
|
||||
|
||||
invariant(
|
||||
start !== null && end !== null,
|
||||
'Provided anchor/focus nodes were outside of root.',
|
||||
);
|
||||
if (start === null || end === null) {
|
||||
throw new Error('Provided anchor/focus nodes were outside of root.');
|
||||
}
|
||||
return {start, end};
|
||||
}
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ const {
|
||||
resetModules,
|
||||
itRenders,
|
||||
itClientRenders,
|
||||
itThrowsWhenRendering,
|
||||
renderIntoDom,
|
||||
serverRender,
|
||||
} = ReactDOMServerIntegrationUtils(initModules);
|
||||
@@ -327,6 +328,81 @@ describe('ReactDOMServerIntegration', () => {
|
||||
expectSelectValue(e, 'bar');
|
||||
});
|
||||
|
||||
itRenders(
|
||||
'a select with options that use dangerouslySetInnerHTML',
|
||||
async render => {
|
||||
const e = await render(
|
||||
<select defaultValue="baz" value="bar" readOnly={true}>
|
||||
<option
|
||||
id="foo"
|
||||
value="foo"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: 'Foo',
|
||||
}}>
|
||||
{undefined}
|
||||
</option>
|
||||
<option
|
||||
id="bar"
|
||||
value="bar"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: 'Bar',
|
||||
}}>
|
||||
{null}
|
||||
</option>
|
||||
<option
|
||||
id="baz"
|
||||
value="baz"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: 'Baz',
|
||||
}}
|
||||
/>
|
||||
</select>,
|
||||
1,
|
||||
);
|
||||
expectSelectValue(e, 'bar');
|
||||
},
|
||||
);
|
||||
|
||||
itThrowsWhenRendering(
|
||||
'a select with option that uses dangerouslySetInnerHTML and 0 as child',
|
||||
async render => {
|
||||
await render(
|
||||
<select defaultValue="baz" value="foo" readOnly={true}>
|
||||
<option
|
||||
id="foo"
|
||||
value="foo"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: 'Foo',
|
||||
}}>
|
||||
{0}
|
||||
</option>
|
||||
</select>,
|
||||
1,
|
||||
);
|
||||
},
|
||||
'Can only set one of `children` or `props.dangerouslySetInnerHTML`.',
|
||||
);
|
||||
|
||||
itThrowsWhenRendering(
|
||||
'a select with option that uses dangerouslySetInnerHTML and empty string as child',
|
||||
async render => {
|
||||
await render(
|
||||
<select defaultValue="baz" value="foo" readOnly={true}>
|
||||
<option
|
||||
id="foo"
|
||||
value="foo"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: 'Foo',
|
||||
}}>
|
||||
{''}
|
||||
</option>
|
||||
</select>,
|
||||
1,
|
||||
);
|
||||
},
|
||||
'Can only set one of `children` or `props.dangerouslySetInnerHTML`.',
|
||||
);
|
||||
|
||||
itRenders(
|
||||
'a select value overriding defaultValue no matter the prop order',
|
||||
async render => {
|
||||
@@ -348,9 +424,13 @@ describe('ReactDOMServerIntegration', () => {
|
||||
ControlledSelect;
|
||||
beforeEach(() => {
|
||||
ControlledInput = class extends React.Component {
|
||||
static defaultProps = {
|
||||
type: 'text',
|
||||
initialValue: 'Hello',
|
||||
};
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {value: 'Hello'};
|
||||
super(...arguments);
|
||||
this.state = {value: this.props.initialValue};
|
||||
}
|
||||
handleChange(event) {
|
||||
if (this.props.onChange) {
|
||||
@@ -361,6 +441,7 @@ describe('ReactDOMServerIntegration', () => {
|
||||
render() {
|
||||
return (
|
||||
<input
|
||||
type={this.props.type}
|
||||
value={this.state.value}
|
||||
onChange={this.handleChange.bind(this)}
|
||||
/>
|
||||
@@ -551,6 +632,27 @@ describe('ReactDOMServerIntegration', () => {
|
||||
expect(changeCount).toBe(0);
|
||||
});
|
||||
|
||||
it('should not blow away user-interaction on successful reconnect to an uncontrolled range input', () =>
|
||||
testUserInteractionBeforeClientRender(
|
||||
<input type="text" defaultValue="0.5" />,
|
||||
'0.5',
|
||||
'1',
|
||||
));
|
||||
|
||||
it('should not blow away user-interaction on successful reconnect to a controlled range input', async () => {
|
||||
let changeCount = 0;
|
||||
await testUserInteractionBeforeClientRender(
|
||||
<ControlledInput
|
||||
type="range"
|
||||
initialValue="0.25"
|
||||
onChange={() => changeCount++}
|
||||
/>,
|
||||
'0.25',
|
||||
'1',
|
||||
);
|
||||
expect(changeCount).toBe(0);
|
||||
});
|
||||
|
||||
it('should not blow away user-entered text on successful reconnect to an uncontrolled checkbox', () =>
|
||||
testUserInteractionBeforeClientRender(
|
||||
<input type="checkbox" defaultChecked={true} />,
|
||||
|
||||
@@ -42,7 +42,7 @@ describe('ReactDOMServerIntegration', () => {
|
||||
resetModules();
|
||||
});
|
||||
|
||||
describe('context', function() {
|
||||
describe('legacy context', function() {
|
||||
let PurpleContext, RedContext;
|
||||
beforeEach(() => {
|
||||
class Parent extends React.Component {
|
||||
253
packages/react-dom/src/__tests__/ReactDOMServerIntegrationNewContext-test.js
vendored
Normal file
253
packages/react-dom/src/__tests__/ReactDOMServerIntegrationNewContext-test.js
vendored
Normal file
@@ -0,0 +1,253 @@
|
||||
/**
|
||||
* 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';
|
||||
|
||||
const ReactDOMServerIntegrationUtils = require('./utils/ReactDOMServerIntegrationTestUtils');
|
||||
|
||||
let React;
|
||||
let ReactDOM;
|
||||
let ReactDOMServer;
|
||||
|
||||
function initModules() {
|
||||
// Reset warning cache.
|
||||
jest.resetModuleRegistry();
|
||||
React = require('react');
|
||||
ReactDOM = require('react-dom');
|
||||
ReactDOMServer = require('react-dom/server');
|
||||
|
||||
// Make them available to the helpers.
|
||||
return {
|
||||
ReactDOM,
|
||||
ReactDOMServer,
|
||||
};
|
||||
}
|
||||
|
||||
const {resetModules, itRenders} = ReactDOMServerIntegrationUtils(initModules);
|
||||
|
||||
describe('ReactDOMServerIntegration', () => {
|
||||
beforeEach(() => {
|
||||
resetModules();
|
||||
});
|
||||
|
||||
describe('context', function() {
|
||||
let PurpleContext, RedContext, Consumer;
|
||||
beforeEach(() => {
|
||||
let Context = React.createContext('none');
|
||||
|
||||
class Parent extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Context.Provider value={this.props.text}>
|
||||
{this.props.children}
|
||||
</Context.Provider>
|
||||
);
|
||||
}
|
||||
}
|
||||
Consumer = Context.Consumer;
|
||||
PurpleContext = props => <Parent text="purple">{props.children}</Parent>;
|
||||
RedContext = props => <Parent text="red">{props.children}</Parent>;
|
||||
});
|
||||
|
||||
itRenders('class child with context', async render => {
|
||||
class ClassChildWithContext extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<Consumer>{text => text}</Consumer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const e = await render(
|
||||
<PurpleContext>
|
||||
<ClassChildWithContext />
|
||||
</PurpleContext>,
|
||||
);
|
||||
expect(e.textContent).toBe('purple');
|
||||
});
|
||||
|
||||
itRenders('stateless child with context', async render => {
|
||||
function StatelessChildWithContext(props) {
|
||||
return <Consumer>{text => text}</Consumer>;
|
||||
}
|
||||
|
||||
const e = await render(
|
||||
<PurpleContext>
|
||||
<StatelessChildWithContext />
|
||||
</PurpleContext>,
|
||||
);
|
||||
expect(e.textContent).toBe('purple');
|
||||
});
|
||||
|
||||
itRenders('class child with default context', async render => {
|
||||
class ClassChildWithWrongContext extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<div id="classWrongChild">
|
||||
<Consumer>{text => text}</Consumer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const e = await render(<ClassChildWithWrongContext />);
|
||||
expect(e.textContent).toBe('none');
|
||||
});
|
||||
|
||||
itRenders('stateless child with wrong context', async render => {
|
||||
function StatelessChildWithWrongContext(props) {
|
||||
return (
|
||||
<div id="statelessWrongChild">
|
||||
<Consumer>{text => text}</Consumer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const e = await render(<StatelessChildWithWrongContext />);
|
||||
expect(e.textContent).toBe('none');
|
||||
});
|
||||
|
||||
itRenders('with context passed through to a grandchild', async render => {
|
||||
function Grandchild(props) {
|
||||
return (
|
||||
<div>
|
||||
<Consumer>{text => text}</Consumer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const Child = props => <Grandchild />;
|
||||
|
||||
const e = await render(
|
||||
<PurpleContext>
|
||||
<Child />
|
||||
</PurpleContext>,
|
||||
);
|
||||
expect(e.textContent).toBe('purple');
|
||||
});
|
||||
|
||||
itRenders('a child context overriding a parent context', async render => {
|
||||
const Grandchild = props => {
|
||||
return (
|
||||
<div>
|
||||
<Consumer>{text => text}</Consumer>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const e = await render(
|
||||
<PurpleContext>
|
||||
<RedContext>
|
||||
<Grandchild />
|
||||
</RedContext>
|
||||
</PurpleContext>,
|
||||
);
|
||||
expect(e.textContent).toBe('red');
|
||||
});
|
||||
|
||||
itRenders('multiple contexts', async render => {
|
||||
const Theme = React.createContext('dark');
|
||||
const Language = React.createContext('french');
|
||||
class Parent extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Theme.Provider value="light">
|
||||
<Child />
|
||||
</Theme.Provider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function Child() {
|
||||
return (
|
||||
<Language.Provider value="english">
|
||||
<Grandchild />
|
||||
</Language.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
const Grandchild = props => {
|
||||
return (
|
||||
<div>
|
||||
<Theme.Consumer>
|
||||
{theme => <div id="theme">{theme}</div>}
|
||||
</Theme.Consumer>
|
||||
<Language.Consumer>
|
||||
{language => <div id="language">{language}</div>}
|
||||
</Language.Consumer>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const e = await render(<Parent />);
|
||||
expect(e.querySelector('#theme').textContent).toBe('light');
|
||||
expect(e.querySelector('#language').textContent).toBe('english');
|
||||
});
|
||||
|
||||
itRenders('nested context unwinding', async render => {
|
||||
const Theme = React.createContext('dark');
|
||||
const Language = React.createContext('french');
|
||||
|
||||
const App = () => (
|
||||
<div>
|
||||
<Theme.Provider value="light">
|
||||
<Language.Provider value="english">
|
||||
<Theme.Provider value="dark">
|
||||
<Theme.Consumer>
|
||||
{theme => <div id="theme1">{theme}</div>}
|
||||
</Theme.Consumer>
|
||||
</Theme.Provider>
|
||||
<Theme.Consumer>
|
||||
{theme => <div id="theme2">{theme}</div>}
|
||||
</Theme.Consumer>
|
||||
<Language.Provider value="sanskrit">
|
||||
<Theme.Provider value="blue">
|
||||
<Theme.Provider value="red">
|
||||
<Language.Consumer>
|
||||
{() => (
|
||||
<Language.Provider value="chinese">
|
||||
<Language.Provider value="hungarian" />
|
||||
<Language.Consumer>
|
||||
{language => <div id="language1">{language}</div>}
|
||||
</Language.Consumer>
|
||||
</Language.Provider>
|
||||
)}
|
||||
</Language.Consumer>
|
||||
</Theme.Provider>
|
||||
<Language.Consumer>
|
||||
{language => (
|
||||
<React.Fragment>
|
||||
<Theme.Consumer>
|
||||
{theme => <div id="theme3">{theme}</div>}
|
||||
</Theme.Consumer>
|
||||
<div id="language2">{language}</div>
|
||||
</React.Fragment>
|
||||
)}
|
||||
</Language.Consumer>
|
||||
</Theme.Provider>
|
||||
</Language.Provider>
|
||||
</Language.Provider>
|
||||
</Theme.Provider>
|
||||
<Language.Consumer>
|
||||
{language => <div id="language3">{language}</div>}
|
||||
</Language.Consumer>
|
||||
</div>
|
||||
);
|
||||
let e = await render(<App />);
|
||||
expect(e.querySelector('#theme1').textContent).toBe('dark');
|
||||
expect(e.querySelector('#theme2').textContent).toBe('light');
|
||||
expect(e.querySelector('#theme3').textContent).toBe('blue');
|
||||
expect(e.querySelector('#language1').textContent).toBe('chinese');
|
||||
expect(e.querySelector('#language2').textContent).toBe('sanskrit');
|
||||
expect(e.querySelector('#language3').textContent).toBe('french');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -69,9 +69,9 @@ describe('ReactDOMServerLifecycles', () => {
|
||||
});
|
||||
|
||||
describe('react-lifecycles-compat', () => {
|
||||
const polyfill = require('react-lifecycles-compat');
|
||||
const {polyfill} = require('react-lifecycles-compat');
|
||||
|
||||
it('should not warn about deprecated cWM/cWRP for polyfilled components', () => {
|
||||
it('should not warn for components with polyfilled getDerivedStateFromProps', () => {
|
||||
class PolyfilledComponent extends React.Component {
|
||||
state = {};
|
||||
static getDerivedStateFromProps() {
|
||||
@@ -84,7 +84,35 @@ describe('ReactDOMServerLifecycles', () => {
|
||||
|
||||
polyfill(PolyfilledComponent);
|
||||
|
||||
ReactDOMServer.renderToString(<PolyfilledComponent />);
|
||||
const container = document.createElement('div');
|
||||
ReactDOMServer.renderToString(
|
||||
<React.StrictMode>
|
||||
<PolyfilledComponent />
|
||||
</React.StrictMode>,
|
||||
container,
|
||||
);
|
||||
});
|
||||
|
||||
it('should not warn for components with polyfilled getSnapshotBeforeUpdate', () => {
|
||||
class PolyfilledComponent extends React.Component {
|
||||
getSnapshotBeforeUpdate() {
|
||||
return null;
|
||||
}
|
||||
componentDidUpdate() {}
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
polyfill(PolyfilledComponent);
|
||||
|
||||
const container = document.createElement('div');
|
||||
ReactDOMServer.renderToString(
|
||||
<React.StrictMode>
|
||||
<PolyfilledComponent />
|
||||
</React.StrictMode>,
|
||||
container,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -39,7 +39,7 @@ describe('ReactDOMTextComponent', () => {
|
||||
</div>,
|
||||
el,
|
||||
);
|
||||
let nodes = filterOutComments(ReactDOM.findDOMNode(inst).childNodes);
|
||||
let nodes = filterOutComments(inst.childNodes);
|
||||
|
||||
let foo = nodes[1];
|
||||
let bar = nodes[2];
|
||||
@@ -56,7 +56,7 @@ describe('ReactDOMTextComponent', () => {
|
||||
);
|
||||
// After the update, the text nodes should have stayed in place (as opposed
|
||||
// to getting unmounted and remounted)
|
||||
nodes = filterOutComments(ReactDOM.findDOMNode(inst).childNodes);
|
||||
nodes = filterOutComments(inst.childNodes);
|
||||
expect(nodes[1]).toBe(foo);
|
||||
expect(nodes[2]).toBe(bar);
|
||||
expect(foo.data).toBe('baz');
|
||||
@@ -74,8 +74,7 @@ describe('ReactDOMTextComponent', () => {
|
||||
el,
|
||||
);
|
||||
|
||||
let container = ReactDOM.findDOMNode(inst);
|
||||
let childNodes = filterOutComments(container.childNodes);
|
||||
let childNodes = filterOutComments(inst.childNodes);
|
||||
let childDiv = childNodes[1];
|
||||
|
||||
inst = ReactDOM.render(
|
||||
@@ -86,8 +85,7 @@ describe('ReactDOMTextComponent', () => {
|
||||
</div>,
|
||||
el,
|
||||
);
|
||||
container = ReactDOM.findDOMNode(inst);
|
||||
childNodes = filterOutComments(container.childNodes);
|
||||
childNodes = filterOutComments(inst.childNodes);
|
||||
expect(childNodes.length).toBe(1);
|
||||
expect(childNodes[0]).toBe(childDiv);
|
||||
|
||||
@@ -99,8 +97,7 @@ describe('ReactDOMTextComponent', () => {
|
||||
</div>,
|
||||
el,
|
||||
);
|
||||
container = ReactDOM.findDOMNode(inst);
|
||||
childNodes = filterOutComments(container.childNodes);
|
||||
childNodes = filterOutComments(inst.childNodes);
|
||||
expect(childNodes.length).toBe(3);
|
||||
expect(childNodes[0].data).toBe('foo');
|
||||
expect(childNodes[1]).toBe(childDiv);
|
||||
@@ -125,8 +122,7 @@ describe('ReactDOMTextComponent', () => {
|
||||
el,
|
||||
);
|
||||
|
||||
let container = ReactDOM.findDOMNode(inst);
|
||||
container.normalize();
|
||||
inst.normalize();
|
||||
|
||||
inst = ReactDOM.render(
|
||||
<div>
|
||||
@@ -138,8 +134,7 @@ describe('ReactDOMTextComponent', () => {
|
||||
</div>,
|
||||
el,
|
||||
);
|
||||
container = ReactDOM.findDOMNode(inst);
|
||||
expect(container.textContent).toBe('barbazquxfoo');
|
||||
expect(inst.textContent).toBe('barbazquxfoo');
|
||||
});
|
||||
|
||||
xit('can reconcile text merged by Node.normalize()', () => {
|
||||
@@ -153,8 +148,7 @@ describe('ReactDOMTextComponent', () => {
|
||||
el,
|
||||
);
|
||||
|
||||
let container = ReactDOM.findDOMNode(inst);
|
||||
container.normalize();
|
||||
inst.normalize();
|
||||
|
||||
inst = ReactDOM.render(
|
||||
<div>
|
||||
@@ -164,8 +158,7 @@ describe('ReactDOMTextComponent', () => {
|
||||
</div>,
|
||||
el,
|
||||
);
|
||||
container = ReactDOM.findDOMNode(inst);
|
||||
expect(container.textContent).toBe('barbazqux');
|
||||
expect(inst.textContent).toBe('barbazqux');
|
||||
});
|
||||
|
||||
it('can reconcile text from pre-rendered markup', () => {
|
||||
@@ -207,15 +200,14 @@ describe('ReactDOMTextComponent', () => {
|
||||
el,
|
||||
);
|
||||
|
||||
let container = ReactDOM.findDOMNode(inst);
|
||||
let childNodes = filterOutComments(ReactDOM.findDOMNode(inst).childNodes);
|
||||
let childNodes = filterOutComments(inst.childNodes);
|
||||
let textNode = childNodes[1];
|
||||
textNode.textContent = 'foo';
|
||||
container.insertBefore(
|
||||
inst.insertBefore(
|
||||
document.createTextNode('bar'),
|
||||
childNodes[1].nextSibling,
|
||||
);
|
||||
container.insertBefore(
|
||||
inst.insertBefore(
|
||||
document.createTextNode('baz'),
|
||||
childNodes[1].nextSibling,
|
||||
);
|
||||
@@ -227,8 +219,7 @@ describe('ReactDOMTextComponent', () => {
|
||||
</div>,
|
||||
el,
|
||||
);
|
||||
container = ReactDOM.findDOMNode(inst);
|
||||
expect(container.textContent).toBe('barbazqux');
|
||||
expect(inst.textContent).toBe('barbazqux');
|
||||
});
|
||||
|
||||
xit('can reconcile text arbitrarily split into multiple nodes on some substitutions only', () => {
|
||||
@@ -246,21 +237,20 @@ describe('ReactDOMTextComponent', () => {
|
||||
el,
|
||||
);
|
||||
|
||||
let container = ReactDOM.findDOMNode(inst);
|
||||
let childNodes = filterOutComments(ReactDOM.findDOMNode(inst).childNodes);
|
||||
let childNodes = filterOutComments(inst.childNodes);
|
||||
let textNode = childNodes[3];
|
||||
textNode.textContent = 'foo';
|
||||
container.insertBefore(
|
||||
inst.insertBefore(
|
||||
document.createTextNode('bar'),
|
||||
childNodes[3].nextSibling,
|
||||
);
|
||||
container.insertBefore(
|
||||
inst.insertBefore(
|
||||
document.createTextNode('baz'),
|
||||
childNodes[3].nextSibling,
|
||||
);
|
||||
let secondTextNode = childNodes[5];
|
||||
secondTextNode.textContent = 'bar';
|
||||
container.insertBefore(
|
||||
inst.insertBefore(
|
||||
document.createTextNode('foo'),
|
||||
childNodes[5].nextSibling,
|
||||
);
|
||||
@@ -277,8 +267,7 @@ describe('ReactDOMTextComponent', () => {
|
||||
</div>,
|
||||
el,
|
||||
);
|
||||
container = ReactDOM.findDOMNode(inst);
|
||||
expect(container.textContent).toBe('bazbarbazquxbarbazbar');
|
||||
expect(inst.textContent).toBe('bazbarbazquxbarbazbar');
|
||||
});
|
||||
|
||||
xit('can unmount normalized text nodes', () => {
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
const emptyFunction = require('fbjs/lib/emptyFunction');
|
||||
function emptyFunction() {}
|
||||
|
||||
describe('ReactDOMTextarea', () => {
|
||||
let React;
|
||||
@@ -218,7 +218,7 @@ describe('ReactDOMTextarea', () => {
|
||||
|
||||
const node = container.firstChild;
|
||||
let nodeValue = 'a';
|
||||
const nodeValueSetter = jest.genMockFn();
|
||||
const nodeValueSetter = jest.fn();
|
||||
Object.defineProperty(node, 'value', {
|
||||
get: function() {
|
||||
return nodeValue;
|
||||
@@ -229,10 +229,10 @@ describe('ReactDOMTextarea', () => {
|
||||
});
|
||||
|
||||
ReactDOM.render(<textarea value="a" onChange={emptyFunction} />, container);
|
||||
expect(nodeValueSetter.mock.calls.length).toBe(0);
|
||||
expect(nodeValueSetter).toHaveBeenCalledTimes(0);
|
||||
|
||||
ReactDOM.render(<textarea value="b" onChange={emptyFunction} />, container);
|
||||
expect(nodeValueSetter.mock.calls.length).toBe(1);
|
||||
expect(nodeValueSetter).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should properly control a value of number `0`', () => {
|
||||
|
||||
@@ -24,7 +24,7 @@ describe('ReactEmptyComponent', () => {
|
||||
ReactDOM = require('react-dom');
|
||||
ReactTestUtils = require('react-dom/test-utils');
|
||||
|
||||
log = jasmine.createSpy();
|
||||
log = jest.fn();
|
||||
|
||||
TogglingComponent = class extends React.Component {
|
||||
state = {component: this.props.firstComponent};
|
||||
@@ -91,11 +91,17 @@ describe('ReactEmptyComponent', () => {
|
||||
ReactTestUtils.renderIntoDocument(instance1);
|
||||
ReactTestUtils.renderIntoDocument(instance2);
|
||||
|
||||
expect(log.calls.count()).toBe(4);
|
||||
expect(log.calls.argsFor(0)[0]).toBe(null);
|
||||
expect(log.calls.argsFor(1)[0].tagName).toBe('DIV');
|
||||
expect(log.calls.argsFor(2)[0].tagName).toBe('DIV');
|
||||
expect(log.calls.argsFor(3)[0]).toBe(null);
|
||||
expect(log).toHaveBeenCalledTimes(4);
|
||||
expect(log).toHaveBeenNthCalledWith(1, null);
|
||||
expect(log).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
expect.objectContaining({tagName: 'DIV'}),
|
||||
);
|
||||
expect(log).toHaveBeenNthCalledWith(
|
||||
3,
|
||||
expect.objectContaining({tagName: 'DIV'}),
|
||||
);
|
||||
expect(log).toHaveBeenNthCalledWith(4, null);
|
||||
});
|
||||
|
||||
it('should be able to switch in a list of children', () => {
|
||||
@@ -111,13 +117,22 @@ describe('ReactEmptyComponent', () => {
|
||||
</div>,
|
||||
);
|
||||
|
||||
expect(log.calls.count()).toBe(6);
|
||||
expect(log.calls.argsFor(0)[0]).toBe(null);
|
||||
expect(log.calls.argsFor(1)[0]).toBe(null);
|
||||
expect(log.calls.argsFor(2)[0]).toBe(null);
|
||||
expect(log.calls.argsFor(3)[0].tagName).toBe('DIV');
|
||||
expect(log.calls.argsFor(4)[0].tagName).toBe('DIV');
|
||||
expect(log.calls.argsFor(5)[0].tagName).toBe('DIV');
|
||||
expect(log).toHaveBeenCalledTimes(6);
|
||||
expect(log).toHaveBeenNthCalledWith(1, null);
|
||||
expect(log).toHaveBeenNthCalledWith(2, null);
|
||||
expect(log).toHaveBeenNthCalledWith(3, null);
|
||||
expect(log).toHaveBeenNthCalledWith(
|
||||
4,
|
||||
expect.objectContaining({tagName: 'DIV'}),
|
||||
);
|
||||
expect(log).toHaveBeenNthCalledWith(
|
||||
5,
|
||||
expect.objectContaining({tagName: 'DIV'}),
|
||||
);
|
||||
expect(log).toHaveBeenNthCalledWith(
|
||||
6,
|
||||
expect.objectContaining({tagName: 'DIV'}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should distinguish between a script placeholder and an actual script tag', () => {
|
||||
@@ -135,11 +150,17 @@ describe('ReactEmptyComponent', () => {
|
||||
ReactTestUtils.renderIntoDocument(instance2);
|
||||
}).not.toThrow();
|
||||
|
||||
expect(log.calls.count()).toBe(4);
|
||||
expect(log.calls.argsFor(0)[0]).toBe(null);
|
||||
expect(log.calls.argsFor(1)[0].tagName).toBe('SCRIPT');
|
||||
expect(log.calls.argsFor(2)[0].tagName).toBe('SCRIPT');
|
||||
expect(log.calls.argsFor(3)[0]).toBe(null);
|
||||
expect(log).toHaveBeenCalledTimes(4);
|
||||
expect(log).toHaveBeenNthCalledWith(1, null);
|
||||
expect(log).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
expect.objectContaining({tagName: 'SCRIPT'}),
|
||||
);
|
||||
expect(log).toHaveBeenNthCalledWith(
|
||||
3,
|
||||
expect.objectContaining({tagName: 'SCRIPT'}),
|
||||
);
|
||||
expect(log).toHaveBeenNthCalledWith(4, null);
|
||||
});
|
||||
|
||||
it(
|
||||
@@ -172,11 +193,17 @@ describe('ReactEmptyComponent', () => {
|
||||
ReactTestUtils.renderIntoDocument(instance2);
|
||||
}).not.toThrow();
|
||||
|
||||
expect(log.calls.count()).toBe(4);
|
||||
expect(log.calls.argsFor(0)[0].tagName).toBe('DIV');
|
||||
expect(log.calls.argsFor(1)[0]).toBe(null);
|
||||
expect(log.calls.argsFor(2)[0]).toBe(null);
|
||||
expect(log.calls.argsFor(3)[0].tagName).toBe('DIV');
|
||||
expect(log).toHaveBeenCalledTimes(4);
|
||||
expect(log).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
expect.objectContaining({tagName: 'DIV'}),
|
||||
);
|
||||
expect(log).toHaveBeenNthCalledWith(2, null);
|
||||
expect(log).toHaveBeenNthCalledWith(3, null);
|
||||
expect(log).toHaveBeenNthCalledWith(
|
||||
4,
|
||||
expect.objectContaining({tagName: 'DIV'}),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
|
||||
let React;
|
||||
let ReactDOM;
|
||||
let ReactTestUtils;
|
||||
|
||||
describe('ReactEventIndependence', () => {
|
||||
beforeEach(() => {
|
||||
@@ -19,49 +18,66 @@ describe('ReactEventIndependence', () => {
|
||||
|
||||
React = require('react');
|
||||
ReactDOM = require('react-dom');
|
||||
ReactTestUtils = require('react-dom/test-utils');
|
||||
});
|
||||
|
||||
it('does not crash with other react inside', () => {
|
||||
let clicks = 0;
|
||||
const div = ReactTestUtils.renderIntoDocument(
|
||||
<div
|
||||
onClick={() => clicks++}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: '<button data-reactid=".z">click me</div>',
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
ReactTestUtils.SimulateNative.click(div.firstChild);
|
||||
expect(clicks).toBe(1);
|
||||
const container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
try {
|
||||
const div = ReactDOM.render(
|
||||
<div
|
||||
onClick={() => clicks++}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: '<button data-reactid=".z">click me</div>',
|
||||
}}
|
||||
/>,
|
||||
container,
|
||||
);
|
||||
|
||||
div.firstChild.click();
|
||||
expect(clicks).toBe(1);
|
||||
} finally {
|
||||
document.body.removeChild(container);
|
||||
}
|
||||
});
|
||||
|
||||
it('does not crash with other react outside', () => {
|
||||
let clicks = 0;
|
||||
const outer = document.createElement('div');
|
||||
outer.setAttribute('data-reactid', '.z');
|
||||
const inner = ReactDOM.render(
|
||||
<button onClick={() => clicks++}>click me</button>,
|
||||
outer,
|
||||
);
|
||||
ReactTestUtils.SimulateNative.click(inner);
|
||||
expect(clicks).toBe(1);
|
||||
document.body.appendChild(outer);
|
||||
try {
|
||||
outer.setAttribute('data-reactid', '.z');
|
||||
const inner = ReactDOM.render(
|
||||
<button onClick={() => clicks++}>click me</button>,
|
||||
outer,
|
||||
);
|
||||
inner.click();
|
||||
expect(clicks).toBe(1);
|
||||
} finally {
|
||||
document.body.removeChild(outer);
|
||||
}
|
||||
});
|
||||
|
||||
it('does not when event fired on unmounted tree', () => {
|
||||
let clicks = 0;
|
||||
const container = document.createElement('div');
|
||||
const button = ReactDOM.render(
|
||||
<button onClick={() => clicks++}>click me</button>,
|
||||
container,
|
||||
);
|
||||
document.body.appendChild(container);
|
||||
try {
|
||||
const button = ReactDOM.render(
|
||||
<button onClick={() => clicks++}>click me</button>,
|
||||
container,
|
||||
);
|
||||
|
||||
// Now we unmount the component, as if caused by a non-React event handler
|
||||
// for the same click we're about to simulate, like closing a layer:
|
||||
ReactDOM.unmountComponentAtNode(container);
|
||||
ReactTestUtils.SimulateNative.click(button);
|
||||
// Now we unmount the component, as if caused by a non-React event handler
|
||||
// for the same click we're about to simulate, like closing a layer:
|
||||
ReactDOM.unmountComponentAtNode(container);
|
||||
button.click();
|
||||
|
||||
// Since the tree is unmounted, we don't dispatch the click event.
|
||||
expect(clicks).toBe(0);
|
||||
// Since the tree is unmounted, we don't dispatch the click event.
|
||||
expect(clicks).toBe(0);
|
||||
} finally {
|
||||
document.body.removeChild(container);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -78,7 +78,7 @@ describe('ReactIdentity', () => {
|
||||
|
||||
const instance = ReactDOM.render(<Wrapper />, container);
|
||||
const span = instance.refs.span;
|
||||
expect(ReactDOM.findDOMNode(span)).not.toBe(null);
|
||||
expect(span).not.toBe(null);
|
||||
}
|
||||
|
||||
it('should allow any character as a key, in a detached parent', () => {
|
||||
|
||||
@@ -91,25 +91,25 @@ describe('ReactMount', () => {
|
||||
}
|
||||
}
|
||||
|
||||
expect(mockMount.mock.calls.length).toBe(0);
|
||||
expect(mockUnmount.mock.calls.length).toBe(0);
|
||||
expect(mockMount).toHaveBeenCalledTimes(0);
|
||||
expect(mockUnmount).toHaveBeenCalledTimes(0);
|
||||
|
||||
ReactDOM.render(<Component text="orange" key="A" />, container);
|
||||
expect(container.firstChild.innerHTML).toBe('orange');
|
||||
expect(mockMount.mock.calls.length).toBe(1);
|
||||
expect(mockUnmount.mock.calls.length).toBe(0);
|
||||
expect(mockMount).toHaveBeenCalledTimes(1);
|
||||
expect(mockUnmount).toHaveBeenCalledTimes(0);
|
||||
|
||||
// If we change the key, the component is unmounted and remounted
|
||||
ReactDOM.render(<Component text="green" key="B" />, container);
|
||||
expect(container.firstChild.innerHTML).toBe('green');
|
||||
expect(mockMount.mock.calls.length).toBe(2);
|
||||
expect(mockUnmount.mock.calls.length).toBe(1);
|
||||
expect(mockMount).toHaveBeenCalledTimes(2);
|
||||
expect(mockUnmount).toHaveBeenCalledTimes(1);
|
||||
|
||||
// But if we don't change the key, the component instance is reused
|
||||
ReactDOM.render(<Component text="blue" key="B" />, container);
|
||||
expect(container.firstChild.innerHTML).toBe('blue');
|
||||
expect(mockMount.mock.calls.length).toBe(2);
|
||||
expect(mockUnmount.mock.calls.length).toBe(1);
|
||||
expect(mockMount).toHaveBeenCalledTimes(2);
|
||||
expect(mockUnmount).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should reuse markup if rendering to the same target twice', () => {
|
||||
|
||||
@@ -36,9 +36,9 @@ describe('ReactMultiChild', () => {
|
||||
}
|
||||
}
|
||||
|
||||
expect(mockMount.mock.calls.length).toBe(0);
|
||||
expect(mockUpdate.mock.calls.length).toBe(0);
|
||||
expect(mockUnmount.mock.calls.length).toBe(0);
|
||||
expect(mockMount).toHaveBeenCalledTimes(0);
|
||||
expect(mockUpdate).toHaveBeenCalledTimes(0);
|
||||
expect(mockUnmount).toHaveBeenCalledTimes(0);
|
||||
|
||||
ReactDOM.render(
|
||||
<div>
|
||||
@@ -47,9 +47,9 @@ describe('ReactMultiChild', () => {
|
||||
container,
|
||||
);
|
||||
|
||||
expect(mockMount.mock.calls.length).toBe(1);
|
||||
expect(mockUpdate.mock.calls.length).toBe(0);
|
||||
expect(mockUnmount.mock.calls.length).toBe(0);
|
||||
expect(mockMount).toHaveBeenCalledTimes(1);
|
||||
expect(mockUpdate).toHaveBeenCalledTimes(0);
|
||||
expect(mockUnmount).toHaveBeenCalledTimes(0);
|
||||
|
||||
ReactDOM.render(
|
||||
<div>
|
||||
@@ -58,9 +58,9 @@ describe('ReactMultiChild', () => {
|
||||
container,
|
||||
);
|
||||
|
||||
expect(mockMount.mock.calls.length).toBe(1);
|
||||
expect(mockUpdate.mock.calls.length).toBe(1);
|
||||
expect(mockUnmount.mock.calls.length).toBe(0);
|
||||
expect(mockMount).toHaveBeenCalledTimes(1);
|
||||
expect(mockUpdate).toHaveBeenCalledTimes(1);
|
||||
expect(mockUnmount).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it('should replace children with different constructors', () => {
|
||||
@@ -77,8 +77,8 @@ describe('ReactMultiChild', () => {
|
||||
}
|
||||
}
|
||||
|
||||
expect(mockMount.mock.calls.length).toBe(0);
|
||||
expect(mockUnmount.mock.calls.length).toBe(0);
|
||||
expect(mockMount).toHaveBeenCalledTimes(0);
|
||||
expect(mockUnmount).toHaveBeenCalledTimes(0);
|
||||
|
||||
ReactDOM.render(
|
||||
<div>
|
||||
@@ -87,8 +87,8 @@ describe('ReactMultiChild', () => {
|
||||
container,
|
||||
);
|
||||
|
||||
expect(mockMount.mock.calls.length).toBe(1);
|
||||
expect(mockUnmount.mock.calls.length).toBe(0);
|
||||
expect(mockMount).toHaveBeenCalledTimes(1);
|
||||
expect(mockUnmount).toHaveBeenCalledTimes(0);
|
||||
|
||||
ReactDOM.render(
|
||||
<div>
|
||||
@@ -97,8 +97,8 @@ describe('ReactMultiChild', () => {
|
||||
container,
|
||||
);
|
||||
|
||||
expect(mockMount.mock.calls.length).toBe(1);
|
||||
expect(mockUnmount.mock.calls.length).toBe(1);
|
||||
expect(mockMount).toHaveBeenCalledTimes(1);
|
||||
expect(mockUnmount).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should NOT replace children with different owners', () => {
|
||||
@@ -121,13 +121,13 @@ describe('ReactMultiChild', () => {
|
||||
}
|
||||
}
|
||||
|
||||
expect(mockMount.mock.calls.length).toBe(0);
|
||||
expect(mockUnmount.mock.calls.length).toBe(0);
|
||||
expect(mockMount).toHaveBeenCalledTimes(0);
|
||||
expect(mockUnmount).toHaveBeenCalledTimes(0);
|
||||
|
||||
ReactDOM.render(<WrapperComponent />, container);
|
||||
|
||||
expect(mockMount.mock.calls.length).toBe(1);
|
||||
expect(mockUnmount.mock.calls.length).toBe(0);
|
||||
expect(mockMount).toHaveBeenCalledTimes(1);
|
||||
expect(mockUnmount).toHaveBeenCalledTimes(0);
|
||||
|
||||
ReactDOM.render(
|
||||
<WrapperComponent>
|
||||
@@ -136,8 +136,8 @@ describe('ReactMultiChild', () => {
|
||||
container,
|
||||
);
|
||||
|
||||
expect(mockMount.mock.calls.length).toBe(1);
|
||||
expect(mockUnmount.mock.calls.length).toBe(0);
|
||||
expect(mockMount).toHaveBeenCalledTimes(1);
|
||||
expect(mockUnmount).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it('should replace children with different keys', () => {
|
||||
@@ -154,8 +154,8 @@ describe('ReactMultiChild', () => {
|
||||
}
|
||||
}
|
||||
|
||||
expect(mockMount.mock.calls.length).toBe(0);
|
||||
expect(mockUnmount.mock.calls.length).toBe(0);
|
||||
expect(mockMount).toHaveBeenCalledTimes(0);
|
||||
expect(mockUnmount).toHaveBeenCalledTimes(0);
|
||||
|
||||
ReactDOM.render(
|
||||
<div>
|
||||
@@ -164,8 +164,8 @@ describe('ReactMultiChild', () => {
|
||||
container,
|
||||
);
|
||||
|
||||
expect(mockMount.mock.calls.length).toBe(1);
|
||||
expect(mockUnmount.mock.calls.length).toBe(0);
|
||||
expect(mockMount).toHaveBeenCalledTimes(1);
|
||||
expect(mockUnmount).toHaveBeenCalledTimes(0);
|
||||
|
||||
ReactDOM.render(
|
||||
<div>
|
||||
@@ -174,8 +174,8 @@ describe('ReactMultiChild', () => {
|
||||
container,
|
||||
);
|
||||
|
||||
expect(mockMount.mock.calls.length).toBe(2);
|
||||
expect(mockUnmount.mock.calls.length).toBe(1);
|
||||
expect(mockMount).toHaveBeenCalledTimes(2);
|
||||
expect(mockUnmount).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should warn for duplicated array keys with component stack info', () => {
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
'use strict';
|
||||
|
||||
let React;
|
||||
let ReactCallReturn;
|
||||
let ReactDOMServer;
|
||||
let PropTypes;
|
||||
|
||||
@@ -23,7 +22,6 @@ describe('ReactDOMServer', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
React = require('react');
|
||||
ReactCallReturn = require('react-call-return');
|
||||
PropTypes = require('prop-types');
|
||||
ReactDOMServer = require('react-dom/server');
|
||||
});
|
||||
@@ -623,25 +621,6 @@ describe('ReactDOMServer', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw rendering call/return on the server', () => {
|
||||
expect(() => {
|
||||
ReactDOMServer.renderToString(
|
||||
<div>{ReactCallReturn.unstable_createReturn(42)}</div>,
|
||||
);
|
||||
}).toThrow(
|
||||
'The experimental Call and Return types are not currently supported by the server renderer.',
|
||||
);
|
||||
expect(() => {
|
||||
ReactDOMServer.renderToString(
|
||||
<div>
|
||||
{ReactCallReturn.unstable_createCall(null, function() {}, {})}
|
||||
</div>,
|
||||
);
|
||||
}).toThrow(
|
||||
'The experimental Call and Return types are not currently supported by the server renderer.',
|
||||
);
|
||||
});
|
||||
|
||||
it('should warn when server rendering a class with a render method that does not extend React.Component', () => {
|
||||
class ClassWithRenderNotExtended {
|
||||
render() {
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
let React;
|
||||
let ReactDOM;
|
||||
let ReactDOMServer;
|
||||
let ReactTestUtils;
|
||||
|
||||
// These tests rely both on ReactDOMServer and ReactDOM.
|
||||
// If a test only needs ReactDOMServer, put it in ReactServerRendering-test instead.
|
||||
@@ -21,7 +20,6 @@ describe('ReactDOMServerHydration', () => {
|
||||
jest.resetModules();
|
||||
React = require('react');
|
||||
ReactDOM = require('react-dom');
|
||||
ReactTestUtils = require('react-dom/test-utils');
|
||||
ReactDOMServer = require('react-dom/server');
|
||||
});
|
||||
|
||||
@@ -48,67 +46,73 @@ describe('ReactDOMServerHydration', () => {
|
||||
}
|
||||
|
||||
const element = document.createElement('div');
|
||||
ReactDOM.render(<TestComponent />, element);
|
||||
document.body.appendChild(element);
|
||||
try {
|
||||
ReactDOM.render(<TestComponent />, element);
|
||||
|
||||
let lastMarkup = element.innerHTML;
|
||||
let lastMarkup = element.innerHTML;
|
||||
|
||||
// Exercise the update path. Markup should not change,
|
||||
// but some lifecycle methods should be run again.
|
||||
ReactDOM.render(<TestComponent name="x" />, element);
|
||||
expect(mountCount).toEqual(1);
|
||||
// Exercise the update path. Markup should not change,
|
||||
// but some lifecycle methods should be run again.
|
||||
ReactDOM.render(<TestComponent name="x" />, element);
|
||||
expect(mountCount).toEqual(1);
|
||||
|
||||
// Unmount and remount. We should get another mount event and
|
||||
// we should get different markup, as the IDs are unique each time.
|
||||
ReactDOM.unmountComponentAtNode(element);
|
||||
expect(element.innerHTML).toEqual('');
|
||||
ReactDOM.render(<TestComponent name="x" />, element);
|
||||
expect(mountCount).toEqual(2);
|
||||
expect(element.innerHTML).not.toEqual(lastMarkup);
|
||||
// Unmount and remount. We should get another mount event and
|
||||
// we should get different markup, as the IDs are unique each time.
|
||||
ReactDOM.unmountComponentAtNode(element);
|
||||
expect(element.innerHTML).toEqual('');
|
||||
ReactDOM.render(<TestComponent name="x" />, element);
|
||||
expect(mountCount).toEqual(2);
|
||||
expect(element.innerHTML).not.toEqual(lastMarkup);
|
||||
|
||||
// Now kill the node and render it on top of server-rendered markup, as if
|
||||
// we used server rendering. We should mount again, but the markup should
|
||||
// be unchanged. We will append a sentinel at the end of innerHTML to be
|
||||
// sure that innerHTML was not changed.
|
||||
ReactDOM.unmountComponentAtNode(element);
|
||||
expect(element.innerHTML).toEqual('');
|
||||
// Now kill the node and render it on top of server-rendered markup, as if
|
||||
// we used server rendering. We should mount again, but the markup should
|
||||
// be unchanged. We will append a sentinel at the end of innerHTML to be
|
||||
// sure that innerHTML was not changed.
|
||||
ReactDOM.unmountComponentAtNode(element);
|
||||
expect(element.innerHTML).toEqual('');
|
||||
|
||||
lastMarkup = ReactDOMServer.renderToString(<TestComponent name="x" />);
|
||||
element.innerHTML = lastMarkup;
|
||||
lastMarkup = ReactDOMServer.renderToString(<TestComponent name="x" />);
|
||||
element.innerHTML = lastMarkup;
|
||||
|
||||
let instance;
|
||||
let instance;
|
||||
|
||||
expect(() => {
|
||||
instance = ReactDOM.render(<TestComponent name="x" />, element);
|
||||
}).toLowPriorityWarnDev(
|
||||
'render(): Calling ReactDOM.render() to hydrate server-rendered markup ' +
|
||||
'will stop working in React v17. Replace the ReactDOM.render() call ' +
|
||||
'with ReactDOM.hydrate() if you want React to attach to the server HTML.',
|
||||
);
|
||||
expect(mountCount).toEqual(3);
|
||||
expect(element.innerHTML).toBe(lastMarkup);
|
||||
expect(() => {
|
||||
instance = ReactDOM.render(<TestComponent name="x" />, element);
|
||||
}).toLowPriorityWarnDev(
|
||||
'render(): Calling ReactDOM.render() to hydrate server-rendered markup ' +
|
||||
'will stop working in React v17. Replace the ReactDOM.render() call ' +
|
||||
'with ReactDOM.hydrate() if you want React to attach to the server HTML.',
|
||||
);
|
||||
expect(mountCount).toEqual(3);
|
||||
expect(element.innerHTML).toBe(lastMarkup);
|
||||
|
||||
// Ensure the events system works after mount into server markup
|
||||
expect(numClicks).toEqual(0);
|
||||
ReactTestUtils.Simulate.click(ReactDOM.findDOMNode(instance.refs.span));
|
||||
expect(numClicks).toEqual(1);
|
||||
// Ensure the events system works after mount into server markup
|
||||
expect(numClicks).toEqual(0);
|
||||
|
||||
ReactDOM.unmountComponentAtNode(element);
|
||||
expect(element.innerHTML).toEqual('');
|
||||
instance.refs.span.click();
|
||||
expect(numClicks).toEqual(1);
|
||||
|
||||
// Now simulate a situation where the app is not idempotent. React should
|
||||
// warn but do the right thing.
|
||||
element.innerHTML = lastMarkup;
|
||||
expect(() => {
|
||||
instance = ReactDOM.render(<TestComponent name="y" />, element);
|
||||
}).toWarnDev('Text content did not match. Server: "x" Client: "y"');
|
||||
expect(mountCount).toEqual(4);
|
||||
expect(element.innerHTML.length > 0).toBe(true);
|
||||
expect(element.innerHTML).not.toEqual(lastMarkup);
|
||||
ReactDOM.unmountComponentAtNode(element);
|
||||
expect(element.innerHTML).toEqual('');
|
||||
|
||||
// Ensure the events system works after markup mismatch.
|
||||
expect(numClicks).toEqual(1);
|
||||
ReactTestUtils.Simulate.click(ReactDOM.findDOMNode(instance.refs.span));
|
||||
expect(numClicks).toEqual(2);
|
||||
// Now simulate a situation where the app is not idempotent. React should
|
||||
// warn but do the right thing.
|
||||
element.innerHTML = lastMarkup;
|
||||
expect(() => {
|
||||
instance = ReactDOM.render(<TestComponent name="y" />, element);
|
||||
}).toWarnDev('Text content did not match. Server: "x" Client: "y"');
|
||||
expect(mountCount).toEqual(4);
|
||||
expect(element.innerHTML.length > 0).toBe(true);
|
||||
expect(element.innerHTML).not.toEqual(lastMarkup);
|
||||
|
||||
// Ensure the events system works after markup mismatch.
|
||||
expect(numClicks).toEqual(1);
|
||||
instance.refs.span.click();
|
||||
expect(numClicks).toEqual(2);
|
||||
} finally {
|
||||
document.body.removeChild(element);
|
||||
}
|
||||
});
|
||||
|
||||
it('should have the correct mounting behavior (new hydrate API)', () => {
|
||||
@@ -134,59 +138,64 @@ describe('ReactDOMServerHydration', () => {
|
||||
}
|
||||
|
||||
const element = document.createElement('div');
|
||||
ReactDOM.render(<TestComponent />, element);
|
||||
document.body.appendChild(element);
|
||||
try {
|
||||
ReactDOM.render(<TestComponent />, element);
|
||||
|
||||
let lastMarkup = element.innerHTML;
|
||||
let lastMarkup = element.innerHTML;
|
||||
|
||||
// Exercise the update path. Markup should not change,
|
||||
// but some lifecycle methods should be run again.
|
||||
ReactDOM.render(<TestComponent name="x" />, element);
|
||||
expect(mountCount).toEqual(1);
|
||||
// Exercise the update path. Markup should not change,
|
||||
// but some lifecycle methods should be run again.
|
||||
ReactDOM.render(<TestComponent name="x" />, element);
|
||||
expect(mountCount).toEqual(1);
|
||||
|
||||
// Unmount and remount. We should get another mount event and
|
||||
// we should get different markup, as the IDs are unique each time.
|
||||
ReactDOM.unmountComponentAtNode(element);
|
||||
expect(element.innerHTML).toEqual('');
|
||||
ReactDOM.render(<TestComponent name="x" />, element);
|
||||
expect(mountCount).toEqual(2);
|
||||
expect(element.innerHTML).not.toEqual(lastMarkup);
|
||||
// Unmount and remount. We should get another mount event and
|
||||
// we should get different markup, as the IDs are unique each time.
|
||||
ReactDOM.unmountComponentAtNode(element);
|
||||
expect(element.innerHTML).toEqual('');
|
||||
ReactDOM.render(<TestComponent name="x" />, element);
|
||||
expect(mountCount).toEqual(2);
|
||||
expect(element.innerHTML).not.toEqual(lastMarkup);
|
||||
|
||||
// Now kill the node and render it on top of server-rendered markup, as if
|
||||
// we used server rendering. We should mount again, but the markup should
|
||||
// be unchanged. We will append a sentinel at the end of innerHTML to be
|
||||
// sure that innerHTML was not changed.
|
||||
ReactDOM.unmountComponentAtNode(element);
|
||||
expect(element.innerHTML).toEqual('');
|
||||
// Now kill the node and render it on top of server-rendered markup, as if
|
||||
// we used server rendering. We should mount again, but the markup should
|
||||
// be unchanged. We will append a sentinel at the end of innerHTML to be
|
||||
// sure that innerHTML was not changed.
|
||||
ReactDOM.unmountComponentAtNode(element);
|
||||
expect(element.innerHTML).toEqual('');
|
||||
|
||||
lastMarkup = ReactDOMServer.renderToString(<TestComponent name="x" />);
|
||||
element.innerHTML = lastMarkup;
|
||||
lastMarkup = ReactDOMServer.renderToString(<TestComponent name="x" />);
|
||||
element.innerHTML = lastMarkup;
|
||||
|
||||
let instance = ReactDOM.hydrate(<TestComponent name="x" />, element);
|
||||
expect(mountCount).toEqual(3);
|
||||
expect(element.innerHTML).toBe(lastMarkup);
|
||||
let instance = ReactDOM.hydrate(<TestComponent name="x" />, element);
|
||||
expect(mountCount).toEqual(3);
|
||||
expect(element.innerHTML).toBe(lastMarkup);
|
||||
|
||||
// Ensure the events system works after mount into server markup
|
||||
expect(numClicks).toEqual(0);
|
||||
ReactTestUtils.Simulate.click(ReactDOM.findDOMNode(instance.refs.span));
|
||||
expect(numClicks).toEqual(1);
|
||||
// Ensure the events system works after mount into server markup
|
||||
expect(numClicks).toEqual(0);
|
||||
instance.refs.span.click();
|
||||
expect(numClicks).toEqual(1);
|
||||
|
||||
ReactDOM.unmountComponentAtNode(element);
|
||||
expect(element.innerHTML).toEqual('');
|
||||
ReactDOM.unmountComponentAtNode(element);
|
||||
expect(element.innerHTML).toEqual('');
|
||||
|
||||
// Now simulate a situation where the app is not idempotent. React should
|
||||
// warn but do the right thing.
|
||||
element.innerHTML = lastMarkup;
|
||||
expect(() => {
|
||||
instance = ReactDOM.hydrate(<TestComponent name="y" />, element);
|
||||
}).toWarnDev('Text content did not match. Server: "x" Client: "y"');
|
||||
expect(mountCount).toEqual(4);
|
||||
expect(element.innerHTML.length > 0).toBe(true);
|
||||
expect(element.innerHTML).not.toEqual(lastMarkup);
|
||||
// Now simulate a situation where the app is not idempotent. React should
|
||||
// warn but do the right thing.
|
||||
element.innerHTML = lastMarkup;
|
||||
expect(() => {
|
||||
instance = ReactDOM.hydrate(<TestComponent name="y" />, element);
|
||||
}).toWarnDev('Text content did not match. Server: "x" Client: "y"');
|
||||
expect(mountCount).toEqual(4);
|
||||
expect(element.innerHTML.length > 0).toBe(true);
|
||||
expect(element.innerHTML).not.toEqual(lastMarkup);
|
||||
|
||||
// Ensure the events system works after markup mismatch.
|
||||
expect(numClicks).toEqual(1);
|
||||
ReactTestUtils.Simulate.click(ReactDOM.findDOMNode(instance.refs.span));
|
||||
expect(numClicks).toEqual(2);
|
||||
// Ensure the events system works after markup mismatch.
|
||||
expect(numClicks).toEqual(1);
|
||||
instance.refs.span.click();
|
||||
expect(numClicks).toEqual(2);
|
||||
} finally {
|
||||
document.body.removeChild(element);
|
||||
}
|
||||
});
|
||||
|
||||
// We have a polyfill for autoFocus on the client, but we intentionally don't
|
||||
|
||||
@@ -280,17 +280,16 @@ describe('ReactTestUtils', () => {
|
||||
};
|
||||
spyOnDevAndProd(obj, 'handler').and.callThrough();
|
||||
const container = document.createElement('div');
|
||||
const instance = ReactDOM.render(
|
||||
const node = ReactDOM.render(
|
||||
<input type="text" onChange={obj.handler} />,
|
||||
container,
|
||||
);
|
||||
|
||||
const node = ReactDOM.findDOMNode(instance);
|
||||
node.value = 'giraffe';
|
||||
ReactTestUtils.Simulate.change(node);
|
||||
|
||||
expect(obj.handler).toHaveBeenCalledWith(
|
||||
jasmine.objectContaining({target: node}),
|
||||
expect.objectContaining({target: node}),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -321,12 +320,12 @@ describe('ReactTestUtils', () => {
|
||||
container,
|
||||
);
|
||||
|
||||
const node = ReactDOM.findDOMNode(instance.refs.input);
|
||||
const node = instance.refs.input;
|
||||
node.value = 'zebra';
|
||||
ReactTestUtils.Simulate.change(node);
|
||||
|
||||
expect(obj.handler).toHaveBeenCalledWith(
|
||||
jasmine.objectContaining({target: node}),
|
||||
expect.objectContaining({target: node}),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -337,7 +336,7 @@ describe('ReactTestUtils', () => {
|
||||
}
|
||||
}
|
||||
|
||||
const handler = jasmine.createSpy('spy');
|
||||
const handler = jest.fn().mockName('spy');
|
||||
const shallowRenderer = createRenderer();
|
||||
const result = shallowRenderer.render(
|
||||
<SomeComponent handleClick={handler} />,
|
||||
@@ -358,7 +357,7 @@ describe('ReactTestUtils', () => {
|
||||
}
|
||||
}
|
||||
|
||||
const handler = jasmine.createSpy('spy');
|
||||
const handler = jest.fn().mockName('spy');
|
||||
const container = document.createElement('div');
|
||||
const instance = ReactDOM.render(
|
||||
<SomeComponent handleClick={handler} />,
|
||||
@@ -394,7 +393,7 @@ describe('ReactTestUtils', () => {
|
||||
|
||||
it('should set the type of the event', () => {
|
||||
let event;
|
||||
const stub = jest.genMockFn().mockImplementation(e => {
|
||||
const stub = jest.fn().mockImplementation(e => {
|
||||
e.persist();
|
||||
event = e;
|
||||
});
|
||||
@@ -431,7 +430,7 @@ describe('ReactTestUtils', () => {
|
||||
ReactTestUtils.Simulate.change(input);
|
||||
|
||||
expect(onChange).toHaveBeenCalledWith(
|
||||
jasmine.objectContaining({target: input}),
|
||||
expect.objectContaining({target: input}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -35,6 +35,7 @@ Array [
|
||||
"ended",
|
||||
"error",
|
||||
"focus",
|
||||
"gotPointerCapture",
|
||||
"input",
|
||||
"invalid",
|
||||
"keyDown",
|
||||
@@ -44,6 +45,7 @@ Array [
|
||||
"loadStart",
|
||||
"loadedData",
|
||||
"loadedMetadata",
|
||||
"lostPointerCapture",
|
||||
"mouseDown",
|
||||
"mouseEnter",
|
||||
"mouseLeave",
|
||||
@@ -55,6 +57,14 @@ Array [
|
||||
"pause",
|
||||
"play",
|
||||
"playing",
|
||||
"pointerCancel",
|
||||
"pointerDown",
|
||||
"pointerEnter",
|
||||
"pointerLeave",
|
||||
"pointerMove",
|
||||
"pointerOut",
|
||||
"pointerOver",
|
||||
"pointerUp",
|
||||
"progress",
|
||||
"rateChange",
|
||||
"reset",
|
||||
|
||||
100
packages/react-dom/src/__tests__/refs-test.js
vendored
100
packages/react-dom/src/__tests__/refs-test.js
vendored
@@ -10,6 +10,7 @@
|
||||
'use strict';
|
||||
|
||||
let React = require('react');
|
||||
let ReactDOM = require('react-dom');
|
||||
let ReactTestUtils = require('react-dom/test-utils');
|
||||
|
||||
/**
|
||||
@@ -81,24 +82,6 @@ class TestRefsComponent extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a TestRefsComponent and ensure that the main refs are wired up.
|
||||
*/
|
||||
const renderTestRefsComponent = function() {
|
||||
const testRefsComponent = ReactTestUtils.renderIntoDocument(
|
||||
<TestRefsComponent />,
|
||||
);
|
||||
expect(testRefsComponent instanceof TestRefsComponent).toBe(true);
|
||||
|
||||
const generalContainer = testRefsComponent.refs.myContainer;
|
||||
expect(generalContainer instanceof GeneralContainerComponent).toBe(true);
|
||||
|
||||
const counter = testRefsComponent.refs.myCounter;
|
||||
expect(counter instanceof ClickCounter).toBe(true);
|
||||
|
||||
return testRefsComponent;
|
||||
};
|
||||
|
||||
const expectClickLogsLengthToBe = function(instance, length) {
|
||||
const clickLogs = ReactTestUtils.scryRenderedDOMComponentsWithClass(
|
||||
instance,
|
||||
@@ -109,12 +92,40 @@ const expectClickLogsLengthToBe = function(instance, length) {
|
||||
};
|
||||
|
||||
describe('reactiverefs', () => {
|
||||
let container;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
React = require('react');
|
||||
ReactDOM = require('react-dom');
|
||||
ReactTestUtils = require('react-dom/test-utils');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (container) {
|
||||
document.body.removeChild(container);
|
||||
container = null;
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Render a TestRefsComponent and ensure that the main refs are wired up.
|
||||
*/
|
||||
const renderTestRefsComponent = function() {
|
||||
container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
const testRefsComponent = ReactDOM.render(<TestRefsComponent />, container);
|
||||
expect(testRefsComponent instanceof TestRefsComponent).toBe(true);
|
||||
|
||||
const generalContainer = testRefsComponent.refs.myContainer;
|
||||
expect(generalContainer instanceof GeneralContainerComponent).toBe(true);
|
||||
|
||||
const counter = testRefsComponent.refs.myCounter;
|
||||
expect(counter instanceof ClickCounter).toBe(true);
|
||||
|
||||
return testRefsComponent;
|
||||
};
|
||||
|
||||
/**
|
||||
* Ensure that for every click log there is a corresponding ref (from the
|
||||
* perspective of the injected ClickCounter component.
|
||||
@@ -129,18 +140,18 @@ describe('reactiverefs', () => {
|
||||
expectClickLogsLengthToBe(testRefsComponent, 1);
|
||||
|
||||
// After clicking the reset, there should still only be one click log ref.
|
||||
ReactTestUtils.Simulate.click(testRefsComponent.refs.resetDiv);
|
||||
testRefsComponent.refs.resetDiv.click();
|
||||
expectClickLogsLengthToBe(testRefsComponent, 1);
|
||||
|
||||
// Begin incrementing clicks (and therefore refs).
|
||||
ReactTestUtils.Simulate.click(clickIncrementer);
|
||||
clickIncrementer.click();
|
||||
expectClickLogsLengthToBe(testRefsComponent, 2);
|
||||
|
||||
ReactTestUtils.Simulate.click(clickIncrementer);
|
||||
clickIncrementer.click();
|
||||
expectClickLogsLengthToBe(testRefsComponent, 3);
|
||||
|
||||
// Now reset again
|
||||
ReactTestUtils.Simulate.click(testRefsComponent.refs.resetDiv);
|
||||
testRefsComponent.refs.resetDiv.click();
|
||||
expectClickLogsLengthToBe(testRefsComponent, 1);
|
||||
});
|
||||
});
|
||||
@@ -168,6 +179,7 @@ describe('ref swapping', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
React = require('react');
|
||||
ReactDOM = require('react-dom');
|
||||
ReactTestUtils = require('react-dom/test-utils');
|
||||
|
||||
RefHopsAround = class extends React.Component {
|
||||
@@ -293,7 +305,6 @@ describe('ref swapping', () => {
|
||||
|
||||
describe('root level refs', () => {
|
||||
it('attaches and detaches root refs', () => {
|
||||
const ReactDOM = require('react-dom');
|
||||
let inst = null;
|
||||
|
||||
// host node
|
||||
@@ -401,3 +412,46 @@ describe('creating element with ref in constructor', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('strings refs across renderers', () => {
|
||||
it('does not break', () => {
|
||||
class Parent extends React.Component {
|
||||
render() {
|
||||
// This component owns both refs.
|
||||
return (
|
||||
<Indirection
|
||||
child1={<div ref="child1" />}
|
||||
child2={<div ref="child2" />}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class Indirection extends React.Component {
|
||||
componentDidUpdate() {
|
||||
// One ref is being rendered later using another renderer copy.
|
||||
jest.resetModules();
|
||||
const AnotherCopyOfReactDOM = require('react-dom');
|
||||
AnotherCopyOfReactDOM.render(this.props.child2, div2);
|
||||
}
|
||||
render() {
|
||||
// The other one is being rendered directly.
|
||||
return this.props.child1;
|
||||
}
|
||||
}
|
||||
|
||||
const div1 = document.createElement('div');
|
||||
const div2 = document.createElement('div');
|
||||
const inst = ReactDOM.render(<Parent />, div1);
|
||||
// Only the first ref has rendered yet.
|
||||
expect(inst.refs.child1.tagName).toBe('DIV');
|
||||
expect(inst.refs.child1).toBe(div1.firstChild);
|
||||
|
||||
// Now both refs should be rendered.
|
||||
ReactDOM.render(<Parent />, div1);
|
||||
expect(inst.refs.child1.tagName).toBe('DIV');
|
||||
expect(inst.refs.child1).toBe(div1.firstChild);
|
||||
expect(inst.refs.child2.tagName).toBe('DIV');
|
||||
expect(inst.refs.child2).toBe(div2.firstChild);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -53,9 +53,7 @@ module.exports = function(initModules) {
|
||||
console.error.calls.count() !== 0
|
||||
) {
|
||||
console.log(
|
||||
`We expected ${
|
||||
count
|
||||
} warning(s), but saw ${console.error.calls.count()} warning(s).`,
|
||||
`We expected ${count} warning(s), but saw ${console.error.calls.count()} warning(s).`,
|
||||
);
|
||||
if (console.error.calls.count() > 0) {
|
||||
console.log(`We saw these warnings:`);
|
||||
@@ -65,7 +63,7 @@ module.exports = function(initModules) {
|
||||
}
|
||||
}
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(count);
|
||||
expect(console.error).toHaveBeenCalledTimes(count);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -237,9 +235,7 @@ module.exports = function(initModules) {
|
||||
testFn(clientCleanRender));
|
||||
it(`renders ${desc} with client render on top of good server markup`, () =>
|
||||
testFn(clientRenderOnServerString));
|
||||
it(`renders ${
|
||||
desc
|
||||
} with client render on top of bad server markup`, async () => {
|
||||
it(`renders ${desc} with client render on top of bad server markup`, async () => {
|
||||
try {
|
||||
await testFn(clientRenderOnBadMarkup);
|
||||
} catch (x) {
|
||||
|
||||
562
packages/react-dom/src/client/ReactDOM.js
vendored
562
packages/react-dom/src/client/ReactDOM.js
vendored
@@ -14,14 +14,14 @@ import type {
|
||||
FiberRoot,
|
||||
Batch as FiberRootBatch,
|
||||
} from 'react-reconciler/src/ReactFiberRoot';
|
||||
import type {Container} from './ReactDOMHostConfig';
|
||||
|
||||
import '../shared/checkReact';
|
||||
import './ReactDOMClientInjection';
|
||||
|
||||
import ReactFiberReconciler from 'react-reconciler';
|
||||
// TODO: direct imports like some-package/src/* are bad. Fix me.
|
||||
import * as DOMRenderer from 'react-reconciler/inline.dom';
|
||||
import * as ReactPortal from 'shared/ReactPortal';
|
||||
import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment';
|
||||
import {canUseDOM} from 'shared/ExecutionEnvironment';
|
||||
import * as ReactGenericBatching from 'events/ReactGenericBatching';
|
||||
import * as ReactControlledComponent from 'events/ReactControlledComponent';
|
||||
import * as EventPluginHub from 'events/EventPluginHub';
|
||||
@@ -29,58 +29,35 @@ import * as EventPluginRegistry from 'events/EventPluginRegistry';
|
||||
import * as EventPropagators from 'events/EventPropagators';
|
||||
import * as ReactInstanceMap from 'shared/ReactInstanceMap';
|
||||
import ReactVersion from 'shared/ReactVersion';
|
||||
import * as ReactDOMFrameScheduling from 'shared/ReactDOMFrameScheduling';
|
||||
import {ReactCurrentOwner} from 'shared/ReactGlobalSharedState';
|
||||
import getComponentName from 'shared/getComponentName';
|
||||
import invariant from 'fbjs/lib/invariant';
|
||||
import invariant from 'shared/invariant';
|
||||
import lowPriorityWarning from 'shared/lowPriorityWarning';
|
||||
import warning from 'fbjs/lib/warning';
|
||||
import warning from 'shared/warning';
|
||||
|
||||
import * as ReactDOMComponentTree from './ReactDOMComponentTree';
|
||||
import * as ReactDOMFiberComponent from './ReactDOMFiberComponent';
|
||||
import * as ReactInputSelection from './ReactInputSelection';
|
||||
import setTextContent from './setTextContent';
|
||||
import validateDOMNesting from './validateDOMNesting';
|
||||
import * as ReactBrowserEventEmitter from '../events/ReactBrowserEventEmitter';
|
||||
import * as ReactDOMEventListener from '../events/ReactDOMEventListener';
|
||||
import {getChildNamespace} from '../shared/DOMNamespaces';
|
||||
import {
|
||||
ELEMENT_NODE,
|
||||
TEXT_NODE,
|
||||
COMMENT_NODE,
|
||||
DOCUMENT_NODE,
|
||||
DOCUMENT_FRAGMENT_NODE,
|
||||
} from '../shared/HTMLNodeType';
|
||||
import {ROOT_ATTRIBUTE_NAME} from '../shared/DOMProperty';
|
||||
const {
|
||||
createElement,
|
||||
createTextNode,
|
||||
setInitialProperties,
|
||||
diffProperties,
|
||||
updateProperties,
|
||||
diffHydratedProperties,
|
||||
diffHydratedText,
|
||||
warnForUnmatchedText,
|
||||
warnForDeletedHydratableElement,
|
||||
warnForDeletedHydratableText,
|
||||
warnForInsertedHydratedElement,
|
||||
warnForInsertedHydratedText,
|
||||
} = ReactDOMFiberComponent;
|
||||
const {updatedAncestorInfo} = validateDOMNesting;
|
||||
const {precacheFiberNode, updateFiberProps} = ReactDOMComponentTree;
|
||||
|
||||
let SUPPRESS_HYDRATION_WARNING;
|
||||
let topLevelUpdateWarnings;
|
||||
let warnOnInvalidCallback;
|
||||
let didWarnAboutUnstableCreatePortal = false;
|
||||
|
||||
if (__DEV__) {
|
||||
SUPPRESS_HYDRATION_WARNING = 'suppressHydrationWarning';
|
||||
if (
|
||||
typeof Map !== 'function' ||
|
||||
// $FlowIssue Flow incorrectly thinks Map has no prototype
|
||||
Map.prototype == null ||
|
||||
typeof Map.prototype.forEach !== 'function' ||
|
||||
typeof Set !== 'function' ||
|
||||
// $FlowIssue Flow incorrectly thinks Set has no prototype
|
||||
Set.prototype == null ||
|
||||
typeof Set.prototype.clear !== 'function' ||
|
||||
typeof Set.prototype.forEach !== 'function'
|
||||
@@ -157,26 +134,6 @@ type DOMContainer =
|
||||
_reactRootContainer: ?Root,
|
||||
});
|
||||
|
||||
type Container = Element | Document;
|
||||
type Props = {
|
||||
autoFocus?: boolean,
|
||||
children?: mixed,
|
||||
hidden?: boolean,
|
||||
suppressHydrationWarning?: boolean,
|
||||
};
|
||||
type Instance = Element;
|
||||
type TextInstance = Text;
|
||||
|
||||
type HostContextDev = {
|
||||
namespace: string,
|
||||
ancestorInfo: mixed,
|
||||
};
|
||||
type HostContextProd = string;
|
||||
type HostContext = HostContextDev | HostContextProd;
|
||||
|
||||
let eventsEnabled: ?boolean = null;
|
||||
let selectionInformation: ?mixed = null;
|
||||
|
||||
type Batch = FiberRootBatch & {
|
||||
render(children: ReactNodeList): Work,
|
||||
then(onComplete: () => mixed): void,
|
||||
@@ -491,503 +448,6 @@ function shouldHydrateDueToLegacyHeuristic(container) {
|
||||
);
|
||||
}
|
||||
|
||||
function shouldAutoFocusHostComponent(type: string, props: Props): boolean {
|
||||
switch (type) {
|
||||
case 'button':
|
||||
case 'input':
|
||||
case 'select':
|
||||
case 'textarea':
|
||||
return !!props.autoFocus;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const DOMRenderer = ReactFiberReconciler({
|
||||
getRootHostContext(rootContainerInstance: Container): HostContext {
|
||||
let type;
|
||||
let namespace;
|
||||
const nodeType = rootContainerInstance.nodeType;
|
||||
switch (nodeType) {
|
||||
case DOCUMENT_NODE:
|
||||
case DOCUMENT_FRAGMENT_NODE: {
|
||||
type = nodeType === DOCUMENT_NODE ? '#document' : '#fragment';
|
||||
let root = (rootContainerInstance: any).documentElement;
|
||||
namespace = root ? root.namespaceURI : getChildNamespace(null, '');
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
const container: any =
|
||||
nodeType === COMMENT_NODE
|
||||
? rootContainerInstance.parentNode
|
||||
: rootContainerInstance;
|
||||
const ownNamespace = container.namespaceURI || null;
|
||||
type = container.tagName;
|
||||
namespace = getChildNamespace(ownNamespace, type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (__DEV__) {
|
||||
const validatedTag = type.toLowerCase();
|
||||
const ancestorInfo = updatedAncestorInfo(null, validatedTag, null);
|
||||
return {namespace, ancestorInfo};
|
||||
}
|
||||
return namespace;
|
||||
},
|
||||
|
||||
getChildHostContext(
|
||||
parentHostContext: HostContext,
|
||||
type: string,
|
||||
): HostContext {
|
||||
if (__DEV__) {
|
||||
const parentHostContextDev = ((parentHostContext: any): HostContextDev);
|
||||
const namespace = getChildNamespace(parentHostContextDev.namespace, type);
|
||||
const ancestorInfo = updatedAncestorInfo(
|
||||
parentHostContextDev.ancestorInfo,
|
||||
type,
|
||||
null,
|
||||
);
|
||||
return {namespace, ancestorInfo};
|
||||
}
|
||||
const parentNamespace = ((parentHostContext: any): HostContextProd);
|
||||
return getChildNamespace(parentNamespace, type);
|
||||
},
|
||||
|
||||
getPublicInstance(instance) {
|
||||
return instance;
|
||||
},
|
||||
|
||||
prepareForCommit(): void {
|
||||
eventsEnabled = ReactBrowserEventEmitter.isEnabled();
|
||||
selectionInformation = ReactInputSelection.getSelectionInformation();
|
||||
ReactBrowserEventEmitter.setEnabled(false);
|
||||
},
|
||||
|
||||
resetAfterCommit(): void {
|
||||
ReactInputSelection.restoreSelection(selectionInformation);
|
||||
selectionInformation = null;
|
||||
ReactBrowserEventEmitter.setEnabled(eventsEnabled);
|
||||
eventsEnabled = null;
|
||||
},
|
||||
|
||||
createInstance(
|
||||
type: string,
|
||||
props: Props,
|
||||
rootContainerInstance: Container,
|
||||
hostContext: HostContext,
|
||||
internalInstanceHandle: Object,
|
||||
): Instance {
|
||||
let parentNamespace: string;
|
||||
if (__DEV__) {
|
||||
// TODO: take namespace into account when validating.
|
||||
const hostContextDev = ((hostContext: any): HostContextDev);
|
||||
validateDOMNesting(type, null, hostContextDev.ancestorInfo);
|
||||
if (
|
||||
typeof props.children === 'string' ||
|
||||
typeof props.children === 'number'
|
||||
) {
|
||||
const string = '' + props.children;
|
||||
const ownAncestorInfo = updatedAncestorInfo(
|
||||
hostContextDev.ancestorInfo,
|
||||
type,
|
||||
null,
|
||||
);
|
||||
validateDOMNesting(null, string, ownAncestorInfo);
|
||||
}
|
||||
parentNamespace = hostContextDev.namespace;
|
||||
} else {
|
||||
parentNamespace = ((hostContext: any): HostContextProd);
|
||||
}
|
||||
const domElement: Instance = createElement(
|
||||
type,
|
||||
props,
|
||||
rootContainerInstance,
|
||||
parentNamespace,
|
||||
);
|
||||
precacheFiberNode(internalInstanceHandle, domElement);
|
||||
updateFiberProps(domElement, props);
|
||||
return domElement;
|
||||
},
|
||||
|
||||
appendInitialChild(
|
||||
parentInstance: Instance,
|
||||
child: Instance | TextInstance,
|
||||
): void {
|
||||
parentInstance.appendChild(child);
|
||||
},
|
||||
|
||||
finalizeInitialChildren(
|
||||
domElement: Instance,
|
||||
type: string,
|
||||
props: Props,
|
||||
rootContainerInstance: Container,
|
||||
): boolean {
|
||||
setInitialProperties(domElement, type, props, rootContainerInstance);
|
||||
return shouldAutoFocusHostComponent(type, props);
|
||||
},
|
||||
|
||||
prepareUpdate(
|
||||
domElement: Instance,
|
||||
type: string,
|
||||
oldProps: Props,
|
||||
newProps: Props,
|
||||
rootContainerInstance: Container,
|
||||
hostContext: HostContext,
|
||||
): null | Array<mixed> {
|
||||
if (__DEV__) {
|
||||
const hostContextDev = ((hostContext: any): HostContextDev);
|
||||
if (
|
||||
typeof newProps.children !== typeof oldProps.children &&
|
||||
(typeof newProps.children === 'string' ||
|
||||
typeof newProps.children === 'number')
|
||||
) {
|
||||
const string = '' + newProps.children;
|
||||
const ownAncestorInfo = updatedAncestorInfo(
|
||||
hostContextDev.ancestorInfo,
|
||||
type,
|
||||
null,
|
||||
);
|
||||
validateDOMNesting(null, string, ownAncestorInfo);
|
||||
}
|
||||
}
|
||||
return diffProperties(
|
||||
domElement,
|
||||
type,
|
||||
oldProps,
|
||||
newProps,
|
||||
rootContainerInstance,
|
||||
);
|
||||
},
|
||||
|
||||
shouldSetTextContent(type: string, props: Props): boolean {
|
||||
return (
|
||||
type === 'textarea' ||
|
||||
typeof props.children === 'string' ||
|
||||
typeof props.children === 'number' ||
|
||||
(typeof props.dangerouslySetInnerHTML === 'object' &&
|
||||
props.dangerouslySetInnerHTML !== null &&
|
||||
typeof props.dangerouslySetInnerHTML.__html === 'string')
|
||||
);
|
||||
},
|
||||
|
||||
shouldDeprioritizeSubtree(type: string, props: Props): boolean {
|
||||
return !!props.hidden;
|
||||
},
|
||||
|
||||
createTextInstance(
|
||||
text: string,
|
||||
rootContainerInstance: Container,
|
||||
hostContext: HostContext,
|
||||
internalInstanceHandle: Object,
|
||||
): TextInstance {
|
||||
if (__DEV__) {
|
||||
const hostContextDev = ((hostContext: any): HostContextDev);
|
||||
validateDOMNesting(null, text, hostContextDev.ancestorInfo);
|
||||
}
|
||||
const textNode: TextInstance = createTextNode(text, rootContainerInstance);
|
||||
precacheFiberNode(internalInstanceHandle, textNode);
|
||||
return textNode;
|
||||
},
|
||||
|
||||
now: ReactDOMFrameScheduling.now,
|
||||
|
||||
mutation: {
|
||||
commitMount(
|
||||
domElement: Instance,
|
||||
type: string,
|
||||
newProps: Props,
|
||||
internalInstanceHandle: Object,
|
||||
): void {
|
||||
// Despite the naming that might imply otherwise, this method only
|
||||
// fires if there is an `Update` effect scheduled during mounting.
|
||||
// This happens if `finalizeInitialChildren` returns `true` (which it
|
||||
// does to implement the `autoFocus` attribute on the client). But
|
||||
// there are also other cases when this might happen (such as patching
|
||||
// up text content during hydration mismatch). So we'll check this again.
|
||||
if (shouldAutoFocusHostComponent(type, newProps)) {
|
||||
((domElement: any):
|
||||
| HTMLButtonElement
|
||||
| HTMLInputElement
|
||||
| HTMLSelectElement
|
||||
| HTMLTextAreaElement).focus();
|
||||
}
|
||||
},
|
||||
|
||||
commitUpdate(
|
||||
domElement: Instance,
|
||||
updatePayload: Array<mixed>,
|
||||
type: string,
|
||||
oldProps: Props,
|
||||
newProps: Props,
|
||||
internalInstanceHandle: Object,
|
||||
): void {
|
||||
// Update the props handle so that we know which props are the ones with
|
||||
// with current event handlers.
|
||||
updateFiberProps(domElement, newProps);
|
||||
// Apply the diff to the DOM node.
|
||||
updateProperties(domElement, updatePayload, type, oldProps, newProps);
|
||||
},
|
||||
|
||||
resetTextContent(domElement: Instance): void {
|
||||
setTextContent(domElement, '');
|
||||
},
|
||||
|
||||
commitTextUpdate(
|
||||
textInstance: TextInstance,
|
||||
oldText: string,
|
||||
newText: string,
|
||||
): void {
|
||||
textInstance.nodeValue = newText;
|
||||
},
|
||||
|
||||
appendChild(
|
||||
parentInstance: Instance,
|
||||
child: Instance | TextInstance,
|
||||
): void {
|
||||
parentInstance.appendChild(child);
|
||||
},
|
||||
|
||||
appendChildToContainer(
|
||||
container: Container,
|
||||
child: Instance | TextInstance,
|
||||
): void {
|
||||
if (container.nodeType === COMMENT_NODE) {
|
||||
(container.parentNode: any).insertBefore(child, container);
|
||||
} else {
|
||||
container.appendChild(child);
|
||||
}
|
||||
},
|
||||
|
||||
insertBefore(
|
||||
parentInstance: Instance,
|
||||
child: Instance | TextInstance,
|
||||
beforeChild: Instance | TextInstance,
|
||||
): void {
|
||||
parentInstance.insertBefore(child, beforeChild);
|
||||
},
|
||||
|
||||
insertInContainerBefore(
|
||||
container: Container,
|
||||
child: Instance | TextInstance,
|
||||
beforeChild: Instance | TextInstance,
|
||||
): void {
|
||||
if (container.nodeType === COMMENT_NODE) {
|
||||
(container.parentNode: any).insertBefore(child, beforeChild);
|
||||
} else {
|
||||
container.insertBefore(child, beforeChild);
|
||||
}
|
||||
},
|
||||
|
||||
removeChild(
|
||||
parentInstance: Instance,
|
||||
child: Instance | TextInstance,
|
||||
): void {
|
||||
parentInstance.removeChild(child);
|
||||
},
|
||||
|
||||
removeChildFromContainer(
|
||||
container: Container,
|
||||
child: Instance | TextInstance,
|
||||
): void {
|
||||
if (container.nodeType === COMMENT_NODE) {
|
||||
(container.parentNode: any).removeChild(child);
|
||||
} else {
|
||||
container.removeChild(child);
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
hydration: {
|
||||
canHydrateInstance(
|
||||
instance: Instance | TextInstance,
|
||||
type: string,
|
||||
props: Props,
|
||||
): null | Instance {
|
||||
if (
|
||||
instance.nodeType !== ELEMENT_NODE ||
|
||||
type.toLowerCase() !== instance.nodeName.toLowerCase()
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
// This has now been refined to an element node.
|
||||
return ((instance: any): Instance);
|
||||
},
|
||||
|
||||
canHydrateTextInstance(
|
||||
instance: Instance | TextInstance,
|
||||
text: string,
|
||||
): null | TextInstance {
|
||||
if (text === '' || instance.nodeType !== TEXT_NODE) {
|
||||
// Empty strings are not parsed by HTML so there won't be a correct match here.
|
||||
return null;
|
||||
}
|
||||
// This has now been refined to a text node.
|
||||
return ((instance: any): TextInstance);
|
||||
},
|
||||
|
||||
getNextHydratableSibling(
|
||||
instance: Instance | TextInstance,
|
||||
): null | Instance | TextInstance {
|
||||
let node = instance.nextSibling;
|
||||
// Skip non-hydratable nodes.
|
||||
while (
|
||||
node &&
|
||||
node.nodeType !== ELEMENT_NODE &&
|
||||
node.nodeType !== TEXT_NODE
|
||||
) {
|
||||
node = node.nextSibling;
|
||||
}
|
||||
return (node: any);
|
||||
},
|
||||
|
||||
getFirstHydratableChild(
|
||||
parentInstance: Container | Instance,
|
||||
): null | Instance | TextInstance {
|
||||
let next = parentInstance.firstChild;
|
||||
// Skip non-hydratable nodes.
|
||||
while (
|
||||
next &&
|
||||
next.nodeType !== ELEMENT_NODE &&
|
||||
next.nodeType !== TEXT_NODE
|
||||
) {
|
||||
next = next.nextSibling;
|
||||
}
|
||||
return (next: any);
|
||||
},
|
||||
|
||||
hydrateInstance(
|
||||
instance: Instance,
|
||||
type: string,
|
||||
props: Props,
|
||||
rootContainerInstance: Container,
|
||||
hostContext: HostContext,
|
||||
internalInstanceHandle: Object,
|
||||
): null | Array<mixed> {
|
||||
precacheFiberNode(internalInstanceHandle, instance);
|
||||
// TODO: Possibly defer this until the commit phase where all the events
|
||||
// get attached.
|
||||
updateFiberProps(instance, props);
|
||||
let parentNamespace: string;
|
||||
if (__DEV__) {
|
||||
const hostContextDev = ((hostContext: any): HostContextDev);
|
||||
parentNamespace = hostContextDev.namespace;
|
||||
} else {
|
||||
parentNamespace = ((hostContext: any): HostContextProd);
|
||||
}
|
||||
return diffHydratedProperties(
|
||||
instance,
|
||||
type,
|
||||
props,
|
||||
parentNamespace,
|
||||
rootContainerInstance,
|
||||
);
|
||||
},
|
||||
|
||||
hydrateTextInstance(
|
||||
textInstance: TextInstance,
|
||||
text: string,
|
||||
internalInstanceHandle: Object,
|
||||
): boolean {
|
||||
precacheFiberNode(internalInstanceHandle, textInstance);
|
||||
return diffHydratedText(textInstance, text);
|
||||
},
|
||||
|
||||
didNotMatchHydratedContainerTextInstance(
|
||||
parentContainer: Container,
|
||||
textInstance: TextInstance,
|
||||
text: string,
|
||||
) {
|
||||
if (__DEV__) {
|
||||
warnForUnmatchedText(textInstance, text);
|
||||
}
|
||||
},
|
||||
|
||||
didNotMatchHydratedTextInstance(
|
||||
parentType: string,
|
||||
parentProps: Props,
|
||||
parentInstance: Instance,
|
||||
textInstance: TextInstance,
|
||||
text: string,
|
||||
) {
|
||||
if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
|
||||
warnForUnmatchedText(textInstance, text);
|
||||
}
|
||||
},
|
||||
|
||||
didNotHydrateContainerInstance(
|
||||
parentContainer: Container,
|
||||
instance: Instance | TextInstance,
|
||||
) {
|
||||
if (__DEV__) {
|
||||
if (instance.nodeType === 1) {
|
||||
warnForDeletedHydratableElement(parentContainer, (instance: any));
|
||||
} else {
|
||||
warnForDeletedHydratableText(parentContainer, (instance: any));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
didNotHydrateInstance(
|
||||
parentType: string,
|
||||
parentProps: Props,
|
||||
parentInstance: Instance,
|
||||
instance: Instance | TextInstance,
|
||||
) {
|
||||
if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
|
||||
if (instance.nodeType === 1) {
|
||||
warnForDeletedHydratableElement(parentInstance, (instance: any));
|
||||
} else {
|
||||
warnForDeletedHydratableText(parentInstance, (instance: any));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
didNotFindHydratableContainerInstance(
|
||||
parentContainer: Container,
|
||||
type: string,
|
||||
props: Props,
|
||||
) {
|
||||
if (__DEV__) {
|
||||
warnForInsertedHydratedElement(parentContainer, type, props);
|
||||
}
|
||||
},
|
||||
|
||||
didNotFindHydratableContainerTextInstance(
|
||||
parentContainer: Container,
|
||||
text: string,
|
||||
) {
|
||||
if (__DEV__) {
|
||||
warnForInsertedHydratedText(parentContainer, text);
|
||||
}
|
||||
},
|
||||
|
||||
didNotFindHydratableInstance(
|
||||
parentType: string,
|
||||
parentProps: Props,
|
||||
parentInstance: Instance,
|
||||
type: string,
|
||||
props: Props,
|
||||
) {
|
||||
if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
|
||||
warnForInsertedHydratedElement(parentInstance, type, props);
|
||||
}
|
||||
},
|
||||
|
||||
didNotFindHydratableTextInstance(
|
||||
parentType: string,
|
||||
parentProps: Props,
|
||||
parentInstance: Instance,
|
||||
text: string,
|
||||
) {
|
||||
if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
|
||||
warnForInsertedHydratedText(parentInstance, text);
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
scheduleDeferredCallback: ReactDOMFrameScheduling.rIC,
|
||||
cancelDeferredCallback: ReactDOMFrameScheduling.cIC,
|
||||
});
|
||||
|
||||
ReactGenericBatching.injection.injectRenderer(DOMRenderer);
|
||||
|
||||
let warnedAboutHydrateAPI = false;
|
||||
@@ -1270,6 +730,8 @@ const ReactDOM: Object = {
|
||||
|
||||
unstable_deferredUpdates: DOMRenderer.deferredUpdates,
|
||||
|
||||
unstable_interactiveUpdates: DOMRenderer.interactiveUpdates,
|
||||
|
||||
flushSync: DOMRenderer.flushSync,
|
||||
|
||||
unstable_flushControlled: DOMRenderer.flushControlled,
|
||||
@@ -1306,11 +768,7 @@ const foundDevTools = DOMRenderer.injectIntoDevTools({
|
||||
});
|
||||
|
||||
if (__DEV__) {
|
||||
if (
|
||||
!foundDevTools &&
|
||||
ExecutionEnvironment.canUseDOM &&
|
||||
window.top === window.self
|
||||
) {
|
||||
if (!foundDevTools && canUseDOM && window.top === window.self) {
|
||||
// If we're in Chrome or Firefox, provide a download link if not installed.
|
||||
if (
|
||||
(navigator.userAgent.indexOf('Chrome') > -1 &&
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
*/
|
||||
|
||||
import {HostComponent, HostText} from 'shared/ReactTypeOfWork';
|
||||
import invariant from 'fbjs/lib/invariant';
|
||||
import invariant from 'shared/invariant';
|
||||
|
||||
const randomKey = Math.random()
|
||||
.toString(36)
|
||||
|
||||
@@ -10,8 +10,7 @@
|
||||
// TODO: direct imports like some-package/src/* are bad. Fix me.
|
||||
import ReactDebugCurrentFiber from 'react-reconciler/src/ReactDebugCurrentFiber';
|
||||
import {registrationNameModules} from 'events/EventPluginRegistry';
|
||||
import emptyFunction from 'fbjs/lib/emptyFunction';
|
||||
import warning from 'fbjs/lib/warning';
|
||||
import warning from 'shared/warning';
|
||||
|
||||
import * as DOMPropertyOperations from './DOMPropertyOperations';
|
||||
import * as ReactDOMFiberInput from './ReactDOMFiberInput';
|
||||
@@ -21,8 +20,16 @@ import * as ReactDOMFiberTextarea from './ReactDOMFiberTextarea';
|
||||
import * as inputValueTracking from './inputValueTracking';
|
||||
import setInnerHTML from './setInnerHTML';
|
||||
import setTextContent from './setTextContent';
|
||||
import {
|
||||
TOP_ERROR,
|
||||
TOP_INVALID,
|
||||
TOP_LOAD,
|
||||
TOP_RESET,
|
||||
TOP_SUBMIT,
|
||||
TOP_TOGGLE,
|
||||
} from '../events/DOMTopLevelEventTypes';
|
||||
import {listenTo, trapBubbledEvent} from '../events/ReactBrowserEventEmitter';
|
||||
import {mediaEventTypes} from '../events/BrowserEventConstants';
|
||||
import {mediaEventTypes} from '../events/DOMTopLevelEventTypes';
|
||||
import * as CSSPropertyOperations from '../shared/CSSPropertyOperations';
|
||||
import {Namespaces, getIntrinsicNamespace} from '../shared/DOMNamespaces';
|
||||
import {
|
||||
@@ -55,7 +62,7 @@ const HTML = '__html';
|
||||
|
||||
const {html: HTML_NAMESPACE} = Namespaces;
|
||||
|
||||
let getStack = emptyFunction.thatReturns('');
|
||||
let getStack = () => '';
|
||||
|
||||
let warnedUnknownTags;
|
||||
let suppressHydrationWarning;
|
||||
@@ -224,6 +231,8 @@ function getOwnerDocumentFromRootContainer(
|
||||
: rootContainerElement.ownerDocument;
|
||||
}
|
||||
|
||||
function noop() {}
|
||||
|
||||
function trapClickOnNonInteractiveElement(node: HTMLElement) {
|
||||
// Mobile Safari does not fire properly bubble click events on
|
||||
// non-interactive elements, which means delegated click listeners do not
|
||||
@@ -234,7 +243,7 @@ function trapClickOnNonInteractiveElement(node: HTMLElement) {
|
||||
// bookkeeping for it. Not sure if we need to clear it when the listener is
|
||||
// removed.
|
||||
// TODO: Only do this for the relevant Safaris maybe?
|
||||
node.onclick = emptyFunction;
|
||||
node.onclick = noop;
|
||||
}
|
||||
|
||||
function setInitialDOMProperties(
|
||||
@@ -424,7 +433,11 @@ export function setInitialProperties(
|
||||
const isCustomComponentTag = isCustomComponent(tag, rawProps);
|
||||
if (__DEV__) {
|
||||
validatePropertiesInDevelopment(tag, rawProps);
|
||||
if (isCustomComponentTag && !didWarnShadyDOM && domElement.shadyRoot) {
|
||||
if (
|
||||
isCustomComponentTag &&
|
||||
!didWarnShadyDOM &&
|
||||
(domElement: any).shadyRoot
|
||||
) {
|
||||
warning(
|
||||
false,
|
||||
'%s is using shady DOM. Using shady DOM with React can ' +
|
||||
@@ -440,43 +453,41 @@ export function setInitialProperties(
|
||||
switch (tag) {
|
||||
case 'iframe':
|
||||
case 'object':
|
||||
trapBubbledEvent('topLoad', 'load', domElement);
|
||||
trapBubbledEvent(TOP_LOAD, domElement);
|
||||
props = rawProps;
|
||||
break;
|
||||
case 'video':
|
||||
case 'audio':
|
||||
// Create listener for each media event
|
||||
for (const event in mediaEventTypes) {
|
||||
if (mediaEventTypes.hasOwnProperty(event)) {
|
||||
trapBubbledEvent(event, mediaEventTypes[event], domElement);
|
||||
}
|
||||
for (let i = 0; i < mediaEventTypes.length; i++) {
|
||||
trapBubbledEvent(mediaEventTypes[i], domElement);
|
||||
}
|
||||
props = rawProps;
|
||||
break;
|
||||
case 'source':
|
||||
trapBubbledEvent('topError', 'error', domElement);
|
||||
trapBubbledEvent(TOP_ERROR, domElement);
|
||||
props = rawProps;
|
||||
break;
|
||||
case 'img':
|
||||
case 'image':
|
||||
case 'link':
|
||||
trapBubbledEvent('topError', 'error', domElement);
|
||||
trapBubbledEvent('topLoad', 'load', domElement);
|
||||
trapBubbledEvent(TOP_ERROR, domElement);
|
||||
trapBubbledEvent(TOP_LOAD, domElement);
|
||||
props = rawProps;
|
||||
break;
|
||||
case 'form':
|
||||
trapBubbledEvent('topReset', 'reset', domElement);
|
||||
trapBubbledEvent('topSubmit', 'submit', domElement);
|
||||
trapBubbledEvent(TOP_RESET, domElement);
|
||||
trapBubbledEvent(TOP_SUBMIT, domElement);
|
||||
props = rawProps;
|
||||
break;
|
||||
case 'details':
|
||||
trapBubbledEvent('topToggle', 'toggle', domElement);
|
||||
trapBubbledEvent(TOP_TOGGLE, domElement);
|
||||
props = rawProps;
|
||||
break;
|
||||
case 'input':
|
||||
ReactDOMFiberInput.initWrapperState(domElement, rawProps);
|
||||
props = ReactDOMFiberInput.getHostProps(domElement, rawProps);
|
||||
trapBubbledEvent('topInvalid', 'invalid', domElement);
|
||||
trapBubbledEvent(TOP_INVALID, domElement);
|
||||
// For controlled components we always need to ensure we're listening
|
||||
// to onChange. Even if there is no listener.
|
||||
ensureListeningTo(rootContainerElement, 'onChange');
|
||||
@@ -488,7 +499,7 @@ export function setInitialProperties(
|
||||
case 'select':
|
||||
ReactDOMFiberSelect.initWrapperState(domElement, rawProps);
|
||||
props = ReactDOMFiberSelect.getHostProps(domElement, rawProps);
|
||||
trapBubbledEvent('topInvalid', 'invalid', domElement);
|
||||
trapBubbledEvent(TOP_INVALID, domElement);
|
||||
// For controlled components we always need to ensure we're listening
|
||||
// to onChange. Even if there is no listener.
|
||||
ensureListeningTo(rootContainerElement, 'onChange');
|
||||
@@ -496,7 +507,7 @@ export function setInitialProperties(
|
||||
case 'textarea':
|
||||
ReactDOMFiberTextarea.initWrapperState(domElement, rawProps);
|
||||
props = ReactDOMFiberTextarea.getHostProps(domElement, rawProps);
|
||||
trapBubbledEvent('topInvalid', 'invalid', domElement);
|
||||
trapBubbledEvent(TOP_INVALID, domElement);
|
||||
// For controlled components we always need to ensure we're listening
|
||||
// to onChange. Even if there is no listener.
|
||||
ensureListeningTo(rootContainerElement, 'onChange');
|
||||
@@ -520,7 +531,7 @@ export function setInitialProperties(
|
||||
// TODO: Make sure we check if this is still unmounted or do any clean
|
||||
// up necessary since we never stop tracking anymore.
|
||||
inputValueTracking.track((domElement: any));
|
||||
ReactDOMFiberInput.postMountWrapper(domElement, rawProps);
|
||||
ReactDOMFiberInput.postMountWrapper(domElement, rawProps, false);
|
||||
break;
|
||||
case 'textarea':
|
||||
// TODO: Make sure we check if this is still unmounted or do any clean
|
||||
@@ -814,7 +825,11 @@ export function diffHydratedProperties(
|
||||
suppressHydrationWarning = rawProps[SUPPRESS_HYDRATION_WARNING] === true;
|
||||
isCustomComponentTag = isCustomComponent(tag, rawProps);
|
||||
validatePropertiesInDevelopment(tag, rawProps);
|
||||
if (isCustomComponentTag && !didWarnShadyDOM && domElement.shadyRoot) {
|
||||
if (
|
||||
isCustomComponentTag &&
|
||||
!didWarnShadyDOM &&
|
||||
(domElement: any).shadyRoot
|
||||
) {
|
||||
warning(
|
||||
false,
|
||||
'%s is using shady DOM. Using shady DOM with React can ' +
|
||||
@@ -829,36 +844,34 @@ export function diffHydratedProperties(
|
||||
switch (tag) {
|
||||
case 'iframe':
|
||||
case 'object':
|
||||
trapBubbledEvent('topLoad', 'load', domElement);
|
||||
trapBubbledEvent(TOP_LOAD, domElement);
|
||||
break;
|
||||
case 'video':
|
||||
case 'audio':
|
||||
// Create listener for each media event
|
||||
for (const event in mediaEventTypes) {
|
||||
if (mediaEventTypes.hasOwnProperty(event)) {
|
||||
trapBubbledEvent(event, mediaEventTypes[event], domElement);
|
||||
}
|
||||
for (let i = 0; i < mediaEventTypes.length; i++) {
|
||||
trapBubbledEvent(mediaEventTypes[i], domElement);
|
||||
}
|
||||
break;
|
||||
case 'source':
|
||||
trapBubbledEvent('topError', 'error', domElement);
|
||||
trapBubbledEvent(TOP_ERROR, domElement);
|
||||
break;
|
||||
case 'img':
|
||||
case 'image':
|
||||
case 'link':
|
||||
trapBubbledEvent('topError', 'error', domElement);
|
||||
trapBubbledEvent('topLoad', 'load', domElement);
|
||||
trapBubbledEvent(TOP_ERROR, domElement);
|
||||
trapBubbledEvent(TOP_LOAD, domElement);
|
||||
break;
|
||||
case 'form':
|
||||
trapBubbledEvent('topReset', 'reset', domElement);
|
||||
trapBubbledEvent('topSubmit', 'submit', domElement);
|
||||
trapBubbledEvent(TOP_RESET, domElement);
|
||||
trapBubbledEvent(TOP_SUBMIT, domElement);
|
||||
break;
|
||||
case 'details':
|
||||
trapBubbledEvent('topToggle', 'toggle', domElement);
|
||||
trapBubbledEvent(TOP_TOGGLE, domElement);
|
||||
break;
|
||||
case 'input':
|
||||
ReactDOMFiberInput.initWrapperState(domElement, rawProps);
|
||||
trapBubbledEvent('topInvalid', 'invalid', domElement);
|
||||
trapBubbledEvent(TOP_INVALID, domElement);
|
||||
// For controlled components we always need to ensure we're listening
|
||||
// to onChange. Even if there is no listener.
|
||||
ensureListeningTo(rootContainerElement, 'onChange');
|
||||
@@ -868,14 +881,14 @@ export function diffHydratedProperties(
|
||||
break;
|
||||
case 'select':
|
||||
ReactDOMFiberSelect.initWrapperState(domElement, rawProps);
|
||||
trapBubbledEvent('topInvalid', 'invalid', domElement);
|
||||
trapBubbledEvent(TOP_INVALID, domElement);
|
||||
// For controlled components we always need to ensure we're listening
|
||||
// to onChange. Even if there is no listener.
|
||||
ensureListeningTo(rootContainerElement, 'onChange');
|
||||
break;
|
||||
case 'textarea':
|
||||
ReactDOMFiberTextarea.initWrapperState(domElement, rawProps);
|
||||
trapBubbledEvent('topInvalid', 'invalid', domElement);
|
||||
trapBubbledEvent(TOP_INVALID, domElement);
|
||||
// For controlled components we always need to ensure we're listening
|
||||
// to onChange. Even if there is no listener.
|
||||
ensureListeningTo(rootContainerElement, 'onChange');
|
||||
@@ -1065,7 +1078,7 @@ export function diffHydratedProperties(
|
||||
// TODO: Make sure we check if this is still unmounted or do any clean
|
||||
// up necessary since we never stop tracking anymore.
|
||||
inputValueTracking.track((domElement: any));
|
||||
ReactDOMFiberInput.postMountWrapper(domElement, rawProps);
|
||||
ReactDOMFiberInput.postMountWrapper(domElement, rawProps, true);
|
||||
break;
|
||||
case 'textarea':
|
||||
// TODO: Make sure we check if this is still unmounted or do any clean
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
|
||||
// TODO: direct imports like some-package/src/* are bad. Fix me.
|
||||
import ReactDebugCurrentFiber from 'react-reconciler/src/ReactDebugCurrentFiber';
|
||||
import invariant from 'fbjs/lib/invariant';
|
||||
import warning from 'fbjs/lib/warning';
|
||||
import invariant from 'shared/invariant';
|
||||
import warning from 'shared/warning';
|
||||
|
||||
import * as DOMPropertyOperations from './DOMPropertyOperations';
|
||||
import {getFiberCurrentPropsFromNode} from './ReactDOMComponentTree';
|
||||
@@ -205,20 +205,32 @@ export function updateWrapper(element: Element, props: Object) {
|
||||
}
|
||||
}
|
||||
|
||||
export function postMountWrapper(element: Element, props: Object) {
|
||||
export function postMountWrapper(
|
||||
element: Element,
|
||||
props: Object,
|
||||
isHydrating: boolean,
|
||||
) {
|
||||
const node = ((element: any): InputWithWrapperState);
|
||||
|
||||
if (props.hasOwnProperty('value') || props.hasOwnProperty('defaultValue')) {
|
||||
const initialValue = '' + node._wrapperState.initialValue;
|
||||
const currentValue = node.value;
|
||||
|
||||
// Do not assign value if it is already set. This prevents user text input
|
||||
// from being lost during SSR hydration.
|
||||
if (node.value === '') {
|
||||
node.value = '' + node._wrapperState.initialValue;
|
||||
if (!isHydrating) {
|
||||
// Do not re-assign the value property if there is no change. This
|
||||
// potentially avoids a DOM write and prevents Firefox (~60.0.1) from
|
||||
// prematurely marking required inputs as invalid
|
||||
if (initialValue !== currentValue) {
|
||||
node.value = initialValue;
|
||||
}
|
||||
}
|
||||
|
||||
// value must be assigned before defaultValue. This fixes an issue where the
|
||||
// visually displayed value of date inputs disappears on mobile Safari and Chrome:
|
||||
// https://github.com/facebook/react/issues/7233
|
||||
node.defaultValue = '' + node._wrapperState.initialValue;
|
||||
node.defaultValue = initialValue;
|
||||
}
|
||||
|
||||
// Normally, we'd just do `node.checked = node.checked` upon initial mount, less this bug
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import warning from 'fbjs/lib/warning';
|
||||
import warning from 'shared/warning';
|
||||
|
||||
let didWarnSelectedSetOnOption = false;
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
// TODO: direct imports like some-package/src/* are bad. Fix me.
|
||||
import ReactDebugCurrentFiber from 'react-reconciler/src/ReactDebugCurrentFiber';
|
||||
import warning from 'fbjs/lib/warning';
|
||||
import warning from 'shared/warning';
|
||||
|
||||
import ReactControlledValuePropTypes from '../shared/ReactControlledValuePropTypes';
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import invariant from 'fbjs/lib/invariant';
|
||||
import warning from 'fbjs/lib/warning';
|
||||
import invariant from 'shared/invariant';
|
||||
import warning from 'shared/warning';
|
||||
// TODO: direct imports like some-package/src/* are bad. Fix me.
|
||||
import ReactDebugCurrentFiber from 'react-reconciler/src/ReactDebugCurrentFiber';
|
||||
|
||||
|
||||
581
packages/react-dom/src/client/ReactDOMHostConfig.js
vendored
Normal file
581
packages/react-dom/src/client/ReactDOMHostConfig.js
vendored
Normal file
@@ -0,0 +1,581 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import * as ReactScheduler from 'shared/ReactScheduler';
|
||||
|
||||
import * as ReactDOMComponentTree from './ReactDOMComponentTree';
|
||||
import * as ReactDOMFiberComponent from './ReactDOMFiberComponent';
|
||||
import * as ReactInputSelection from './ReactInputSelection';
|
||||
import setTextContent from './setTextContent';
|
||||
import validateDOMNesting from './validateDOMNesting';
|
||||
import * as ReactBrowserEventEmitter from '../events/ReactBrowserEventEmitter';
|
||||
import {getChildNamespace} from '../shared/DOMNamespaces';
|
||||
import {
|
||||
ELEMENT_NODE,
|
||||
TEXT_NODE,
|
||||
COMMENT_NODE,
|
||||
DOCUMENT_NODE,
|
||||
DOCUMENT_FRAGMENT_NODE,
|
||||
} from '../shared/HTMLNodeType';
|
||||
|
||||
export type Type = string;
|
||||
export type Props = {
|
||||
autoFocus?: boolean,
|
||||
children?: mixed,
|
||||
hidden?: boolean,
|
||||
suppressHydrationWarning?: boolean,
|
||||
dangerouslySetInnerHTML?: mixed,
|
||||
};
|
||||
export type Container = Element | Document;
|
||||
export type Instance = Element;
|
||||
export type TextInstance = Text;
|
||||
export type HydratableInstance = Element | Text;
|
||||
export type PublicInstance = Element | Text;
|
||||
type HostContextDev = {
|
||||
namespace: string,
|
||||
ancestorInfo: mixed,
|
||||
};
|
||||
type HostContextProd = string;
|
||||
export type HostContext = HostContextDev | HostContextProd;
|
||||
export type UpdatePayload = Array<mixed>;
|
||||
export type ChildSet = void; // Unused
|
||||
|
||||
const {
|
||||
createElement,
|
||||
createTextNode,
|
||||
setInitialProperties,
|
||||
diffProperties,
|
||||
updateProperties,
|
||||
diffHydratedProperties,
|
||||
diffHydratedText,
|
||||
warnForUnmatchedText,
|
||||
warnForDeletedHydratableElement,
|
||||
warnForDeletedHydratableText,
|
||||
warnForInsertedHydratedElement,
|
||||
warnForInsertedHydratedText,
|
||||
} = ReactDOMFiberComponent;
|
||||
const {updatedAncestorInfo} = validateDOMNesting;
|
||||
const {precacheFiberNode, updateFiberProps} = ReactDOMComponentTree;
|
||||
|
||||
let SUPPRESS_HYDRATION_WARNING;
|
||||
if (__DEV__) {
|
||||
SUPPRESS_HYDRATION_WARNING = 'suppressHydrationWarning';
|
||||
}
|
||||
|
||||
let eventsEnabled: ?boolean = null;
|
||||
let selectionInformation: ?mixed = null;
|
||||
|
||||
function shouldAutoFocusHostComponent(type: string, props: Props): boolean {
|
||||
switch (type) {
|
||||
case 'button':
|
||||
case 'input':
|
||||
case 'select':
|
||||
case 'textarea':
|
||||
return !!props.autoFocus;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export * from 'shared/HostConfigWithNoPersistence';
|
||||
|
||||
export function getRootHostContext(
|
||||
rootContainerInstance: Container,
|
||||
): HostContext {
|
||||
let type;
|
||||
let namespace;
|
||||
const nodeType = rootContainerInstance.nodeType;
|
||||
switch (nodeType) {
|
||||
case DOCUMENT_NODE:
|
||||
case DOCUMENT_FRAGMENT_NODE: {
|
||||
type = nodeType === DOCUMENT_NODE ? '#document' : '#fragment';
|
||||
let root = (rootContainerInstance: any).documentElement;
|
||||
namespace = root ? root.namespaceURI : getChildNamespace(null, '');
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
const container: any =
|
||||
nodeType === COMMENT_NODE
|
||||
? rootContainerInstance.parentNode
|
||||
: rootContainerInstance;
|
||||
const ownNamespace = container.namespaceURI || null;
|
||||
type = container.tagName;
|
||||
namespace = getChildNamespace(ownNamespace, type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (__DEV__) {
|
||||
const validatedTag = type.toLowerCase();
|
||||
const ancestorInfo = updatedAncestorInfo(null, validatedTag, null);
|
||||
return {namespace, ancestorInfo};
|
||||
}
|
||||
return namespace;
|
||||
}
|
||||
|
||||
export function getChildHostContext(
|
||||
parentHostContext: HostContext,
|
||||
type: string,
|
||||
rootContainerInstance: Container,
|
||||
): HostContext {
|
||||
if (__DEV__) {
|
||||
const parentHostContextDev = ((parentHostContext: any): HostContextDev);
|
||||
const namespace = getChildNamespace(parentHostContextDev.namespace, type);
|
||||
const ancestorInfo = updatedAncestorInfo(
|
||||
parentHostContextDev.ancestorInfo,
|
||||
type,
|
||||
null,
|
||||
);
|
||||
return {namespace, ancestorInfo};
|
||||
}
|
||||
const parentNamespace = ((parentHostContext: any): HostContextProd);
|
||||
return getChildNamespace(parentNamespace, type);
|
||||
}
|
||||
|
||||
export function getPublicInstance(instance: Instance): * {
|
||||
return instance;
|
||||
}
|
||||
|
||||
export function prepareForCommit(containerInfo: Container): void {
|
||||
eventsEnabled = ReactBrowserEventEmitter.isEnabled();
|
||||
selectionInformation = ReactInputSelection.getSelectionInformation();
|
||||
ReactBrowserEventEmitter.setEnabled(false);
|
||||
}
|
||||
|
||||
export function resetAfterCommit(containerInfo: Container): void {
|
||||
ReactInputSelection.restoreSelection(selectionInformation);
|
||||
selectionInformation = null;
|
||||
ReactBrowserEventEmitter.setEnabled(eventsEnabled);
|
||||
eventsEnabled = null;
|
||||
}
|
||||
|
||||
export function createInstance(
|
||||
type: string,
|
||||
props: Props,
|
||||
rootContainerInstance: Container,
|
||||
hostContext: HostContext,
|
||||
internalInstanceHandle: Object,
|
||||
): Instance {
|
||||
let parentNamespace: string;
|
||||
if (__DEV__) {
|
||||
// TODO: take namespace into account when validating.
|
||||
const hostContextDev = ((hostContext: any): HostContextDev);
|
||||
validateDOMNesting(type, null, hostContextDev.ancestorInfo);
|
||||
if (
|
||||
typeof props.children === 'string' ||
|
||||
typeof props.children === 'number'
|
||||
) {
|
||||
const string = '' + props.children;
|
||||
const ownAncestorInfo = updatedAncestorInfo(
|
||||
hostContextDev.ancestorInfo,
|
||||
type,
|
||||
null,
|
||||
);
|
||||
validateDOMNesting(null, string, ownAncestorInfo);
|
||||
}
|
||||
parentNamespace = hostContextDev.namespace;
|
||||
} else {
|
||||
parentNamespace = ((hostContext: any): HostContextProd);
|
||||
}
|
||||
const domElement: Instance = createElement(
|
||||
type,
|
||||
props,
|
||||
rootContainerInstance,
|
||||
parentNamespace,
|
||||
);
|
||||
precacheFiberNode(internalInstanceHandle, domElement);
|
||||
updateFiberProps(domElement, props);
|
||||
return domElement;
|
||||
}
|
||||
|
||||
export function appendInitialChild(
|
||||
parentInstance: Instance,
|
||||
child: Instance | TextInstance,
|
||||
): void {
|
||||
parentInstance.appendChild(child);
|
||||
}
|
||||
|
||||
export function finalizeInitialChildren(
|
||||
domElement: Instance,
|
||||
type: string,
|
||||
props: Props,
|
||||
rootContainerInstance: Container,
|
||||
hostContext: HostContext,
|
||||
): boolean {
|
||||
setInitialProperties(domElement, type, props, rootContainerInstance);
|
||||
return shouldAutoFocusHostComponent(type, props);
|
||||
}
|
||||
|
||||
export function prepareUpdate(
|
||||
domElement: Instance,
|
||||
type: string,
|
||||
oldProps: Props,
|
||||
newProps: Props,
|
||||
rootContainerInstance: Container,
|
||||
hostContext: HostContext,
|
||||
): null | Array<mixed> {
|
||||
if (__DEV__) {
|
||||
const hostContextDev = ((hostContext: any): HostContextDev);
|
||||
if (
|
||||
typeof newProps.children !== typeof oldProps.children &&
|
||||
(typeof newProps.children === 'string' ||
|
||||
typeof newProps.children === 'number')
|
||||
) {
|
||||
const string = '' + newProps.children;
|
||||
const ownAncestorInfo = updatedAncestorInfo(
|
||||
hostContextDev.ancestorInfo,
|
||||
type,
|
||||
null,
|
||||
);
|
||||
validateDOMNesting(null, string, ownAncestorInfo);
|
||||
}
|
||||
}
|
||||
return diffProperties(
|
||||
domElement,
|
||||
type,
|
||||
oldProps,
|
||||
newProps,
|
||||
rootContainerInstance,
|
||||
);
|
||||
}
|
||||
|
||||
export function shouldSetTextContent(type: string, props: Props): boolean {
|
||||
return (
|
||||
type === 'textarea' ||
|
||||
typeof props.children === 'string' ||
|
||||
typeof props.children === 'number' ||
|
||||
(typeof props.dangerouslySetInnerHTML === 'object' &&
|
||||
props.dangerouslySetInnerHTML !== null &&
|
||||
typeof props.dangerouslySetInnerHTML.__html === 'string')
|
||||
);
|
||||
}
|
||||
|
||||
export function shouldDeprioritizeSubtree(type: string, props: Props): boolean {
|
||||
return !!props.hidden;
|
||||
}
|
||||
|
||||
export function createTextInstance(
|
||||
text: string,
|
||||
rootContainerInstance: Container,
|
||||
hostContext: HostContext,
|
||||
internalInstanceHandle: Object,
|
||||
): TextInstance {
|
||||
if (__DEV__) {
|
||||
const hostContextDev = ((hostContext: any): HostContextDev);
|
||||
validateDOMNesting(null, text, hostContextDev.ancestorInfo);
|
||||
}
|
||||
const textNode: TextInstance = createTextNode(text, rootContainerInstance);
|
||||
precacheFiberNode(internalInstanceHandle, textNode);
|
||||
return textNode;
|
||||
}
|
||||
|
||||
export const now = ReactScheduler.now;
|
||||
export const isPrimaryRenderer = true;
|
||||
export const scheduleDeferredCallback = ReactScheduler.scheduleWork;
|
||||
export const cancelDeferredCallback = ReactScheduler.cancelScheduledWork;
|
||||
|
||||
// -------------------
|
||||
// Mutation
|
||||
// -------------------
|
||||
|
||||
export const supportsMutation = true;
|
||||
|
||||
export function commitMount(
|
||||
domElement: Instance,
|
||||
type: string,
|
||||
newProps: Props,
|
||||
internalInstanceHandle: Object,
|
||||
): void {
|
||||
// Despite the naming that might imply otherwise, this method only
|
||||
// fires if there is an `Update` effect scheduled during mounting.
|
||||
// This happens if `finalizeInitialChildren` returns `true` (which it
|
||||
// does to implement the `autoFocus` attribute on the client). But
|
||||
// there are also other cases when this might happen (such as patching
|
||||
// up text content during hydration mismatch). So we'll check this again.
|
||||
if (shouldAutoFocusHostComponent(type, newProps)) {
|
||||
((domElement: any):
|
||||
| HTMLButtonElement
|
||||
| HTMLInputElement
|
||||
| HTMLSelectElement
|
||||
| HTMLTextAreaElement).focus();
|
||||
}
|
||||
}
|
||||
|
||||
export function commitUpdate(
|
||||
domElement: Instance,
|
||||
updatePayload: Array<mixed>,
|
||||
type: string,
|
||||
oldProps: Props,
|
||||
newProps: Props,
|
||||
internalInstanceHandle: Object,
|
||||
): void {
|
||||
// Update the props handle so that we know which props are the ones with
|
||||
// with current event handlers.
|
||||
updateFiberProps(domElement, newProps);
|
||||
// Apply the diff to the DOM node.
|
||||
updateProperties(domElement, updatePayload, type, oldProps, newProps);
|
||||
}
|
||||
|
||||
export function resetTextContent(domElement: Instance): void {
|
||||
setTextContent(domElement, '');
|
||||
}
|
||||
|
||||
export function commitTextUpdate(
|
||||
textInstance: TextInstance,
|
||||
oldText: string,
|
||||
newText: string,
|
||||
): void {
|
||||
textInstance.nodeValue = newText;
|
||||
}
|
||||
|
||||
export function appendChild(
|
||||
parentInstance: Instance,
|
||||
child: Instance | TextInstance,
|
||||
): void {
|
||||
parentInstance.appendChild(child);
|
||||
}
|
||||
|
||||
export function appendChildToContainer(
|
||||
container: Container,
|
||||
child: Instance | TextInstance,
|
||||
): void {
|
||||
if (container.nodeType === COMMENT_NODE) {
|
||||
(container.parentNode: any).insertBefore(child, container);
|
||||
} else {
|
||||
container.appendChild(child);
|
||||
}
|
||||
}
|
||||
|
||||
export function insertBefore(
|
||||
parentInstance: Instance,
|
||||
child: Instance | TextInstance,
|
||||
beforeChild: Instance | TextInstance,
|
||||
): void {
|
||||
parentInstance.insertBefore(child, beforeChild);
|
||||
}
|
||||
|
||||
export function insertInContainerBefore(
|
||||
container: Container,
|
||||
child: Instance | TextInstance,
|
||||
beforeChild: Instance | TextInstance,
|
||||
): void {
|
||||
if (container.nodeType === COMMENT_NODE) {
|
||||
(container.parentNode: any).insertBefore(child, beforeChild);
|
||||
} else {
|
||||
container.insertBefore(child, beforeChild);
|
||||
}
|
||||
}
|
||||
|
||||
export function removeChild(
|
||||
parentInstance: Instance,
|
||||
child: Instance | TextInstance,
|
||||
): void {
|
||||
parentInstance.removeChild(child);
|
||||
}
|
||||
|
||||
export function removeChildFromContainer(
|
||||
container: Container,
|
||||
child: Instance | TextInstance,
|
||||
): void {
|
||||
if (container.nodeType === COMMENT_NODE) {
|
||||
(container.parentNode: any).removeChild(child);
|
||||
} else {
|
||||
container.removeChild(child);
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------
|
||||
// Hydration
|
||||
// -------------------
|
||||
|
||||
export const supportsHydration = true;
|
||||
|
||||
export function canHydrateInstance(
|
||||
instance: Instance | TextInstance,
|
||||
type: string,
|
||||
props: Props,
|
||||
): null | Instance {
|
||||
if (
|
||||
instance.nodeType !== ELEMENT_NODE ||
|
||||
type.toLowerCase() !== instance.nodeName.toLowerCase()
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
// This has now been refined to an element node.
|
||||
return ((instance: any): Instance);
|
||||
}
|
||||
|
||||
export function canHydrateTextInstance(
|
||||
instance: Instance | TextInstance,
|
||||
text: string,
|
||||
): null | TextInstance {
|
||||
if (text === '' || instance.nodeType !== TEXT_NODE) {
|
||||
// Empty strings are not parsed by HTML so there won't be a correct match here.
|
||||
return null;
|
||||
}
|
||||
// This has now been refined to a text node.
|
||||
return ((instance: any): TextInstance);
|
||||
}
|
||||
|
||||
export function getNextHydratableSibling(
|
||||
instance: Instance | TextInstance,
|
||||
): null | Instance | TextInstance {
|
||||
let node = instance.nextSibling;
|
||||
// Skip non-hydratable nodes.
|
||||
while (
|
||||
node &&
|
||||
node.nodeType !== ELEMENT_NODE &&
|
||||
node.nodeType !== TEXT_NODE
|
||||
) {
|
||||
node = node.nextSibling;
|
||||
}
|
||||
return (node: any);
|
||||
}
|
||||
|
||||
export function getFirstHydratableChild(
|
||||
parentInstance: Container | Instance,
|
||||
): null | Instance | TextInstance {
|
||||
let next = parentInstance.firstChild;
|
||||
// Skip non-hydratable nodes.
|
||||
while (
|
||||
next &&
|
||||
next.nodeType !== ELEMENT_NODE &&
|
||||
next.nodeType !== TEXT_NODE
|
||||
) {
|
||||
next = next.nextSibling;
|
||||
}
|
||||
return (next: any);
|
||||
}
|
||||
|
||||
export function hydrateInstance(
|
||||
instance: Instance,
|
||||
type: string,
|
||||
props: Props,
|
||||
rootContainerInstance: Container,
|
||||
hostContext: HostContext,
|
||||
internalInstanceHandle: Object,
|
||||
): null | Array<mixed> {
|
||||
precacheFiberNode(internalInstanceHandle, instance);
|
||||
// TODO: Possibly defer this until the commit phase where all the events
|
||||
// get attached.
|
||||
updateFiberProps(instance, props);
|
||||
let parentNamespace: string;
|
||||
if (__DEV__) {
|
||||
const hostContextDev = ((hostContext: any): HostContextDev);
|
||||
parentNamespace = hostContextDev.namespace;
|
||||
} else {
|
||||
parentNamespace = ((hostContext: any): HostContextProd);
|
||||
}
|
||||
return diffHydratedProperties(
|
||||
instance,
|
||||
type,
|
||||
props,
|
||||
parentNamespace,
|
||||
rootContainerInstance,
|
||||
);
|
||||
}
|
||||
|
||||
export function hydrateTextInstance(
|
||||
textInstance: TextInstance,
|
||||
text: string,
|
||||
internalInstanceHandle: Object,
|
||||
): boolean {
|
||||
precacheFiberNode(internalInstanceHandle, textInstance);
|
||||
return diffHydratedText(textInstance, text);
|
||||
}
|
||||
|
||||
export function didNotMatchHydratedContainerTextInstance(
|
||||
parentContainer: Container,
|
||||
textInstance: TextInstance,
|
||||
text: string,
|
||||
) {
|
||||
if (__DEV__) {
|
||||
warnForUnmatchedText(textInstance, text);
|
||||
}
|
||||
}
|
||||
|
||||
export function didNotMatchHydratedTextInstance(
|
||||
parentType: string,
|
||||
parentProps: Props,
|
||||
parentInstance: Instance,
|
||||
textInstance: TextInstance,
|
||||
text: string,
|
||||
) {
|
||||
if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
|
||||
warnForUnmatchedText(textInstance, text);
|
||||
}
|
||||
}
|
||||
|
||||
export function didNotHydrateContainerInstance(
|
||||
parentContainer: Container,
|
||||
instance: Instance | TextInstance,
|
||||
) {
|
||||
if (__DEV__) {
|
||||
if (instance.nodeType === 1) {
|
||||
warnForDeletedHydratableElement(parentContainer, (instance: any));
|
||||
} else {
|
||||
warnForDeletedHydratableText(parentContainer, (instance: any));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function didNotHydrateInstance(
|
||||
parentType: string,
|
||||
parentProps: Props,
|
||||
parentInstance: Instance,
|
||||
instance: Instance | TextInstance,
|
||||
) {
|
||||
if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
|
||||
if (instance.nodeType === 1) {
|
||||
warnForDeletedHydratableElement(parentInstance, (instance: any));
|
||||
} else {
|
||||
warnForDeletedHydratableText(parentInstance, (instance: any));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function didNotFindHydratableContainerInstance(
|
||||
parentContainer: Container,
|
||||
type: string,
|
||||
props: Props,
|
||||
) {
|
||||
if (__DEV__) {
|
||||
warnForInsertedHydratedElement(parentContainer, type, props);
|
||||
}
|
||||
}
|
||||
|
||||
export function didNotFindHydratableContainerTextInstance(
|
||||
parentContainer: Container,
|
||||
text: string,
|
||||
) {
|
||||
if (__DEV__) {
|
||||
warnForInsertedHydratedText(parentContainer, text);
|
||||
}
|
||||
}
|
||||
|
||||
export function didNotFindHydratableInstance(
|
||||
parentType: string,
|
||||
parentProps: Props,
|
||||
parentInstance: Instance,
|
||||
type: string,
|
||||
props: Props,
|
||||
) {
|
||||
if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
|
||||
warnForInsertedHydratedElement(parentInstance, type, props);
|
||||
}
|
||||
}
|
||||
|
||||
export function didNotFindHydratableTextInstance(
|
||||
parentType: string,
|
||||
parentProps: Props,
|
||||
parentInstance: Instance,
|
||||
text: string,
|
||||
) {
|
||||
if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
|
||||
warnForInsertedHydratedText(parentInstance, text);
|
||||
}
|
||||
}
|
||||
@@ -5,11 +5,48 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import containsNode from 'fbjs/lib/containsNode';
|
||||
import getActiveElement from 'fbjs/lib/getActiveElement';
|
||||
import getActiveElement from './getActiveElement';
|
||||
|
||||
import * as ReactDOMSelection from './ReactDOMSelection';
|
||||
import {ELEMENT_NODE} from '../shared/HTMLNodeType';
|
||||
import {ELEMENT_NODE, TEXT_NODE} from '../shared/HTMLNodeType';
|
||||
|
||||
// TODO: this code is originally inlined from fbjs.
|
||||
// It is likely that we don't actually need all these checks
|
||||
// for the particular use case in this file.
|
||||
function isNode(object) {
|
||||
const doc = object ? object.ownerDocument || object : document;
|
||||
const defaultView = doc.defaultView || window;
|
||||
return !!(
|
||||
object &&
|
||||
(typeof defaultView.Node === 'function'
|
||||
? object instanceof defaultView.Node
|
||||
: typeof object === 'object' &&
|
||||
typeof object.nodeType === 'number' &&
|
||||
typeof object.nodeName === 'string')
|
||||
);
|
||||
}
|
||||
|
||||
function isTextNode(object) {
|
||||
return isNode(object) && object.nodeType === TEXT_NODE;
|
||||
}
|
||||
|
||||
function containsNode(outerNode, innerNode) {
|
||||
if (!outerNode || !innerNode) {
|
||||
return false;
|
||||
} else if (outerNode === innerNode) {
|
||||
return true;
|
||||
} else if (isTextNode(outerNode)) {
|
||||
return false;
|
||||
} else if (isTextNode(innerNode)) {
|
||||
return containsNode(outerNode, innerNode.parentNode);
|
||||
} else if ('contains' in outerNode) {
|
||||
return outerNode.contains(innerNode);
|
||||
} else if (outerNode.compareDocumentPosition) {
|
||||
return !!(outerNode.compareDocumentPosition(innerNode) & 16);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function isInDocument(node) {
|
||||
return containsNode(document.documentElement, node);
|
||||
@@ -22,11 +59,21 @@ function isInDocument(node) {
|
||||
* Input selection module for React.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @hasSelectionCapabilities: we get the element types that support selection
|
||||
* from https://html.spec.whatwg.org/#do-not-apply, looking at `selectionStart`
|
||||
* and `selectionEnd` rows.
|
||||
*/
|
||||
export function hasSelectionCapabilities(elem) {
|
||||
const nodeName = elem && elem.nodeName && elem.nodeName.toLowerCase();
|
||||
return (
|
||||
nodeName &&
|
||||
((nodeName === 'input' && elem.type === 'text') ||
|
||||
((nodeName === 'input' &&
|
||||
(elem.type === 'text' ||
|
||||
elem.type === 'search' ||
|
||||
elem.type === 'tel' ||
|
||||
elem.type === 'url' ||
|
||||
elem.type === 'password')) ||
|
||||
nodeName === 'textarea' ||
|
||||
elem.contentEditable === 'true')
|
||||
);
|
||||
@@ -52,7 +99,10 @@ export function restoreSelection(priorSelectionInformation) {
|
||||
const priorFocusedElem = priorSelectionInformation.focusedElem;
|
||||
const priorSelectionRange = priorSelectionInformation.selectionRange;
|
||||
if (curFocusedElem !== priorFocusedElem && isInDocument(priorFocusedElem)) {
|
||||
if (hasSelectionCapabilities(priorFocusedElem)) {
|
||||
if (
|
||||
priorSelectionRange !== null &&
|
||||
hasSelectionCapabilities(priorFocusedElem)
|
||||
) {
|
||||
setSelection(priorFocusedElem, priorSelectionRange);
|
||||
}
|
||||
|
||||
@@ -69,7 +119,9 @@ export function restoreSelection(priorSelectionInformation) {
|
||||
}
|
||||
}
|
||||
|
||||
priorFocusedElem.focus();
|
||||
if (typeof priorFocusedElem.focus === 'function') {
|
||||
priorFocusedElem.focus();
|
||||
}
|
||||
|
||||
for (let i = 0; i < ancestors.length; i++) {
|
||||
const info = ancestors[i];
|
||||
|
||||
20
packages/react-dom/src/client/getActiveElement.js
vendored
Normal file
20
packages/react-dom/src/client/getActiveElement.js
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Copyright (c) 2015-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.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
export default function getActiveElement(doc: ?Document): ?Element {
|
||||
doc = doc || (typeof document !== 'undefined' ? document : undefined);
|
||||
if (typeof doc === 'undefined') {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return doc.activeElement || doc.body;
|
||||
} catch (e) {
|
||||
return doc.body;
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment';
|
||||
import {canUseDOM} from 'shared/ExecutionEnvironment';
|
||||
|
||||
let contentKey = null;
|
||||
|
||||
@@ -16,7 +16,7 @@ let contentKey = null;
|
||||
* @internal
|
||||
*/
|
||||
function getTextContentAccessor() {
|
||||
if (!contentKey && ExecutionEnvironment.canUseDOM) {
|
||||
if (!contentKey && canUseDOM) {
|
||||
// Prefer textContent to innerText because many browsers support both but
|
||||
// SVG <text> elements don't support innerText even when <div> does.
|
||||
contentKey =
|
||||
|
||||
@@ -63,20 +63,21 @@ function trackValueOnNode(node: any): ?ValueTracker {
|
||||
// (needed for certain tests that spyOn input values and Safari)
|
||||
if (
|
||||
node.hasOwnProperty(valueField) ||
|
||||
typeof descriptor === 'undefined' ||
|
||||
typeof descriptor.get !== 'function' ||
|
||||
typeof descriptor.set !== 'function'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {get, set} = descriptor;
|
||||
Object.defineProperty(node, valueField, {
|
||||
configurable: true,
|
||||
get: function() {
|
||||
return descriptor.get.call(this);
|
||||
return get.call(this);
|
||||
},
|
||||
set: function(value) {
|
||||
currentValue = '' + value;
|
||||
descriptor.set.call(this, value);
|
||||
set.call(this, value);
|
||||
},
|
||||
});
|
||||
// We could've passed this the first time
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user