Compare commits
225 Commits
v16.3.0
...
16.4.2-dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
54adb2674a | ||
|
|
d922ed2cf2 | ||
|
|
5b19684fc3 | ||
|
|
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 | ||
|
|
3232616348 | ||
|
|
6494f6b6b8 | ||
|
|
82f67d65fd | ||
|
|
66c44a7bc3 | ||
|
|
1e97a71a82 | ||
|
|
2e1cc28027 | ||
|
|
a4cef29703 | ||
|
|
1591c8ebab | ||
|
|
5dfbfe9da7 | ||
|
|
c27a99812e | ||
|
|
915bb5321a | ||
|
|
3e9515eede | ||
|
|
b8461524db | ||
|
|
3eae866e03 | ||
|
|
52afbe0ebb | ||
|
|
725c054d4d | ||
|
|
b99d0b1416 | ||
|
|
b6e0512a81 | ||
|
|
40d07724fc | ||
|
|
933f882a9d | ||
|
|
2f7bca0eb2 | ||
|
|
f88deda83b | ||
|
|
8dfb057881 | ||
|
|
76b4ba0129 | ||
|
|
ea37545037 | ||
|
|
20c5d97bb6 | ||
|
|
181747a6cc | ||
|
|
bc753a716e | ||
|
|
6bf2797d6c | ||
|
|
5b16b39508 | ||
|
|
cf649b40a5 | ||
|
|
7a3416f275 | ||
|
|
27535e7bfc | ||
|
|
8ec0e4a99d | ||
|
|
d328e362e8 | ||
|
|
e932e321a8 | ||
|
|
5e3706cca0 | ||
|
|
1c2876d5b5 | ||
|
|
b15b165e07 | ||
|
|
dc059579c3 | ||
|
|
787b343f67 | ||
|
|
2279843ef9 | ||
|
|
a2cc3c38e2 | ||
|
|
36c2939372 | ||
|
|
0f2f90bd9a | ||
|
|
da4e85567b | ||
|
|
eb6e752cab | ||
|
|
ba245f6f9b | ||
|
|
6f2ea73978 | ||
|
|
4ccf58a94d | ||
|
|
7a27ebd52a | ||
|
|
6b99c6f9d3 | ||
|
|
59dac9d7a6 | ||
|
|
0c80977061 | ||
|
|
fa8e67893f | ||
|
|
59b39056d9 | ||
|
|
18ba36d891 | ||
|
|
43044757e5 | ||
|
|
2c3f5fb97b |
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
|
||||
|
||||
157
CHANGELOG.md
157
CHANGELOG.md
@@ -7,22 +7,175 @@
|
||||
|
||||
### React
|
||||
|
||||
* Fix a false positive warning in IE11 when using `React.Fragment`. ([@XaveScor](https://github.com/XaveScor) in [#11823](https://github.com/facebook/react/pull/11823))
|
||||
* 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))
|
||||
|
||||
</details>
|
||||
|
||||
## 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
|
||||
|
||||
* Fix a false positive warning in IE11 when using `Fragment`. ([@heikkilamarko](https://github.com/heikkilamarko) in [#12504](https://github.com/facebook/react/pull/12504))
|
||||
* Prefix a private API. ([@Andarist](https://github.com/Andarist) in [#12501](https://github.com/facebook/react/pull/12501))
|
||||
* Improve the warning when calling `setState()` in constructor. ([@gaearon](https://github.com/gaearon) in [#12532](https://github.com/facebook/react/pull/12532))
|
||||
|
||||
### React DOM
|
||||
|
||||
* Fix `getDerivedStateFromProps()` not getting applied in some cases. ([@acdlite](https://github.com/acdlite) in [#12528](https://github.com/facebook/react/pull/12528))
|
||||
* Fix a performance regression in development mode. ([@gaearon](https://github.com/gaearon) in [#12510](https://github.com/facebook/react/pull/12510))
|
||||
* Fix error handling bugs in development mode. ([@gaearon](https://github.com/gaearon) and [@acdlite](https://github.com/acdlite) in [#12508](https://github.com/facebook/react/pull/12508))
|
||||
* Improve user timing API messages for profiling. ([@flarnie](https://github.com/flarnie) in [#12384](https://github.com/facebook/react/pull/12384))
|
||||
|
||||
### Create Subscription
|
||||
|
||||
* Set the package version to be in sync with React releases. ([@bvaughn](https://github.com/bvaughn) in [#12526](https://github.com/facebook/react/pull/12526))
|
||||
* Add a peer dependency on React 16.3+. ([@NMinhNguyen](https://github.com/NMinhNguyen) in [#12496](https://github.com/facebook/react/pull/12496))
|
||||
|
||||
## 16.3.0 (March 29, 2018)
|
||||
|
||||
### React
|
||||
|
||||
* Add a new officially supported context API. ([@acdlite](https://github.com/acdlite) in [#11818](https://github.com/facebook/react/pull/11818))
|
||||
* Add a new `React.createRef()` API as an ergonomic alternative to callback refs. ([@trueadm](https://github.com/trueadm) in [#12162](https://github.com/facebook/react/pull/12162))
|
||||
* Add a new `React.forwardRef()` API to let components forward their refs to a child. ([@bvaughn](https://github.com/bvaughn) in [#12346](https://github.com/facebook/react/pull/12346))
|
||||
* Fix a false positive warning in IE11 when using `React.Fragment`. ([@XaveScor](https://github.com/XaveScor) in [#11823](https://github.com/facebook/react/pull/11823))
|
||||
* Replace `React.unstable_AsyncComponent` with `React.unstable_AsyncMode`. ([@acdlite](https://github.com/acdlite) in [#12117](https://github.com/facebook/react/pull/12117))
|
||||
* Improve the error message when calling `setState()` on an unmounted component. ([@sophiebits](https://github.com/sophiebits) in [#12347](https://github.com/facebook/react/pull/12347))
|
||||
|
||||
### React DOM
|
||||
|
||||
* Add a new `getDerivedStateFromProps()` lifecycle and `UNSAFE_` aliases for the legacy lifecycles. ([@bvaughn](https://github.com/bvaughn) in [#12028](https://github.com/facebook/react/pull/12028))
|
||||
* Add a new `getSnapshotBeforeUpdate()` lifecycle. ([@bvaughn](https://github.com/bvaughn) in [#12404](https://github.com/facebook/react/pull/12404))
|
||||
* Add a new `<React.StrictMode>` wrapper to help prepare apps for async rendering. ([@bvaughn](https://github.com/bvaughn) in [#12083](https://github.com/facebook/react/pull/12083))
|
||||
* Add support for `onLoad` and `onError` events on the `<link>` tag. ([@roderickhsiao](https://github.com/roderickhsiao) in [#11825](https://github.com/facebook/react/pull/11825))
|
||||
* Add support for `noModule` boolean attribute on the `<script>` tag. ([@aweary](https://github.com/aweary) in [#11900](https://github.com/facebook/react/pull/11900))
|
||||
* Fix minor DOM input bugs in IE and Safari. ([@nhunzaker](https://github.com/nhunzaker) in [#11534](https://github.com/facebook/react/pull/11534))
|
||||
* Correctly detect Ctrl + Enter in `onKeyPress` in more browsers. ([@nstraub](https://github.com/nstraub) in [#10514](https://github.com/facebook/react/pull/10514))
|
||||
* Fix containing elements getting focused on SSR markup mismatch. ([@koba04](https://github.com/koba04) in [#11737](https://github.com/facebook/react/pull/11737))
|
||||
* Fix `value` and `defaultValue` to ignore Symbol values. ([@nhunzaker](https://github.com/nhunzaker) in [#11741](https://github.com/facebook/react/pull/11741))
|
||||
* Fix refs to class components not getting cleaned up when the attribute is removed. ([@bvaughn](https://github.com/bvaughn) in [#12178](https://github.com/facebook/react/pull/12178))
|
||||
* Fix an IE/Edge issue when rendering inputs into a different window. ([@M-ZubairAhmed](https://github.com/M-ZubairAhmed) in [#11870](https://github.com/facebook/react/pull/11870))
|
||||
* Throw with a meaningful message if the component runs after jsdom has been destroyed. ([@gaearon](https://github.com/gaearon) in [#11677](https://github.com/facebook/react/pull/11677))
|
||||
* Don't crash if there is a global variable called `opera` with a `null` value. [@alisherdavronov](https://github.com/alisherdavronov) in [#11854](https://github.com/facebook/react/pull/11854))
|
||||
* Don't check for old versions of Opera. ([@skiritsis](https://github.com/skiritsis) in [#11921](https://github.com/facebook/react/pull/11921))
|
||||
* Deduplicate warning messages about `<option selected>`. ([@watadarkstar](https://github.com/watadarkstar) in [#11821](https://github.com/facebook/react/pull/11821))
|
||||
* Deduplicate warning messages about invalid callback. ([@yenshih](https://github.com/yenshih) in [#11833](https://github.com/facebook/react/pull/11833))
|
||||
* Deprecate `ReactDOM.unstable_createPortal()` in favor of `ReactDOM.createPortal()`. ([@prometheansacrifice](https://github.com/prometheansacrifice) in [#11747](https://github.com/facebook/react/pull/11747))
|
||||
* Don't emit User Timing entries for context types. ([@abhaynikam](https://github.com/abhaynikam) in [#12250](https://github.com/facebook/react/pull/12250))
|
||||
* Improve the error message when context consumer child isn't a function. ([@raunofreiberg](https://github.com/raunofreiberg) in [#12267](https://github.com/facebook/react/pull/12267))
|
||||
* Improve the error message when adding a ref to a functional component. ([@skiritsis](https://github.com/skiritsis) in [#11782](https://github.com/facebook/react/pull/11782))
|
||||
|
||||
### React DOM Server
|
||||
|
||||
* Prevent an infinite loop when attempting to render portals with SSR. ([@gaearon](https://github.com/gaearon) in [#11709](https://github.com/facebook/react/pull/11709))
|
||||
* Warn if a class doesn't extend `React.Component`. ([@wyze](https://github.com/wyze) in [#11993](https://github.com/facebook/react/pull/11993))
|
||||
* Fix an issue with `this.state` of different components getting mixed up. ([@sophiebits](https://github.com/sophiebits) in [#12323](https://github.com/facebook/react/pull/12323))
|
||||
* Provide a better message when component type is undefined. ([@HeroProtagonist](https://github.com/HeroProtagonist) in [#11966](https://github.com/facebook/react/pull/11966))
|
||||
|
||||
</details>
|
||||
## React Test Renderer
|
||||
|
||||
* Fix handling of fragments in `toTree()`. ([@maciej-ka](https://github.com/maciej-ka) in [#12107](https://github.com/facebook/react/pull/12107) and [@gaearon](https://github.com/gaearon) in [#12154](https://github.com/facebook/react/pull/12154))
|
||||
* Shallow renderer should assign state to `null` for components that don't set it. ([@jwbay](https://github.com/jwbay) in [#11965](https://github.com/facebook/react/pull/11965))
|
||||
* Shallow renderer should filter legacy context according to `contextTypes`. ([@koba04](https://github.com/koba04) in [#11922](https://github.com/facebook/react/pull/11922))
|
||||
* Add an unstable API for testing asynchronous rendering. ([@acdlite](https://github.com/acdlite) in [#12478](https://github.com/facebook/react/pull/12478))
|
||||
|
||||
### React Is (New)
|
||||
|
||||
* First release of the [new package](https://github.com/facebook/react/tree/master/packages/react-is) that libraries can use to detect different React node types. ([@bvaughn](https://github.com/bvaughn) in [#12199](https://github.com/facebook/react/pull/12199))
|
||||
* Add `ReactIs.isValidElementType()` to help higher-order components validate their inputs. ([@jamesreggio](https://github.com/jamesreggio) in [#12483](https://github.com/facebook/react/pull/12483))
|
||||
|
||||
### React Lifecycles Compat (New)
|
||||
|
||||
* First release of the [new package](https://github.com/reactjs/react-lifecycles-compat) to help library developers target multiple versions of React. ([@bvaughn](https://github.com/bvaughn) in [#12105](https://github.com/facebook/react/pull/12105))
|
||||
|
||||
### Create Subscription (New)
|
||||
|
||||
* First release of the [new package](https://github.com/facebook/react/tree/master/packages/create-subscription) to subscribe to external data sources safely for async rendering. ([@bvaughn](https://github.com/bvaughn) in [#12325](https://github.com/facebook/react/pull/12325))
|
||||
|
||||
### React Reconciler (Experimental)
|
||||
|
||||
* Expose `react-reconciler/persistent` for building renderers that use persistent data structures. ([@gaearon](https://github.com/gaearon) in [#12156](https://github.com/facebook/react/pull/12156))
|
||||
* Pass host context to `finalizeInitialChildren()`. ([@jquense](https://github.com/jquense) in [#11970](https://github.com/facebook/react/pull/11970))
|
||||
* Remove `useSyncScheduling` from the host config. ([@acdlite](https://github.com/acdlite) in [#11771](https://github.com/facebook/react/pull/11771))
|
||||
|
||||
### React Call Return (Experimental)
|
||||
|
||||
* Fix a crash on updates. ([@rmhartog](https://github.com/rmhartog) in [#11955](https://github.com/facebook/react/pull/11955))
|
||||
|
||||
## 16.2.0 (November 28, 2017)
|
||||
|
||||
|
||||
@@ -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)) {
|
||||
@@ -117,14 +118,12 @@ function git(args) {
|
||||
previousBuildResults
|
||||
);
|
||||
|
||||
const percentToWarrentShowing = 1;
|
||||
const packagesToShow = results
|
||||
.filter(
|
||||
r =>
|
||||
Math.abs(r.prevFileSizeChange) >= percentToWarrentShowing ||
|
||||
Math.abs(r.prevGzipSizeChange) >= percentToWarrentShowing
|
||||
Math.abs(r.prevFileSizeAbsoluteChange) >= 300 || // bytes
|
||||
Math.abs(r.prevGzipSizeAbsoluteChange) >= 100 // bytes
|
||||
)
|
||||
|
||||
.map(r => r.packageName);
|
||||
|
||||
if (packagesToShow.length) {
|
||||
|
||||
@@ -64,6 +64,7 @@ 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>
|
||||
</select>
|
||||
</label>
|
||||
<label htmlFor="react_version">
|
||||
|
||||
@@ -11,6 +11,7 @@ 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';
|
||||
|
||||
const React = window.React;
|
||||
|
||||
@@ -46,6 +47,8 @@ function FixturesPage() {
|
||||
return <CustomElementFixtures />;
|
||||
case '/media-events':
|
||||
return <MediaEventsFixtures />;
|
||||
case '/pointer-events':
|
||||
return <PointerEventsFixtures />;
|
||||
default:
|
||||
return <p>Please select a test fixture.</p>;
|
||||
}
|
||||
|
||||
@@ -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" />
|
||||
|
||||
469
fixtures/schedule/index.html
Normal file
469
fixtures/schedule/index.html
Normal file
@@ -0,0 +1,469 @@
|
||||
<!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 with 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>
|
||||
</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');
|
||||
};
|
||||
}
|
||||
</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
|
||||
26
package.json
26
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"private": true,
|
||||
"version": "16.3.0",
|
||||
"version": "16.4.2",
|
||||
"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",
|
||||
@@ -62,16 +60,17 @@
|
||||
"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 +78,13 @@
|
||||
"ncp": "^2.0.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"platform": "^1.1.0",
|
||||
"prettier": "1.8.1",
|
||||
"prettier": "1.11.1",
|
||||
"prop-types": "^15.6.0",
|
||||
"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 +101,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 +118,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"
|
||||
|
||||
@@ -145,7 +145,7 @@ const ReplaySubscription = createSubscription({
|
||||
|
||||
Below is an example showing how `create-subscription` can be used with native Promises.
|
||||
|
||||
**Note** that it an initial render value of `undefined` is unavoidable due to the fact that Promises provide no way to synchronously read their current value.
|
||||
**Note** that an initial render value of `undefined` is unavoidable due to the fact that Promises provide no way to synchronously read their current value.
|
||||
|
||||
**Note** the lack of a way to "unsubscribe" from a Promise can result in memory leaks as long as something has a reference to the Promise. This should be taken into consideration when determining whether Promises are appropriate to use in this way within your application.
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "create-subscription",
|
||||
"description": "utility for subscribing to external data sources inside React components",
|
||||
"version": "0.1.0",
|
||||
"version": "16.4.2",
|
||||
"repository": "facebook/react",
|
||||
"files": [
|
||||
"LICENSE",
|
||||
@@ -13,7 +13,7 @@
|
||||
"fbjs": "^0.8.16"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.3.0-alpha.1"
|
||||
"react": "^16.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"rxjs": "^5.5.6"
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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];
|
||||
@@ -10,7 +10,7 @@
|
||||
import invariant from 'fbjs/lib/invariant';
|
||||
import warning from 'fbjs/lib/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
|
||||
|
||||
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;
|
||||
@@ -1,151 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2016-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.
|
||||
*/
|
||||
|
||||
const TouchHistoryMath = {
|
||||
/**
|
||||
* This code is optimized and not intended to look beautiful. This allows
|
||||
* computing of touch centroids that have moved after `touchesChangedAfter`
|
||||
* timeStamp. You can compute the current centroid involving all touches
|
||||
* moves after `touchesChangedAfter`, or you can compute the previous
|
||||
* centroid of all touches that were moved after `touchesChangedAfter`.
|
||||
*
|
||||
* @param {TouchHistoryMath} touchHistory Standard Responder touch track
|
||||
* data.
|
||||
* @param {number} touchesChangedAfter timeStamp after which moved touches
|
||||
* are considered "actively moving" - not just "active".
|
||||
* @param {boolean} isXAxis Consider `x` dimension vs. `y` dimension.
|
||||
* @param {boolean} ofCurrent Compute current centroid for actively moving
|
||||
* touches vs. previous centroid of now actively moving touches.
|
||||
* @return {number} value of centroid in specified dimension.
|
||||
*/
|
||||
centroidDimension: function(
|
||||
touchHistory,
|
||||
touchesChangedAfter,
|
||||
isXAxis,
|
||||
ofCurrent,
|
||||
) {
|
||||
const touchBank = touchHistory.touchBank;
|
||||
let total = 0;
|
||||
let count = 0;
|
||||
|
||||
const oneTouchData =
|
||||
touchHistory.numberActiveTouches === 1
|
||||
? touchHistory.touchBank[touchHistory.indexOfSingleActiveTouch]
|
||||
: null;
|
||||
|
||||
if (oneTouchData !== null) {
|
||||
if (
|
||||
oneTouchData.touchActive &&
|
||||
oneTouchData.currentTimeStamp > touchesChangedAfter
|
||||
) {
|
||||
total +=
|
||||
ofCurrent && isXAxis
|
||||
? oneTouchData.currentPageX
|
||||
: ofCurrent && !isXAxis
|
||||
? oneTouchData.currentPageY
|
||||
: !ofCurrent && isXAxis
|
||||
? oneTouchData.previousPageX
|
||||
: oneTouchData.previousPageY;
|
||||
count = 1;
|
||||
}
|
||||
} else {
|
||||
for (let i = 0; i < touchBank.length; i++) {
|
||||
const touchTrack = touchBank[i];
|
||||
if (
|
||||
touchTrack !== null &&
|
||||
touchTrack !== undefined &&
|
||||
touchTrack.touchActive &&
|
||||
touchTrack.currentTimeStamp >= touchesChangedAfter
|
||||
) {
|
||||
let toAdd; // Yuck, program temporarily in invalid state.
|
||||
if (ofCurrent && isXAxis) {
|
||||
toAdd = touchTrack.currentPageX;
|
||||
} else if (ofCurrent && !isXAxis) {
|
||||
toAdd = touchTrack.currentPageY;
|
||||
} else if (!ofCurrent && isXAxis) {
|
||||
toAdd = touchTrack.previousPageX;
|
||||
} else {
|
||||
toAdd = touchTrack.previousPageY;
|
||||
}
|
||||
total += toAdd;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return count > 0 ? total / count : TouchHistoryMath.noCentroid;
|
||||
},
|
||||
|
||||
currentCentroidXOfTouchesChangedAfter: function(
|
||||
touchHistory,
|
||||
touchesChangedAfter,
|
||||
) {
|
||||
return TouchHistoryMath.centroidDimension(
|
||||
touchHistory,
|
||||
touchesChangedAfter,
|
||||
true, // isXAxis
|
||||
true, // ofCurrent
|
||||
);
|
||||
},
|
||||
|
||||
currentCentroidYOfTouchesChangedAfter: function(
|
||||
touchHistory,
|
||||
touchesChangedAfter,
|
||||
) {
|
||||
return TouchHistoryMath.centroidDimension(
|
||||
touchHistory,
|
||||
touchesChangedAfter,
|
||||
false, // isXAxis
|
||||
true, // ofCurrent
|
||||
);
|
||||
},
|
||||
|
||||
previousCentroidXOfTouchesChangedAfter: function(
|
||||
touchHistory,
|
||||
touchesChangedAfter,
|
||||
) {
|
||||
return TouchHistoryMath.centroidDimension(
|
||||
touchHistory,
|
||||
touchesChangedAfter,
|
||||
true, // isXAxis
|
||||
false, // ofCurrent
|
||||
);
|
||||
},
|
||||
|
||||
previousCentroidYOfTouchesChangedAfter: function(
|
||||
touchHistory,
|
||||
touchesChangedAfter,
|
||||
) {
|
||||
return TouchHistoryMath.centroidDimension(
|
||||
touchHistory,
|
||||
touchesChangedAfter,
|
||||
false, // isXAxis
|
||||
false, // ofCurrent
|
||||
);
|
||||
},
|
||||
|
||||
currentCentroidX: function(touchHistory) {
|
||||
return TouchHistoryMath.centroidDimension(
|
||||
touchHistory,
|
||||
0, // touchesChangedAfter
|
||||
true, // isXAxis
|
||||
true, // ofCurrent
|
||||
);
|
||||
},
|
||||
|
||||
currentCentroidY: function(touchHistory) {
|
||||
return TouchHistoryMath.centroidDimension(
|
||||
touchHistory,
|
||||
0, // touchesChangedAfter
|
||||
false, // isXAxis
|
||||
true, // ofCurrent
|
||||
);
|
||||
},
|
||||
|
||||
noCentroid: -1,
|
||||
};
|
||||
|
||||
export default TouchHistoryMath;
|
||||
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.0",
|
||||
"version": "16.4.2",
|
||||
"main": "index.js",
|
||||
"repository": "facebook/react",
|
||||
"keywords": [
|
||||
|
||||
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;
|
||||
|
||||
400
packages/react-art/src/ReactARTHostConfig.js
vendored
Normal file
400
packages/react-art/src/ReactARTHostConfig.js
vendored
Normal file
@@ -0,0 +1,400 @@
|
||||
/**
|
||||
* 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 'fbjs/lib/invariant';
|
||||
import emptyObject from 'fbjs/lib/emptyObject';
|
||||
|
||||
import {TYPES, EVENT_TYPES, childrenAsString} from './ReactARTInternals';
|
||||
|
||||
const pooledTransform = new Transform();
|
||||
|
||||
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 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 emptyObject;
|
||||
}
|
||||
|
||||
export function getChildHostContext() {
|
||||
return emptyObject;
|
||||
}
|
||||
|
||||
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 '';
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
@@ -339,11 +343,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 +409,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 +420,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 +428,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 +440,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.6.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.0",
|
||||
"version": "16.4.2",
|
||||
"description": "React package for working with the DOM.",
|
||||
"main": "index.js",
|
||||
"repository": "facebook/react",
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -214,10 +214,10 @@ describe('ReactComponentLifeCycle', () => {
|
||||
expect(() => {
|
||||
ReactTestUtils.renderIntoDocument(<StatefulComponent />);
|
||||
}).toWarnDev(
|
||||
'Warning: setState(...): Can only update a mounted or ' +
|
||||
'mounting component. This usually means you called setState() on an ' +
|
||||
'unmounted component. This is a no-op.\n\nPlease check the code for the ' +
|
||||
'StatefulComponent component.',
|
||||
"Warning: Can't call setState on a component that is not yet mounted. " +
|
||||
'This is a no-op, but it might indicate a bug in your application. ' +
|
||||
'Instead, assign to `this.state` directly or define a `state = {};` ' +
|
||||
'class property with the desired state in the StatefulComponent component.',
|
||||
);
|
||||
|
||||
// Check deduplication; (no extra warnings should be logged).
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -140,23 +140,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', () => {
|
||||
@@ -225,6 +230,54 @@ describe('ReactCompositeComponent', () => {
|
||||
expect(inputProps.prop).not.toBeDefined();
|
||||
});
|
||||
|
||||
it('should warn about `forceUpdate` on not-yet-mounted components', () => {
|
||||
class MyComponent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.forceUpdate();
|
||||
}
|
||||
render() {
|
||||
return <div />;
|
||||
}
|
||||
}
|
||||
|
||||
const container = document.createElement('div');
|
||||
expect(() => ReactDOM.render(<MyComponent />, container)).toWarnDev(
|
||||
"Warning: Can't call forceUpdate on a component that is not yet mounted. " +
|
||||
'This is a no-op, but it might indicate a bug in your application. ' +
|
||||
'Instead, assign to `this.state` directly or define a `state = {};` ' +
|
||||
'class property with the desired state in the MyComponent component.',
|
||||
);
|
||||
|
||||
// No additional warning should be recorded
|
||||
const container2 = document.createElement('div');
|
||||
ReactDOM.render(<MyComponent />, container2);
|
||||
});
|
||||
|
||||
it('should warn about `setState` on not-yet-mounted components', () => {
|
||||
class MyComponent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.setState();
|
||||
}
|
||||
render() {
|
||||
return <div />;
|
||||
}
|
||||
}
|
||||
|
||||
const container = document.createElement('div');
|
||||
expect(() => ReactDOM.render(<MyComponent />, container)).toWarnDev(
|
||||
"Warning: Can't call setState on a component that is not yet mounted. " +
|
||||
'This is a no-op, but it might indicate a bug in your application. ' +
|
||||
'Instead, assign to `this.state` directly or define a `state = {};` ' +
|
||||
'class property with the desired state in the MyComponent component.',
|
||||
);
|
||||
|
||||
// No additional warning should be recorded
|
||||
const container2 = document.createElement('div');
|
||||
ReactDOM.render(<MyComponent />, container2);
|
||||
});
|
||||
|
||||
it('should warn about `forceUpdate` on unmounted components', () => {
|
||||
const container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
|
||||
@@ -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'],
|
||||
|
||||
155
packages/react-dom/src/__tests__/ReactDOM-test.js
vendored
155
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', () => {
|
||||
@@ -377,6 +371,26 @@ describe('ReactDOM', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('should not crash calling findDOMNode inside a functional component', () => {
|
||||
const container = document.createElement('div');
|
||||
|
||||
class Component extends React.Component {
|
||||
render() {
|
||||
return <div />;
|
||||
}
|
||||
}
|
||||
|
||||
const instance = ReactTestUtils.renderIntoDocument(<Component />);
|
||||
const App = () => {
|
||||
ReactDOM.findDOMNode(instance);
|
||||
return <div />;
|
||||
};
|
||||
|
||||
if (__DEV__) {
|
||||
ReactDOM.render(<App />, container);
|
||||
}
|
||||
});
|
||||
|
||||
it('throws in DEV if jsdom is destroyed by the time setState() is called', () => {
|
||||
class App extends React.Component {
|
||||
state = {x: 1};
|
||||
@@ -419,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(
|
||||
'React depends on 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;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -554,39 +554,174 @@ describe('ReactDOMComponent', () => {
|
||||
expect(stubStyle.color).toEqual('green');
|
||||
});
|
||||
|
||||
it('should reject attribute key injection attack on markup', () => {
|
||||
it('should reject attribute key injection attack on markup for regular DOM (SSR)', () => {
|
||||
expect(() => {
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const container = document.createElement('div');
|
||||
const element = React.createElement(
|
||||
const element1 = React.createElement(
|
||||
'div',
|
||||
{'blah" onclick="beevil" noise="hi': 'selected'},
|
||||
null,
|
||||
);
|
||||
const element2 = React.createElement(
|
||||
'div',
|
||||
{'></div><script>alert("hi")</script>': 'selected'},
|
||||
null,
|
||||
);
|
||||
let result1 = ReactDOMServer.renderToString(element1);
|
||||
let result2 = ReactDOMServer.renderToString(element2);
|
||||
expect(result1.toLowerCase()).not.toContain('onclick');
|
||||
expect(result2.toLowerCase()).not.toContain('script');
|
||||
}
|
||||
}).toWarnDev([
|
||||
'Warning: Invalid attribute name: `blah" onclick="beevil" noise="hi`',
|
||||
'Warning: Invalid attribute name: `></div><script>alert("hi")</script>`',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should reject attribute key injection attack on markup for custom elements (SSR)', () => {
|
||||
expect(() => {
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const element1 = React.createElement(
|
||||
'x-foo-component',
|
||||
{'blah" onclick="beevil" noise="hi': 'selected'},
|
||||
null,
|
||||
);
|
||||
ReactDOM.render(element, container);
|
||||
const element2 = React.createElement(
|
||||
'x-foo-component',
|
||||
{'></x-foo-component><script>alert("hi")</script>': 'selected'},
|
||||
null,
|
||||
);
|
||||
let result1 = ReactDOMServer.renderToString(element1);
|
||||
let result2 = ReactDOMServer.renderToString(element2);
|
||||
expect(result1.toLowerCase()).not.toContain('onclick');
|
||||
expect(result2.toLowerCase()).not.toContain('script');
|
||||
}
|
||||
}).toWarnDev(
|
||||
}).toWarnDev([
|
||||
'Warning: Invalid attribute name: `blah" onclick="beevil" noise="hi`',
|
||||
);
|
||||
'Warning: Invalid attribute name: `></x-foo-component><script>alert("hi")</script>`',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should reject attribute key injection attack on update', () => {
|
||||
it('should reject attribute key injection attack on mount for regular DOM', () => {
|
||||
expect(() => {
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const container = document.createElement('div');
|
||||
ReactDOM.render(
|
||||
React.createElement(
|
||||
'div',
|
||||
{'blah" onclick="beevil" noise="hi': 'selected'},
|
||||
null,
|
||||
),
|
||||
container,
|
||||
);
|
||||
expect(container.firstChild.attributes.length).toBe(0);
|
||||
ReactDOM.unmountComponentAtNode(container);
|
||||
ReactDOM.render(
|
||||
React.createElement(
|
||||
'div',
|
||||
{'></div><script>alert("hi")</script>': 'selected'},
|
||||
null,
|
||||
),
|
||||
container,
|
||||
);
|
||||
expect(container.firstChild.attributes.length).toBe(0);
|
||||
}
|
||||
}).toWarnDev([
|
||||
'Warning: Invalid attribute name: `blah" onclick="beevil" noise="hi`',
|
||||
'Warning: Invalid attribute name: `></div><script>alert("hi")</script>`',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should reject attribute key injection attack on mount for custom elements', () => {
|
||||
expect(() => {
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const container = document.createElement('div');
|
||||
ReactDOM.render(
|
||||
React.createElement(
|
||||
'x-foo-component',
|
||||
{'blah" onclick="beevil" noise="hi': 'selected'},
|
||||
null,
|
||||
),
|
||||
container,
|
||||
);
|
||||
expect(container.firstChild.attributes.length).toBe(0);
|
||||
ReactDOM.unmountComponentAtNode(container);
|
||||
ReactDOM.render(
|
||||
React.createElement(
|
||||
'x-foo-component',
|
||||
{'></x-foo-component><script>alert("hi")</script>': 'selected'},
|
||||
null,
|
||||
),
|
||||
container,
|
||||
);
|
||||
expect(container.firstChild.attributes.length).toBe(0);
|
||||
}
|
||||
}).toWarnDev([
|
||||
'Warning: Invalid attribute name: `blah" onclick="beevil" noise="hi`',
|
||||
'Warning: Invalid attribute name: `></x-foo-component><script>alert("hi")</script>`',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should reject attribute key injection attack on update for regular DOM', () => {
|
||||
expect(() => {
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const container = document.createElement('div');
|
||||
const beforeUpdate = React.createElement('div', {}, null);
|
||||
ReactDOM.render(beforeUpdate, container);
|
||||
ReactDOM.render(
|
||||
React.createElement(
|
||||
'div',
|
||||
{'blah" onclick="beevil" noise="hi': 'selected'},
|
||||
null,
|
||||
),
|
||||
container,
|
||||
);
|
||||
expect(container.firstChild.attributes.length).toBe(0);
|
||||
ReactDOM.render(
|
||||
React.createElement(
|
||||
'div',
|
||||
{'></div><script>alert("hi")</script>': 'selected'},
|
||||
null,
|
||||
),
|
||||
container,
|
||||
);
|
||||
expect(container.firstChild.attributes.length).toBe(0);
|
||||
}
|
||||
}).toWarnDev([
|
||||
'Warning: Invalid attribute name: `blah" onclick="beevil" noise="hi`',
|
||||
'Warning: Invalid attribute name: `></div><script>alert("hi")</script>`',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should reject attribute key injection attack on update for custom elements', () => {
|
||||
expect(() => {
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const container = document.createElement('div');
|
||||
const beforeUpdate = React.createElement('x-foo-component', {}, null);
|
||||
ReactDOM.render(beforeUpdate, container);
|
||||
|
||||
const afterUpdate = React.createElement(
|
||||
'x-foo-component',
|
||||
{'blah" onclick="beevil" noise="hi': 'selected'},
|
||||
null,
|
||||
ReactDOM.render(
|
||||
React.createElement(
|
||||
'x-foo-component',
|
||||
{'blah" onclick="beevil" noise="hi': 'selected'},
|
||||
null,
|
||||
),
|
||||
container,
|
||||
);
|
||||
ReactDOM.render(afterUpdate, container);
|
||||
expect(container.firstChild.attributes.length).toBe(0);
|
||||
ReactDOM.render(
|
||||
React.createElement(
|
||||
'x-foo-component',
|
||||
{'></x-foo-component><script>alert("hi")</script>': 'selected'},
|
||||
null,
|
||||
),
|
||||
container,
|
||||
);
|
||||
expect(container.firstChild.attributes.length).toBe(0);
|
||||
}
|
||||
}).toWarnDev(
|
||||
}).toWarnDev([
|
||||
'Warning: Invalid attribute name: `blah" onclick="beevil" noise="hi`',
|
||||
);
|
||||
'Warning: Invalid attribute name: `></x-foo-component><script>alert("hi")</script>`',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should update arbitrary attributes for tags containing dashes', () => {
|
||||
@@ -738,28 +873,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 +903,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 +912,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 +947,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 +985,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 +1119,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');
|
||||
}
|
||||
});
|
||||
@@ -1000,7 +1135,11 @@ describe('ReactDOMComponent', () => {
|
||||
|
||||
expect(() => {
|
||||
returnedValue = ReactDOMServer.renderToString(<Container />);
|
||||
}).toWarnDev('<BR /> is using uppercase HTML.');
|
||||
}).toWarnDev(
|
||||
'<BR /> is using incorrect casing. ' +
|
||||
'Use PascalCase for React components, ' +
|
||||
'or lowercase for HTML elements.',
|
||||
);
|
||||
expect(returnedValue).not.toContain('</BR>');
|
||||
});
|
||||
|
||||
@@ -1012,7 +1151,11 @@ describe('ReactDOMComponent', () => {
|
||||
|
||||
expect(() =>
|
||||
ReactTestUtils.renderIntoDocument(React.createElement('IMG')),
|
||||
).toWarnDev('<IMG /> is using uppercase HTML.');
|
||||
).toWarnDev(
|
||||
'<IMG /> is using incorrect casing. ' +
|
||||
'Use PascalCase for React components, ' +
|
||||
'or lowercase for HTML elements.',
|
||||
);
|
||||
});
|
||||
|
||||
it('should warn on props reserved for future use', () => {
|
||||
@@ -1059,7 +1202,9 @@ describe('ReactDOMComponent', () => {
|
||||
expect(() =>
|
||||
ReactTestUtils.renderIntoDocument(<hasOwnProperty />),
|
||||
).toWarnDev([
|
||||
'<hasOwnProperty /> is using uppercase HTML',
|
||||
'<hasOwnProperty /> is using incorrect casing. ' +
|
||||
'Use PascalCase for React components, ' +
|
||||
'or lowercase for HTML elements.',
|
||||
'The tag <hasOwnProperty> is unrecognized in this browser',
|
||||
]);
|
||||
} finally {
|
||||
@@ -1305,7 +1450,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();
|
||||
});
|
||||
|
||||
@@ -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}>
|
||||
@@ -476,7 +460,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 +474,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 +584,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 +630,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();
|
||||
});
|
||||
|
||||
@@ -348,9 +348,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 +365,7 @@ describe('ReactDOMServerIntegration', () => {
|
||||
render() {
|
||||
return (
|
||||
<input
|
||||
type={this.props.type}
|
||||
value={this.state.value}
|
||||
onChange={this.handleChange.bind(this)}
|
||||
/>
|
||||
@@ -551,6 +556,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', () => {
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
@@ -176,6 +174,19 @@ describe('ReactDOMServer', () => {
|
||||
(__DEV__ ? '\n in iframe (at **)' : ''),
|
||||
);
|
||||
});
|
||||
|
||||
it('should not crash on poisoned hasOwnProperty', () => {
|
||||
let html;
|
||||
expect(
|
||||
() =>
|
||||
(html = ReactDOMServer.renderToString(
|
||||
<div hasOwnProperty="poison">
|
||||
<span unknown="test" />
|
||||
</div>,
|
||||
)),
|
||||
).toWarnDev(['React does not recognize the `hasOwnProperty` prop']);
|
||||
expect(html).toContain('<span unknown="test">');
|
||||
});
|
||||
});
|
||||
|
||||
describe('renderToStaticMarkup', () => {
|
||||
@@ -602,11 +613,13 @@ describe('ReactDOMServer', () => {
|
||||
</div>,
|
||||
),
|
||||
).toWarnDev([
|
||||
'Warning: <inPUT /> is using uppercase HTML. Always use lowercase ' +
|
||||
'HTML tags in React.',
|
||||
'Warning: <inPUT /> is using incorrect casing. ' +
|
||||
'Use PascalCase for React components, ' +
|
||||
'or lowercase for HTML elements.',
|
||||
// linearGradient doesn't warn
|
||||
'Warning: <iFrame /> is using uppercase HTML. Always use lowercase ' +
|
||||
'HTML tags in React.',
|
||||
'Warning: <iFrame /> is using incorrect casing. ' +
|
||||
'Use PascalCase for React components, ' +
|
||||
'or lowercase for HTML elements.',
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -621,25 +634,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",
|
||||
|
||||
@@ -65,9 +65,7 @@ describe('findDOMNode', () => {
|
||||
it('findDOMNode should reject random objects', () => {
|
||||
expect(function() {
|
||||
ReactDOM.findDOMNode({foo: 'bar'});
|
||||
}).toThrowError(
|
||||
'Element appears to be neither ReactComponent nor DOMNode. Keys: foo',
|
||||
);
|
||||
}).toThrowError('Argument appears to not be a ReactComponent. Keys: foo');
|
||||
});
|
||||
|
||||
it('findDOMNode should reject unmounted objects with render func', () => {
|
||||
|
||||
57
packages/react-dom/src/__tests__/refs-test.js
vendored
57
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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
565
packages/react-dom/src/client/ReactDOM.js
vendored
565
packages/react-dom/src/client/ReactDOM.js
vendored
@@ -14,12 +14,12 @@ 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 * as ReactGenericBatching from 'events/ReactGenericBatching';
|
||||
@@ -29,7 +29,6 @@ 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';
|
||||
@@ -38,49 +37,27 @@ import warning from 'fbjs/lib/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;
|
||||
@@ -1147,20 +607,7 @@ const ReactDOM: Object = {
|
||||
return (componentOrElement: any);
|
||||
}
|
||||
|
||||
const inst = ReactInstanceMap.get(componentOrElement);
|
||||
if (inst) {
|
||||
return DOMRenderer.findHostInstance(inst);
|
||||
}
|
||||
|
||||
if (typeof componentOrElement.render === 'function') {
|
||||
invariant(false, 'Unable to find node on an unmounted component.');
|
||||
} else {
|
||||
invariant(
|
||||
false,
|
||||
'Element appears to be neither ReactComponent nor DOMNode. Keys: %s',
|
||||
Object.keys(componentOrElement),
|
||||
);
|
||||
}
|
||||
return DOMRenderer.findHostInstance(componentOrElement);
|
||||
},
|
||||
|
||||
hydrate(element: React$Node, container: DOMContainer, callback: ?Function) {
|
||||
@@ -1283,6 +730,8 @@ const ReactDOM: Object = {
|
||||
|
||||
unstable_deferredUpdates: DOMRenderer.deferredUpdates,
|
||||
|
||||
unstable_interactiveUpdates: DOMRenderer.interactiveUpdates,
|
||||
|
||||
flushSync: DOMRenderer.flushSync,
|
||||
|
||||
unstable_flushControlled: DOMRenderer.flushControlled,
|
||||
|
||||
@@ -21,8 +21,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 {
|
||||
@@ -355,8 +363,9 @@ export function createElement(
|
||||
// allow <SVG> or <mATH>.
|
||||
warning(
|
||||
isCustomComponentTag || type === type.toLowerCase(),
|
||||
'<%s /> is using uppercase HTML. Always use lowercase HTML tags ' +
|
||||
'in React.',
|
||||
'<%s /> is using incorrect casing. ' +
|
||||
'Use PascalCase for React components, ' +
|
||||
'or lowercase for HTML elements.',
|
||||
type,
|
||||
);
|
||||
}
|
||||
@@ -423,7 +432,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 ' +
|
||||
@@ -439,43 +452,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');
|
||||
@@ -487,7 +498,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');
|
||||
@@ -495,7 +506,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');
|
||||
@@ -519,7 +530,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
|
||||
@@ -813,7 +824,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 ' +
|
||||
@@ -828,36 +843,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');
|
||||
@@ -867,14 +880,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');
|
||||
@@ -1064,7 +1077,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
|
||||
|
||||
@@ -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
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -22,11 +22,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 +62,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 +82,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];
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -5,11 +5,23 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import type {TopLevelTypes} from './BrowserEventConstants';
|
||||
import type {TopLevelType} from 'events/TopLevelEventTypes';
|
||||
|
||||
import {accumulateTwoPhaseDispatches} from 'events/EventPropagators';
|
||||
import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment';
|
||||
|
||||
import {
|
||||
TOP_BLUR,
|
||||
TOP_COMPOSITION_START,
|
||||
TOP_COMPOSITION_END,
|
||||
TOP_COMPOSITION_UPDATE,
|
||||
TOP_KEY_DOWN,
|
||||
TOP_KEY_PRESS,
|
||||
TOP_KEY_UP,
|
||||
TOP_MOUSE_DOWN,
|
||||
TOP_TEXT_INPUT,
|
||||
TOP_PASTE,
|
||||
} from './DOMTopLevelEventTypes';
|
||||
import * as FallbackCompositionState from './FallbackCompositionState';
|
||||
import SyntheticCompositionEvent from './SyntheticCompositionEvent';
|
||||
import SyntheticInputEvent from './SyntheticInputEvent';
|
||||
@@ -50,10 +62,10 @@ const eventTypes = {
|
||||
captured: 'onBeforeInputCapture',
|
||||
},
|
||||
dependencies: [
|
||||
'topCompositionEnd',
|
||||
'topKeyPress',
|
||||
'topTextInput',
|
||||
'topPaste',
|
||||
TOP_COMPOSITION_END,
|
||||
TOP_KEY_PRESS,
|
||||
TOP_TEXT_INPUT,
|
||||
TOP_PASTE,
|
||||
],
|
||||
},
|
||||
compositionEnd: {
|
||||
@@ -62,12 +74,12 @@ const eventTypes = {
|
||||
captured: 'onCompositionEndCapture',
|
||||
},
|
||||
dependencies: [
|
||||
'topBlur',
|
||||
'topCompositionEnd',
|
||||
'topKeyDown',
|
||||
'topKeyPress',
|
||||
'topKeyUp',
|
||||
'topMouseDown',
|
||||
TOP_BLUR,
|
||||
TOP_COMPOSITION_END,
|
||||
TOP_KEY_DOWN,
|
||||
TOP_KEY_PRESS,
|
||||
TOP_KEY_UP,
|
||||
TOP_MOUSE_DOWN,
|
||||
],
|
||||
},
|
||||
compositionStart: {
|
||||
@@ -76,12 +88,12 @@ const eventTypes = {
|
||||
captured: 'onCompositionStartCapture',
|
||||
},
|
||||
dependencies: [
|
||||
'topBlur',
|
||||
'topCompositionStart',
|
||||
'topKeyDown',
|
||||
'topKeyPress',
|
||||
'topKeyUp',
|
||||
'topMouseDown',
|
||||
TOP_BLUR,
|
||||
TOP_COMPOSITION_START,
|
||||
TOP_KEY_DOWN,
|
||||
TOP_KEY_PRESS,
|
||||
TOP_KEY_UP,
|
||||
TOP_MOUSE_DOWN,
|
||||
],
|
||||
},
|
||||
compositionUpdate: {
|
||||
@@ -90,12 +102,12 @@ const eventTypes = {
|
||||
captured: 'onCompositionUpdateCapture',
|
||||
},
|
||||
dependencies: [
|
||||
'topBlur',
|
||||
'topCompositionUpdate',
|
||||
'topKeyDown',
|
||||
'topKeyPress',
|
||||
'topKeyUp',
|
||||
'topMouseDown',
|
||||
TOP_BLUR,
|
||||
TOP_COMPOSITION_UPDATE,
|
||||
TOP_KEY_DOWN,
|
||||
TOP_KEY_PRESS,
|
||||
TOP_KEY_UP,
|
||||
TOP_MOUSE_DOWN,
|
||||
],
|
||||
},
|
||||
};
|
||||
@@ -124,11 +136,11 @@ function isKeypressCommand(nativeEvent) {
|
||||
*/
|
||||
function getCompositionEventType(topLevelType) {
|
||||
switch (topLevelType) {
|
||||
case 'topCompositionStart':
|
||||
case TOP_COMPOSITION_START:
|
||||
return eventTypes.compositionStart;
|
||||
case 'topCompositionEnd':
|
||||
case TOP_COMPOSITION_END:
|
||||
return eventTypes.compositionEnd;
|
||||
case 'topCompositionUpdate':
|
||||
case TOP_COMPOSITION_UPDATE:
|
||||
return eventTypes.compositionUpdate;
|
||||
}
|
||||
}
|
||||
@@ -142,7 +154,7 @@ function getCompositionEventType(topLevelType) {
|
||||
* @return {boolean}
|
||||
*/
|
||||
function isFallbackCompositionStart(topLevelType, nativeEvent) {
|
||||
return topLevelType === 'topKeyDown' && nativeEvent.keyCode === START_KEYCODE;
|
||||
return topLevelType === TOP_KEY_DOWN && nativeEvent.keyCode === START_KEYCODE;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -154,16 +166,16 @@ function isFallbackCompositionStart(topLevelType, nativeEvent) {
|
||||
*/
|
||||
function isFallbackCompositionEnd(topLevelType, nativeEvent) {
|
||||
switch (topLevelType) {
|
||||
case 'topKeyUp':
|
||||
case TOP_KEY_UP:
|
||||
// Command keys insert or clear IME input.
|
||||
return END_KEYCODES.indexOf(nativeEvent.keyCode) !== -1;
|
||||
case 'topKeyDown':
|
||||
case TOP_KEY_DOWN:
|
||||
// Expect IME keyCode on each keydown. If we get any other
|
||||
// code we must have exited earlier.
|
||||
return nativeEvent.keyCode !== START_KEYCODE;
|
||||
case 'topKeyPress':
|
||||
case 'topMouseDown':
|
||||
case 'topBlur':
|
||||
case TOP_KEY_PRESS:
|
||||
case TOP_MOUSE_DOWN:
|
||||
case TOP_BLUR:
|
||||
// Events are not possible without cancelling IME.
|
||||
return true;
|
||||
default:
|
||||
@@ -252,15 +264,15 @@ function extractCompositionEvent(
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {TopLevelTypes} topLevelType Record from `BrowserEventConstants`.
|
||||
* @param {TopLevelType} topLevelType Number from `TopLevelType`.
|
||||
* @param {object} nativeEvent Native browser event.
|
||||
* @return {?string} The string corresponding to this `beforeInput` event.
|
||||
*/
|
||||
function getNativeBeforeInputChars(topLevelType: TopLevelTypes, nativeEvent) {
|
||||
function getNativeBeforeInputChars(topLevelType: TopLevelType, nativeEvent) {
|
||||
switch (topLevelType) {
|
||||
case 'topCompositionEnd':
|
||||
case TOP_COMPOSITION_END:
|
||||
return getDataFromCustomEvent(nativeEvent);
|
||||
case 'topKeyPress':
|
||||
case TOP_KEY_PRESS:
|
||||
/**
|
||||
* If native `textInput` events are available, our goal is to make
|
||||
* use of them. However, there is a special case: the spacebar key.
|
||||
@@ -283,7 +295,7 @@ function getNativeBeforeInputChars(topLevelType: TopLevelTypes, nativeEvent) {
|
||||
hasSpaceKeypress = true;
|
||||
return SPACEBAR_CHAR;
|
||||
|
||||
case 'topTextInput':
|
||||
case TOP_TEXT_INPUT:
|
||||
// Record the characters to be added to the DOM.
|
||||
const chars = nativeEvent.data;
|
||||
|
||||
@@ -306,18 +318,18 @@ function getNativeBeforeInputChars(topLevelType: TopLevelTypes, nativeEvent) {
|
||||
* For browsers that do not provide the `textInput` event, extract the
|
||||
* appropriate string to use for SyntheticInputEvent.
|
||||
*
|
||||
* @param {string} topLevelType Record from `BrowserEventConstants`.
|
||||
* @param {number} topLevelType Number from `TopLevelEventTypes`.
|
||||
* @param {object} nativeEvent Native browser event.
|
||||
* @return {?string} The fallback string for this `beforeInput` event.
|
||||
*/
|
||||
function getFallbackBeforeInputChars(topLevelType: TopLevelTypes, nativeEvent) {
|
||||
function getFallbackBeforeInputChars(topLevelType: TopLevelType, nativeEvent) {
|
||||
// If we are currently composing (IME) and using a fallback to do so,
|
||||
// try to extract the composed characters from the fallback object.
|
||||
// If composition event is available, we extract a string only at
|
||||
// compositionevent, otherwise extract it at fallback events.
|
||||
if (isComposing) {
|
||||
if (
|
||||
topLevelType === 'topCompositionEnd' ||
|
||||
topLevelType === TOP_COMPOSITION_END ||
|
||||
(!canUseCompositionEvent &&
|
||||
isFallbackCompositionEnd(topLevelType, nativeEvent))
|
||||
) {
|
||||
@@ -330,11 +342,11 @@ function getFallbackBeforeInputChars(topLevelType: TopLevelTypes, nativeEvent) {
|
||||
}
|
||||
|
||||
switch (topLevelType) {
|
||||
case 'topPaste':
|
||||
case TOP_PASTE:
|
||||
// If a paste event occurs after a keypress, throw out the input
|
||||
// chars. Paste events should not lead to BeforeInput events.
|
||||
return null;
|
||||
case 'topKeyPress':
|
||||
case TOP_KEY_PRESS:
|
||||
/**
|
||||
* As of v27, Firefox may fire keypress events even when no character
|
||||
* will be inserted. A few possibilities:
|
||||
@@ -365,7 +377,7 @@ function getFallbackBeforeInputChars(topLevelType: TopLevelTypes, nativeEvent) {
|
||||
}
|
||||
}
|
||||
return null;
|
||||
case 'topCompositionEnd':
|
||||
case TOP_COMPOSITION_END:
|
||||
return useFallbackCompositionData ? null : nativeEvent.data;
|
||||
default:
|
||||
return null;
|
||||
|
||||
@@ -1,97 +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.
|
||||
*/
|
||||
|
||||
import getVendorPrefixedEventName from './getVendorPrefixedEventName';
|
||||
|
||||
/**
|
||||
* Types of raw signals from the browser caught at the top level.
|
||||
*
|
||||
* For events like 'submit' or audio/video events which don't consistently
|
||||
* bubble (which we trap at a lower node than `document`), binding
|
||||
* at `document` would cause duplicate events so we don't include them here.
|
||||
*/
|
||||
export const topLevelTypes = {
|
||||
topAnimationEnd: getVendorPrefixedEventName('animationend'),
|
||||
topAnimationIteration: getVendorPrefixedEventName('animationiteration'),
|
||||
topAnimationStart: getVendorPrefixedEventName('animationstart'),
|
||||
topBlur: 'blur',
|
||||
topCancel: 'cancel',
|
||||
topChange: 'change',
|
||||
topClick: 'click',
|
||||
topClose: 'close',
|
||||
topCompositionEnd: 'compositionend',
|
||||
topCompositionStart: 'compositionstart',
|
||||
topCompositionUpdate: 'compositionupdate',
|
||||
topContextMenu: 'contextmenu',
|
||||
topCopy: 'copy',
|
||||
topCut: 'cut',
|
||||
topDoubleClick: 'dblclick',
|
||||
topDrag: 'drag',
|
||||
topDragEnd: 'dragend',
|
||||
topDragEnter: 'dragenter',
|
||||
topDragExit: 'dragexit',
|
||||
topDragLeave: 'dragleave',
|
||||
topDragOver: 'dragover',
|
||||
topDragStart: 'dragstart',
|
||||
topDrop: 'drop',
|
||||
topFocus: 'focus',
|
||||
topInput: 'input',
|
||||
topKeyDown: 'keydown',
|
||||
topKeyPress: 'keypress',
|
||||
topKeyUp: 'keyup',
|
||||
topLoad: 'load',
|
||||
topLoadStart: 'loadstart',
|
||||
topMouseDown: 'mousedown',
|
||||
topMouseMove: 'mousemove',
|
||||
topMouseOut: 'mouseout',
|
||||
topMouseOver: 'mouseover',
|
||||
topMouseUp: 'mouseup',
|
||||
topPaste: 'paste',
|
||||
topScroll: 'scroll',
|
||||
topSelectionChange: 'selectionchange',
|
||||
topTextInput: 'textInput',
|
||||
topToggle: 'toggle',
|
||||
topTouchCancel: 'touchcancel',
|
||||
topTouchEnd: 'touchend',
|
||||
topTouchMove: 'touchmove',
|
||||
topTouchStart: 'touchstart',
|
||||
topTransitionEnd: getVendorPrefixedEventName('transitionend'),
|
||||
topWheel: 'wheel',
|
||||
};
|
||||
|
||||
// There are so many media events, it makes sense to just
|
||||
// maintain a list of them. Note these aren't technically
|
||||
// "top-level" since they don't bubble. We should come up
|
||||
// with a better naming convention if we come to refactoring
|
||||
// the event system.
|
||||
export const mediaEventTypes = {
|
||||
topAbort: 'abort',
|
||||
topCanPlay: 'canplay',
|
||||
topCanPlayThrough: 'canplaythrough',
|
||||
topDurationChange: 'durationchange',
|
||||
topEmptied: 'emptied',
|
||||
topEncrypted: 'encrypted',
|
||||
topEnded: 'ended',
|
||||
topError: 'error',
|
||||
topLoadedData: 'loadeddata',
|
||||
topLoadedMetadata: 'loadedmetadata',
|
||||
topLoadStart: 'loadstart',
|
||||
topPause: 'pause',
|
||||
topPlay: 'play',
|
||||
topPlaying: 'playing',
|
||||
topProgress: 'progress',
|
||||
topRateChange: 'ratechange',
|
||||
topSeeked: 'seeked',
|
||||
topSeeking: 'seeking',
|
||||
topStalled: 'stalled',
|
||||
topSuspend: 'suspend',
|
||||
topTimeUpdate: 'timeupdate',
|
||||
topVolumeChange: 'volumechange',
|
||||
topWaiting: 'waiting',
|
||||
};
|
||||
|
||||
export type TopLevelTypes = $Enum<typeof topLevelTypes>;
|
||||
@@ -13,6 +13,16 @@ import SyntheticEvent from 'events/SyntheticEvent';
|
||||
import isTextInputElement from 'shared/isTextInputElement';
|
||||
import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment';
|
||||
|
||||
import {
|
||||
TOP_BLUR,
|
||||
TOP_CHANGE,
|
||||
TOP_CLICK,
|
||||
TOP_FOCUS,
|
||||
TOP_INPUT,
|
||||
TOP_KEY_DOWN,
|
||||
TOP_KEY_UP,
|
||||
TOP_SELECTION_CHANGE,
|
||||
} from './DOMTopLevelEventTypes';
|
||||
import getEventTarget from './getEventTarget';
|
||||
import isEventSupported from './isEventSupported';
|
||||
import {getNodeFromInstance} from '../client/ReactDOMComponentTree';
|
||||
@@ -26,14 +36,14 @@ const eventTypes = {
|
||||
captured: 'onChangeCapture',
|
||||
},
|
||||
dependencies: [
|
||||
'topBlur',
|
||||
'topChange',
|
||||
'topClick',
|
||||
'topFocus',
|
||||
'topInput',
|
||||
'topKeyDown',
|
||||
'topKeyUp',
|
||||
'topSelectionChange',
|
||||
TOP_BLUR,
|
||||
TOP_CHANGE,
|
||||
TOP_CLICK,
|
||||
TOP_FOCUS,
|
||||
TOP_INPUT,
|
||||
TOP_KEY_DOWN,
|
||||
TOP_KEY_UP,
|
||||
TOP_SELECTION_CHANGE,
|
||||
],
|
||||
},
|
||||
};
|
||||
@@ -100,7 +110,7 @@ function getInstIfValueChanged(targetInst) {
|
||||
}
|
||||
|
||||
function getTargetInstForChangeEvent(topLevelType, targetInst) {
|
||||
if (topLevelType === 'topChange') {
|
||||
if (topLevelType === TOP_CHANGE) {
|
||||
return targetInst;
|
||||
}
|
||||
}
|
||||
@@ -155,7 +165,7 @@ function handlePropertyChange(nativeEvent) {
|
||||
}
|
||||
|
||||
function handleEventsForInputEventPolyfill(topLevelType, target, targetInst) {
|
||||
if (topLevelType === 'topFocus') {
|
||||
if (topLevelType === TOP_FOCUS) {
|
||||
// In IE9, propertychange fires for most input events but is buggy and
|
||||
// doesn't fire when text is deleted, but conveniently, selectionchange
|
||||
// appears to fire in all of the remaining cases so we catch those and
|
||||
@@ -168,7 +178,7 @@ function handleEventsForInputEventPolyfill(topLevelType, target, targetInst) {
|
||||
// missed a blur event somehow.
|
||||
stopWatchingForValueChange();
|
||||
startWatchingForValueChange(target, targetInst);
|
||||
} else if (topLevelType === 'topBlur') {
|
||||
} else if (topLevelType === TOP_BLUR) {
|
||||
stopWatchingForValueChange();
|
||||
}
|
||||
}
|
||||
@@ -176,9 +186,9 @@ function handleEventsForInputEventPolyfill(topLevelType, target, targetInst) {
|
||||
// For IE8 and IE9.
|
||||
function getTargetInstForInputEventPolyfill(topLevelType, targetInst) {
|
||||
if (
|
||||
topLevelType === 'topSelectionChange' ||
|
||||
topLevelType === 'topKeyUp' ||
|
||||
topLevelType === 'topKeyDown'
|
||||
topLevelType === TOP_SELECTION_CHANGE ||
|
||||
topLevelType === TOP_KEY_UP ||
|
||||
topLevelType === TOP_KEY_DOWN
|
||||
) {
|
||||
// On the selectionchange event, the target is just document which isn't
|
||||
// helpful for us so just check activeElement instead.
|
||||
@@ -210,25 +220,19 @@ function shouldUseClickEvent(elem) {
|
||||
}
|
||||
|
||||
function getTargetInstForClickEvent(topLevelType, targetInst) {
|
||||
if (topLevelType === 'topClick') {
|
||||
if (topLevelType === TOP_CLICK) {
|
||||
return getInstIfValueChanged(targetInst);
|
||||
}
|
||||
}
|
||||
|
||||
function getTargetInstForInputOrChangeEvent(topLevelType, targetInst) {
|
||||
if (topLevelType === 'topInput' || topLevelType === 'topChange') {
|
||||
if (topLevelType === TOP_INPUT || topLevelType === TOP_CHANGE) {
|
||||
return getInstIfValueChanged(targetInst);
|
||||
}
|
||||
}
|
||||
|
||||
function handleControlledInputBlur(inst, node) {
|
||||
// TODO: In IE, inst is occasionally null. Why?
|
||||
if (inst == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Fiber and ReactDOM keep wrapper state in separate places
|
||||
let state = inst._wrapperState || node._wrapperState;
|
||||
function handleControlledInputBlur(node) {
|
||||
let state = node._wrapperState;
|
||||
|
||||
if (!state || !state.controlled || node.type !== 'number') {
|
||||
return;
|
||||
@@ -292,8 +296,8 @@ const ChangeEventPlugin = {
|
||||
}
|
||||
|
||||
// When blurring, set the value attribute for number inputs
|
||||
if (topLevelType === 'topBlur') {
|
||||
handleControlledInputBlur(targetInst, targetNode);
|
||||
if (topLevelType === TOP_BLUR) {
|
||||
handleControlledInputBlur(targetNode);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
182
packages/react-dom/src/events/DOMTopLevelEventTypes.js
vendored
Normal file
182
packages/react-dom/src/events/DOMTopLevelEventTypes.js
vendored
Normal file
@@ -0,0 +1,182 @@
|
||||
/**
|
||||
* 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 type {DOMTopLevelEventType} from 'events/TopLevelEventTypes';
|
||||
|
||||
import {
|
||||
unsafeCastStringToDOMTopLevelType,
|
||||
unsafeCastDOMTopLevelTypeToString,
|
||||
} from 'events/TopLevelEventTypes';
|
||||
import getVendorPrefixedEventName from './getVendorPrefixedEventName';
|
||||
|
||||
/**
|
||||
* To identify top level events in ReactDOM, we use constants defined by this
|
||||
* module. This is the only module that uses the unsafe* methods to express
|
||||
* that the constants actually correspond to the browser event names. This lets
|
||||
* us save some bundle size by avoiding a top level type -> event name map.
|
||||
* The rest of ReactDOM code should import top level types from this file.
|
||||
*/
|
||||
export const TOP_ABORT = unsafeCastStringToDOMTopLevelType('abort');
|
||||
export const TOP_ANIMATION_END = unsafeCastStringToDOMTopLevelType(
|
||||
getVendorPrefixedEventName('animationend'),
|
||||
);
|
||||
export const TOP_ANIMATION_ITERATION = unsafeCastStringToDOMTopLevelType(
|
||||
getVendorPrefixedEventName('animationiteration'),
|
||||
);
|
||||
export const TOP_ANIMATION_START = unsafeCastStringToDOMTopLevelType(
|
||||
getVendorPrefixedEventName('animationstart'),
|
||||
);
|
||||
export const TOP_BLUR = unsafeCastStringToDOMTopLevelType('blur');
|
||||
export const TOP_CAN_PLAY = unsafeCastStringToDOMTopLevelType('canplay');
|
||||
export const TOP_CAN_PLAY_THROUGH = unsafeCastStringToDOMTopLevelType(
|
||||
'canplaythrough',
|
||||
);
|
||||
export const TOP_CANCEL = unsafeCastStringToDOMTopLevelType('cancel');
|
||||
export const TOP_CHANGE = unsafeCastStringToDOMTopLevelType('change');
|
||||
export const TOP_CLICK = unsafeCastStringToDOMTopLevelType('click');
|
||||
export const TOP_CLOSE = unsafeCastStringToDOMTopLevelType('close');
|
||||
export const TOP_COMPOSITION_END = unsafeCastStringToDOMTopLevelType(
|
||||
'compositionend',
|
||||
);
|
||||
export const TOP_COMPOSITION_START = unsafeCastStringToDOMTopLevelType(
|
||||
'compositionstart',
|
||||
);
|
||||
export const TOP_COMPOSITION_UPDATE = unsafeCastStringToDOMTopLevelType(
|
||||
'compositionupdate',
|
||||
);
|
||||
export const TOP_CONTEXT_MENU = unsafeCastStringToDOMTopLevelType(
|
||||
'contextmenu',
|
||||
);
|
||||
export const TOP_COPY = unsafeCastStringToDOMTopLevelType('copy');
|
||||
export const TOP_CUT = unsafeCastStringToDOMTopLevelType('cut');
|
||||
export const TOP_DOUBLE_CLICK = unsafeCastStringToDOMTopLevelType('dblclick');
|
||||
export const TOP_DRAG = unsafeCastStringToDOMTopLevelType('drag');
|
||||
export const TOP_DRAG_END = unsafeCastStringToDOMTopLevelType('dragend');
|
||||
export const TOP_DRAG_ENTER = unsafeCastStringToDOMTopLevelType('dragenter');
|
||||
export const TOP_DRAG_EXIT = unsafeCastStringToDOMTopLevelType('dragexit');
|
||||
export const TOP_DRAG_LEAVE = unsafeCastStringToDOMTopLevelType('dragleave');
|
||||
export const TOP_DRAG_OVER = unsafeCastStringToDOMTopLevelType('dragover');
|
||||
export const TOP_DRAG_START = unsafeCastStringToDOMTopLevelType('dragstart');
|
||||
export const TOP_DROP = unsafeCastStringToDOMTopLevelType('drop');
|
||||
export const TOP_DURATION_CHANGE = unsafeCastStringToDOMTopLevelType(
|
||||
'durationchange',
|
||||
);
|
||||
export const TOP_EMPTIED = unsafeCastStringToDOMTopLevelType('emptied');
|
||||
export const TOP_ENCRYPTED = unsafeCastStringToDOMTopLevelType('encrypted');
|
||||
export const TOP_ENDED = unsafeCastStringToDOMTopLevelType('ended');
|
||||
export const TOP_ERROR = unsafeCastStringToDOMTopLevelType('error');
|
||||
export const TOP_FOCUS = unsafeCastStringToDOMTopLevelType('focus');
|
||||
export const TOP_GOT_POINTER_CAPTURE = unsafeCastStringToDOMTopLevelType(
|
||||
'gotpointercapture',
|
||||
);
|
||||
export const TOP_INPUT = unsafeCastStringToDOMTopLevelType('input');
|
||||
export const TOP_INVALID = unsafeCastStringToDOMTopLevelType('invalid');
|
||||
export const TOP_KEY_DOWN = unsafeCastStringToDOMTopLevelType('keydown');
|
||||
export const TOP_KEY_PRESS = unsafeCastStringToDOMTopLevelType('keypress');
|
||||
export const TOP_KEY_UP = unsafeCastStringToDOMTopLevelType('keyup');
|
||||
export const TOP_LOAD = unsafeCastStringToDOMTopLevelType('load');
|
||||
export const TOP_LOAD_START = unsafeCastStringToDOMTopLevelType('loadstart');
|
||||
export const TOP_LOADED_DATA = unsafeCastStringToDOMTopLevelType('loadeddata');
|
||||
export const TOP_LOADED_METADATA = unsafeCastStringToDOMTopLevelType(
|
||||
'loadedmetadata',
|
||||
);
|
||||
export const TOP_LOST_POINTER_CAPTURE = unsafeCastStringToDOMTopLevelType(
|
||||
'lostpointercapture',
|
||||
);
|
||||
export const TOP_MOUSE_DOWN = unsafeCastStringToDOMTopLevelType('mousedown');
|
||||
export const TOP_MOUSE_MOVE = unsafeCastStringToDOMTopLevelType('mousemove');
|
||||
export const TOP_MOUSE_OUT = unsafeCastStringToDOMTopLevelType('mouseout');
|
||||
export const TOP_MOUSE_OVER = unsafeCastStringToDOMTopLevelType('mouseover');
|
||||
export const TOP_MOUSE_UP = unsafeCastStringToDOMTopLevelType('mouseup');
|
||||
export const TOP_PASTE = unsafeCastStringToDOMTopLevelType('paste');
|
||||
export const TOP_PAUSE = unsafeCastStringToDOMTopLevelType('pause');
|
||||
export const TOP_PLAY = unsafeCastStringToDOMTopLevelType('play');
|
||||
export const TOP_PLAYING = unsafeCastStringToDOMTopLevelType('playing');
|
||||
export const TOP_POINTER_CANCEL = unsafeCastStringToDOMTopLevelType(
|
||||
'pointercancel',
|
||||
);
|
||||
export const TOP_POINTER_DOWN = unsafeCastStringToDOMTopLevelType(
|
||||
'pointerdown',
|
||||
);
|
||||
export const TOP_POINTER_ENTER = unsafeCastStringToDOMTopLevelType(
|
||||
'pointerenter',
|
||||
);
|
||||
export const TOP_POINTER_LEAVE = unsafeCastStringToDOMTopLevelType(
|
||||
'pointerleave',
|
||||
);
|
||||
export const TOP_POINTER_MOVE = unsafeCastStringToDOMTopLevelType(
|
||||
'pointermove',
|
||||
);
|
||||
export const TOP_POINTER_OUT = unsafeCastStringToDOMTopLevelType('pointerout');
|
||||
export const TOP_POINTER_OVER = unsafeCastStringToDOMTopLevelType(
|
||||
'pointerover',
|
||||
);
|
||||
export const TOP_POINTER_UP = unsafeCastStringToDOMTopLevelType('pointerup');
|
||||
export const TOP_PROGRESS = unsafeCastStringToDOMTopLevelType('progress');
|
||||
export const TOP_RATE_CHANGE = unsafeCastStringToDOMTopLevelType('ratechange');
|
||||
export const TOP_RESET = unsafeCastStringToDOMTopLevelType('reset');
|
||||
export const TOP_SCROLL = unsafeCastStringToDOMTopLevelType('scroll');
|
||||
export const TOP_SEEKED = unsafeCastStringToDOMTopLevelType('seeked');
|
||||
export const TOP_SEEKING = unsafeCastStringToDOMTopLevelType('seeking');
|
||||
export const TOP_SELECTION_CHANGE = unsafeCastStringToDOMTopLevelType(
|
||||
'selectionchange',
|
||||
);
|
||||
export const TOP_STALLED = unsafeCastStringToDOMTopLevelType('stalled');
|
||||
export const TOP_SUBMIT = unsafeCastStringToDOMTopLevelType('submit');
|
||||
export const TOP_SUSPEND = unsafeCastStringToDOMTopLevelType('suspend');
|
||||
export const TOP_TEXT_INPUT = unsafeCastStringToDOMTopLevelType('textInput');
|
||||
export const TOP_TIME_UPDATE = unsafeCastStringToDOMTopLevelType('timeupdate');
|
||||
export const TOP_TOGGLE = unsafeCastStringToDOMTopLevelType('toggle');
|
||||
export const TOP_TOUCH_CANCEL = unsafeCastStringToDOMTopLevelType(
|
||||
'touchcancel',
|
||||
);
|
||||
export const TOP_TOUCH_END = unsafeCastStringToDOMTopLevelType('touchend');
|
||||
export const TOP_TOUCH_MOVE = unsafeCastStringToDOMTopLevelType('touchmove');
|
||||
export const TOP_TOUCH_START = unsafeCastStringToDOMTopLevelType('touchstart');
|
||||
export const TOP_TRANSITION_END = unsafeCastStringToDOMTopLevelType(
|
||||
getVendorPrefixedEventName('transitionend'),
|
||||
);
|
||||
export const TOP_VOLUME_CHANGE = unsafeCastStringToDOMTopLevelType(
|
||||
'volumechange',
|
||||
);
|
||||
export const TOP_WAITING = unsafeCastStringToDOMTopLevelType('waiting');
|
||||
export const TOP_WHEEL = unsafeCastStringToDOMTopLevelType('wheel');
|
||||
|
||||
// List of events that need to be individually attached to media elements.
|
||||
// Note that events in this list will *not* be listened to at the top level
|
||||
// unless they're explicitly whitelisted in `ReactBrowserEventEmitter.listenTo`.
|
||||
export const mediaEventTypes = [
|
||||
TOP_ABORT,
|
||||
TOP_CAN_PLAY,
|
||||
TOP_CAN_PLAY_THROUGH,
|
||||
TOP_DURATION_CHANGE,
|
||||
TOP_EMPTIED,
|
||||
TOP_ENCRYPTED,
|
||||
TOP_ENDED,
|
||||
TOP_ERROR,
|
||||
TOP_LOADED_DATA,
|
||||
TOP_LOADED_METADATA,
|
||||
TOP_LOAD_START,
|
||||
TOP_PAUSE,
|
||||
TOP_PLAY,
|
||||
TOP_PLAYING,
|
||||
TOP_PROGRESS,
|
||||
TOP_RATE_CHANGE,
|
||||
TOP_SEEKED,
|
||||
TOP_SEEKING,
|
||||
TOP_STALLED,
|
||||
TOP_SUSPEND,
|
||||
TOP_TIME_UPDATE,
|
||||
TOP_VOLUME_CHANGE,
|
||||
TOP_WAITING,
|
||||
];
|
||||
|
||||
export function getRawEventName(topLevelType: DOMTopLevelEventType): string {
|
||||
return unsafeCastDOMTopLevelTypeToString(topLevelType);
|
||||
}
|
||||
@@ -7,7 +7,14 @@
|
||||
|
||||
import {accumulateEnterLeaveDispatches} from 'events/EventPropagators';
|
||||
|
||||
import {
|
||||
TOP_MOUSE_OUT,
|
||||
TOP_MOUSE_OVER,
|
||||
TOP_POINTER_OUT,
|
||||
TOP_POINTER_OVER,
|
||||
} from './DOMTopLevelEventTypes';
|
||||
import SyntheticMouseEvent from './SyntheticMouseEvent';
|
||||
import SyntheticPointerEvent from './SyntheticPointerEvent';
|
||||
import {
|
||||
getClosestInstanceFromNode,
|
||||
getNodeFromInstance,
|
||||
@@ -16,11 +23,19 @@ import {
|
||||
const eventTypes = {
|
||||
mouseEnter: {
|
||||
registrationName: 'onMouseEnter',
|
||||
dependencies: ['topMouseOut', 'topMouseOver'],
|
||||
dependencies: [TOP_MOUSE_OUT, TOP_MOUSE_OVER],
|
||||
},
|
||||
mouseLeave: {
|
||||
registrationName: 'onMouseLeave',
|
||||
dependencies: ['topMouseOut', 'topMouseOver'],
|
||||
dependencies: [TOP_MOUSE_OUT, TOP_MOUSE_OVER],
|
||||
},
|
||||
pointerEnter: {
|
||||
registrationName: 'onPointerEnter',
|
||||
dependencies: [TOP_POINTER_OUT, TOP_POINTER_OVER],
|
||||
},
|
||||
pointerLeave: {
|
||||
registrationName: 'onPointerLeave',
|
||||
dependencies: [TOP_POINTER_OUT, TOP_POINTER_OVER],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -40,14 +55,17 @@ const EnterLeaveEventPlugin = {
|
||||
nativeEvent,
|
||||
nativeEventTarget,
|
||||
) {
|
||||
if (
|
||||
topLevelType === 'topMouseOver' &&
|
||||
(nativeEvent.relatedTarget || nativeEvent.fromElement)
|
||||
) {
|
||||
const isOverEvent =
|
||||
topLevelType === TOP_MOUSE_OVER || topLevelType === TOP_POINTER_OVER;
|
||||
const isOutEvent =
|
||||
topLevelType === TOP_MOUSE_OUT || topLevelType === TOP_POINTER_OUT;
|
||||
|
||||
if (isOverEvent && (nativeEvent.relatedTarget || nativeEvent.fromElement)) {
|
||||
return null;
|
||||
}
|
||||
if (topLevelType !== 'topMouseOut' && topLevelType !== 'topMouseOver') {
|
||||
// Must not be a mouse in or mouse out - ignoring.
|
||||
|
||||
if (!isOutEvent && !isOverEvent) {
|
||||
// Must not be a mouse or pointer in or out - ignoring.
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -67,7 +85,7 @@ const EnterLeaveEventPlugin = {
|
||||
|
||||
let from;
|
||||
let to;
|
||||
if (topLevelType === 'topMouseOut') {
|
||||
if (isOutEvent) {
|
||||
from = targetInst;
|
||||
const related = nativeEvent.relatedTarget || nativeEvent.toElement;
|
||||
to = related ? getClosestInstanceFromNode(related) : null;
|
||||
@@ -82,26 +100,43 @@ const EnterLeaveEventPlugin = {
|
||||
return null;
|
||||
}
|
||||
|
||||
let eventInterface, leaveEventType, enterEventType, eventTypePrefix;
|
||||
|
||||
if (topLevelType === TOP_MOUSE_OUT || topLevelType === TOP_MOUSE_OVER) {
|
||||
eventInterface = SyntheticMouseEvent;
|
||||
leaveEventType = eventTypes.mouseLeave;
|
||||
enterEventType = eventTypes.mouseEnter;
|
||||
eventTypePrefix = 'mouse';
|
||||
} else if (
|
||||
topLevelType === TOP_POINTER_OUT ||
|
||||
topLevelType === TOP_POINTER_OVER
|
||||
) {
|
||||
eventInterface = SyntheticPointerEvent;
|
||||
leaveEventType = eventTypes.pointerLeave;
|
||||
enterEventType = eventTypes.pointerEnter;
|
||||
eventTypePrefix = 'pointer';
|
||||
}
|
||||
|
||||
const fromNode = from == null ? win : getNodeFromInstance(from);
|
||||
const toNode = to == null ? win : getNodeFromInstance(to);
|
||||
|
||||
const leave = SyntheticMouseEvent.getPooled(
|
||||
eventTypes.mouseLeave,
|
||||
const leave = eventInterface.getPooled(
|
||||
leaveEventType,
|
||||
from,
|
||||
nativeEvent,
|
||||
nativeEventTarget,
|
||||
);
|
||||
leave.type = 'mouseleave';
|
||||
leave.type = eventTypePrefix + 'leave';
|
||||
leave.target = fromNode;
|
||||
leave.relatedTarget = toNode;
|
||||
|
||||
const enter = SyntheticMouseEvent.getPooled(
|
||||
eventTypes.mouseEnter,
|
||||
const enter = eventInterface.getPooled(
|
||||
enterEventType,
|
||||
to,
|
||||
nativeEvent,
|
||||
nativeEventTarget,
|
||||
);
|
||||
enter.type = 'mouseenter';
|
||||
enter.type = eventTypePrefix + 'enter';
|
||||
enter.target = toNode;
|
||||
enter.relatedTarget = fromNode;
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
*/
|
||||
|
||||
export function addEventBubbleListener(
|
||||
element: Element,
|
||||
element: Document | Element,
|
||||
eventType: string,
|
||||
listener: Function,
|
||||
): void {
|
||||
@@ -16,7 +16,7 @@ export function addEventBubbleListener(
|
||||
}
|
||||
|
||||
export function addEventCaptureListener(
|
||||
element: Element,
|
||||
element: Document | Element,
|
||||
eventType: string,
|
||||
listener: Function,
|
||||
): void {
|
||||
|
||||
@@ -3,9 +3,23 @@
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import {registrationNameDependencies} from 'events/EventPluginRegistry';
|
||||
import {
|
||||
TOP_BLUR,
|
||||
TOP_CANCEL,
|
||||
TOP_CLOSE,
|
||||
TOP_FOCUS,
|
||||
TOP_INVALID,
|
||||
TOP_RESET,
|
||||
TOP_SCROLL,
|
||||
TOP_SUBMIT,
|
||||
getRawEventName,
|
||||
mediaEventTypes,
|
||||
} from './DOMTopLevelEventTypes';
|
||||
import {
|
||||
setEnabled,
|
||||
isEnabled,
|
||||
@@ -13,7 +27,6 @@ import {
|
||||
trapCapturedEvent,
|
||||
} from './ReactDOMEventListener';
|
||||
import isEventSupported from './isEventSupported';
|
||||
import {topLevelTypes} from './BrowserEventConstants';
|
||||
|
||||
/**
|
||||
* Summary of `ReactBrowserEventEmitter` event handling:
|
||||
@@ -79,7 +92,7 @@ let reactTopListenersCounter = 0;
|
||||
*/
|
||||
const topListenersIDKey = '_reactListenersID' + ('' + Math.random()).slice(2);
|
||||
|
||||
function getListeningForDocument(mountAt) {
|
||||
function getListeningForDocument(mountAt: any) {
|
||||
// In IE8, `mountAt` is a host object and doesn't have `hasOwnProperty`
|
||||
// directly.
|
||||
if (!Object.prototype.hasOwnProperty.call(mountAt, topListenersIDKey)) {
|
||||
@@ -108,45 +121,61 @@ function getListeningForDocument(mountAt) {
|
||||
* they bubble to document.
|
||||
*
|
||||
* @param {string} registrationName Name of listener (e.g. `onClick`).
|
||||
* @param {object} contentDocumentHandle Document which owns the container
|
||||
* @param {object} mountAt Container where to mount the listener
|
||||
*/
|
||||
export function listenTo(registrationName, contentDocumentHandle) {
|
||||
const mountAt = contentDocumentHandle;
|
||||
export function listenTo(
|
||||
registrationName: string,
|
||||
mountAt: Document | Element,
|
||||
) {
|
||||
const isListening = getListeningForDocument(mountAt);
|
||||
const dependencies = registrationNameDependencies[registrationName];
|
||||
|
||||
for (let i = 0; i < dependencies.length; i++) {
|
||||
const dependency = dependencies[i];
|
||||
if (!(isListening.hasOwnProperty(dependency) && isListening[dependency])) {
|
||||
if (dependency === 'topScroll') {
|
||||
trapCapturedEvent('topScroll', 'scroll', mountAt);
|
||||
} else if (dependency === 'topFocus' || dependency === 'topBlur') {
|
||||
trapCapturedEvent('topFocus', 'focus', mountAt);
|
||||
trapCapturedEvent('topBlur', 'blur', mountAt);
|
||||
|
||||
// to make sure blur and focus event listeners are only attached once
|
||||
isListening.topBlur = true;
|
||||
isListening.topFocus = true;
|
||||
} else if (dependency === 'topCancel') {
|
||||
if (isEventSupported('cancel', true)) {
|
||||
trapCapturedEvent('topCancel', 'cancel', mountAt);
|
||||
}
|
||||
isListening.topCancel = true;
|
||||
} else if (dependency === 'topClose') {
|
||||
if (isEventSupported('close', true)) {
|
||||
trapCapturedEvent('topClose', 'close', mountAt);
|
||||
}
|
||||
isListening.topClose = true;
|
||||
} else if (topLevelTypes.hasOwnProperty(dependency)) {
|
||||
trapBubbledEvent(dependency, topLevelTypes[dependency], mountAt);
|
||||
switch (dependency) {
|
||||
case TOP_SCROLL:
|
||||
trapCapturedEvent(TOP_SCROLL, mountAt);
|
||||
break;
|
||||
case TOP_FOCUS:
|
||||
case TOP_BLUR:
|
||||
trapCapturedEvent(TOP_FOCUS, mountAt);
|
||||
trapCapturedEvent(TOP_BLUR, mountAt);
|
||||
// We set the flag for a single dependency later in this function,
|
||||
// but this ensures we mark both as attached rather than just one.
|
||||
isListening[TOP_BLUR] = true;
|
||||
isListening[TOP_FOCUS] = true;
|
||||
break;
|
||||
case TOP_CANCEL:
|
||||
case TOP_CLOSE:
|
||||
if (isEventSupported(getRawEventName(dependency), true)) {
|
||||
trapCapturedEvent(dependency, mountAt);
|
||||
}
|
||||
break;
|
||||
case TOP_INVALID:
|
||||
case TOP_SUBMIT:
|
||||
case TOP_RESET:
|
||||
// We listen to them on the target DOM elements.
|
||||
// Some of them bubble so we don't want them to fire twice.
|
||||
break;
|
||||
default:
|
||||
// By default, listen on the top level to all non-media events.
|
||||
// Media events don't bubble so adding the listener wouldn't do anything.
|
||||
const isMediaEvent = mediaEventTypes.indexOf(dependency) !== -1;
|
||||
if (!isMediaEvent) {
|
||||
trapBubbledEvent(dependency, mountAt);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
isListening[dependency] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function isListeningToAllDependencies(registrationName, mountAt) {
|
||||
export function isListeningToAllDependencies(
|
||||
registrationName: string,
|
||||
mountAt: Document | Element,
|
||||
) {
|
||||
const isListening = getListeningForDocument(mountAt);
|
||||
const dependencies = registrationNameDependencies[registrationName];
|
||||
for (let i = 0; i < dependencies.length; i++) {
|
||||
|
||||
@@ -3,8 +3,14 @@
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import type {AnyNativeEvent} from 'events/PluginModuleType';
|
||||
import type {Fiber} from 'react-reconciler/src/ReactFiber';
|
||||
import type {DOMTopLevelEventType} from 'events/TopLevelEventTypes';
|
||||
|
||||
import {batchedUpdates, interactiveUpdates} from 'events/ReactGenericBatching';
|
||||
import {runExtractedEventsInBatch} from 'events/EventPluginHub';
|
||||
import {isFiberMounted} from 'react-reconciler/reflection';
|
||||
@@ -14,6 +20,7 @@ import {addEventBubbleListener, addEventCaptureListener} from './EventListener';
|
||||
import getEventTarget from './getEventTarget';
|
||||
import {getClosestInstanceFromNode} from '../client/ReactDOMComponentTree';
|
||||
import SimpleEventPlugin from './SimpleEventPlugin';
|
||||
import {getRawEventName} from './DOMTopLevelEventTypes';
|
||||
|
||||
const {isInteractiveTopLevelEventType} = SimpleEventPlugin;
|
||||
|
||||
@@ -40,7 +47,16 @@ function findRootContainerNode(inst) {
|
||||
}
|
||||
|
||||
// Used to store ancestor hierarchy in top level callback
|
||||
function getTopLevelCallbackBookKeeping(topLevelType, nativeEvent, targetInst) {
|
||||
function getTopLevelCallbackBookKeeping(
|
||||
topLevelType,
|
||||
nativeEvent,
|
||||
targetInst,
|
||||
): {
|
||||
topLevelType: ?DOMTopLevelEventType,
|
||||
nativeEvent: ?AnyNativeEvent,
|
||||
targetInst: Fiber | null,
|
||||
ancestors: Array<Fiber>,
|
||||
} {
|
||||
if (callbackBookkeepingPool.length) {
|
||||
const instance = callbackBookkeepingPool.pop();
|
||||
instance.topLevelType = topLevelType;
|
||||
@@ -101,7 +117,7 @@ function handleTopLevel(bookKeeping) {
|
||||
// TODO: can we stop exporting these?
|
||||
export let _enabled = true;
|
||||
|
||||
export function setEnabled(enabled) {
|
||||
export function setEnabled(enabled: ?boolean) {
|
||||
_enabled = !!enabled;
|
||||
}
|
||||
|
||||
@@ -112,14 +128,16 @@ export function isEnabled() {
|
||||
/**
|
||||
* Traps top-level events by using event bubbling.
|
||||
*
|
||||
* @param {string} topLevelType Record from `BrowserEventConstants`.
|
||||
* @param {string} handlerBaseName Event name (e.g. "click").
|
||||
* @param {number} topLevelType Number from `TopLevelEventTypes`.
|
||||
* @param {object} element Element on which to attach listener.
|
||||
* @return {?object} An object with a remove function which will forcefully
|
||||
* remove the listener.
|
||||
* @internal
|
||||
*/
|
||||
export function trapBubbledEvent(topLevelType, handlerBaseName, element) {
|
||||
export function trapBubbledEvent(
|
||||
topLevelType: DOMTopLevelEventType,
|
||||
element: Document | Element,
|
||||
) {
|
||||
if (!element) {
|
||||
return null;
|
||||
}
|
||||
@@ -129,7 +147,7 @@ export function trapBubbledEvent(topLevelType, handlerBaseName, element) {
|
||||
|
||||
addEventBubbleListener(
|
||||
element,
|
||||
handlerBaseName,
|
||||
getRawEventName(topLevelType),
|
||||
// Check if interactive and wrap in interactiveUpdates
|
||||
dispatch.bind(null, topLevelType),
|
||||
);
|
||||
@@ -138,14 +156,16 @@ export function trapBubbledEvent(topLevelType, handlerBaseName, element) {
|
||||
/**
|
||||
* Traps a top-level event by using event capturing.
|
||||
*
|
||||
* @param {string} topLevelType Record from `BrowserEventConstants`.
|
||||
* @param {string} handlerBaseName Event name (e.g. "click").
|
||||
* @param {number} topLevelType Number from `TopLevelEventTypes`.
|
||||
* @param {object} element Element on which to attach listener.
|
||||
* @return {?object} An object with a remove function which will forcefully
|
||||
* remove the listener.
|
||||
* @internal
|
||||
*/
|
||||
export function trapCapturedEvent(topLevelType, handlerBaseName, element) {
|
||||
export function trapCapturedEvent(
|
||||
topLevelType: DOMTopLevelEventType,
|
||||
element: Document | Element,
|
||||
) {
|
||||
if (!element) {
|
||||
return null;
|
||||
}
|
||||
@@ -155,7 +175,7 @@ export function trapCapturedEvent(topLevelType, handlerBaseName, element) {
|
||||
|
||||
addEventCaptureListener(
|
||||
element,
|
||||
handlerBaseName,
|
||||
getRawEventName(topLevelType),
|
||||
// Check if interactive and wrap in interactiveUpdates
|
||||
dispatch.bind(null, topLevelType),
|
||||
);
|
||||
@@ -165,7 +185,10 @@ function dispatchInteractiveEvent(topLevelType, nativeEvent) {
|
||||
interactiveUpdates(dispatchEvent, topLevelType, nativeEvent);
|
||||
}
|
||||
|
||||
export function dispatchEvent(topLevelType, nativeEvent) {
|
||||
export function dispatchEvent(
|
||||
topLevelType: DOMTopLevelEventType,
|
||||
nativeEvent: AnyNativeEvent,
|
||||
) {
|
||||
if (!_enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,16 @@ import isTextInputElement from 'shared/isTextInputElement';
|
||||
import getActiveElement from 'fbjs/lib/getActiveElement';
|
||||
import shallowEqual from 'fbjs/lib/shallowEqual';
|
||||
|
||||
import {
|
||||
TOP_BLUR,
|
||||
TOP_CONTEXT_MENU,
|
||||
TOP_FOCUS,
|
||||
TOP_KEY_DOWN,
|
||||
TOP_KEY_UP,
|
||||
TOP_MOUSE_DOWN,
|
||||
TOP_MOUSE_UP,
|
||||
TOP_SELECTION_CHANGE,
|
||||
} from './DOMTopLevelEventTypes';
|
||||
import {isListeningToAllDependencies} from './ReactBrowserEventEmitter';
|
||||
import {getNodeFromInstance} from '../client/ReactDOMComponentTree';
|
||||
import * as ReactInputSelection from '../client/ReactInputSelection';
|
||||
@@ -29,14 +39,14 @@ const eventTypes = {
|
||||
captured: 'onSelectCapture',
|
||||
},
|
||||
dependencies: [
|
||||
'topBlur',
|
||||
'topContextMenu',
|
||||
'topFocus',
|
||||
'topKeyDown',
|
||||
'topKeyUp',
|
||||
'topMouseDown',
|
||||
'topMouseUp',
|
||||
'topSelectionChange',
|
||||
TOP_BLUR,
|
||||
TOP_CONTEXT_MENU,
|
||||
TOP_FOCUS,
|
||||
TOP_KEY_DOWN,
|
||||
TOP_KEY_UP,
|
||||
TOP_MOUSE_DOWN,
|
||||
TOP_MOUSE_UP,
|
||||
TOP_SELECTION_CHANGE,
|
||||
],
|
||||
},
|
||||
};
|
||||
@@ -156,7 +166,7 @@ const SelectEventPlugin = {
|
||||
|
||||
switch (topLevelType) {
|
||||
// Track the input node that has focus.
|
||||
case 'topFocus':
|
||||
case TOP_FOCUS:
|
||||
if (
|
||||
isTextInputElement(targetNode) ||
|
||||
targetNode.contentEditable === 'true'
|
||||
@@ -166,18 +176,18 @@ const SelectEventPlugin = {
|
||||
lastSelection = null;
|
||||
}
|
||||
break;
|
||||
case 'topBlur':
|
||||
case TOP_BLUR:
|
||||
activeElement = null;
|
||||
activeElementInst = null;
|
||||
lastSelection = null;
|
||||
break;
|
||||
// Don't fire the event while the user is dragging. This matches the
|
||||
// semantics of the native select event.
|
||||
case 'topMouseDown':
|
||||
case TOP_MOUSE_DOWN:
|
||||
mouseDown = true;
|
||||
break;
|
||||
case 'topContextMenu':
|
||||
case 'topMouseUp':
|
||||
case TOP_CONTEXT_MENU:
|
||||
case TOP_MOUSE_UP:
|
||||
mouseDown = false;
|
||||
return constructSelectEvent(nativeEvent, nativeEventTarget);
|
||||
// Chrome and IE fire non-standard event when selection is changed (and
|
||||
@@ -189,13 +199,13 @@ const SelectEventPlugin = {
|
||||
// keyup, but we check on keydown as well in the case of holding down a
|
||||
// key, when multiple keydown events are fired but only one keyup is.
|
||||
// This is also our approach for IE handling, for the reason above.
|
||||
case 'topSelectionChange':
|
||||
case TOP_SELECTION_CHANGE:
|
||||
if (skipSelectionChangeEvent) {
|
||||
break;
|
||||
}
|
||||
// falls through
|
||||
case 'topKeyDown':
|
||||
case 'topKeyUp':
|
||||
case TOP_KEY_DOWN:
|
||||
case TOP_KEY_UP:
|
||||
return constructSelectEvent(nativeEvent, nativeEventTarget);
|
||||
}
|
||||
|
||||
|
||||
325
packages/react-dom/src/events/SimpleEventPlugin.js
vendored
325
packages/react-dom/src/events/SimpleEventPlugin.js
vendored
@@ -7,7 +7,10 @@
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import type {TopLevelTypes} from './BrowserEventConstants';
|
||||
import type {
|
||||
TopLevelType,
|
||||
DOMTopLevelEventType,
|
||||
} from 'events/TopLevelEventTypes';
|
||||
import type {
|
||||
DispatchConfig,
|
||||
ReactSyntheticEvent,
|
||||
@@ -17,6 +20,8 @@ import type {EventTypes, PluginModule} from 'events/PluginModuleType';
|
||||
|
||||
import {accumulateTwoPhaseDispatches} from 'events/EventPropagators';
|
||||
import SyntheticEvent from 'events/SyntheticEvent';
|
||||
|
||||
import * as DOMTopLevelEventTypes from './DOMTopLevelEventTypes';
|
||||
import warning from 'fbjs/lib/warning';
|
||||
|
||||
import SyntheticAnimationEvent from './SyntheticAnimationEvent';
|
||||
@@ -24,6 +29,7 @@ import SyntheticClipboardEvent from './SyntheticClipboardEvent';
|
||||
import SyntheticFocusEvent from './SyntheticFocusEvent';
|
||||
import SyntheticKeyboardEvent from './SyntheticKeyboardEvent';
|
||||
import SyntheticMouseEvent from './SyntheticMouseEvent';
|
||||
import SyntheticPointerEvent from './SyntheticPointerEvent';
|
||||
import SyntheticDragEvent from './SyntheticDragEvent';
|
||||
import SyntheticTouchEvent from './SyntheticTouchEvent';
|
||||
import SyntheticTransitionEvent from './SyntheticTransitionEvent';
|
||||
@@ -41,93 +47,104 @@ import getEventCharCode from './getEventCharCode';
|
||||
* bubbled: 'onAbort',
|
||||
* captured: 'onAbortCapture',
|
||||
* },
|
||||
* dependencies: ['topAbort'],
|
||||
* dependencies: [TOP_ABORT],
|
||||
* },
|
||||
* ...
|
||||
* };
|
||||
* topLevelEventsToDispatchConfig = {
|
||||
* 'topAbort': { sameConfig }
|
||||
* };
|
||||
* topLevelEventsToDispatchConfig = new Map([
|
||||
* [TOP_ABORT, { sameConfig }],
|
||||
* ]);
|
||||
*/
|
||||
const interactiveEventTypeNames: Array<string> = [
|
||||
'blur',
|
||||
'cancel',
|
||||
'click',
|
||||
'close',
|
||||
'contextMenu',
|
||||
'copy',
|
||||
'cut',
|
||||
'doubleClick',
|
||||
'dragEnd',
|
||||
'dragStart',
|
||||
'drop',
|
||||
'focus',
|
||||
'input',
|
||||
'invalid',
|
||||
'keyDown',
|
||||
'keyPress',
|
||||
'keyUp',
|
||||
'mouseDown',
|
||||
'mouseUp',
|
||||
'paste',
|
||||
'pause',
|
||||
'play',
|
||||
'rateChange',
|
||||
'reset',
|
||||
'seeked',
|
||||
'submit',
|
||||
'touchCancel',
|
||||
'touchEnd',
|
||||
'touchStart',
|
||||
'volumeChange',
|
||||
type EventTuple = [DOMTopLevelEventType, string];
|
||||
const interactiveEventTypeNames: Array<EventTuple> = [
|
||||
[DOMTopLevelEventTypes.TOP_BLUR, 'blur'],
|
||||
[DOMTopLevelEventTypes.TOP_CANCEL, 'cancel'],
|
||||
[DOMTopLevelEventTypes.TOP_CLICK, 'click'],
|
||||
[DOMTopLevelEventTypes.TOP_CLOSE, 'close'],
|
||||
[DOMTopLevelEventTypes.TOP_CONTEXT_MENU, 'contextMenu'],
|
||||
[DOMTopLevelEventTypes.TOP_COPY, 'copy'],
|
||||
[DOMTopLevelEventTypes.TOP_CUT, 'cut'],
|
||||
[DOMTopLevelEventTypes.TOP_DOUBLE_CLICK, 'doubleClick'],
|
||||
[DOMTopLevelEventTypes.TOP_DRAG_END, 'dragEnd'],
|
||||
[DOMTopLevelEventTypes.TOP_DRAG_START, 'dragStart'],
|
||||
[DOMTopLevelEventTypes.TOP_DROP, 'drop'],
|
||||
[DOMTopLevelEventTypes.TOP_FOCUS, 'focus'],
|
||||
[DOMTopLevelEventTypes.TOP_INPUT, 'input'],
|
||||
[DOMTopLevelEventTypes.TOP_INVALID, 'invalid'],
|
||||
[DOMTopLevelEventTypes.TOP_KEY_DOWN, 'keyDown'],
|
||||
[DOMTopLevelEventTypes.TOP_KEY_PRESS, 'keyPress'],
|
||||
[DOMTopLevelEventTypes.TOP_KEY_UP, 'keyUp'],
|
||||
[DOMTopLevelEventTypes.TOP_MOUSE_DOWN, 'mouseDown'],
|
||||
[DOMTopLevelEventTypes.TOP_MOUSE_UP, 'mouseUp'],
|
||||
[DOMTopLevelEventTypes.TOP_PASTE, 'paste'],
|
||||
[DOMTopLevelEventTypes.TOP_PAUSE, 'pause'],
|
||||
[DOMTopLevelEventTypes.TOP_PLAY, 'play'],
|
||||
[DOMTopLevelEventTypes.TOP_POINTER_CANCEL, 'pointerCancel'],
|
||||
[DOMTopLevelEventTypes.TOP_POINTER_DOWN, 'pointerDown'],
|
||||
[DOMTopLevelEventTypes.TOP_POINTER_UP, 'pointerUp'],
|
||||
[DOMTopLevelEventTypes.TOP_RATE_CHANGE, 'rateChange'],
|
||||
[DOMTopLevelEventTypes.TOP_RESET, 'reset'],
|
||||
[DOMTopLevelEventTypes.TOP_SEEKED, 'seeked'],
|
||||
[DOMTopLevelEventTypes.TOP_SUBMIT, 'submit'],
|
||||
[DOMTopLevelEventTypes.TOP_TOUCH_CANCEL, 'touchCancel'],
|
||||
[DOMTopLevelEventTypes.TOP_TOUCH_END, 'touchEnd'],
|
||||
[DOMTopLevelEventTypes.TOP_TOUCH_START, 'touchStart'],
|
||||
[DOMTopLevelEventTypes.TOP_VOLUME_CHANGE, 'volumeChange'],
|
||||
];
|
||||
const nonInteractiveEventTypeNames: Array<string> = [
|
||||
'abort',
|
||||
'animationEnd',
|
||||
'animationIteration',
|
||||
'animationStart',
|
||||
'canPlay',
|
||||
'canPlayThrough',
|
||||
'drag',
|
||||
'dragEnter',
|
||||
'dragExit',
|
||||
'dragLeave',
|
||||
'dragOver',
|
||||
'durationChange',
|
||||
'emptied',
|
||||
'encrypted',
|
||||
'ended',
|
||||
'error',
|
||||
'load',
|
||||
'loadedData',
|
||||
'loadedMetadata',
|
||||
'loadStart',
|
||||
'mouseMove',
|
||||
'mouseOut',
|
||||
'mouseOver',
|
||||
'playing',
|
||||
'progress',
|
||||
'scroll',
|
||||
'seeking',
|
||||
'stalled',
|
||||
'suspend',
|
||||
'timeUpdate',
|
||||
'toggle',
|
||||
'touchMove',
|
||||
'transitionEnd',
|
||||
'waiting',
|
||||
'wheel',
|
||||
const nonInteractiveEventTypeNames: Array<EventTuple> = [
|
||||
[DOMTopLevelEventTypes.TOP_ABORT, 'abort'],
|
||||
[DOMTopLevelEventTypes.TOP_ANIMATION_END, 'animationEnd'],
|
||||
[DOMTopLevelEventTypes.TOP_ANIMATION_ITERATION, 'animationIteration'],
|
||||
[DOMTopLevelEventTypes.TOP_ANIMATION_START, 'animationStart'],
|
||||
[DOMTopLevelEventTypes.TOP_CAN_PLAY, 'canPlay'],
|
||||
[DOMTopLevelEventTypes.TOP_CAN_PLAY_THROUGH, 'canPlayThrough'],
|
||||
[DOMTopLevelEventTypes.TOP_DRAG, 'drag'],
|
||||
[DOMTopLevelEventTypes.TOP_DRAG_ENTER, 'dragEnter'],
|
||||
[DOMTopLevelEventTypes.TOP_DRAG_EXIT, 'dragExit'],
|
||||
[DOMTopLevelEventTypes.TOP_DRAG_LEAVE, 'dragLeave'],
|
||||
[DOMTopLevelEventTypes.TOP_DRAG_OVER, 'dragOver'],
|
||||
[DOMTopLevelEventTypes.TOP_DURATION_CHANGE, 'durationChange'],
|
||||
[DOMTopLevelEventTypes.TOP_EMPTIED, 'emptied'],
|
||||
[DOMTopLevelEventTypes.TOP_ENCRYPTED, 'encrypted'],
|
||||
[DOMTopLevelEventTypes.TOP_ENDED, 'ended'],
|
||||
[DOMTopLevelEventTypes.TOP_ERROR, 'error'],
|
||||
[DOMTopLevelEventTypes.TOP_GOT_POINTER_CAPTURE, 'gotPointerCapture'],
|
||||
[DOMTopLevelEventTypes.TOP_LOAD, 'load'],
|
||||
[DOMTopLevelEventTypes.TOP_LOADED_DATA, 'loadedData'],
|
||||
[DOMTopLevelEventTypes.TOP_LOADED_METADATA, 'loadedMetadata'],
|
||||
[DOMTopLevelEventTypes.TOP_LOAD_START, 'loadStart'],
|
||||
[DOMTopLevelEventTypes.TOP_LOST_POINTER_CAPTURE, 'lostPointerCapture'],
|
||||
[DOMTopLevelEventTypes.TOP_MOUSE_MOVE, 'mouseMove'],
|
||||
[DOMTopLevelEventTypes.TOP_MOUSE_OUT, 'mouseOut'],
|
||||
[DOMTopLevelEventTypes.TOP_MOUSE_OVER, 'mouseOver'],
|
||||
[DOMTopLevelEventTypes.TOP_PLAYING, 'playing'],
|
||||
[DOMTopLevelEventTypes.TOP_POINTER_MOVE, 'pointerMove'],
|
||||
[DOMTopLevelEventTypes.TOP_POINTER_OUT, 'pointerOut'],
|
||||
[DOMTopLevelEventTypes.TOP_POINTER_OVER, 'pointerOver'],
|
||||
[DOMTopLevelEventTypes.TOP_PROGRESS, 'progress'],
|
||||
[DOMTopLevelEventTypes.TOP_SCROLL, 'scroll'],
|
||||
[DOMTopLevelEventTypes.TOP_SEEKING, 'seeking'],
|
||||
[DOMTopLevelEventTypes.TOP_STALLED, 'stalled'],
|
||||
[DOMTopLevelEventTypes.TOP_SUSPEND, 'suspend'],
|
||||
[DOMTopLevelEventTypes.TOP_TIME_UPDATE, 'timeUpdate'],
|
||||
[DOMTopLevelEventTypes.TOP_TOGGLE, 'toggle'],
|
||||
[DOMTopLevelEventTypes.TOP_TOUCH_MOVE, 'touchMove'],
|
||||
[DOMTopLevelEventTypes.TOP_TRANSITION_END, 'transitionEnd'],
|
||||
[DOMTopLevelEventTypes.TOP_WAITING, 'waiting'],
|
||||
[DOMTopLevelEventTypes.TOP_WHEEL, 'wheel'],
|
||||
];
|
||||
|
||||
const eventTypes: EventTypes = {};
|
||||
const topLevelEventsToDispatchConfig: {
|
||||
[key: TopLevelTypes]: DispatchConfig,
|
||||
[key: TopLevelType]: DispatchConfig,
|
||||
} = {};
|
||||
|
||||
function addEventTypeNameToConfig(event: string, isInteractive: boolean) {
|
||||
function addEventTypeNameToConfig(
|
||||
[topEvent, event]: EventTuple,
|
||||
isInteractive: boolean,
|
||||
) {
|
||||
const capitalizedEvent = event[0].toUpperCase() + event.slice(1);
|
||||
const onEvent = 'on' + capitalizedEvent;
|
||||
const topEvent = 'top' + capitalizedEvent;
|
||||
|
||||
const type = {
|
||||
phasedRegistrationNames: {
|
||||
@@ -141,58 +158,60 @@ function addEventTypeNameToConfig(event: string, isInteractive: boolean) {
|
||||
topLevelEventsToDispatchConfig[topEvent] = type;
|
||||
}
|
||||
|
||||
interactiveEventTypeNames.forEach(eventTypeName => {
|
||||
addEventTypeNameToConfig(eventTypeName, true);
|
||||
interactiveEventTypeNames.forEach(eventTuple => {
|
||||
addEventTypeNameToConfig(eventTuple, true);
|
||||
});
|
||||
nonInteractiveEventTypeNames.forEach(eventTypeName => {
|
||||
addEventTypeNameToConfig(eventTypeName, false);
|
||||
nonInteractiveEventTypeNames.forEach(eventTuple => {
|
||||
addEventTypeNameToConfig(eventTuple, false);
|
||||
});
|
||||
|
||||
// Only used in DEV for exhaustiveness validation.
|
||||
const knownHTMLTopLevelTypes = [
|
||||
'topAbort',
|
||||
'topCancel',
|
||||
'topCanPlay',
|
||||
'topCanPlayThrough',
|
||||
'topClose',
|
||||
'topDurationChange',
|
||||
'topEmptied',
|
||||
'topEncrypted',
|
||||
'topEnded',
|
||||
'topError',
|
||||
'topInput',
|
||||
'topInvalid',
|
||||
'topLoad',
|
||||
'topLoadedData',
|
||||
'topLoadedMetadata',
|
||||
'topLoadStart',
|
||||
'topPause',
|
||||
'topPlay',
|
||||
'topPlaying',
|
||||
'topProgress',
|
||||
'topRateChange',
|
||||
'topReset',
|
||||
'topSeeked',
|
||||
'topSeeking',
|
||||
'topStalled',
|
||||
'topSubmit',
|
||||
'topSuspend',
|
||||
'topTimeUpdate',
|
||||
'topToggle',
|
||||
'topVolumeChange',
|
||||
'topWaiting',
|
||||
const knownHTMLTopLevelTypes: Array<DOMTopLevelEventType> = [
|
||||
DOMTopLevelEventTypes.TOP_ABORT,
|
||||
DOMTopLevelEventTypes.TOP_CANCEL,
|
||||
DOMTopLevelEventTypes.TOP_CAN_PLAY,
|
||||
DOMTopLevelEventTypes.TOP_CAN_PLAY_THROUGH,
|
||||
DOMTopLevelEventTypes.TOP_CLOSE,
|
||||
DOMTopLevelEventTypes.TOP_DURATION_CHANGE,
|
||||
DOMTopLevelEventTypes.TOP_EMPTIED,
|
||||
DOMTopLevelEventTypes.TOP_ENCRYPTED,
|
||||
DOMTopLevelEventTypes.TOP_ENDED,
|
||||
DOMTopLevelEventTypes.TOP_ERROR,
|
||||
DOMTopLevelEventTypes.TOP_INPUT,
|
||||
DOMTopLevelEventTypes.TOP_INVALID,
|
||||
DOMTopLevelEventTypes.TOP_LOAD,
|
||||
DOMTopLevelEventTypes.TOP_LOADED_DATA,
|
||||
DOMTopLevelEventTypes.TOP_LOADED_METADATA,
|
||||
DOMTopLevelEventTypes.TOP_LOAD_START,
|
||||
DOMTopLevelEventTypes.TOP_PAUSE,
|
||||
DOMTopLevelEventTypes.TOP_PLAY,
|
||||
DOMTopLevelEventTypes.TOP_PLAYING,
|
||||
DOMTopLevelEventTypes.TOP_PROGRESS,
|
||||
DOMTopLevelEventTypes.TOP_RATE_CHANGE,
|
||||
DOMTopLevelEventTypes.TOP_RESET,
|
||||
DOMTopLevelEventTypes.TOP_SEEKED,
|
||||
DOMTopLevelEventTypes.TOP_SEEKING,
|
||||
DOMTopLevelEventTypes.TOP_STALLED,
|
||||
DOMTopLevelEventTypes.TOP_SUBMIT,
|
||||
DOMTopLevelEventTypes.TOP_SUSPEND,
|
||||
DOMTopLevelEventTypes.TOP_TIME_UPDATE,
|
||||
DOMTopLevelEventTypes.TOP_TOGGLE,
|
||||
DOMTopLevelEventTypes.TOP_VOLUME_CHANGE,
|
||||
DOMTopLevelEventTypes.TOP_WAITING,
|
||||
];
|
||||
|
||||
const SimpleEventPlugin: PluginModule<MouseEvent> = {
|
||||
const SimpleEventPlugin: PluginModule<MouseEvent> & {
|
||||
isInteractiveTopLevelEventType: (topLevelType: TopLevelType) => boolean,
|
||||
} = {
|
||||
eventTypes: eventTypes,
|
||||
|
||||
isInteractiveTopLevelEventType(topLevelType: TopLevelTypes): boolean {
|
||||
isInteractiveTopLevelEventType(topLevelType: TopLevelType): boolean {
|
||||
const config = topLevelEventsToDispatchConfig[topLevelType];
|
||||
return config !== undefined && config.isInteractive === true;
|
||||
},
|
||||
|
||||
extractEvents: function(
|
||||
topLevelType: TopLevelTypes,
|
||||
topLevelType: TopLevelType,
|
||||
targetInst: Fiber,
|
||||
nativeEvent: MouseEvent,
|
||||
nativeEventTarget: EventTarget,
|
||||
@@ -203,7 +222,7 @@ const SimpleEventPlugin: PluginModule<MouseEvent> = {
|
||||
}
|
||||
let EventConstructor;
|
||||
switch (topLevelType) {
|
||||
case 'topKeyPress':
|
||||
case DOMTopLevelEventTypes.TOP_KEY_PRESS:
|
||||
// Firefox creates a keypress event for function keys too. This removes
|
||||
// the unwanted keypress events. Enter is however both printable and
|
||||
// non-printable. One would expect Tab to be as well (but it isn't).
|
||||
@@ -211,67 +230,77 @@ const SimpleEventPlugin: PluginModule<MouseEvent> = {
|
||||
return null;
|
||||
}
|
||||
/* falls through */
|
||||
case 'topKeyDown':
|
||||
case 'topKeyUp':
|
||||
case DOMTopLevelEventTypes.TOP_KEY_DOWN:
|
||||
case DOMTopLevelEventTypes.TOP_KEY_UP:
|
||||
EventConstructor = SyntheticKeyboardEvent;
|
||||
break;
|
||||
case 'topBlur':
|
||||
case 'topFocus':
|
||||
case DOMTopLevelEventTypes.TOP_BLUR:
|
||||
case DOMTopLevelEventTypes.TOP_FOCUS:
|
||||
EventConstructor = SyntheticFocusEvent;
|
||||
break;
|
||||
case 'topClick':
|
||||
case DOMTopLevelEventTypes.TOP_CLICK:
|
||||
// Firefox creates a click event on right mouse clicks. This removes the
|
||||
// unwanted click events.
|
||||
if (nativeEvent.button === 2) {
|
||||
return null;
|
||||
}
|
||||
/* falls through */
|
||||
case 'topDoubleClick':
|
||||
case 'topMouseDown':
|
||||
case 'topMouseMove':
|
||||
case 'topMouseUp':
|
||||
case DOMTopLevelEventTypes.TOP_DOUBLE_CLICK:
|
||||
case DOMTopLevelEventTypes.TOP_MOUSE_DOWN:
|
||||
case DOMTopLevelEventTypes.TOP_MOUSE_MOVE:
|
||||
case DOMTopLevelEventTypes.TOP_MOUSE_UP:
|
||||
// TODO: Disabled elements should not respond to mouse events
|
||||
/* falls through */
|
||||
case 'topMouseOut':
|
||||
case 'topMouseOver':
|
||||
case 'topContextMenu':
|
||||
case DOMTopLevelEventTypes.TOP_MOUSE_OUT:
|
||||
case DOMTopLevelEventTypes.TOP_MOUSE_OVER:
|
||||
case DOMTopLevelEventTypes.TOP_CONTEXT_MENU:
|
||||
EventConstructor = SyntheticMouseEvent;
|
||||
break;
|
||||
case 'topDrag':
|
||||
case 'topDragEnd':
|
||||
case 'topDragEnter':
|
||||
case 'topDragExit':
|
||||
case 'topDragLeave':
|
||||
case 'topDragOver':
|
||||
case 'topDragStart':
|
||||
case 'topDrop':
|
||||
case DOMTopLevelEventTypes.TOP_DRAG:
|
||||
case DOMTopLevelEventTypes.TOP_DRAG_END:
|
||||
case DOMTopLevelEventTypes.TOP_DRAG_ENTER:
|
||||
case DOMTopLevelEventTypes.TOP_DRAG_EXIT:
|
||||
case DOMTopLevelEventTypes.TOP_DRAG_LEAVE:
|
||||
case DOMTopLevelEventTypes.TOP_DRAG_OVER:
|
||||
case DOMTopLevelEventTypes.TOP_DRAG_START:
|
||||
case DOMTopLevelEventTypes.TOP_DROP:
|
||||
EventConstructor = SyntheticDragEvent;
|
||||
break;
|
||||
case 'topTouchCancel':
|
||||
case 'topTouchEnd':
|
||||
case 'topTouchMove':
|
||||
case 'topTouchStart':
|
||||
case DOMTopLevelEventTypes.TOP_TOUCH_CANCEL:
|
||||
case DOMTopLevelEventTypes.TOP_TOUCH_END:
|
||||
case DOMTopLevelEventTypes.TOP_TOUCH_MOVE:
|
||||
case DOMTopLevelEventTypes.TOP_TOUCH_START:
|
||||
EventConstructor = SyntheticTouchEvent;
|
||||
break;
|
||||
case 'topAnimationEnd':
|
||||
case 'topAnimationIteration':
|
||||
case 'topAnimationStart':
|
||||
case DOMTopLevelEventTypes.TOP_ANIMATION_END:
|
||||
case DOMTopLevelEventTypes.TOP_ANIMATION_ITERATION:
|
||||
case DOMTopLevelEventTypes.TOP_ANIMATION_START:
|
||||
EventConstructor = SyntheticAnimationEvent;
|
||||
break;
|
||||
case 'topTransitionEnd':
|
||||
case DOMTopLevelEventTypes.TOP_TRANSITION_END:
|
||||
EventConstructor = SyntheticTransitionEvent;
|
||||
break;
|
||||
case 'topScroll':
|
||||
case DOMTopLevelEventTypes.TOP_SCROLL:
|
||||
EventConstructor = SyntheticUIEvent;
|
||||
break;
|
||||
case 'topWheel':
|
||||
case DOMTopLevelEventTypes.TOP_WHEEL:
|
||||
EventConstructor = SyntheticWheelEvent;
|
||||
break;
|
||||
case 'topCopy':
|
||||
case 'topCut':
|
||||
case 'topPaste':
|
||||
case DOMTopLevelEventTypes.TOP_COPY:
|
||||
case DOMTopLevelEventTypes.TOP_CUT:
|
||||
case DOMTopLevelEventTypes.TOP_PASTE:
|
||||
EventConstructor = SyntheticClipboardEvent;
|
||||
break;
|
||||
case DOMTopLevelEventTypes.TOP_GOT_POINTER_CAPTURE:
|
||||
case DOMTopLevelEventTypes.TOP_LOST_POINTER_CAPTURE:
|
||||
case DOMTopLevelEventTypes.TOP_POINTER_CANCEL:
|
||||
case DOMTopLevelEventTypes.TOP_POINTER_DOWN:
|
||||
case DOMTopLevelEventTypes.TOP_POINTER_MOVE:
|
||||
case DOMTopLevelEventTypes.TOP_POINTER_OUT:
|
||||
case DOMTopLevelEventTypes.TOP_POINTER_OVER:
|
||||
case DOMTopLevelEventTypes.TOP_POINTER_UP:
|
||||
EventConstructor = SyntheticPointerEvent;
|
||||
break;
|
||||
default:
|
||||
if (__DEV__) {
|
||||
if (knownHTMLTopLevelTypes.indexOf(topLevelType) === -1) {
|
||||
|
||||
25
packages/react-dom/src/events/SyntheticPointerEvent.js
vendored
Normal file
25
packages/react-dom/src/events/SyntheticPointerEvent.js
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* 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 SyntheticMouseEvent from './SyntheticMouseEvent';
|
||||
|
||||
/**
|
||||
* @interface PointerEvent
|
||||
* @see http://www.w3.org/TR/pointerevents/
|
||||
*/
|
||||
const SyntheticPointerEvent = SyntheticMouseEvent.extend({
|
||||
pointerId: null,
|
||||
width: null,
|
||||
height: null,
|
||||
pressure: null,
|
||||
tiltX: null,
|
||||
tiltY: null,
|
||||
pointerType: null,
|
||||
isPrimary: null,
|
||||
});
|
||||
|
||||
export default SyntheticPointerEvent;
|
||||
58
packages/react-dom/src/events/TapEventPlugin.js
vendored
58
packages/react-dom/src/events/TapEventPlugin.js
vendored
@@ -7,12 +7,43 @@
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import {isStartish, isEndish} from 'events/EventPluginUtils';
|
||||
import {accumulateTwoPhaseDispatches} from 'events/EventPropagators';
|
||||
import TouchEventUtils from 'fbjs/lib/TouchEventUtils';
|
||||
import type {TopLevelType} from 'events/TopLevelEventTypes';
|
||||
|
||||
import {
|
||||
TOP_MOUSE_DOWN,
|
||||
TOP_MOUSE_MOVE,
|
||||
TOP_MOUSE_UP,
|
||||
TOP_POINTER_CANCEL,
|
||||
TOP_POINTER_DOWN,
|
||||
TOP_POINTER_UP,
|
||||
TOP_POINTER_MOVE,
|
||||
TOP_TOUCH_CANCEL,
|
||||
TOP_TOUCH_END,
|
||||
TOP_TOUCH_MOVE,
|
||||
TOP_TOUCH_START,
|
||||
} from './DOMTopLevelEventTypes';
|
||||
import SyntheticUIEvent from './SyntheticUIEvent';
|
||||
|
||||
function isStartish(topLevelType) {
|
||||
return (
|
||||
topLevelType === TOP_MOUSE_DOWN ||
|
||||
topLevelType === TOP_TOUCH_START ||
|
||||
topLevelType === TOP_POINTER_DOWN
|
||||
);
|
||||
}
|
||||
|
||||
function isEndish(topLevelType) {
|
||||
return (
|
||||
topLevelType === TOP_MOUSE_UP ||
|
||||
topLevelType === TOP_POINTER_CANCEL ||
|
||||
topLevelType === TOP_POINTER_UP ||
|
||||
topLevelType === TOP_TOUCH_CANCEL ||
|
||||
topLevelType === TOP_TOUCH_END
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* We are extending the Flow 'Touch' declaration to enable using bracket
|
||||
* notation to access properties.
|
||||
@@ -75,15 +106,24 @@ function getDistance(coords: CoordinatesType, nativeEvent: _Touch): number {
|
||||
}
|
||||
|
||||
const touchEvents = [
|
||||
'topTouchStart',
|
||||
'topTouchCancel',
|
||||
'topTouchEnd',
|
||||
'topTouchMove',
|
||||
TOP_TOUCH_START,
|
||||
TOP_TOUCH_CANCEL,
|
||||
TOP_TOUCH_END,
|
||||
TOP_TOUCH_MOVE,
|
||||
];
|
||||
|
||||
const dependencies = ['topMouseDown', 'topMouseMove', 'topMouseUp'].concat(
|
||||
touchEvents,
|
||||
);
|
||||
const pointerEvents = [
|
||||
TOP_POINTER_CANCEL,
|
||||
TOP_POINTER_DOWN,
|
||||
TOP_POINTER_MOVE,
|
||||
TOP_POINTER_UP,
|
||||
];
|
||||
|
||||
const dependencies: Array<TopLevelType> = [
|
||||
TOP_MOUSE_DOWN,
|
||||
TOP_MOUSE_MOVE,
|
||||
TOP_MOUSE_UP,
|
||||
].concat(touchEvents, pointerEvents);
|
||||
|
||||
const eventTypes = {
|
||||
touchTap: {
|
||||
@@ -105,7 +145,7 @@ const TapEventPlugin = {
|
||||
eventTypes: eventTypes,
|
||||
|
||||
extractEvents: function(
|
||||
topLevelType: mixed,
|
||||
topLevelType: TopLevelType,
|
||||
targetInst: mixed,
|
||||
nativeEvent: _Touch,
|
||||
nativeEventTarget: EventTarget,
|
||||
|
||||
@@ -200,9 +200,9 @@ describe('BeforeInputEventPlugin', () => {
|
||||
spyOnBeforeInput,
|
||||
spyOnCompositionStart,
|
||||
}) => {
|
||||
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
|
||||
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
|
||||
expect(beforeInputEvent).toBeNull();
|
||||
expect(spyOnCompositionStart.mock.calls.length).toBe(1);
|
||||
expect(spyOnCompositionStart).toHaveBeenCalledTimes(1);
|
||||
expect(compositionStartEvent.type).toBe('compositionstart');
|
||||
expect(compositionStartEvent.data).toBe('test');
|
||||
},
|
||||
@@ -214,116 +214,116 @@ describe('BeforeInputEventPlugin', () => {
|
||||
spyOnBeforeInput,
|
||||
spyOnCompositionUpdate,
|
||||
}) => {
|
||||
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
|
||||
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
|
||||
expect(beforeInputEvent).toBeNull();
|
||||
expect(spyOnCompositionUpdate.mock.calls.length).toBe(1);
|
||||
expect(spyOnCompositionUpdate).toHaveBeenCalledTimes(1);
|
||||
expect(compositionUpdateEvent.type).toBe('compositionupdate');
|
||||
expect(compositionUpdateEvent.data).toBe('test string');
|
||||
},
|
||||
},
|
||||
{
|
||||
run: ({beforeInputEvent, spyOnBeforeInput}) => {
|
||||
expect(spyOnBeforeInput.mock.calls.length).toBe(1);
|
||||
expect(spyOnBeforeInput).toHaveBeenCalledTimes(1);
|
||||
expect(beforeInputEvent.type).toBe('compositionend');
|
||||
expect(beforeInputEvent.data).toBe('test string 3');
|
||||
},
|
||||
},
|
||||
{
|
||||
run: ({beforeInputEvent, spyOnBeforeInput}) => {
|
||||
expect(spyOnBeforeInput.mock.calls.length).toBe(1);
|
||||
expect(spyOnBeforeInput).toHaveBeenCalledTimes(1);
|
||||
expect(beforeInputEvent.type).toBe('textInput');
|
||||
expect(beforeInputEvent.data).toBe('abcß');
|
||||
},
|
||||
},
|
||||
{
|
||||
run: ({beforeInputEvent, spyOnBeforeInput}) => {
|
||||
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
|
||||
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
|
||||
expect(beforeInputEvent).toBeNull();
|
||||
},
|
||||
},
|
||||
{
|
||||
run: ({beforeInputEvent, spyOnBeforeInput}) => {
|
||||
expect(spyOnBeforeInput.mock.calls.length).toBe(1);
|
||||
expect(spyOnBeforeInput).toHaveBeenCalledTimes(1);
|
||||
expect(beforeInputEvent.type).toBe('keypress');
|
||||
expect(beforeInputEvent.data).toBe(' ');
|
||||
},
|
||||
},
|
||||
{
|
||||
run: ({beforeInputEvent, spyOnBeforeInput}) => {
|
||||
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
|
||||
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
|
||||
expect(beforeInputEvent).toBeNull();
|
||||
},
|
||||
},
|
||||
{
|
||||
run: ({beforeInputEvent, spyOnBeforeInput}) => {
|
||||
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
|
||||
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
|
||||
expect(beforeInputEvent).toBeNull();
|
||||
},
|
||||
},
|
||||
{
|
||||
run: ({beforeInputEvent, spyOnBeforeInput}) => {
|
||||
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
|
||||
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
|
||||
expect(beforeInputEvent).toBeNull();
|
||||
},
|
||||
},
|
||||
{
|
||||
run: ({beforeInputEvent, spyOnBeforeInput}) => {
|
||||
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
|
||||
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
|
||||
expect(beforeInputEvent).toBeNull();
|
||||
},
|
||||
},
|
||||
{
|
||||
run: ({beforeInputEvent, spyOnBeforeInput}) => {
|
||||
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
|
||||
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
|
||||
expect(beforeInputEvent).toBeNull();
|
||||
},
|
||||
},
|
||||
{
|
||||
run: ({beforeInputEvent, spyOnBeforeInput}) => {
|
||||
expect(spyOnBeforeInput.mock.calls.length).toBe(1);
|
||||
expect(spyOnBeforeInput).toHaveBeenCalledTimes(1);
|
||||
expect(beforeInputEvent.type).toBe('textInput');
|
||||
expect(beforeInputEvent.data).toBe('\uD83D\uDE0A');
|
||||
},
|
||||
},
|
||||
{
|
||||
run: ({beforeInputEvent, spyOnBeforeInput}) => {
|
||||
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
|
||||
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
|
||||
expect(beforeInputEvent).toBeNull();
|
||||
},
|
||||
},
|
||||
{
|
||||
run: ({beforeInputEvent, spyOnBeforeInput}) => {
|
||||
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
|
||||
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
|
||||
expect(beforeInputEvent).toBeNull();
|
||||
},
|
||||
},
|
||||
{
|
||||
run: ({beforeInputEvent, spyOnBeforeInput}) => {
|
||||
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
|
||||
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
|
||||
expect(beforeInputEvent).toBeNull();
|
||||
},
|
||||
},
|
||||
{
|
||||
run: ({beforeInputEvent, spyOnBeforeInput}) => {
|
||||
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
|
||||
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
|
||||
expect(beforeInputEvent).toBeNull();
|
||||
},
|
||||
},
|
||||
{
|
||||
run: ({beforeInputEvent, spyOnBeforeInput}) => {
|
||||
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
|
||||
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
|
||||
expect(beforeInputEvent).toBeNull();
|
||||
},
|
||||
},
|
||||
{
|
||||
run: ({beforeInputEvent, spyOnBeforeInput}) => {
|
||||
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
|
||||
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
|
||||
expect(beforeInputEvent).toBeNull();
|
||||
},
|
||||
},
|
||||
{
|
||||
run: ({beforeInputEvent, spyOnBeforeInput}) => {
|
||||
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
|
||||
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
|
||||
expect(beforeInputEvent).toBeNull();
|
||||
},
|
||||
},
|
||||
@@ -334,119 +334,119 @@ describe('BeforeInputEventPlugin', () => {
|
||||
assertions: [
|
||||
{
|
||||
run: ({beforeInputEvent, spyOnBeforeInput}) => {
|
||||
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
|
||||
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
|
||||
expect(beforeInputEvent).toBeNull();
|
||||
},
|
||||
},
|
||||
{
|
||||
run: ({beforeInputEvent, spyOnBeforeInput}) => {
|
||||
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
|
||||
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
|
||||
expect(beforeInputEvent).toBeNull();
|
||||
},
|
||||
},
|
||||
{
|
||||
run: ({beforeInputEvent, spyOnBeforeInput}) => {
|
||||
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
|
||||
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
|
||||
expect(beforeInputEvent).toBeNull();
|
||||
},
|
||||
},
|
||||
{
|
||||
run: ({beforeInputEvent, spyOnBeforeInput}) => {
|
||||
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
|
||||
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
|
||||
expect(beforeInputEvent).toBeNull();
|
||||
},
|
||||
},
|
||||
{
|
||||
run: ({beforeInputEvent, spyOnBeforeInput}) => {
|
||||
expect(spyOnBeforeInput.mock.calls.length).toBe(1);
|
||||
expect(spyOnBeforeInput).toHaveBeenCalledTimes(1);
|
||||
expect(beforeInputEvent.type).toBe('keypress');
|
||||
expect(beforeInputEvent.data).toBe('a');
|
||||
},
|
||||
},
|
||||
{
|
||||
run: ({beforeInputEvent, spyOnBeforeInput}) => {
|
||||
expect(spyOnBeforeInput.mock.calls.length).toBe(1);
|
||||
expect(spyOnBeforeInput).toHaveBeenCalledTimes(1);
|
||||
expect(beforeInputEvent.type).toBe('keypress');
|
||||
expect(beforeInputEvent.data).toBe(' ');
|
||||
},
|
||||
},
|
||||
{
|
||||
run: ({beforeInputEvent, spyOnBeforeInput}) => {
|
||||
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
|
||||
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
|
||||
expect(beforeInputEvent).toBeNull();
|
||||
},
|
||||
},
|
||||
{
|
||||
run: ({beforeInputEvent, spyOnBeforeInput}) => {
|
||||
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
|
||||
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
|
||||
expect(beforeInputEvent).toBeNull();
|
||||
},
|
||||
},
|
||||
{
|
||||
run: ({beforeInputEvent, spyOnBeforeInput}) => {
|
||||
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
|
||||
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
|
||||
expect(beforeInputEvent).toBeNull();
|
||||
},
|
||||
},
|
||||
{
|
||||
run: ({beforeInputEvent, spyOnBeforeInput}) => {
|
||||
expect(spyOnBeforeInput.mock.calls.length).toBe(1);
|
||||
expect(spyOnBeforeInput).toHaveBeenCalledTimes(1);
|
||||
expect(beforeInputEvent.type).toBe('keypress');
|
||||
expect(beforeInputEvent.data).toBe('c');
|
||||
},
|
||||
},
|
||||
{
|
||||
run: ({beforeInputEvent, spyOnBeforeInput}) => {
|
||||
expect(spyOnBeforeInput.mock.calls.length).toBe(1);
|
||||
expect(spyOnBeforeInput).toHaveBeenCalledTimes(1);
|
||||
expect(beforeInputEvent.type).toBe('keypress');
|
||||
expect(beforeInputEvent.data).toBe('\uD83D\uDE0A');
|
||||
},
|
||||
},
|
||||
{
|
||||
run: ({beforeInputEvent, spyOnBeforeInput}) => {
|
||||
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
|
||||
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
|
||||
expect(beforeInputEvent).toBeNull();
|
||||
},
|
||||
},
|
||||
{
|
||||
run: ({beforeInputEvent, spyOnBeforeInput}) => {
|
||||
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
|
||||
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
|
||||
expect(beforeInputEvent).toBeNull();
|
||||
},
|
||||
},
|
||||
{
|
||||
run: ({beforeInputEvent, spyOnBeforeInput}) => {
|
||||
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
|
||||
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
|
||||
expect(beforeInputEvent).toBeNull();
|
||||
},
|
||||
},
|
||||
{
|
||||
run: ({beforeInputEvent, spyOnBeforeInput}) => {
|
||||
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
|
||||
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
|
||||
expect(beforeInputEvent).toBeNull();
|
||||
},
|
||||
},
|
||||
{
|
||||
run: ({beforeInputEvent, spyOnBeforeInput}) => {
|
||||
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
|
||||
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
|
||||
expect(beforeInputEvent).toBeNull();
|
||||
},
|
||||
},
|
||||
{
|
||||
run: ({beforeInputEvent, spyOnBeforeInput}) => {
|
||||
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
|
||||
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
|
||||
expect(beforeInputEvent).toBeNull();
|
||||
},
|
||||
},
|
||||
{
|
||||
run: ({beforeInputEvent, spyOnBeforeInput}) => {
|
||||
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
|
||||
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
|
||||
expect(beforeInputEvent).toBeNull();
|
||||
},
|
||||
},
|
||||
{
|
||||
run: ({beforeInputEvent, spyOnBeforeInput}) => {
|
||||
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
|
||||
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
|
||||
expect(beforeInputEvent).toBeNull();
|
||||
},
|
||||
},
|
||||
@@ -457,122 +457,122 @@ describe('BeforeInputEventPlugin', () => {
|
||||
assertions: [
|
||||
{
|
||||
run: ({beforeInputEvent, spyOnBeforeInput}) => {
|
||||
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
|
||||
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
|
||||
expect(beforeInputEvent).toBeNull();
|
||||
},
|
||||
},
|
||||
{
|
||||
run: ({beforeInputEvent, spyOnBeforeInput}) => {
|
||||
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
|
||||
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
|
||||
expect(beforeInputEvent).toBeNull();
|
||||
},
|
||||
},
|
||||
{
|
||||
run: ({beforeInputEvent, spyOnBeforeInput}) => {
|
||||
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
|
||||
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
|
||||
expect(beforeInputEvent).toBeNull();
|
||||
},
|
||||
},
|
||||
{
|
||||
run: ({beforeInputEvent, spyOnBeforeInput}) => {
|
||||
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
|
||||
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
|
||||
expect(beforeInputEvent).toBeNull();
|
||||
},
|
||||
},
|
||||
{
|
||||
run: ({beforeInputEvent, spyOnBeforeInput}) => {
|
||||
expect(spyOnBeforeInput.mock.calls.length).toBe(1);
|
||||
expect(spyOnBeforeInput).toHaveBeenCalledTimes(1);
|
||||
expect(beforeInputEvent.type).toBe('keypress');
|
||||
expect(beforeInputEvent.data).toBe('a');
|
||||
},
|
||||
},
|
||||
{
|
||||
run: ({beforeInputEvent, spyOnBeforeInput}) => {
|
||||
expect(spyOnBeforeInput.mock.calls.length).toBe(1);
|
||||
expect(spyOnBeforeInput).toHaveBeenCalledTimes(1);
|
||||
expect(beforeInputEvent.type).toBe('keypress');
|
||||
expect(beforeInputEvent.data).toBe(' ');
|
||||
},
|
||||
},
|
||||
{
|
||||
run: ({beforeInputEvent, spyOnBeforeInput}) => {
|
||||
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
|
||||
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
|
||||
expect(beforeInputEvent).toBeNull();
|
||||
},
|
||||
},
|
||||
{
|
||||
run: ({beforeInputEvent, spyOnBeforeInput}) => {
|
||||
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
|
||||
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
|
||||
expect(beforeInputEvent).toBeNull();
|
||||
},
|
||||
},
|
||||
{
|
||||
run: ({beforeInputEvent, spyOnBeforeInput}) => {
|
||||
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
|
||||
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
|
||||
expect(beforeInputEvent).toBeNull();
|
||||
},
|
||||
},
|
||||
{
|
||||
run: ({beforeInputEvent, spyOnBeforeInput}) => {
|
||||
expect(spyOnBeforeInput.mock.calls.length).toBe(1);
|
||||
expect(spyOnBeforeInput).toHaveBeenCalledTimes(1);
|
||||
expect(beforeInputEvent.type).toBe('keypress');
|
||||
expect(beforeInputEvent.data).toBe('c');
|
||||
},
|
||||
},
|
||||
{
|
||||
run: ({beforeInputEvent, spyOnBeforeInput}) => {
|
||||
expect(spyOnBeforeInput.mock.calls.length).toBe(1);
|
||||
expect(spyOnBeforeInput).toHaveBeenCalledTimes(1);
|
||||
expect(beforeInputEvent.type).toBe('keypress');
|
||||
expect(beforeInputEvent.data).toBe('\uD83D\uDE0A');
|
||||
},
|
||||
},
|
||||
{
|
||||
run: ({beforeInputEvent, spyOnBeforeInput}) => {
|
||||
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
|
||||
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
|
||||
expect(beforeInputEvent).toBeNull();
|
||||
},
|
||||
},
|
||||
{
|
||||
run: ({beforeInputEvent, spyOnBeforeInput}) => {
|
||||
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
|
||||
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
|
||||
expect(beforeInputEvent).toBeNull();
|
||||
},
|
||||
},
|
||||
{
|
||||
run: ({beforeInputEvent, spyOnBeforeInput}) => {
|
||||
expect(spyOnBeforeInput.mock.calls.length).toBe(1);
|
||||
expect(spyOnBeforeInput).toHaveBeenCalledTimes(1);
|
||||
expect(beforeInputEvent.type).toBe('keydown');
|
||||
expect(beforeInputEvent.data).toBe('bar');
|
||||
},
|
||||
},
|
||||
{
|
||||
run: ({beforeInputEvent, spyOnBeforeInput}) => {
|
||||
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
|
||||
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
|
||||
expect(beforeInputEvent).toBeNull();
|
||||
},
|
||||
},
|
||||
{
|
||||
run: ({beforeInputEvent, spyOnBeforeInput}) => {
|
||||
expect(spyOnBeforeInput.mock.calls.length).toBe(1);
|
||||
expect(spyOnBeforeInput).toHaveBeenCalledTimes(1);
|
||||
expect(beforeInputEvent.type).toBe('keyup');
|
||||
expect(beforeInputEvent.data).toBe('BAR');
|
||||
},
|
||||
},
|
||||
{
|
||||
run: ({beforeInputEvent, spyOnBeforeInput}) => {
|
||||
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
|
||||
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
|
||||
expect(beforeInputEvent).toBeNull();
|
||||
},
|
||||
},
|
||||
{
|
||||
run: ({beforeInputEvent, spyOnBeforeInput}) => {
|
||||
expect(spyOnBeforeInput.mock.calls.length).toBe(1);
|
||||
expect(spyOnBeforeInput).toHaveBeenCalledTimes(1);
|
||||
expect(beforeInputEvent.type).toBe('keypress');
|
||||
expect(beforeInputEvent.data).toBe('Bar');
|
||||
},
|
||||
},
|
||||
{
|
||||
run: ({beforeInputEvent, spyOnBeforeInput}) => {
|
||||
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
|
||||
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
|
||||
expect(beforeInputEvent).toBeNull();
|
||||
},
|
||||
},
|
||||
@@ -583,120 +583,120 @@ describe('BeforeInputEventPlugin', () => {
|
||||
assertions: [
|
||||
{
|
||||
run: ({beforeInputEvent, spyOnBeforeInput}) => {
|
||||
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
|
||||
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
|
||||
expect(beforeInputEvent).toBeNull();
|
||||
},
|
||||
},
|
||||
{
|
||||
run: ({beforeInputEvent, spyOnBeforeInput}) => {
|
||||
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
|
||||
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
|
||||
expect(beforeInputEvent).toBeNull();
|
||||
},
|
||||
},
|
||||
{
|
||||
run: ({beforeInputEvent, spyOnBeforeInput}) => {
|
||||
expect(spyOnBeforeInput.mock.calls.length).toBe(1);
|
||||
expect(spyOnBeforeInput).toHaveBeenCalledTimes(1);
|
||||
expect(beforeInputEvent.type).toBe('compositionend');
|
||||
expect(beforeInputEvent.data).toBe('test string 3');
|
||||
},
|
||||
},
|
||||
{
|
||||
run: ({beforeInputEvent, spyOnBeforeInput}) => {
|
||||
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
|
||||
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
|
||||
expect(beforeInputEvent).toBeNull();
|
||||
},
|
||||
},
|
||||
{
|
||||
run: ({beforeInputEvent, spyOnBeforeInput}) => {
|
||||
expect(spyOnBeforeInput.mock.calls.length).toBe(1);
|
||||
expect(spyOnBeforeInput).toHaveBeenCalledTimes(1);
|
||||
expect(beforeInputEvent.type).toBe('keypress');
|
||||
expect(beforeInputEvent.data).toBe('a');
|
||||
},
|
||||
},
|
||||
{
|
||||
run: ({beforeInputEvent, spyOnBeforeInput}) => {
|
||||
expect(spyOnBeforeInput.mock.calls.length).toBe(1);
|
||||
expect(spyOnBeforeInput).toHaveBeenCalledTimes(1);
|
||||
expect(beforeInputEvent.type).toBe('keypress');
|
||||
expect(beforeInputEvent.data).toBe(' ');
|
||||
},
|
||||
},
|
||||
{
|
||||
run: ({beforeInputEvent, spyOnBeforeInput}) => {
|
||||
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
|
||||
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
|
||||
expect(beforeInputEvent).toBeNull();
|
||||
},
|
||||
},
|
||||
{
|
||||
run: ({beforeInputEvent, spyOnBeforeInput}) => {
|
||||
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
|
||||
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
|
||||
expect(beforeInputEvent).toBeNull();
|
||||
},
|
||||
},
|
||||
{
|
||||
run: ({beforeInputEvent, spyOnBeforeInput}) => {
|
||||
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
|
||||
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
|
||||
expect(beforeInputEvent).toBeNull();
|
||||
},
|
||||
},
|
||||
{
|
||||
run: ({beforeInputEvent, spyOnBeforeInput}) => {
|
||||
expect(spyOnBeforeInput.mock.calls.length).toBe(1);
|
||||
expect(spyOnBeforeInput).toHaveBeenCalledTimes(1);
|
||||
expect(beforeInputEvent.type).toBe('keypress');
|
||||
expect(beforeInputEvent.data).toBe('c');
|
||||
},
|
||||
},
|
||||
{
|
||||
run: ({beforeInputEvent, spyOnBeforeInput}) => {
|
||||
expect(spyOnBeforeInput.mock.calls.length).toBe(1);
|
||||
expect(spyOnBeforeInput).toHaveBeenCalledTimes(1);
|
||||
expect(beforeInputEvent.type).toBe('keypress');
|
||||
expect(beforeInputEvent.data).toBe('\uD83D\uDE0A');
|
||||
},
|
||||
},
|
||||
{
|
||||
run: ({beforeInputEvent, spyOnBeforeInput}) => {
|
||||
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
|
||||
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
|
||||
expect(beforeInputEvent).toBeNull();
|
||||
},
|
||||
},
|
||||
{
|
||||
run: ({beforeInputEvent, spyOnBeforeInput}) => {
|
||||
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
|
||||
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
|
||||
expect(beforeInputEvent).toBeNull();
|
||||
},
|
||||
},
|
||||
{
|
||||
run: ({beforeInputEvent, spyOnBeforeInput}) => {
|
||||
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
|
||||
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
|
||||
expect(beforeInputEvent).toBeNull();
|
||||
},
|
||||
},
|
||||
{
|
||||
run: ({beforeInputEvent, spyOnBeforeInput}) => {
|
||||
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
|
||||
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
|
||||
expect(beforeInputEvent).toBeNull();
|
||||
},
|
||||
},
|
||||
{
|
||||
run: ({beforeInputEvent, spyOnBeforeInput}) => {
|
||||
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
|
||||
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
|
||||
expect(beforeInputEvent).toBeNull();
|
||||
},
|
||||
},
|
||||
{
|
||||
run: ({beforeInputEvent, spyOnBeforeInput}) => {
|
||||
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
|
||||
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
|
||||
expect(beforeInputEvent).toBeNull();
|
||||
},
|
||||
},
|
||||
{
|
||||
run: ({beforeInputEvent, spyOnBeforeInput}) => {
|
||||
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
|
||||
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
|
||||
expect(beforeInputEvent).toBeNull();
|
||||
},
|
||||
},
|
||||
{
|
||||
run: ({beforeInputEvent, spyOnBeforeInput}) => {
|
||||
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
|
||||
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
|
||||
expect(beforeInputEvent).toBeNull();
|
||||
},
|
||||
},
|
||||
|
||||
@@ -32,6 +32,31 @@ describe('ChangeEventPlugin', () => {
|
||||
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');
|
||||
document.body.appendChild(container);
|
||||
});
|
||||
@@ -450,8 +475,6 @@ describe('ChangeEventPlugin', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
ReactFeatureFlags = require('shared/ReactFeatureFlags');
|
||||
ReactFeatureFlags.enableAsyncSubtreeAPI = true;
|
||||
ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
|
||||
ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
|
||||
ReactDOM = require('react-dom');
|
||||
});
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user