Compare commits

..

193 Commits

Author SHA1 Message Date
Luna Ruan
628f6f50b5 Portals trigger Suspense boundaries in SSR and only render on client 2020-02-04 16:00:59 -08:00
Luna Ruan
9b78e66d94 Portals trigger Suspense boundaries in SSR and only render on client 2020-02-04 15:54:15 -08:00
Luna Ruan
64f636b437 Portals trigger Suspense boundaries in SSR and only render on client 2020-02-04 15:46:54 -08:00
Bogdan
29b4d0721c Allow debug-test commands to run on Windows (#17122)
Fix #17121
2020-01-17 10:20:09 -08:00
Dominic Gannaway
b43b36dd91 Remove ReactInstanceMap_DO_NOT_USE shim (#17861) 2020-01-17 12:37:55 +00:00
Dominic Gannaway
9e075d16b2 [react-interactions] Remove deprecated Scope APIs + update Focus components/docs (#17859) 2020-01-17 12:16:39 +00:00
Brian Vaughn
bc1f3e1a69 Fix DevTools Flow error for key-sort function (#17849) 2020-01-15 09:45:16 -08:00
Dominic Gannaway
b6173e643a [react-interactions] Add DO_NOT_USE to Scope methods (#17835) 2020-01-15 09:50:09 +00:00
Brian Vaughn
8aefb1995c Removed 'reactInternal' reference in DevTools overlay highlighter (#17841) 2020-01-14 15:38:09 -08:00
Jason Williams
643dcb5526 Add support for Node v13 for development (#17837) 2020-01-14 23:20:42 +00:00
Brian Vaughn
1e1a989422 Re-enabled DevTools context menu option in Firefox to inspect function prop source (#17838) 2020-01-14 14:00:28 -08:00
Nicolas Gallagher
3bd6adceda Update typescript package and jest preprocessor (#17839) 2020-01-14 13:52:52 -08:00
Nicolas Gallagher
50eafef07e Update babel-eslint and eslint packages (#17829) 2020-01-14 09:26:44 -08:00
André
4f480d0645 Fix typo in TestCase description (#17077) 2020-01-12 11:54:12 -08:00
Dan
f4ca909e0e react-refresh@0.7.2 2020-01-12 17:54:30 +00:00
Dan Abramov
255d9ac5f5 [Fresh] Fix edge case with early function call (#17824) 2020-01-12 17:53:50 +00:00
Nicolas Gallagher
64aae7b06f Fix issue template for questions (#17817) 2020-01-10 10:57:34 -08:00
Emily
d8570ec6ea Create ReactFabric.stopSurface and use that for bridgeless mode binding (#16164) 2020-01-10 10:21:32 -08:00
Nicolas Gallagher
05e487a12f Add GitHub issue templates (#17815) 2020-01-09 16:16:37 -08:00
Ganapati V S
5d6129b5d1 Update ReactSuspenseList-test.internal.js (#17034)
Typo
2020-01-09 14:14:00 -08:00
Dawid Loranc
ff900095b9 Fix message typo in printOperationsArray function (#17039) 2020-01-09 14:13:03 -08:00
Alican Çubukçuoğlu
1b6e269751 Create SECURITY.md (#15784)
Adds SECURITY.md as mentioned in #15722.
2020-01-09 14:07:41 -08:00
Tao
3ac81a57fb Update create-react-app note for eslint-plugin-react-hooks (#16982)
eslint-plugin-react-hooks was added in react-scripts 3.0.0
2020-01-09 13:47:05 -08:00
Brian Vaughn
435259f189 Add better test coverage for hooks in DevTools Profiler's change-detection (#17816) 2020-01-09 13:15:19 -08:00
Nicolas Gallagher
2e4948a34d Add configuration for the "Stale" GitHub App (#17806)
Configuration for the bot that marks and closes stale issues and pull requests.
https://probot.github.io/apps/stale/
2020-01-09 11:13:41 -08:00
Dan Abramov
e706721490 Update Flow to 0.84 (#17805)
* Update Flow to 0.84

* Fix violations

* Use inexact object syntax in files from fbsource

* Fix warning extraction to use a modern parser

* Codemod inexact objects to new syntax

* Tighten types that can be exact

* Revert unintentional formatting changes from codemod
2020-01-09 14:50:44 +00:00
Dan Abramov
b979db4e72 Bump Prettier (#17811)
* Bump Prettier

* Reformat

* Use non-deprecated option
2020-01-09 13:54:11 +00:00
Sebastian Markbåge
18875b2401 Remove special casing of toString values when enableTrustedTypesIntegration (#17774)
* Remove toString of dangerouslySetInnerHTML

As far as I can tell, the toString call was added here:

caae627cd5 (diff-5574f655d491348f422bca600ff6711dR887)

It was never really needed. Subsequently when we added Trusted Types,
this needed to be changed to a special call but we really should just
always let it pass through.

* Remove special casing of toString values when enableTrustedTypesIntegration

As far as I can tell, we only toString in user space because of IE8/9.

We don't really support IE8/9 anymore and by the time this flag is on,
we should be able to deprecate it.

Unless this is also an issue in IE11. I haven't tested yet.
2020-01-08 14:40:07 -08:00
Eli White
19f6fe170c Revert "Revert "Dispatch commands to both UIManagers from both renderers (#17211)" (#17232)" (#17799)
* Revert "Revert "Dispatch commands to both UIManagers from both renderers (#17211)" (#17232)"

This reverts commit d0fc0ba0a6.

* Clean up another __DEV__ warning check
2020-01-07 16:47:19 -08:00
Brian Vaughn
6250462bed Renamed "ReactDOM-fb" imports to "ReactDOM" in www shims (#17797)
* Renamed "ReactDOM-fb" imports to "ReactDOM" in www shims

* Removed ReactDOMComponentTree_DO_NOT_USE.js
2020-01-07 13:05:36 -08:00
Dominic Gannaway
5e21157d86 [react-interactions] Fix Hover issues with portals (#17765) 2020-01-07 19:45:26 +00:00
Brian Vaughn
40568e9773 Updated DevTools CHANGELOG to add pending release details 2020-01-06 10:37:20 -08:00
Brian Vaughn
5586b3022c Merge branch 'GasimGasimzada-scope-devtools-styles' 2020-01-06 10:36:13 -08:00
Brian Vaughn
7580d6717c Fixed padding problem in standalone shell 2020-01-06 10:35:36 -08:00
Brian Vaughn
80e32487b9 Fixed box-sizing problem in test shell 2020-01-06 10:23:55 -08:00
Brian Vaughn
c1b573bf2f Merge branch 'scope-devtools-styles' of https://github.com/GasimGasimzada/react into GasimGasimzada-scope-devtools-styles 2020-01-06 10:13:35 -08:00
Brian Vaughn
fc921a1693 Updated DevTools CHANGELOG to add pending release details 2020-01-06 10:12:26 -08:00
Brian Vaughn
1c63b2e416 Renamed "backend.js" to "react_devtools_backend.js" (#17790) 2020-01-06 09:29:20 -08:00
Brian Vaughn
2bb227ef80 DevTools: Improve function props display for inspected elements (#17789) 2020-01-06 09:19:59 -08:00
Brian Vaughn
24f824250f DevTools Profiler: Improve how empty commits are filtered (#17771)
Previously, DevTools filtered empty commits on the backend, while profiling, through the use of a bailout heuristic that React currently happens to use. This approach was brittle and may have exacerbated the long-standing Profiler bug #16446.

This PR removes that heuristic and adds as a post-processing filtering pass instead. This removes the coupling between DevTools and a React internal implementation detail that may change.

I believe DevTools has two choices of criteria for this filtering:
* Filter commits that have no actual duration metadata.
* Filter commits that have no recorded operations (no mutations to the tree, no changed tree base durations).

I chose the first option, filtering by commits that have no reported metadata. It will miss an edge case, e.g. , but we would have nothing meaningful to show in the Profiler for those cases anyway. (This particular edge case is why one of the snapshots changed with this commit.)

The second option, filtering by recorded operations, could potentially miss a more important edge case: where a component *did* render, but its  didn't change. (In that event, there would be no operations to send.)
2020-01-04 16:50:39 -08:00
Sebastian Markbåge
edeea07207 Remove toString of dangerouslySetInnerHTML (#17773)
As far as I can tell, the toString call was added here:

caae627cd5 (diff-5574f655d491348f422bca600ff6711dR887)

It was never really needed. Subsequently when we added Trusted Types,
this needed to be changed to a special call but we really should just
always let it pass through.
2020-01-04 05:47:23 -08:00
Gasim Gasimzada
9cc094a19a Scope dev tools wildcard styles within DevTools CSS class 2020-01-04 14:04:15 +04:00
Brian Vaughn
f749045a5b DevTools 4.3.0 -> 4.4.0 2020-01-03 10:02:50 -08:00
Sebastian Markbåge
59f21f1b24 HostText needs to copy over from current if it is unchanged in persistent mode (#17538)
stateNode is not one of the fields that gets copied over by createWorkInProgress.

This is bad for persistent mode since it's not the same instance.

In HostComponent we happened to always end up transferring the current stateNode when we bail.
However, we didn't do this for HostText.
2020-01-03 17:35:17 +00:00
Brian Vaughn
7e2ab87a61 DevTools: Replaced unsafe hasOwnProperty() calls (#17768)
DevTools previously called  in several places with user-defined values. This could lead to runtime errors if those values had an overriden  attribute. This commit replaces those callse with  instead.

New test cases have been added.
2020-01-03 09:34:12 -08:00
Brian Vaughn
5d3d71b1dd Partial fix DevTools Profiler ''Could not find node…'' error (#17759)
The Profiler stores:

1. A snapshot of the React tree when profiling started
2. The operations array for each commit
3. Profiling metadata (e.g. durations, what changed, etc) for each commit

It uses this information (snapshot + operations diff) to reconstruct the state of the application for a given commit as it's viewed in the Profiler UI. Because of this, it's very important that the operations and metadata arrays align. If they don't align, the profiler will be unable to correctly reconstruct the tree, and it will likely throw errors (like 'Could not find node…')

#16446 tracks a long-standing bug where these two arrays get misaligned. I am still not entirely sure what causes this bug, but with PR #17253, I exacerbated things by introducing another potential way for it to happen. This PR addresses the regression at least (and adds test coverage for it).

I will follow up this afternoon on the original #16446 issue. I think I may have a lead on what's happening at least, if not exactly an idea of how to reproduce it.
2020-01-03 09:28:31 -08:00
Brian Vaughn
cca994c6a1 DevTools Profiler commit selector wraps around (#17760)
I used to disable the <- and -> buttons when you reached the beginning or end of the profiler data. This can be kind of annoying though when you just want to get to the last commit, and I don't think there's a good reason to enforce it anyway, so I backed that change out. The buttons now wrap around at the beginning or end of the list.
2020-01-02 14:44:07 -08:00
Brian Vaughn
b05cd611ba Fixed CircleCI DevTools build artifact tar internal structure (#17758) 2020-01-02 09:28:20 -08:00
Brian Vaughn
195b3db618 Added missing guard to DevTools for Objects with null proto (#17757)
This prevents a runtime error from occurring when these objects are inspected.
2020-01-02 08:27:29 -08:00
Brian Vaughn
2c1e5d2b22 Build and store DevTools artifacts in Circle CI (#17746) 2020-01-01 07:58:47 -08:00
Brian Vaughn
86883d7aac Updated DevTools pending CHANGELOG 2019-12-30 09:12:49 -08:00
dependabot[bot]
b4e2fac09d Bump handlebars from 4.0.11 to 4.5.3 in /fixtures/expiration (#17729)
Bumps [handlebars](https://github.com/wycats/handlebars.js) from 4.0.11 to 4.5.3.
- [Release notes](https://github.com/wycats/handlebars.js/releases)
- [Changelog](https://github.com/wycats/handlebars.js/blob/master/release-notes.md)
- [Commits](https://github.com/wycats/handlebars.js/compare/v4.0.11...v4.5.3)

Signed-off-by: dependabot[bot] <support@github.com>
2019-12-30 08:57:08 -08:00
dependabot[bot]
e6226e6493 Bump handlebars from 4.0.6 to 4.5.3 in /fixtures/dom (#17730)
Bumps [handlebars](https://github.com/wycats/handlebars.js) from 4.0.6 to 4.5.3.
- [Release notes](https://github.com/wycats/handlebars.js/releases)
- [Changelog](https://github.com/wycats/handlebars.js/blob/master/release-notes.md)
- [Commits](https://github.com/wycats/handlebars.js/compare/v4.0.6...v4.5.3)

Signed-off-by: dependabot[bot] <support@github.com>
2019-12-30 08:56:44 -08:00
dependabot[bot]
3bec713b85 Bump handlebars from 4.0.10 to 4.5.3 in /fixtures/attribute-behavior (#17728)
Bumps [handlebars](https://github.com/wycats/handlebars.js) from 4.0.10 to 4.5.3.
- [Release notes](https://github.com/wycats/handlebars.js/releases)
- [Changelog](https://github.com/wycats/handlebars.js/blob/master/release-notes.md)
- [Commits](https://github.com/wycats/handlebars.js/compare/v4.0.10...v4.5.3)

Signed-off-by: dependabot[bot] <support@github.com>
2019-12-30 08:56:11 -08:00
dependabot[bot]
ee38c00bd5 Bump handlebars from 4.0.6 to 4.5.3 in /fixtures/fiber-debugger (#17727)
Bumps [handlebars](https://github.com/wycats/handlebars.js) from 4.0.6 to 4.5.3.
- [Release notes](https://github.com/wycats/handlebars.js/releases)
- [Changelog](https://github.com/wycats/handlebars.js/blob/master/release-notes.md)
- [Commits](https://github.com/wycats/handlebars.js/compare/v4.0.6...v4.5.3)

Signed-off-by: dependabot[bot] <support@github.com>
2019-12-30 08:56:05 -08:00
dependabot[bot]
4d463fce4e Bump handlebars from 4.1.2 to 4.5.3 (#17732)
Bumps [handlebars](https://github.com/wycats/handlebars.js) from 4.1.2 to 4.5.3.
- [Release notes](https://github.com/wycats/handlebars.js/releases)
- [Changelog](https://github.com/wycats/handlebars.js/blob/master/release-notes.md)
- [Commits](https://github.com/wycats/handlebars.js/compare/v4.1.2...v4.5.3)

Signed-off-by: dependabot[bot] <support@github.com>
2019-12-30 08:46:41 -08:00
Karan Sharma
d259f45813 Fix various typos (#17724) 2019-12-29 13:47:51 -08:00
Chris Watson
9357a483e9 Fix for the standalone devtools not responding to clicks. (#17584) 2019-12-29 13:46:27 -08:00
Brian Vaughn
22ef96ae63 Devtools renable copy attr context menu for firefox (#17740)
* Use exportFunction() to share clipboard copy with JS running in document/page context.

* Remove no-longer-used option to disable copy operation.
2019-12-29 13:27:44 -08:00
Brian Vaughn
2b903da355 Make DevTools check document.contentType before injecting (#17739)
It should only inject the global hook into HTML documents. This will avoid breaking syntax highlighting for e.g. XML documents.
2019-12-29 13:02:50 -08:00
Brian Vaughn
0eac01abcd Added missing Flow type coverage to DevTools context menu (#17733)
The  param should probably be a generic type, but I'm not sure how to satisfy Flow with the current top-level  Map. At least this adds basic coverage (which was missing before, oops).
2019-12-29 08:44:24 -08:00
Brian Vaughn
f887d1aa27 [DevTools] Display RegExp values in props/state (#17690)
Previously, when props/state contained a regexp, it was shown as an
empty object. This commit adds regexps as values in need of special
rehydration (like Symbols or TypedArrays), and display them as a user
might expect.

Co-authored-by: Zirak <zirakertan@gmail.com>
2019-12-22 11:49:28 -08:00
Brian Vaughn
1959671d45 Add tooltip to interaction name in DevTools Profiler sidebar (#17689) 2019-12-22 08:52:53 -08:00
Nick S. Plekhanov
ccc6100d7f Fix comments typos (#17550)
* Fix comments typos

* Fix comments typos

* Fix more typos

Co-authored-by: Brian Vaughn <brian.david.vaughn@gmail.com>
2019-12-22 08:44:34 -08:00
Maxime Bouveron
6215e1c2db Add Shift+Enter to go to previous search result (#17005) 2019-12-22 08:38:14 -08:00
Dominic Gannaway
85d9655d6f [react-dom] Refactor event priority handling to its own module (#17678) 2019-12-20 23:15:36 +00:00
Brian Vaughn
3e09677836 DevTools v4.2.1 -> 4.3.0 2019-12-20 09:11:37 -08:00
Nicolas Gallagher
31350237fa Add unit tests to dom-event-testing-library (#17680)
Unit tests for the simple event mocks
2019-12-20 16:35:02 +00:00
Brian Vaughn
79734771cd Disable "copy to clipboard" and "go to definition" context menu options for Firefox addon (#17668) 2019-12-20 08:31:38 -08:00
Dominic Gannaway
dbc46ac63e [react-interactions] Rename test + fix master (#17679) 2019-12-20 16:00:39 +00:00
Brian Vaughn
1b9328cd9b Null stateNode after unmount (#17666) 2019-12-19 13:32:14 -08:00
Dan Abramov
897976600c [ESLint] Allow partial matches for custom Effect Hooks (#17663) 2019-12-19 17:04:17 +00:00
Nicolas Gallagher
72592310a8 Create packages/dom-event-testing-library (#17660)
Moves the unit testing library for events into the `packages` directory so it can more easily be used in tests for other react packages, and mirrored internally to help with testing of event hooks we prototype in www.
2019-12-19 16:51:25 +00:00
Dominic Gannaway
e7494c86c5 [react-interactions] Remove batchedUpdates from responder lifecycles (#17659) 2019-12-19 13:07:00 +00:00
Nicolas Gallagher
a5e951d4cc [react-interactions] Event testing library improvements (#17614)
Introduces a state machine around pointer events to produce more accurate mock
touch events. This allows multi-touch unit tests to be written entirely in
terms of mock pointer interactions, while producing the expected
'changedTouches', 'targetTouches', and 'touches' fields for mock touch events.
2019-12-18 23:32:48 +00:00
Brian Vaughn
95056b6836 DevTools build script enhancements (#17653)
* Updated DevTools local development instructions to mention experimental build step

* Added a command to download latest experimental release (for DevTools)

* Updated build instructions for clarity

* Added build-for-devtools package alias
2019-12-18 14:34:40 -08:00
Brian Vaughn
fd2a3ae57f Updated DevTools changelog 2019-12-18 12:51:18 -08:00
Brian Vaughn
933f6a07ca DevTools context menu (#17608)
* Added rudimentary context menu hook and menu UI

* Added backend support for copying a value at a specific path for the inspected element

* Added backend support for storing a value (at a specified path) as a global variable

* Added special casing to enable copying undefined/unserializable values to the clipboard

* Added copy and store-as-global context menu options to selected element props panel

* Store global variables separately, with auto-incremented name (like browsers do)

* Added tests for new copy and store-as-global backend functions

* Fixed some ownerDocument/contentWindow edge cases

* Refactored context menu to support dynamic options

Used this mechanism to add a conditional menu option for inspecting the current value (if it's a function)

* Renamed "safeSerialize" to "serializeToString" and added inline comment
2019-12-18 12:12:34 -08:00
Sebastian Markbåge
7dc9745427 [Flight] Chunks API (#17398)
* Add feature flags

* Add Chunk type and constructor

* Wire up Chunk support in the reconciler

* Update reconciler to reconcile Chunks against the render method

This allows the query and args to be updated.

* Drop the ref. Chunks cannot have refs anyway.

* Add Chunk checks in more missing cases

* Rename secondArg

* Add test and fix lazy chunks

Not really a supported use case but for consistency I guess.

* Fix fragment test
2019-12-18 18:25:43 +00:00
Eli White
9354dd2752 Make HostComponent inexact (#17412) 2019-12-18 18:21:42 +00:00
Sebastian Markbåge
4c270375e9 Favor fallthrough switch instead of case statements for work tags (#17648)
* Favor fallthrough switch instead of case statements for work tags

Currently we're inconsistently handling tags that are only relevant
for certain flags. We should throw if the tag is not part of the built
feature flags. This should also mean that the case statements can be
eliminated.

We can achieve this effect by putting the invariant outside of the switch
and always early return in the switch. We already do this in beginWork.
This PR makes this consistent in other places.

* Fail if fundamental/scope tags are discovered without the flag on
2019-12-18 15:53:27 +00:00
Dan Abramov
6fef7c47a9 Add a regression test for switching from Fragment to a component (#17647)
* Add a regression test for switching from Fragment to a component

* Add a few more tests
2019-12-18 15:21:42 +00:00
Dominic Gannaway
9fe1031244 [react-interactions] Rename Flare APIs to deprecated and remove from RN (#17644) 2019-12-18 10:24:46 +00:00
Brian Vaughn
e84327ba0c Added more forwardRef test cases for DevTools (#17642) 2019-12-17 12:45:10 -08:00
Dan Abramov
4b0cdf29a8 Build FB RN targets only in experimental mode (#17641) 2019-12-17 20:22:49 +00:00
zthxxx
24dbe851e8 fix(dev-tools): fix show correct displayName with forwardRef in Dev Tools (#17613)
* fix(dev-tools): fix show correct displayName with forwardRef in Dev Tools

allow set `displayName` after `React.forwardRef()`,
makesure Dev Tools show displayName as same as `getWrappedName` in `shared/getComponentName.js`

* Removed a little unnecessary/redundant code.

* Fixed lint error (removed unused var)
2019-12-17 11:29:16 -08:00
Brian Vaughn
bb75a4fa89 Improved selection and toggling for props/hooks/state (#17588)
1. Enable nested values to be expanded/collapsed by clicking on values as well as keys.
2. Enable keys and values to be selectable (for copy-pasting purposes)
2019-12-17 11:28:37 -08:00
伊撒尔
7309c5f934 Use zero-fill right shift instead of Math.floor (#17616)
For positive numbers, binary displacement is better.
2019-12-17 11:16:04 -08:00
Dan Abramov
9bb3fbe893 Move DevTools CI job to experimental workflow (#17640) 2019-12-17 18:21:38 +00:00
Dan Abramov
3c54df0914 Fix missing stacks in WWW warnings (#17638) 2019-12-17 15:21:18 +00:00
Dan Abramov
b66e86d957 react-refresh@0.7.1 2019-12-17 13:50:57 +00:00
Dan Abramov
c2d1561c60 [Fast Refresh] Support injecting runtime after renderer executes (#17633) 2019-12-17 13:49:39 +00:00
Dan Abramov
0253ee9a2e Additional test infra changes for toErrorDev rename (#17632) 2019-12-17 13:31:47 +00:00
Dan Abramov
36a6e29bb3 Fix test_build_devtools CI job to run test-build-devtools (#17631)
* Skip abandoned project folders in Jest config

This fixes a problem that occurs after renaming a package.

* Fix test_build_devtools to run test-build-devtools

* Exclude console.error plugin for DevTools packages

* Use correct release channel for DevTools tests

This should fix the createRoot error.

* Fix TZ dependent test

* Change DT job dependencies
2019-12-16 16:03:12 -08:00
Dan Abramov
7c21bf72ac Fix release script to ignore empty package folders 2019-12-16 19:16:58 +00:00
Dan Abramov
f42431abe1 Revert "Remove renderPhaseUpdates Map (#17484)" (#17623)
This reverts commit 8a347ed024.
2019-12-16 19:03:00 +00:00
Dan Abramov
0b5a26a489 Rename toWarnDev -> toErrorDev, toLowPriorityWarnDev -> toWarnDev (#17605)
* Rename toWarnDev -> toErrorDev in tests

* Rename toWarnDev matcher implementation to toErrorDev

* Rename toLowPriorityWarnDev -> toWarnDev in tests and implementation
2019-12-16 12:48:16 +00:00
Dan Abramov
0cf22a56a1 Use console directly instead of warning() modules (#17599)
* Replace all warning/lowPriWarning with console calls

* Replace console.warn/error with a custom wrapper at build time

* Fail the build for console.error/warn() where we can't read the stack
2019-12-14 18:09:25 +00:00
Dan Abramov
b6c423daad Use matching test command for equivalence tests (#17604) 2019-12-14 17:52:29 +00:00
Sebastian Markbåge
8a347ed024 Remove renderPhaseUpdates Map (#17484)
* Render phase updates can now be extracted from the pending queue

* Use a custom dispatcher for the second render pass

* Discard render phase updates if component throws

When aborting a render, we also need to throw out render phase updates.
Remove the updates from the queues so they do not persist to the next
render. We already did a single pass through the whole list of hooks, so
we know that any pending updates must have been dispatched during the
render phase. The ones that were dispatched before we started rendering
were already transferred to the current hook's queue.
2019-12-13 16:45:09 -08:00
Dominic Gannaway
be603f5a51 [react-events] Remove lastNativeEvent in favor of SystemFlags (#17585) 2019-12-13 17:03:35 +00:00
Dan Abramov
b15bf36750 Add component stacks to (almost) all warnings (#17586) 2019-12-12 23:47:55 +00:00
Nicolas Gallagher
612a76812f [react-interactions] Mock touchend events should use empty array for 'touches' (#17589)
The 'touches' value should be an empty array rather than 'null'
2019-12-12 22:21:30 +00:00
Brian Vaughn
12c000412d Improved inspected element props with inline previews (#17579)
* Improved inspected element props with inline previews

This mimics the inline preview shown by the brower console and dramatically improves the UX when inspecting deep values. I also updated tests to add more coverage for this new functionality.

* Cleaned up the DataView vs typed array check

* Added early bailouts to DevTools when generating preview strings for iterables/objects/arrays, to avoid doing unnecessary work
2019-12-11 17:52:17 -08:00
Dominic Gannaway
2afeebdcc4 [react-interactions] Remove responder root event types + revert commit phase change (#17577) 2019-12-11 16:48:46 +00:00
Laura buns
9ac42dd074 Remove the condition argument from warning() (#17568)
* prep for codemod

* prep warnings

* rename lint rules

* codemod for ifs

* shim www functions

* Handle more cases in the transform

* Thanks De Morgan

* Run the codemod

* Delete the transform

* Fix up confusing conditions manually

* Fix up www shims to match expected API

* Also check for low-pri warning in the lint rule
2019-12-11 03:28:14 +00:00
Andrew Clark
7bf40e1cfd Initialize update queue object on mount (#17560)
* Refactor Update Queues to Fix Rebasing Bug

Fixes a bug related to rebasing updates. Once an update has committed,
it should never un-commit, even if interrupted by a higher priority
update. The fix includes a refactor of how update queues work.

This commit is a combination of two PRs:

- #17483 by @sebmarkbage refactors the hook update queue
- #17510 by @acdlite refactors the class and root update queue

Landing one without the other would cause state updates to sometimes be
inconsistent across components, so I've combined them into a single
commit in case they need to be reverted.

Co-authored-by: Sebastian Markbåge <sema@fb.com>
Co-authored-by: Andrew Clark <git@andrewclark.io>

* Initialize update queue object on mount

Instead of lazily initializing update queue objects on the first update,
class and host root queues are created on mount. This simplifies the
logic for appending new updates and matches what we do for hooks.
2019-12-10 18:42:42 -06:00
Brian Vaughn
031a5aaffb Show component location for selected element in bottom/right of props panel (#17567)
* Show component location info for selected element in bottom/right of props panel

* Moved RegExp declaration into function basedon PR feedback
2019-12-10 09:24:34 -08:00
Andrew Clark
e039e690b5 Revert Update Queue Refactor
Reverts b617db3d96.

Found some bugs when attempting to land in www. Reverting to fix master.
I'll land again *after* the change successfully land downstream.
2019-12-09 15:16:09 -08:00
Andrew Clark
b617db3d96 Refactor Update Queues to Fix Rebasing Bug
Fixes a bug related to rebasing updates. Once an update has committed,
it should never un-commit, even if interrupted by a higher priority
update. The fix includes a refactor of how update queues work.

This commit is a combination of two PRs:

- #17483 by @sebmarkbage refactors the hook update queue
- #17510 by @acdlite refactors the class and root update queue

Landing one without the other would cause state updates to sometimes be
inconsistent across components, so I've combined them into a single
commit in case they need to be reverted.

Co-authored-by: Sebastian Markbåge <sema@fb.com>
Co-authored-by: Andrew Clark <git@andrewclark.io>
2019-12-09 13:19:26 -08:00
Dominic Gannaway
3c1efa0d77 [react-interactions] Remove Focus/FocusWithin root event types (#17555) 2019-12-09 14:27:45 +00:00
Nicolas Gallagher
9e937e7e86 [react-interactions] fix Press/Tap behavior for virtual middle clicks (#17554)
Tools like BetterTouchTool for macOS trigger middle-clicks with a 'buttons'
value that doesn't correspond to the middle-mouse button. To account for this
we also inspect the value of 'button'.

Close #17367
2019-12-09 13:04:24 +00:00
Laura buns
b43eec7eaa Replace wrap-warning-with-env-check with an eslint plugin (#17540)
* Replace Babel plugin with an ESLint plugin

* Fix ESLint rule violations

* Move shared conditions higher

* Test formatting nits

* Tweak ESLint rule

* Bugfix: inside else branch, 'if' tests are not satisfactory

* Use a stricter check for exactly if (__DEV__)

This makes it easier to see what's going on and matches dominant style in the codebase.

* Fix remaining files after stricter check
2019-12-06 18:25:54 +00:00
Dominic Gannaway
acfe4b21b2 [react-interactions] Upgrade passive event listeners to active listeners (#17513) 2019-12-04 19:30:50 +00:00
Farhad Yasir
5235d193d7 fix: make serializable data in react-devtools (#17233)
* fix: make serializable data for bridge in react-devtools

* fix: add bigint data type in hydration

* refactor: remove console.log

* test: update unit tests for bigint in react-devtools
2019-12-04 15:53:00 +00:00
Sebastian Markbåge
5064c7f6aa Revert Rerender Error Check (#17519)
* Add failing test

* Revert "Move rerender error check to avoid global state"

This reverts commit 3e77742d8c4e64b89f816c0b1ce0bc156f8c5f61.
2019-12-03 23:10:36 -08:00
Dominic Gannaway
6d105ad3f6 [react-interactions] Move Flare event registration to commit phase (#17518) 2019-12-04 02:08:20 +00:00
Sebastian Markbåge
dc18b8b8d2 Don't group Idle/Offscreen work with other work (#17456)
When we suspend we always try a lower level but we shouldn't try offscreen.
2019-12-03 13:38:02 -08:00
Sebastian Markbåge
f523b2e0d3 Use fewer global variables in Hooks (#17480)
* We don't need the global state for this

* Move componentUpdateQueue and sideEffectTag out of global state

* Move firstWorkInProgressHook off global state

* Move remainingExpirationTime off global state

* Reset fiber to its current state if it throws

* Move rerender error check to avoid global state

This means that it's harder to find it since it's not in the dispatch
function's stack but we can add a DEV only one for that if we really
need it. Alternatively, we can check it in against the renderUpdates queue.

* Move next___Hook out of global state

* Assert that currentlyRenderingFiber is always set

When accessed, this should always be set. This could enforced by storing
this on the dispatcher for example.

* Add another test just to be safe
2019-12-03 12:51:36 -08:00
Sebastian Markbåge
d75323f65d Remove case that only exists for createBatch (#17506)
The comment says this is only needed for createBatch().commit() which
doesn't exist anymore.
2019-12-03 10:35:17 -08:00
Sebastian Markbåge
79572e34d1 Adjust SuspenseList CPU bound heuristic (#17455)
* Adjust SuspenseList CPU bound heuristic

In SuspenseList we switch to rendering fallbacks (or stop rendering further
rows in the case of tail="collapsed/hidden") if it takes more than 500ms
to render the list. The limit of 500ms is similar to the train model and
designed to be short enough to be in the not noticeable range.

This works well if each row is small because we time the 500ms range well.
However, if we have a few large rows then we're likely to exceed the limit
by a lot. E.g. two 480ms rows hits almost a second instead of 500ms.

This PR adjusts the heuristic to instead compute whether something has
expired based on the render time of the last row. I.e. if we think rendering
one more row would exceed the timeout, then we don't attempt.

This still works well for small rows and bails earlier for large rows.

The expiration is still based on the start of the list rather than the
start of the render. It should probably be based on the start of the render
but that's a bigger change and needs some thought.

* Comment
2019-12-02 17:53:08 -08:00
Sung M. Kim
b64938e123 Fixed changelog formatting #17481 (#17487) 2019-11-29 13:57:38 +00:00
Dan Abramov
969f4b5bb8 Change DevTools hook warning message (#17478) 2019-11-28 00:47:13 +00:00
Brian Vaughn
3816ae7c38 DevTools version bump 4.2.0 -> 4.2.1 2019-11-27 07:49:49 -08:00
Brian Vaughn
5fc97420b9 Remove dependency on local git checkout from build process (#17475)
* Remove dependency on local git checkout from build process

* Updated Webpack dependencies to account for recent changes to deps for Flight
2019-11-27 07:48:09 -08:00
Dan Abramov
6470e0f169 [Fresh] Make all errors recoverable (#17438)
* [Fresh] Detect root updates more reliably

* [Fresh] Use WeakMap for root elements

* [Fresh] Make initial failures recoverable too

* Fix DevTools check

* Fix wrong flow type
2019-11-25 17:25:34 +00:00
Andrew Clark
54f66731c8 Update release script URL (#17428)
CircleCI API endpoint changed
2019-11-21 13:19:03 -08:00
Dan Abramov
237a966da0 [Fresh] Fix an infinite loop in an edge case (#17414)
* [Fresh] Fix an infinite loop in an edge case

* Make it work in IE11
2019-11-21 14:10:26 +00:00
Dominic Gannaway
007a276b65 [react-interactions] Fix memory leak in event responder system (#17421) 2019-11-21 13:15:37 +00:00
Dominic Gannaway
3fdfa231ad [react-interactions] Refine virtual click detection for FF+JAWS/NVDA (#17422) 2019-11-21 13:09:30 +00:00
Dominic Gannaway
a7d07ff24d [react-interactions] Rename Flare listeners prop to DEPRECATED_flareListeners (#17394) 2019-11-18 13:32:50 +00:00
Sebastian Markbåge
3ad076472c Update Fixtures to use new APIs (#17380)
Also renamed unstable-async to concurrent
2019-11-17 14:42:47 -08:00
Andrew Clark
1f2da0babd Forgot to mark test as experimental (#17391) 2019-11-17 13:59:25 -08:00
David Garner
a807c307c4 [eslint] Check forwardRef callbacks (#17255)
* [eslint] Check forwardRef callbacks (#17220)

* [eslint] Make tests more realistic (#17220)

* [eslint] Check anonymous callback of React.memo for rules-of-hooks (#17220)

* [eslint] Add tests for callbacks not known to be components (#17220)

* [eslint] Correct comments and add another test (#17220)
2019-11-17 13:39:08 +00:00
Andrew Clark
2586303662 [Bugfix] Pending state is always user-blocking (#17382)
Fixes a bug where `isPending` is only set to `true` if `startTransition`
is called from inside an input event. That's usually the case, but
not always.

Now it works regardless of where you call it.
2019-11-15 15:46:09 -08:00
Sebastian Markbåge
8e74a31b2d Add Webpack as devDependency (#17379)
This is now used by the Flight package so it needs to be installed as a
peer.
2019-11-15 14:02:59 -08:00
Sebastian Markbåge
39dbb14da3 [Flight] Move Flight DOM to a Webpack Specific Package (#17372)
* Move Flight DOM to Webpack Specific Packagee

We'll have Webpack specific coupling so we need to ensure that it can be
versioned separately from various Webpack versions. We'll also have builds
for other bundlers in the future.

* Move to peerDep

* Move DOM Flight Tests

* Merge ReactFlightIntegration into ReactFlightDOM

This was an integration test. We can add to it.

* Fix fixture paths
2019-11-15 11:46:07 -08:00
Dominic Gannaway
532810a370 [react-interactions] FocusWithin beforeblur propagation fix (#17375) 2019-11-15 12:05:00 +00:00
Dominic Gannaway
f30ccb9ed9 [react-interactions] Fix unatached fiber bug (#17371) 2019-11-15 00:47:33 +00:00
Luna Ruan
1832f832c5 Changelog for 16.12 2019-11-14 16:06:10 -08:00
Luna Ruan
9a5f28dbed update version numbers for 16.12 2019-11-14 16:02:18 -08:00
Ricky
769dd522a2 [Fast Refresh] Fix for intentional unmounts after an error (#17368) 2019-11-14 18:40:25 +00:00
Dominic Gannaway
df8db4e005 [react-interactions] Enable event system before dispatching blur (#17365) 2019-11-14 13:47:39 +00:00
Dominic Gannaway
fc43644eba [react-interactions] Follow up active element blur logic (#17364) 2019-11-14 11:55:50 +00:00
Dominic Gannaway
a61886b16b [react-interactions] Refine custom active element blur logic (#17354) 2019-11-13 20:46:00 +00:00
Andrew Clark
b53ea6ca05 [Bugfix] Passive effects triggered by synchronous renders in a multi-root app (#17347)
* Regression test: Effects dropped across roots

See #17066

* [Bugfix] Passive effects loop

The bug
-------

In a multi-root app, certain passive effects (`useEffect`) are never
fired. See #17066.

The underlying problem
----------------------

The implicit contract of `flushPassiveEffects` is that, right after
calling it, there should be no pending passive effects. In the normal
case, in concurrent mode, this is true. But the current implementation
fails to account for the case where a passive effect schedules
synchronous work, which in turn schedules additional passive effects.

This led to `rootWithPendingPassiveEffects` being overwritten in the
commit phase, because an assignment that assumed it was replacing null
was actually replacing a reference to another root, which has the
consequence of dropping passive effects on that root.

The fix
-------

The fix I've chosen here is, at the beginning of the commit phase, keep
flushing passive effects in a loop until there are no more.

This doesn't not change the "public" implementation of
`flushPassiveEffects`, though it arguably should work this way, too. I
say "public" because it's only used by implementation layers on top of
React which we control: mainly, the legacy version of `act` that does
not use the mock Scheduler build. So there's probably still a bug
in that `act` implementation.

I will address `act` in a follow-up. The ideal solution is to replace
the legacy `act` with one implemented directly in the renderer, using a
special testing-only build of React DOM. Since that requires a breaking
change, we'll need an interim solution. We could make the "public" `act`
recursively flush effects in a loop, as I've done for the commit phase.
However, I think a better solution is to stop automatically flushing the
synchronous update queue at the end of `flushPassiveEffects`, and
instead require the caller to explicitly call `flushSyncUpdateQueue` (or
the equivalent) if needed. This follows the same pattern we use
internally in the work loop, which is designed to avoid factoring
hazards like the one that resulted in this bug.
2019-11-12 10:41:07 -08:00
Vasilii Cuhar
f4cc45ce96 [Fresh] Add options to configure RefreshSig and RefreshReg identifiers (#17340) 2019-11-12 14:16:23 +00:00
Eli White
ade764157f [Native] If statement cleanup for null targets (#17346) 2019-11-11 12:58:30 -08:00
Eli White
3dcec3a925 [Native] Add FeatureFlag to dispatch events with instance currentTarget (#17345)
* [Native] Add FeatureFlag to dispatch events with instance targets

* Prettier

* [Native] Change currentTarget to be an instance behind a flag 2/2
2019-11-11 12:42:06 -08:00
Eli White
2c6ea0b3ff [Native] Add FeatureFlag to dispatch events with instance targets (#17323)
* [Native] Add FeatureFlag to dispatch events with instance targets

* Prettier
2019-11-11 11:35:29 -08:00
Eli White
01bce8c248 Change legacy-events plugin nativeEventTarget to allow null (#17344) 2019-11-11 11:22:41 -08:00
Dominic Gannaway
6cff70a740 [react-interactions] Expost host instance to Scope Query function (#17341) 2019-11-11 13:51:46 +00:00
Dan Abramov
b8f8258775 Split ReactDOM entry point (#17331)
* Split ReactDOM entry point

* BatchedRoot -> BlockingRoot
2019-11-10 13:43:29 +00:00
Dan Abramov
a7b4d51a20 Warn when doing createRoot twice on the same node (another approach) (#17329)
* Unify fields used for createRoot warning and event system

* Warn when doing createRoot twice on the same node

* Stricter check for modern roots

* Unmark asynchronously

* Fix Flow
2019-11-10 00:54:13 +00:00
Dan Abramov
be3bfa6fab [Flight] Basic Integration Test (#17307)
* [Flight] Basic Integration Test

* Just act()

* Lint

* Remove unnecessary acts

* Use Concurrent Mode

* it.experimental

* Fix prod test by advancing time

* Don't observe initial state
2019-11-09 02:56:03 +00:00
Dan Abramov
182f64f938 [Flight] End-to-End Fixture (#17319) 2019-11-09 02:44:27 +00:00
Dan Abramov
6cb6b1d668 Add yarn build --unsafe-partial (#17316)
* Add yarn build --partial

* unsafe-partial
2019-11-08 19:41:40 +00:00
Moti Zilberman
38dd17ab98 [RN] Hoist static deepDiffer options object (#17303) 2019-11-07 16:03:55 +00:00
Moti Zilberman
61d3dd0e08 Update deepDiffer usage in React Native renderer (#17282)
* Add RN prop diffing test with function values

* Update RN deepDiffer mock

* Explicitly ignore functions in RN prop differ
2019-11-07 04:00:20 -08:00
Dominic Gannaway
e701632ad4 [react-interactions] Change unmount blur logic to a dedicated event (#17291) 2019-11-07 10:27:02 +00:00
Dominic Gannaway
ce4b3e9981 [react-interactions] Add optional searchNodes to Scope.queryAllNodes (#17293) 2019-11-06 22:52:59 +00:00
Sebastian Markbåge
dee03049f5 [Flight] Basic Streaming Suspense Support (#17285)
* Return whether to keep flowing in Host config

* Emit basic chunk based streaming in the Flight server

When something suspends a new chunk is created.

* Add reentrancy check

The WHATWG API is designed to be pulled recursively.

We should refactor to favor that approach.

* Basic streaming Suspense support on the client

* Add basic suspense in example

* Add comment describing the protocol that the server generates
2019-11-06 09:48:34 -08:00
Dan Abramov
f50f39b55f [Flight] Better compat with http.createServer (#17289) 2019-11-06 17:10:26 +00:00
Brian Vaughn
3452706308 DevTools cleanup (#17283)
1. Add a Store test for memo, lazy, and forwardRef components
2. Remove dead code for React.lazy
3. Update DT tests to include HOC badge names in the serialized store
2019-11-05 22:19:10 -08:00
Dominic Gannaway
cd1bdcd067 [react-interactions] Prevent duplicate onPress firing for keyboard Enter (#17266)
* [react-interactions] Prevent duplicate onPress firing for keyboard Enter

* address feedback
2019-11-05 23:28:26 +00:00
Waseem Dahman
4f02c93c7c Fix devtools displaying Anonymous for memo of ref-forwarding components (#17274)
* [react-is] return correct typeOf value of forwardRef

* [react-devtools-shared] use correct displayName of memo(forwardRef(Component))

* [react-devtools-shared] add resolveFiberType and resolve fiber type of memo recursively

Resolving the fiber type of memo recursively before passing it to getDisplayName
will prevent it from displaying "Anonymous" as displayName for components
wrapped with both memo and forwardRef: memo(forwardRef(Component))

* rework resolveFiberType
2019-11-05 14:08:01 -08:00
Brian Vaughn
053cf0fedc Fix react-is memo and lazy type checks (#17278) 2019-11-05 10:37:12 -08:00
Andrew Clark
0f3838a01b Remove debugRenderPhaseSideEffects flag (#17270)
There are two similar flags, `debugRenderPhaseSideEffects` and
`debugRenderPhaseSideEffectsForStrictMode`. The strict mode one is the
only one that is actually used. I think originally the theory is that
we would one day turn it on for all components, even outside strict
mode. But what we'll do instead is migrate everyone to strict mode.

The only place `debugRenderPhaseSideEffects` was being used was in
an internal test file. I rewrote those tests to use public APIs.
2019-11-04 14:07:05 -08:00
Dominic Gannaway
cb09dbe0ab [react-interactions] Add handleSimulateChildBlur upon DOM node removal (#17225)
* [react-interactions] Add handleSimulateChildBlur upon DOM node removal
2019-11-04 20:02:45 +00:00
Eli White
6095993d46 Types: findHostInstance_DEPRECATED returns React.ElementRef<HostComponent<mixed>> (#17265)
* Types: findHostInstance_DEPRECATED returns React.ElementRef<HostComponent<mixed>>

* Prettier
2019-11-04 10:14:01 -08:00
Mateusz Burzyński
62ef25077e Avoid bundling in ponyfill for Object.assign in use-subscription package (#17259) 2019-11-04 07:33:10 -08:00
Sebastian Markbåge
f4148b2561 [Flight] Move around the Server side a bit (#17251)
* Rename ReactFlightStreamer -> ReactFlightServer

* Unify Browser/Node stream tests into one file and use the client reader

* Defer to the actual ReactDOM for HTML rendering for now

This will need to use a variant of Fizz to do inline SSR in Flight.
However, I don't want to build the whole impl right now but also don't
want to exclude the use case yet. So I outsource it to the existing
renderer. Ofc, this doesn't work with Suspense atm.
2019-11-01 17:39:24 -07:00
Sebastian Markbåge
fadc97167f [Flight] Add Client Infrastructure (#17234)
* Change demo to server

* Expose client in package.json

* Reorganize tests

We don't want unit tests but instead test how both server and clients work
together. So this merges server/client test files.

* Fill in the client implementation a bit

* Use new client in fixture

* Add Promise/Uint8Array to lint rule

I'll probably end up deleting these deps later but they're here for now.
2019-11-01 16:05:07 -07:00
Brian Vaughn
36fd29f09f Don't show empty (no work) commits in Profiler (#17253) 2019-11-01 15:51:39 -07:00
Andrew Clark
a2e05b6c14 [Scheduler] Delete old rAF implementation (#17252)
We've been using the message loop version for a while. Time to delete.
2019-11-01 13:32:22 -07:00
Andrew Clark
6dc2734b41 Codemod tests to it.experimental (#17243)
`it.experimental` marks that a test only works in Experimental builds.

It also asserts that a test does *not* work in the stable builds. The
main benefit is that we're less likely to accidentally expose an
experimental API before we intend. It also forces us to un- mark an
experimental test once it become stable.
2019-11-01 10:20:08 -07:00
Ives van Hoorne
a1ff9fd7bb Add CodeSandbox CI Config (#17175)
* Add CodeSandbox CI Config

* Add default sandbox to build

* Make build more efficient and add scheduler

* Force build

* Add scheduler image

* Add scheduler/tracing to the build

* Force another build
2019-10-31 10:44:48 -07:00
Brian Vaughn
273679a78c DevTools standalone shell changes: (#17213)
* DevTools standalone shell changes:

1. Update React Native connection wording
2. Tweak styles slightly

* Tweaked standalone DevTools error warning text
2019-10-31 09:22:08 -07:00
Eli White
d0fc0ba0a6 Revert "Dispatch commands to both UIManagers from both renderers (#17211)" (#17232)
This reverts commit 8eee0eb01c.
2019-10-30 18:03:37 -07:00
Eli White
bdcdb69a24 Rename findHostInstance_deprecated to findHostInstance_DEPRECATED (#17228) 2019-10-30 13:10:16 -07:00
Eli White
515746c217 Add findHostInstance_deprecated to the React Native Renderer (#17224) 2019-10-30 11:42:37 -07:00
Brian Vaughn
9a35adc96d Only call Profiler onRender when a descendant had work (#17223) 2019-10-30 11:08:42 -07:00
Eli White
8eee0eb01c Dispatch commands to both UIManagers from both renderers (#17211)
* Dispatch commands to both UIManagers from both renderers

* Merge test files
2019-10-29 18:16:23 -07:00
Sebastian Markbåge
f4e974d26e Add Experimental Flight Infrastructure (#16398)
* Add Flight Build and Unify HostFormat Config between Flight and Fizz

* Add basic resolution of models

* Add basic Flight fixture

Demonstrates the streaming protocol.

* Rename to flight-server to distinguish from the client parts

* Add Flight Client package and entry point

* Fix fixture
2019-10-29 14:45:47 -07:00
Sebastian Markbåge
6cd365cac6 Don't treat the last row in hidden as deleted if already mounted (#17206)
Already mounted rows that resuspend may be considered as part of a tail
if they're at the end. However, for purposes of the tail="..." option
they don't get deleted. We deal with that in cutOffTailIfNeeded.

However, if they're also the first to suspend in the "hidden" case, we have
a special case that deletes the actual rendered row. This needs to consider
if that row was already mounted or things go wrong.
2019-10-29 13:38:25 -07:00
Dominic Gannaway
048879edad [react-interactions] Ensure props on scope query function is always object (#17212) 2019-10-29 13:26:46 -07:00
Brian Vaughn
3497ccc149 Add guard to handle modified React elements with non-string keys (#17164) 2019-10-29 08:41:48 -07:00
Lea Rosema
0f64703edf Update .mailmap (#17177) 2019-10-28 16:48:54 +00:00
Deniz Susman
3f9c036758 Typo fix in comment (#17111) 2019-10-26 08:33:05 -07:00
Dan Abramov
f6b8d31a76 Rename createSyncRoot to createBlockingRoot (#17165)
* Rename createSyncRoot to createBlockingRoot

* Fix up
2019-10-23 15:04:39 -07:00
Wilco Fiers
9c02d26549 docs: Fixed a typo in readme.md (#17119) 2019-10-23 18:52:14 +01:00
Andrew Clark
57b2fbb741 Changelog for 16.11.0 2019-10-22 14:30:12 -07:00
Andrew Clark
8075c8505b Update local package versions for 16.10 release 2019-10-22 14:30:06 -07:00
687 changed files with 31694 additions and 16754 deletions

View File

@@ -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
View 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"]
}

View File

@@ -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: {

View File

@@ -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
View 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
View 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
View 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

View File

@@ -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
View 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!

View File

@@ -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>

View File

@@ -8,7 +8,7 @@ module.exports = {
jsxBracketSameLine: true,
trailingComma: 'es5',
printWidth: 80,
parser: 'babylon',
parser: 'babel',
overrides: [
{

View File

@@ -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
View 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

View File

@@ -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) => {

View File

@@ -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"

View File

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@@ -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 />);

View File

@@ -55,4 +55,4 @@ class IframeSubtree extends React.Component {
}
}
export default (ReactDOM.createPortal ? IframePortal : IframeSubtree);
export default ReactDOM.createPortal ? IframePortal : IframeSubtree;

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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)
),

View File

@@ -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"

View File

@@ -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]);
}

View File

@@ -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"

View File

@@ -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>

View File

@@ -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>

View File

@@ -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"

View 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
View File

@@ -0,0 +1 @@
SKIP_PREFLIGHT_CHECK=true

23
fixtures/flight/.gitignore vendored Normal file
View 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*

View 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"
]
}
}

View 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>

View 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);
};

View 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;
}
});

View 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;

View 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

File diff suppressed because it is too large Load Diff

View File

@@ -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} />);

View File

@@ -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"

View File

@@ -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');
})
);
});

View File

@@ -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",

View File

@@ -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",

View File

@@ -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,
});
});

View File

@@ -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> {

View 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 sequencewith complete mock eventsis 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});
```

View File

@@ -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"`;

View 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 {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,
}
`);
});
});

View 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,
};

View File

@@ -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,
};

View 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();
}

View File

@@ -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,
});

View File

@@ -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,
};

View File

@@ -0,0 +1,5 @@
{
"private": true,
"name": "dom-event-testing-library",
"version": "0.0.0"
}

View 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);
});
}

View 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();
}

View File

@@ -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:

View File

@@ -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: `

View File

@@ -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.

View File

@@ -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",

View File

@@ -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?

View File

@@ -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

View File

@@ -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": {

View File

@@ -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(

View File

@@ -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>;
/**

View File

@@ -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`.');
}
};
}

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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,
...
};

View File

@@ -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(

View File

@@ -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();

View File

@@ -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<>;

View File

@@ -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,

View File

@@ -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.');
}
}
}
}

View File

@@ -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,
);
}
}
}

View File

@@ -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(

View File

@@ -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"

View File

@@ -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
}

View File

@@ -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 **)',

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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.',

View File

@@ -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);

View File

@@ -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([

View File

@@ -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",

View File

@@ -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.

View File

@@ -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>
`;
}
}

View File

@@ -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 = {

View File

@@ -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 = {

View File

@@ -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