Compare commits
193 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
628f6f50b5 | ||
|
|
9b78e66d94 | ||
|
|
64f636b437 | ||
|
|
29b4d0721c | ||
|
|
b43b36dd91 | ||
|
|
9e075d16b2 | ||
|
|
bc1f3e1a69 | ||
|
|
b6173e643a | ||
|
|
8aefb1995c | ||
|
|
643dcb5526 | ||
|
|
1e1a989422 | ||
|
|
3bd6adceda | ||
|
|
50eafef07e | ||
|
|
4f480d0645 | ||
|
|
f4ca909e0e | ||
|
|
255d9ac5f5 | ||
|
|
64aae7b06f | ||
|
|
d8570ec6ea | ||
|
|
05e487a12f | ||
|
|
5d6129b5d1 | ||
|
|
ff900095b9 | ||
|
|
1b6e269751 | ||
|
|
3ac81a57fb | ||
|
|
435259f189 | ||
|
|
2e4948a34d | ||
|
|
e706721490 | ||
|
|
b979db4e72 | ||
|
|
18875b2401 | ||
|
|
19f6fe170c | ||
|
|
6250462bed | ||
|
|
5e21157d86 | ||
|
|
40568e9773 | ||
|
|
5586b3022c | ||
|
|
7580d6717c | ||
|
|
80e32487b9 | ||
|
|
c1b573bf2f | ||
|
|
fc921a1693 | ||
|
|
1c63b2e416 | ||
|
|
2bb227ef80 | ||
|
|
24f824250f | ||
|
|
edeea07207 | ||
|
|
9cc094a19a | ||
|
|
f749045a5b | ||
|
|
59f21f1b24 | ||
|
|
7e2ab87a61 | ||
|
|
5d3d71b1dd | ||
|
|
cca994c6a1 | ||
|
|
b05cd611ba | ||
|
|
195b3db618 | ||
|
|
2c1e5d2b22 | ||
|
|
86883d7aac | ||
|
|
b4e2fac09d | ||
|
|
e6226e6493 | ||
|
|
3bec713b85 | ||
|
|
ee38c00bd5 | ||
|
|
4d463fce4e | ||
|
|
d259f45813 | ||
|
|
9357a483e9 | ||
|
|
22ef96ae63 | ||
|
|
2b903da355 | ||
|
|
0eac01abcd | ||
|
|
f887d1aa27 | ||
|
|
1959671d45 | ||
|
|
ccc6100d7f | ||
|
|
6215e1c2db | ||
|
|
85d9655d6f | ||
|
|
3e09677836 | ||
|
|
31350237fa | ||
|
|
79734771cd | ||
|
|
dbc46ac63e | ||
|
|
1b9328cd9b | ||
|
|
897976600c | ||
|
|
72592310a8 | ||
|
|
e7494c86c5 | ||
|
|
a5e951d4cc | ||
|
|
95056b6836 | ||
|
|
fd2a3ae57f | ||
|
|
933f6a07ca | ||
|
|
7dc9745427 | ||
|
|
9354dd2752 | ||
|
|
4c270375e9 | ||
|
|
6fef7c47a9 | ||
|
|
9fe1031244 | ||
|
|
e84327ba0c | ||
|
|
4b0cdf29a8 | ||
|
|
24dbe851e8 | ||
|
|
bb75a4fa89 | ||
|
|
7309c5f934 | ||
|
|
9bb3fbe893 | ||
|
|
3c54df0914 | ||
|
|
b66e86d957 | ||
|
|
c2d1561c60 | ||
|
|
0253ee9a2e | ||
|
|
36a6e29bb3 | ||
|
|
7c21bf72ac | ||
|
|
f42431abe1 | ||
|
|
0b5a26a489 | ||
|
|
0cf22a56a1 | ||
|
|
b6c423daad | ||
|
|
8a347ed024 | ||
|
|
be603f5a51 | ||
|
|
b15bf36750 | ||
|
|
612a76812f | ||
|
|
12c000412d | ||
|
|
2afeebdcc4 | ||
|
|
9ac42dd074 | ||
|
|
7bf40e1cfd | ||
|
|
031a5aaffb | ||
|
|
e039e690b5 | ||
|
|
b617db3d96 | ||
|
|
3c1efa0d77 | ||
|
|
9e937e7e86 | ||
|
|
b43eec7eaa | ||
|
|
acfe4b21b2 | ||
|
|
5235d193d7 | ||
|
|
5064c7f6aa | ||
|
|
6d105ad3f6 | ||
|
|
dc18b8b8d2 | ||
|
|
f523b2e0d3 | ||
|
|
d75323f65d | ||
|
|
79572e34d1 | ||
|
|
b64938e123 | ||
|
|
969f4b5bb8 | ||
|
|
3816ae7c38 | ||
|
|
5fc97420b9 | ||
|
|
6470e0f169 | ||
|
|
54f66731c8 | ||
|
|
237a966da0 | ||
|
|
007a276b65 | ||
|
|
3fdfa231ad | ||
|
|
a7d07ff24d | ||
|
|
3ad076472c | ||
|
|
1f2da0babd | ||
|
|
a807c307c4 | ||
|
|
2586303662 | ||
|
|
8e74a31b2d | ||
|
|
39dbb14da3 | ||
|
|
532810a370 | ||
|
|
f30ccb9ed9 | ||
|
|
1832f832c5 | ||
|
|
9a5f28dbed | ||
|
|
769dd522a2 | ||
|
|
df8db4e005 | ||
|
|
fc43644eba | ||
|
|
a61886b16b | ||
|
|
b53ea6ca05 | ||
|
|
f4cc45ce96 | ||
|
|
ade764157f | ||
|
|
3dcec3a925 | ||
|
|
2c6ea0b3ff | ||
|
|
01bce8c248 | ||
|
|
6cff70a740 | ||
|
|
b8f8258775 | ||
|
|
a7b4d51a20 | ||
|
|
be3bfa6fab | ||
|
|
182f64f938 | ||
|
|
6cb6b1d668 | ||
|
|
38dd17ab98 | ||
|
|
61d3dd0e08 | ||
|
|
e701632ad4 | ||
|
|
ce4b3e9981 | ||
|
|
dee03049f5 | ||
|
|
f50f39b55f | ||
|
|
3452706308 | ||
|
|
cd1bdcd067 | ||
|
|
4f02c93c7c | ||
|
|
053cf0fedc | ||
|
|
0f3838a01b | ||
|
|
cb09dbe0ab | ||
|
|
6095993d46 | ||
|
|
62ef25077e | ||
|
|
f4148b2561 | ||
|
|
fadc97167f | ||
|
|
36fd29f09f | ||
|
|
a2e05b6c14 | ||
|
|
6dc2734b41 | ||
|
|
a1ff9fd7bb | ||
|
|
273679a78c | ||
|
|
d0fc0ba0a6 | ||
|
|
bdcdb69a24 | ||
|
|
515746c217 | ||
|
|
9a35adc96d | ||
|
|
8eee0eb01c | ||
|
|
f4e974d26e | ||
|
|
6cd365cac6 | ||
|
|
048879edad | ||
|
|
3497ccc149 | ||
|
|
0f64703edf | ||
|
|
3f9c036758 | ||
|
|
f6b8d31a76 | ||
|
|
9c02d26549 | ||
|
|
57b2fbb741 | ||
|
|
8075c8505b |
@@ -193,6 +193,22 @@ jobs:
|
||||
- dist
|
||||
- sizes/*.json
|
||||
|
||||
build_devtools_and_process_artifacts:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
parallelism: 20
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace: *attach_workspace
|
||||
- *restore_yarn_cache
|
||||
- *run_yarn
|
||||
- run:
|
||||
environment:
|
||||
RELEASE_CHANNEL: experimental
|
||||
command: ./scripts/circleci/pack_and_store_devtools_artifacts.sh
|
||||
- store_artifacts:
|
||||
path: ./build/devtools.tgz
|
||||
|
||||
# These jobs are named differently so we can distinguish the stable and
|
||||
# and experimental artifacts
|
||||
process_artifacts: *process_artifacts
|
||||
@@ -267,7 +283,7 @@ jobs:
|
||||
RELEASE_CHANNEL: experimental
|
||||
command: yarn test-build --maxWorkers=2
|
||||
|
||||
test_build_devtools:
|
||||
test_devtools:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
steps:
|
||||
@@ -277,8 +293,8 @@ jobs:
|
||||
- *run_yarn
|
||||
- run:
|
||||
environment:
|
||||
RELEASE_CHANNEL: stable
|
||||
command: yarn test-build --maxWorkers=2
|
||||
RELEASE_CHANNEL: experimental
|
||||
command: yarn test-build-devtools --maxWorkers=2
|
||||
|
||||
test_dom_fixtures:
|
||||
docker: *docker
|
||||
@@ -374,9 +390,6 @@ workflows:
|
||||
- test_build_prod:
|
||||
requires:
|
||||
- build
|
||||
- test_build_devtools:
|
||||
requires:
|
||||
- build
|
||||
- test_dom_fixtures:
|
||||
requires:
|
||||
- build
|
||||
@@ -405,6 +418,12 @@ workflows:
|
||||
- lint_build:
|
||||
requires:
|
||||
- build_experimental
|
||||
- test_devtools:
|
||||
requires:
|
||||
- build_experimental
|
||||
- build_devtools_and_process_artifacts:
|
||||
requires:
|
||||
- build_experimental
|
||||
|
||||
fuzz_tests:
|
||||
triggers:
|
||||
|
||||
10
.codesandbox/ci.json
Normal file
10
.codesandbox/ci.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"packages": ["packages/react", "packages/react-dom", "packages/scheduler"],
|
||||
"buildCommand": "build --type=NODE react/index,react-dom/index,scheduler/index,scheduler/tracing",
|
||||
"publishDirectory": {
|
||||
"react": "build/node_modules/react",
|
||||
"react-dom": "build/node_modules/react-dom",
|
||||
"scheduler": "build/node_modules/scheduler"
|
||||
},
|
||||
"sandboxes": ["new"]
|
||||
}
|
||||
26
.eslintrc.js
26
.eslintrc.js
@@ -9,7 +9,10 @@ const OFF = 0;
|
||||
const ERROR = 2;
|
||||
|
||||
module.exports = {
|
||||
extends: 'fbjs',
|
||||
extends: [
|
||||
'fbjs',
|
||||
'prettier'
|
||||
],
|
||||
|
||||
// Stop ESLint from looking for a configuration file in parent folders
|
||||
root: true,
|
||||
@@ -30,10 +33,10 @@ module.exports = {
|
||||
rules: {
|
||||
'accessor-pairs': OFF,
|
||||
'brace-style': [ERROR, '1tbs'],
|
||||
'comma-dangle': [ERROR, 'always-multiline'],
|
||||
'consistent-return': OFF,
|
||||
'dot-location': [ERROR, 'property'],
|
||||
'dot-notation': ERROR,
|
||||
// We use console['error']() as a signal to not transform it:
|
||||
'dot-notation': [ERROR, {allowPattern: '^(error|warn)$'}],
|
||||
'eol-last': ERROR,
|
||||
eqeqeq: [ERROR, 'allow-null'],
|
||||
indent: OFF,
|
||||
@@ -92,7 +95,9 @@ module.exports = {
|
||||
// the second argument of warning/invariant should be a literal string
|
||||
'react-internal/no-primitive-constructors': ERROR,
|
||||
'react-internal/no-to-warn-dev-within-to-throw': ERROR,
|
||||
'react-internal/warning-and-invariant-args': ERROR,
|
||||
'react-internal/invariant-args': ERROR,
|
||||
'react-internal/warning-args': ERROR,
|
||||
'react-internal/no-production-logging': ERROR,
|
||||
},
|
||||
|
||||
overrides: [
|
||||
@@ -133,6 +138,19 @@ module.exports = {
|
||||
'jest/valid-expect-in-promise': ERROR,
|
||||
},
|
||||
},
|
||||
{
|
||||
files: [
|
||||
'**/__tests__/**/*.js',
|
||||
'scripts/**/*.js',
|
||||
'packages/*/npm/**/*.js',
|
||||
'packages/dom-event-testing-library/**/*.js',
|
||||
'packages/react-devtools*/**/*.js'
|
||||
],
|
||||
rules: {
|
||||
'react-internal/no-production-logging': OFF,
|
||||
'react-internal/warning-args': OFF,
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['packages/react-native-renderer/**/*.js'],
|
||||
globals: {
|
||||
|
||||
16
.github/ISSUE_TEMPLATE.md
vendored
16
.github/ISSUE_TEMPLATE.md
vendored
@@ -1,14 +1,4 @@
|
||||
<!--
|
||||
Note: if the issue is about documentation or the website, please file it at:
|
||||
https://github.com/reactjs/reactjs.org/issues/new
|
||||
-->
|
||||
👉 Please follow one of these issue templates:
|
||||
- https://github.com/facebook/react/issues/new/choose
|
||||
|
||||
**Do you want to request a *feature* or report a *bug*?**
|
||||
|
||||
**What is the current behavior?**
|
||||
|
||||
**If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem. Your bug will get fixed much faster if we can run your code and it doesn't have dependencies other than React. Paste the link to your JSFiddle (https://jsfiddle.net/Luktwrdm/) or CodeSandbox (https://codesandbox.io/s/new) example below:**
|
||||
|
||||
**What is the expected behavior?**
|
||||
|
||||
**Which versions of React, and which browser / OS are affected by this issue? Did this work in previous versions of React?**
|
||||
Note: to keep the backlog clean and actionable, issues may be immediately closed if they do not follow one of the above issue templates.
|
||||
|
||||
41
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
41
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
---
|
||||
name: "🐛 Bug Report"
|
||||
about: Report a reproducible bug or regression.
|
||||
title: 'Bug: '
|
||||
labels: 'Status: Unconfirmed'
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
Please provide a clear and concise description of what the bug is. Include
|
||||
screenshots if needed. Please test using the latest version of the relevant
|
||||
React packages to make sure your issue has not already been fixed.
|
||||
-->
|
||||
|
||||
React version:
|
||||
|
||||
## Steps To Reproduce
|
||||
|
||||
1.
|
||||
2.
|
||||
|
||||
<!--
|
||||
Your bug will get fixed much faster if we can run your code and it doesn't
|
||||
have dependencies other than React. Issues without reproduction steps or
|
||||
code examples may be immediately closed as not actionable.
|
||||
-->
|
||||
|
||||
Link to code example:
|
||||
|
||||
<!--
|
||||
Please provide a CodeSandbox (https://codesandbox.io/s/new), a link to a
|
||||
repository on GitHub, or provide a minimal code example that reproduces the
|
||||
problem. You may provide a screenshot of the application if you think it is
|
||||
relevant to your bug report. Here are some tips for providing a minimal
|
||||
example: https://stackoverflow.com/help/mcve.
|
||||
-->
|
||||
|
||||
## The current behavior
|
||||
|
||||
|
||||
## The expected behavior
|
||||
13
.github/ISSUE_TEMPLATE/documentation.md
vendored
Normal file
13
.github/ISSUE_TEMPLATE/documentation.md
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
---
|
||||
name: "📃 Documentation Issue"
|
||||
about: This issue tracker is not for documentation issues. Please file documentation issues at https://github.com/reactjs/reactjs.org.
|
||||
title: 'Docs: '
|
||||
labels: 'Resolution: Invalid'
|
||||
|
||||
---
|
||||
|
||||
🚨 This issue tracker is not for documentation issues. 🚨
|
||||
|
||||
The React website is hosted on a separate repository. You may let the
|
||||
team know about any issues with the documentation by opening an issue there:
|
||||
- https://github.com/reactjs/reactjs.org/issues/new
|
||||
29
.github/ISSUE_TEMPLATE/question.md
vendored
Normal file
29
.github/ISSUE_TEMPLATE/question.md
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
---
|
||||
name: "🤔 Questions and Help"
|
||||
about: This issue tracker is not for questions. Please ask questions at https://stackoverflow.com/questions/tagged/react.
|
||||
title: 'Question: '
|
||||
labels: 'Resolution: Invalid, Type: Question'
|
||||
|
||||
---
|
||||
|
||||
🚨 This issue tracker is not for questions. 🚨
|
||||
|
||||
As it happens, support requests that are created as issues are likely to be closed. We want to make sure you are able to find the help you seek. Please take a look at the following resources.
|
||||
|
||||
## Coding Questions
|
||||
|
||||
If you have a coding question related to React and React DOM, it might be better suited for Stack Overflow. It's a great place to browse through frequent questions about using React, as well as ask for help with specific questions.
|
||||
|
||||
https://stackoverflow.com/questions/tagged/react
|
||||
|
||||
## Talk to other React developers
|
||||
|
||||
There are many online forums which are a great place for discussion about best practices and application architecture as well as the future of React.
|
||||
|
||||
https://reactjs.org/community/support.html#popular-discussion-forums
|
||||
|
||||
## Proposals
|
||||
|
||||
If you'd like to discuss topics related to the future of React, or would like to propose a new feature or change before sending a pull request, please check out the discussions and proposals repository.
|
||||
|
||||
https://github.com/reactjs/rfcs
|
||||
37
.github/PULL_REQUEST_TEMPLATE.md
vendored
37
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,14 +1,27 @@
|
||||
**Before submitting a pull request,** please make sure the following is done:
|
||||
<!--
|
||||
Thanks for submitting a pull request!
|
||||
We appreciate you spending the time to work on these changes. Please provide enough information so that others can review your pull request. The three fields below are mandatory.
|
||||
|
||||
1. Fork [the repository](https://github.com/facebook/react) and create your branch from `master`.
|
||||
2. Run `yarn` in the repository root.
|
||||
3. If you've fixed a bug or added code that should be tested, add tests!
|
||||
4. Ensure the test suite passes (`yarn test`). Tip: `yarn test --watch TestName` is helpful in development.
|
||||
5. Run `yarn test-prod` to test in the production environment. It supports the same options as `yarn test`.
|
||||
6. If you need a debugger, run `yarn debug-test --watch TestName`, open `chrome://inspect`, and press "Inspect".
|
||||
7. Format your code with [prettier](https://github.com/prettier/prettier) (`yarn prettier`).
|
||||
8. Make sure your code lints (`yarn lint`). Tip: `yarn linc` to only check changed files.
|
||||
9. Run the [Flow](https://flowtype.org/) typechecks (`yarn flow`).
|
||||
10. If you haven't already, complete the CLA.
|
||||
Before submitting a pull request, please make sure the following is done:
|
||||
|
||||
**Learn more about contributing:** https://reactjs.org/docs/how-to-contribute.html
|
||||
1. Fork [the repository](https://github.com/facebook/react) and create your branch from `master`.
|
||||
2. Run `yarn` in the repository root.
|
||||
3. If you've fixed a bug or added code that should be tested, add tests!
|
||||
4. Ensure the test suite passes (`yarn test`). Tip: `yarn test --watch TestName` is helpful in development.
|
||||
5. Run `yarn test-prod` to test in the production environment. It supports the same options as `yarn test`.
|
||||
6. If you need a debugger, run `yarn debug-test --watch TestName`, open `chrome://inspect`, and press "Inspect".
|
||||
7. Format your code with [prettier](https://github.com/prettier/prettier) (`yarn prettier`).
|
||||
8. Make sure your code lints (`yarn lint`). Tip: `yarn linc` to only check changed files.
|
||||
9. Run the [Flow](https://flowtype.org/) typechecks (`yarn flow`).
|
||||
10. If you haven't already, complete the CLA.
|
||||
|
||||
Learn more about contributing: https://reactjs.org/docs/how-to-contribute.html
|
||||
-->
|
||||
|
||||
## Summary
|
||||
|
||||
<!-- Explain the **motivation** for making this change. What existing problem does the pull request solve? -->
|
||||
|
||||
## Test Plan
|
||||
|
||||
<!-- Demonstrate the code is solid. Example: The exact commands you ran and their output, screenshots / videos if the pull request changes the user interface. -->
|
||||
|
||||
31
.github/stale.yml
vendored
Normal file
31
.github/stale.yml
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
# Configuration for probot-stale - https://github.com/probot/stale
|
||||
# Number of days of inactivity before an issue becomes stale
|
||||
daysUntilStale: 90
|
||||
# Number of days of inactivity before a stale issue is closed
|
||||
daysUntilClose: 7
|
||||
# Issues with these labels will never be considered stale
|
||||
exemptLabels:
|
||||
- "Partner"
|
||||
- "React Core Team"
|
||||
- "Resolution: Backlog"
|
||||
- "Type: Bug"
|
||||
- "Type: Discussion"
|
||||
- "Type: Regression"
|
||||
# Label to use when marking an issue as stale
|
||||
staleLabel: "Resolution: Stale"
|
||||
issues:
|
||||
# Comment to post when marking an issue as stale.
|
||||
markComment: >
|
||||
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs.
|
||||
Thank you for your contribution.
|
||||
# Comment to post when closing a stale issue.
|
||||
closeComment: >
|
||||
Closing this issue after a prolonged period of inactivity. If this issue is still present in the latest release, please create a new issue with up-to-date information. Thank you!
|
||||
pulls:
|
||||
# Comment to post when marking a pull request as stale.
|
||||
markComment: >
|
||||
This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs.
|
||||
Thank you for your contribution.
|
||||
# Comment to post when closing a stale pull request.
|
||||
closeComment: >
|
||||
Closing this pull request after a prolonged period of inactivity. If this issue is still present in the latest release, please ask for this pull request to be reopened. Thank you!
|
||||
1
.mailmap
1
.mailmap
@@ -88,6 +88,7 @@ Kevin Coughlin <kevintcoughlin@gmail.com> <kevincoughlin@tumblr.com>
|
||||
Krystian Karczewski <karcz.k@gmail.com>
|
||||
Kunal Mehta <k.mehta@berkeley.edu> <kunalm@fb.com>
|
||||
Laurence Rowe <l@lrowe.co.uk> <laurence@lrowe.co.uk>
|
||||
Lea Rosema <terabaud@gmail.com>
|
||||
Marcin K. <katzoo@github.mail>
|
||||
Mark Anderson <undernewmanagement@users.noreply.github.com>
|
||||
Mark Funk <mfunk86@gmail.com> <mark@boomtownroi.com>
|
||||
|
||||
@@ -8,7 +8,7 @@ module.exports = {
|
||||
jsxBracketSameLine: true,
|
||||
trailingComma: 'es5',
|
||||
printWidth: 80,
|
||||
parser: 'babylon',
|
||||
parser: 'babel',
|
||||
|
||||
overrides: [
|
||||
{
|
||||
|
||||
19
CHANGELOG.md
19
CHANGELOG.md
@@ -6,6 +6,23 @@
|
||||
</summary>
|
||||
</details>
|
||||
|
||||
## 16.12.0 (November 14, 2019)
|
||||
|
||||
### React DOM
|
||||
|
||||
* Fix passive effects (`useEffect`) not being fired in a multi-root app. ([@acdlite](https://github.com/acdlite) in [#17347](https://github.com/facebook/react/pull/17347))
|
||||
|
||||
### React Is
|
||||
|
||||
* Fix `lazy` and `memo` types considered elements instead of components ([@bvaughn](https://github.com/bvaughn) in [#17278](https://github.com/facebook/react/pull/17278))
|
||||
|
||||
## 16.11.0 (October 22, 2019)
|
||||
|
||||
### React DOM
|
||||
|
||||
* Fix `mouseenter` handlers from firing twice inside nested React containers. [@yuanoook](https://github.com/yuanoook) in [#16928](https://github.com/facebook/react/pull/16928)
|
||||
* Remove `unstable_createRoot` and `unstable_createSyncRoot` experimental APIs. (These are available in the Experimental channel as `createRoot` and `createSyncRoot`.) ([@acdlite](http://github.com/acdlite) in [#17088](https://github.com/facebook/react/pull/17088))
|
||||
|
||||
## 16.10.2 (October 3, 2019)
|
||||
|
||||
### React DOM
|
||||
@@ -517,7 +534,7 @@ This release was published in a broken state and should be skipped.
|
||||
* 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))
|
||||
|
||||
## React Test Renderer
|
||||
### 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))
|
||||
|
||||
7
SECURITY.md
Normal file
7
SECURITY.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# Reporting Security Issues
|
||||
|
||||
If you believe you have found a security vulnerability in React, we encourage you to let us know right away. We will investigate all legitimate reports and do our best to quickly fix the problem.
|
||||
|
||||
Please refer to the following page for our responsible disclosure policy, reward guidelines, and those things that should not be reported:
|
||||
|
||||
https://www.facebook.com/whitehat
|
||||
@@ -461,9 +461,7 @@ function prepareState(initGlobals) {
|
||||
hasSameBehaviorForAll,
|
||||
rowPatternHash,
|
||||
// "Good enough" id that we can store in localStorage
|
||||
rowIdHash: `${attribute.name} ${attribute.tagName} ${
|
||||
attribute.overrideStringValue
|
||||
}`,
|
||||
rowIdHash: `${attribute.name} ${attribute.tagName} ${attribute.overrideStringValue}`,
|
||||
};
|
||||
const rowGroup = rowPatternHashes.get(rowPatternHash) || new Set();
|
||||
rowGroup.add(row);
|
||||
@@ -866,14 +864,12 @@ class App extends React.Component {
|
||||
// Sort
|
||||
switch (sortOrder) {
|
||||
case ALPHABETICAL:
|
||||
return filteredAttributes.sort(
|
||||
(attr1, attr2) =>
|
||||
attr1.name.toLowerCase() < attr2.name.toLowerCase() ? -1 : 1
|
||||
return filteredAttributes.sort((attr1, attr2) =>
|
||||
attr1.name.toLowerCase() < attr2.name.toLowerCase() ? -1 : 1
|
||||
);
|
||||
case REV_ALPHABETICAL:
|
||||
return filteredAttributes.sort(
|
||||
(attr1, attr2) =>
|
||||
attr1.name.toLowerCase() < attr2.name.toLowerCase() ? 1 : -1
|
||||
return filteredAttributes.sort((attr1, attr2) =>
|
||||
attr1.name.toLowerCase() < attr2.name.toLowerCase() ? 1 : -1
|
||||
);
|
||||
case GROUPED_BY_ROW_PATTERN: {
|
||||
return filteredAttributes.sort((attr1, attr2) => {
|
||||
|
||||
@@ -268,7 +268,7 @@ async-each@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d"
|
||||
|
||||
async@^1.4.0, async@^1.5.2:
|
||||
async@^1.5.2:
|
||||
version "1.5.2"
|
||||
resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
|
||||
|
||||
@@ -1448,6 +1448,10 @@ commander@2.11.x, commander@~2.11.0:
|
||||
version "2.11.0"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.11.0.tgz#157152fd1e7a6c8d98a5b715cf376df928004563"
|
||||
|
||||
commander@~2.20.3:
|
||||
version "2.20.3"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
|
||||
|
||||
commondir@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
|
||||
@@ -2838,14 +2842,14 @@ handle-thing@^1.2.5:
|
||||
resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-1.2.5.tgz#fd7aad726bf1a5fd16dfc29b2f7a6601d27139c4"
|
||||
|
||||
handlebars@^4.0.3:
|
||||
version "4.0.10"
|
||||
resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.10.tgz#3d30c718b09a3d96f23ea4cc1f403c4d3ba9ff4f"
|
||||
version "4.5.3"
|
||||
resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.5.3.tgz#5cf75bd8714f7605713511a56be7c349becb0482"
|
||||
dependencies:
|
||||
async "^1.4.0"
|
||||
neo-async "^2.6.0"
|
||||
optimist "^0.6.1"
|
||||
source-map "^0.4.4"
|
||||
source-map "^0.6.1"
|
||||
optionalDependencies:
|
||||
uglify-js "^2.6"
|
||||
uglify-js "^3.1.4"
|
||||
|
||||
har-schema@^1.0.5:
|
||||
version "1.0.5"
|
||||
@@ -4180,6 +4184,10 @@ negotiator@0.6.1:
|
||||
version "0.6.1"
|
||||
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9"
|
||||
|
||||
neo-async@^2.6.0:
|
||||
version "2.6.1"
|
||||
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c"
|
||||
|
||||
no-case@^2.2.0:
|
||||
version "2.3.1"
|
||||
resolved "https://registry.yarnpkg.com/no-case/-/no-case-2.3.1.tgz#7aeba1c73a52184265554b7dc03baf720df80081"
|
||||
@@ -5696,11 +5704,9 @@ source-map@0.5.x, source-map@^0.5.0, source-map@^0.5.3, source-map@^0.5.6, sourc
|
||||
version "0.5.7"
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
|
||||
|
||||
source-map@^0.4.4:
|
||||
version "0.4.4"
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b"
|
||||
dependencies:
|
||||
amdefine ">=0.0.4"
|
||||
source-map@^0.6.1, source-map@~0.6.1:
|
||||
version "0.6.1"
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
|
||||
|
||||
source-map@~0.2.0:
|
||||
version "0.2.0"
|
||||
@@ -6090,7 +6096,7 @@ uglify-js@3.0.x, uglify-js@^3.0.13:
|
||||
commander "~2.11.0"
|
||||
source-map "~0.5.1"
|
||||
|
||||
uglify-js@^2.6, uglify-js@^2.8.29:
|
||||
uglify-js@^2.8.29:
|
||||
version "2.8.29"
|
||||
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.29.tgz#29c5733148057bb4e1f75df35b7a9cb72e6a59dd"
|
||||
dependencies:
|
||||
@@ -6099,6 +6105,13 @@ uglify-js@^2.6, uglify-js@^2.8.29:
|
||||
optionalDependencies:
|
||||
uglify-to-browserify "~1.0.0"
|
||||
|
||||
uglify-js@^3.1.4:
|
||||
version "3.7.3"
|
||||
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.7.3.tgz#f918fce9182f466d5140f24bb0ff35c2d32dcc6a"
|
||||
dependencies:
|
||||
commander "~2.20.3"
|
||||
source-map "~0.6.1"
|
||||
|
||||
uglify-to-browserify@~1.0.0:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7"
|
||||
|
||||
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.8 KiB |
@@ -1,5 +1,5 @@
|
||||
import React, {PureComponent} from 'react';
|
||||
import {flushSync, unstable_createRoot} from 'react-dom';
|
||||
import {flushSync, createRoot} from 'react-dom';
|
||||
import Scheduler from 'scheduler';
|
||||
import _ from 'lodash';
|
||||
import Charts from './Charts';
|
||||
@@ -107,9 +107,10 @@ class App extends PureComponent {
|
||||
this.debouncedHandleChange(value);
|
||||
break;
|
||||
case 'async':
|
||||
unstable_scheduleCallback(() => {
|
||||
// TODO: useTransition hook instead.
|
||||
setTimeout(() => {
|
||||
this.setState({value});
|
||||
});
|
||||
}, 0);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -146,5 +147,5 @@ class App extends PureComponent {
|
||||
}
|
||||
|
||||
const container = document.getElementById('root');
|
||||
const root = ReactDOM.unstable_createRoot(container);
|
||||
root.render(<App />, container);
|
||||
const root = createRoot(container);
|
||||
root.render(<App />);
|
||||
@@ -55,4 +55,4 @@ class IframeSubtree extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
export default (ReactDOM.createPortal ? IframePortal : IframeSubtree);
|
||||
export default ReactDOM.createPortal ? IframePortal : IframeSubtree;
|
||||
|
||||
@@ -13,7 +13,7 @@ export default class ButtonTestCases extends React.Component {
|
||||
<FixtureSet title="Buttons">
|
||||
<TestCase
|
||||
title="onClick with disabled buttons"
|
||||
description="The onClick event handler should not be invoked when clicking on a disabled buyaton">
|
||||
description="The onClick event handler should not be invoked when clicking on a disabled button">
|
||||
<TestCase.Steps>
|
||||
<li>Click on the disabled button</li>
|
||||
</TestCase.Steps>
|
||||
|
||||
@@ -33,7 +33,8 @@ export default function Home() {
|
||||
<td>
|
||||
<a href="https://www.mozilla.org/en-US/firefox/organizations/">
|
||||
ESR<sup>†</sup>
|
||||
</a>, Latest
|
||||
</a>
|
||||
, Latest
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
@@ -172,7 +172,7 @@ class SelectFixture extends React.Component {
|
||||
<option value="monkey">monkey</option>
|
||||
<option value="lion">lion</option>
|
||||
<option value="mongoose">mongoose</option>
|
||||
<option value="tiger">tiget</option>
|
||||
<option value="tiger">tiger</option>
|
||||
</select>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -213,8 +213,8 @@ class TextInputFixtures extends React.Component {
|
||||
|
||||
<Fixture>
|
||||
<SuspendyTree>
|
||||
<img src="https://upload.wikimedia.org/wikipedia/commons/e/ee/Atom_%282%29.png" />React
|
||||
is cool
|
||||
<img src="https://upload.wikimedia.org/wikipedia/commons/e/ee/Atom_%282%29.png" />
|
||||
React is cool
|
||||
</SuspendyTree>
|
||||
</Fixture>
|
||||
</TestCase>
|
||||
@@ -308,7 +308,9 @@ class TextInputFixtures extends React.Component {
|
||||
<div style={{height: 200, overflow: 'scroll'}}>
|
||||
{Array(20)
|
||||
.fill()
|
||||
.map((_, i) => <h2 key={i}>{i + 1}</h2>)}
|
||||
.map((_, i) => (
|
||||
<h2 key={i}>{i + 1}</h2>
|
||||
))}
|
||||
</div>
|
||||
</SuspendyTree>
|
||||
</Fixture>
|
||||
|
||||
@@ -205,9 +205,7 @@ const createMatcherFor = consoleMethod =>
|
||||
if (withoutStack !== warningsWithoutComponentStack.length) {
|
||||
return {
|
||||
message: () =>
|
||||
`Expected ${withoutStack} warnings without a component stack but received ${
|
||||
warningsWithoutComponentStack.length
|
||||
}:\n` +
|
||||
`Expected ${withoutStack} warnings without a component stack but received ${warningsWithoutComponentStack.length}:\n` +
|
||||
warningsWithoutComponentStack.map(warning =>
|
||||
this.utils.printReceived(warning)
|
||||
),
|
||||
|
||||
@@ -338,26 +338,16 @@ async-each@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d"
|
||||
|
||||
async@^1.4.0, async@^1.5.2:
|
||||
async@^1.5.2:
|
||||
version "1.5.2"
|
||||
resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
|
||||
|
||||
async@^2.1.2, async@^2.4.1:
|
||||
async@^2.1.2, async@^2.1.4, async@^2.4.1:
|
||||
version "2.5.0"
|
||||
resolved "https://registry.yarnpkg.com/async/-/async-2.5.0.tgz#843190fd6b7357a0b9e1c956edddd5ec8462b54d"
|
||||
dependencies:
|
||||
lodash "^4.14.0"
|
||||
|
||||
async@^2.1.4:
|
||||
version "2.1.4"
|
||||
resolved "https://registry.yarnpkg.com/async/-/async-2.1.4.tgz#2d2160c7788032e4dd6cbe2502f1f9a2c8f6cde4"
|
||||
dependencies:
|
||||
lodash "^4.14.0"
|
||||
|
||||
async@~0.2.6:
|
||||
version "0.2.10"
|
||||
resolved "https://registry.yarnpkg.com/async/-/async-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1"
|
||||
|
||||
asynckit@^0.4.0:
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
|
||||
@@ -1682,6 +1672,11 @@ commander@^2.9.0:
|
||||
dependencies:
|
||||
graceful-readlink ">= 1.0.0"
|
||||
|
||||
commander@~2.20.3:
|
||||
version "2.20.3"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
|
||||
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
|
||||
|
||||
commondir@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
|
||||
@@ -2723,7 +2718,7 @@ faye-websocket@~0.11.0:
|
||||
|
||||
fb-watchman@^1.8.0:
|
||||
version "1.9.0"
|
||||
resolved "http://registry.npmjs.org/fb-watchman/-/fb-watchman-1.9.0.tgz#6f268f1f347a6b3c875d1e89da7e1ed79adfc0ec"
|
||||
resolved "https://registry.npmjs.org/fb-watchman/-/fb-watchman-1.9.0.tgz#6f268f1f347a6b3c875d1e89da7e1ed79adfc0ec"
|
||||
dependencies:
|
||||
bser "^1.0.2"
|
||||
|
||||
@@ -3141,14 +3136,15 @@ handle-thing@^1.2.5:
|
||||
resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-1.2.5.tgz#fd7aad726bf1a5fd16dfc29b2f7a6601d27139c4"
|
||||
|
||||
handlebars@^4.0.3:
|
||||
version "4.0.6"
|
||||
resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.6.tgz#2ce4484850537f9c97a8026d5399b935c4ed4ed7"
|
||||
version "4.5.3"
|
||||
resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.5.3.tgz#5cf75bd8714f7605713511a56be7c349becb0482"
|
||||
integrity sha512-3yPecJoJHK/4c6aZhSvxOyG4vJKDshV36VHp0iVCDVh7o9w2vwi3NSnL2MMPj3YdduqaBcu7cGbggJQM0br9xA==
|
||||
dependencies:
|
||||
async "^1.4.0"
|
||||
neo-async "^2.6.0"
|
||||
optimist "^0.6.1"
|
||||
source-map "^0.4.4"
|
||||
source-map "^0.6.1"
|
||||
optionalDependencies:
|
||||
uglify-js "^2.6"
|
||||
uglify-js "^3.1.4"
|
||||
|
||||
har-schema@^1.0.5:
|
||||
version "1.0.5"
|
||||
@@ -3597,7 +3593,7 @@ is-plain-obj@^1.0.0:
|
||||
|
||||
is-posix-bracket@^0.1.0:
|
||||
version "0.1.1"
|
||||
resolved "http://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz#3334dc79774368e92f016e6fbc0a88f5cd6e6bc4"
|
||||
resolved "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz#3334dc79774368e92f016e6fbc0a88f5cd6e6bc4"
|
||||
|
||||
is-primitive@^2.0.0:
|
||||
version "2.0.0"
|
||||
@@ -4461,7 +4457,7 @@ minimatch@^3.0.4:
|
||||
dependencies:
|
||||
brace-expansion "^1.1.7"
|
||||
|
||||
minimist@0.0.8, minimist@~0.0.1:
|
||||
minimist@0.0.8:
|
||||
version "0.0.8"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
|
||||
|
||||
@@ -4469,6 +4465,11 @@ minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
|
||||
|
||||
minimist@~0.0.1:
|
||||
version "0.0.10"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf"
|
||||
integrity sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=
|
||||
|
||||
mkdirp@0.5.1, mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1:
|
||||
version "0.5.1"
|
||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
|
||||
@@ -4520,6 +4521,11 @@ negotiator@0.6.1:
|
||||
version "0.6.1"
|
||||
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9"
|
||||
|
||||
neo-async@^2.6.0:
|
||||
version "2.6.1"
|
||||
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c"
|
||||
integrity sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==
|
||||
|
||||
no-case@^2.2.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/no-case/-/no-case-2.3.0.tgz#ca2825ccb76b18e6f79d573dcfbf1eace33dd164"
|
||||
@@ -4775,6 +4781,7 @@ opn@5.1.0:
|
||||
optimist@^0.6.1:
|
||||
version "0.6.1"
|
||||
resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686"
|
||||
integrity sha1-2j6nRob6IaGaERwybpDrFaAZZoY=
|
||||
dependencies:
|
||||
minimist "~0.0.1"
|
||||
wordwrap "~0.0.2"
|
||||
@@ -6146,11 +6153,10 @@ source-map@0.5.6, source-map@0.5.x, source-map@^0.5.0, source-map@^0.5.3, source
|
||||
version "0.5.6"
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412"
|
||||
|
||||
source-map@^0.4.4:
|
||||
version "0.4.4"
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b"
|
||||
dependencies:
|
||||
amdefine ">=0.0.4"
|
||||
source-map@^0.6.1, source-map@~0.6.1:
|
||||
version "0.6.1"
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
|
||||
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
|
||||
|
||||
source-map@~0.2.0:
|
||||
version "0.2.0"
|
||||
@@ -6580,15 +6586,6 @@ uglify-js@3.0.x, uglify-js@^3.0.13:
|
||||
commander "~2.11.0"
|
||||
source-map "~0.5.1"
|
||||
|
||||
uglify-js@^2.6:
|
||||
version "2.7.5"
|
||||
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.7.5.tgz#4612c0c7baaee2ba7c487de4904ae122079f2ca8"
|
||||
dependencies:
|
||||
async "~0.2.6"
|
||||
source-map "~0.5.1"
|
||||
uglify-to-browserify "~1.0.0"
|
||||
yargs "~3.10.0"
|
||||
|
||||
uglify-js@^2.8.29:
|
||||
version "2.8.29"
|
||||
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.29.tgz#29c5733148057bb4e1f75df35b7a9cb72e6a59dd"
|
||||
@@ -6598,6 +6595,14 @@ uglify-js@^2.8.29:
|
||||
optionalDependencies:
|
||||
uglify-to-browserify "~1.0.0"
|
||||
|
||||
uglify-js@^3.1.4:
|
||||
version "3.7.3"
|
||||
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.7.3.tgz#f918fce9182f466d5140f24bb0ff35c2d32dcc6a"
|
||||
integrity sha512-7tINm46/3puUA4hCkKYo4Xdts+JDaVC9ZPRcG8Xw9R4nhO/gZgUM3TENq8IF4Vatk8qCig4MzP/c8G4u2BkVQg==
|
||||
dependencies:
|
||||
commander "~2.20.3"
|
||||
source-map "~0.6.1"
|
||||
|
||||
uglify-to-browserify@~1.0.0:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7"
|
||||
@@ -6941,6 +6946,7 @@ wordwrap@0.0.2:
|
||||
wordwrap@~0.0.2:
|
||||
version "0.0.3"
|
||||
resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107"
|
||||
integrity sha1-o9XabNXAvAAI03I0u68b7WMFkQc=
|
||||
|
||||
wordwrap@~1.0.0:
|
||||
version "1.0.0"
|
||||
|
||||
@@ -8,22 +8,19 @@ function Comment({comment, commentSource}) {
|
||||
const currentUserID = comment.viewer.id;
|
||||
const environment = RelayEnvironment.forUser(currentUserID);
|
||||
const commentID = nullthrows(comment.id);
|
||||
useEffect(
|
||||
() => {
|
||||
const subscription = SubscriptionCounter.subscribeOnce(
|
||||
`StoreSubscription_${commentID}`,
|
||||
() =>
|
||||
StoreSubscription.subscribe(
|
||||
environment,
|
||||
{
|
||||
comment_id: commentID,
|
||||
},
|
||||
currentUserID,
|
||||
commentSource
|
||||
)
|
||||
);
|
||||
return () => subscription.dispose();
|
||||
},
|
||||
[commentID, commentSource, currentUserID, environment]
|
||||
);
|
||||
useEffect(() => {
|
||||
const subscription = SubscriptionCounter.subscribeOnce(
|
||||
`StoreSubscription_${commentID}`,
|
||||
() =>
|
||||
StoreSubscription.subscribe(
|
||||
environment,
|
||||
{
|
||||
comment_id: commentID,
|
||||
},
|
||||
currentUserID,
|
||||
commentSource
|
||||
)
|
||||
);
|
||||
return () => subscription.dispose();
|
||||
}, [commentID, commentSource, currentUserID, environment]);
|
||||
}
|
||||
|
||||
@@ -83,10 +83,6 @@ alphanum-sort@^1.0.1, alphanum-sort@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3"
|
||||
|
||||
amdefine@>=0.0.4:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5"
|
||||
|
||||
ansi-align@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-1.1.0.tgz#2f0c1658829739add5ebb15e6b0c6e3423f016ba"
|
||||
@@ -260,7 +256,7 @@ async-each@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d"
|
||||
|
||||
async@^1.4.0, async@^1.5.2:
|
||||
async@^1.5.2:
|
||||
version "1.5.2"
|
||||
resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
|
||||
|
||||
@@ -1419,6 +1415,10 @@ commander@2.11.x, commander@~2.11.0:
|
||||
version "2.11.0"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.11.0.tgz#157152fd1e7a6c8d98a5b715cf376df928004563"
|
||||
|
||||
commander@~2.20.3:
|
||||
version "2.20.3"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
|
||||
|
||||
commondir@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
|
||||
@@ -2818,14 +2818,14 @@ handle-thing@^1.2.5:
|
||||
resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-1.2.5.tgz#fd7aad726bf1a5fd16dfc29b2f7a6601d27139c4"
|
||||
|
||||
handlebars@^4.0.3:
|
||||
version "4.0.11"
|
||||
resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.11.tgz#630a35dfe0294bc281edae6ffc5d329fc7982dcc"
|
||||
version "4.5.3"
|
||||
resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.5.3.tgz#5cf75bd8714f7605713511a56be7c349becb0482"
|
||||
dependencies:
|
||||
async "^1.4.0"
|
||||
neo-async "^2.6.0"
|
||||
optimist "^0.6.1"
|
||||
source-map "^0.4.4"
|
||||
source-map "^0.6.1"
|
||||
optionalDependencies:
|
||||
uglify-js "^2.6"
|
||||
uglify-js "^3.1.4"
|
||||
|
||||
har-schema@^1.0.5:
|
||||
version "1.0.5"
|
||||
@@ -4179,6 +4179,10 @@ negotiator@0.6.1:
|
||||
version "0.6.1"
|
||||
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9"
|
||||
|
||||
neo-async@^2.6.0:
|
||||
version "2.6.1"
|
||||
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c"
|
||||
|
||||
no-case@^2.2.0:
|
||||
version "2.3.2"
|
||||
resolved "https://registry.yarnpkg.com/no-case/-/no-case-2.3.2.tgz#60b813396be39b3f1288a4c1ed5d1e7d28b464ac"
|
||||
@@ -5723,12 +5727,6 @@ source-map@0.5.x, source-map@^0.5.3, source-map@^0.5.6, source-map@~0.5.1, sourc
|
||||
version "0.5.7"
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
|
||||
|
||||
source-map@^0.4.4:
|
||||
version "0.4.4"
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b"
|
||||
dependencies:
|
||||
amdefine ">=0.0.4"
|
||||
|
||||
source-map@^0.6.1, source-map@~0.6.1:
|
||||
version "0.6.1"
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
|
||||
@@ -6119,7 +6117,7 @@ uglify-js@3.1.x, uglify-js@^3.0.13:
|
||||
commander "~2.11.0"
|
||||
source-map "~0.6.1"
|
||||
|
||||
uglify-js@^2.6, uglify-js@^2.8.29:
|
||||
uglify-js@^2.8.29:
|
||||
version "2.8.29"
|
||||
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.29.tgz#29c5733148057bb4e1f75df35b7a9cb72e6a59dd"
|
||||
dependencies:
|
||||
@@ -6128,6 +6126,13 @@ uglify-js@^2.6, uglify-js@^2.8.29:
|
||||
optionalDependencies:
|
||||
uglify-to-browserify "~1.0.0"
|
||||
|
||||
uglify-js@^3.1.4:
|
||||
version "3.7.3"
|
||||
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.7.3.tgz#f918fce9182f466d5140f24bb0ff35c2d32dcc6a"
|
||||
dependencies:
|
||||
commander "~2.20.3"
|
||||
source-map "~0.6.1"
|
||||
|
||||
uglify-to-browserify@~1.0.0:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7"
|
||||
|
||||
@@ -192,8 +192,7 @@ class App extends Component {
|
||||
}
|
||||
/>
|
||||
<p>
|
||||
Step {currentStep}
|
||||
: {friendlyAction} (
|
||||
Step {currentStep}: {friendlyAction} (
|
||||
<a style={{color: 'gray'}} onClick={this.handleEdit} href="#">
|
||||
Edit
|
||||
</a>
|
||||
|
||||
@@ -330,83 +330,76 @@ export default function Fibers({fibers, show, graphSettings, ...rest}) {
|
||||
]}
|
||||
</div>
|
||||
</Vertex>,
|
||||
fiber.child &&
|
||||
show.child && (
|
||||
<Edge
|
||||
source={fiber.id}
|
||||
target={fiber.child}
|
||||
kind="child"
|
||||
weight={1000}
|
||||
key={`${fiber.id}-${fiber.child}-child`}>
|
||||
child
|
||||
</Edge>
|
||||
),
|
||||
fiber.sibling &&
|
||||
show.sibling && (
|
||||
<Edge
|
||||
source={fiber.id}
|
||||
target={fiber.sibling}
|
||||
kind="sibling"
|
||||
weight={2000}
|
||||
key={`${fiber.id}-${fiber.sibling}-sibling`}>
|
||||
sibling
|
||||
</Edge>
|
||||
),
|
||||
fiber.return &&
|
||||
show.return && (
|
||||
<Edge
|
||||
source={fiber.id}
|
||||
target={fiber.return}
|
||||
kind="return"
|
||||
weight={1000}
|
||||
key={`${fiber.id}-${fiber.return}-return`}>
|
||||
return
|
||||
</Edge>
|
||||
),
|
||||
fiber.nextEffect &&
|
||||
show.fx && (
|
||||
<Edge
|
||||
source={fiber.id}
|
||||
target={fiber.nextEffect}
|
||||
kind="fx"
|
||||
weight={100}
|
||||
key={`${fiber.id}-${fiber.nextEffect}-nextEffect`}>
|
||||
nextFx
|
||||
</Edge>
|
||||
),
|
||||
fiber.firstEffect &&
|
||||
show.fx && (
|
||||
<Edge
|
||||
source={fiber.id}
|
||||
target={fiber.firstEffect}
|
||||
kind="fx"
|
||||
weight={100}
|
||||
key={`${fiber.id}-${fiber.firstEffect}-firstEffect`}>
|
||||
firstFx
|
||||
</Edge>
|
||||
),
|
||||
fiber.lastEffect &&
|
||||
show.fx && (
|
||||
<Edge
|
||||
source={fiber.id}
|
||||
target={fiber.lastEffect}
|
||||
kind="fx"
|
||||
weight={100}
|
||||
key={`${fiber.id}-${fiber.lastEffect}-lastEffect`}>
|
||||
lastFx
|
||||
</Edge>
|
||||
),
|
||||
fiber.alternate &&
|
||||
show.alt && (
|
||||
<Edge
|
||||
source={fiber.id}
|
||||
target={fiber.alternate}
|
||||
kind="alt"
|
||||
weight={10}
|
||||
key={`${fiber.id}-${fiber.alternate}-alt`}>
|
||||
alt
|
||||
</Edge>
|
||||
),
|
||||
fiber.child && show.child && (
|
||||
<Edge
|
||||
source={fiber.id}
|
||||
target={fiber.child}
|
||||
kind="child"
|
||||
weight={1000}
|
||||
key={`${fiber.id}-${fiber.child}-child`}>
|
||||
child
|
||||
</Edge>
|
||||
),
|
||||
fiber.sibling && show.sibling && (
|
||||
<Edge
|
||||
source={fiber.id}
|
||||
target={fiber.sibling}
|
||||
kind="sibling"
|
||||
weight={2000}
|
||||
key={`${fiber.id}-${fiber.sibling}-sibling`}>
|
||||
sibling
|
||||
</Edge>
|
||||
),
|
||||
fiber.return && show.return && (
|
||||
<Edge
|
||||
source={fiber.id}
|
||||
target={fiber.return}
|
||||
kind="return"
|
||||
weight={1000}
|
||||
key={`${fiber.id}-${fiber.return}-return`}>
|
||||
return
|
||||
</Edge>
|
||||
),
|
||||
fiber.nextEffect && show.fx && (
|
||||
<Edge
|
||||
source={fiber.id}
|
||||
target={fiber.nextEffect}
|
||||
kind="fx"
|
||||
weight={100}
|
||||
key={`${fiber.id}-${fiber.nextEffect}-nextEffect`}>
|
||||
nextFx
|
||||
</Edge>
|
||||
),
|
||||
fiber.firstEffect && show.fx && (
|
||||
<Edge
|
||||
source={fiber.id}
|
||||
target={fiber.firstEffect}
|
||||
kind="fx"
|
||||
weight={100}
|
||||
key={`${fiber.id}-${fiber.firstEffect}-firstEffect`}>
|
||||
firstFx
|
||||
</Edge>
|
||||
),
|
||||
fiber.lastEffect && show.fx && (
|
||||
<Edge
|
||||
source={fiber.id}
|
||||
target={fiber.lastEffect}
|
||||
kind="fx"
|
||||
weight={100}
|
||||
key={`${fiber.id}-${fiber.lastEffect}-lastEffect`}>
|
||||
lastFx
|
||||
</Edge>
|
||||
),
|
||||
fiber.alternate && show.alt && (
|
||||
<Edge
|
||||
source={fiber.id}
|
||||
target={fiber.alternate}
|
||||
kind="alt"
|
||||
weight={10}
|
||||
key={`${fiber.id}-${fiber.alternate}-alt`}>
|
||||
alt
|
||||
</Edge>
|
||||
),
|
||||
])}
|
||||
</Graph>
|
||||
</div>
|
||||
|
||||
@@ -194,7 +194,7 @@ async@^0.9.0:
|
||||
version "0.9.2"
|
||||
resolved "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d"
|
||||
|
||||
async@^1.3.0, async@^1.4.0, async@^1.4.2, async@^1.5.0:
|
||||
async@^1.3.0, async@^1.4.2, async@^1.5.0:
|
||||
version "1.5.2"
|
||||
resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
|
||||
|
||||
@@ -1236,6 +1236,10 @@ commander@2.9.x, commander@^2.9.0:
|
||||
dependencies:
|
||||
graceful-readlink ">= 1.0.0"
|
||||
|
||||
commander@~2.20.3:
|
||||
version "2.20.3"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
|
||||
|
||||
commondir@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
|
||||
@@ -2302,14 +2306,14 @@ gzip-size@3.0.0:
|
||||
duplexer "^0.1.1"
|
||||
|
||||
handlebars@^4.0.3:
|
||||
version "4.0.6"
|
||||
resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.6.tgz#2ce4484850537f9c97a8026d5399b935c4ed4ed7"
|
||||
version "4.5.3"
|
||||
resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.5.3.tgz#5cf75bd8714f7605713511a56be7c349becb0482"
|
||||
dependencies:
|
||||
async "^1.4.0"
|
||||
neo-async "^2.6.0"
|
||||
optimist "^0.6.1"
|
||||
source-map "^0.4.4"
|
||||
source-map "^0.6.1"
|
||||
optionalDependencies:
|
||||
uglify-js "^2.6"
|
||||
uglify-js "^3.1.4"
|
||||
|
||||
har-validator@~2.0.6:
|
||||
version "2.0.6"
|
||||
@@ -3369,7 +3373,7 @@ minimatch@3.0.3, minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.3:
|
||||
dependencies:
|
||||
brace-expansion "^1.0.0"
|
||||
|
||||
minimist@0.0.8, minimist@~0.0.1:
|
||||
minimist@0.0.8:
|
||||
version "0.0.8"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
|
||||
|
||||
@@ -3377,6 +3381,10 @@ minimist@^1.1.1, minimist@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
|
||||
|
||||
minimist@~0.0.1:
|
||||
version "0.0.10"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf"
|
||||
|
||||
"mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1:
|
||||
version "0.5.1"
|
||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
|
||||
@@ -3413,6 +3421,10 @@ negotiator@0.6.1:
|
||||
version "0.6.1"
|
||||
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9"
|
||||
|
||||
neo-async@^2.6.0:
|
||||
version "2.6.1"
|
||||
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c"
|
||||
|
||||
no-case@^2.2.0:
|
||||
version "2.3.1"
|
||||
resolved "https://registry.yarnpkg.com/no-case/-/no-case-2.3.1.tgz#7aeba1c73a52184265554b7dc03baf720df80081"
|
||||
@@ -4650,7 +4662,7 @@ source-map-support@^0.4.2:
|
||||
dependencies:
|
||||
source-map "^0.5.3"
|
||||
|
||||
source-map@0.4.x, source-map@^0.4.4, source-map@~0.4.1:
|
||||
source-map@0.4.x, source-map@~0.4.1:
|
||||
version "0.4.4"
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b"
|
||||
dependencies:
|
||||
@@ -4660,6 +4672,10 @@ source-map@^0.5.0, source-map@^0.5.3, source-map@^0.5.6, source-map@~0.5.1, sour
|
||||
version "0.5.6"
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412"
|
||||
|
||||
source-map@^0.6.1, source-map@~0.6.1:
|
||||
version "0.6.1"
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
|
||||
|
||||
source-map@~0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.2.0.tgz#dab73fbcfc2ba819b4de03bd6f6eaa48164b3f9d"
|
||||
@@ -4952,7 +4968,7 @@ ua-parser-js@^0.7.9:
|
||||
version "0.7.12"
|
||||
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.12.tgz#04c81a99bdd5dc52263ea29d24c6bf8d4818a4bb"
|
||||
|
||||
uglify-js@2.7.x, uglify-js@^2.6, uglify-js@~2.7.3:
|
||||
uglify-js@2.7.x, uglify-js@~2.7.3:
|
||||
version "2.7.5"
|
||||
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.7.5.tgz#4612c0c7baaee2ba7c487de4904ae122079f2ca8"
|
||||
dependencies:
|
||||
@@ -4961,6 +4977,13 @@ uglify-js@2.7.x, uglify-js@^2.6, uglify-js@~2.7.3:
|
||||
uglify-to-browserify "~1.0.0"
|
||||
yargs "~3.10.0"
|
||||
|
||||
uglify-js@^3.1.4:
|
||||
version "3.7.3"
|
||||
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.7.3.tgz#f918fce9182f466d5140f24bb0ff35c2d32dcc6a"
|
||||
dependencies:
|
||||
commander "~2.20.3"
|
||||
source-map "~0.6.1"
|
||||
|
||||
uglify-to-browserify@~1.0.0:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7"
|
||||
|
||||
108
fixtures/flight-browser/index.html
Normal file
108
fixtures/flight-browser/index.html
Normal file
@@ -0,0 +1,108 @@
|
||||
<!DOCTYPE html>
|
||||
<html style="width: 100%; height: 100%; overflow: hidden">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Flight Example</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Flight Example</h1>
|
||||
<div id="container">
|
||||
<p>
|
||||
To install React, follow the instructions on
|
||||
<a href="https://github.com/facebook/react/">GitHub</a>.
|
||||
</p>
|
||||
<p>
|
||||
If you can see this, React is <strong>not</strong> working right.
|
||||
If you checked out the source from GitHub make sure to run <code>npm run build</code>.
|
||||
</p>
|
||||
</div>
|
||||
<script src="../../build/dist/react.development.js"></script>
|
||||
<script src="../../build/dist/react-dom.development.js"></script>
|
||||
<script src="../../build/dist/react-dom-server.browser.development.js"></script>
|
||||
<script src="../../build/dist/react-flight-dom-webpack-server.browser.development.js"></script>
|
||||
<script src="../../build/dist/react-flight-dom-webpack.development.js"></script>
|
||||
<script src="https://unpkg.com/babel-standalone@6/babel.js"></script>
|
||||
<script type="text/babel">
|
||||
let Suspense = React.Suspense;
|
||||
|
||||
function Text({children}) {
|
||||
return <span>{children}</span>;
|
||||
}
|
||||
function HTML() {
|
||||
return (
|
||||
<div>
|
||||
<Text>hello</Text>
|
||||
<Text>world</Text>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
let resolved = false;
|
||||
let promise = new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
resolved = true;
|
||||
resolve();
|
||||
}, 100);
|
||||
});
|
||||
function read() {
|
||||
if (!resolved) {
|
||||
throw promise;
|
||||
}
|
||||
}
|
||||
|
||||
function Title() {
|
||||
read();
|
||||
return 'Title';
|
||||
}
|
||||
|
||||
let model = {
|
||||
title: <Title />,
|
||||
content: {
|
||||
__html: <HTML />,
|
||||
}
|
||||
};
|
||||
|
||||
let stream = ReactFlightDOMServer.renderToReadableStream(model);
|
||||
let response = new Response(stream, {
|
||||
headers: {'Content-Type': 'text/html'},
|
||||
});
|
||||
display(response);
|
||||
|
||||
async function display(responseToDisplay) {
|
||||
let blob = await responseToDisplay.blob();
|
||||
let url = URL.createObjectURL(blob);
|
||||
|
||||
let data = ReactFlightDOMClient.readFromFetch(
|
||||
fetch(url)
|
||||
);
|
||||
// The client also supports XHR streaming.
|
||||
// var xhr = new XMLHttpRequest();
|
||||
// xhr.open('GET', url);
|
||||
// let data = ReactFlightDOMClient.readFromXHR(xhr);
|
||||
// xhr.send();
|
||||
|
||||
renderResult(data);
|
||||
}
|
||||
|
||||
function Shell({ data }) {
|
||||
let model = data.model;
|
||||
return <div>
|
||||
<Suspense fallback="...">
|
||||
<h1>{model.title}</h1>
|
||||
</Suspense>
|
||||
<div dangerouslySetInnerHTML={model.content} />
|
||||
</div>;
|
||||
}
|
||||
|
||||
function renderResult(data) {
|
||||
let container = document.getElementById('container');
|
||||
ReactDOM.render(
|
||||
<Suspense fallback="Loading...">
|
||||
<Shell data={data} />
|
||||
</Suspense>,
|
||||
container
|
||||
);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
1
fixtures/flight/.env
Normal file
1
fixtures/flight/.env
Normal file
@@ -0,0 +1 @@
|
||||
SKIP_PREFLIGHT_CHECK=true
|
||||
23
fixtures/flight/.gitignore
vendored
Normal file
23
fixtures/flight/.gitignore
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
37
fixtures/flight/package.json
Normal file
37
fixtures/flight/package.json
Normal file
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"name": "flight",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@babel/register": "^7.7.0",
|
||||
"concurrently": "^5.0.0",
|
||||
"express": "^4.17.1",
|
||||
"react-scripts": "3.2.0"
|
||||
},
|
||||
"scripts": {
|
||||
"prestart": "cp -r ../../build/node_modules/* ./node_modules/",
|
||||
"prebuild": "cp -r ../../build/node_modules/* ./node_modules/",
|
||||
"start": "concurrently \"npm run start:server\" \"npm run start:client\"",
|
||||
"start:client": "react-scripts start",
|
||||
"start:server": "NODE_ENV=development node server",
|
||||
"start:prod": "react-scripts build && NODE_ENV=production node server",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test --env=jsdom",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": "react-app"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
}
|
||||
}
|
||||
11
fixtures/flight/public/index.html
Normal file
11
fixtures/flight/public/index.html
Normal file
@@ -0,0 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Flight</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
28
fixtures/flight/server/handler.js
Normal file
28
fixtures/flight/server/handler.js
Normal file
@@ -0,0 +1,28 @@
|
||||
'use strict';
|
||||
|
||||
const ReactFlightDOMServer = require('react-flight-dom-webpack/server');
|
||||
const React = require('react');
|
||||
const Stream = require('stream');
|
||||
|
||||
function Text({children}) {
|
||||
return <span>{children}</span>;
|
||||
}
|
||||
|
||||
function HTML() {
|
||||
return (
|
||||
<div>
|
||||
<Text>Hello</Text>
|
||||
<Text>world</Text>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = function(req, res) {
|
||||
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||
let model = {
|
||||
content: {
|
||||
__html: <HTML />,
|
||||
},
|
||||
};
|
||||
ReactFlightDOMServer.pipeToNodeWritable(model, res);
|
||||
};
|
||||
46
fixtures/flight/server/index.js
Normal file
46
fixtures/flight/server/index.js
Normal file
@@ -0,0 +1,46 @@
|
||||
'use strict';
|
||||
|
||||
const babelRegister = require('@babel/register');
|
||||
|
||||
babelRegister({
|
||||
ignore: [/\/(build|node_modules)\//],
|
||||
presets: ['react-app'],
|
||||
});
|
||||
|
||||
const express = require('express');
|
||||
const app = express();
|
||||
|
||||
// Application
|
||||
app.get('/', function(req, res) {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
for (var key in require.cache) {
|
||||
delete require.cache[key];
|
||||
}
|
||||
}
|
||||
require('./handler')(req, res);
|
||||
});
|
||||
|
||||
app.listen(3001, () => {
|
||||
console.log('Flight Server listening on port 3001...');
|
||||
});
|
||||
|
||||
app.on('error', function(error) {
|
||||
if (error.syscall !== 'listen') {
|
||||
throw error;
|
||||
}
|
||||
|
||||
var bind = typeof port === 'string' ? 'Pipe ' + port : 'Port ' + port;
|
||||
|
||||
switch (error.code) {
|
||||
case 'EACCES':
|
||||
console.error(bind + ' requires elevated privileges');
|
||||
process.exit(1);
|
||||
break;
|
||||
case 'EADDRINUSE':
|
||||
console.error(bind + ' is already in use');
|
||||
process.exit(1);
|
||||
break;
|
||||
default:
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
15
fixtures/flight/src/App.js
Normal file
15
fixtures/flight/src/App.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import React, {Suspense} from 'react';
|
||||
|
||||
function Content({data}) {
|
||||
return <p dangerouslySetInnerHTML={data.model.content} />;
|
||||
}
|
||||
|
||||
function App({data}) {
|
||||
return (
|
||||
<Suspense fallback={<h1>Loading...</h1>}>
|
||||
<Content data={data} />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
7
fixtures/flight/src/index.js
Normal file
7
fixtures/flight/src/index.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import ReactFlightDOMClient from 'react-flight-dom-webpack';
|
||||
import App from './App';
|
||||
|
||||
let data = ReactFlightDOMClient.readFromFetch(fetch('http://localhost:3001'));
|
||||
ReactDOM.render(<App data={data} />, document.getElementById('root'));
|
||||
9863
fixtures/flight/yarn.lock
Normal file
9863
fixtures/flight/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import {unstable_createRoot} from 'react-dom';
|
||||
import {createRoot} from 'react-dom';
|
||||
|
||||
import App from './components/App';
|
||||
|
||||
let root = unstable_createRoot(document, {hydrate: true});
|
||||
let root = createRoot(document, {hydrate: true});
|
||||
root.render(<App assets={window.assetManifest} />);
|
||||
|
||||
@@ -2999,6 +2999,11 @@ js-tokens@^3.0.0:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.1.tgz#08e9f132484a2c45a30907e9dc4d5567b7f114d7"
|
||||
|
||||
"js-tokens@^3.0.0 || ^4.0.0":
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
|
||||
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
|
||||
|
||||
js-yaml@^3.4.3, js-yaml@^3.5.1, js-yaml@^3.7.0, js-yaml@~3.7.0:
|
||||
version "3.7.0"
|
||||
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.7.0.tgz#5c967ddd837a9bfdca5f2de84253abe8a1c03b80"
|
||||
@@ -3248,6 +3253,13 @@ loose-envify@^1.0.0:
|
||||
dependencies:
|
||||
js-tokens "^3.0.0"
|
||||
|
||||
loose-envify@^1.1.0, loose-envify@^1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
|
||||
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
|
||||
dependencies:
|
||||
js-tokens "^3.0.0 || ^4.0.0"
|
||||
|
||||
lower-case@^1.1.1:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-1.1.4.tgz#9a2cabd1b9e8e0ae993a4bf7d5875c39c42e8eac"
|
||||
@@ -3558,9 +3570,10 @@ oauth-sign@~0.8.1:
|
||||
version "0.8.2"
|
||||
resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43"
|
||||
|
||||
object-assign@4.1.1, object-assign@^4.0.1, object-assign@^4.1.0:
|
||||
object-assign@4.1.1, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
|
||||
integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
|
||||
|
||||
object.omit@^2.0.0:
|
||||
version "2.0.1"
|
||||
@@ -4071,6 +4084,15 @@ promise@7.1.1:
|
||||
dependencies:
|
||||
asap "~2.0.3"
|
||||
|
||||
prop-types@^15.6.2:
|
||||
version "15.7.2"
|
||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
|
||||
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
|
||||
dependencies:
|
||||
loose-envify "^1.4.0"
|
||||
object-assign "^4.1.1"
|
||||
react-is "^16.8.1"
|
||||
|
||||
proxy-addr@~1.1.3:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-1.1.4.tgz#27e545f6960a44a627d9b44467e35c1b6b4ce2f3"
|
||||
@@ -4160,6 +4182,11 @@ react-dev-utils@^0.5.2:
|
||||
version "0.0.0"
|
||||
uid ""
|
||||
|
||||
react-is@^16.8.1:
|
||||
version "16.12.0"
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.12.0.tgz#2cc0fe0fba742d97fd527c42a13bec4eeb06241c"
|
||||
integrity sha512-rPCkf/mWBtKc97aLL9/txD8DZdemK0vkA3JMLShjlJB3Pj3s+lpf1KaBzMfQrAmhMQB0n1cU/SUGgKKBCe837Q==
|
||||
|
||||
react-scripts@0.9.5:
|
||||
version "0.9.5"
|
||||
resolved "https://registry.yarnpkg.com/react-scripts/-/react-scripts-0.9.5.tgz#e9f05c8427e27131662a9b9d7a9786d1ff16bb3f"
|
||||
@@ -4495,6 +4522,14 @@ sax@^1.2.1, sax@~1.2.1:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.2.tgz#fd8631a23bc7826bef5d871bdb87378c95647828"
|
||||
|
||||
scheduler@^0.18.0:
|
||||
version "0.18.0"
|
||||
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.18.0.tgz#5901ad6659bc1d8f3fdaf36eb7a67b0d6746b1c4"
|
||||
integrity sha512-agTSHR1Nbfi6ulI0kYNK0203joW2Y5W4po4l+v03tOoiJKpTBbxpNhWDvqc/4IcOw+KLmSiQLTasZ4cab2/UWQ==
|
||||
dependencies:
|
||||
loose-envify "^1.1.0"
|
||||
object-assign "^4.1.1"
|
||||
|
||||
"semver@2 || 3 || 4 || 5", semver@^5.1.0, semver@^5.3.0:
|
||||
version "5.3.0"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const {createElement, Component, Placeholder} = React;
|
||||
const {unstable_createRoot: createRoot} = ReactDOM;
|
||||
const {createElement, Component, Suspense} = React;
|
||||
const {createRoot} = ReactDOM;
|
||||
const {
|
||||
unstable_subscribe: subscribe,
|
||||
unstable_trace: trace,
|
||||
@@ -56,8 +56,8 @@ const read = key => {
|
||||
|
||||
const TestApp = () =>
|
||||
createElement(
|
||||
Placeholder,
|
||||
{delayMs: 100, fallback: createElement(PlaceholderText)},
|
||||
Suspense,
|
||||
{fallback: createElement(PlaceholderText)},
|
||||
createElement(SuspendingChild, {text: 'foo'}),
|
||||
createElement(SuspendingChild, {text: 'bar'}),
|
||||
createElement(SuspendingChild, {text: 'baz'})
|
||||
@@ -91,13 +91,11 @@ subscribe({
|
||||
const element = document.getElementById('root');
|
||||
trace('initial_render', performance.now(), () => {
|
||||
const root = createRoot(element);
|
||||
const batch = root.createBatch();
|
||||
log.app('batch.render()');
|
||||
batch.render(createElement(TestApp));
|
||||
batch.then(
|
||||
log.app('render()');
|
||||
root.render(
|
||||
createElement(TestApp),
|
||||
wrap(() => {
|
||||
log.app('batch.commit()');
|
||||
batch.commit();
|
||||
log.app('committed');
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
23
package.json
23
package.json
@@ -8,6 +8,7 @@
|
||||
"@babel/code-frame": "^7.0.0",
|
||||
"@babel/core": "^7.0.0",
|
||||
"@babel/helper-module-imports": "^7.0.0",
|
||||
"@babel/parser": "^7.0.0",
|
||||
"@babel/plugin-external-helpers": "^7.0.0",
|
||||
"@babel/plugin-proposal-class-properties": "^7.0.0",
|
||||
"@babel/plugin-proposal-object-rest-spread": "^7.0.0",
|
||||
@@ -34,9 +35,8 @@
|
||||
"@babel/traverse": "^7.0.0",
|
||||
"@mattiasbuelens/web-streams-polyfill": "0.1.0",
|
||||
"art": "^0.10.1",
|
||||
"babel-eslint": "^10.0.0",
|
||||
"babel-eslint": "^10.0.3",
|
||||
"babel-plugin-syntax-trailing-function-commas": "^6.5.0",
|
||||
"babylon": "6.18.0",
|
||||
"chalk": "^1.1.3",
|
||||
"cli-table": "^0.3.1",
|
||||
"coffee-script": "^1.8.0",
|
||||
@@ -46,8 +46,9 @@
|
||||
"cross-env": "^5.1.1",
|
||||
"danger": "^9.1.8",
|
||||
"error-stack-parser": "^2.0.2",
|
||||
"eslint": "^6.1.0",
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-config-fbjs": "^1.1.1",
|
||||
"eslint-config-prettier": "^6.9.0",
|
||||
"eslint-plugin-babel": "^5.3.0",
|
||||
"eslint-plugin-flowtype": "^2.25.0",
|
||||
"eslint-plugin-jest": "^22.15.0",
|
||||
@@ -56,7 +57,7 @@
|
||||
"eslint-plugin-react-internal": "link:./scripts/eslint-rules",
|
||||
"fbjs-scripts": "^0.8.3",
|
||||
"filesize": "^3.5.6",
|
||||
"flow-bin": "^0.72.0",
|
||||
"flow-bin": "^0.84.0",
|
||||
"glob": "^6.0.4",
|
||||
"glob-stream": "^6.1.0",
|
||||
"google-closure-compiler": "20190301.0.0",
|
||||
@@ -71,7 +72,7 @@
|
||||
"ncp": "^2.0.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"pacote": "^9.5.6",
|
||||
"prettier": "1.13.7",
|
||||
"prettier": "1.19.1",
|
||||
"prop-types": "^15.6.2",
|
||||
"random-seed": "^0.3.0",
|
||||
"react-lifecycles-compat": "^3.0.2",
|
||||
@@ -87,31 +88,33 @@
|
||||
"targz": "^1.0.1",
|
||||
"through2": "^2.0.0",
|
||||
"tmp": "~0.0.28",
|
||||
"typescript": "~1.8.10"
|
||||
"typescript": "^3.7.4",
|
||||
"webpack": "^4.41.2"
|
||||
},
|
||||
"devEngines": {
|
||||
"node": "8.x || 9.x || 10.x || 11.x || 12.x"
|
||||
"node": "8.x || 9.x || 10.x || 11.x || 12.x || 13.x"
|
||||
},
|
||||
"jest": {
|
||||
"testRegex": "/scripts/jest/dont-run-jest-directly\\.js$"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "node ./scripts/rollup/build.js",
|
||||
"build-for-devtools": "cross-env RELEASE_CHANNEL=experimental yarn build -- react/index,react-dom,react-is,react-debug-tools,scheduler,react-test-renderer --type=NODE",
|
||||
"linc": "node ./scripts/tasks/linc.js",
|
||||
"lint": "node ./scripts/tasks/eslint.js",
|
||||
"lint-build": "node ./scripts/rollup/validate/index.js",
|
||||
"extract-errors": "yarn build --type=dev --extract-errors",
|
||||
"postinstall": "node node_modules/fbjs-scripts/node/check-dev-engines.js package.json && node ./scripts/flow/createFlowConfigs.js && node ./scripts/yarn/downloadReactIsForPrettyFormat.js",
|
||||
"debug-test": "cross-env NODE_ENV=development node --inspect-brk node_modules/.bin/jest --config ./scripts/jest/config.source.js --runInBand",
|
||||
"debug-test": "cross-env NODE_ENV=development node --inspect-brk node_modules/jest/bin/jest.js --config ./scripts/jest/config.source.js --runInBand",
|
||||
"test": "cross-env NODE_ENV=development jest --config ./scripts/jest/config.source.js",
|
||||
"test-persistent": "cross-env NODE_ENV=development jest --config ./scripts/jest/config.source-persistent.js",
|
||||
"debug-test-persistent": "cross-env NODE_ENV=development node --inspect-brk node_modules/.bin/jest --config ./scripts/jest/config.source-persistent.js --runInBand",
|
||||
"debug-test-persistent": "cross-env NODE_ENV=development node --inspect-brk node_modules/jest/bin/jest.js --config ./scripts/jest/config.source-persistent.js --runInBand",
|
||||
"test-prod": "cross-env NODE_ENV=production jest --config ./scripts/jest/config.source.js",
|
||||
"test-prod-build": "yarn test-build-prod",
|
||||
"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",
|
||||
"test-build-devtools": "cross-env NODE_ENV=development jest --config ./scripts/jest/config.build-devtools.js",
|
||||
"debug-test-build-devtools": "cross-env NODE_ENV=development node --inspect-brk node_modules/.bin/jest --config ./scripts/jest/config.build-devtools.js",
|
||||
"debug-test-build-devtools": "cross-env NODE_ENV=development node --inspect-brk node_modules/jest/bin/jest.js --config ./scripts/jest/config.build-devtools.js",
|
||||
"test-dom-fixture": "cd fixtures/dom && yarn && yarn prestart && yarn test",
|
||||
"flow": "node ./scripts/tasks/flow.js",
|
||||
"flow-ci": "node ./scripts/tasks/flow-ci.js",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "create-subscription",
|
||||
"description": "utility for subscribing to external data sources inside React components",
|
||||
"version": "16.10.2",
|
||||
"version": "16.12.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/facebook/react.git",
|
||||
|
||||
@@ -118,7 +118,10 @@ describe('createSubscription', () => {
|
||||
const Subscription = createSubscription({
|
||||
getCurrentValue: source => undefined,
|
||||
subscribe: (source, callback) => {
|
||||
source.then(value => callback(value), value => callback(value));
|
||||
source.then(
|
||||
value => callback(value),
|
||||
value => callback(value),
|
||||
);
|
||||
// (Can't unsubscribe from a Promise)
|
||||
return () => {};
|
||||
},
|
||||
@@ -196,7 +199,10 @@ describe('createSubscription', () => {
|
||||
const Subscription = createSubscription({
|
||||
getCurrentValue: source => undefined,
|
||||
subscribe: (source, callback) => {
|
||||
source.then(value => callback(value), value => callback(value));
|
||||
source.then(
|
||||
value => callback(value),
|
||||
value => callback(value),
|
||||
);
|
||||
// (Can't unsubscribe from a Promise)
|
||||
return () => {};
|
||||
},
|
||||
@@ -452,7 +458,7 @@ describe('createSubscription', () => {
|
||||
},
|
||||
() => null,
|
||||
);
|
||||
}).toWarnDev('Subscription must specify a getCurrentValue function', {
|
||||
}).toErrorDev('Subscription must specify a getCurrentValue function', {
|
||||
withoutStack: true,
|
||||
});
|
||||
});
|
||||
@@ -465,7 +471,7 @@ describe('createSubscription', () => {
|
||||
},
|
||||
() => null,
|
||||
);
|
||||
}).toWarnDev('Subscription must specify a subscribe function', {
|
||||
}).toErrorDev('Subscription must specify a subscribe function', {
|
||||
withoutStack: true,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
|
||||
import React from 'react';
|
||||
import invariant from 'shared/invariant';
|
||||
import warningWithoutStack from 'shared/warningWithoutStack';
|
||||
|
||||
type Unsubscribe = () => void;
|
||||
|
||||
@@ -30,29 +29,29 @@ export function createSubscription<Property, Value>(
|
||||
callback: (value: Value | void) => void,
|
||||
) => Unsubscribe,
|
||||
|}>,
|
||||
): React$ComponentType<{
|
||||
): React$ComponentType<{|
|
||||
children: (value: Value | void) => React$Node,
|
||||
source: Property,
|
||||
}> {
|
||||
|}> {
|
||||
const {getCurrentValue, subscribe} = config;
|
||||
|
||||
warningWithoutStack(
|
||||
typeof getCurrentValue === 'function',
|
||||
'Subscription must specify a getCurrentValue function',
|
||||
);
|
||||
warningWithoutStack(
|
||||
typeof subscribe === 'function',
|
||||
'Subscription must specify a subscribe function',
|
||||
);
|
||||
if (__DEV__) {
|
||||
if (typeof getCurrentValue !== 'function') {
|
||||
console.error('Subscription must specify a getCurrentValue function');
|
||||
}
|
||||
if (typeof subscribe !== 'function') {
|
||||
console.error('Subscription must specify a subscribe function');
|
||||
}
|
||||
}
|
||||
|
||||
type Props = {
|
||||
type Props = {|
|
||||
children: (value: Value) => React$Element<any>,
|
||||
source: Property,
|
||||
};
|
||||
type State = {
|
||||
|};
|
||||
type State = {|
|
||||
source: Property,
|
||||
value: Value | void,
|
||||
};
|
||||
|};
|
||||
|
||||
// Reference: https://gist.github.com/bvaughn/d569177d70b50b58bff69c3c4a5353f3
|
||||
class Subscription extends React.Component<Props, State> {
|
||||
|
||||
98
packages/dom-event-testing-library/README.md
Normal file
98
packages/dom-event-testing-library/README.md
Normal file
@@ -0,0 +1,98 @@
|
||||
# `dom-event-testing-library`
|
||||
|
||||
A library for unit testing events via high-level interactions, e.g., `pointerdown`,
|
||||
that produce realistic and complete DOM event sequences.
|
||||
|
||||
There are number of challenges involved in unit testing modules that work with
|
||||
DOM events.
|
||||
|
||||
1. Gesture recognizers may need to support environments with and without support for
|
||||
the `PointerEvent` API.
|
||||
2. Gesture recognizers may need to support various user interaction modes including
|
||||
mouse, touch, and pen use.
|
||||
3. Gesture recognizers must account for the actual event sequences browsers produce
|
||||
(e.g., emulated touch and mouse events.)
|
||||
4. Gesture recognizers must work with "virtual" events produced by tools like
|
||||
screen-readers.
|
||||
|
||||
Writing unit tests to cover all these scenarios is tedious and error prone. This
|
||||
event testing library is designed to solve these issues by allowing developers to
|
||||
more easily dispatch events in unit tests, and to more reliably test pointer
|
||||
interactions using a high-level API based on `PointerEvent`. Here's a basic example:
|
||||
|
||||
```js
|
||||
import {
|
||||
describeWithPointerEvent,
|
||||
testWithPointerType,
|
||||
createEventTarget,
|
||||
setPointerEvent,
|
||||
resetActivePointers
|
||||
} from 'dom-event-testing-library';
|
||||
|
||||
describeWithPointerEvent('useTap', hasPointerEvent => {
|
||||
beforeEach(() => {
|
||||
// basic PointerEvent mock
|
||||
setPointerEvent(hasPointerEvent);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// clear active pointers between test runs
|
||||
resetActivePointers();
|
||||
});
|
||||
|
||||
// test all the pointer types supported by the environment
|
||||
testWithPointerType('pointer down', pointerType => {
|
||||
const ref = createRef(null);
|
||||
const onTapStart = jest.fn();
|
||||
render(() => {
|
||||
useTap(ref, { onTapStart });
|
||||
return <div ref={ref} />
|
||||
});
|
||||
|
||||
// create an event target
|
||||
const target = createEventTarget(ref.current);
|
||||
// dispatch high-level pointer event
|
||||
target.pointerdown({ pointerType });
|
||||
|
||||
expect(onTapStart).toBeCalled();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
This tests the interaction in multiple scenarios. In each case, a realistic DOM
|
||||
event sequence–with complete mock events–is produced. When running in a mock
|
||||
environment without the `PointerEvent` API, the test runs for both `mouse` and
|
||||
`touch` pointer types. When `touch` is the pointer type it produces emulated mouse
|
||||
events. When running in a mock environment with the `PointerEvent` API, the test
|
||||
runs for `mouse`, `touch`, and `pen` pointer types.
|
||||
|
||||
It's important to cover all these scenarios because it's very easy to introduce
|
||||
bugs – e.g., double calling of callbacks – if not accounting for emulated mouse
|
||||
events, differences in target capturing between `touch` and `mouse` pointers, and
|
||||
the different semantics of `button` across event APIs.
|
||||
|
||||
Default values are provided for the expected native events properties. They can
|
||||
also be customized as needed in a test.
|
||||
|
||||
```js
|
||||
target.pointerdown({
|
||||
button: 0,
|
||||
buttons: 1,
|
||||
pageX: 10,
|
||||
pageY: 10,
|
||||
pointerType,
|
||||
// NOTE: use x,y instead of clientX,clientY
|
||||
x: 10,
|
||||
y: 10
|
||||
});
|
||||
```
|
||||
|
||||
Tests that dispatch multiple pointer events will dispatch multi-touch native events
|
||||
on the target.
|
||||
|
||||
```js
|
||||
// first pointer is active
|
||||
target.pointerdown({pointerId: 1, pointerType});
|
||||
// second pointer is active
|
||||
target.pointerdown({pointerId: 2, pointerType});
|
||||
```
|
||||
@@ -0,0 +1,15 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`describeWithPointerEvent: MouseEvent/TouchEvent provides boolean to tests 1`] = `false`;
|
||||
|
||||
exports[`describeWithPointerEvent: MouseEvent/TouchEvent testWithPointerType: mouse 1`] = `"mouse"`;
|
||||
|
||||
exports[`describeWithPointerEvent: MouseEvent/TouchEvent testWithPointerType: touch 1`] = `"touch"`;
|
||||
|
||||
exports[`describeWithPointerEvent: PointerEvent provides boolean to tests 1`] = `true`;
|
||||
|
||||
exports[`describeWithPointerEvent: PointerEvent testWithPointerType: mouse 1`] = `"mouse"`;
|
||||
|
||||
exports[`describeWithPointerEvent: PointerEvent testWithPointerType: pen 1`] = `"pen"`;
|
||||
|
||||
exports[`describeWithPointerEvent: PointerEvent testWithPointerType: touch 1`] = `"touch"`;
|
||||
@@ -0,0 +1,361 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @emails react-core
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import {defaultBrowserChromeSize} from '../constants';
|
||||
|
||||
import {
|
||||
createEventTarget,
|
||||
describeWithPointerEvent,
|
||||
testWithPointerType,
|
||||
resetActivePointers,
|
||||
} from '../index';
|
||||
|
||||
/**
|
||||
* Unit test helpers
|
||||
*/
|
||||
describeWithPointerEvent('describeWithPointerEvent', pointerEvent => {
|
||||
test('provides boolean to tests', () => {
|
||||
expect(pointerEvent).toMatchSnapshot();
|
||||
});
|
||||
|
||||
testWithPointerType('testWithPointerType', pointerType => {
|
||||
expect(pointerType).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* createEventTarget
|
||||
*/
|
||||
describe('createEventTarget', () => {
|
||||
let node;
|
||||
beforeEach(() => {
|
||||
node = document.createElement('div');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
node = null;
|
||||
resetActivePointers();
|
||||
});
|
||||
|
||||
test('returns expected API', () => {
|
||||
const target = createEventTarget(node);
|
||||
expect(target.node).toEqual(node);
|
||||
expect(Object.keys(target)).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"node",
|
||||
"blur",
|
||||
"click",
|
||||
"focus",
|
||||
"keydown",
|
||||
"keyup",
|
||||
"scroll",
|
||||
"virtualclick",
|
||||
"contextmenu",
|
||||
"pointercancel",
|
||||
"pointerdown",
|
||||
"pointerhover",
|
||||
"pointermove",
|
||||
"pointerenter",
|
||||
"pointerexit",
|
||||
"pointerup",
|
||||
"tap",
|
||||
"setBoundingClientRect",
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
/**
|
||||
* Simple events
|
||||
*/
|
||||
|
||||
describe('.blur()', () => {
|
||||
test('default', () => {
|
||||
const target = createEventTarget(node);
|
||||
node.addEventListener('blur', e => {
|
||||
expect(e.relatedTarget).toMatchInlineSnapshot(`null`);
|
||||
});
|
||||
target.blur();
|
||||
});
|
||||
|
||||
test('custom payload', () => {
|
||||
const target = createEventTarget(node);
|
||||
node.addEventListener('blur', e => {
|
||||
expect(e.relatedTarget).toMatchInlineSnapshot(`null`);
|
||||
});
|
||||
target.blur();
|
||||
});
|
||||
});
|
||||
|
||||
describe('.click()', () => {
|
||||
test('default', () => {
|
||||
const target = createEventTarget(node);
|
||||
node.addEventListener('click', e => {
|
||||
expect(e.altKey).toEqual(false);
|
||||
expect(e.button).toEqual(0);
|
||||
expect(e.buttons).toEqual(0);
|
||||
expect(e.clientX).toEqual(0);
|
||||
expect(e.clientY).toEqual(0);
|
||||
expect(e.ctrlKey).toEqual(false);
|
||||
expect(e.detail).toEqual(1);
|
||||
expect(typeof e.getModifierState).toEqual('function');
|
||||
expect(e.metaKey).toEqual(false);
|
||||
expect(e.movementX).toEqual(0);
|
||||
expect(e.movementY).toEqual(0);
|
||||
expect(e.offsetX).toEqual(0);
|
||||
expect(e.offsetY).toEqual(0);
|
||||
expect(e.pageX).toEqual(0);
|
||||
expect(e.pageY).toEqual(0);
|
||||
expect(typeof e.preventDefault).toEqual('function');
|
||||
expect(e.screenX).toEqual(0);
|
||||
expect(e.screenY).toEqual(defaultBrowserChromeSize);
|
||||
expect(e.shiftKey).toEqual(false);
|
||||
expect(typeof e.timeStamp).toEqual('number');
|
||||
});
|
||||
target.click();
|
||||
});
|
||||
|
||||
test('custom payload', () => {
|
||||
const target = createEventTarget(node);
|
||||
node.addEventListener('click', e => {
|
||||
expect(e.altKey).toEqual(true);
|
||||
expect(e.button).toEqual(1);
|
||||
expect(e.buttons).toEqual(4);
|
||||
expect(e.clientX).toEqual(10);
|
||||
expect(e.clientY).toEqual(20);
|
||||
expect(e.ctrlKey).toEqual(true);
|
||||
expect(e.metaKey).toEqual(true);
|
||||
expect(e.movementX).toEqual(1);
|
||||
expect(e.movementY).toEqual(2);
|
||||
expect(e.offsetX).toEqual(5);
|
||||
expect(e.offsetY).toEqual(5);
|
||||
expect(e.pageX).toEqual(50);
|
||||
expect(e.pageY).toEqual(50);
|
||||
expect(e.screenX).toEqual(10);
|
||||
expect(e.screenY).toEqual(20 + defaultBrowserChromeSize);
|
||||
expect(e.shiftKey).toEqual(true);
|
||||
});
|
||||
target.click({
|
||||
altKey: true,
|
||||
button: 1,
|
||||
buttons: 4,
|
||||
x: 10,
|
||||
y: 20,
|
||||
ctrlKey: true,
|
||||
metaKey: true,
|
||||
movementX: 1,
|
||||
movementY: 2,
|
||||
offsetX: 5,
|
||||
offsetY: 5,
|
||||
pageX: 50,
|
||||
pageY: 50,
|
||||
shiftKey: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('.focus()', () => {
|
||||
test('default', () => {
|
||||
const target = createEventTarget(node);
|
||||
node.addEventListener('focus', e => {
|
||||
expect(e.relatedTarget).toMatchInlineSnapshot(`null`);
|
||||
});
|
||||
target.blur();
|
||||
});
|
||||
|
||||
test('custom payload', () => {
|
||||
const target = createEventTarget(node);
|
||||
node.addEventListener('focus', e => {
|
||||
expect(e.relatedTarget).toMatchInlineSnapshot(`null`);
|
||||
});
|
||||
target.blur();
|
||||
});
|
||||
});
|
||||
|
||||
describe('.keydown()', () => {
|
||||
test('default', () => {
|
||||
const target = createEventTarget(node);
|
||||
node.addEventListener('keydown', e => {
|
||||
expect(e.altKey).toEqual(false);
|
||||
expect(e.ctrlKey).toEqual(false);
|
||||
expect(typeof e.getModifierState).toEqual('function');
|
||||
expect(e.key).toEqual('');
|
||||
expect(e.metaKey).toEqual(false);
|
||||
expect(typeof e.preventDefault).toEqual('function');
|
||||
expect(e.shiftKey).toEqual(false);
|
||||
expect(typeof e.timeStamp).toEqual('number');
|
||||
});
|
||||
target.keydown();
|
||||
});
|
||||
|
||||
test('custom payload', () => {
|
||||
const target = createEventTarget(node);
|
||||
node.addEventListener('keydown', e => {
|
||||
expect(e.altKey).toEqual(true);
|
||||
expect(e.ctrlKey).toEqual(true);
|
||||
expect(e.isComposing).toEqual(true);
|
||||
expect(e.key).toEqual('Enter');
|
||||
expect(e.metaKey).toEqual(true);
|
||||
expect(e.shiftKey).toEqual(true);
|
||||
});
|
||||
target.keydown({
|
||||
altKey: true,
|
||||
ctrlKey: true,
|
||||
isComposing: true,
|
||||
key: 'Enter',
|
||||
metaKey: true,
|
||||
shiftKey: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('.keyup()', () => {
|
||||
test('default', () => {
|
||||
const target = createEventTarget(node);
|
||||
node.addEventListener('keyup', e => {
|
||||
expect(e.altKey).toEqual(false);
|
||||
expect(e.ctrlKey).toEqual(false);
|
||||
expect(typeof e.getModifierState).toEqual('function');
|
||||
expect(e.key).toEqual('');
|
||||
expect(e.metaKey).toEqual(false);
|
||||
expect(typeof e.preventDefault).toEqual('function');
|
||||
expect(e.shiftKey).toEqual(false);
|
||||
expect(typeof e.timeStamp).toEqual('number');
|
||||
});
|
||||
target.keydown();
|
||||
});
|
||||
|
||||
test('custom payload', () => {
|
||||
const target = createEventTarget(node);
|
||||
node.addEventListener('keyup', e => {
|
||||
expect(e.altKey).toEqual(true);
|
||||
expect(e.ctrlKey).toEqual(true);
|
||||
expect(e.isComposing).toEqual(true);
|
||||
expect(e.key).toEqual('Enter');
|
||||
expect(e.metaKey).toEqual(true);
|
||||
expect(e.shiftKey).toEqual(true);
|
||||
});
|
||||
target.keyup({
|
||||
altKey: true,
|
||||
ctrlKey: true,
|
||||
isComposing: true,
|
||||
key: 'Enter',
|
||||
metaKey: true,
|
||||
shiftKey: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('.scroll()', () => {
|
||||
test('default', () => {
|
||||
const target = createEventTarget(node);
|
||||
node.addEventListener('scroll', e => {
|
||||
expect(e.type).toEqual('scroll');
|
||||
});
|
||||
target.scroll();
|
||||
});
|
||||
});
|
||||
|
||||
describe('.virtualclick()', () => {
|
||||
test('default', () => {
|
||||
const target = createEventTarget(node);
|
||||
node.addEventListener('click', e => {
|
||||
expect(e.altKey).toEqual(false);
|
||||
expect(e.button).toEqual(0);
|
||||
expect(e.buttons).toEqual(0);
|
||||
expect(e.clientX).toEqual(0);
|
||||
expect(e.clientY).toEqual(0);
|
||||
expect(e.ctrlKey).toEqual(false);
|
||||
expect(e.detail).toEqual(0);
|
||||
expect(typeof e.getModifierState).toEqual('function');
|
||||
expect(e.metaKey).toEqual(false);
|
||||
expect(e.movementX).toEqual(0);
|
||||
expect(e.movementY).toEqual(0);
|
||||
expect(e.offsetX).toEqual(0);
|
||||
expect(e.offsetY).toEqual(0);
|
||||
expect(e.pageX).toEqual(0);
|
||||
expect(e.pageY).toEqual(0);
|
||||
expect(typeof e.preventDefault).toEqual('function');
|
||||
expect(e.screenX).toEqual(0);
|
||||
expect(e.screenY).toEqual(0);
|
||||
expect(e.shiftKey).toEqual(false);
|
||||
expect(typeof e.timeStamp).toEqual('number');
|
||||
});
|
||||
target.virtualclick();
|
||||
});
|
||||
|
||||
test('custom payload', () => {
|
||||
const target = createEventTarget(node);
|
||||
node.addEventListener('click', e => {
|
||||
// expect most of the custom payload to be ignored
|
||||
expect(e.altKey).toEqual(true);
|
||||
expect(e.button).toEqual(1);
|
||||
expect(e.buttons).toEqual(0);
|
||||
expect(e.clientX).toEqual(0);
|
||||
expect(e.clientY).toEqual(0);
|
||||
expect(e.ctrlKey).toEqual(true);
|
||||
expect(e.detail).toEqual(0);
|
||||
expect(e.metaKey).toEqual(true);
|
||||
expect(e.pageX).toEqual(0);
|
||||
expect(e.pageY).toEqual(0);
|
||||
expect(e.screenX).toEqual(0);
|
||||
expect(e.screenY).toEqual(0);
|
||||
expect(e.shiftKey).toEqual(true);
|
||||
});
|
||||
target.virtualclick({
|
||||
altKey: true,
|
||||
button: 1,
|
||||
buttons: 4,
|
||||
x: 10,
|
||||
y: 20,
|
||||
ctrlKey: true,
|
||||
metaKey: true,
|
||||
pageX: 50,
|
||||
pageY: 50,
|
||||
shiftKey: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Complex event sequences
|
||||
*
|
||||
* ...coming soon
|
||||
*/
|
||||
|
||||
/**
|
||||
* Other APIs
|
||||
*/
|
||||
|
||||
test('.setBoundingClientRect()', () => {
|
||||
const target = createEventTarget(node);
|
||||
expect(node.getBoundingClientRect()).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"bottom": 0,
|
||||
"height": 0,
|
||||
"left": 0,
|
||||
"right": 0,
|
||||
"top": 0,
|
||||
"width": 0,
|
||||
}
|
||||
`);
|
||||
target.setBoundingClientRect({x: 10, y: 20, width: 100, height: 200});
|
||||
expect(node.getBoundingClientRect()).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"bottom": 220,
|
||||
"height": 200,
|
||||
"left": 10,
|
||||
"right": 110,
|
||||
"top": 20,
|
||||
"width": 100,
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
65
packages/dom-event-testing-library/constants.js
Normal file
65
packages/dom-event-testing-library/constants.js
Normal file
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @emails react-core
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
export const defaultPointerId = 1;
|
||||
export const defaultPointerSize = 23;
|
||||
export const defaultBrowserChromeSize = 50;
|
||||
|
||||
/**
|
||||
* Button property
|
||||
* This property only guarantees to indicate which buttons are pressed during events caused by pressing or
|
||||
* releasing one or multiple buttons. As such, it is not reliable for events such as 'mouseenter', 'mouseleave',
|
||||
* 'mouseover', 'mouseout' or 'mousemove'. Furthermore, the semantics differ for PointerEvent, where the value
|
||||
* for 'pointermove' will always be -1.
|
||||
*/
|
||||
|
||||
export const buttonType = {
|
||||
// no change since last event
|
||||
none: -1,
|
||||
// left-mouse
|
||||
// touch contact
|
||||
// pen contact
|
||||
primary: 0,
|
||||
// right-mouse
|
||||
// pen barrel button
|
||||
secondary: 2,
|
||||
// middle mouse
|
||||
auxiliary: 1,
|
||||
// back mouse
|
||||
back: 3,
|
||||
// forward mouse
|
||||
forward: 4,
|
||||
// pen eraser
|
||||
eraser: 5,
|
||||
};
|
||||
|
||||
/**
|
||||
* Buttons bitmask
|
||||
*/
|
||||
|
||||
export const buttonsType = {
|
||||
none: 0,
|
||||
// left-mouse
|
||||
// touch contact
|
||||
// pen contact
|
||||
primary: 1,
|
||||
// right-mouse
|
||||
// pen barrel button
|
||||
secondary: 2,
|
||||
// middle mouse
|
||||
auxiliary: 4,
|
||||
// back mouse
|
||||
back: 8,
|
||||
// forward mouse
|
||||
forward: 16,
|
||||
// pen eraser
|
||||
eraser: 32,
|
||||
};
|
||||
@@ -22,7 +22,9 @@ export function hasPointerEvent() {
|
||||
export function setPointerEvent(bool) {
|
||||
const pointerCaptureFn = name => id => {
|
||||
if (typeof id !== 'number') {
|
||||
console.error(`A pointerId must be passed to "${name}"`);
|
||||
if (__DEV__) {
|
||||
console.error('A pointerId must be passed to "%s"', name);
|
||||
}
|
||||
}
|
||||
};
|
||||
global.PointerEvent = bool ? emptyFunction : undefined;
|
||||
@@ -63,26 +65,3 @@ export const platform = {
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Buttons bitmask
|
||||
*/
|
||||
|
||||
export const buttonsType = {
|
||||
none: 0,
|
||||
// left-mouse
|
||||
// touch contact
|
||||
// pen contact
|
||||
primary: 1,
|
||||
// right-mouse
|
||||
// pen barrel button
|
||||
secondary: 2,
|
||||
// middle mouse
|
||||
auxiliary: 4,
|
||||
// back mouse
|
||||
back: 8,
|
||||
// forward mouse
|
||||
forward: 16,
|
||||
// pen eraser
|
||||
eraser: 32,
|
||||
};
|
||||
361
packages/dom-event-testing-library/domEventSequences.js
Normal file
361
packages/dom-event-testing-library/domEventSequences.js
Normal file
@@ -0,0 +1,361 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @emails react-core
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import {
|
||||
buttonType,
|
||||
buttonsType,
|
||||
defaultPointerId,
|
||||
defaultPointerSize,
|
||||
defaultBrowserChromeSize,
|
||||
} from './constants';
|
||||
import * as domEvents from './domEvents';
|
||||
import {hasPointerEvent, platform} from './domEnvironment';
|
||||
import * as touchStore from './touchStore';
|
||||
|
||||
/**
|
||||
* Converts a PointerEvent payload to a Touch
|
||||
*/
|
||||
function createTouch(target, payload) {
|
||||
const {
|
||||
height = defaultPointerSize,
|
||||
pageX,
|
||||
pageY,
|
||||
pointerId,
|
||||
pressure = 1,
|
||||
twist = 0,
|
||||
width = defaultPointerSize,
|
||||
x = 0,
|
||||
y = 0,
|
||||
} = payload;
|
||||
|
||||
return {
|
||||
clientX: x,
|
||||
clientY: y,
|
||||
force: pressure,
|
||||
identifier: pointerId,
|
||||
pageX: pageX || x,
|
||||
pageY: pageY || y,
|
||||
radiusX: width / 2,
|
||||
radiusY: height / 2,
|
||||
rotationAngle: twist,
|
||||
target,
|
||||
screenX: x,
|
||||
screenY: y + defaultBrowserChromeSize,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a PointerEvent to a TouchEvent
|
||||
*/
|
||||
function createTouchEventPayload(target, touch, payload) {
|
||||
const {
|
||||
altKey = false,
|
||||
ctrlKey = false,
|
||||
metaKey = false,
|
||||
preventDefault,
|
||||
shiftKey = false,
|
||||
timeStamp,
|
||||
} = payload;
|
||||
|
||||
return {
|
||||
altKey,
|
||||
changedTouches: [touch],
|
||||
ctrlKey,
|
||||
metaKey,
|
||||
preventDefault,
|
||||
shiftKey,
|
||||
targetTouches: touchStore.getTargetTouches(target),
|
||||
timeStamp,
|
||||
touches: touchStore.getTouches(),
|
||||
};
|
||||
}
|
||||
|
||||
function getPointerType(payload) {
|
||||
let pointerType = 'mouse';
|
||||
if (payload != null && payload.pointerType != null) {
|
||||
pointerType = payload.pointerType;
|
||||
}
|
||||
return pointerType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pointer events sequences.
|
||||
*
|
||||
* Creates representative browser event sequences for high-level gestures based on pointers.
|
||||
* This allows unit tests to be written in terms of simple pointer interactions while testing
|
||||
* that the responses to those interactions account for the complex sequence of events that
|
||||
* browsers produce as a result.
|
||||
*
|
||||
* Every time a new pointer touches the surface a 'touchstart' event should be dispatched.
|
||||
* - 'changedTouches' contains the new touch.
|
||||
* - 'targetTouches' contains all the active pointers for the target.
|
||||
* - 'touches' contains all the active pointers on the surface.
|
||||
*
|
||||
* Every time an existing pointer moves a 'touchmove' event should be dispatched.
|
||||
* - 'changedTouches' contains the updated touch.
|
||||
*
|
||||
* Every time an existing pointer leaves the surface a 'touchend' event should be dispatched.
|
||||
* - 'changedTouches' contains the released touch.
|
||||
* - 'targetTouches' contains any of the remaining active pointers for the target.
|
||||
*/
|
||||
|
||||
export function contextmenu(
|
||||
target,
|
||||
defaultPayload,
|
||||
{pointerType = 'mouse', modified} = {},
|
||||
) {
|
||||
const dispatch = arg => target.dispatchEvent(arg);
|
||||
|
||||
const payload = {
|
||||
pointerId: defaultPointerId,
|
||||
pointerType,
|
||||
...defaultPayload,
|
||||
};
|
||||
|
||||
const preventDefault = payload.preventDefault;
|
||||
|
||||
if (pointerType === 'touch') {
|
||||
if (hasPointerEvent()) {
|
||||
dispatch(
|
||||
domEvents.pointerdown({
|
||||
...payload,
|
||||
button: buttonType.primary,
|
||||
buttons: buttonsType.primary,
|
||||
}),
|
||||
);
|
||||
}
|
||||
const touch = createTouch(target, payload);
|
||||
touchStore.addTouch(touch);
|
||||
const touchEventPayload = createTouchEventPayload(target, touch, payload);
|
||||
dispatch(domEvents.touchstart(touchEventPayload));
|
||||
dispatch(
|
||||
domEvents.contextmenu({
|
||||
button: buttonType.primary,
|
||||
buttons: buttonsType.none,
|
||||
preventDefault,
|
||||
}),
|
||||
);
|
||||
touchStore.removeTouch(touch);
|
||||
} else if (pointerType === 'mouse') {
|
||||
if (modified === true) {
|
||||
const button = buttonType.primary;
|
||||
const buttons = buttonsType.primary;
|
||||
const ctrlKey = true;
|
||||
if (hasPointerEvent()) {
|
||||
dispatch(
|
||||
domEvents.pointerdown({button, buttons, ctrlKey, pointerType}),
|
||||
);
|
||||
}
|
||||
dispatch(domEvents.mousedown({button, buttons, ctrlKey}));
|
||||
if (platform.get() === 'mac') {
|
||||
dispatch(
|
||||
domEvents.contextmenu({button, buttons, ctrlKey, preventDefault}),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
const button = buttonType.secondary;
|
||||
const buttons = buttonsType.secondary;
|
||||
if (hasPointerEvent()) {
|
||||
dispatch(domEvents.pointerdown({button, buttons, pointerType}));
|
||||
}
|
||||
dispatch(domEvents.mousedown({button, buttons}));
|
||||
dispatch(domEvents.contextmenu({button, buttons, preventDefault}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function pointercancel(target, defaultPayload) {
|
||||
const dispatchEvent = arg => target.dispatchEvent(arg);
|
||||
const pointerType = getPointerType(defaultPayload);
|
||||
|
||||
const payload = {
|
||||
pointerId: defaultPointerId,
|
||||
pointerType,
|
||||
...defaultPayload,
|
||||
};
|
||||
|
||||
if (hasPointerEvent()) {
|
||||
dispatchEvent(domEvents.pointercancel(payload));
|
||||
} else {
|
||||
if (pointerType === 'mouse') {
|
||||
dispatchEvent(domEvents.dragstart(payload));
|
||||
} else {
|
||||
const touch = createTouch(target, payload);
|
||||
touchStore.removeTouch(touch);
|
||||
const touchEventPayload = createTouchEventPayload(target, touch, payload);
|
||||
dispatchEvent(domEvents.touchcancel(touchEventPayload));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function pointerdown(target, defaultPayload) {
|
||||
const dispatch = arg => target.dispatchEvent(arg);
|
||||
const pointerType = getPointerType(defaultPayload);
|
||||
|
||||
const payload = {
|
||||
button: buttonType.primary,
|
||||
buttons: buttonsType.primary,
|
||||
pointerId: defaultPointerId,
|
||||
pointerType,
|
||||
...defaultPayload,
|
||||
};
|
||||
|
||||
if (pointerType === 'mouse') {
|
||||
if (hasPointerEvent()) {
|
||||
dispatch(domEvents.pointerover(payload));
|
||||
dispatch(domEvents.pointerenter(payload));
|
||||
}
|
||||
dispatch(domEvents.mouseover(payload));
|
||||
dispatch(domEvents.mouseenter(payload));
|
||||
if (hasPointerEvent()) {
|
||||
dispatch(domEvents.pointerdown(payload));
|
||||
}
|
||||
dispatch(domEvents.mousedown(payload));
|
||||
if (document.activeElement !== target) {
|
||||
dispatch(domEvents.focus());
|
||||
}
|
||||
} else {
|
||||
if (hasPointerEvent()) {
|
||||
dispatch(domEvents.pointerover(payload));
|
||||
dispatch(domEvents.pointerenter(payload));
|
||||
dispatch(domEvents.pointerdown(payload));
|
||||
}
|
||||
const touch = createTouch(target, payload);
|
||||
touchStore.addTouch(touch);
|
||||
const touchEventPayload = createTouchEventPayload(target, touch, payload);
|
||||
dispatch(domEvents.touchstart(touchEventPayload));
|
||||
if (hasPointerEvent()) {
|
||||
dispatch(domEvents.gotpointercapture(payload));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function pointerenter(target, defaultPayload) {
|
||||
const dispatch = arg => target.dispatchEvent(arg);
|
||||
|
||||
const payload = {
|
||||
pointerId: defaultPointerId,
|
||||
...defaultPayload,
|
||||
};
|
||||
|
||||
if (hasPointerEvent()) {
|
||||
dispatch(domEvents.pointerover(payload));
|
||||
dispatch(domEvents.pointerenter(payload));
|
||||
}
|
||||
dispatch(domEvents.mouseover(payload));
|
||||
dispatch(domEvents.mouseenter(payload));
|
||||
}
|
||||
|
||||
export function pointerexit(target, defaultPayload) {
|
||||
const dispatch = arg => target.dispatchEvent(arg);
|
||||
|
||||
const payload = {
|
||||
pointerId: defaultPointerId,
|
||||
...defaultPayload,
|
||||
};
|
||||
|
||||
if (hasPointerEvent()) {
|
||||
dispatch(domEvents.pointerout(payload));
|
||||
dispatch(domEvents.pointerleave(payload));
|
||||
}
|
||||
dispatch(domEvents.mouseout(payload));
|
||||
dispatch(domEvents.mouseleave(payload));
|
||||
}
|
||||
|
||||
export function pointerhover(target, defaultPayload) {
|
||||
const dispatch = arg => target.dispatchEvent(arg);
|
||||
|
||||
const payload = {
|
||||
pointerId: defaultPointerId,
|
||||
...defaultPayload,
|
||||
};
|
||||
|
||||
if (hasPointerEvent()) {
|
||||
dispatch(domEvents.pointermove(payload));
|
||||
}
|
||||
dispatch(domEvents.mousemove(payload));
|
||||
}
|
||||
|
||||
export function pointermove(target, defaultPayload) {
|
||||
const dispatch = arg => target.dispatchEvent(arg);
|
||||
const pointerType = getPointerType(defaultPayload);
|
||||
|
||||
const payload = {
|
||||
pointerId: defaultPointerId,
|
||||
pointerType,
|
||||
...defaultPayload,
|
||||
};
|
||||
|
||||
if (hasPointerEvent()) {
|
||||
dispatch(
|
||||
domEvents.pointermove({
|
||||
pressure: pointerType === 'touch' ? 1 : 0.5,
|
||||
...payload,
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
if (pointerType === 'mouse') {
|
||||
dispatch(domEvents.mousemove(payload));
|
||||
} else {
|
||||
const touch = createTouch(target, payload);
|
||||
touchStore.updateTouch(touch);
|
||||
const touchEventPayload = createTouchEventPayload(target, touch, payload);
|
||||
dispatch(domEvents.touchmove(touchEventPayload));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function pointerup(target, defaultPayload) {
|
||||
const dispatch = arg => target.dispatchEvent(arg);
|
||||
const pointerType = getPointerType(defaultPayload);
|
||||
|
||||
const payload = {
|
||||
pointerId: defaultPointerId,
|
||||
pointerType,
|
||||
...defaultPayload,
|
||||
};
|
||||
|
||||
if (pointerType === 'mouse') {
|
||||
if (hasPointerEvent()) {
|
||||
dispatch(domEvents.pointerup(payload));
|
||||
}
|
||||
dispatch(domEvents.mouseup(payload));
|
||||
dispatch(domEvents.click(payload));
|
||||
} else {
|
||||
if (hasPointerEvent()) {
|
||||
dispatch(domEvents.pointerup(payload));
|
||||
dispatch(domEvents.lostpointercapture(payload));
|
||||
dispatch(domEvents.pointerout(payload));
|
||||
dispatch(domEvents.pointerleave(payload));
|
||||
}
|
||||
const touch = createTouch(target, payload);
|
||||
touchStore.removeTouch(touch);
|
||||
const touchEventPayload = createTouchEventPayload(target, touch, payload);
|
||||
dispatch(domEvents.touchend(touchEventPayload));
|
||||
dispatch(domEvents.mouseover(payload));
|
||||
dispatch(domEvents.mousemove(payload));
|
||||
dispatch(domEvents.mousedown(payload));
|
||||
if (document.activeElement !== target) {
|
||||
dispatch(domEvents.focus());
|
||||
}
|
||||
dispatch(domEvents.mouseup(payload));
|
||||
dispatch(domEvents.click(payload));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function should be called after each test to ensure the touchStore is cleared
|
||||
* in cases where the mock pointers weren't released before the test completed
|
||||
* (e.g., a test failed or ran a partial gesture).
|
||||
*/
|
||||
export function resetActivePointers() {
|
||||
touchStore.clear();
|
||||
}
|
||||
@@ -9,7 +9,12 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import {buttonsType} from './domEnvironment';
|
||||
import {
|
||||
buttonType,
|
||||
buttonsType,
|
||||
defaultPointerSize,
|
||||
defaultBrowserChromeSize,
|
||||
} from './constants';
|
||||
|
||||
/**
|
||||
* Native event object mocks for higher-level events.
|
||||
@@ -26,9 +31,6 @@ import {buttonsType} from './domEnvironment';
|
||||
* 3. PointerEvent and TouchEvent fields are normalized (e.g., 'rotationAngle' -> 'twist')
|
||||
*/
|
||||
|
||||
const defaultPointerSize = 23;
|
||||
const defaultBrowserChromeSize = 50;
|
||||
|
||||
function emptyFunction() {}
|
||||
|
||||
function createEvent(type, data = {}) {
|
||||
@@ -65,6 +67,7 @@ function createPointerEvent(
|
||||
type,
|
||||
{
|
||||
altKey = false,
|
||||
button = buttonType.none,
|
||||
buttons = buttonsType.none,
|
||||
ctrlKey = false,
|
||||
detail = 1,
|
||||
@@ -76,7 +79,7 @@ function createPointerEvent(
|
||||
offsetY = 0,
|
||||
pageX,
|
||||
pageY,
|
||||
pointerId = 1,
|
||||
pointerId,
|
||||
pressure = 0,
|
||||
preventDefault = emptyFunction,
|
||||
pointerType = 'mouse',
|
||||
@@ -98,6 +101,7 @@ function createPointerEvent(
|
||||
|
||||
return createEvent(type, {
|
||||
altKey,
|
||||
button,
|
||||
buttons,
|
||||
clientX: x,
|
||||
clientY: y,
|
||||
@@ -164,6 +168,7 @@ function createMouseEvent(
|
||||
type,
|
||||
{
|
||||
altKey = false,
|
||||
button = buttonType.none,
|
||||
buttons = buttonsType.none,
|
||||
ctrlKey = false,
|
||||
detail = 1,
|
||||
@@ -187,6 +192,7 @@ function createMouseEvent(
|
||||
|
||||
return createEvent(type, {
|
||||
altKey,
|
||||
button,
|
||||
buttons,
|
||||
clientX: x,
|
||||
clientY: y,
|
||||
@@ -211,80 +217,12 @@ function createMouseEvent(
|
||||
}
|
||||
|
||||
function createTouchEvent(type, payload) {
|
||||
const touchesPayload = Array.isArray(payload) ? payload : [payload];
|
||||
const firstTouch = touchesPayload[0];
|
||||
let altKey = false;
|
||||
let ctrlKey = false;
|
||||
let metaKey = false;
|
||||
let preventDefault = emptyFunction;
|
||||
let shiftKey = false;
|
||||
let timeStamp;
|
||||
|
||||
if (firstTouch != null) {
|
||||
if (firstTouch.altKey != null) {
|
||||
altKey = firstTouch.altKey;
|
||||
}
|
||||
if (firstTouch.ctrlKey != null) {
|
||||
ctrlKey = firstTouch.ctrlKey;
|
||||
}
|
||||
if (firstTouch.metaKey != null) {
|
||||
metaKey = firstTouch.metaKey;
|
||||
}
|
||||
if (firstTouch.preventDefault != null) {
|
||||
preventDefault = firstTouch.preventDefault;
|
||||
}
|
||||
if (firstTouch.shiftKey != null) {
|
||||
shiftKey = firstTouch.shiftKey;
|
||||
}
|
||||
if (firstTouch.timeStamp != null) {
|
||||
timeStamp = firstTouch.timeStamp;
|
||||
}
|
||||
}
|
||||
|
||||
const touches = touchesPayload.map(
|
||||
({
|
||||
height = defaultPointerSize,
|
||||
pageX,
|
||||
pageY,
|
||||
pointerId = 1,
|
||||
pressure = 1,
|
||||
twist = 0,
|
||||
width = defaultPointerSize,
|
||||
x = 0,
|
||||
y = 0,
|
||||
} = {}) => {
|
||||
return {
|
||||
clientX: x,
|
||||
clientY: y,
|
||||
force: pressure,
|
||||
identifier: pointerId,
|
||||
pageX: pageX || x,
|
||||
pageY: pageY || y,
|
||||
radiusX: width / 2,
|
||||
radiusY: height / 2,
|
||||
rotationAngle: twist,
|
||||
screenX: x,
|
||||
screenY: y + defaultBrowserChromeSize,
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
const activeTouches = type !== 'touchend' ? touches : null;
|
||||
|
||||
return createEvent(type, {
|
||||
altKey,
|
||||
changedTouches: touches,
|
||||
ctrlKey,
|
||||
...payload,
|
||||
detail: 0,
|
||||
metaKey,
|
||||
preventDefault,
|
||||
shiftKey,
|
||||
sourceCapabilities: {
|
||||
firesTouchEvents: true,
|
||||
},
|
||||
targetTouches: activeTouches,
|
||||
timeStamp,
|
||||
touches: activeTouches,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -293,11 +231,14 @@ function createTouchEvent(type, payload) {
|
||||
*/
|
||||
|
||||
export function blur({relatedTarget} = {}) {
|
||||
return createEvent('blur', {relatedTarget});
|
||||
return new FocusEvent('blur', {relatedTarget});
|
||||
}
|
||||
|
||||
export function click(payload) {
|
||||
return createMouseEvent('click', payload);
|
||||
return createMouseEvent('click', {
|
||||
button: buttonType.primary,
|
||||
...payload,
|
||||
});
|
||||
}
|
||||
|
||||
export function contextmenu(payload) {
|
||||
@@ -315,7 +256,7 @@ export function dragstart(payload) {
|
||||
}
|
||||
|
||||
export function focus({relatedTarget} = {}) {
|
||||
return createEvent('focus', {relatedTarget});
|
||||
return new FocusEvent('focus', {relatedTarget});
|
||||
}
|
||||
|
||||
export function scroll() {
|
||||
@@ -324,6 +265,7 @@ export function scroll() {
|
||||
|
||||
export function virtualclick(payload) {
|
||||
return createMouseEvent('click', {
|
||||
button: 0,
|
||||
...payload,
|
||||
buttons: 0,
|
||||
detail: 0,
|
||||
@@ -383,6 +325,7 @@ export function pointercancel(payload) {
|
||||
export function pointerdown(payload) {
|
||||
const isTouch = payload != null && payload.pointerType === 'touch';
|
||||
return createPointerEvent('pointerdown', {
|
||||
button: buttonType.primary,
|
||||
buttons: buttonsType.primary,
|
||||
pressure: isTouch ? 1 : 0.5,
|
||||
...payload,
|
||||
@@ -398,7 +341,10 @@ export function pointerleave(payload) {
|
||||
}
|
||||
|
||||
export function pointermove(payload) {
|
||||
return createPointerEvent('pointermove', payload);
|
||||
return createPointerEvent('pointermove', {
|
||||
...payload,
|
||||
button: buttonType.none,
|
||||
});
|
||||
}
|
||||
|
||||
export function pointerout(payload) {
|
||||
@@ -411,6 +357,7 @@ export function pointerover(payload) {
|
||||
|
||||
export function pointerup(payload) {
|
||||
return createPointerEvent('pointerup', {
|
||||
button: buttonType.primary,
|
||||
...payload,
|
||||
buttons: buttonsType.none,
|
||||
pressure: 0,
|
||||
@@ -422,13 +369,18 @@ export function pointerup(payload) {
|
||||
*/
|
||||
|
||||
export function mousedown(payload) {
|
||||
// The value of 'buttons' for 'mousedown' must not be 0
|
||||
// The value of 'button' and 'buttons' for 'mousedown' must not be none.
|
||||
const button =
|
||||
payload == null || payload.button === buttonType.none
|
||||
? buttonType.primary
|
||||
: payload.button;
|
||||
const buttons =
|
||||
payload == null || payload.buttons === 0
|
||||
payload == null || payload.buttons === buttonsType.none
|
||||
? buttonsType.primary
|
||||
: payload.buttons;
|
||||
return createMouseEvent('mousedown', {
|
||||
...payload,
|
||||
button,
|
||||
buttons,
|
||||
});
|
||||
}
|
||||
@@ -455,6 +407,7 @@ export function mouseover(payload) {
|
||||
|
||||
export function mouseup(payload) {
|
||||
return createMouseEvent('mouseup', {
|
||||
button: buttonType.primary,
|
||||
...payload,
|
||||
buttons: buttonsType.none,
|
||||
});
|
||||
@@ -9,19 +9,16 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import {buttonType, buttonsType} from './constants';
|
||||
import * as domEvents from './domEvents';
|
||||
import * as domEventSequences from './domEventSequences';
|
||||
import {
|
||||
buttonsType,
|
||||
hasPointerEvent,
|
||||
setPointerEvent,
|
||||
platform,
|
||||
} from './domEnvironment';
|
||||
import {hasPointerEvent, setPointerEvent, platform} from './domEnvironment';
|
||||
import {describeWithPointerEvent, testWithPointerType} from './testHelpers';
|
||||
|
||||
const createEventTarget = node => ({
|
||||
node,
|
||||
/**
|
||||
* General events abstraction.
|
||||
* Simple events abstraction.
|
||||
*/
|
||||
blur(payload) {
|
||||
node.dispatchEvent(domEvents.blur(payload));
|
||||
@@ -29,58 +26,30 @@ const createEventTarget = node => ({
|
||||
click(payload) {
|
||||
node.dispatchEvent(domEvents.click(payload));
|
||||
},
|
||||
contextmenu(payload, options) {
|
||||
domEventSequences.contextmenu(node, payload, options);
|
||||
},
|
||||
focus(payload) {
|
||||
node.dispatchEvent(domEvents.focus(payload));
|
||||
node.focus();
|
||||
},
|
||||
scroll(payload) {
|
||||
node.dispatchEvent(domEvents.scroll(payload));
|
||||
},
|
||||
/**
|
||||
* KeyboardEvent abstraction.
|
||||
*/
|
||||
keydown(payload) {
|
||||
node.dispatchEvent(domEvents.keydown(payload));
|
||||
},
|
||||
keyup(payload) {
|
||||
node.dispatchEvent(domEvents.keyup(payload));
|
||||
},
|
||||
scroll(payload) {
|
||||
node.dispatchEvent(domEvents.scroll(payload));
|
||||
},
|
||||
virtualclick(payload) {
|
||||
node.dispatchEvent(domEvents.virtualclick(payload));
|
||||
},
|
||||
tabNext() {
|
||||
node.dispatchEvent(
|
||||
domEvents.keydown({
|
||||
key: 'Tab',
|
||||
}),
|
||||
);
|
||||
node.dispatchEvent(
|
||||
domEvents.keyup({
|
||||
key: 'Tab',
|
||||
}),
|
||||
);
|
||||
},
|
||||
tabPrevious() {
|
||||
node.dispatchEvent(
|
||||
domEvents.keydown({
|
||||
key: 'Tab',
|
||||
shiftKey: true,
|
||||
}),
|
||||
);
|
||||
node.dispatchEvent(
|
||||
domEvents.keyup({
|
||||
key: 'Tab',
|
||||
shiftKey: true,
|
||||
}),
|
||||
);
|
||||
},
|
||||
/**
|
||||
* PointerEvent abstraction.
|
||||
* Dispatches the expected sequence of PointerEvents, MouseEvents, and
|
||||
* TouchEvents for a given environment.
|
||||
*/
|
||||
contextmenu(payload, options) {
|
||||
domEventSequences.contextmenu(node, payload, options);
|
||||
},
|
||||
// node no longer receives events for the pointer
|
||||
pointercancel(payload) {
|
||||
domEventSequences.pointercancel(node, payload);
|
||||
@@ -135,35 +104,16 @@ const createEventTarget = node => ({
|
||||
},
|
||||
});
|
||||
|
||||
function describeWithPointerEvent(message, describeFn) {
|
||||
const pointerEvent = 'PointerEvent';
|
||||
const fallback = 'MouseEvent/TouchEvent';
|
||||
describe.each`
|
||||
value | name
|
||||
${true} | ${pointerEvent}
|
||||
${false} | ${fallback}
|
||||
`(`${message}: $name`, entry => {
|
||||
const hasPointerEvents = entry.value;
|
||||
setPointerEvent(hasPointerEvents);
|
||||
describeFn(hasPointerEvents);
|
||||
});
|
||||
}
|
||||
|
||||
function testWithPointerType(message, testFn) {
|
||||
const table = hasPointerEvent()
|
||||
? ['mouse', 'touch', 'pen']
|
||||
: ['mouse', 'touch'];
|
||||
test.each(table)(`${message}: %s`, pointerType => {
|
||||
testFn(pointerType);
|
||||
});
|
||||
}
|
||||
const resetActivePointers = domEventSequences.resetActivePointers;
|
||||
|
||||
export {
|
||||
buttonType,
|
||||
buttonsType,
|
||||
createEventTarget,
|
||||
describeWithPointerEvent,
|
||||
platform,
|
||||
hasPointerEvent,
|
||||
resetActivePointers,
|
||||
setPointerEvent,
|
||||
testWithPointerType,
|
||||
};
|
||||
5
packages/dom-event-testing-library/package.json
Normal file
5
packages/dom-event-testing-library/package.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "dom-event-testing-library",
|
||||
"version": "0.0.0"
|
||||
}
|
||||
35
packages/dom-event-testing-library/testHelpers.js
Normal file
35
packages/dom-event-testing-library/testHelpers.js
Normal file
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @emails react-core
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import {hasPointerEvent, setPointerEvent} from './domEnvironment';
|
||||
|
||||
export function describeWithPointerEvent(message, describeFn) {
|
||||
const pointerEvent = 'PointerEvent';
|
||||
const fallback = 'MouseEvent/TouchEvent';
|
||||
describe.each`
|
||||
value | name
|
||||
${true} | ${pointerEvent}
|
||||
${false} | ${fallback}
|
||||
`(`${message}: $name`, entry => {
|
||||
const hasPointerEvents = entry.value;
|
||||
setPointerEvent(hasPointerEvents);
|
||||
describeFn(hasPointerEvents);
|
||||
});
|
||||
}
|
||||
|
||||
export function testWithPointerType(message, testFn) {
|
||||
const table = hasPointerEvent()
|
||||
? ['mouse', 'touch', 'pen']
|
||||
: ['mouse', 'touch'];
|
||||
test.each(table)(`${message}: %s`, pointerType => {
|
||||
testFn(pointerType);
|
||||
});
|
||||
}
|
||||
82
packages/dom-event-testing-library/touchStore.js
Normal file
82
packages/dom-event-testing-library/touchStore.js
Normal file
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @emails react-core
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Touch events state machine.
|
||||
*
|
||||
* Keeps track of the active pointers and allows them to be reflected in touch events.
|
||||
*/
|
||||
|
||||
const activeTouches = new Map();
|
||||
|
||||
export function addTouch(touch) {
|
||||
const identifier = touch.identifier;
|
||||
const target = touch.target;
|
||||
if (!activeTouches.has(target)) {
|
||||
activeTouches.set(target, new Map());
|
||||
}
|
||||
if (activeTouches.get(target).get(identifier)) {
|
||||
// Do not allow existing touches to be overwritten
|
||||
console.error(
|
||||
'Touch with identifier %s already exists. Did not record touch start.',
|
||||
identifier,
|
||||
);
|
||||
} else {
|
||||
activeTouches.get(target).set(identifier, touch);
|
||||
}
|
||||
}
|
||||
|
||||
export function updateTouch(touch) {
|
||||
const identifier = touch.identifier;
|
||||
const target = touch.target;
|
||||
if (activeTouches.get(target) != null) {
|
||||
activeTouches.get(target).set(identifier, touch);
|
||||
} else {
|
||||
console.error(
|
||||
'Touch with identifier %s does not exist. Cannot record touch move without a touch start.',
|
||||
identifier,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function removeTouch(touch) {
|
||||
const identifier = touch.identifier;
|
||||
const target = touch.target;
|
||||
if (activeTouches.get(target) != null) {
|
||||
if (activeTouches.get(target).has(identifier)) {
|
||||
activeTouches.get(target).delete(identifier);
|
||||
} else {
|
||||
console.error(
|
||||
'Touch with identifier %s does not exist. Cannot record touch end without a touch start.',
|
||||
identifier,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function getTouches() {
|
||||
const touches = [];
|
||||
activeTouches.forEach((_, target) => {
|
||||
touches.push(...getTargetTouches(target));
|
||||
});
|
||||
return touches;
|
||||
}
|
||||
|
||||
export function getTargetTouches(target) {
|
||||
if (activeTouches.get(target) != null) {
|
||||
return Array.from(activeTouches.get(target).values());
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
export function clear() {
|
||||
activeTouches.clear();
|
||||
}
|
||||
@@ -6,7 +6,7 @@ It is a part of the [Hooks API](https://reactjs.org/docs/hooks-intro.html) for R
|
||||
|
||||
## Installation
|
||||
|
||||
**Note: If you're using Create React App, please wait for a corresponding release of `react-scripts` that includes this rule instead of adding it directly.**
|
||||
**Note: If you're using Create React App, please use `react-scripts` >= 3 instead of adding it directly.**
|
||||
|
||||
Assuming you already have ESLint installed, run:
|
||||
|
||||
|
||||
@@ -3154,6 +3154,37 @@ const tests = {
|
||||
`and use that variable in the cleanup function.`,
|
||||
],
|
||||
},
|
||||
{
|
||||
code: `
|
||||
function MyComponent() {
|
||||
const myRef = useRef();
|
||||
useLayoutEffect_SAFE_FOR_SSR(() => {
|
||||
const handleMove = () => {};
|
||||
myRef.current.addEventListener('mousemove', handleMove);
|
||||
return () => myRef.current.removeEventListener('mousemove', handleMove);
|
||||
});
|
||||
return <div ref={myRef} />;
|
||||
}
|
||||
`,
|
||||
output: `
|
||||
function MyComponent() {
|
||||
const myRef = useRef();
|
||||
useLayoutEffect_SAFE_FOR_SSR(() => {
|
||||
const handleMove = () => {};
|
||||
myRef.current.addEventListener('mousemove', handleMove);
|
||||
return () => myRef.current.removeEventListener('mousemove', handleMove);
|
||||
});
|
||||
return <div ref={myRef} />;
|
||||
}
|
||||
`,
|
||||
errors: [
|
||||
`The ref value 'myRef.current' will likely have changed by the time ` +
|
||||
`this effect cleanup function runs. If this ref points to a node ` +
|
||||
`rendered by React, copy 'myRef.current' to a variable inside the effect, ` +
|
||||
`and use that variable in the cleanup function.`,
|
||||
],
|
||||
options: [{additionalHooks: 'useLayoutEffect_SAFE_FOR_SSR'}],
|
||||
},
|
||||
{
|
||||
// Autofix ignores constant primitives (leaving the ones that are there).
|
||||
code: `
|
||||
|
||||
@@ -141,6 +141,46 @@ const tests = {
|
||||
return useHook1(useHook2());
|
||||
}
|
||||
`,
|
||||
`
|
||||
// Valid because hooks can be used in anonymous arrow-function arguments
|
||||
// to forwardRef.
|
||||
const FancyButton = React.forwardRef((props, ref) => {
|
||||
useHook();
|
||||
return <button {...props} ref={ref} />
|
||||
});
|
||||
`,
|
||||
`
|
||||
// Valid because hooks can be used in anonymous function arguments to
|
||||
// forwardRef.
|
||||
const FancyButton = React.forwardRef(function (props, ref) {
|
||||
useHook();
|
||||
return <button {...props} ref={ref} />
|
||||
});
|
||||
`,
|
||||
`
|
||||
// Valid because hooks can be used in anonymous function arguments to
|
||||
// forwardRef.
|
||||
const FancyButton = forwardRef(function (props, ref) {
|
||||
useHook();
|
||||
return <button {...props} ref={ref} />
|
||||
});
|
||||
`,
|
||||
`
|
||||
// Valid because hooks can be used in anonymous function arguments to
|
||||
// React.memo.
|
||||
const MemoizedFunction = React.memo(props => {
|
||||
useHook();
|
||||
return <button {...props} />
|
||||
});
|
||||
`,
|
||||
`
|
||||
// Valid because hooks can be used in anonymous function arguments to
|
||||
// memo.
|
||||
const MemoizedFunction = memo(function (props) {
|
||||
useHook();
|
||||
return <button {...props} />
|
||||
});
|
||||
`,
|
||||
`
|
||||
// Valid because classes can call functions.
|
||||
// We don't consider these to be hooks.
|
||||
@@ -262,6 +302,24 @@ const tests = {
|
||||
});
|
||||
}
|
||||
`,
|
||||
`
|
||||
// This is valid because "use"-prefixed functions called in
|
||||
// unnamed function arguments are not assumed to be hooks.
|
||||
React.unknownFunction((foo, bar) => {
|
||||
if (foo) {
|
||||
useNotAHook(bar)
|
||||
}
|
||||
});
|
||||
`,
|
||||
`
|
||||
// This is valid because "use"-prefixed functions called in
|
||||
// unnamed function arguments are not assumed to be hooks.
|
||||
unknownFunction(function(foo, bar) {
|
||||
if (foo) {
|
||||
useNotAHook(bar)
|
||||
}
|
||||
});
|
||||
`,
|
||||
`
|
||||
// Regression test for incorrectly flagged valid code.
|
||||
function RegressionTest() {
|
||||
@@ -437,6 +495,32 @@ const tests = {
|
||||
`,
|
||||
errors: [genericError('useHookInsideCallback')],
|
||||
},
|
||||
{
|
||||
code: `
|
||||
// Invalid because it's a common misunderstanding.
|
||||
// We *could* make it valid but the runtime error could be confusing.
|
||||
const ComponentWithHookInsideCallback = React.forwardRef((props, ref) => {
|
||||
useEffect(() => {
|
||||
useHookInsideCallback();
|
||||
});
|
||||
return <button {...props} ref={ref} />
|
||||
});
|
||||
`,
|
||||
errors: [genericError('useHookInsideCallback')],
|
||||
},
|
||||
{
|
||||
code: `
|
||||
// Invalid because it's a common misunderstanding.
|
||||
// We *could* make it valid but the runtime error could be confusing.
|
||||
const ComponentWithHookInsideCallback = React.memo(props => {
|
||||
useEffect(() => {
|
||||
useHookInsideCallback();
|
||||
});
|
||||
return <button {...props} />
|
||||
});
|
||||
`,
|
||||
errors: [genericError('useHookInsideCallback')],
|
||||
},
|
||||
{
|
||||
code: `
|
||||
// Invalid because it's a common misunderstanding.
|
||||
@@ -695,6 +779,55 @@ const tests = {
|
||||
// conditionalError('useState'),
|
||||
],
|
||||
},
|
||||
{
|
||||
code: `
|
||||
// Invalid because it's dangerous and might not warn otherwise.
|
||||
// This *must* be invalid.
|
||||
const FancyButton = React.forwardRef((props, ref) => {
|
||||
if (props.fancy) {
|
||||
useCustomHook();
|
||||
}
|
||||
return <button ref={ref}>{props.children}</button>;
|
||||
});
|
||||
`,
|
||||
errors: [conditionalError('useCustomHook')],
|
||||
},
|
||||
{
|
||||
code: `
|
||||
// Invalid because it's dangerous and might not warn otherwise.
|
||||
// This *must* be invalid.
|
||||
const FancyButton = forwardRef(function(props, ref) {
|
||||
if (props.fancy) {
|
||||
useCustomHook();
|
||||
}
|
||||
return <button ref={ref}>{props.children}</button>;
|
||||
});
|
||||
`,
|
||||
errors: [conditionalError('useCustomHook')],
|
||||
},
|
||||
{
|
||||
code: `
|
||||
// Invalid because it's dangerous and might not warn otherwise.
|
||||
// This *must* be invalid.
|
||||
const MemoizedButton = memo(function(props) {
|
||||
if (props.fancy) {
|
||||
useCustomHook();
|
||||
}
|
||||
return <button>{props.children}</button>;
|
||||
});
|
||||
`,
|
||||
errors: [conditionalError('useCustomHook')],
|
||||
},
|
||||
{
|
||||
code: `
|
||||
// This is invalid because "use"-prefixed functions used in named
|
||||
// functions are assumed to be hooks.
|
||||
React.unknownFunction(function notAComponent(foo, bar) {
|
||||
useProbablyAHook(bar)
|
||||
});
|
||||
`,
|
||||
errors: [functionError('useProbablyAHook', 'notAComponent')],
|
||||
},
|
||||
{
|
||||
code: `
|
||||
// Invalid because it's dangerous.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "eslint-plugin-react-hooks",
|
||||
"description": "ESLint rules for React Hooks",
|
||||
"version": "2.1.2",
|
||||
"version": "2.3.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/facebook/react.git",
|
||||
|
||||
@@ -81,7 +81,7 @@ export default {
|
||||
// Get the reactive hook node.
|
||||
const reactiveHook = node.parent.callee;
|
||||
const reactiveHookName = getNodeWithoutReactNamespace(reactiveHook).name;
|
||||
const isEffect = reactiveHookName.endsWith('Effect');
|
||||
const isEffect = /Effect($|[^a-z])/g.test(reactiveHookName);
|
||||
|
||||
// Get the declared dependencies for this reactive hook. If there is no
|
||||
// second argument then the reactive callback will re-run on every render.
|
||||
@@ -613,21 +613,15 @@ export default {
|
||||
context.report({
|
||||
node: declaredDependencyNode,
|
||||
message:
|
||||
`The ${
|
||||
declaredDependencyNode.raw
|
||||
} literal is not a valid dependency ` +
|
||||
`The ${declaredDependencyNode.raw} literal is not a valid dependency ` +
|
||||
`because it never changes. ` +
|
||||
`Did you mean to include ${
|
||||
declaredDependencyNode.value
|
||||
} in the array instead?`,
|
||||
`Did you mean to include ${declaredDependencyNode.value} in the array instead?`,
|
||||
});
|
||||
} else {
|
||||
context.report({
|
||||
node: declaredDependencyNode,
|
||||
message:
|
||||
`The ${
|
||||
declaredDependencyNode.raw
|
||||
} literal is not a valid dependency ` +
|
||||
`The ${declaredDependencyNode.raw} literal is not a valid dependency ` +
|
||||
'because it never changes. You can safely remove it.',
|
||||
});
|
||||
}
|
||||
@@ -697,22 +691,16 @@ export default {
|
||||
bareFunctions.forEach(({fn, suggestUseCallback}) => {
|
||||
let message =
|
||||
`The '${fn.name.name}' function makes the dependencies of ` +
|
||||
`${reactiveHookName} Hook (at line ${
|
||||
declaredDependenciesNode.loc.start.line
|
||||
}) ` +
|
||||
`${reactiveHookName} Hook (at line ${declaredDependenciesNode.loc.start.line}) ` +
|
||||
`change on every render.`;
|
||||
if (suggestUseCallback) {
|
||||
message +=
|
||||
` To fix this, ` +
|
||||
`wrap the '${
|
||||
fn.name.name
|
||||
}' definition into its own useCallback() Hook.`;
|
||||
`wrap the '${fn.name.name}' definition into its own useCallback() Hook.`;
|
||||
} else {
|
||||
message +=
|
||||
` Move it inside the ${reactiveHookName} callback. ` +
|
||||
`Alternatively, wrap the '${
|
||||
fn.name.name
|
||||
}' definition into its own useCallback() Hook.`;
|
||||
`Alternatively, wrap the '${fn.name.name}' definition into its own useCallback() Hook.`;
|
||||
}
|
||||
// TODO: What if the function needs to change on every render anyway?
|
||||
// Should we suggest removing effect deps as an appropriate fix too?
|
||||
|
||||
@@ -54,6 +54,41 @@ function isComponentName(node) {
|
||||
}
|
||||
}
|
||||
|
||||
function isReactFunction(node, functionName) {
|
||||
return (
|
||||
node.name === functionName ||
|
||||
(node.type === 'MemberExpression' &&
|
||||
node.object.name === 'React' &&
|
||||
node.property.name === functionName)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the node is a callback argument of forwardRef. This render function
|
||||
* should follow the rules of hooks.
|
||||
*/
|
||||
|
||||
function isForwardRefCallback(node) {
|
||||
return !!(
|
||||
node.parent &&
|
||||
node.parent.callee &&
|
||||
isReactFunction(node.parent.callee, 'forwardRef')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the node is a callback argument of React.memo. This anonymous
|
||||
* functional component should follow the rules of hooks.
|
||||
*/
|
||||
|
||||
function isMemoCallback(node) {
|
||||
return !!(
|
||||
node.parent &&
|
||||
node.parent.callee &&
|
||||
isReactFunction(node.parent.callee, 'memo')
|
||||
);
|
||||
}
|
||||
|
||||
function isInsideComponentOrHook(node) {
|
||||
while (node) {
|
||||
const functionName = getFunctionName(node);
|
||||
@@ -62,6 +97,9 @@ function isInsideComponentOrHook(node) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (isForwardRefCallback(node) || isMemoCallback(node)) {
|
||||
return true;
|
||||
}
|
||||
node = node.parent;
|
||||
}
|
||||
return false;
|
||||
@@ -290,7 +328,8 @@ export default {
|
||||
// `undefined` then we know either that we have an anonymous function
|
||||
// expression or our code path is not in a function. In both cases we
|
||||
// will want to error since neither are React function components or
|
||||
// hook functions.
|
||||
// hook functions - unless it is an anonymous function argument to
|
||||
// forwardRef or memo.
|
||||
const codePathFunctionName = getFunctionName(codePathNode);
|
||||
|
||||
// This is a valid code path for React hooks if we are directly in a React
|
||||
@@ -301,7 +340,7 @@ export default {
|
||||
const isDirectlyInsideComponentOrHook = codePathFunctionName
|
||||
? isComponentName(codePathFunctionName) ||
|
||||
isHook(codePathFunctionName)
|
||||
: false;
|
||||
: isForwardRefCallback(codePathNode) || isMemoCallback(codePathNode);
|
||||
|
||||
// Compute the earliest finalizer level using information from the
|
||||
// cache. We expect all reachable final segments to have a cache entry
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "jest-react",
|
||||
"version": "0.8.2",
|
||||
"version": "0.10.0",
|
||||
"description": "Jest matchers and utilities for testing React components.",
|
||||
"main": "index.js",
|
||||
"repository": {
|
||||
|
||||
@@ -134,7 +134,7 @@ function extractPluginEvents(
|
||||
topLevelType: TopLevelType,
|
||||
targetInst: null | Fiber,
|
||||
nativeEvent: AnyNativeEvent,
|
||||
nativeEventTarget: EventTarget,
|
||||
nativeEventTarget: null | EventTarget,
|
||||
eventSystemFlags: EventSystemFlags,
|
||||
): Array<ReactSyntheticEvent> | ReactSyntheticEvent | null {
|
||||
let events = null;
|
||||
@@ -161,7 +161,7 @@ export function runExtractedPluginEventsInBatch(
|
||||
topLevelType: TopLevelType,
|
||||
targetInst: null | Fiber,
|
||||
nativeEvent: AnyNativeEvent,
|
||||
nativeEventTarget: EventTarget,
|
||||
nativeEventTarget: null | EventTarget,
|
||||
eventSystemFlags: EventSystemFlags,
|
||||
) {
|
||||
const events = extractPluginEvents(
|
||||
|
||||
@@ -16,7 +16,7 @@ import type {
|
||||
|
||||
import invariant from 'shared/invariant';
|
||||
|
||||
type NamesToPlugins = {[key: PluginName]: PluginModule<AnyNativeEvent>};
|
||||
type NamesToPlugins = {[key: PluginName]: PluginModule<AnyNativeEvent>, ...};
|
||||
type EventPluginOrder = null | Array<PluginName>;
|
||||
|
||||
/**
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
|
||||
import {invokeGuardedCallbackAndCatchFirstError} from 'shared/ReactErrorUtils';
|
||||
import invariant from 'shared/invariant';
|
||||
import warningWithoutStack from 'shared/warningWithoutStack';
|
||||
|
||||
export let getFiberCurrentPropsFromNode = null;
|
||||
export let getInstanceFromNode = null;
|
||||
@@ -22,11 +21,12 @@ export function setComponentTree(
|
||||
getInstanceFromNode = getInstanceFromNodeImpl;
|
||||
getNodeFromInstance = getNodeFromInstanceImpl;
|
||||
if (__DEV__) {
|
||||
warningWithoutStack(
|
||||
getNodeFromInstance && getInstanceFromNode,
|
||||
'EventPluginUtils.setComponentTree(...): Injected ' +
|
||||
'module is missing getNodeFromInstance or getInstanceFromNode.',
|
||||
);
|
||||
if (!getNodeFromInstance || !getInstanceFromNode) {
|
||||
console.error(
|
||||
'EventPluginUtils.setComponentTree(...): Injected ' +
|
||||
'module is missing getNodeFromInstance or getInstanceFromNode.',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,20 +40,19 @@ if (__DEV__) {
|
||||
const listenersLen = listenersIsArr
|
||||
? dispatchListeners.length
|
||||
: dispatchListeners
|
||||
? 1
|
||||
: 0;
|
||||
? 1
|
||||
: 0;
|
||||
|
||||
const instancesIsArr = Array.isArray(dispatchInstances);
|
||||
const instancesLen = instancesIsArr
|
||||
? dispatchInstances.length
|
||||
: dispatchInstances
|
||||
? 1
|
||||
: 0;
|
||||
? 1
|
||||
: 0;
|
||||
|
||||
warningWithoutStack(
|
||||
instancesIsArr === listenersIsArr && instancesLen === listenersLen,
|
||||
'EventPluginUtils: Invalid `event`.',
|
||||
);
|
||||
if (instancesIsArr !== listenersIsArr || instancesLen !== listenersLen) {
|
||||
console.error('EventPluginUtils: Invalid `event`.');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
traverseTwoPhase,
|
||||
traverseEnterLeave,
|
||||
} from 'shared/ReactTreeTraversal';
|
||||
import warningWithoutStack from 'shared/warningWithoutStack';
|
||||
|
||||
import {getListener} from './EventPluginHub';
|
||||
import accumulateInto from './accumulateInto';
|
||||
@@ -46,7 +45,9 @@ function listenerAtPhase(inst, event, propagationPhase: PropagationPhases) {
|
||||
*/
|
||||
function accumulateDirectionalDispatches(inst, phase, event) {
|
||||
if (__DEV__) {
|
||||
warningWithoutStack(inst, 'Dispatching inst must not be null');
|
||||
if (!inst) {
|
||||
console.error('Dispatching inst must not be null');
|
||||
}
|
||||
}
|
||||
const listener = listenerAtPhase(inst, event, phase);
|
||||
if (listener) {
|
||||
|
||||
@@ -15,3 +15,4 @@ export const IS_PASSIVE = 1 << 2;
|
||||
export const IS_ACTIVE = 1 << 3;
|
||||
export const PASSIVE_NOT_SUPPORTED = 1 << 4;
|
||||
export const IS_REPLAYED = 1 << 5;
|
||||
export const IS_FIRST_ANCESTOR = 1 << 6;
|
||||
|
||||
@@ -15,7 +15,7 @@ import type {
|
||||
import type {TopLevelType} from './TopLevelEventTypes';
|
||||
import type {EventSystemFlags} from 'legacy-events/EventSystemFlags';
|
||||
|
||||
export type EventTypes = {[key: string]: DispatchConfig};
|
||||
export type EventTypes = {[key: string]: DispatchConfig, ...};
|
||||
|
||||
export type AnyNativeEvent = Event | KeyboardEvent | MouseEvent | Touch;
|
||||
|
||||
@@ -27,8 +27,9 @@ export type PluginModule<NativeEvent> = {
|
||||
topLevelType: TopLevelType,
|
||||
targetInst: null | Fiber,
|
||||
nativeTarget: NativeEvent,
|
||||
nativeEventTarget: EventTarget,
|
||||
nativeEventTarget: null | EventTarget,
|
||||
eventSystemFlags: EventSystemFlags,
|
||||
) => ?ReactSyntheticEvent,
|
||||
tapMoveThreshold?: number,
|
||||
...
|
||||
};
|
||||
|
||||
@@ -33,8 +33,12 @@ function restoreStateOfTarget(target) {
|
||||
'setRestoreImplementation() needs to be called to handle a target for controlled ' +
|
||||
'events. This error is likely caused by a bug in React. Please file an issue.',
|
||||
);
|
||||
const props = getFiberCurrentPropsFromNode(internalInstance.stateNode);
|
||||
restoreImpl(internalInstance.stateNode, internalInstance.type, props);
|
||||
const stateNode = internalInstance.stateNode;
|
||||
// Guard against Fiber being unmounted.
|
||||
if (stateNode) {
|
||||
const props = getFiberCurrentPropsFromNode(stateNode);
|
||||
restoreImpl(internalInstance.stateNode, internalInstance.type, props);
|
||||
}
|
||||
}
|
||||
|
||||
export function setRestoreImplementation(
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
restoreStateIfNeeded,
|
||||
} from './ReactControlledComponent';
|
||||
|
||||
import {enableFlareAPI} from 'shared/ReactFeatureFlags';
|
||||
import {enableDeprecatedFlareAPI} from 'shared/ReactFeatureFlags';
|
||||
import {invokeGuardedCallbackAndCatchFirstError} from 'shared/ReactErrorUtils';
|
||||
|
||||
// Used as a way to call batchedUpdates when we don't have a reference to
|
||||
@@ -118,8 +118,9 @@ export function flushDiscreteUpdatesIfNeeded(timeStamp: number) {
|
||||
// behaviour as we had before this change, so the risks are low.
|
||||
if (
|
||||
!isInsideEventHandler &&
|
||||
(!enableFlareAPI ||
|
||||
(timeStamp === 0 || lastFlushedEventTimeStamp !== timeStamp))
|
||||
(!enableDeprecatedFlareAPI ||
|
||||
timeStamp === 0 ||
|
||||
lastFlushedEventTimeStamp !== timeStamp)
|
||||
) {
|
||||
lastFlushedEventTimeStamp = timeStamp;
|
||||
flushDiscreteUpdatesImpl();
|
||||
|
||||
@@ -12,17 +12,17 @@ import type {Fiber} from 'react-reconciler/src/ReactFiber';
|
||||
import type {EventPriority} from 'shared/ReactTypes';
|
||||
import type {TopLevelType} from './TopLevelEventTypes';
|
||||
|
||||
export type DispatchConfig = {
|
||||
export type DispatchConfig = {|
|
||||
dependencies: Array<TopLevelType>,
|
||||
phasedRegistrationNames?: {
|
||||
phasedRegistrationNames?: {|
|
||||
bubbled: string,
|
||||
captured: string,
|
||||
},
|
||||
|},
|
||||
registrationName?: string,
|
||||
eventPriority: EventPriority,
|
||||
};
|
||||
|};
|
||||
|
||||
export type ReactSyntheticEvent = {
|
||||
export type ReactSyntheticEvent = {|
|
||||
dispatchConfig: DispatchConfig,
|
||||
getPooled: (
|
||||
dispatchConfig: DispatchConfig,
|
||||
@@ -31,4 +31,4 @@ export type ReactSyntheticEvent = {
|
||||
nativeEventTarget: EventTarget,
|
||||
) => ReactSyntheticEvent,
|
||||
isPersistent: () => boolean,
|
||||
} & SyntheticEvent<>;
|
||||
|} & SyntheticEvent<>;
|
||||
|
||||
@@ -350,10 +350,10 @@ function setResponderAndExtractTransfer(
|
||||
const shouldSetEventType = isStartish(topLevelType)
|
||||
? eventTypes.startShouldSetResponder
|
||||
: isMoveish(topLevelType)
|
||||
? eventTypes.moveShouldSetResponder
|
||||
: topLevelType === TOP_SELECTION_CHANGE
|
||||
? eventTypes.selectionChangeShouldSetResponder
|
||||
: eventTypes.scrollShouldSetResponder;
|
||||
? eventTypes.moveShouldSetResponder
|
||||
: topLevelType === TOP_SELECTION_CHANGE
|
||||
? eventTypes.selectionChangeShouldSetResponder
|
||||
: eventTypes.scrollShouldSetResponder;
|
||||
|
||||
// TODO: stop one short of the current responder.
|
||||
const bubbleShouldSetFrom = !responderInst
|
||||
@@ -515,9 +515,11 @@ const ResponderEventPlugin = {
|
||||
if (trackedTouchCount >= 0) {
|
||||
trackedTouchCount -= 1;
|
||||
} else {
|
||||
console.warn(
|
||||
'Ended a touch event which was not counted in `trackedTouchCount`.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
console.warn(
|
||||
'Ended a touch event which was not counted in `trackedTouchCount`.',
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -548,10 +550,10 @@ const ResponderEventPlugin = {
|
||||
const incrementalTouch = isResponderTouchStart
|
||||
? eventTypes.responderStart
|
||||
: isResponderTouchMove
|
||||
? eventTypes.responderMove
|
||||
: isResponderTouchEnd
|
||||
? eventTypes.responderEnd
|
||||
: null;
|
||||
? eventTypes.responderMove
|
||||
: isResponderTouchEnd
|
||||
? eventTypes.responderEnd
|
||||
: null;
|
||||
|
||||
if (incrementalTouch) {
|
||||
const gesture = ResponderSyntheticEvent.getPooled(
|
||||
@@ -575,8 +577,8 @@ const ResponderEventPlugin = {
|
||||
const finalTouch = isResponderTerminate
|
||||
? eventTypes.responderTerminate
|
||||
: isResponderRelease
|
||||
? eventTypes.responderRelease
|
||||
: null;
|
||||
? eventTypes.responderRelease
|
||||
: null;
|
||||
if (finalTouch) {
|
||||
const finalEvent = ResponderSyntheticEvent.getPooled(
|
||||
finalTouch,
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
*/
|
||||
|
||||
import invariant from 'shared/invariant';
|
||||
import warningWithoutStack from 'shared/warningWithoutStack';
|
||||
|
||||
import {isStartish, isMoveish, isEndish} from './ResponderTopLevelEventTypes';
|
||||
|
||||
@@ -17,7 +16,7 @@ import {isStartish, isMoveish, isEndish} from './ResponderTopLevelEventTypes';
|
||||
* should typically only see IDs in the range of 1-20 because IDs get recycled
|
||||
* when touches end and start again.
|
||||
*/
|
||||
type TouchRecord = {
|
||||
type TouchRecord = {|
|
||||
touchActive: boolean,
|
||||
startPageX: number,
|
||||
startPageY: number,
|
||||
@@ -28,7 +27,7 @@ type TouchRecord = {
|
||||
previousPageX: number,
|
||||
previousPageY: number,
|
||||
previousTimeStamp: number,
|
||||
};
|
||||
|};
|
||||
|
||||
const MAX_TOUCH_BANK = 20;
|
||||
const touchBank: Array<TouchRecord> = [];
|
||||
@@ -47,10 +46,12 @@ type Touch = {
|
||||
pageX: number,
|
||||
pageY: number,
|
||||
timestamp: number,
|
||||
...
|
||||
};
|
||||
type TouchEvent = {
|
||||
changedTouches: Array<Touch>,
|
||||
touches: Array<Touch>,
|
||||
...
|
||||
};
|
||||
|
||||
function timestampForTouch(touch: Touch): number {
|
||||
@@ -95,13 +96,14 @@ function resetTouchRecord(touchRecord: TouchRecord, touch: Touch): void {
|
||||
function getTouchIdentifier({identifier}: Touch): number {
|
||||
invariant(identifier != null, 'Touch object is missing identifier.');
|
||||
if (__DEV__) {
|
||||
warningWithoutStack(
|
||||
identifier <= MAX_TOUCH_BANK,
|
||||
'Touch identifier %s is greater than maximum supported %s which causes ' +
|
||||
'performance issues backfilling array locations for all of the indices.',
|
||||
identifier,
|
||||
MAX_TOUCH_BANK,
|
||||
);
|
||||
if (identifier > MAX_TOUCH_BANK) {
|
||||
console.error(
|
||||
'Touch identifier %s is greater than maximum supported %s which causes ' +
|
||||
'performance issues backfilling array locations for all of the indices.',
|
||||
identifier,
|
||||
MAX_TOUCH_BANK,
|
||||
);
|
||||
}
|
||||
}
|
||||
return identifier;
|
||||
}
|
||||
@@ -129,12 +131,15 @@ function recordTouchMove(touch: Touch): void {
|
||||
touchRecord.currentTimeStamp = timestampForTouch(touch);
|
||||
touchHistory.mostRecentTimeStamp = timestampForTouch(touch);
|
||||
} else {
|
||||
console.warn(
|
||||
'Cannot record touch move without a touch start.\n' + 'Touch Move: %s\n',
|
||||
'Touch Bank: %s',
|
||||
printTouch(touch),
|
||||
printTouchBank(),
|
||||
);
|
||||
if (__DEV__) {
|
||||
console.warn(
|
||||
'Cannot record touch move without a touch start.\n' +
|
||||
'Touch Move: %s\n' +
|
||||
'Touch Bank: %s',
|
||||
printTouch(touch),
|
||||
printTouchBank(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,12 +155,15 @@ function recordTouchEnd(touch: Touch): void {
|
||||
touchRecord.currentTimeStamp = timestampForTouch(touch);
|
||||
touchHistory.mostRecentTimeStamp = timestampForTouch(touch);
|
||||
} else {
|
||||
console.warn(
|
||||
'Cannot record touch end without a touch start.\n' + 'Touch End: %s\n',
|
||||
'Touch Bank: %s',
|
||||
printTouch(touch),
|
||||
printTouchBank(),
|
||||
);
|
||||
if (__DEV__) {
|
||||
console.warn(
|
||||
'Cannot record touch end without a touch start.\n' +
|
||||
'Touch End: %s\n' +
|
||||
'Touch Bank: %s',
|
||||
printTouch(touch),
|
||||
printTouchBank(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,10 +208,9 @@ const ResponderTouchHistoryStore = {
|
||||
}
|
||||
if (__DEV__) {
|
||||
const activeRecord = touchBank[touchHistory.indexOfSingleActiveTouch];
|
||||
warningWithoutStack(
|
||||
activeRecord != null && activeRecord.touchActive,
|
||||
'Cannot find single active touch.',
|
||||
);
|
||||
if (activeRecord == null || !activeRecord.touchActive) {
|
||||
console.error('Cannot find single active touch.');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
/* eslint valid-typeof: 0 */
|
||||
|
||||
import invariant from 'shared/invariant';
|
||||
import warningWithoutStack from 'shared/warningWithoutStack';
|
||||
|
||||
const EVENT_POOL_SIZE = 10;
|
||||
|
||||
@@ -283,17 +282,17 @@ function getPooledWarningPropertyDefinition(propName, getVal) {
|
||||
}
|
||||
|
||||
function warn(action, result) {
|
||||
const warningCondition = false;
|
||||
warningWithoutStack(
|
||||
warningCondition,
|
||||
"This synthetic event is reused for performance reasons. If you're seeing this, " +
|
||||
"you're %s `%s` on a released/nullified synthetic event. %s. " +
|
||||
'If you must keep the original synthetic event around, use event.persist(). ' +
|
||||
'See https://fb.me/react-event-pooling for more information.',
|
||||
action,
|
||||
propName,
|
||||
result,
|
||||
);
|
||||
if (__DEV__) {
|
||||
console.error(
|
||||
"This synthetic event is reused for performance reasons. If you're seeing this, " +
|
||||
"you're %s `%s` on a released/nullified synthetic event. %s. " +
|
||||
'If you must keep the original synthetic event around, use event.persist(). ' +
|
||||
'See https://fb.me/react-event-pooling for more information.',
|
||||
action,
|
||||
propName,
|
||||
result,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -79,12 +79,12 @@ const _touchConfig = function(
|
||||
topType === 'topTouchStart'
|
||||
? allTouchObjects
|
||||
: topType === 'topTouchMove'
|
||||
? allTouchObjects
|
||||
: topType === 'topTouchEnd'
|
||||
? antiSubsequence(allTouchObjects, changedIndices)
|
||||
: topType === 'topTouchCancel'
|
||||
? antiSubsequence(allTouchObjects, changedIndices)
|
||||
: null;
|
||||
? allTouchObjects
|
||||
: topType === 'topTouchEnd'
|
||||
? antiSubsequence(allTouchObjects, changedIndices)
|
||||
: topType === 'topTouchCancel'
|
||||
? antiSubsequence(allTouchObjects, changedIndices)
|
||||
: null;
|
||||
|
||||
return {
|
||||
nativeEvent: touchEvent(
|
||||
|
||||
@@ -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.10.2",
|
||||
"version": "16.12.0",
|
||||
"main": "index.js",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -27,7 +27,7 @@
|
||||
"loose-envify": "^1.1.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"prop-types": "^15.6.2",
|
||||
"scheduler": "^0.16.2"
|
||||
"scheduler": "^0.18.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.0.0"
|
||||
|
||||
8
packages/react-art/src/ReactARTHostConfig.js
vendored
8
packages/react-art/src/ReactARTHostConfig.js
vendored
@@ -426,7 +426,7 @@ export function unhideTextInstance(textInstance, text): void {
|
||||
// Noop
|
||||
}
|
||||
|
||||
export function mountResponderInstance(
|
||||
export function DEPRECATED_mountResponderInstance(
|
||||
responder: ReactEventResponder<any, any>,
|
||||
responderInstance: ReactEventResponderInstance<any, any>,
|
||||
props: Object,
|
||||
@@ -436,7 +436,7 @@ export function mountResponderInstance(
|
||||
throw new Error('Not yet implemented.');
|
||||
}
|
||||
|
||||
export function unmountResponderInstance(
|
||||
export function DEPRECATED_unmountResponderInstance(
|
||||
responderInstance: ReactEventResponderInstance<any, any>,
|
||||
): void {
|
||||
throw new Error('Not yet implemented.');
|
||||
@@ -465,3 +465,7 @@ export function unmountFundamentalComponent(fundamentalInstance) {
|
||||
export function getInstanceFromNode(node) {
|
||||
throw new Error('Not yet implemented.');
|
||||
}
|
||||
|
||||
export function beforeRemoveInstance(instance) {
|
||||
// noop
|
||||
}
|
||||
|
||||
@@ -214,7 +214,9 @@ describe('ReactART', () => {
|
||||
const chars = this.props.chars.split('');
|
||||
return (
|
||||
<Surface>
|
||||
{chars.map(text => <Shape key={text} title={text} />)}
|
||||
{chars.map(text => (
|
||||
<Shape key={text} title={text} />
|
||||
))}
|
||||
</Surface>
|
||||
);
|
||||
}
|
||||
@@ -420,7 +422,7 @@ describe('ReactARTComponents', () => {
|
||||
ReactTestRenderer.create(
|
||||
<Circle stroke="green" strokeWidth={3} fill="blue" />,
|
||||
),
|
||||
).toWarnDev(
|
||||
).toErrorDev(
|
||||
'Warning: Failed prop type: The prop `radius` is marked as required in `Circle`, ' +
|
||||
'but its value is `undefined`.' +
|
||||
'\n in Circle (at **)',
|
||||
@@ -437,7 +439,7 @@ describe('ReactARTComponents', () => {
|
||||
it('should warn if width/height is missing on a Rectangle component', () => {
|
||||
expect(() =>
|
||||
ReactTestRenderer.create(<Rectangle stroke="green" fill="blue" />),
|
||||
).toWarnDev([
|
||||
).toErrorDev([
|
||||
'Warning: Failed prop type: The prop `width` is marked as required in `Rectangle`, ' +
|
||||
'but its value is `undefined`.' +
|
||||
'\n in Rectangle (at **)',
|
||||
@@ -462,7 +464,7 @@ describe('ReactARTComponents', () => {
|
||||
});
|
||||
|
||||
it('should warn if outerRadius/startAngle/endAngle is missing on a Wedge component', () => {
|
||||
expect(() => ReactTestRenderer.create(<Wedge fill="blue" />)).toWarnDev([
|
||||
expect(() => ReactTestRenderer.create(<Wedge fill="blue" />)).toErrorDev([
|
||||
'Warning: Failed prop type: The prop `outerRadius` is marked as required in `Wedge`, ' +
|
||||
'but its value is `undefined`.' +
|
||||
'\n in Wedge (at **)',
|
||||
|
||||
34
packages/react-cache/src/ReactCache.js
vendored
34
packages/react-cache/src/ReactCache.js
vendored
@@ -8,17 +8,15 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import warningWithoutStack from 'shared/warningWithoutStack';
|
||||
|
||||
import {createLRU} from './LRU';
|
||||
|
||||
type Thenable<T> = {
|
||||
then(resolve: (T) => mixed, reject: (mixed) => mixed): mixed,
|
||||
...
|
||||
};
|
||||
|
||||
type Suspender = {
|
||||
then(resolve: () => mixed, reject: () => mixed): mixed,
|
||||
};
|
||||
type Suspender = {then(resolve: () => mixed, reject: () => mixed): mixed, ...};
|
||||
|
||||
type PendingResult = {|
|
||||
status: 0,
|
||||
@@ -40,6 +38,7 @@ type Result<V> = PendingResult | ResolvedResult<V> | RejectedResult;
|
||||
type Resource<I, V> = {
|
||||
read(I): V,
|
||||
preload(I): void,
|
||||
...
|
||||
};
|
||||
|
||||
const Pending = 0;
|
||||
@@ -64,18 +63,21 @@ function readContext(Context, observedBits) {
|
||||
|
||||
function identityHashFn(input) {
|
||||
if (__DEV__) {
|
||||
warningWithoutStack(
|
||||
typeof input === 'string' ||
|
||||
typeof input === 'number' ||
|
||||
typeof input === 'boolean' ||
|
||||
input === undefined ||
|
||||
input === null,
|
||||
'Invalid key type. Expected a string, number, symbol, or boolean, ' +
|
||||
'but instead received: %s' +
|
||||
'\n\nTo use non-primitive values as keys, you must pass a hash ' +
|
||||
'function as the second argument to createResource().',
|
||||
input,
|
||||
);
|
||||
if (
|
||||
typeof input !== 'string' &&
|
||||
typeof input !== 'number' &&
|
||||
typeof input !== 'boolean' &&
|
||||
input !== undefined &&
|
||||
input !== null
|
||||
) {
|
||||
console.error(
|
||||
'Invalid key type. Expected a string, number, symbol, or boolean, ' +
|
||||
'but instead received: %s' +
|
||||
'\n\nTo use non-primitive values as keys, you must pass a hash ' +
|
||||
'function as the second argument to createResource().',
|
||||
input,
|
||||
);
|
||||
}
|
||||
}
|
||||
return input;
|
||||
}
|
||||
|
||||
@@ -33,46 +33,53 @@ describe('ReactCache', () => {
|
||||
ReactTestRenderer = require('react-test-renderer');
|
||||
Scheduler = require('scheduler');
|
||||
|
||||
TextResource = createResource(([text, ms = 0]) => {
|
||||
let listeners = null;
|
||||
let status = 'pending';
|
||||
let value = null;
|
||||
return {
|
||||
then(resolve, reject) {
|
||||
switch (status) {
|
||||
case 'pending': {
|
||||
if (listeners === null) {
|
||||
listeners = [{resolve, reject}];
|
||||
setTimeout(() => {
|
||||
if (textResourceShouldFail) {
|
||||
Scheduler.unstable_yieldValue(`Promise rejected [${text}]`);
|
||||
status = 'rejected';
|
||||
value = new Error('Failed to load: ' + text);
|
||||
listeners.forEach(listener => listener.reject(value));
|
||||
} else {
|
||||
Scheduler.unstable_yieldValue(`Promise resolved [${text}]`);
|
||||
status = 'resolved';
|
||||
value = text;
|
||||
listeners.forEach(listener => listener.resolve(value));
|
||||
}
|
||||
}, ms);
|
||||
} else {
|
||||
listeners.push({resolve, reject});
|
||||
TextResource = createResource(
|
||||
([text, ms = 0]) => {
|
||||
let listeners = null;
|
||||
let status = 'pending';
|
||||
let value = null;
|
||||
return {
|
||||
then(resolve, reject) {
|
||||
switch (status) {
|
||||
case 'pending': {
|
||||
if (listeners === null) {
|
||||
listeners = [{resolve, reject}];
|
||||
setTimeout(() => {
|
||||
if (textResourceShouldFail) {
|
||||
Scheduler.unstable_yieldValue(
|
||||
`Promise rejected [${text}]`,
|
||||
);
|
||||
status = 'rejected';
|
||||
value = new Error('Failed to load: ' + text);
|
||||
listeners.forEach(listener => listener.reject(value));
|
||||
} else {
|
||||
Scheduler.unstable_yieldValue(
|
||||
`Promise resolved [${text}]`,
|
||||
);
|
||||
status = 'resolved';
|
||||
value = text;
|
||||
listeners.forEach(listener => listener.resolve(value));
|
||||
}
|
||||
}, ms);
|
||||
} else {
|
||||
listeners.push({resolve, reject});
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'resolved': {
|
||||
resolve(value);
|
||||
break;
|
||||
}
|
||||
case 'rejected': {
|
||||
reject(value);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'resolved': {
|
||||
resolve(value);
|
||||
break;
|
||||
}
|
||||
case 'rejected': {
|
||||
reject(value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
}, ([text, ms]) => text);
|
||||
},
|
||||
};
|
||||
},
|
||||
([text, ms]) => text,
|
||||
);
|
||||
|
||||
textResourceShouldFail = false;
|
||||
});
|
||||
@@ -172,15 +179,12 @@ describe('ReactCache', () => {
|
||||
if (__DEV__) {
|
||||
expect(() => {
|
||||
expect(Scheduler).toFlushAndYield(['App', 'Loading...']);
|
||||
}).toWarnDev(
|
||||
[
|
||||
'Invalid key type. Expected a string, number, symbol, or ' +
|
||||
'boolean, but instead received: Hi,100\n\n' +
|
||||
'To use non-primitive values as keys, you must pass a hash ' +
|
||||
'function as the second argument to createResource().',
|
||||
],
|
||||
{withoutStack: true},
|
||||
);
|
||||
}).toErrorDev([
|
||||
'Invalid key type. Expected a string, number, symbol, or ' +
|
||||
'boolean, but instead received: Hi,100\n\n' +
|
||||
'To use non-primitive values as keys, you must pass a hash ' +
|
||||
'function as the second argument to createResource().',
|
||||
]);
|
||||
} else {
|
||||
expect(Scheduler).toFlushAndYield(['App', 'Loading...']);
|
||||
}
|
||||
@@ -296,26 +300,29 @@ describe('ReactCache', () => {
|
||||
|
||||
it('if a thenable resolves multiple times, does not update the first cached value', () => {
|
||||
let resolveThenable;
|
||||
const BadTextResource = createResource(([text, ms = 0]) => {
|
||||
let listeners = null;
|
||||
let value = null;
|
||||
return {
|
||||
then(resolve, reject) {
|
||||
if (value !== null) {
|
||||
resolve(value);
|
||||
} else {
|
||||
if (listeners === null) {
|
||||
listeners = [resolve];
|
||||
resolveThenable = v => {
|
||||
listeners.forEach(listener => listener(v));
|
||||
};
|
||||
const BadTextResource = createResource(
|
||||
([text, ms = 0]) => {
|
||||
let listeners = null;
|
||||
let value = null;
|
||||
return {
|
||||
then(resolve, reject) {
|
||||
if (value !== null) {
|
||||
resolve(value);
|
||||
} else {
|
||||
listeners.push(resolve);
|
||||
if (listeners === null) {
|
||||
listeners = [resolve];
|
||||
resolveThenable = v => {
|
||||
listeners.forEach(listener => listener(v));
|
||||
};
|
||||
} else {
|
||||
listeners.push(resolve);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
}, ([text, ms]) => text);
|
||||
},
|
||||
};
|
||||
},
|
||||
([text, ms]) => text,
|
||||
);
|
||||
|
||||
function BadAsyncText(props) {
|
||||
const text = props.text;
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
SimpleMemoComponent,
|
||||
ContextProvider,
|
||||
ForwardRef,
|
||||
Chunk,
|
||||
} from 'shared/ReactWorkTags';
|
||||
|
||||
type CurrentDispatcherRef = typeof ReactSharedInternals.ReactCurrentDispatcher;
|
||||
@@ -35,6 +36,7 @@ type HookLogEntry = {
|
||||
primitive: string,
|
||||
stackError: Error,
|
||||
value: mixed,
|
||||
...
|
||||
};
|
||||
|
||||
let hookLog: Array<HookLogEntry> = [];
|
||||
@@ -115,8 +117,8 @@ function useState<S>(
|
||||
hook !== null
|
||||
? hook.memoizedState
|
||||
: typeof initialState === 'function'
|
||||
? initialState()
|
||||
: initialState;
|
||||
? initialState()
|
||||
: initialState;
|
||||
hookLog.push({primitive: 'State', stackError: new Error(), value: state});
|
||||
return [state, (action: BasicStateAction<S>) => {}];
|
||||
}
|
||||
@@ -141,7 +143,7 @@ function useReducer<S, I, A>(
|
||||
return [state, (action: A) => {}];
|
||||
}
|
||||
|
||||
function useRef<T>(initialValue: T): {current: T} {
|
||||
function useRef<T>(initialValue: T): {|current: T|} {
|
||||
let hook = nextHook();
|
||||
let ref = hook !== null ? hook.memoizedState : {current: initialValue};
|
||||
hookLog.push({
|
||||
@@ -173,7 +175,7 @@ function useEffect(
|
||||
}
|
||||
|
||||
function useImperativeHandle<T>(
|
||||
ref: {current: T | null} | ((inst: T | null) => mixed) | null | void,
|
||||
ref: {|current: T | null|} | ((inst: T | null) => mixed) | null | void,
|
||||
create: () => T,
|
||||
inputs: Array<mixed> | void | null,
|
||||
): void {
|
||||
@@ -284,6 +286,7 @@ export type HooksNode = {
|
||||
name: string,
|
||||
value: mixed,
|
||||
subHooks: Array<HooksNode>,
|
||||
...
|
||||
};
|
||||
export type HooksTree = Array<HooksNode>;
|
||||
|
||||
@@ -623,7 +626,8 @@ export function inspectHooksOfFiber(
|
||||
if (
|
||||
fiber.tag !== FunctionComponent &&
|
||||
fiber.tag !== SimpleMemoComponent &&
|
||||
fiber.tag !== ForwardRef
|
||||
fiber.tag !== ForwardRef &&
|
||||
fiber.tag !== Chunk
|
||||
) {
|
||||
throw new Error(
|
||||
'Unknown Fiber. Needs to be a function component to inspect hooks.',
|
||||
|
||||
@@ -180,7 +180,7 @@ describe('React hooks DevTools integration', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('should support overriding suspense in sync mode', () => {
|
||||
it('should support overriding suspense in legacy mode', () => {
|
||||
if (__DEV__) {
|
||||
// Lock the first render
|
||||
setSuspenseHandler(() => true);
|
||||
|
||||
@@ -17,19 +17,19 @@ describe('ReactHooksInspection', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
const ReactFeatureFlags = require('shared/ReactFeatureFlags');
|
||||
ReactFeatureFlags.enableFlareAPI = true;
|
||||
ReactFeatureFlags.enableDeprecatedFlareAPI = true;
|
||||
React = require('react');
|
||||
ReactDebugTools = require('react-debug-tools');
|
||||
});
|
||||
|
||||
it('should inspect a simple useResponder hook', () => {
|
||||
const TestResponder = React.unstable_createResponder('TestResponder', {});
|
||||
const TestResponder = React.DEPRECATED_createResponder('TestResponder', {});
|
||||
|
||||
function Foo(props) {
|
||||
const listener = React.unstable_useResponder(TestResponder, {
|
||||
const listener = React.DEPRECATED_useResponder(TestResponder, {
|
||||
preventDefault: false,
|
||||
});
|
||||
return <div listeners={listener}>Hello world</div>;
|
||||
return <div DEPRECATED_flareListeners={listener}>Hello world</div>;
|
||||
}
|
||||
let tree = ReactDebugTools.inspectHooks(Foo, {});
|
||||
expect(tree).toEqual([
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "react-devtools-core",
|
||||
"version": "4.2.0",
|
||||
"version": "4.4.0",
|
||||
"description": "Use react-devtools outside of the browser",
|
||||
"license": "MIT",
|
||||
"main": "./dist/backend.js",
|
||||
|
||||
10
packages/react-devtools-core/src/backend.js
vendored
10
packages/react-devtools-core/src/backend.js
vendored
@@ -27,15 +27,14 @@ type ConnectOptions = {
|
||||
resolveRNStyle?: ResolveNativeStyle,
|
||||
isAppActive?: () => boolean,
|
||||
websocket?: ?WebSocket,
|
||||
...
|
||||
};
|
||||
|
||||
installHook(window);
|
||||
|
||||
const hook: DevToolsHook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__;
|
||||
|
||||
let savedComponentFilters: Array<
|
||||
ComponentFilter,
|
||||
> = getDefaultComponentFilters();
|
||||
let savedComponentFilters: Array<ComponentFilter> = getDefaultComponentFilters();
|
||||
|
||||
function debug(methodName: string, ...args) {
|
||||
if (__DEBUG__) {
|
||||
@@ -56,8 +55,7 @@ export function connectToDevTools(options: ?ConnectOptions) {
|
||||
websocket,
|
||||
resolveRNStyle = null,
|
||||
isAppActive = () => true,
|
||||
} =
|
||||
options || {};
|
||||
} = options || {};
|
||||
|
||||
let retryTimeoutID: TimeoutID | null = null;
|
||||
|
||||
@@ -123,7 +121,7 @@ export function connectToDevTools(options: ?ConnectOptions) {
|
||||
});
|
||||
bridge.addListener(
|
||||
'inspectElement',
|
||||
({id, rendererID}: {id: number, rendererID: number}) => {
|
||||
({id, rendererID}: {id: number, rendererID: number, ...}) => {
|
||||
const renderer = agent.rendererInterfaces[rendererID];
|
||||
if (renderer != null) {
|
||||
// Send event for RN to highlight.
|
||||
|
||||
22
packages/react-devtools-core/src/standalone.js
vendored
22
packages/react-devtools-core/src/standalone.js
vendored
@@ -143,9 +143,27 @@ function onError({code, message}) {
|
||||
safeUnmount();
|
||||
|
||||
if (code === 'EADDRINUSE') {
|
||||
node.innerHTML = `<div id="waiting"><h2>Another instance of DevTools is running</h2></div>`;
|
||||
node.innerHTML = `
|
||||
<div class="box">
|
||||
<div class="box-header">
|
||||
Another instance of DevTools is running.
|
||||
</div>
|
||||
<div class="box-content">
|
||||
Only one copy of DevTools can be used at a time.
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
node.innerHTML = `<div id="waiting"><h2>Unknown error (${message})</h2></div>`;
|
||||
node.innerHTML = `
|
||||
<div class="box">
|
||||
<div class="box-header">
|
||||
Unknown error
|
||||
</div>
|
||||
<div class="box-content">
|
||||
${message}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const {resolve} = require('path');
|
||||
const {DefinePlugin} = require('webpack');
|
||||
const {
|
||||
getGitHubURL,
|
||||
GITHUB_URL,
|
||||
getVersionString,
|
||||
} = require('react-devtools-extensions/utils');
|
||||
|
||||
@@ -15,7 +15,6 @@ const builtModulesDir = resolve(__dirname, '..', '..', 'build', 'node_modules');
|
||||
|
||||
const __DEV__ = NODE_ENV === 'development';
|
||||
|
||||
const GITHUB_URL = getGitHubURL();
|
||||
const DEVTOOLS_VERSION = getVersionString();
|
||||
|
||||
module.exports = {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const {resolve} = require('path');
|
||||
const {DefinePlugin} = require('webpack');
|
||||
const {
|
||||
getGitHubURL,
|
||||
GITHUB_URL,
|
||||
getVersionString,
|
||||
} = require('react-devtools-extensions/utils');
|
||||
|
||||
@@ -15,7 +15,6 @@ const builtModulesDir = resolve(__dirname, '..', '..', 'build', 'node_modules');
|
||||
|
||||
const __DEV__ = NODE_ENV === 'development';
|
||||
|
||||
const GITHUB_URL = getGitHubURL();
|
||||
const DEVTOOLS_VERSION = getVersionString();
|
||||
|
||||
module.exports = {
|
||||
|
||||
@@ -6,16 +6,26 @@ The easiest way to install this extension is as a browser add-on:
|
||||
* [Chrome web store](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi?hl=en)
|
||||
* [Firefox Add-ons](https://addons.mozilla.org/en-US/firefox/addon/react-devtools/)
|
||||
|
||||
## Development
|
||||
|
||||
## Local development
|
||||
You can also build and install this extension from source.
|
||||
|
||||
DevTools embeds local versions of several NPM packages also in this workspae. If you have not already built them, you'll need to do that before getting started by running the following command in the root directory of this repository:
|
||||
```sh
|
||||
yarn build -- react,react-dom,react-is,scheduler --type=NODE
|
||||
```
|
||||
### Prerequisite steps
|
||||
DevTools depends on local versions of several NPM packages<sup>1</sup> also in this workspace. You'll need to either build or download those packages first.
|
||||
|
||||
Once the above packages have been built, you can build the extension by running:
|
||||
<sup>1</sup> Note that at this time, an _experimental_ build is required because DevTools depends on the `createRoot` API.
|
||||
|
||||
#### Build from source
|
||||
To build dependencies from source, run the following command from the root of the repository:
|
||||
```sh
|
||||
yarn build-for-devtools
|
||||
```
|
||||
#### Download from CI
|
||||
To use the latest build from CI, run the following command from the root of the repository:
|
||||
```sh
|
||||
./scripts/release/download-experimental-build.js
|
||||
```
|
||||
### Build steps
|
||||
Once the above packages have been built or downloaded, you can build the extension by running:
|
||||
```sh
|
||||
cd packages/react-devtools-extensions/
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user