Compare commits

...

246 Commits

Author SHA1 Message Date
Sebastian Markbåge
d346d92084 Allow Suspense Mismatch on the Client to Silently Proceed (#16943)
* Regression test: Suspense + hydration + legacy

* Allow Suspense Mismatch on the Client to Silently Proceed

This fixes but isn't actually the semantics that we want this case to have.
2019-09-28 10:50:57 -07:00
Sebastian Markbåge
3694a3b5e9 Selective Hydration (#16880)
* Add Feature Flag for Selective Hydration

* Enable Synchronous Hydration of Discrete Events

* Resolve cyclic dependency
2019-09-25 15:26:27 -07:00
Dominic Gannaway
4bb0e96b4b [react-interactions] FocusTable key press bound propgataion (#16895) 2019-09-25 21:12:39 +02:00
Brian Vaughn
fa1a326227 Update useEditableValue hook to sync external value changes (#16878)
* Update useEditableValue to mirror value cahnges

Previously, the hook initialized local state (in useState) to mirror the prop/state value. Updates to the value were ignored though. (Once the state was initialized, it was never updated.) The new hook updates the local/editable state to mirror the external value unless there are already pending, local edits being made.

* Optimistic CHANGELOG update

* Added additional useEditableValue() unit test cases
2019-09-25 10:46:27 -07:00
Dominic Gannaway
57bf275fbd [devtools] Add support for React Scope symbol/number (#16893) 2019-09-25 19:03:37 +02:00
Dominic Gannaway
7c3bd08b37 [react-interactions] Add more documentation for a11y components (#16894) 2019-09-25 19:01:15 +02:00
Sebastian Markbåge
a06d181af0 Include tag in begin/complete invariant (#16881) 2019-09-25 08:10:35 -07:00
Sebastian Markbåge
0d8c0cd096 These flags are hard coded in our internal config (#16883)
By hard coding them here they can get properly DCE and don't have to be
sent to users. The internal config should only have dynamic flags.
2019-09-25 08:00:29 -07:00
Dominic Gannaway
d6d83d7060 [react-interactions] Add Portal propagation configuration (#16889) 2019-09-25 16:56:51 +02:00
Dominic Gannaway
d0ebde77f6 [react-interactions] Add initial docs explaining React Scopes (#16892) 2019-09-25 14:57:47 +02:00
Joshua Gross
32e5c97d11 [React Native] Improve errors for invalid ViewConfig getter functions (#16879)
* [React Native] Improve logging for missing view configs and invalid view config getter functions

* [React Native] Improve logging for missing view configs and invalid view config getter functions
2019-09-24 17:46:21 -07:00
Dominic Gannaway
ebc299fc2f [react-interactions] TabFocus -> FocusManager (#16874) 2019-09-24 23:26:20 +02:00
Dominic Gannaway
793f176dad [react-interactions] Make FocusList bundle (#16876) 2019-09-24 18:02:13 +02:00
Dominic Gannaway
68a87eee54 [react-interactions] Add FocusList component (#16875) 2019-09-24 17:14:29 +02:00
Jessica Franco
18d2e0c03e Warning system refactoring (part 1) (#16799)
* Rename lowPriorityWarning to lowPriorityWarningWithoutStack

This maintains parity with the other warning-like functions.

* Duplicate the toWarnDev tests to test toLowPriorityWarnDev

* Make a lowPriorityWarning version of warning.js

* Extract both variants in print-warning

Avoids parsing lowPriorityWarning.js itself as the way it forwards the
call to lowPriorityWarningWithoutStack is not analyzable.
2019-09-24 13:45:38 +01:00
Andrew Clark
8b580a89d6 Idle updates should not be blocked by hidden work (#16871)
* Idle updates should not be blocked by hidden work

Use the special `Idle` expiration time for updates that are triggered at
Scheduler's `IdlePriority`, instead of `Never`.

The key difference between Idle and Never¹ is that Never work can be
committed in an inconsistent state without tearing the UI. The main
example is offscreen content, like a hidden subtree.

¹ "Never" isn't the best name. I originally called it that because it
"never" expires, but neither does Idle. Since it's mostly used for
offscreen subtrees, we could call it "Offscreen." However, it's also
used for dehydrated Suspense boundaries, which are inconsistent in the
sense that they haven't finished yet, but aren't visibly inconsistent
because the server rendered HTML matches what the hydrated tree would
look like.

* Reset as early as possible using local variable

* Updates in a hidden effect should be Idle

I had made them Never to avoid an extra render when a hidden effect
updates the hidden component -- if they are Idle, we have to render once
at Idle, which bails out on the hidden subtree, then again at Never to
actually process the update -- but the problem of needing an extra
render pass to bail out hidden updates already exists and we should fix
that properly instead of adding yet another special case.
2019-09-23 20:52:48 -07:00
Nicolas Gallagher
c5e7190ed4 [react-interactions] Press with useRef instead of useState (#16870)
We only need to read and modify the value for the lifetime of the hook
2019-09-23 16:25:13 -07:00
Brian Vaughn
911104a129 DevTools CHANGELOG update 2019-09-23 12:58:58 -07:00
Brian Vaughn
bce2ac63a9 Revert change to backend injection method from PR #16752 (#16864)
PR #16752 changed how we were injecting the backend script to be done by the content script in order to work around Trusted Type limitations with our previous approach. This may have caused a regression (see #16840) so I'm backing it out to verify.
2019-09-23 12:56:36 -07:00
Brian Vaughn
9b3cde9b62 Fix DevTools v4.1 editable hook regression (#16867)
* Fixed a regression in hooks editor from a recent EditableValue change

* Fixed a reset/state bug in useEditableValue() hook and removed unnecessary useMemo()
2019-09-23 12:56:01 -07:00
Dominic Gannaway
1a6294d3e2 [react-interaction] Refactor a11y components more (#16866) 2019-09-23 21:30:49 +02:00
Dominic Gannaway
1758b3f7ba [react-interactions] Add no-op stopPropagation + preventDefault to Press (#16868)
Fix
2019-09-23 21:18:40 +02:00
Andrew Clark
013b7ad117 [suspense][error handling] Inline renderRoot and fix error handling bug (#16801)
* Outline push/pop logic in `renderRoot`

I want to get rid of the the `isSync` argument to `renderRoot`, and
instead use separate functions for concurrent and synchronous render.

As a first step, this extracts the push/pop logic that happens before
and after the render phase into helper functions.

* Extract `catch` block into helper function

Similar to previous commit. Extract error handling logic into
a separate function so it can be reused.

* Fork `renderRoot` for sync and concurrent

Removes `isSync` argument in favor of separate functions.

* Extra "root completion" logic to separate function

Moving this out to avoid an accidental early return, which would
bypass the call to `ensureRootIsScheduled` and freeze the UI.

* Inline `renderRoot`

Inlines `renderRoot` into `performConcurrentWorkOnRoot` and
`performSyncWorkOnRoot`. This lets me remove the `isSync` argument
and also get rid of a redundant try-catch wrapper.

* [suspense][error handling] Add failing unit test

Covers an edge case where an error is thrown inside the complete phase
of a component that is in the return path of a component that suspends.
The second error should also be handled (i.e. able to be captured by
an error boundary.

The test is currently failing because there's a call to
`completeUnitOfWork` inside the main render phase `catch` block. That
call is not itself wrapped in try-catch, so anything that throws is
treated as a fatal/unhandled error.

I believe this bug is only observable if something in the host config
throws; and, only in legacy mode, because in concurrent/batched mode,
`completeUnitOfWork` on fiber that throws follows the "unwind" path
only, not the "complete" path, and the "unwind" path does not call
any host config methods.

* [scheduler][profiler] Start time of delayed tasks

Fixes a bug in the Scheduler profiler where the start time of a delayed
tasks is always 0.

* Remove ad hoc `throw`

Fatal errors (errors that are not captured by an error boundary) are
currently rethrown from directly inside the render phase's `catch`
block. This is a refactor hazard because the code in this branch has
to mirror the code that happens at the end of the function, when exiting
the render phase in the normal case.

This commit moves the throw to the end, using a new root exit status.

* Handle errors that occur on unwind
2019-09-23 11:23:28 -07:00
Sebastian Markbåge
0a527707cd Event Replaying (#16725)
* Add Event Replaying Infra

* Wire up Roots and Suspense boundaries, to retry events, after they commit

* Replay discrete events in order in a separate scheduler callback

* Add continuous events

These events only replay their last target if the target is not yet
hydrated. That way we don't have to wait for a previously hovered
boundary before invoking the current target.

* Enable tests from before

These tests were written with replaying in mind and now we can properly
enable them.

* Unify replaying and dispatching

* Mark system flags as a replay and pass to legacy events

That way we can check if this is a replay and therefore needs a special
case. One such special case is "mouseover" where we check the
relatedTarget.

* Eagerly listen to all replayable events

To minimize breakages in a minor, I only do this for the new root APIs
since replaying only matters there anyway. Only if hydrating.

For Flare, I have to attach all active listeners since the current
system has one DOM listener for each. In a follow up I plan on optimizing
that by only attaching one if there's at least one active listener
which would allow us to start with only passive and then upgrade.

* Desperate attempt to save bytese

* Add test for mouseover replaying

We need to check if the "relatedTarget" is mounted due to how the old
event system dispatches from the "out" event.

* Fix for nested boundaries and suspense in root container

This is a follow up to #16673 which didn't have a test because it wasn't
observable yet. This shows that it had a bug.

* Rename RESPONDER_EVENT_SYSTEM to PLUGIN_EVENT_SYSTEM
2019-09-23 11:21:10 -07:00
Andrew Clark
a87d245fc2 [work loop] Prevent work loop from being inlined (#16865)
Uses Closure Compiler's `@noinline` directive.

See https://github.com/google/closure-compiler/wiki/Annotating-JavaScript-for-the-Closure-Compiler#noinline
2019-09-23 11:12:47 -07:00
Nicolas Gallagher
312b462d54 [react-interactions] Improve consistency of Tap responder (#16837)
Makes sure that touch events with modifier keys behave the same way as other
pointer types (i.e., does not call `onTapStart` if the gesture begins with a
modifier key held down)
2019-09-23 10:48:11 -07:00
Dominic Gannaway
70754f10d4 [react-interaction] Tweak Focus Table component (#16862) 2019-09-23 16:39:43 +02:00
Dominic Gannaway
d7f6dd5a80 [react-interactions] Fix typo in FocusTable (#16860) 2019-09-23 14:20:43 +02:00
Dan Abramov
cef47cbc01 Rename experimental react-ui => react-interactions (#16842) 2019-09-20 11:51:03 +01:00
Dominic Gannaway
57a5805a9f [react-ui] Add preventDefault+stopPropagation to Keyboard + update Focus components (#16833) 2019-09-20 11:21:38 +02:00
Brian Vaughn
08b51aa384 Added React DevTools v4.1.0 release date to CHANGELOG 2019-09-19 15:22:22 -07:00
Brian Vaughn
b5cebedfbe React DevTools version bump 4.0.6 -> 4.1.0 2019-09-19 15:11:48 -07:00
Sebastian Markbåge
35bf9d27a3 Exclude react-dom when flow checking other builds (#16737)
This is because the HostConfig can't be guaranteed to be consistent with
other code such as code that touches the DOM directly.

Ideally we'd have a more systemic solution to this since it will pop
up for other packages later too.
2019-09-19 12:41:51 -07:00
Nicolas Gallagher
fd870e6b6a [react-ui/events] Tap responder API changes (#16827)
This patch limits the `onTap*` callbacks to the primary pointer button.
Auxiliary button and modified primary button interactions call
`onAuxiliaryTap`, cancel any active tap, and preserve the native behavior.
2019-09-19 09:36:29 -07:00
Dan Abramov
4ddcb8e134 [DevTools] Remove Welcome dialog (#16834) 2019-09-19 08:41:18 -07:00
Dominic Gannaway
924a305780 [react-ui] Remove event object warnings (#16822) 2019-09-19 12:46:17 +02:00
halvves
a5df18a9e5 prevent firefox marking required textareas invalid (#16578)
* prevent firefox marking required textareas invalid

Bug was caused by an IE10/IE11 bugfix dealing with the placeholder attribute and textContent. Solved by avoiding the IE bugfix when textContent was empty.

Closes #16402

* more explicit conditional check for textContent

re: @philipp-spiess code review

* clarify textarea test fixture's expected result

better describe the behavior we are testing for
re: @philipp-spiess code review
2019-09-18 14:38:02 -07:00
Dan Abramov
f818af9b03 [Fresh] Always remount classes (#16823) 2019-09-18 16:32:11 +01:00
Ricky
6ecfa90eb0 [React Native] Fix for view config registrations (#16821) 2019-09-18 15:31:00 +01:00
Dominic Gannaway
18cb59050b [react-core] Do not null fiber.sibling in detachFiber (#16820) 2019-09-18 14:18:32 +02:00
Kuba Juszczyk
d862f0ea56 Optimize objectIs (#16212) 2019-09-17 23:55:43 +02:00
Pavlo Tymchuk
d1c2555861 [react-devtools-shared] Added string type check for object name prop in getDisplayName function (#16798)
* [react-devtools-shared] Added string type check for object name prop in getDisplayName function from utils.js file; tests included;

* Re-added empty string check to getDisplayName()

* Tweaked tests to use real functions

This more closely simulates how the utility is being used in production, and would catch cases like anonymous functions (with empty string names).
2019-09-17 13:17:04 -07:00
Brian Vaughn
70dcdd265d Updated pending CHANGELOG for DevTools 2019-09-17 13:15:53 -07:00
Brian Vaughn
e8c7ddeef2 Release script filters DevTools NPM pakcages (#16810) 2019-09-17 12:46:13 -07:00
Dominic Gannaway
8f1533f4d8 [react-ui] Fix bundle name [hotfix] (#16811) 2019-09-17 21:10:51 +02:00
Dominic Gannaway
7c802de797 [react-a11y] Add react-ui/accessibility to bundle build (#16804) 2019-09-17 20:22:09 +02:00
Andrew Clark
901139c291 [scheduler][profiler] Start time of delayed tasks (#16809)
Fixes a bug in the Scheduler profiler where the start time of a delayed
tasks is always 0.
2019-09-17 10:24:48 -07:00
Dominic Gannaway
f40ceb0010 [react-ui] FocusGrid -> ReactFocusTable + tweaks and fixes (#16806) 2019-09-17 19:22:10 +02:00
Dominic Gannaway
2f1e8c5f78 [react-core] Clear more properties in detachFiber (#16807) 2019-09-17 17:30:22 +02:00
Dan Abramov
8e0c574122 Follow-up to initial Trusted Types support (#16795)
* Follow-up to initial Trusted Types support

* Fast-path both strings and numbers

* Move shared objects out of every test
2019-09-17 16:06:26 +01:00
Nicolas Gallagher
3af05de1aa [react-ui] usePress from useKeyboard and useTap (#16772)
This implements 'usePress' in user-space as a combination of 'useKeyboard' and 'useTap'.  The existing 'usePress' API is preserved for now. The previous 'usePress' implementation is moved to 'PressLegacy'.
2019-09-16 14:36:27 -07:00
Dominic Gannaway
494300b366 [react-ui] Move experimental event+a11y work to react-ui package (#16794) 2019-09-16 20:05:50 +02:00
Nicolas Gallagher
9691eb273a [react-events] Keyboard support for virtual clicks (#16780)
This accounts for all clicks that are natively dispatched following relevant
keyboard interactions (e.g., key is "Enter"), as well as programmatic clicks,
and screen-reader virtual clicks.
2019-09-16 10:40:05 -07:00
Emanuel Tesař
b8d079b413 Add trusted types to react on client side (#16157)
* Add trusted types to react on client side

* Implement changes according to review

* Remove support for trusted URLs, change TrustedTypes to trustedTypes

* Add support for deprecated trusted URLs

* Apply PR suggesstions

* Warn only once, remove forgotten check, put it behind a flag

* Move comment

* Fix PR comments

* Fix html toString concatenation

* Fix forgotten else branch

* Fix PR comments
2019-09-16 13:43:22 +01:00
Heaven
cdbfa5044b fix typo inteval -> interval & continutation -> continuation (#16760)
* fix typo alterate -> alternate

* fix typo interal -> interval & continutation -> continuation
2019-09-14 12:51:20 -07:00
Andrew Clark
45b6443c90 Spelling is fundamental (#16782) 2019-09-13 16:27:18 -07:00
Andrew Clark
45898d0be0 [Scheduler] Prevent event log from growing unbounded (#16781)
If a Scheduler profile runs without stopping, the event log will grow
unbounded. Eventually it will run out of memory and the VM will throw
an error.

To prevent this from happening, let's automatically stop the profiler
once the log exceeds a certain limit. We'll also print a warning with
advice to call `stopLoggingProfilingEvents` explicitly.
2019-09-13 15:50:25 -07:00
Nicolas Gallagher
87eaa90ef8 [react-events] Keyboard calls preventDefault on 'click' events (#16779)
Make sure to call preventDefault for any 'click' events that follow a 'keydown'
event that matches 'preventKeys'
2019-09-13 13:19:18 -07:00
Brian Vaughn
0c0b30b8c7 Remove unnecessary interaction tracing ping wrapper (#16777) 2019-09-13 11:04:36 -07:00
Andrew Clark
137ea783b1 Re-enable risky work loop changes (#16771)
The stack of PRs in #16743 was reverted. This adds them back.
2019-09-13 09:33:46 -07:00
Brian Vaughn
d6f6b951e1 Support disabling interaction tracing for suspense promises (#16776)
* Support disabling interaction tracing for suspense promises

If a thrown Promise has the __reactDoNotTraceInteractions attribute, React will not wrapped its callbacks to continue tracing any current interaction(s).

* Added optional '__reactDoNotTraceInteractions' attribute to Flow Thenable type
2019-09-13 08:59:57 -07:00
Dominic Gannaway
b4b8a349a3 [react-interactions] Add experimental FocusGrid API (#16766) 2019-09-13 12:29:39 +02:00
Andrew Clark
a7dabcb60a Revert "Re-arrange slightly to prevent refactor hazard (#16743)" (#16769)
This reverts commit ab4951fc03.

* Track "pending" and "suspended" ranges

A FiberRoot can have pending work at many distinct priorities. (Note: we
refer to these levels as "expiration times" to distinguish the concept
from Scheduler's notion of priority levels, which represent broad
categories of work. React expiration times are more granualar. They're
more like a concurrent thread ID, which also happens to correspond to a
moment on a timeline. It's an overloaded concept and I'm handwaving over
some of the details.)

Given a root, there's no convenient way to read all the pending levels
in the entire tree, i.e. there's no single queue-like structure that
tracks all the levels, because that granularity of information is not
needed by our algorithms. Instead we track the subset of information
that we actually need — most importantly, the highest priority level
that exists in the entire tree.

Aside from that, the other information we track includes the range of
pending levels that are known to be suspended, and therefore should not
be worked on.

This is a refactor of how that information is tracked, and what each
field represents:

- A *pending* level is work that is unfinished, or not yet committed.
  This includes work that is suspended from committing.
  `firstPendingTime` and `lastPendingTime` represent the range of
  pending work. (Previously, "pending" was the same as "not suspended.")
- A *suspended* level is work that did not complete because data was
  missing. `firstSuspendedTime` and `lastSuspendedTime` represent the
  range of suspended work. It is a subset of the pending range. (These
  fields are new to this commit.)
- `nextAfterSuspendedTime` represents the next known level that comes
  after the suspended range.

This commit doesn't change much in terms of observable behavior. The one
change is that, when a level is suspended, React will continue working
on the next known level instead of jumping straight to the last pending
level. Subsequent commits will use this new structure for a more
substantial refactor for how tasks are scheduled per root.

* Get next expiration time from FiberRoot

Given a FiberRoot, we should be able to determine the next expiration
time that needs to be worked on, taking into account the levels that
are pending, suspended, pinged, and so on.

This removes the `expirationTime` argument from
`scheduleCallbackForRoot`, and renames it to `ensureRootIsScheduled` to
reflect the new signature. The expiration time is instead read from the
root using a new function, `getNextExpirationTimeToWorkOn`.

The next step will be to remove the `expirationTime` argument from
`renderRoot`, too.

* Don't bind expiration time to render callback

This is a fragile pattern because there's only meant to be a single
task per root, running at a single expiration time. Instead of binding
the expiration time to the render task, or closing over it, we should
determine the correct expiration time to work on using fields we
store on the root object itself.

This removes the "return a continuation" pattern from the
`renderRoot` function. Continuation handling is now handled by
the wrapper function, which I've renamed from `runRootCallback` to
`performWorkOnRoot`. That function is merely an entry point to
`renderRoot`, so I've also removed the callback argument.

So to sum up, at at the beginning of each task, `performWorkOnRoot`
determines which expiration time to work on, then calls `renderRoot`.
And before exiting, it checks if it needs to schedule another task.

* Update error recovery test to match new semantics

* Remove `lastPendingTime` field

It's no longer used anywhere

* Restart on update to already suspended root

If the work-in-progress root already suspended with a delay, then the
current render definitely won't finish. We should interrupt the render
and switch to the incoming update.

* Restart on suspend if return path has an update

Similar to the previous commit, if we suspend with a delay, and
something in the return path has a pending update, we should abort
the current render and switch to the update instead.

* Track the next unprocessed level globally

Instead of backtracking the return path. The main advantage over the
backtracking approach is that we don't have to backtrack from the source
fiber. (The main disadvantages are that it requires another module-level
variable, and that it could include updates from unrelated
sibling paths.)

* Re-arrange slightly to prevent refactor hazard

It should not be possible to perform any work on a root without
calling `ensureRootIsScheduled` before exiting. Otherwise, we could
fail to schedule a callback for pending work and the app could freeze.

To help prevent a future refactor from introducing such a bug, this
change makes it so that `renderRoot` is always wrapped in try-finally,
and the `finally` block calls `ensureRootIsScheduled`.

* Remove recursive calls to `renderRoot`.

There are a few leftover cases where `renderRoot` is called recursively.
All of them are related to synchronously flushing work before its
expiration time.

We can remove these calls by tracking the last expired level on the
root, similar to what we do for other types of pending work, like pings.

* Remove argument from performSyncWorkOnRoot

Read the expiration time from the root, like we do
in performConcurrentWorkOnRoot.
2019-09-12 14:21:57 -07:00
Dominic Gannaway
4b0b556dcf [react-interactions] Refactor TabFocusController (#16768) 2019-09-12 22:28:07 +02:00
Brian Vaughn
fb39f62925 Added upcoming changes to DevTools CHANGELOG 2019-09-12 08:34:28 -07:00
Anton Korzunov
ba932a5ad9 fix: inspect ClassComponent.render instead of constructor, fixes #16749 (#16759) 2019-09-12 08:32:42 -07:00
Dominic Gannaway
35a202d0e7 [react-events] Ensure we restore currentInstance + currentTimers (#16758) 2019-09-12 12:44:05 +02:00
Dominic Gannaway
3717c25a7e [react-interactions] More Tab Focus control handling (#16751) 2019-09-11 22:35:33 +02:00
Andrew Clark
0a2215cc0e [Scheduler][www] Put profiling feature behind flag (#16757)
Our infra currently doesn't support loading a separate profiling
build of Scheduler. Until that's fixed, the recommendation is to load
a single build and gate the profiling feature behind a flag.

Alternative to #16659
2019-09-11 13:28:03 -07:00
Brian Vaughn
8f03109cd2 Moved backend injection to the content script (#16752)
* Moved backend injection logic to content script

* Moved backend injection logic to content script

* Moved injection logic to content script

* Formatting changes

* remove ability to inject arbitrary scripts

* Removed done(), added some comments explaining the change

* Lint fixes

* Moved inline comment.

* Deleted inject() script since it was no longer being used
2019-09-11 09:51:32 -07:00
Brian Vaughn
efa780d0ab Removed DT inject() script since it's no longer being used 2019-09-11 09:51:24 -07:00
Brian Vaughn
4290967d4c Merge branch 'tt-compat' of https://github.com/onionymous/react into onionymous-tt-compat 2019-09-11 09:34:31 -07:00
Brian Vaughn
f09854a9e8 Moved inline comment. 2019-09-11 09:30:57 -07:00
Stephanie Ding
776d1c69b9 Lint fixes 2019-09-11 08:11:14 -07:00
Dominic Gannaway
3a49dff386 [react-events] Use context.objectAssign in Tap responder (#16748) 2019-09-11 17:06:40 +02:00
Stephanie Ding
2e75000f40 Removed done(), added some comments explaining the change 2019-09-11 08:05:27 -07:00
Matt Kane
56114a4b22 Change trackedTouchCount console.error to warn (#16750) 2019-09-11 14:10:26 +02:00
Dominic Gannaway
ae724be7be [react-interactions] Add TabFocusContainer and TabbableScope UI components (#16732) 2019-09-11 12:46:41 +02:00
Andrew Clark
ab4951fc03 Re-arrange slightly to prevent refactor hazard (#16743)
* Track "pending" and "suspended" ranges

A FiberRoot can have pending work at many distinct priorities. (Note: we
refer to these levels as "expiration times" to distinguish the concept
from Scheduler's notion of priority levels, which represent broad
categories of work. React expiration times are more granualar. They're
more like a concurrent thread ID, which also happens to correspond to a
moment on a timeline. It's an overloaded concept and I'm handwaving over
some of the details.)

Given a root, there's no convenient way to read all the pending levels
in the entire tree, i.e. there's no single queue-like structure that
tracks all the levels, because that granularity of information is not
needed by our algorithms. Instead we track the subset of information
that we actually need — most importantly, the highest priority level
that exists in the entire tree.

Aside from that, the other information we track includes the range of
pending levels that are known to be suspended, and therefore should not
be worked on.

This is a refactor of how that information is tracked, and what each
field represents:

- A *pending* level is work that is unfinished, or not yet committed.
  This includes work that is suspended from committing.
  `firstPendingTime` and `lastPendingTime` represent the range of
  pending work. (Previously, "pending" was the same as "not suspended.")
- A *suspended* level is work that did not complete because data was
  missing. `firstSuspendedTime` and `lastSuspendedTime` represent the
  range of suspended work. It is a subset of the pending range. (These
  fields are new to this commit.)
- `nextAfterSuspendedTime` represents the next known level that comes
  after the suspended range.

This commit doesn't change much in terms of observable behavior. The one
change is that, when a level is suspended, React will continue working
on the next known level instead of jumping straight to the last pending
level. Subsequent commits will use this new structure for a more
substantial refactor for how tasks are scheduled per root.

* Get next expiration time from FiberRoot

Given a FiberRoot, we should be able to determine the next expiration
time that needs to be worked on, taking into account the levels that
are pending, suspended, pinged, and so on.

This removes the `expirationTime` argument from
`scheduleCallbackForRoot`, and renames it to `ensureRootIsScheduled` to
reflect the new signature. The expiration time is instead read from the
root using a new function, `getNextExpirationTimeToWorkOn`.

The next step will be to remove the `expirationTime` argument from
`renderRoot`, too.

* Don't bind expiration time to render callback

This is a fragile pattern because there's only meant to be a single
task per root, running at a single expiration time. Instead of binding
the expiration time to the render task, or closing over it, we should
determine the correct expiration time to work on using fields we
store on the root object itself.

This removes the "return a continuation" pattern from the
`renderRoot` function. Continuation handling is now handled by
the wrapper function, which I've renamed from `runRootCallback` to
`performWorkOnRoot`. That function is merely an entry point to
`renderRoot`, so I've also removed the callback argument.

So to sum up, at at the beginning of each task, `performWorkOnRoot`
determines which expiration time to work on, then calls `renderRoot`.
And before exiting, it checks if it needs to schedule another task.

* Update error recovery test to match new semantics

* Remove `lastPendingTime` field

It's no longer used anywhere

* Restart on update to already suspended root

If the work-in-progress root already suspended with a delay, then the
current render definitely won't finish. We should interrupt the render
and switch to the incoming update.

* Restart on suspend if return path has an update

Similar to the previous commit, if we suspend with a delay, and
something in the return path has a pending update, we should abort
the current render and switch to the update instead.

* Track the next unprocessed level globally

Instead of backtracking the return path. The main advantage over the
backtracking approach is that we don't have to backtrack from the source
fiber. (The main disadvantages are that it requires another module-level
variable, and that it could include updates from unrelated
sibling paths.)

* Re-arrange slightly to prevent refactor hazard

It should not be possible to perform any work on a root without
calling `ensureRootIsScheduled` before exiting. Otherwise, we could
fail to schedule a callback for pending work and the app could freeze.

To help prevent a future refactor from introducing such a bug, this
change makes it so that `renderRoot` is always wrapped in try-finally,
and the `finally` block calls `ensureRootIsScheduled`.

* Remove recursive calls to `renderRoot`.

There are a few leftover cases where `renderRoot` is called recursively.
All of them are related to synchronously flushing work before its
expiration time.

We can remove these calls by tracking the last expired level on the
root, similar to what we do for other types of pending work, like pings.

* Remove argument from performSyncWorkOnRoot

Read the expiration time from the root, like we do
in performConcurrentWorkOnRoot.
2019-09-10 20:07:12 -07:00
Sebastian Markbåge
b0a8a3e041 Mark root as already hydrated after committing (#16739)
* Mark root as already hydrated after committing

* Remove current/child check for hydration and rely on the root flag instead
2019-09-10 20:02:02 -07:00
Sebastian Markbåge
e04f4259c4 Handle SuspenseListComponent getting retried (#16745)
This happens for example when a deleted boundary transfers its pending
promises to the list so that the list can be retried.

This wasn't caught by unit tests because this flag wasn't on in those
tests.
2019-09-10 19:38:44 -07:00
Stephanie Ding
8a6cd3cd12 remove ability to inject arbitrary scripts 2019-09-10 18:06:23 -07:00
Stephanie Ding
d51f062d03 Formatting changes 2019-09-10 17:46:29 -07:00
Stephanie Ding
85c7211014 Moved injection logic to content script 2019-09-10 17:45:27 -07:00
Stephanie Ding
788036c7ed Moved backend injection logic to content script 2019-09-10 17:34:12 -07:00
Stephanie Ding
c93038fabe Moved backend injection logic to content script 2019-09-10 17:22:04 -07:00
Brian Vaughn
2c98af77c3 DevTools: Props editing interface tweaks (#16740)
* Fix DevTools new prop input size
* Don't allow adding new values unless an overridePropsFn function has been provided.
* Do not show empty 'none' label ablve a new prop input
2019-09-10 14:57:33 -07:00
Brian Vaughn
2ce5801c25 Added upcoming changes to DevTools CHANGELOG 2019-09-10 13:32:53 -07:00
Hristo Kanchev
709baf1fec [DevTools] Support for adding props | Improved state/props value editing (#16700)
* Extracted sanitizeForParse

* Added canAddEntries flag to InspectedElementTree

* Added EditableKey component.

* Added support to add an additional entry.

* Added support to add more complex data structures in the EditableValue component. Added support to change the dataType of the value that is being changed.

* Fixed flow error.

* Removed unneeded fragment.

* Renamed EditableKey -> EditableName

* Removed unneeded dependency

* Removed problematic props to state hook.

* Prettified changes.

* Removed unused import.

* Fixed shouldStringify check.

* Removed testing props from EditableProps.

* Made some inline tweaks
2019-09-10 13:30:43 -07:00
Hristo Kanchev
4ef6387d6e [DevTools] [Context] Legacy Context (#16617)
* Added hasLegacyContext check.

* Passed hasLegacyContext as prop to SelectedElement

* Changing context labels based on hasLegacyContext

* Fixed flow types.

* Fixed typos.

* Added tests for hasLegacyContext.

* Renamed test.

* Removed test imports.
2019-09-10 13:30:20 -07:00
Liad Yosef
c317fc273b Correct link for troubleshooting react-dev-tools (#16690) (#16708)
* Correct link for troubleshooting react-dev-tools (#16690)

As pointed out in #16690 - the link for 'React Tab Doesn't Show Up' points to the empty README.MD.
This points it to that section in the v3 version README.MD - until an updated section will be added to the new dev-tools.

* Add a "The React Tab Doesn't Show Up" section

Add the troubleshooting section to the react dev tools readme

* point to the correct section in react-dev-tools readme

After adding the troubleshooting section to the readme - this will point to the correct place

* Moved README file to GitHub

* Update new issue link to include DevTools label
2019-09-10 13:14:19 -07:00
Nicolas Gallagher
41a78cd85c [react-events] Tap: add maximumDistance prop (#16689)
A prop for configuring the maximum distance that the active pointer can move before the tap is cancelled.
2019-09-10 12:52:43 -07:00
Dan Abramov
2400400788 react-refresh@0.4.2 2019-09-10 20:46:34 +01:00
Dan Abramov
ba6bb0fccf [Fresh] Hash signatures (#16738) 2019-09-10 20:28:13 +01:00
Dominic Gannaway
fd3e8cb0ae [react-events] Remove stopPropagation (Press) + use document for delegation (#16730) 2019-09-10 20:31:24 +02:00
Heaven
38c03ce006 Fix typo in commet (#16727) 2019-09-10 10:27:38 -07:00
Hristo Kanchev
4905590e1e Fixed font family issue in FF. (#16701) 2019-09-09 16:21:04 -07:00
Daniel Lo Nigro
35f447ddbf Remove console.log from copyWithSet (#16716) 2019-09-09 16:18:09 -07:00
Sebastian Markbåge
440cbf2ee5 Let's schedule the passive effects even earlier (#16714)
It turns out I needed to schedule mine in the mutation phase and there
are also clean up life-cycles there.
2019-09-09 15:06:03 -07:00
Sebastian Markbåge
cc2492ccf1 Schedule passive callbacks before layout effects are invoked (#16713) 2019-09-09 13:34:57 -07:00
Nicolas Gallagher
031eba789f [react-events] Tap: change order of events (#16694)
Before:

start -> change -> update -> end (cancel) -> change

Now:

start -> change -> update -> change -> end (cancel)
2019-09-09 09:08:17 -07:00
Nicolas Gallagher
f26fe8c0a7 [react-events] Keyboard: fix callback return types (#16693) 2019-09-09 09:07:42 -07:00
Heaven
9444c876d5 Remove wrong copy-paste code in test (#16695) 2019-09-09 11:34:08 +01:00
Dan Abramov
b260bef398 [Fresh] Add skipEnvCheck option to Babel plugin (#16688) 2019-09-06 20:30:16 +01:00
Dan Abramov
2f15881859 react-refresh@0.4.1 2019-09-06 20:02:43 +01:00
Dan Abramov
9044bb0fa3 [Fresh] Fix a crash with implicit arrow return (#16687) 2019-09-06 19:58:07 +01:00
Dan Abramov
21d79ce040 Add FreshRuntime WWW bundle, remove ESLint (#16684) 2019-09-06 16:48:07 +01:00
Alex Rohleder
206d61f722 fix typos on react-devtools comments (#16681) 2019-09-06 07:47:44 -07:00
Heaven
61836fba2a Fix typo: wnless -> unless (#16680) 2019-09-06 07:47:35 -07:00
Sebastian Markbåge
e11bf42cea Check for Suspense boundary in a root Container (#16673)
If we find a Container that might mean that we're on a node that is inside
a Suspense boundary that is directly inside the Container root.

Imagine the div is a Container and the span is a dehydrated instance:

```
<div>
  <!--$-->
  <span />
  <!--/$-->
</div>
```

There's no way to tests this yet since I'm not actually utilizing
the return value yet.

The solution is to just use the same path to check for a Suspense boundary
as if we find a parent instance.
2019-09-05 16:06:05 -07:00
Dan Abramov
962dfc2c33 Remove experimental scheduler flags (#16672) 2019-09-05 20:08:06 +01:00
Nicolas Gallagher
ff006451ad [react-events] Fix isTargetWithinNode type (#16671)
isTargetWithinNode passes the childTarget to getClosestInstanceFromNode which
does not account for a null value of 'node'.
2019-09-05 11:36:31 -07:00
Dan Abramov
040ca0fad7 Enable MessageLoop implementation by default (#16408) 2019-09-05 19:25:51 +01:00
Brian Vaughn
d96f478f8a use-subscription tearing fix (#16623)
* Add (failing) subscription tearing test and bugfix
* Added more inline comments to test
* Simplified tearing test case slightly
2019-09-05 11:12:46 -07:00
Luna Ruan
79e46b6778 updated flags from false to dicated on www (#16647) 2019-09-05 09:51:44 -07:00
Sebastian Markbåge
8d7c733f1f [Partial Hydration] Don't invoke listeners on parent of dehydrated event target (#16591)
* Don't invoke listeners on parent of dehydrated event target

* Move Suspense boundary check to getClosestInstanceFromNode

Now getClosestInstanceFromNode can return either a host component,
host text component or suspense component when the suspense
component is dehydrated.

We then use that to ignore events on a suspense component.

* Attach the HostRoot fiber to the DOM container

This lets us detect if an event happens on this root's subtree before it
has rendered something.

* Add todo

The approach of checking isFiberMounted answers if we might be in an
in-progress hydration but it doesn't answer which root or boundary
might be in-progress so we don't know what to wait for.

This needs some refactoring.

* Refactor isFiberMountedImpl to getNearestMountedFiber

We'll need the nearest boundary for event replaying so this prepares for
that.

This surfaced an issue that we attach Hydrating tag on the root but normally
this (and Placement) is attached on the child. This surfaced an issue
that this can lead to both Placement and Hydrating effects which is not
supported so we need to ensure that we only ever use one or the other.

* Add todo for bug I spotted

* Cache tags

* Check the ContainerInstanceKey before the InstanceKey

The container is inside the instance, so we must find it before the
instance, since otherwise we'll miss it.
2019-09-05 08:51:31 -07:00
Nicolas Gallagher
9ce8711d5a [react-events] Tap responder (#16628)
This is a partial replacement for the 'Press' responder:

1. `useTap` is scoped to pointers (no keyboard support). Our current thinking is
that "responders" should be limited to working with pointers, and that they can
be combined with 'useKeyboard' in user-space. For example, we might create a
'usePress' hook in user-space that combines 'useTap' with 'useKeyboard' to react
to both pointers and keyboard interactions.

2. `useTap` cancels the gesture once the pointer moves over an element that is
not within the responder target's subtree. This differs from `usePress` (and
React Native), where the gesture remains active after the pointer exits the
target's subtree and is restarted once the pointer reenters. One of the
drawbacks with the `usePress` behavior is that it requires repeatedly measuring
DOM elements (which can cause jank) to perform hit region tests. `useTap` avoids
doing this and relies on `document.elementFromPoint` only to support the
TouchEvent fallbacks.

3. `useTap` calls `onTapUpdate` when the active gesture's state changes,
`onTapEnd` when the gesture successfully completes. and `onTapCancel` when it
fails. There is no `onTap` callback. `usePress` did not explicitly report back
when the gesture failed, and product developers were confused about the
difference between `onPress` and `onPressEnd`.

4. `useTap` explicitly separates the PointerEvent implementation from the
MouseEvent/TouchEvent fallback.

5. `useTap` has better unit test coverage . All pointer types and the fallback
environment are tested. The shape of the gesture state object is also defined
and tested.
2019-09-04 17:09:33 -07:00
Dominic Gannaway
e86146e714 [react-events] Refine executeUserEventHandler (#16662) 2019-09-05 00:36:29 +01:00
Dominic Gannaway
c66edb9f8b [react-events] Refactor getCurrentTarget to getResponderNode (#16660) 2019-09-04 20:04:35 +01:00
Dominic Gannaway
9ff60ff16b [react-events] Fix Scope listener issue (#16658) 2019-09-04 18:57:39 +01:00
Dominic Gannaway
7126a37bf4 [react-events] Keyboard responder propagation handling (#16657) 2019-09-04 18:25:05 +01:00
Dominic Gannaway
539640d89f [react-events] Various core tweaks for event responder system (#16654) 2019-09-04 18:05:56 +01:00
Dominic Gannaway
af032764a9 [react-events] Adds preventKeys support to Keyboard responder (#16642) 2019-09-04 01:38:36 +01:00
Brian Vaughn
f705e2bac7 Updated pending CHANGELOG for DevTools 2019-09-03 08:37:12 -07:00
Hristo Kanchev
77bb102398 [DevTools] [Profiler]: Save profile now working in Firefox (#16612)
* Added anchor dom element in order to successfully download profiling data.
* Reworked downloadFile to accept a DOMElement in order for FF to successfully download profiling data.
* Prettify downloadFile changes.
2019-09-03 08:35:12 -07:00
Heaven
92f094d86d fix typo: oncurrent - concurrent (#16633) 2019-09-02 11:21:48 +01:00
Dominic Gannaway
46f912fd57 [react-core] Add more support for experimental React Scope API (#16621) 2019-08-30 18:27:14 +01:00
Paul O’Shannessy
2c1e6bf619 Adopt Contributor Covenant (#16613)
In order to foster healthy open source communities, we're adopting the
[Contributor Covenant](https://www.contributor-covenant.org/). It has been
built by open source community members and represents a shared understanding of
what is expected from a healthy community.
2019-08-29 16:04:47 -07:00
Brian Vaughn
f962feb882 Updated extensions build-from-source instructions in README 2019-08-29 08:49:08 -07:00
Simen Bekkhus
ac6193687f add integrity field in yarn.lock (#16601) 2019-08-29 16:36:02 +01:00
Sebastian Silbermann
16329bd954 chore: Update nvmrc with latest lts (#16610) 2019-08-29 16:30:35 +01:00
Dominic Gannaway
4e544cffee [react-events] Split out mixed event responder tests (#16608) 2019-08-29 16:13:37 +01:00
Gerald Monaco
f61138e068 Use renderToStaticMarkup for tests (#16516) 2019-08-29 16:08:38 +01:00
James George
980112b146 rephrase comment (#16559) 2019-08-29 14:51:07 +01:00
Bas Peeters
8a7c2e50f1 Remove duplicate character in regex group (#16572) 2019-08-29 14:49:27 +01:00
Tom Quirk
557d472fe3 add <thead>, <tfoot> to table > tr warning (#16535) 2019-08-29 14:46:27 +01:00
Simen Bekkhus
37fcd04681 chore: upgrade to danger 9 (#16602) 2019-08-29 13:46:53 +01:00
Dominic Gannaway
bd79be9b68 [react-core] Add experimental React Scope component API (#16587) 2019-08-29 12:06:51 +01:00
Brian Vaughn
996acf9036 Updated DevTools extension build script to work when run remotely (#16603) 2019-08-28 17:01:26 -07:00
Dominic Gannaway
34aaec6f90 [react-events] Ensure screen reader virtual clicks support preventDefault (#16600) 2019-08-28 17:39:07 +01:00
Dan Abramov
01fb68b9bf Don't ignore dependencies for render phase update (#16574) 2019-08-28 16:55:56 +01:00
LEE SUK JAE
1b585f630b Fix link on error-codes/README.md (#16595) 2019-08-28 15:49:45 +01:00
Brian Vaughn
0f3e82f3c4 Merge branch 'devtools-v4-merge' 2019-08-28 07:39:49 -07:00
Brian Vaughn
b8390310b1 Revert "Import React DevTools v4"
This reverts commit b438699d36.
2019-08-28 07:39:32 -07:00
Brian Vaughn
b438699d36 Import React DevTools v4
Imports the entire React DevTools v4 git repository (with history).
2019-08-28 07:35:53 -07:00
Brian Vaughn
ada5991422 Removed an unnecessary/unused DT Flow type 2019-08-28 07:28:06 -07:00
Brian Vaughn
08ce280cbc Updated DT regression fixture path 2019-08-28 07:25:52 -07:00
Brian Vaughn
b034ac6d38 Merge branch 'master' into devtools-v4-merge 2019-08-28 07:13:49 -07:00
Luna Ruan
f512537754 Babel Transform JSX to React.jsx/React.jsxDEV Plugin (#16432)
This babel transform is a fork of the @babel/plugin-transform-react-jsx transform and is for experimentation purposes only. We don't plan to own this code in the future, and we will upstream this to Babel at some point once we've proven out the concept.

As per the RFC to simplify element creation, we want to change the JSX transform from targeting React.createElement(type, props, children) to React.jsx(type, props, key). This modifies the existing @babel/plugin-transform-react-jsx (and helper) babel plugin to support React.jsx and React.jsxDEV.

The main differences between React.jsx/React.jsxDEV and React.createElement are:
1.) key is now passed as an explicit argument rather than through props
3.) children are now passed through props rather than as an explicit argument
4.) props must always be an object
5.) __source and and __self are now passed as separate arguments into React.jsxDEV rather than through props

Part of the rationale for this change is that we want to deprecate key spread through props because this is an expensive dynamic comparison operation. We want users instead always explicitly pass key as a prop. However, in the interim, we need a way to distinguish between <div {...props} key={foo} /> and <div key={foo} {...props} />. Therefore, until we completely deprecate key spreading, we will use React.createElement to transform <div {...props} key="Hi" /> and React.jsx to transform everything else.
2019-08-27 16:00:20 -07:00
Nicolas Gallagher
cb15f18dc1 [react-events] Improve mock event object accuracy (#16590)
* Better simulation for pointercancel
* Fix pressure values for different pointers
* Add describe/test helpers for pointer events
2019-08-27 15:45:54 -07:00
Brian Vaughn
bc8b15332b Updated README docs, example screenshots, etc 2019-08-27 15:20:34 -07:00
Brian Vaughn
7153dd516f Fixed a StyleEditor variable resolution regression 2019-08-27 12:53:38 -07:00
Brian Vaughn
33d439f8fd Merge branch 'master' into devtools-v4-merge 2019-08-27 11:00:41 -07:00
Brian Vaughn
fb316787cb Removed unused Chrome Flow types 2019-08-27 10:54:21 -07:00
Brian Vaughn
8e1434e80e Added FB copyright header 2019-08-27 10:54:01 -07:00
Brian Vaughn
49b0f87d10 Suppress act/renderer warning for DevTools tests 2019-08-27 10:46:27 -07:00
Brian Vaughn
8c684bf7e1 Removed forked DevTools Flow types 2019-08-27 10:42:02 -07:00
Brian Vaughn
9a016c0c2b Removed outdated snapshot 2019-08-27 10:31:36 -07:00
Brian Vaughn
f55b4f8e28 CI runs DevTools tests against built source 2019-08-27 10:28:58 -07:00
Dominic Gannaway
4ef269606c [react-events] Support screen reader virtual clicks (#16584) 2019-08-27 17:32:50 +01:00
Brian Vaughn
892ca8137e Disabled DevTools tests from yarn-build target 2019-08-27 09:08:49 -07:00
Brian Vaughn
896c993ada Fixed remaining DevTools broken tests by fixing a hydration/spread bug 2019-08-27 08:50:36 -07:00
Brian Vaughn
e3cc42be97 Fix Console patching test by resetting modules 2019-08-26 13:53:29 -07:00
Brian Vaughn
177f357d9d Updated DevTools test setup to no longer mock test renerer 2019-08-26 13:46:00 -07:00
Brian Vaughn
a48593a8d3 Iterating on DevTools tests: Trying to run tests against pre-build react-dom and react-test-renderers 2019-08-26 13:43:09 -07:00
Brian Vaughn
ee4806f47a Fixed flushing problem with tests 2019-08-26 13:08:35 -07:00
Brian Vaughn
9d4fd7a249 Merged changes from 4.0.5 -> 4.0.6 from DevTools fork 2019-08-26 10:12:18 -07:00
Brian Vaughn
4c1514495b Merge branch 'source' of github.com:bvaughn/react-devtools-experimental 2019-08-26 10:06:00 -07:00
Brian Vaughn
13a93e7b82 Configured CI to run DevTools tests. Updated other test configs not to include DevTools tests. 2019-08-26 09:58:28 -07:00
Brian Vaughn
c00a920640 Merge branch 'master' into devtools-v4-merge 2019-08-26 09:42:25 -07:00
Brian Vaughn
0da7bd0604 React DevTools CHANGELOG entry for 4.0.6 2019-08-26 09:34:06 -07:00
Brian Vaughn
a39d9c3dff 4.0.5 -> 4.0.6 2019-08-26 08:42:38 -07:00
Brian Vaughn
1e3b0b520e Added a null check around memoizedProps 2019-08-24 09:04:26 -07:00
Brian Vaughn
84b492f344 Polyfill Symbol usage 2019-08-23 17:38:45 -06:00
Dominic Gannaway
fc80772078 [react-events] Ensure updateEventListeners updates in commit phase (#16540) 2019-08-22 23:58:16 +01:00
Dan Abramov
0f6e3cd61c [Scheduler] Profiler Features (second try) (#16542)
* Revert "Revert "[Scheduler] Profiling features (#16145)" (#16392)"

This reverts commit 4ba1412305.

* Fix copy paste mistake

* Remove init path dependency on ArrayBuffer

* Add a regression test for cancelling multiple tasks

* Prevent deopt from adding isQueued later

* Remove pop() calls that were added for profiling

* Verify that Suspend/Unsuspend events match up in tests

This currently breaks tests.

* Treat Suspend and Resume as exiting and entering work loop

Their definitions used to be more fuzzy. For example, Suspend didn't always fire on exit, and sometimes fired when we did _not_ exit (such as at task enqueue).

I chatted to Boone, and he's saying treating Suspend and Resume as strictly exiting and entering the loop is fine for their use case.

* Revert "Prevent deopt from adding isQueued later"

This reverts commit 9c30b0b695d81e9c43b296ab93d895e4416ef713.

Unnecessary because GCC

* Start counter with 1

* Group exports into unstable_Profiling namespace

* No catch in PROD codepath

* No label TODO

* No null checks
2019-08-22 13:58:12 -07:00
Nicolas Gallagher
474b650cac [react-events] Rename hook exports (#16533)
For example, 'useHoverResponder' becomes 'useHover'
2019-08-22 13:30:35 -07:00
Nicolas Gallagher
2f03aa6eed [react-events] Fix middle-click for Press (#16546)
Browsers always report 'buttons' as 0 when a pointer is released.
2019-08-22 10:22:14 -07:00
Bruno Scopelliti
16c3408638 Only warn in case the fourth argument is a function (#16543) 2019-08-22 17:31:27 +01:00
Sebastian Markbåge
05f5192e81 [Partial Hydration] Dispatching events should not work until hydration commits (#16532)
* Refactor a bit to use less property access

* Add test for invoking an event before mount

* Add Hydration effect tag

This is equivalent to a "Placement" effect in that it's a new insertion
to the tree but it doesn't need an actual mutation.

It is only used to determine if a subtree has actually mounted yet.

* Use the Hydration flag for Roots

Previous roots had a Placement flag on them as a hack for this case but
since we have a special flag for it now, we can just use that.

* Add Flare test
2019-08-22 08:46:20 -07:00
Brian Vaughn
bf9415834f Fixed outdated extension popup wording 2019-08-21 14:28:57 -07:00
Brian Vaughn
6dda816102 Remove emoji prefix from Firefox extension tab labels
Firefox adds these already for React, so the extra emoji character was confusing. For now, we still prepend it for Chrome
2019-08-21 14:23:51 -07:00
Brian Vaughn
79bda69d88 Renamed DevTools Jest config to be more concistent with others 2019-08-21 13:40:24 -07:00
Dan Abramov
8a01b50fc3 eslint-plugin-react-hooks@2.0.1 2019-08-21 21:40:20 +01:00
Dan Abramov
3ed289b3b1 Clear canceled task node early (#16403) 2019-08-21 19:49:22 +01:00
Dan Abramov
0672829053 Bump ESLint plugin to 2.0 (#16528) 2019-08-21 19:14:34 +01:00
Nicolas Gallagher
2559111c21 [react-events] Rely on 'buttons' rather than 'button' (#16479)
The semantics of 'button' on events differs between PointerEvent and
MouseEvent, whereas they are the same for 'buttons'. Furthermore, 'buttons'
allows developers to determine when multiple buttons are pressed as the same
time.

https://w3c.github.io/pointerevents/#the-button-property
2019-08-21 10:07:15 -07:00
Dan Abramov
c433fbb593 Revert "Revert "[ESLint] Forbid top-level use*() calls (#16455)"" (#16525)
* Revert "Revert "[ESLint] Forbid top-level use*() calls (#16455)" (#16522)"

This reverts commit 507f0fb372.

* Update RulesOfHooks.js
2019-08-21 15:43:31 +01:00
Sunil Pai
507f0fb372 Revert "[ESLint] Forbid top-level use*() calls (#16455)" (#16522)
This reverts commit 96eb703bbf.
2019-08-21 10:20:34 +01:00
Brian Vaughn
66c9fedc34 Flow fixes 2019-08-20 13:33:33 -07:00
Brian Vaughn
fe943c339d Add custom DevTools Flow definitions to shared flowconfig 2019-08-20 11:37:45 -07:00
Brian Vaughn
2e549efae5 Moved DevTools custom Flow definitions 2019-08-20 11:37:31 -07:00
Brian Vaughn
4da836af71 Merged changes from 4.0.0 -> 4.0.5 from DevTools fork 2019-08-20 11:34:51 -07:00
Brian Vaughn
3ad50710a7 Merge branch 'source' of github.com:bvaughn/react-devtools-experimental 2019-08-20 11:00:02 -07:00
Brian Vaughn
833f206348 Merge branch 'master' into devtools-v4-merge 2019-08-20 10:41:38 -07:00
bbolek
efa5dbe7a5 Update CHANGELOG.md (#16439)
* Update CHANGELOG.md

Fixed typo
2019-08-20 09:51:01 -07:00
Heaven
da0a47bec3 fix typo in CHNAGELOG.md (#16447) 2019-08-20 09:48:03 -07:00
Morgan McCauley
69aafbf4df Fix spelling in react-devtools CHANGELOG.md (#16448) 2019-08-20 09:47:18 -07:00
Brian Vaughn
2843a1556e Fixed invalid object-assign version (4.0.4 -> 4.0.1) 2019-08-20 09:45:09 -07:00
Brian Vaughn
aa56fe34ac Improved GitHub issue repro instructions prompt 2019-08-20 09:44:41 -07:00
Sebastian Markbåge
c80678c760 Add "hydrationOptions" behind the enableSuspenseCallback flag (#16434)
This gets invoked when a boundary is either hydrated or if it is deleted
because it updated or got deleted before it mounted.
2019-08-19 13:26:39 -07:00
Dan Abramov
2d68bd0960 Fix message loop behavior when host callback is cancelled (#16407)
* Add a regression test for cancelCallback with message loop

* If there's nothing scheduled, we're not running

* Add more tests from #16271
2019-08-19 21:20:21 +01:00
Dan Abramov
96eb703bbf [ESLint] Forbid top-level use*() calls (#16455)
* Add a way to skip/only tests to RulesOfHooks test

* [ESLint] Forbid top-level use*() calls

* Add a regression test for logical expressions

This is not a change. Just adding more coverage.
2019-08-19 19:54:06 +01:00
Dan Abramov
56f93a7f38 Throw on unhandled SSR suspending (#16460)
* Throw on unhandled SSR suspending

* Add a nicer message when the flag is off

* Tweak internal refinement error message
2019-08-19 19:53:02 +01:00
Dominic Gannaway
dce430ad92 [Flare] Rework the responder dispatching/batching mechanism (#16334) 2019-08-19 19:22:46 +01:00
Brian Vaughn
6ae6a7c020 Updated React DevTools changelog for 4.0.5 2019-08-19 09:28:10 -07:00
Brian Vaughn
5441b094a4 4.0.4 -> 4.0.5 2019-08-19 09:26:55 -07:00
Nicolas Gallagher
56d1b0fb59 [react-events] DOM event testing library (#16433)
This patch formalizes the mock native events and event sequences used in unit tests.

The `createEventTarget` function returns an object that can be used to dispatch native event sequences on the target without having to manually do so across all the scenarios we need to account for. Unit tests can be written as if we were only working with PointerEvent, but they will dispatch realistic native event sequences based on the execution environment (e.g., is PointerEvent supported?) and pointer type.

```
describe.each(environments)('Suite', (hasPointerEvents) => {
  beforeEach(() => {
    // setup
  });

  test.each(pointerTypes)('Test', (pointerType) => {
    const target = createEventTarget(node);
    target.pointerdown({pointerType});
    expect(callback).toBeCalled();
  });
});
```

Every native event that is dispatched now includes a complete object by default. The properties of the events can be customized. Properties that shouldn't be relied on in responder implementations are excluded from the mock native events to ensure tests will fail. Equivalent properties are normalized across different event types, e.g., 'pointerId' is converted to 'identifier' before a TouchEvent is dispatched.
2019-08-19 09:21:55 -07:00
Brian Vaughn
01b1e7e2cf Alpha-sort props/state/context keys 2019-08-19 09:17:52 -07:00
Brian Vaughn
d2456c7572 Fixed standalone target not properly serving backend over localhost:8097 2019-08-19 07:36:41 -07:00
Brian Vaughn
14c2eab7c3 Resolved Yarn conflict for object-assign 2019-08-19 07:04:47 -07:00
Brian Vaughn
e89c19d16c Added DevTools 4.0.4 CHANGELOG entry 2019-08-18 08:55:49 -07:00
Brian Vaughn
3c6a219466 4.0.3 -> 4.0.4 2019-08-18 08:45:52 -07:00
Brian Vaughn
4697f5b379 Profiler bugfix for filtering out all commits after selecting a fiber 2019-08-18 08:34:40 -07:00
Brian Vaughn
d97af798d2 Updated DevTools CHANLOGE to add an unreleased change 2019-08-17 21:19:00 -07:00
Brian Vaughn
95ca079556 Fixed standalone bug that prevented backend from being served over localhost:8097 2019-08-17 21:11:10 -07:00
Brian Vaughn
21e793fb4f Added 4.0.1, 4.0.2, and 4.0.3 changelog entries (#16438)
* Added 4.0.1, 4.0.2, and 4.0.3 changelog entries
* Added entry about Map/Set/Immutable
2019-08-17 11:47:38 -07:00
Brian Vaughn
95ffd3ccf8 4.0.2 -> 4.0.3 2019-08-17 11:31:31 -07:00
Brian Vaughn
2935d6a18d Unserializable data type fix for standalone shell 2019-08-17 11:31:20 -07:00
Brian Vaughn
ce65df7092 Added support for unserializable types (e.g. Set/Map, Immutable) 2019-08-17 10:20:26 -07:00
Brian Vaughn
f66a20f439 Caps lock bug template request for repro steps :) 2019-08-16 14:47:55 -07:00
Brian Vaughn
69b2ecc531 Added explicit (empty) label next to empty arrays and objects 2019-08-16 13:47:09 -07:00
Brian Vaughn
024bac4263 Support single-quote strings in style editor 2019-08-16 13:35:30 -07:00
Brian Vaughn
74f4a3f972 Improved editing props demo experience for TODO list 2019-08-16 13:15:16 -07:00
Brian Vaughn
f616613059 Display hook values for components that only use context 2019-08-16 11:33:28 -07:00
Brian Vaughn
454157dd66 Multi-renderer profiling improvements
Add support for mixed v15/v16 renderers that previously caused profiling to fail with 'profiling not supported by this renderer' type errors
2019-08-16 11:25:44 -07:00
Brian Vaughn
49399aa3e7 Prettier 2019-08-16 09:05:26 -07:00
Brian Vaughn
5e043adba7 Added Set+Map to dev shell, even though we don't support deep inspecting them yet 2019-08-16 09:05:21 -07:00
Dan Abramov
c1d3f7f1a9 [DevTools Changelog] Add a note on 4.0.2 2019-08-16 16:15:19 +01:00
Brian Vaughn
2bcc6c6d04 4.0.1 -> 4.0.2 2019-08-15 17:35:30 -07:00
Brian Vaughn
527fc4a63f Refactored to remove need for new webNavigation permission 2019-08-15 17:34:34 -07:00
Brian Vaughn
c100cc7b31 4.0.0 -> 4.0.1 2019-08-15 16:29:13 -07:00
Brian Vaughn
545de6f02e Removed some unnecessary manifest permissions 2019-08-15 16:28:10 -07:00
Dan Abramov
6f86294e68 [DevTools Changelog] Add a note about restoring selection (#16409)
Also a tiny nit, "inline" spelling seems more common in this context. My eyes stumbled at it on every read.
2019-08-15 23:11:37 +01:00
Brian Vaughn
d0dcbe5594 Changed version name string to show version and date. Moved commit number into description string. 2019-08-15 14:38:16 -07:00
Brian Vaughn
0763c48ed8 Bumped all versions to 4.0.0 2019-08-15 14:28:09 -07:00
Brian Vaughn
732f3a6ef1 4.0.0-alpha.9 -> 4.0.0-alpha.10 2019-08-15 11:26:04 -07:00
Brian Vaughn
600c57a9b9 Added OVERVIEW.md and updated CHANGELOG to point to it (#16405) 2019-08-15 11:22:11 -07:00
Brian Vaughn
db9e5c9715 Updated all GitHub links to point to React repo 2019-08-15 11:17:59 -07:00
Brian Vaughn
9b5985b3c1 Added release date to DevTools CHANGELOG 2019-08-15 10:40:59 -07:00
Nicolas Gallagher
ebd1f5ddb0 [react-events] Press: improve test coverage (#16397)
1. Run the tests in both an environment without PointerEvent and one with PointerEvent.
2. Improve test coverage to include both mouse and touch pointers.
3. Change 'Press' so that it only listens to either pointer events or fallbacks events.
2019-08-15 10:28:00 -07:00
Brian Vaughn
85fbe3be3f Merge branch 'master' into devtools-v4-merge 2019-08-15 10:14:34 -07:00
Brian Vaughn
a9304e79d4 Add DevTools package placeholder package.json 2019-08-15 10:12:27 -07:00
Brian Vaughn
6edff8f5e1 Added CHANGELOG and READMEs for DevTools v4 NPM packages (#16404) 2019-08-15 10:06:16 -07:00
Brian Vaughn
7ce229d3b0 Made some incremental progress on Jest tests 2019-08-15 09:53:22 -07:00
Brian Vaughn
a6aba5a587 Add explicit MIT license file (previously only in package.json) 2019-08-14 17:04:05 -07:00
481 changed files with 22571 additions and 7455 deletions

View File

@@ -158,6 +158,16 @@ jobs:
- *run_yarn
- run: yarn test-build --maxWorkers=2
test_build_devtools:
docker: *docker
environment: *environment
steps:
- checkout
- attach_workspace: *attach_workspace
- *restore_yarn_cache
- *run_yarn
- run: yarn test-build-devtools --maxWorkers=2
test_dom_fixtures:
docker: *docker
environment: *environment
@@ -231,6 +241,9 @@ workflows:
- test_build_prod:
requires:
- build
- test_build_devtools:
requires:
- build
- test_dom_fixtures:
requires:
- build

View File

@@ -142,10 +142,13 @@ module.exports = {
],
globals: {
SharedArrayBuffer: true,
spyOnDev: true,
spyOnDevAndProd: true,
spyOnProd: true,
__PROFILE__: true,
__UMD__: true,
trustedTypes: true,
},
};

2
.nvmrc
View File

@@ -1 +1 @@
v8.4.0
v10.16.3

View File

@@ -41,6 +41,10 @@
* Warn in Strict Mode if effects are scheduled outside an `act()` call. ([@threepointone](https://github.com/threepointone) in [#15763](https://github.com/facebook/react/pull/15763) and [#16041](https://github.com/facebook/react/pull/16041))
* Warn when using `act` from the wrong renderer. ([@threepointone](https://github.com/threepointone) in [#15756](https://github.com/facebook/react/pull/15756))
### ESLint Plugin: React Hooks
* Report Hook calls at the top level as a violation. ([gaearon](https://github.com/gaearon) in [#16455](https://github.com/facebook/react/pull/16455))
## 16.8.6 (March 27, 2019)
### React DOM

View File

@@ -1,3 +1,76 @@
# Code of Conduct
Facebook has adopted a Code of Conduct that we expect project participants to adhere to. Please [read the full text](https://code.fb.com/codeofconduct/) so that you can understand what actions will and will not be tolerated.
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to make participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies within all project spaces, and it also applies when
an individual is representing the project or its community in public spaces.
Examples of representing a project or community include using an official
project e-mail address, posting via an official social media account, or acting
as an appointed representative at an online or offline event. Representation of
a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at <opensource-conduct@fb.com>. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq

View File

@@ -23,8 +23,8 @@
<br/><br/>
Use the <code>server</code> script instead:
<br/><br/>
<code>node ./fixtures/regression/server.js</code><br/>
<code>open <a href="http://localhost:3000/fixtures/regression/14.9.html">http://localhost:3000/fixtures/regression/14.9.html</a></code>
<code>node ./fixtures/devtools/regression/server.js</code><br/>
<code>open <a href="http://localhost:3000/14.9.html">http://localhost:3000/14.9.html</a></code>
</div>
<script src="shared.js" type="text/babel"></script>

View File

@@ -23,8 +23,8 @@
<br/><br/>
Use the <code>server</code> script instead:
<br/><br/>
<code>node ./fixtures/regression/server.js</code><br/>
<code>open <a href="http://localhost:3000/fixtures/regression/15.0.html">http://localhost:3000/fixtures/regression/15.0.html</a></code>
<code>node ./fixtures/devtools/regression/server.js</code><br/>
<code>open <a href="http://localhost:3000/15.0.html">http://localhost:3000/15.0.html</a></code>
</div>
<script src="shared.js" type="text/babel"></script>

View File

@@ -23,8 +23,8 @@
<br/><br/>
Use the <code>server</code> script instead:
<br/><br/>
<code>node ./fixtures/regression/server.js</code><br/>
<code>open <a href="http://localhost:3000/fixtures/regression/15.1.html">http://localhost:3000/fixtures/regression/15.1.html</a></code>
<code>node ./fixtures/devtools/regression/server.js</code><br/>
<code>open <a href="http://localhost:3000/15.1.html">http://localhost:3000/15.1.html</a></code>
</div>
<script src="shared.js" type="text/babel"></script>

View File

@@ -23,8 +23,8 @@
<br/><br/>
Use the <code>server</code> script instead:
<br/><br/>
<code>node ./fixtures/regression/server.js</code><br/>
<code>open <a href="http://localhost:3000/fixtures/regression/15.2.html">http://localhost:3000/fixtures/regression/15.2.html</a></code>
<code>node ./fixtures/devtools/regression/server.js</code><br/>
<code>open <a href="http://localhost:3000/15.2.html">http://localhost:3000/15.2.html</a></code>
</div>
<script src="shared.js" type="text/babel"></script>

View File

@@ -23,8 +23,8 @@
<br/><br/>
Use the <code>server</code> script instead:
<br/><br/>
<code>node ./fixtures/regression/server.js</code><br/>
<code>open <a href="http://localhost:3000/fixtures/regression/15.3.html">http://localhost:3000/fixtures/regression/15.3.html</a></code>
<code>node ./fixtures/devtools/regression/server.js</code><br/>
<code>open <a href="http://localhost:3000/15.3.html">http://localhost:3000/15.3.html</a></code>
</div>
<script src="shared.js" type="text/babel"></script>

View File

@@ -23,8 +23,8 @@
<br/><br/>
Use the <code>server</code> script instead:
<br/><br/>
<code>node ./fixtures/regression/server.js</code><br/>
<code>open <a href="http://localhost:3000/fixtures/regression/15.4.html">http://localhost:3000/fixtures/regression/15.4.html</a></code>
<code>node ./fixtures/devtools/regression/server.js</code><br/>
<code>open <a href="http://localhost:3000/15.4.html">http://localhost:3000/15.4.html</a></code>
</div>
<script src="shared.js" type="text/babel"></script>

View File

@@ -23,8 +23,8 @@
<br/><br/>
Use the <code>server</code> script instead:
<br/><br/>
<code>node ./fixtures/regression/server.js</code><br/>
<code>open <a href="http://localhost:3000/fixtures/regression/15.5.html">http://localhost:3000/fixtures/regression/15.5.html</a></code>
<code>node ./fixtures/devtools/regression/server.js</code><br/>
<code>open <a href="http://localhost:3000/15.5.html">http://localhost:3000/15.5.html</a></code>
</div>
<script src="shared.js" type="text/babel"></script>

View File

@@ -23,8 +23,8 @@
<br/><br/>
Use the <code>server</code> script instead:
<br/><br/>
<code>node ./fixtures/regression/server.js</code><br/>
<code>open <a href="http://localhost:3000/fixtures/regression/15.6.html">http://localhost:3000/fixtures/regression/15.6.html</a></code>
<code>node ./fixtures/devtools/regression/server.js</code><br/>
<code>open <a href="http://localhost:3000/15.6.html">http://localhost:3000/15.6.html</a></code>
</div>
<script src="shared.js" type="text/babel"></script>

View File

@@ -23,8 +23,8 @@
<br/><br/>
Use the <code>server</code> script instead:
<br/><br/>
<code>node ./fixtures/regression/server.js</code><br/>
<code>open <a href="http://localhost:3000/fixtures/regression/16.0.html">http://localhost:3000/fixtures/regression/16.0.html</a></code>
<code>node ./fixtures/devtools/regression/server.js</code><br/>
<code>open <a href="http://localhost:3000/16.0.html">http://localhost:3000/16.0.html</a></code>
</div>
<script src="shared.js" type="text/babel"></script>

View File

@@ -23,8 +23,8 @@
<br/><br/>
Use the <code>server</code> script instead:
<br/><br/>
<code>node ./fixtures/regression/server.js</code><br/>
<code>open <a href="http://localhost:3000/fixtures/regression/16.1.html">http://localhost:3000/fixtures/regression/16.1.html</a></code>
<code>node ./fixtures/devtools/regression/server.js</code><br/>
<code>open <a href="http://localhost:3000/16.1.html">http://localhost:3000/16.1.html</a></code>
</div>
<script src="shared.js" type="text/babel"></script>

View File

@@ -23,8 +23,8 @@
<br/><br/>
Use the <code>server</code> script instead:
<br/><br/>
<code>node ./fixtures/regression/server.js</code><br/>
<code>open <a href="http://localhost:3000/fixtures/regression/16.2.html">http://localhost:3000/fixtures/regression/16.2.html</a></code>
<code>node ./fixtures/devtools/regression/server.js</code><br/>
<code>open <a href="http://localhost:3000/16.2.html">http://localhost:3000/16.2.html</a></code>
</div>
<script src="shared.js" type="text/babel"></script>

View File

@@ -23,8 +23,8 @@
<br/><br/>
Use the <code>server</code> script instead:
<br/><br/>
<code>node ./fixtures/regression/server.js</code><br/>
<code>open <a href="http://localhost:3000/fixtures/regression/16.3.html">http://localhost:3000/fixtures/regression/16.3.html</a></code>
<code>node ./fixtures/devtools/regression/server.js</code><br/>
<code>open <a href="http://localhost:3000/16.3.html">http://localhost:3000/16.3.html</a></code>
</div>
<script src="shared.js" type="text/babel"></script>

View File

@@ -23,8 +23,8 @@
<br/><br/>
Use the <code>server</code> script instead:
<br/><br/>
<code>node ./fixtures/regression/server.js</code><br/>
<code>open <a href="http://localhost:3000/fixtures/regression/16.4.html">http://localhost:3000/fixtures/regression/16.4.html</a></code>
<code>node ./fixtures/devtools/regression/server.js</code><br/>
<code>open <a href="http://localhost:3000/16.4.html">http://localhost:3000/16.4.html</a></code>
</div>
<script src="shared.js" type="text/babel"></script>

View File

@@ -25,8 +25,8 @@
<br/><br/>
Use the <code>server</code> script instead:
<br/><br/>
<code>node ./fixtures/regression/server.js</code><br/>
<code>open <a href="http://localhost:3000/fixtures/regression/16.5.html">http://localhost:3000/fixtures/regression/16.5.html</a></code>
<code>node ./fixtures/devtools/regression/server.js</code><br/>
<code>open <a href="http://localhost:3000/16.5.html">http://localhost:3000/16.5.html</a></code>
</div>
<script src="shared.js" type="text/babel"></script>

View File

@@ -26,8 +26,8 @@
<br/><br/>
Use the <code>server</code> script instead:
<br/><br/>
<code>node ./fixtures/regression/server.js</code><br/>
<code>open <a href="http://localhost:3000/fixtures/regression/16.6.html">http://localhost:3000/fixtures/regression/16.6.html</a></code>
<code>node ./fixtures/devtools/regression/server.js</code><br/>
<code>open <a href="http://localhost:3000/16.6.html">http://localhost:3000/16.6.html</a></code>
</div>
<script src="shared.js" type="text/babel"></script>

View File

@@ -26,8 +26,8 @@
<br/><br/>
Use the <code>server</code> script instead:
<br/><br/>
<code>node ./fixtures/regression/server.js</code><br/>
<code>open <a href="http://localhost:3000/fixtures/regression/16.7.html">http://localhost:3000/fixtures/regression/16.7.html</a></code>
<code>node ./fixtures/devtools/regression/server.js</code><br/>
<code>open <a href="http://localhost:3000/16.7.html">http://localhost:3000/16.7.html</a></code>
</div>
<script src="shared.js" type="text/babel"></script>

View File

@@ -26,8 +26,8 @@
<br/><br/>
Use the <code>server</code> script instead:
<br/><br/>
<code>node ./fixtures/regression/server.js</code><br/>
<code>open <a href="http://localhost:3000/fixtures/regression/canary.html">http://localhost:3000/fixtures/regression/canary.html</a></code>
<code>node ./fixtures/devtools/regression/server.js</code><br/>
<code>open <a href="http://localhost:3000/canary.html">http://localhost:3000/canary.html</a></code>
</div>
<script src="shared.js" type="text/babel"></script>

View File

@@ -26,8 +26,8 @@
<br/><br/>
Use the <code>server</code> script instead:
<br/><br/>
<code>node ./fixtures/regression/server.js</code><br/>
<code>open <a href="http://localhost:3000/fixtures/regression/next.html">http://localhost:3000/fixtures/regression/next.html</a></code>
<code>node ./fixtures/devtools/regression/server.js</code><br/>
<code>open <a href="http://localhost:3000/next.html">http://localhost:3000/next.html</a></code>
</div>
<script src="shared.js" type="text/babel"></script>

View File

@@ -9,6 +9,7 @@
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/immutable@4.0.0-rc.12/dist/immutable.js"></script>
<!-- Don't use this in production: -->
<script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>
@@ -255,6 +256,33 @@
);
}
const set = new Set(['abc', 123]);
const map = new Map([['name', 'Brian'], ['food', 'sushi']]);
const setOfSets = new Set([new Set(['a', 'b', 'c']), new Set([1, 2, 3])]);
const mapOfMaps = new Map([['first', map], ['second', map]]);
const typedArray = Int8Array.from([100, -100, 0]);
const immutable = Immutable.fromJS({
a: [{ hello: 'there' }, 'fixed', true],
b: 123,
c: {
'1': 'xyz',
xyz: 1,
},
});
function UnserializableProps() {
return (
<ChildComponent
map={map}
set={set}
mapOfMaps={mapOfMaps}
setOfSets={setOfSets}
typedArray={typedArray}
immutable={immutable}
/>
);
}
function ChildComponent(props: any) {
return null;
}
@@ -264,6 +292,7 @@
<Fragment>
<SimpleValues />
<ObjectProps />
<UnserializableProps />
<CustomObject />
</Fragment>
);

View File

@@ -1,3 +1,4 @@
import Fixture from '../../Fixture';
import FixtureSet from '../../FixtureSet';
import TestCase from '../../TestCase';
@@ -39,6 +40,44 @@ export default class TextAreaFixtures extends React.Component {
<textarea placeholder="Hello, world" />
</div>
</TestCase>
<TestCase
title="Required Textareas"
affectedBrowsers="Firefox"
relatedIssues="16402">
<TestCase.Steps>
<li>View this test in Firefox</li>
</TestCase.Steps>
<TestCase.ExpectedResult>
You should{' '}
<b>
<i>not</i>
</b>{' '}
see a red aura on initial page load, indicating the textarea is
invalid.
<br />
This aura looks roughly like:
<textarea style={{boxShadow: '0 0 1px 1px red', marginLeft: 8}} />
</TestCase.ExpectedResult>
<Fixture>
<form className="control-box">
<fieldset>
<legend>Empty value prop string</legend>
<textarea value="" required={true} />
</fieldset>
<fieldset>
<legend>No value prop</legend>
<textarea required={true} />
</fieldset>
<fieldset>
<legend>Empty defaultValue prop string</legend>
<textarea required={true} defaultValue="" />
</fieldset>
</form>
</Fixture>
</TestCase>
</FixtureSet>
);
}

View File

@@ -12,6 +12,7 @@
"@babel/plugin-proposal-class-properties": "^7.0.0",
"@babel/plugin-proposal-object-rest-spread": "^7.0.0",
"@babel/plugin-syntax-dynamic-import": "^7.0.0",
"@babel/plugin-syntax-jsx": "^7.2.0",
"@babel/plugin-transform-arrow-functions": "^7.0.0",
"@babel/plugin-transform-async-to-generator": "^7.0.0",
"@babel/plugin-transform-block-scoped-functions": "^7.0.0",
@@ -43,14 +44,14 @@
"coveralls": "^2.11.6",
"create-react-class": "^15.6.3",
"cross-env": "^5.1.1",
"danger": "^3.0.4",
"danger": "^9.1.8",
"error-stack-parser": "^2.0.2",
"eslint": "^6.1.0",
"eslint-config-fbjs": "^1.1.1",
"eslint-plugin-babel": "^5.3.0",
"eslint-plugin-flowtype": "^2.25.0",
"eslint-plugin-jest": "^22.15.0",
"eslint-plugin-no-for-of-loops": "^1.0.0",
"eslint-plugin-babel": "^5.3.0",
"eslint-plugin-react": "^6.7.1",
"eslint-plugin-react-internal": "link:./scripts/eslint-rules",
"fbjs-scripts": "^0.8.3",
@@ -61,8 +62,8 @@
"google-closure-compiler": "20190301.0.0",
"gzip-size": "^3.0.0",
"jasmine-check": "^1.0.0-rc.0",
"jest": "^23.1.0",
"jest-diff": "^23.0.1",
"jest": "^23",
"jest-diff": "^23",
"jest-snapshot-serializer-raw": "^1.1.0",
"minimatch": "^3.0.4",
"minimist": "^1.2.0",
@@ -108,6 +109,8 @@
"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",
"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

@@ -0,0 +1,5 @@
This package is intended to eventually replace the current `@babel/plugin-transform-react-jsx`, changing the JSX transform from targeting `React.createElement(type, props, children)` to `React.jsx(types, props, key)`.
https://github.com/reactjs/rfcs/blob/createlement-rfc/text/0000-create-element-changes.md
**This is experimental and not intended to be used directly.**

View File

@@ -0,0 +1,392 @@
/**
* 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.
*/
/* eslint-disable quotes */
'use strict';
const babel = require('@babel/core');
const codeFrame = require('@babel/code-frame');
const {wrap} = require('jest-snapshot-serializer-raw');
function transform(input, options) {
return wrap(
babel.transform(input, {
configFile: false,
plugins: [
'@babel/plugin-syntax-jsx',
'@babel/plugin-transform-arrow-functions',
...(options && options.development
? [
'@babel/plugin-transform-react-jsx-source',
'@babel/plugin-transform-react-jsx-self',
]
: []),
[
'./packages/babel-plugin-react-jsx',
{
development: __DEV__,
useBuiltIns: true,
useCreateElement: true,
...options,
},
],
],
}).code
);
}
describe('transform react to jsx', () => {
it('fragment with no children', () => {
expect(transform(`var x = <></>`)).toMatchSnapshot();
});
it('React.Fragment to set keys and source', () => {
expect(
transform(`var x = <React.Fragment key='foo'><div /></React.Fragment>`, {
development: true,
})
).toMatchSnapshot();
});
it('normal fragments not to set key and source', () => {
expect(
transform(`var x = <><div /></>`, {
development: true,
})
).toMatchSnapshot();
});
it('should properly handle comments adjacent to children', () => {
expect(
transform(`
var x = (
<div>
{/* A comment at the beginning */}
{/* A second comment at the beginning */}
<span>
{/* A nested comment */}
</span>
{/* A sandwiched comment */}
<br />
{/* A comment at the end */}
{/* A second comment at the end */}
</div>
);
`)
).toMatchSnapshot();
});
it('adds appropriate new lines when using spread attribute', () => {
expect(transform(`<Component {...props} sound="moo" />`)).toMatchSnapshot();
});
it('arrow functions', () => {
expect(
transform(`
var foo = function () {
return () => <this />;
};
var bar = function () {
return () => <this.foo />;
};
`)
).toMatchSnapshot();
});
it('assignment', () => {
expect(
transform(`var div = <Component {...props} foo="bar" />`)
).toMatchSnapshot();
});
it('concatenates adjacent string literals', () => {
expect(
transform(`
var x =
<div>
foo
{"bar"}
baz
<div>
buz
bang
</div>
qux
{null}
quack
</div>
`)
).toMatchSnapshot();
});
it('should allow constructor as prop', () => {
expect(transform(`<Component constructor="foo" />;`)).toMatchSnapshot();
});
it('should allow deeper js namespacing', () => {
expect(
transform(`<Namespace.DeepNamespace.Component />;`)
).toMatchSnapshot();
});
it('should allow elements as attributes', () => {
expect(transform(`<div attr=<div /> />`)).toMatchSnapshot();
});
it('should allow js namespacing', () => {
expect(transform(`<Namespace.Component />;`)).toMatchSnapshot();
});
it('should allow nested fragments', () => {
expect(
transform(`
<div>
< >
<>
<span>Hello</span>
<span>world</span>
</>
<>
<span>Goodbye</span>
<span>world</span>
</>
</>
</div>
`)
).toMatchSnapshot();
});
it('should avoid wrapping in extra parens if not needed', () => {
expect(
transform(`
var x = <div>
<Component />
</div>;
var x = <div>
{props.children}
</div>;
var x = <Composite>
{props.children}
</Composite>;
var x = <Composite>
<Composite2 />
</Composite>;
`)
).toMatchSnapshot();
});
it('should convert simple tags', () => {
expect(transform(`var x = <div></div>;`)).toMatchSnapshot();
});
it('should convert simple text', () => {
expect(transform(`var x = <div>text</div>;`)).toMatchSnapshot();
});
it('should disallow spread children', () => {
let _error;
const code = `<div>{...children}</div>;`;
try {
transform(code);
} catch (error) {
_error = error;
}
expect(_error).toEqual(
new SyntaxError(
'undefined: Spread children are not supported in React.' +
'\n' +
codeFrame.codeFrameColumns(
code,
{start: {line: 1, column: 6}},
{highlightCode: true}
)
)
);
});
it('should escape xhtml jsxattribute', () => {
expect(
transform(`
<div id="wôw" />;
<div id="\w" />;
<div id="w &lt; w" />;
`)
).toMatchSnapshot();
});
it('should escape xhtml jsxtext', () => {
/* eslint-disable no-irregular-whitespace */
expect(
transform(`
<div>wow</div>;
<div>wôw</div>;
<div>w & w</div>;
<div>w &amp; w</div>;
<div>w &nbsp; w</div>;
<div>this should not parse as unicode: \u00a0</div>;
<div>this should parse as nbsp:   </div>;
<div>this should parse as unicode: {'\u00a0 '}</div>;
<div>w &lt; w</div>;
`)
).toMatchSnapshot();
/*eslint-enable */
});
it('should handle attributed elements', () => {
expect(
transform(`
var HelloMessage = React.createClass({
render: function() {
return <div>Hello {this.props.name}</div>;
}
});
React.render(<HelloMessage name={
<span>
Sebastian
</span>
} />, mountNode);
`)
).toMatchSnapshot();
});
it('should handle has own property correctly', () => {
expect(
transform(`<hasOwnProperty>testing</hasOwnProperty>;`)
).toMatchSnapshot();
});
it('should have correct comma in nested children', () => {
expect(
transform(`
var x = <div>
<div><br /></div>
<Component>{foo}<br />{bar}</Component>
<br />
</div>;
`)
).toMatchSnapshot();
});
it('should insert commas after expressions before whitespace', () => {
expect(
transform(`
var x =
<div
attr1={
"foo" + "bar"
}
attr2={
"foo" + "bar" +
"baz" + "bug"
}
attr3={
"foo" + "bar" +
"baz" + "bug"
}
attr4="baz">
</div>
`)
).toMatchSnapshot();
});
it('should not add quotes to identifier names', () => {
expect(
transform(`var e = <F aaa new const var default foo-bar/>;`)
).toMatchSnapshot();
});
it('should not strip nbsp even couple with other whitespace', () => {
expect(transform(`<div>&nbsp; </div>;`)).toMatchSnapshot();
});
it('should not strip tags with a single child of nbsp', () => {
expect(transform(`<div>&nbsp;</div>;`)).toMatchSnapshot();
});
it('should properly handle comments between props', () => {
expect(
transform(`
var x = (
<div
/* a multi-line
comment */
attr1="foo">
<span // a double-slash comment
attr2="bar"
/>
</div>
);
`)
).toMatchSnapshot();
});
it('should quote jsx attributes', () => {
expect(
transform(`<button data-value='a value'>Button</button>`)
).toMatchSnapshot();
});
it('should support xml namespaces if flag', () => {
expect(
transform('<f:image n:attr />', {throwIfNamespace: false})
).toMatchSnapshot();
});
it('should throw error namespaces if not flag', () => {
let _error;
const code = `<f:image />`;
try {
transform(code);
} catch (error) {
_error = error;
}
expect(_error).toEqual(
new SyntaxError(
"undefined: Namespace tags are not supported by default. React's " +
"JSX doesn't support namespace tags. You can turn on the " +
"'throwIfNamespace' flag to bypass this warning." +
'\n' +
codeFrame.codeFrameColumns(
code,
{start: {line: 1, column: 2}},
{highlightCode: true}
)
)
);
});
it('should transform known hyphenated tags', () => {
expect(transform(`<font-face />`)).toMatchSnapshot();
});
it('wraps props in react spread for first spread attributes', () => {
expect(transform(`<Component {...x} y={2} z />`)).toMatchSnapshot();
});
it('wraps props in react spread for last spread attributes', () => {
expect(transform(`<Component y={2} z { ... x } />`)).toMatchSnapshot();
});
it('wraps props in react spread for middle spread attributes', () => {
expect(transform(`<Component y={2} { ... x } z />`)).toMatchSnapshot();
});
it('useBuiltIns false uses extend instead of Object.assign', () => {
expect(
transform(`<Component y={2} {...x} />`, {useBuiltIns: false})
).toMatchSnapshot();
});
});

View File

@@ -0,0 +1,483 @@
/**
* 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.
*/
/* eslint-disable quotes */
'use strict';
const babel = require('@babel/core');
const codeFrame = require('@babel/code-frame');
const {wrap} = require('jest-snapshot-serializer-raw');
function transform(input, options) {
return wrap(
babel.transform(input, {
configFile: false,
plugins: [
'@babel/plugin-syntax-jsx',
'@babel/plugin-transform-arrow-functions',
...(options && options.development
? [
'@babel/plugin-transform-react-jsx-source',
'@babel/plugin-transform-react-jsx-self',
]
: []),
[
'./packages/babel-plugin-react-jsx',
{
useBuiltIns: true,
useCreateElement: false,
...options,
},
],
],
}).code
);
}
describe('transform react to jsx', () => {
it('fragment with no children', () => {
expect(transform(`var x = <></>`)).toMatchSnapshot();
});
it('fragments', () => {
expect(transform(`var x = <><div /></>`)).toMatchSnapshot();
});
it('fragments to set keys', () => {
expect(
transform(`var x = <React.Fragment key="foo"></React.Fragment>`)
).toMatchSnapshot();
});
it('React.fragment to set keys and source', () => {
expect(
transform(`var x = <React.Fragment key='foo'></React.Fragment>`, {
development: true,
})
).toMatchSnapshot();
});
it('fragments in dev mode (no key and source)', () => {
expect(
transform(`var x = <><div /></>`, {
development: true,
})
).toMatchSnapshot();
});
it('nonStatic children', () => {
expect(
transform(
`var x = (
<div>
{[<span key={'0'} />, <span key={'1'} />]}
</div>
);
`,
{
development: true,
}
)
).toMatchSnapshot();
});
it('static children', () => {
expect(
transform(
`var x = (
<div>
<span />
{[<span key={'0'} />, <span key={'1'} />]}
</div>
);
`,
{
development: true,
}
)
).toMatchSnapshot();
});
it('uses jsxDEV instead of jsx in dev mode', () => {
expect(
transform(`var x = <span propOne="one">Hi</span>`, {development: true})
).toMatchSnapshot();
});
it('properly passes in source and self', () => {
expect(
transform(`var x = <div />;`, {development: true})
).toMatchSnapshot();
});
it('should properly handle potentially null variables', () => {
expect(
transform(`
var foo = null;
var x = <div {...foo} />;
`)
).toMatchSnapshot();
});
it('properly handles keys', () => {
expect(
transform(`var x = (
<div>
<div key="1" />
<div key="2" meow="wolf" />
<div key="3" />
</div>
);`)
).toMatchSnapshot();
});
it('uses createElement when the key comes after a spread', () => {
expect(
transform(`var x = (
<div {...props} key="1" foo="bar" />
);`)
).toMatchSnapshot();
});
it('uses jsx when the key comes before a spread', () => {
expect(
transform(`var x = (
<div key="1" {...props} foo="bar" />
);`)
).toMatchSnapshot();
});
it('should properly handle comments adjacent to children', () => {
expect(
transform(`
var x = (
<div>
{/* A comment at the beginning */}
{/* A second comment at the beginning */}
<span>
{/* A nested comment */}
</span>
{/* A sandwiched comment */}
<br />
{/* A comment at the end */}
{/* A second comment at the end */}
</div>
);
`)
).toMatchSnapshot();
});
it('adds appropriate new lines when using spread attribute', () => {
expect(transform(`<Component {...props} sound="moo" />`)).toMatchSnapshot();
});
it('arrow functions', () => {
expect(
transform(`
var foo = function () {
return () => <this />;
};
var bar = function () {
return () => <this.foo />;
};
`)
).toMatchSnapshot();
});
it('assignment', () => {
expect(
transform(`var div = <Component {...props} foo="bar" />`)
).toMatchSnapshot();
});
it('concatenates adjacent string literals', () => {
expect(
transform(`
var x =
<div>
foo
{"bar"}
baz
<div>
buz
bang
</div>
qux
{null}
quack
</div>
`)
).toMatchSnapshot();
});
it('should allow constructor as prop', () => {
expect(transform(`<Component constructor="foo" />;`)).toMatchSnapshot();
});
it('should allow deeper js namespacing', () => {
expect(
transform(`<Namespace.DeepNamespace.Component />;`)
).toMatchSnapshot();
});
it('should allow elements as attributes', () => {
expect(transform(`<div attr=<div /> />`)).toMatchSnapshot();
});
it('should allow js namespacing', () => {
expect(transform(`<Namespace.Component />;`)).toMatchSnapshot();
});
it('should allow nested fragments', () => {
expect(
transform(`
<div>
< >
<>
<span>Hello</span>
<span>world</span>
</>
<>
<span>Goodbye</span>
<span>world</span>
</>
</>
</div>
`)
).toMatchSnapshot();
});
it('should avoid wrapping in extra parens if not needed', () => {
expect(
transform(`
var x = <div>
<Component />
</div>;
var x = <div>
{props.children}
</div>;
var x = <Composite>
{props.children}
</Composite>;
var x = <Composite>
<Composite2 />
</Composite>;
`)
).toMatchSnapshot();
});
it('should convert simple tags', () => {
expect(transform(`var x = <div></div>;`)).toMatchSnapshot();
});
it('should convert simple text', () => {
expect(transform(`var x = <div>text</div>;`)).toMatchSnapshot();
});
it('should disallow spread children', () => {
let _error;
const code = `<div>{...children}</div>;`;
try {
transform(code);
} catch (error) {
_error = error;
}
expect(_error).toEqual(
new SyntaxError(
'undefined: Spread children are not supported in React.' +
'\n' +
codeFrame.codeFrameColumns(
code,
{start: {line: 1, column: 6}},
{highlightCode: true}
)
)
);
});
it('should escape xhtml jsxattribute', () => {
expect(
transform(`
<div id="wôw" />;
<div id="\w" />;
<div id="w &lt; w" />;
`)
).toMatchSnapshot();
});
it('should escape xhtml jsxtext', () => {
/* eslint-disable no-irregular-whitespace */
expect(
transform(`
<div>wow</div>;
<div>wôw</div>;
<div>w & w</div>;
<div>w &amp; w</div>;
<div>w &nbsp; w</div>;
<div>this should not parse as unicode: \u00a0</div>;
<div>this should parse as nbsp:   </div>;
<div>this should parse as unicode: {'\u00a0 '}</div>;
<div>w &lt; w</div>;
`)
).toMatchSnapshot();
/*eslint-enable */
});
it('should handle attributed elements', () => {
expect(
transform(`
var HelloMessage = React.createClass({
render: function() {
return <div>Hello {this.props.name}</div>;
}
});
React.render(<HelloMessage name={
<span>
Sebastian
</span>
} />, mountNode);
`)
).toMatchSnapshot();
});
it('should handle has own property correctly', () => {
expect(
transform(`<hasOwnProperty>testing</hasOwnProperty>;`)
).toMatchSnapshot();
});
it('should have correct comma in nested children', () => {
expect(
transform(`
var x = <div>
<div><br /></div>
<Component>{foo}<br />{bar}</Component>
<br />
</div>;
`)
).toMatchSnapshot();
});
it('should insert commas after expressions before whitespace', () => {
expect(
transform(`
var x =
<div
attr1={
"foo" + "bar"
}
attr2={
"foo" + "bar" +
"baz" + "bug"
}
attr3={
"foo" + "bar" +
"baz" + "bug"
}
attr4="baz">
</div>
`)
).toMatchSnapshot();
});
it('should not add quotes to identifier names', () => {
expect(
transform(`var e = <F aaa new const var default foo-bar/>;`)
).toMatchSnapshot();
});
it('should not strip nbsp even couple with other whitespace', () => {
expect(transform(`<div>&nbsp; </div>;`)).toMatchSnapshot();
});
it('should not strip tags with a single child of nbsp', () => {
expect(transform(`<div>&nbsp;</div>;`)).toMatchSnapshot();
});
it('should properly handle comments between props', () => {
expect(
transform(`
var x = (
<div
/* a multi-line
comment */
attr1="foo">
<span // a double-slash comment
attr2="bar"
/>
</div>
);
`)
).toMatchSnapshot();
});
it('should quote jsx attributes', () => {
expect(
transform(`<button data-value='a value'>Button</button>`)
).toMatchSnapshot();
});
it('should support xml namespaces if flag', () => {
expect(
transform('<f:image n:attr />', {throwIfNamespace: false})
).toMatchSnapshot();
});
it('should throw error namespaces if not flag', () => {
let _error;
const code = `<f:image />`;
try {
transform(code);
} catch (error) {
_error = error;
}
expect(_error).toEqual(
new SyntaxError(
"undefined: Namespace tags are not supported by default. React's " +
"JSX doesn't support namespace tags. You can turn on the " +
"'throwIfNamespace' flag to bypass this warning." +
'\n' +
codeFrame.codeFrameColumns(
code,
{start: {line: 1, column: 2}},
{highlightCode: true}
)
)
);
});
it('should transform known hyphenated tags', () => {
expect(transform(`<font-face />`)).toMatchSnapshot();
});
it('wraps props in react spread for first spread attributes', () => {
expect(transform(`<Component {...x} y={2} z />`)).toMatchSnapshot();
});
it('wraps props in react spread for last spread attributes', () => {
expect(transform(`<Component y={2} z { ... x } />`)).toMatchSnapshot();
});
it('wraps props in react spread for middle spread attributes', () => {
expect(transform(`<Component y={2} { ... x } z />`)).toMatchSnapshot();
});
it('useBuiltIns false uses extend instead of Object.assign', () => {
expect(
transform(`<Component y={2} {...x} />`, {useBuiltIns: false})
).toMatchSnapshot();
});
});

View File

@@ -0,0 +1,213 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`transform react to jsx React.Fragment to set keys and source 1`] = `
var _jsxFileName = "";
var x = React.createElement(React.Fragment, {
key: "foo",
__source: {
fileName: _jsxFileName,
lineNumber: 1
},
__self: this
}, React.createElement("div", {
__source: {
fileName: _jsxFileName,
lineNumber: 1
},
__self: this
}));
`;
exports[`transform react to jsx adds appropriate new lines when using spread attribute 1`] = `
React.createElement(Component, Object.assign({}, props, {
sound: "moo"
}));
`;
exports[`transform react to jsx arrow functions 1`] = `
var foo = function () {
var _this = this;
return function () {
return React.createElement(_this, null);
};
};
var bar = function () {
var _this2 = this;
return function () {
return React.createElement(_this2.foo, null);
};
};
`;
exports[`transform react to jsx assignment 1`] = `
var div = React.createElement(Component, Object.assign({}, props, {
foo: "bar"
}));
`;
exports[`transform react to jsx concatenates adjacent string literals 1`] = `var x = React.createElement("div", null, "foo", "bar", "baz", React.createElement("div", null, "buz bang"), "qux", null, "quack");`;
exports[`transform react to jsx fragment with no children 1`] = `var x = React.createElement(React.Fragment, null);`;
exports[`transform react to jsx normal fragments not to set key and source 1`] = `
var _jsxFileName = "";
var x = React.createElement(React.Fragment, null, React.createElement("div", {
__source: {
fileName: _jsxFileName,
lineNumber: 1
},
__self: this
}));
`;
exports[`transform react to jsx should allow constructor as prop 1`] = `
React.createElement(Component, {
constructor: "foo"
});
`;
exports[`transform react to jsx should allow deeper js namespacing 1`] = `React.createElement(Namespace.DeepNamespace.Component, null);`;
exports[`transform react to jsx should allow elements as attributes 1`] = `
React.createElement("div", {
attr: React.createElement("div", null)
});
`;
exports[`transform react to jsx should allow js namespacing 1`] = `React.createElement(Namespace.Component, null);`;
exports[`transform react to jsx should allow nested fragments 1`] = `React.createElement("div", null, React.createElement(React.Fragment, null, React.createElement(React.Fragment, null, React.createElement("span", null, "Hello"), React.createElement("span", null, "world")), React.createElement(React.Fragment, null, React.createElement("span", null, "Goodbye"), React.createElement("span", null, "world"))));`;
exports[`transform react to jsx should avoid wrapping in extra parens if not needed 1`] = `
var x = React.createElement("div", null, React.createElement(Component, null));
var x = React.createElement("div", null, props.children);
var x = React.createElement(Composite, null, props.children);
var x = React.createElement(Composite, null, React.createElement(Composite2, null));
`;
exports[`transform react to jsx should convert simple tags 1`] = `var x = React.createElement("div", null);`;
exports[`transform react to jsx should convert simple text 1`] = `var x = React.createElement("div", null, "text");`;
exports[`transform react to jsx should escape xhtml jsxattribute 1`] = `
React.createElement("div", {
id: "w\\xF4w"
});
React.createElement("div", {
id: "w"
});
React.createElement("div", {
id: "w < w"
});
`;
exports[`transform react to jsx should escape xhtml jsxtext 1`] = `
React.createElement("div", null, "wow");
React.createElement("div", null, "w\\xF4w");
React.createElement("div", null, "w & w");
React.createElement("div", null, "w & w");
React.createElement("div", null, "w \\xA0 w");
React.createElement("div", null, "this should not parse as unicode: \\xA0");
React.createElement("div", null, "this should parse as nbsp: \\xA0 ");
React.createElement("div", null, "this should parse as unicode: ", '  ');
React.createElement("div", null, "w < w");
`;
exports[`transform react to jsx should handle attributed elements 1`] = `
var HelloMessage = React.createClass({
render: function () {
return React.createElement("div", null, "Hello ", this.props.name);
}
});
React.render(React.createElement(HelloMessage, {
name: React.createElement("span", null, "Sebastian")
}), mountNode);
`;
exports[`transform react to jsx should handle has own property correctly 1`] = `React.createElement("hasOwnProperty", null, "testing");`;
exports[`transform react to jsx should have correct comma in nested children 1`] = `var x = React.createElement("div", null, React.createElement("div", null, React.createElement("br", null)), React.createElement(Component, null, foo, React.createElement("br", null), bar), React.createElement("br", null));`;
exports[`transform react to jsx should insert commas after expressions before whitespace 1`] = `
var x = React.createElement("div", {
attr1: "foo" + "bar",
attr2: "foo" + "bar" + "baz" + "bug",
attr3: "foo" + "bar" + "baz" + "bug",
attr4: "baz"
});
`;
exports[`transform react to jsx should not add quotes to identifier names 1`] = `
var e = React.createElement(F, {
aaa: true,
new: true,
const: true,
var: true,
default: true,
"foo-bar": true
});
`;
exports[`transform react to jsx should not strip nbsp even couple with other whitespace 1`] = `React.createElement("div", null, "\\xA0 ");`;
exports[`transform react to jsx should not strip tags with a single child of nbsp 1`] = `React.createElement("div", null, "\\xA0");`;
exports[`transform react to jsx should properly handle comments adjacent to children 1`] = `var x = React.createElement("div", null, React.createElement("span", null), React.createElement("br", null));`;
exports[`transform react to jsx should properly handle comments between props 1`] = `
var x = React.createElement("div", {
/* a multi-line
comment */
attr1: "foo"
}, React.createElement("span", {
// a double-slash comment
attr2: "bar"
}));
`;
exports[`transform react to jsx should quote jsx attributes 1`] = `
React.createElement("button", {
"data-value": "a value"
}, "Button");
`;
exports[`transform react to jsx should support xml namespaces if flag 1`] = `
React.createElement("f:image", {
"n:attr": true
});
`;
exports[`transform react to jsx should transform known hyphenated tags 1`] = `React.createElement("font-face", null);`;
exports[`transform react to jsx useBuiltIns false uses extend instead of Object.assign 1`] = `
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
React.createElement(Component, _extends({
y: 2
}, x));
`;
exports[`transform react to jsx wraps props in react spread for first spread attributes 1`] = `
React.createElement(Component, Object.assign({}, x, {
y: 2,
z: true
}));
`;
exports[`transform react to jsx wraps props in react spread for last spread attributes 1`] = `
React.createElement(Component, Object.assign({
y: 2,
z: true
}, x));
`;
exports[`transform react to jsx wraps props in react spread for middle spread attributes 1`] = `
React.createElement(Component, Object.assign({
y: 2
}, x, {
z: true
}));
`;

View File

@@ -0,0 +1,374 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`transform react to jsx React.fragment to set keys and source 1`] = `
var _jsxFileName = "";
var x = React.jsxDEV(React.Fragment, {}, "foo", false, {
fileName: _jsxFileName,
lineNumber: 1
}, this);
`;
exports[`transform react to jsx adds appropriate new lines when using spread attribute 1`] = `
React.jsx(Component, Object.assign({}, props, {
sound: "moo"
}));
`;
exports[`transform react to jsx arrow functions 1`] = `
var foo = function () {
var _this = this;
return function () {
return React.jsx(_this, {});
};
};
var bar = function () {
var _this2 = this;
return function () {
return React.jsx(_this2.foo, {});
};
};
`;
exports[`transform react to jsx assignment 1`] = `
var div = React.jsx(Component, Object.assign({}, props, {
foo: "bar"
}));
`;
exports[`transform react to jsx concatenates adjacent string literals 1`] = `
var x = React.jsxs("div", {
children: ["foo", "bar", "baz", React.jsx("div", {
children: "buz bang"
}), "qux", null, "quack"]
});
`;
exports[`transform react to jsx fragment with no children 1`] = `var x = React.jsx(React.Fragment, {});`;
exports[`transform react to jsx fragments 1`] = `
var x = React.jsx(React.Fragment, {
children: React.jsx("div", {})
});
`;
exports[`transform react to jsx fragments in dev mode (no key and source) 1`] = `
var _jsxFileName = "";
var x = React.jsxDEV(React.Fragment, {
children: React.jsxDEV("div", {}, undefined, false, {
fileName: _jsxFileName,
lineNumber: 1
}, this)
}, undefined, false);
`;
exports[`transform react to jsx fragments to set keys 1`] = `var x = React.jsx(React.Fragment, {}, "foo");`;
exports[`transform react to jsx nonStatic children 1`] = `
var _jsxFileName = "";
var x = React.jsxDEV("div", {
children: [React.jsxDEV("span", {}, '0', false, {
fileName: _jsxFileName,
lineNumber: 3
}, this), React.jsxDEV("span", {}, '1', false, {
fileName: _jsxFileName,
lineNumber: 3
}, this)]
}, undefined, false, {
fileName: _jsxFileName,
lineNumber: 2
}, this);
`;
exports[`transform react to jsx properly handles keys 1`] = `
var x = React.jsxs("div", {
children: [React.jsx("div", {}, "1"), React.jsx("div", {
meow: "wolf"
}, "2"), React.jsx("div", {}, "3")]
});
`;
exports[`transform react to jsx properly passes in source and self 1`] = `
var _jsxFileName = "";
var x = React.jsxDEV("div", {}, undefined, false, {
fileName: _jsxFileName,
lineNumber: 1
}, this);
`;
exports[`transform react to jsx should allow constructor as prop 1`] = `
React.jsx(Component, {
constructor: "foo"
});
`;
exports[`transform react to jsx should allow deeper js namespacing 1`] = `React.jsx(Namespace.DeepNamespace.Component, {});`;
exports[`transform react to jsx should allow elements as attributes 1`] = `
React.jsx("div", {
attr: React.jsx("div", {})
});
`;
exports[`transform react to jsx should allow js namespacing 1`] = `React.jsx(Namespace.Component, {});`;
exports[`transform react to jsx should allow nested fragments 1`] = `
React.jsx("div", {
children: React.jsxs(React.Fragment, {
children: [React.jsxs(React.Fragment, {
children: [React.jsx("span", {
children: "Hello"
}), React.jsx("span", {
children: "world"
})]
}), React.jsxs(React.Fragment, {
children: [React.jsx("span", {
children: "Goodbye"
}), React.jsx("span", {
children: "world"
})]
})]
})
});
`;
exports[`transform react to jsx should avoid wrapping in extra parens if not needed 1`] = `
var x = React.jsx("div", {
children: React.jsx(Component, {})
});
var x = React.jsx("div", {
children: props.children
});
var x = React.jsx(Composite, {
children: props.children
});
var x = React.jsx(Composite, {
children: React.jsx(Composite2, {})
});
`;
exports[`transform react to jsx should convert simple tags 1`] = `var x = React.jsx("div", {});`;
exports[`transform react to jsx should convert simple text 1`] = `
var x = React.jsx("div", {
children: "text"
});
`;
exports[`transform react to jsx should escape xhtml jsxattribute 1`] = `
React.jsx("div", {
id: "w\\xF4w"
});
React.jsx("div", {
id: "w"
});
React.jsx("div", {
id: "w < w"
});
`;
exports[`transform react to jsx should escape xhtml jsxtext 1`] = `
React.jsx("div", {
children: "wow"
});
React.jsx("div", {
children: "w\\xF4w"
});
React.jsx("div", {
children: "w & w"
});
React.jsx("div", {
children: "w & w"
});
React.jsx("div", {
children: "w \\xA0 w"
});
React.jsx("div", {
children: "this should not parse as unicode: \\xA0"
});
React.jsx("div", {
children: "this should parse as nbsp: \\xA0 "
});
React.jsxs("div", {
children: ["this should parse as unicode: ", '  ']
});
React.jsx("div", {
children: "w < w"
});
`;
exports[`transform react to jsx should handle attributed elements 1`] = `
var HelloMessage = React.createClass({
render: function () {
return React.jsxs("div", {
children: ["Hello ", this.props.name]
});
}
});
React.render(React.jsx(HelloMessage, {
name: React.jsx("span", {
children: "Sebastian"
})
}), mountNode);
`;
exports[`transform react to jsx should handle has own property correctly 1`] = `
React.jsx("hasOwnProperty", {
children: "testing"
});
`;
exports[`transform react to jsx should have correct comma in nested children 1`] = `
var x = React.jsxs("div", {
children: [React.jsx("div", {
children: React.jsx("br", {})
}), React.jsxs(Component, {
children: [foo, React.jsx("br", {}), bar]
}), React.jsx("br", {})]
});
`;
exports[`transform react to jsx should insert commas after expressions before whitespace 1`] = `
var x = React.jsx("div", {
attr1: "foo" + "bar",
attr2: "foo" + "bar" + "baz" + "bug",
attr3: "foo" + "bar" + "baz" + "bug",
attr4: "baz"
});
`;
exports[`transform react to jsx should not add quotes to identifier names 1`] = `
var e = React.jsx(F, {
aaa: true,
new: true,
const: true,
var: true,
default: true,
"foo-bar": true
});
`;
exports[`transform react to jsx should not strip nbsp even couple with other whitespace 1`] = `
React.jsx("div", {
children: "\\xA0 "
});
`;
exports[`transform react to jsx should not strip tags with a single child of nbsp 1`] = `
React.jsx("div", {
children: "\\xA0"
});
`;
exports[`transform react to jsx should properly handle comments adjacent to children 1`] = `
var x = React.jsxs("div", {
children: [React.jsx("span", {}), React.jsx("br", {})]
});
`;
exports[`transform react to jsx should properly handle comments between props 1`] = `
var x = React.jsx("div", {
/* a multi-line
comment */
attr1: "foo",
children: React.jsx("span", {
// a double-slash comment
attr2: "bar"
})
});
`;
exports[`transform react to jsx should properly handle potentially null variables 1`] = `
var foo = null;
var x = React.jsx("div", Object.assign({}, foo));
`;
exports[`transform react to jsx should quote jsx attributes 1`] = `
React.jsx("button", {
"data-value": "a value",
children: "Button"
});
`;
exports[`transform react to jsx should support xml namespaces if flag 1`] = `
React.jsx("f:image", {
"n:attr": true
});
`;
exports[`transform react to jsx should transform known hyphenated tags 1`] = `React.jsx("font-face", {});`;
exports[`transform react to jsx static children 1`] = `
var _jsxFileName = "";
var x = React.jsxDEV("div", {
children: [React.jsxDEV("span", {}, undefined, false, {
fileName: _jsxFileName,
lineNumber: 3
}, this), [React.jsxDEV("span", {}, '0', false, {
fileName: _jsxFileName,
lineNumber: 4
}, this), React.jsxDEV("span", {}, '1', false, {
fileName: _jsxFileName,
lineNumber: 4
}, this)]]
}, undefined, true, {
fileName: _jsxFileName,
lineNumber: 2
}, this);
`;
exports[`transform react to jsx useBuiltIns false uses extend instead of Object.assign 1`] = `
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
React.jsx(Component, _extends({
y: 2
}, x));
`;
exports[`transform react to jsx uses createElement when the key comes after a spread 1`] = `
var x = React.createElement("div", Object.assign({}, props, {
key: "1",
foo: "bar"
}));
`;
exports[`transform react to jsx uses jsx when the key comes before a spread 1`] = `
var x = React.jsx("div", Object.assign({}, props, {
foo: "bar"
}), "1");
`;
exports[`transform react to jsx uses jsxDEV instead of jsx in dev mode 1`] = `
var _jsxFileName = "";
var x = React.jsxDEV("span", {
propOne: "one",
children: "Hi"
}, undefined, false, {
fileName: _jsxFileName,
lineNumber: 1
}, this);
`;
exports[`transform react to jsx wraps props in react spread for first spread attributes 1`] = `
React.jsx(Component, Object.assign({}, x, {
y: 2,
z: true
}));
`;
exports[`transform react to jsx wraps props in react spread for last spread attributes 1`] = `
React.jsx(Component, Object.assign({
y: 2,
z: true
}, x));
`;
exports[`transform react to jsx wraps props in react spread for middle spread attributes 1`] = `
React.jsx(Component, Object.assign({
y: 2
}, x, {
z: true
}));
`;

View File

@@ -0,0 +1,3 @@
'use strict';
module.exports = require('./src/TransformJSXToReactBabelPlugin');

View File

@@ -0,0 +1,7 @@
'use strict';
if (process.env.NODE_ENV === 'production') {
module.exports = require('./cjs/react-jsx-babel.production.min.js');
} else {
module.exports = require('./cjs/react-jsx-babel.development.js');
}

View File

@@ -0,0 +1,18 @@
{
"name": "babel-plugin-react-jsx",
"version": "0.1.0",
"private": true,
"description": "@babel/plugin-transform-react-jsx",
"main": "index.js",
"dependencies": {
"esutils": "^2.0.0"
},
"files": [
"README.md",
"index.js",
"build-info.json",
"cjs/",
"umd/"
]
}

View File

@@ -0,0 +1,611 @@
// MIT License
// Copyright (c) 2014-present Sebastian McKenzie and other contributors
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// © 2019 GitHub, Inc.
'use strict';
const esutils = require('esutils');
function helper(babel, opts) {
const {types: t} = babel;
const visitor = {};
visitor.JSXNamespacedName = function(path, state) {
const throwIfNamespace =
state.opts.throwIfNamespace === undefined
? true
: !!state.opts.throwIfNamespace;
if (throwIfNamespace) {
throw path.buildCodeFrameError(
`Namespace tags are not supported by default. React's JSX doesn't support namespace tags. \
You can turn on the 'throwIfNamespace' flag to bypass this warning.`,
);
}
};
visitor.JSXSpreadChild = function(path) {
throw path.buildCodeFrameError(
'Spread children are not supported in React.',
);
};
visitor.JSXElement = {
exit(path, file) {
let callExpr;
if (file.opts.useCreateElement || shouldUseCreateElement(path)) {
callExpr = buildCreateElementCall(path, file);
} else {
callExpr = buildJSXElementCall(path, file);
}
if (callExpr) {
path.replaceWith(t.inherits(callExpr, path.node));
}
},
};
visitor.JSXFragment = {
exit(path, file) {
if (opts.compat) {
throw path.buildCodeFrameError(
'Fragment tags are only supported in React 16 and up.',
);
}
let callExpr;
if (file.opts.useCreateElement) {
callExpr = buildCreateElementFragmentCall(path, file);
} else {
callExpr = buildJSXFragmentCall(path, file);
}
if (callExpr) {
path.replaceWith(t.inherits(callExpr, path.node));
}
},
};
return visitor;
function convertJSXIdentifier(node, parent) {
if (t.isJSXIdentifier(node)) {
if (node.name === 'this' && t.isReferenced(node, parent)) {
return t.thisExpression();
} else if (esutils.keyword.isIdentifierNameES6(node.name)) {
node.type = 'Identifier';
} else {
return t.stringLiteral(node.name);
}
} else if (t.isJSXMemberExpression(node)) {
return t.memberExpression(
convertJSXIdentifier(node.object, node),
convertJSXIdentifier(node.property, node),
);
} else if (t.isJSXNamespacedName(node)) {
/**
* If there is flag "throwIfNamespace"
* print XMLNamespace like string literal
*/
return t.stringLiteral(`${node.namespace.name}:${node.name.name}`);
}
return node;
}
function convertAttributeValue(node) {
if (t.isJSXExpressionContainer(node)) {
return node.expression;
} else {
return node;
}
}
function convertAttribute(node) {
const value = convertAttributeValue(node.value || t.booleanLiteral(true));
if (t.isStringLiteral(value) && !t.isJSXExpressionContainer(node.value)) {
value.value = value.value.replace(/\n\s+/g, ' ');
// "raw" JSXText should not be used from a StringLiteral because it needs to be escaped.
if (value.extra && value.extra.raw) {
delete value.extra.raw;
}
}
if (t.isJSXNamespacedName(node.name)) {
node.name = t.stringLiteral(
node.name.namespace.name + ':' + node.name.name.name,
);
} else if (esutils.keyword.isIdentifierNameES6(node.name.name)) {
node.name.type = 'Identifier';
} else {
node.name = t.stringLiteral(node.name.name);
}
return t.inherits(t.objectProperty(node.name, value), node);
}
// We want to use React.createElement, even in the case of
// jsx, for <div {...props} key={key} /> to distinguish it
// from <div key={key} {...props} />. This is an intermediary
// step while we deprecate key spread from props. Afterwards,
// we will remove createElement entirely
function shouldUseCreateElement(path) {
const openingPath = path.get('openingElement');
const attributes = openingPath.node.attributes;
let seenPropsSpread = false;
for (let i = 0; i < attributes.length; i++) {
const attr = attributes[i];
if (
seenPropsSpread &&
t.isJSXAttribute(attr) &&
attr.name.name === 'key'
) {
return true;
} else if (t.isJSXSpreadAttribute(attr)) {
seenPropsSpread = true;
}
}
return false;
}
// Builds JSX into:
// Production: React.jsx(type, arguments, key)
// Development: React.jsxDEV(type, arguments, key, isStaticChildren, source, self)
function buildJSXElementCall(path, file) {
if (opts.filter && !opts.filter(path.node, file)) {
return;
}
const openingPath = path.get('openingElement');
openingPath.parent.children = t.react.buildChildren(openingPath.parent);
const tagExpr = convertJSXIdentifier(
openingPath.node.name,
openingPath.node,
);
const args = [];
let tagName;
if (t.isIdentifier(tagExpr)) {
tagName = tagExpr.name;
} else if (t.isLiteral(tagExpr)) {
tagName = tagExpr.value;
}
const state = {
tagExpr: tagExpr,
tagName: tagName,
args: args,
};
if (opts.pre) {
opts.pre(state, file);
}
let attribs = [];
let key;
let source;
let self;
// for React.jsx, key, __source (dev), and __self (dev) is passed in as
// a separate argument rather than in the args object. We go through the
// props and filter out these three keywords so we can pass them in
// as separate arguments later
for (let i = 0; i < openingPath.node.attributes.length; i++) {
const attr = openingPath.node.attributes[i];
if (t.isJSXAttribute(attr) && t.isJSXIdentifier(attr.name)) {
if (attr.name.name === 'key') {
key = convertAttribute(attr).value;
} else if (attr.name.name === '__source') {
source = convertAttribute(attr).value;
} else if (attr.name.name === '__self') {
self = convertAttribute(attr).value;
} else {
attribs.push(attr);
}
} else {
attribs.push(attr);
}
}
if (attribs.length || path.node.children.length) {
attribs = buildJSXOpeningElementAttributes(
attribs,
file,
path.node.children,
);
} else {
// attributes should never be null
attribs = t.objectExpression([]);
}
args.push(attribs);
if (!file.opts.development) {
if (key !== undefined) {
args.push(key);
}
} else {
// isStaticChildren, __source, and __self are only used in development
args.push(
key === undefined ? t.identifier('undefined') : key,
t.booleanLiteral(path.node.children.length > 1),
source === undefined ? t.identifier('undefined') : source,
self === undefined ? t.identifier('undefined') : self,
);
}
if (opts.post) {
opts.post(state, file);
}
return (
state.call ||
t.callExpression(
path.node.children.length > 1 ? state.staticCallee : state.callee,
args,
)
);
}
// Builds props for React.jsx. This function adds children into the props
// and ensures that props is always an object
function buildJSXOpeningElementAttributes(attribs, file, children) {
let _props = [];
const objs = [];
const useBuiltIns = file.opts.useBuiltIns || false;
if (typeof useBuiltIns !== 'boolean') {
throw new Error(
'transform-react-jsx currently only accepts a boolean option for ' +
'useBuiltIns (defaults to false)',
);
}
while (attribs.length) {
const prop = attribs.shift();
if (t.isJSXSpreadAttribute(prop)) {
_props = pushProps(_props, objs);
objs.push(prop.argument);
} else {
_props.push(convertAttribute(prop));
}
}
// In React.JSX, children is no longer a separate argument, but passed in
// through the argument object
if (children && children.length > 0) {
if (children.length === 1) {
_props.push(t.objectProperty(t.identifier('children'), children[0]));
} else {
_props.push(
t.objectProperty(
t.identifier('children'),
t.arrayExpression(children),
),
);
}
}
pushProps(_props, objs);
if (objs.length === 1) {
// only one object
if (!t.isObjectExpression(objs[0])) {
// if the prop object isn't an object, use Object.assign or _extends
// to ensure that the prop will always be an object (as opposed to a variable
// that could be null at some point)
const expressionHelper = useBuiltIns
? t.memberExpression(t.identifier('Object'), t.identifier('assign'))
: file.addHelper('extends');
attribs = t.callExpression(expressionHelper, [
t.objectExpression([]),
objs[0],
]);
} else {
attribs = objs[0];
}
} else {
// looks like we have multiple objects
if (!t.isObjectExpression(objs[0])) {
objs.unshift(t.objectExpression([]));
}
const expressionHelper = useBuiltIns
? t.memberExpression(t.identifier('Object'), t.identifier('assign'))
: file.addHelper('extends');
// spread it
attribs = t.callExpression(expressionHelper, objs);
}
return attribs;
}
// Builds JSX Fragment <></> into
// Production: React.jsx(type, arguments)
// Development: React.jsxDEV(type, { children})
function buildJSXFragmentCall(path, file) {
if (opts.filter && !opts.filter(path.node, file)) {
return;
}
const openingPath = path.get('openingElement');
openingPath.parent.children = t.react.buildChildren(openingPath.parent);
const args = [];
const tagName = null;
const tagExpr = file.get('jsxFragIdentifier')();
const state = {
tagExpr: tagExpr,
tagName: tagName,
args: args,
};
if (opts.pre) {
opts.pre(state, file);
}
let childrenNode;
if (path.node.children.length > 0) {
if (path.node.children.length === 1) {
childrenNode = path.node.children[0];
} else {
childrenNode = t.arrayExpression(path.node.children);
}
}
args.push(
t.objectExpression(
childrenNode !== undefined
? [t.objectProperty(t.identifier('children'), childrenNode)]
: [],
),
);
if (file.opts.development) {
args.push(
t.identifier('undefined'),
t.booleanLiteral(path.node.children.length > 1),
);
}
if (opts.post) {
opts.post(state, file);
}
return (
state.call ||
t.callExpression(
path.node.children.length > 1 ? state.staticCallee : state.callee,
args,
)
);
}
// Builds JSX into:
// Production: React.createElement(type, arguments, children)
// Development: React.createElement(type, arguments, children, source, self)
function buildCreateElementCall(path, file) {
if (opts.filter && !opts.filter(path.node, file)) {
return;
}
const openingPath = path.get('openingElement');
openingPath.parent.children = t.react.buildChildren(openingPath.parent);
const tagExpr = convertJSXIdentifier(
openingPath.node.name,
openingPath.node,
);
const args = [];
let tagName;
if (t.isIdentifier(tagExpr)) {
tagName = tagExpr.name;
} else if (t.isLiteral(tagExpr)) {
tagName = tagExpr.value;
}
const state = {
tagExpr: tagExpr,
tagName: tagName,
args: args,
};
if (opts.pre) {
opts.pre(state, file);
}
let attribs = openingPath.node.attributes;
if (attribs.length) {
attribs = buildCreateElementOpeningElementAttributes(attribs, file);
} else {
attribs = t.nullLiteral();
}
args.push(attribs, ...path.node.children);
if (opts.post) {
opts.post(state, file);
}
return state.call || t.callExpression(state.oldCallee, args);
}
function pushProps(_props, objs) {
if (!_props.length) {
return _props;
}
objs.push(t.objectExpression(_props));
return [];
}
/**
* The logic for this is quite terse. It's because we need to
* support spread elements. We loop over all attributes,
* breaking on spreads, we then push a new object containing
* all prior attributes to an array for later processing.
*/
function buildCreateElementOpeningElementAttributes(attribs, file) {
let _props = [];
const objs = [];
const useBuiltIns = file.opts.useBuiltIns || false;
if (typeof useBuiltIns !== 'boolean') {
throw new Error(
'transform-react-jsx currently only accepts a boolean option for ' +
'useBuiltIns (defaults to false)',
);
}
while (attribs.length) {
const prop = attribs.shift();
if (t.isJSXSpreadAttribute(prop)) {
_props = pushProps(_props, objs);
objs.push(prop.argument);
} else {
const attr = convertAttribute(prop);
_props.push(attr);
}
}
pushProps(_props, objs);
if (objs.length === 1) {
// only one object
attribs = objs[0];
} else {
// looks like we have multiple objects
if (!t.isObjectExpression(objs[0])) {
objs.unshift(t.objectExpression([]));
}
const expressionHelper = useBuiltIns
? t.memberExpression(t.identifier('Object'), t.identifier('assign'))
: file.addHelper('extends');
// spread it
attribs = t.callExpression(expressionHelper, objs);
}
return attribs;
}
function buildCreateElementFragmentCall(path, file) {
if (opts.filter && !opts.filter(path.node, file)) {
return;
}
const openingPath = path.get('openingElement');
openingPath.parent.children = t.react.buildChildren(openingPath.parent);
const args = [];
const tagName = null;
const tagExpr = file.get('jsxFragIdentifier')();
const state = {
tagExpr: tagExpr,
tagName: tagName,
args: args,
};
if (opts.pre) {
opts.pre(state, file);
}
// no attributes are allowed with <> syntax
args.push(t.nullLiteral(), ...path.node.children);
if (opts.post) {
opts.post(state, file);
}
return state.call || t.callExpression(state.oldCallee, args);
}
}
module.exports = function(babel) {
const {types: t} = babel;
const createIdentifierParser = id => () => {
return id
.split('.')
.map(name => t.identifier(name))
.reduce((object, property) => t.memberExpression(object, property));
};
const visitor = helper(babel, {
pre(state) {
const tagName = state.tagName;
const args = state.args;
if (t.react.isCompatTag(tagName)) {
args.push(t.stringLiteral(tagName));
} else {
args.push(state.tagExpr);
}
},
post(state, pass) {
state.callee = pass.get('jsxIdentifier')();
state.staticCallee = pass.get('jsxStaticIdentifier')();
state.oldCallee = pass.get('oldJSXIdentifier')();
},
});
visitor.Program = {
enter(path, state) {
state.set(
'oldJSXIdentifier',
createIdentifierParser('React.createElement'),
);
state.set(
'jsxIdentifier',
createIdentifierParser(
state.opts.development ? 'React.jsxDEV' : 'React.jsx',
),
);
state.set(
'jsxStaticIdentifier',
createIdentifierParser(
state.opts.development ? 'React.jsxDEV' : 'React.jsxs',
),
);
state.set('jsxFragIdentifier', createIdentifierParser('React.Fragment'));
},
};
visitor.JSXAttribute = function(path) {
if (t.isJSXElement(path.node.value)) {
path.node.value = t.jsxExpressionContainer(path.node.value);
}
};
return {
name: 'transform-react-jsx',
visitor,
};
};

View File

@@ -19,8 +19,17 @@ ESLintTester.setDefaultConfig({
},
});
const eslintTester = new ESLintTester();
eslintTester.run('react-hooks', ReactHooksESLintRule, {
// ***************************************************
// For easier local testing, you can add to any case:
// {
// skip: true,
// --or--
// only: true,
// ...
// }
// ***************************************************
const tests = {
valid: [
`
// Valid because components can use hooks.
@@ -223,21 +232,20 @@ eslintTester.run('react-hooks', ReactHooksESLintRule, {
(class {i() { useState(); }});
`,
`
// Currently valid although we *could* consider these invalid.
// It doesn't make a lot of difference because it would crash early.
// Valid because they're not matching use[A-Z].
fooState();
use();
_use();
useState();
_useState();
use42();
useHook();
use_hook();
React.useState();
`,
`
// Regression test for the popular "history" library
const {createHistory, useBasename} = require('history-2.1.2');
const browserHistory = useBasename(createHistory)({
// This is grey area.
// Currently it's valid (although React.useCallback would fail here).
// We could also get stricter and disallow it, just like we did
// with non-namespace use*() top-level calls.
const History = require('history-2.1.2');
const browserHistory = History.useBasename(History.createHistory)({
basename: '/',
});
`,
@@ -669,8 +677,63 @@ eslintTester.run('react-hooks', ReactHooksESLintRule, {
conditionalError('useState'),
],
},
{
code: `
// Invalid because it's dangerous and might not warn otherwise.
// This *must* be invalid.
function useHook({ bar }) {
let foo1 = bar && useState();
let foo2 = bar || useState();
let foo3 = bar ?? useState();
}
`,
errors: [
conditionalError('useState'),
conditionalError('useState'),
// TODO: ideally this *should* warn, but ESLint
// doesn't plan full support for ?? until it advances.
// conditionalError('useState'),
],
},
{
code: `
// Invalid because it's dangerous.
// Normally, this would crash, but not if you use inline requires.
// This *must* be invalid.
// It's expected to have some false positives, but arguably
// they are confusing anyway due to the use*() convention
// already being associated with Hooks.
useState();
if (foo) {
const foo = React.useCallback(() => {});
}
useCustomHook();
`,
errors: [
topLevelError('useState'),
topLevelError('React.useCallback'),
topLevelError('useCustomHook'),
],
},
{
code: `
// Technically this is a false positive.
// We *could* make it valid (and it used to be).
//
// However, top-level Hook-like calls can be very dangerous
// in environments with inline requires because they can mask
// the runtime error by accident.
// So we prefer to disallow it despite the false positive.
const {createHistory, useBasename} = require('history-2.1.2');
const browserHistory = useBasename(createHistory)({
basename: '/',
});
`,
errors: [topLevelError('useBasename')],
},
],
});
};
function conditionalError(hook, hasPreviousFinalizer = false) {
return {
@@ -708,3 +771,42 @@ function genericError(hook) {
'Hook function.',
};
}
function topLevelError(hook) {
return {
message:
`React Hook "${hook}" cannot be called at the top level. React Hooks ` +
'must be called in a React function component or a custom React ' +
'Hook function.',
};
}
// For easier local testing
if (!process.env.CI) {
let only = [];
let skipped = [];
[...tests.valid, ...tests.invalid].forEach(t => {
if (t.skip) {
delete t.skip;
skipped.push(t);
}
if (t.only) {
delete t.only;
only.push(t);
}
});
const predicate = t => {
if (only.length > 0) {
return only.indexOf(t) !== -1;
}
if (skipped.length > 0) {
return skipped.indexOf(t) === -1;
}
return true;
};
tests.valid = tests.valid.filter(predicate);
tests.invalid = tests.invalid.filter(predicate);
}
const eslintTester = new ESLintTester();
eslintTester.run('react-hooks', ReactHooksESLintRule, tests);

View File

@@ -1,7 +1,7 @@
{
"name": "eslint-plugin-react-hooks",
"description": "ESLint rules for React Hooks",
"version": "1.7.0",
"version": "2.0.1",
"repository": {
"type": "git",
"url": "https://github.com/facebook/react.git",

View File

@@ -432,9 +432,12 @@ export default {
'React Hook function.';
context.report({node: hook, message});
} else if (codePathNode.type === 'Program') {
// For now, ignore if it's in top level scope.
// We could warn here but there are false positives related
// configuring libraries like `history`.
// These are dangerous if you have inline requires enabled.
const message =
`React Hook "${context.getSource(hook)}" cannot be called ` +
'at the top level. React Hooks must be called in a ' +
'React function component or a custom React Hook function.';
context.report({node: hook, message});
} else {
// Assume in all other cases the user called a hook in some
// random function callback. This should usually be true for

View File

@@ -22,6 +22,7 @@ import type {ReactSyntheticEvent} from './ReactSyntheticEventType';
import type {Fiber} from 'react-reconciler/src/ReactFiber';
import type {AnyNativeEvent} from './PluginModuleType';
import type {TopLevelType} from './TopLevelEventTypes';
import type {EventSystemFlags} from 'legacy-events/EventSystemFlags';
function isInteractive(tag) {
return (
@@ -131,6 +132,7 @@ export function getListener(inst: Fiber, registrationName: string) {
*/
function extractPluginEvents(
topLevelType: TopLevelType,
eventSystemFlags: EventSystemFlags,
targetInst: null | Fiber,
nativeEvent: AnyNativeEvent,
nativeEventTarget: EventTarget,
@@ -142,6 +144,7 @@ function extractPluginEvents(
if (possiblePlugin) {
const extractedEvents = possiblePlugin.extractEvents(
topLevelType,
eventSystemFlags,
targetInst,
nativeEvent,
nativeEventTarget,
@@ -156,12 +159,14 @@ function extractPluginEvents(
export function runExtractedPluginEventsInBatch(
topLevelType: TopLevelType,
eventSystemFlags: EventSystemFlags,
targetInst: null | Fiber,
nativeEvent: AnyNativeEvent,
nativeEventTarget: EventTarget,
) {
const events = extractPluginEvents(
topLevelType,
eventSystemFlags,
targetInst,
nativeEvent,
nativeEventTarget,

View File

@@ -14,3 +14,4 @@ export const RESPONDER_EVENT_SYSTEM = 1 << 1;
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;

View File

@@ -13,6 +13,7 @@ import type {
ReactSyntheticEvent,
} from './ReactSyntheticEventType';
import type {TopLevelType} from './TopLevelEventTypes';
import type {EventSystemFlags} from 'legacy-events/EventSystemFlags';
export type EventTypes = {[key: string]: DispatchConfig};
@@ -24,6 +25,7 @@ export type PluginModule<NativeEvent> = {
eventTypes: EventTypes,
extractEvents: (
topLevelType: TopLevelType,
eventSystemFlags: EventSystemFlags,
targetInst: null | Fiber,
nativeTarget: NativeEvent,
nativeEventTarget: EventTarget,

View File

@@ -9,7 +9,9 @@ import {
needsStateRestore,
restoreStateIfNeeded,
} from './ReactControlledComponent';
import {enableFlareAPI} from 'shared/ReactFeatureFlags';
import {invokeGuardedCallbackAndCatchFirstError} from 'shared/ReactErrorUtils';
// Used as a way to call batchedUpdates when we don't have a reference to
// the renderer. Such as when we're dispatching events or if third party
@@ -28,6 +30,7 @@ let flushDiscreteUpdatesImpl = function() {};
let batchedEventUpdatesImpl = batchedUpdatesImpl;
let isInsideEventHandler = false;
let isBatchingEventUpdates = false;
function finishEventHandler() {
// Here we wait until all updates have propagated, which is important
@@ -60,20 +63,32 @@ export function batchedUpdates(fn, bookkeeping) {
}
export function batchedEventUpdates(fn, a, b) {
if (isInsideEventHandler) {
if (isBatchingEventUpdates) {
// If we are currently inside another batch, we need to wait until it
// fully completes before restoring state.
return fn(a, b);
}
isInsideEventHandler = true;
isBatchingEventUpdates = true;
try {
return batchedEventUpdatesImpl(fn, a, b);
} finally {
isInsideEventHandler = false;
isBatchingEventUpdates = false;
finishEventHandler();
}
}
// This is for the React Flare event system
export function executeUserEventHandler(fn: any => void, value: any): void {
const previouslyInEventHandler = isInsideEventHandler;
try {
isInsideEventHandler = true;
const type = typeof value === 'object' && value !== null ? value.type : '';
invokeGuardedCallbackAndCatchFirstError(type, fn, undefined, value);
} finally {
isInsideEventHandler = previouslyInEventHandler;
}
}
export function discreteUpdates(fn, a, b, c) {
const prevIsInsideEventHandler = isInsideEventHandler;
isInsideEventHandler = true;

View File

@@ -504,6 +504,7 @@ const ResponderEventPlugin = {
*/
extractEvents: function(
topLevelType,
eventSystemFlags,
targetInst,
nativeEvent,
nativeEventTarget,
@@ -514,7 +515,7 @@ const ResponderEventPlugin = {
if (trackedTouchCount >= 0) {
trackedTouchCount -= 1;
} else {
console.error(
console.warn(
'Ended a touch event which was not counted in `trackedTouchCount`.',
);
return null;

View File

@@ -10,6 +10,7 @@
'use strict';
const {HostComponent} = require('shared/ReactWorkTags');
const {PLUGIN_EVENT_SYSTEM} = require('legacy-events/EventSystemFlags');
let EventBatching;
let EventPluginUtils;
@@ -313,6 +314,7 @@ const run = function(config, hierarchyConfig, nativeEventConfig) {
// Trigger the event
const extractedEvents = ResponderEventPlugin.extractEvents(
nativeEventConfig.topLevelType,
PLUGIN_EVENT_SYSTEM,
nativeEventConfig.targetInst,
nativeEventConfig.nativeEvent,
nativeEventConfig.target,

View File

@@ -66,7 +66,7 @@ class Surface extends React.Component {
this._surface = Mode.Surface(+width, +height, this._tagRef);
this._mountNode = createContainer(this._surface, LegacyRoot, false);
this._mountNode = createContainer(this._surface, LegacyRoot, false, null);
updateContainer(this.props.children, this._mountNode, this);
}

View File

@@ -432,7 +432,6 @@ export function mountResponderInstance(
props: Object,
state: Object,
instance: Object,
rootContainerInstance: Object,
) {
throw new Error('Not yet implemented.');
}

View File

@@ -12,20 +12,17 @@ This is similar requiring the `react-devtools` package, but provides several con
```js
const { connectToDevTools } = require("react-devtools-core");
connectToDevTools({
// Config options
});
connectToDevTools(config);
```
Run `connectToDevTools()` in the same context as React to set up a connection to DevTools.
Be sure to run this function *before* importing e.g. `react`, `react-dom`, `react-native`.
The `options` object may contain:
The `config` object may contain:
* `host: string` (defaults to "localhost") - Websocket will connect to this host.
* `port: number` (defaults to `8097`) - Websocket will connect to this port.
* `websocket: Websocket` - Custom websocked to use. Overrides `host` and `port` settings if provided.
* `resolveNativeStyle: (style: number) => ?Object` - Used by the React Native style plug-in.
* `resolveRNStyle: (style: number) => ?Object` - Used by the React Native style plug-in.
* `isAppActive: () => boolean` - If provided, DevTools will poll this method and wait until it returns true before connecting to React.
## `react-devtools-core/standalone`
@@ -42,3 +39,15 @@ require("react-devtools-core/standalone")
```
Reference the `react-devtools` package for a complete integration example.
## Development
Watch for changes made to the backend entry point and rebuild:
```sh
yarn start:backend
```
Watch for changes made to the standalone UI entry point and rebuild:
```sh
yarn start:standalone
```

View File

@@ -1,12 +1,13 @@
{
"name": "react-devtools-core",
"version": "4.0.0-alpha.9",
"version": "4.1.0",
"description": "Use react-devtools outside of the browser",
"license": "MIT",
"main": "./dist/backend.js",
"repository": {
"url": "https://github.com/bvaughn/react-devtools-experimental.git",
"type": "git"
"type": "git",
"url": "https://github.com/facebook/react.git",
"directory": "packages/react-devtools-core"
},
"files": [
"dist",
@@ -23,6 +24,7 @@
"start:standalone": "cross-env NODE_ENV=development webpack --config webpack.standalone.js --watch"
},
"dependencies": {
"es6-symbol": "^3",
"shell-quote": "^1.6.1",
"ws": "^7"
},

View File

@@ -1,4 +1,11 @@
// @flow
/**
* 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.
*
* @flow
*/
import Agent from 'react-devtools-shared/src/backend/agent';
import Bridge from 'react-devtools-shared/src/bridge';

View File

@@ -1,4 +1,11 @@
// @flow
/**
* 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.
*
* @flow
*/
import {existsSync} from 'fs';
import {basename, join, isAbsolute} from 'path';

View File

@@ -1,4 +1,11 @@
// @flow
/**
* 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.
*
* @flow
*/
import {createElement} from 'react';
import {
@@ -14,7 +21,8 @@ import {
getAppendComponentStack,
} from 'react-devtools-shared/src/utils';
import {Server} from 'ws';
import {existsSync, readFileSync} from 'fs';
import {join} from 'path';
import {readFileSync} from 'fs';
import {installHook} from 'react-devtools-shared/src/hook';
import DevTools from 'react-devtools-shared/src/devtools/views/DevTools';
import {doesFilePathExist, launchEditor} from './editor';
@@ -32,11 +40,6 @@ let nodeWaitingToConnectHTML: string = '';
let projectRoots: Array<string> = [];
let statusListener: StatusListener = (message: string) => {};
// Unlike browser extension users, people using the standalone have actively installed version 4,
// So we probably don't need to show them a changelog notice.
// We should give embedded users (e.g. Nuclide, Sonar) a way of showing this dialog though.
let showWelcomeToTheNewDevToolsDialog: boolean = false;
function setContentDOMNode(value: HTMLElement) {
node = value;
@@ -55,11 +58,6 @@ function setStatusListener(value: StatusListener) {
return DevtoolsUI;
}
function setShowWelcomeToTheNewDevToolsDialog(value: boolean) {
showWelcomeToTheNewDevToolsDialog = value;
return DevtoolsUI;
}
let bridge: FrontendBridge | null = null;
let store: Store | null = null;
let root = null;
@@ -100,7 +98,6 @@ function reload() {
bridge: ((bridge: any): FrontendBridge),
canViewElementSourceFunction,
showTabBar: true,
showWelcomeToTheNewDevToolsDialog,
store: ((store: any): Store),
warnIfLegacyBackendDetected: true,
viewElementSourceFunction,
@@ -259,14 +256,8 @@ function startServer(port?: number = 8097) {
});
httpServer.on('request', (request, response) => {
// NPM installs should read from node_modules,
// But local dev mode needs to use a relative path.
const basePath = existsSync('./node_modules/react-devtools-core')
? 'node_modules/react-devtools-core'
: '../react-devtools-core';
// Serve a file that immediately sets up the connection.
const backendFile = readFileSync(`${basePath}/dist/backend.js`);
const backendFile = readFileSync(join(__dirname, 'backend.js'));
// The renderer interface doesn't read saved component filters directly,
// because they are generally stored in localStorage within the context of the extension.
@@ -316,7 +307,6 @@ const DevtoolsUI = {
connectToSocket,
setContentDOMNode,
setProjectRoots,
setShowWelcomeToTheNewDevToolsDialog,
setStatusListener,
startServer,
};

View File

@@ -40,6 +40,12 @@ module.exports = {
scheduler: resolve(builtModulesDir, 'scheduler'),
},
},
node: {
// Don't replace __dirname!
// This would break the standalone DevTools ability to load the backend.
// see https://github.com/facebook/react-devtools/issues/1269
__dirname: false,
},
plugins: [
new DefinePlugin({
__DEV__: false,

View File

@@ -1,17 +1,27 @@
This is the source code for the React DevTools browser extension.
# Installation
## Installation
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/)
# Build
## Development
You can also build and install from source:
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 install
yarn build:chrome # builds at "packages/react-devtools-extensions/chrome/build"
yarn build:firefox # builds at "packages/react-devtools-extensions/firefox/build"
yarn build -- react,react-dom,react-is,scheduler --type=NODE
```
Once the above packages have been built, you can build the extension by running:
```sh
cd packages/react-devtools-extensions/
yarn build:chrome # => packages/react-devtools-extensions/chrome/build
yarn run test:chrome # Test Chrome extension
yarn build:firefox # => packages/react-devtools-extensions/firefox/build
yarn run test:firefox # Test Firefox extension
```

View File

@@ -61,14 +61,13 @@ const build = async (tempPath, manifestPath) => {
);
const commit = getGitCommit();
const versionDateString = `${commit} (${new Date().toLocaleDateString()})`;
const dateString = new Date().toLocaleDateString();
const manifest = JSON.parse(readFileSync(copiedManifestPath).toString());
const versionDateString = `${manifest.version} (${dateString})`;
if (manifest.version_name) {
manifest.version_name = versionDateString;
} else {
manifest.description += `\n\nCreated from revision ${versionDateString}`;
}
manifest.description += `\n\nCreated from revision ${commit} on ${dateString}.`;
writeFileSync(copiedManifestPath, JSON.stringify(manifest, null, 2));

View File

@@ -29,9 +29,22 @@ const main = async () => {
safeKeyPath = join(relative(cwd, process.cwd()), keyPath);
}
execSync(`crx pack ./unpacked -o ReactDevTools.crx -p ${safeKeyPath}`, {
cwd,
});
const crxPath = join(
__dirname,
'..',
'..',
'..',
'node_modules',
'.bin',
'crx'
);
execSync(
`${crxPath} pack ./unpacked -o ReactDevTools.crx -p ${safeKeyPath}`,
{
cwd,
}
);
}
console.log(chalk.green('\nThe Chrome extension has been built!'));

View File

@@ -2,8 +2,8 @@
"manifest_version": 2,
"name": "React Developer Tools",
"description": "Adds React debugging tools to the Chrome Developer Tools.",
"version": "4.0.0",
"version_name": "4.0.0",
"version": "4.1.0",
"version_name": "4.1.0",
"minimum_chrome_version": "49",
@@ -40,15 +40,7 @@
"persistent": false
},
"permissions": [
"<all_urls>",
"background",
"tabs",
"webNavigation",
"file:///*",
"http://*/*",
"https://*/*"
],
"permissions": ["file:///*", "http://*/*", "https://*/*"],
"content_scripts": [
{

View File

@@ -18,12 +18,11 @@
<h3>
Created on <strong>%date%</strong> from
<a href="http://github.com/bvaughn/react-devtools-experimental/commit/%commit%"><code>%commit%</code></a>
<a href="http://github.com/facebook/react/commit/%commit%"><code>%commit%</code></a>
</h3>
<p>
This is a preview build of an <a href="https://github.com/facebook/react/tree/master/packages/react-devtools-extensions">unreleased DevTools extension</a>.
It has no developer support.
This is a preview build of the <a href="https://github.com/facebook/react">React DevTools extension</a>.
</p>
<h2>Installation instructions</h2>
@@ -37,10 +36,5 @@
Please report bugs as <a href="https://github.com/facebook/react/issues/new?labels=Component:%20Developer%20Tools">GitHub issues</a>.
Please include all of the info required to reproduce the bug (e.g. links, code, instructions).
</p>
<h2>Feature requests</h2>
<p>
Feature requests are not being accepted at this time.
</p>
</body>
</html>

View File

@@ -2,7 +2,7 @@
"manifest_version": 2,
"name": "React Developer Tools",
"description": "Adds React debugging tools to the Firefox Developer Tools.",
"version": "4.0.0",
"version": "4.1.0",
"applications": {
"gecko": {
@@ -44,15 +44,7 @@
"scripts": ["build/background.js"]
},
"permissions": [
"<all_urls>",
"activeTab",
"tabs",
"webNavigation",
"file:///*",
"http://*/*",
"https://*/*"
],
"permissions": ["file:///*", "http://*/*", "https://*/*"],
"content_scripts": [
{

View File

@@ -1,96 +0,0 @@
// @flow
'use strict';
declare var chrome: {
devtools: {
network: {
onNavigated: {
addListener: (cb: (url: string) => void) => void,
removeListener: (cb: () => void) => void,
},
},
inspectedWindow: {
eval: (code: string, cb?: (res: any, err: ?Object) => any) => void,
tabId: number,
},
panels: {
create: (
title: string,
icon: string,
filename: string,
cb: (panel: {
onHidden: {
addListener: (cb: (window: Object) => void) => void,
},
onShown: {
addListener: (cb: (window: Object) => void) => void,
},
}) => void
) => void,
themeName: ?string,
},
},
tabs: {
create: (options: Object) => void,
executeScript: (tabId: number, options: Object, fn: () => void) => void,
onUpdated: {
addListener: (
fn: (tabId: number, changeInfo: Object, tab: Object) => void
) => void,
},
query: (options: Object, fn: (tabArray: Array<Object>) => void) => void,
},
browserAction: {
setIcon: (options: {
tabId: number,
path: {[key: string]: string},
}) => void,
setPopup: (options: {
tabId: number,
popup: string,
}) => void,
},
runtime: {
getURL: (path: string) => string,
sendMessage: (config: Object) => void,
connect: (
config: Object
) => {
disconnect: () => void,
onMessage: {
addListener: (fn: (message: Object) => void) => void,
},
onDisconnect: {
addListener: (fn: (message: Object) => void) => void,
},
postMessage: (data: Object) => void,
},
onConnect: {
addListener: (
fn: (port: {
name: string,
sender: {
tab: {
id: number,
url: string,
},
},
}) => void
) => void,
},
onMessage: {
addListener: (
fn: (
req: Object,
sender: {
url: string,
tab: {
id: number,
},
}
) => void
) => void,
},
},
};

View File

@@ -30,6 +30,7 @@
"chrome-launch": "^1.1.4",
"child-process-promise": "^2.2.1",
"css-loader": "^1.0.1",
"crx": "^5.0.0",
"firefox-profile": "^1.0.2",
"node-libs-browser": "0.5.3",
"nullthrows": "^1.0.0",

View File

@@ -28,5 +28,5 @@
</p>
<hr />
<p>
Open the developer tools, and the React tab will appear to the right.
Open the developer tools, and "Components" and "Profiler" tabs will appear to the right.
</p>

View File

@@ -24,5 +24,5 @@
</p>
<hr />
<p>
Open the developer tools, and the React tab will appear to the right.
Open the developer tools, and "Components" and "Profiler" tabs will appear to the right.
</p>

View File

@@ -17,5 +17,5 @@
<p>
<b>This page doesn&rsquo;t appear to be using React.</b>
<br />
If this seems wrong, follow the <a href="https://github.com/facebook/react-devtools/blob/master/README.md#the-react-tab-doesnt-show-up">troubleshooting instructions</a>.
If this seems wrong, follow the <a href="https://github.com/facebook/react/tree/master/packages/react-devtools#the-react-tab-doesnt-show-up">troubleshooting instructions</a>.
</p>

View File

@@ -25,5 +25,5 @@
</p>
<hr />
<p>
Open the developer tools, and the React tab will appear to the right.
Open the developer tools, and "Components" and "Profiler" tabs will appear to the right.
</p>

View File

@@ -17,5 +17,5 @@
<p>
<b>This page is using the production build of React. &#x2705;</b>
<br />
Open the developer tools, and the React tab will appear to the right.
Open the developer tools, and "Components" and "Profiler" tabs will appear to the right.
</p>

View File

@@ -27,5 +27,5 @@
</p>
<hr />
<p>
Open the developer tools, and the React tab will appear to the right.
Open the developer tools, and "Components" and "Profiler" tabs will appear to the right.
</p>

View File

@@ -24,6 +24,8 @@ import DevTools from 'react-devtools-shared/src/devtools/views/DevTools';
const LOCAL_STORAGE_SUPPORTS_PROFILING_KEY =
'React::DevTools::supportsProfiling';
const isChrome = getBrowserName() === 'Chrome';
let panelCreated = false;
// The renderer interface can't read saved component filters directly,
@@ -120,9 +122,13 @@ function createPanelIfReactLoaded() {
localStorageRemoveItem(LOCAL_STORAGE_SUPPORTS_PROFILING_KEY);
}
if (store !== null) {
profilingData = store.profilerStore.profilingData;
}
store = new Store(bridge, {
isProfiling,
supportsReloadAndProfile: getBrowserName() === 'Chrome',
supportsReloadAndProfile: isChrome,
supportsProfiling,
});
store.profilerStore.profilingData = profilingData;
@@ -228,7 +234,7 @@ function createPanelIfReactLoaded() {
let needsToSyncElementSelection = false;
chrome.devtools.panels.create(
'Components',
isChrome ? '⚛ Components' : 'Components',
'',
'panel.html',
extensionPanel => {
@@ -258,7 +264,7 @@ function createPanelIfReactLoaded() {
);
chrome.devtools.panels.create(
'Profiler',
isChrome ? '⚛ Profiler' : 'Profiler',
'',
'panel.html',
extensionPanel => {
@@ -281,21 +287,6 @@ function createPanelIfReactLoaded() {
chrome.devtools.network.onNavigated.removeListener(checkPageForReact);
// Shutdown bridge before a new page is loaded.
chrome.webNavigation.onBeforeNavigate.addListener(
function onBeforeNavigate(details) {
// Ignore navigation events from other tabs (or from within frames).
if (details.tabId !== tabId || details.frameId !== 0) {
return;
}
// `bridge.shutdown()` will remove all listeners we added, so we don't have to.
bridge.shutdown();
profilingData = store.profilerStore.profilingData;
},
);
// Re-initialize DevTools panel when a new page is loaded.
chrome.devtools.network.onNavigated.addListener(function onNavigated() {
// Re-initialize saved filters on navigation,

View File

@@ -12,10 +12,21 @@ export function createViewElementSource(bridge: Bridge, store: Store) {
setTimeout(() => {
// Ask Chrome to display the location of the component function,
// or a render method if it is a Class (ideally Class instance, not type)
// assuming the renderer found one.
chrome.devtools.inspectedWindow.eval(`
if (window.$type != null) {
inspect(window.$type);
if (
window.$type &&
window.$type.prototype &&
window.$type.prototype.isReactComponent
) {
// inspect Component.render, not constructor
inspect(window.$type.prototype.render);
} else {
// inspect Functional Component
inspect(window.$type);
}
}
`);
}, 100);

View File

@@ -21,24 +21,32 @@ The frontend and backend can be initialized in any order, but **the backend must
### `react-devtools-inline/backend`
* **`initialize(contentWindow)`** -
Installs the global hook on the window. This hook is how React and DevTools communicate. **This method must be called before React is loaded.** (This means before any `import` or `require` statements!)
Installs the global hook on the window. This hook is how React and DevTools communicate. **This method must be called before React is loaded.**<sup>2</sup>
* **`activate(contentWindow)`** -
Lets the backend know when the frontend is ready. It should not be called until after the frontend has been initialized, else the frontend might miss important tree-initialization events.
```js
import { activate, initialize } from 'react-devtools-inline/backend';
// This should be the iframe the React application is running in.
const iframe = document.getElementById(frameID);
const contentWindow = iframe.contentWindow;
// Call this before importing React (or any other packages that might import React).
initialize();
initialize(contentWindow);
// Initialize the frontend...
// Call this only once the frontend has been initialized.
activate();
activate(contentWindow);
```
<sup>2</sup> The backend must be initialized before React is loaded. (This means before any `import` or `require` statements or `<script>` tags that include React.)
### `react-devtools-inline/frontend`
* **`initialize(contentWindow)`** -
Configures the DevTools interface to listen to the `window` the backend was injected into. This method returns a React component that can be rendered directly<sup>2</sup>.
Configures the DevTools interface to listen to the `window` the backend was injected into. This method returns a React component that can be rendered directly<sup>3</sup>.
```js
import { initialize } from 'react-devtools-inline/frontend';
@@ -52,7 +60,7 @@ const contentWindow = iframe.contentWindow;
const DevTools = initialize(contentWindow);
```
<sup>2</sup> Because the DevTools interface makes use of several new React APIs (e.g. suspense, concurrent mode) it should be rendered using either `ReactDOM.unstable_createRoot` or `ReactDOM.unstable_createSyncRoot`. It should not be rendered with `ReactDOM.render`.
<sup>3</sup> Because the DevTools interface makes use of several new React APIs (e.g. suspense, concurrent mode) it should be rendered using either `ReactDOM.unstable_createRoot` or `ReactDOM.unstable_createSyncRoot`. **It should not be rendered with `ReactDOM.render`.**
## Examples
@@ -143,4 +151,11 @@ iframe.onload = () => {
"*"
);
};
```
## Development
Watch for changes made to the source code and rebuild:
```sh
yarn start
```

View File

@@ -1,12 +1,13 @@
{
"name": "react-devtools-inline",
"version": "4.0.0-alpha.9",
"version": "4.1.0",
"description": "Embed react-devtools within a website",
"license": "MIT",
"main": "./dist/backend.js",
"repository": {
"url": "https://github.com/bvaughn/react-devtools-experimental.git",
"type": "git"
"type": "git",
"url": "https://github.com/facebook/react.git",
"directory": "packages/react-devtools-inline"
},
"files": [
"dist",
@@ -19,7 +20,9 @@
"prepublish": "yarn run build",
"start": "cross-env NODE_ENV=development webpack --config webpack.config.js --watch"
},
"dependencies": {},
"dependencies": {
"es6-symbol": "^3"
},
"devDependencies": {
"@babel/core": "^7.1.6",
"@babel/plugin-proposal-class-properties": "^7.1.0",

View File

@@ -2,12 +2,17 @@
"private": true,
"name": "react-devtools-shared",
"version": "0.0.0",
"devDependencies": {
"react-15": "npm:react@^15",
"react-dom-15": "npm:react-dom@^15"
},
"dependencies": {
"@babel/runtime": "^7.0.0",
"@reach/menu-button": "^0.1.17",
"@reach/tooltip": "^0.2.2",
"clipboard-js": "^0.3.6",
"events": "^3.0.0",
"local-storage-fallback": "^4.1.1",
"lodash.throttle": "^4.1.1",
"memoize-one": "^3.1.1",
"react-virtualized-auto-sizer": "^1.0.2"

View File

@@ -1,5 +1,41 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`InspectedElementContext should dehydrate complex nested values when requested: 1: Initially inspect element 1`] = `
{
"id": 2,
"owners": null,
"context": null,
"hooks": null,
"props": {
"set_of_sets": {
"0": {},
"1": {}
}
},
"state": null
}
`;
exports[`InspectedElementContext should dehydrate complex nested values when requested: 2: Inspect props.set_of_sets.0 1`] = `
{
"id": 2,
"owners": null,
"context": null,
"hooks": null,
"props": {
"set_of_sets": {
"0": {
"0": 1,
"1": 2,
"2": 3
},
"1": {}
}
},
"state": null
}
`;
exports[`InspectedElementContext should include updates for nested values that were previously hydrated: 1: Initially inspect element 1`] = `
{
"id": 2,
@@ -91,6 +127,28 @@ exports[`InspectedElementContext should include updates for nested values that w
}
`;
exports[`InspectedElementContext should inspect hooks for components that only use context: 1: Inspected element 2 1`] = `
{
"id": 2,
"owners": null,
"context": null,
"hooks": [
{
"id": null,
"isStateEditable": false,
"name": "Context",
"value": true,
"subHooks": []
}
],
"props": {
"a": 1,
"b": "abc"
},
"state": null
}
`;
exports[`InspectedElementContext should inspect the currently selected element: 1: Inspected element 2 1`] = `
{
"id": 2,
@@ -427,13 +485,38 @@ exports[`InspectedElementContext should support complex data types: 1: Inspected
"context": null,
"hooks": null,
"props": {
"html_element": {},
"fn": {},
"symbol": {},
"react_element": {},
"array_buffer": {},
"typed_array": {},
"date": {}
"date": {},
"fn": {},
"html_element": {},
"immutable": {
"0": {},
"1": {},
"2": {}
},
"map": {
"0": {},
"1": {}
},
"map_of_maps": {
"0": {},
"1": {}
},
"react_element": {},
"set": {
"0": "abc",
"1": 123
},
"set_of_sets": {
"0": {},
"1": {}
},
"symbol": {},
"typed_array": {
"0": 100,
"1": -100,
"2": 0
}
},
"state": null
}

View File

@@ -1091,7 +1091,7 @@ Object {
}
`;
exports[`TreeListContext tree state should select the next and previous elements in the tree: 3: select element after (0) 1`] = `
exports[`TreeListContext tree state should select the next and previous elements in the tree: 3: select element after (0) 1`] = `
Object {
"inspectedElementID": 3,
"numElements": 4,
@@ -1105,7 +1105,7 @@ Object {
}
`;
exports[`TreeListContext tree state should select the next and previous elements in the tree: 3: select element after (1) 1`] = `
exports[`TreeListContext tree state should select the next and previous elements in the tree: 3: select element after (1) 1`] = `
Object {
"inspectedElementID": 4,
"numElements": 4,
@@ -1119,7 +1119,7 @@ Object {
}
`;
exports[`TreeListContext tree state should select the next and previous elements in the tree: 3: select element after (2) 1`] = `
exports[`TreeListContext tree state should select the next and previous elements in the tree: 3: select element after (2) 1`] = `
Object {
"inspectedElementID": 5,
"numElements": 4,

View File

@@ -1,4 +1,11 @@
// @flow
/**
* 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.
*
* @flow
*/
describe('Bridge', () => {
let Bridge;

View File

@@ -1,4 +1,11 @@
// @flow
/**
* 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.
*
* @flow
*/
describe('console', () => {
let React;
@@ -13,23 +20,12 @@ describe('console', () => {
let unpatchConsole;
beforeEach(() => {
const Console = require('../backend/console');
jest.resetModules();
const Console = require('react-devtools-shared/src/backend/console');
patchConsole = Console.patch;
unpatchConsole = Console.unpatch;
const inject = global.__REACT_DEVTOOLS_GLOBAL_HOOK__.inject;
global.__REACT_DEVTOOLS_GLOBAL_HOOK__.inject = internals => {
inject(internals);
Console.registerRenderer(internals);
};
React = require('react');
ReactDOM = require('react-dom');
const utils = require('./utils');
act = utils.act;
// Patch a fake console so we can verify with tests below.
// Patching the real console is too complicated,
// because Jest itself has hooks into it as does our test env setup.
@@ -46,7 +42,22 @@ describe('console', () => {
Console.dangerous_setTargetConsoleForTesting(fakeConsole);
// Note the Console module only patches once,
// so it's important to patch the test console before injection.
patchConsole();
const inject = global.__REACT_DEVTOOLS_GLOBAL_HOOK__.inject;
global.__REACT_DEVTOOLS_GLOBAL_HOOK__.inject = internals => {
inject(internals);
Console.registerRenderer(internals);
};
React = require('react');
ReactDOM = require('react-dom');
const utils = require('./utils');
act = utils.act;
});
function normalizeCodeLocInfo(str) {

View File

@@ -1,4 +1,11 @@
// @flow
/**
* 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.
*
* @flow
*/
import typeof ReactTestRenderer from 'react-test-renderer';
import type {GetInspectedElementPath} from 'react-devtools-shared/src/devtools/views/Components/InspectedElementContext';
@@ -8,6 +15,7 @@ import type Store from 'react-devtools-shared/src/devtools/store';
describe('InspectedElementContext', () => {
let React;
let ReactDOM;
let PropTypes;
let TestRenderer: ReactTestRenderer;
let bridge: FrontendBridge;
let store: Store;
@@ -33,6 +41,7 @@ describe('InspectedElementContext', () => {
React = require('react');
ReactDOM = require('react-dom');
PropTypes = require('prop-types');
TestUtils = require('react-dom/test-utils');
TestRenderer = utils.requireTestRenderer();
@@ -107,6 +116,119 @@ describe('InspectedElementContext', () => {
done();
});
it('should have hasLegacyContext flag set to either "true" or "false" depending on which context API is used.', async done => {
const contextData = {
bool: true,
};
// Legacy Context API.
class LegacyContextProvider extends React.Component<any> {
static childContextTypes = {
bool: PropTypes.bool,
};
getChildContext() {
return contextData;
}
render() {
return this.props.children;
}
}
class LegacyContextConsumer extends React.Component<any> {
static contextTypes = {
bool: PropTypes.bool,
};
render() {
return null;
}
}
// Modern Context API
const BoolContext = React.createContext(contextData.bool);
BoolContext.displayName = 'BoolContext';
class ModernContextType extends React.Component<any> {
static contextType = BoolContext;
render() {
return null;
}
}
const ModernContext = React.createContext();
ModernContext.displayName = 'ModernContext';
const container = document.createElement('div');
await utils.actAsync(() =>
ReactDOM.render(
<React.Fragment>
<LegacyContextProvider>
<LegacyContextConsumer />
</LegacyContextProvider>
<BoolContext.Consumer>{value => null}</BoolContext.Consumer>
<ModernContextType />
<ModernContext.Provider value={contextData}>
<ModernContext.Consumer>{value => null}</ModernContext.Consumer>
</ModernContext.Provider>
</React.Fragment>,
container,
),
);
const ids = [
{
// <LegacyContextConsumer />
id: ((store.getElementIDAtIndex(1): any): number),
shouldHaveLegacyContext: true,
},
{
// <BoolContext.Consumer>
id: ((store.getElementIDAtIndex(2): any): number),
shouldHaveLegacyContext: false,
},
{
// <ModernContextType />
id: ((store.getElementIDAtIndex(3): any): number),
shouldHaveLegacyContext: false,
},
{
// <ModernContext.Consumer>
id: ((store.getElementIDAtIndex(5): any): number),
shouldHaveLegacyContext: false,
},
];
function Suspender({target, shouldHaveLegacyContext}) {
const {getInspectedElement} = React.useContext(InspectedElementContext);
const inspectedElement = getInspectedElement(target);
expect(inspectedElement.context).not.toBe(null);
expect(inspectedElement.hasLegacyContext).toBe(shouldHaveLegacyContext);
return null;
}
for (let i = 0; i < ids.length; i++) {
const {id, shouldHaveLegacyContext} = ids[i];
await utils.actAsync(
() =>
TestRenderer.create(
<Contexts
defaultSelectedElementID={id}
defaultSelectedElementIndex={0}>
<React.Suspense fallback={null}>
<Suspender
target={id}
shouldHaveLegacyContext={shouldHaveLegacyContext}
/>
</React.Suspense>
</Contexts>,
),
false,
);
}
done();
});
it('should poll for updates for the currently selected element', async done => {
const Example = () => null;
@@ -385,23 +507,42 @@ describe('InspectedElementContext', () => {
});
it('should support complex data types', async done => {
const Immutable = require('immutable');
const Example = () => null;
const div = document.createElement('div');
const exmapleFunction = () => {};
const typedArray = new Uint8Array(3);
const exampleFunction = () => {};
const setShallow = new Set(['abc', 123]);
const mapShallow = new Map([['name', 'Brian'], ['food', 'sushi']]);
const setOfSets = new Set([new Set(['a', 'b', 'c']), new Set([1, 2, 3])]);
const mapOfMaps = new Map([['first', mapShallow], ['second', mapShallow]]);
const typedArray = Int8Array.from([100, -100, 0]);
const immutableMap = Immutable.fromJS({
a: [{hello: 'there'}, 'fixed', true],
b: 123,
c: {
'1': 'xyz',
xyz: 1,
},
});
const container = document.createElement('div');
await utils.actAsync(() =>
ReactDOM.render(
<Example
html_element={div}
fn={exmapleFunction}
symbol={Symbol('symbol')}
react_element={<span />}
array_buffer={typedArray.buffer}
typed_array={typedArray}
date={new Date()}
fn={exampleFunction}
html_element={div}
immutable={immutableMap}
map={mapShallow}
map_of_maps={mapOfMaps}
react_element={<span />}
set={setShallow}
set_of_sets={setOfSets}
symbol={Symbol('symbol')}
typed_array={typedArray}
/>,
container,
),
@@ -435,37 +576,77 @@ describe('InspectedElementContext', () => {
expect(inspectedElement).toMatchSnapshot(`1: Inspected element ${id}`);
const {
html_element,
fn,
symbol,
react_element,
array_buffer,
typed_array,
date,
fn,
html_element,
immutable,
map,
map_of_maps,
react_element,
set,
set_of_sets,
symbol,
typed_array,
} = (inspectedElement: any).props;
expect(html_element[meta.inspectable]).toBe(false);
expect(html_element[meta.name]).toBe('DIV');
expect(html_element[meta.type]).toBe('html_element');
expect(fn[meta.inspectable]).toBe(false);
expect(fn[meta.name]).toBe('exmapleFunction');
expect(fn[meta.type]).toBe('function');
expect(symbol[meta.inspectable]).toBe(false);
expect(symbol[meta.name]).toBe('Symbol(symbol)');
expect(symbol[meta.type]).toBe('symbol');
expect(react_element[meta.inspectable]).toBe(false);
expect(react_element[meta.name]).toBe('span');
expect(react_element[meta.type]).toBe('react_element');
expect(array_buffer[meta.size]).toBe(3);
expect(array_buffer[meta.inspectable]).toBe(false);
expect(array_buffer[meta.name]).toBe('ArrayBuffer');
expect(array_buffer[meta.type]).toBe('array_buffer');
expect(typed_array[meta.size]).toBe(3);
expect(typed_array[meta.inspectable]).toBe(false);
expect(typed_array[meta.name]).toBe('Uint8Array');
expect(typed_array[meta.type]).toBe('typed_array');
expect(date[meta.inspectable]).toBe(false);
expect(date[meta.type]).toBe('date');
expect(fn[meta.inspectable]).toBe(false);
expect(fn[meta.name]).toBe('exampleFunction');
expect(fn[meta.type]).toBe('function');
expect(html_element[meta.inspectable]).toBe(false);
expect(html_element[meta.name]).toBe('DIV');
expect(html_element[meta.type]).toBe('html_element');
expect(immutable[meta.inspectable]).toBeUndefined(); // Complex type
expect(immutable[meta.name]).toBe('Map');
expect(immutable[meta.type]).toBe('iterator');
expect(map[meta.inspectable]).toBeUndefined(); // Complex type
expect(map[meta.name]).toBe('Map');
expect(map[meta.type]).toBe('iterator');
expect(map[0][meta.type]).toBe('array');
expect(map_of_maps[meta.inspectable]).toBeUndefined(); // Complex type
expect(map_of_maps[meta.name]).toBe('Map');
expect(map_of_maps[meta.type]).toBe('iterator');
expect(map_of_maps[0][meta.type]).toBe('array');
expect(react_element[meta.inspectable]).toBe(false);
expect(react_element[meta.name]).toBe('span');
expect(react_element[meta.type]).toBe('react_element');
expect(set[meta.inspectable]).toBeUndefined(); // Complex type
expect(set[meta.name]).toBe('Set');
expect(set[meta.type]).toBe('iterator');
expect(set[0]).toBe('abc');
expect(set[1]).toBe(123);
expect(set_of_sets[meta.inspectable]).toBeUndefined(); // Complex type
expect(set_of_sets[meta.name]).toBe('Set');
expect(set_of_sets[meta.type]).toBe('iterator');
expect(set_of_sets['0'][meta.inspectable]).toBe(true);
expect(symbol[meta.inspectable]).toBe(false);
expect(symbol[meta.name]).toBe('Symbol(symbol)');
expect(symbol[meta.type]).toBe('symbol');
expect(typed_array[meta.inspectable]).toBeUndefined(); // Complex type
expect(typed_array[meta.size]).toBe(3);
expect(typed_array[meta.name]).toBe('Int8Array');
expect(typed_array[meta.type]).toBe('typed_array');
expect(typed_array[0]).toBe(100);
expect(typed_array[1]).toBe(-100);
expect(typed_array[2]).toBe(0);
done();
});
@@ -655,6 +836,61 @@ describe('InspectedElementContext', () => {
done();
});
it('should dehydrate complex nested values when requested', async done => {
const Example = () => null;
const container = document.createElement('div');
await utils.actAsync(() =>
ReactDOM.render(
<Example
set_of_sets={new Set([new Set([1, 2, 3]), new Set(['a', 'b', 'c'])])}
/>,
container,
),
);
const id = ((store.getElementIDAtIndex(0): any): number);
let getInspectedElementPath: GetInspectedElementPath = ((null: any): GetInspectedElementPath);
let inspectedElement = null;
function Suspender({target}) {
const context = React.useContext(InspectedElementContext);
getInspectedElementPath = context.getInspectedElementPath;
inspectedElement = context.getInspectedElement(target);
return null;
}
await utils.actAsync(
() =>
TestRenderer.create(
<Contexts
defaultSelectedElementID={id}
defaultSelectedElementIndex={0}>
<React.Suspense fallback={null}>
<Suspender target={id} />
</React.Suspense>
</Contexts>,
),
false,
);
expect(getInspectedElementPath).not.toBeNull();
expect(inspectedElement).not.toBeNull();
expect(inspectedElement).toMatchSnapshot('1: Initially inspect element');
inspectedElement = null;
TestUtils.act(() => {
TestRenderer.act(() => {
getInspectedElementPath(id, ['props', 'set_of_sets', 0]);
jest.runOnlyPendingTimers();
});
});
expect(inspectedElement).not.toBeNull();
expect(inspectedElement).toMatchSnapshot('2: Inspect props.set_of_sets.0');
done();
});
it('should include updates for nested values that were previously hydrated', async done => {
const Example = () => null;
@@ -846,4 +1082,46 @@ describe('InspectedElementContext', () => {
done();
});
it('should inspect hooks for components that only use context', async done => {
const Context = React.createContext(true);
const Example = () => {
const value = React.useContext(Context);
return value;
};
const container = document.createElement('div');
await utils.actAsync(() =>
ReactDOM.render(<Example a={1} b="abc" />, container),
);
const id = ((store.getElementIDAtIndex(0): any): number);
let didFinish = false;
function Suspender({target}) {
const {getInspectedElement} = React.useContext(InspectedElementContext);
const inspectedElement = getInspectedElement(id);
expect(inspectedElement).toMatchSnapshot(`1: Inspected element ${id}`);
didFinish = true;
return null;
}
await utils.actAsync(
() =>
TestRenderer.create(
<Contexts
defaultSelectedElementID={id}
defaultSelectedElementIndex={0}>
<React.Suspense fallback={null}>
<Suspender target={id} />
</React.Suspense>
</Contexts>,
),
false,
);
expect(didFinish).toBe(true);
done();
});
});

View File

@@ -126,13 +126,38 @@ Object {
"context": {},
"hooks": null,
"props": {
"html_element": {},
"fn": {},
"symbol": {},
"react_element": {},
"array_buffer": {},
"typed_array": {},
"date": {}
"date": {},
"fn": {},
"html_element": {},
"immutable": {
"0": {},
"1": {},
"2": {}
},
"map": {
"0": {},
"1": {}
},
"map_of_maps": {
"0": {},
"1": {}
},
"react_element": {},
"set": {
"0": "abc",
"1": 123
},
"set_of_sets": {
"0": {},
"1": {}
},
"symbol": {},
"typed_array": {
"0": 100,
"1": -100,
"2": 0
}
},
"state": null
},

View File

@@ -1,4 +1,11 @@
// @flow
/**
* 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.
*
* @flow
*/
import type {InspectedElementPayload} from 'react-devtools-shared/src/backend/types';
import type {DehydratedData} from 'react-devtools-shared/src/devtools/views/Components/types';
@@ -23,7 +30,11 @@ describe('InspectedElementContext', () => {
dehydratedData: DehydratedData | null,
): Object | null {
if (dehydratedData !== null) {
return hydrate(dehydratedData.data, dehydratedData.cleaned);
return hydrate(
dehydratedData.data,
dehydratedData.cleaned,
dehydratedData.unserializable,
);
} else {
return null;
}
@@ -132,22 +143,41 @@ describe('InspectedElementContext', () => {
});
it('should support complex data types', async done => {
const Immutable = require('immutable');
const Example = () => null;
const div = document.createElement('div');
const exmapleFunction = () => {};
const typedArray = new Uint8Array(3);
const exampleFunction = () => {};
const setShallow = new Set(['abc', 123]);
const mapShallow = new Map([['name', 'Brian'], ['food', 'sushi']]);
const setOfSets = new Set([new Set(['a', 'b', 'c']), new Set([1, 2, 3])]);
const mapOfMaps = new Map([['first', mapShallow], ['second', mapShallow]]);
const typedArray = Int8Array.from([100, -100, 0]);
const immutableMap = Immutable.fromJS({
a: [{hello: 'there'}, 'fixed', true],
b: 123,
c: {
'1': 'xyz',
xyz: 1,
},
});
act(() =>
ReactDOM.render(
<Example
html_element={div}
fn={exmapleFunction}
symbol={Symbol('symbol')}
react_element={<span />}
array_buffer={typedArray.buffer}
typed_array={typedArray}
date={new Date()}
fn={exampleFunction}
html_element={div}
immutable={immutableMap}
map={mapShallow}
map_of_maps={mapOfMaps}
react_element={<span />}
set={setShallow}
set_of_sets={setOfSets}
symbol={Symbol('symbol')}
typed_array={typedArray}
/>,
document.createElement('div'),
),
@@ -159,37 +189,77 @@ describe('InspectedElementContext', () => {
expect(inspectedElement).toMatchSnapshot('1: Initial inspection');
const {
html_element,
fn,
symbol,
react_element,
array_buffer,
typed_array,
date,
fn,
html_element,
immutable,
map,
map_of_maps,
react_element,
set,
set_of_sets,
symbol,
typed_array,
} = inspectedElement.value.props;
expect(html_element[meta.inspectable]).toBe(false);
expect(html_element[meta.name]).toBe('DIV');
expect(html_element[meta.type]).toBe('html_element');
expect(fn[meta.inspectable]).toBe(false);
expect(fn[meta.name]).toBe('exmapleFunction');
expect(fn[meta.type]).toBe('function');
expect(symbol[meta.inspectable]).toBe(false);
expect(symbol[meta.name]).toBe('Symbol(symbol)');
expect(symbol[meta.type]).toBe('symbol');
expect(react_element[meta.inspectable]).toBe(false);
expect(react_element[meta.name]).toBe('span');
expect(react_element[meta.type]).toBe('react_element');
expect(array_buffer[meta.size]).toBe(3);
expect(array_buffer[meta.inspectable]).toBe(false);
expect(array_buffer[meta.name]).toBe('ArrayBuffer');
expect(array_buffer[meta.type]).toBe('array_buffer');
expect(typed_array[meta.size]).toBe(3);
expect(typed_array[meta.inspectable]).toBe(false);
expect(typed_array[meta.name]).toBe('Uint8Array');
expect(typed_array[meta.type]).toBe('typed_array');
expect(date[meta.inspectable]).toBe(false);
expect(date[meta.type]).toBe('date');
expect(fn[meta.inspectable]).toBe(false);
expect(fn[meta.name]).toBe('exampleFunction');
expect(fn[meta.type]).toBe('function');
expect(html_element[meta.inspectable]).toBe(false);
expect(html_element[meta.name]).toBe('DIV');
expect(html_element[meta.type]).toBe('html_element');
expect(immutable[meta.inspectable]).toBeUndefined(); // Complex type
expect(immutable[meta.name]).toBe('Map');
expect(immutable[meta.type]).toBe('iterator');
expect(map[meta.inspectable]).toBeUndefined(); // Complex type
expect(map[meta.name]).toBe('Map');
expect(map[meta.type]).toBe('iterator');
expect(map[0][meta.type]).toBe('array');
expect(map_of_maps[meta.inspectable]).toBeUndefined(); // Complex type
expect(map_of_maps[meta.name]).toBe('Map');
expect(map_of_maps[meta.type]).toBe('iterator');
expect(map_of_maps[0][meta.type]).toBe('array');
expect(react_element[meta.inspectable]).toBe(false);
expect(react_element[meta.name]).toBe('span');
expect(react_element[meta.type]).toBe('react_element');
expect(set[meta.inspectable]).toBeUndefined(); // Complex type
expect(set[meta.name]).toBe('Set');
expect(set[meta.type]).toBe('iterator');
expect(set[0]).toBe('abc');
expect(set[1]).toBe(123);
expect(set_of_sets[meta.inspectable]).toBeUndefined(); // Complex type
expect(set_of_sets[meta.name]).toBe('Set');
expect(set_of_sets[meta.type]).toBe('iterator');
expect(set_of_sets['0'][meta.inspectable]).toBe(true);
expect(symbol[meta.inspectable]).toBe(false);
expect(symbol[meta.name]).toBe('Symbol(symbol)');
expect(symbol[meta.type]).toBe('symbol');
expect(typed_array[meta.inspectable]).toBeUndefined(); // Complex type
expect(typed_array[meta.size]).toBe(3);
expect(typed_array[meta.name]).toBe('Int8Array');
expect(typed_array[meta.type]).toBe('typed_array');
expect(typed_array[0]).toBe(100);
expect(typed_array[1]).toBe(-100);
expect(typed_array[2]).toBe(0);
done();
});

View File

@@ -1,4 +1,11 @@
// @flow
/**
* 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.
*
* @flow
*/
describe('Store (legacy)', () => {
let React;

View File

@@ -1,4 +1,11 @@
// @flow
/**
* 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.
*
* @flow
*/
import typeof ReactTestRenderer from 'react-test-renderer';
import type {Element} from 'react-devtools-shared/src/devtools/views/Components/types';

View File

@@ -1,4 +1,11 @@
// @flow
/**
* 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.
*
* @flow
*/
import typeof ReactTestRenderer from 'react-test-renderer';
import type {FrontendBridge} from 'react-devtools-shared/src/bridge';
@@ -136,7 +143,7 @@ describe('ProfilerContext', () => {
expect(context.didRecordCommits).toBe(false);
expect(context.isProcessingData).toBe(false);
expect(context.isProfiling).toBe(false);
expect(context.profilingData).not.toBe(null);
expect(context.profilingData).toBe(null);
done();
});

View File

@@ -1,4 +1,11 @@
// @flow
/**
* 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.
*
* @flow
*/
import type Store from 'react-devtools-shared/src/devtools/store';

View File

@@ -1,4 +1,11 @@
// @flow
/**
* 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.
*
* @flow
*/
import typeof ReactTestRenderer from 'react-test-renderer';
import type {FrontendBridge} from 'react-devtools-shared/src/bridge';

View File

@@ -1,4 +1,11 @@
// @flow
/**
* 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.
*
* @flow
*/
import typeof TestRendererType from 'react-test-renderer';
import type Store from 'react-devtools-shared/src/devtools/store';

View File

@@ -1,4 +1,11 @@
// @flow
/**
* 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.
*
* @flow
*/
import typeof TestRendererType from 'react-test-renderer';
import type Store from 'react-devtools-shared/src/devtools/store';

View File

@@ -1,4 +1,11 @@
// @flow
/**
* 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.
*
* @flow
*/
describe('profiling utils', () => {
let utils;

View File

@@ -1,15 +1,9 @@
// @flow
import storage from 'local-storage-fallback';
// In case async/await syntax is used in a test.
import 'regenerator-runtime/runtime';
'use strict';
// DevTools stores preferences between sessions in localStorage
if (!global.hasOwnProperty('localStorage')) {
global.localStorage = storage;
global.localStorage = require('local-storage-fallback').default;
}
// Mimic the global we set with Webpack's DefinePlugin
global.__DEV__ = process.env.NODE_ENV !== 'production';
global.__TEST__ = process.env.NODE_ENV === 'test';

View File

@@ -1,4 +1,11 @@
// @flow
/**
* 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.
*
* @flow
*/
import type {
BackendBridge,
@@ -27,9 +34,17 @@ env.beforeEach(() => {
const originalConsoleError = console.error;
// $FlowFixMe
console.error = (...args) => {
if (args[0] === 'Warning: React DevTools encountered an error: %s') {
const firstArg = args[0];
if (firstArg === 'Warning: React DevTools encountered an error: %s') {
// Rethrow errors from React.
throw args[1];
} else if (
typeof firstArg === 'string' &&
firstArg.startsWith("Warning: It looks like you're using the wrong act()")
) {
// DevTools intentionally wraps updates with acts from both DOM and test-renderer,
// since test updates are expected to impact both renderers.
return;
}
originalConsoleError.apply(console, args);
};

View File

@@ -1,4 +1,11 @@
// @flow
/**
* 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.
*
* @flow
*/
describe('Store', () => {
let React;

View File

@@ -1,4 +1,11 @@
// @flow
/**
* 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.
*
* @flow
*/
import type {FrontendBridge} from 'react-devtools-shared/src/bridge';
import type Store from 'react-devtools-shared/src/devtools/store';

View File

@@ -1,4 +1,11 @@
// @flow
/**
* 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.
*
* @flow
*/
const {printOwnersList} = require('../devtools/utils');

View File

@@ -1,4 +1,11 @@
// @flow
/**
* 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.
*
* @flow
*/
describe('StoreStress (Sync Mode)', () => {
let React;

View File

@@ -1,4 +1,11 @@
// @flow
/**
* 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.
*
* @flow
*/
describe('StoreStressConcurrent', () => {
let React;

View File

@@ -1,4 +1,11 @@
// @flow
/**
* 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.
*
* @flow
*/
import typeof ReactTestRenderer from 'react-test-renderer';
import type {FrontendBridge} from 'react-devtools-shared/src/bridge';

View File

@@ -0,0 +1,173 @@
/**
* 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.
*
* @flow
*/
describe('useEditableValue', () => {
let act;
let React;
let ReactDOM;
let useEditableValue;
beforeEach(() => {
const utils = require('./utils');
act = utils.act;
React = require('react');
ReactDOM = require('react-dom');
useEditableValue = require('../devtools/views/hooks').useEditableValue;
});
it('should override editable state when external props are updated', () => {
let state;
function Example({value}) {
const tuple = useEditableValue(value);
state = tuple[0];
return null;
}
const container = document.createElement('div');
ReactDOM.render(<Example value={1} />, container);
expect(state.editableValue).toEqual('1');
expect(state.externalValue).toEqual(1);
expect(state.parsedValue).toEqual(1);
expect(state.hasPendingChanges).toBe(false);
expect(state.isValid).toBe(true);
// If there are NO pending changes,
// an update to the external prop value should override the local/pending value.
ReactDOM.render(<Example value={2} />, container);
expect(state.editableValue).toEqual('2');
expect(state.externalValue).toEqual(2);
expect(state.parsedValue).toEqual(2);
expect(state.hasPendingChanges).toBe(false);
expect(state.isValid).toBe(true);
});
it('should not override editable state when external props are updated if there are pending changes', () => {
let dispatch, state;
function Example({value}) {
const tuple = useEditableValue(value);
state = tuple[0];
dispatch = tuple[1];
return null;
}
const container = document.createElement('div');
ReactDOM.render(<Example value={1} />, container);
expect(state.editableValue).toEqual('1');
expect(state.externalValue).toEqual(1);
expect(state.parsedValue).toEqual(1);
expect(state.hasPendingChanges).toBe(false);
expect(state.isValid).toBe(true);
// Update (local) editable state.
act(() =>
dispatch({
type: 'UPDATE',
editableValue: '2',
externalValue: 1,
}),
);
expect(state.editableValue).toEqual('2');
expect(state.externalValue).toEqual(1);
expect(state.parsedValue).toEqual(2);
expect(state.hasPendingChanges).toBe(true);
expect(state.isValid).toBe(true);
// If there ARE pending changes,
// an update to the external prop value should NOT override the local/pending value.
ReactDOM.render(<Example value={3} />, container);
expect(state.editableValue).toEqual('2');
expect(state.externalValue).toEqual(3);
expect(state.parsedValue).toEqual(2);
expect(state.hasPendingChanges).toBe(true);
expect(state.isValid).toBe(true);
});
it('should parse edits to ensure valid JSON', () => {
let dispatch, state;
function Example({value}) {
const tuple = useEditableValue(value);
state = tuple[0];
dispatch = tuple[1];
return null;
}
const container = document.createElement('div');
ReactDOM.render(<Example value={1} />, container);
expect(state.editableValue).toEqual('1');
expect(state.externalValue).toEqual(1);
expect(state.parsedValue).toEqual(1);
expect(state.hasPendingChanges).toBe(false);
expect(state.isValid).toBe(true);
// Update (local) editable state.
act(() =>
dispatch({
type: 'UPDATE',
editableValue: '"a',
externalValue: 1,
}),
);
expect(state.editableValue).toEqual('"a');
expect(state.externalValue).toEqual(1);
expect(state.parsedValue).toEqual(1);
expect(state.hasPendingChanges).toBe(true);
expect(state.isValid).toBe(false);
});
it('should reset to external value upon request', () => {
let dispatch, state;
function Example({value}) {
const tuple = useEditableValue(value);
state = tuple[0];
dispatch = tuple[1];
return null;
}
const container = document.createElement('div');
ReactDOM.render(<Example value={1} />, container);
expect(state.editableValue).toEqual('1');
expect(state.externalValue).toEqual(1);
expect(state.parsedValue).toEqual(1);
expect(state.hasPendingChanges).toBe(false);
expect(state.isValid).toBe(true);
// Update (local) editable state.
act(() =>
dispatch({
type: 'UPDATE',
editableValue: '2',
externalValue: 1,
}),
);
expect(state.editableValue).toEqual('2');
expect(state.externalValue).toEqual(1);
expect(state.parsedValue).toEqual(2);
expect(state.hasPendingChanges).toBe(true);
expect(state.isValid).toBe(true);
// Reset editable state
act(() =>
dispatch({
type: 'RESET',
externalValue: 1,
}),
);
expect(state.editableValue).toEqual('1');
expect(state.externalValue).toEqual(1);
expect(state.parsedValue).toEqual(1);
expect(state.hasPendingChanges).toBe(false);
expect(state.isValid).toBe(true);
});
});

View File

@@ -0,0 +1,40 @@
/**
* 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.
*
* @flow
*/
import {getDisplayName} from 'react-devtools-shared/src/utils';
describe('utils', () => {
describe('getDisplayName', () => {
it('should return a function name', () => {
function FauxComponent() {}
expect(getDisplayName(FauxComponent)).toEqual('FauxComponent');
});
it('should return a displayName name if specified', () => {
function FauxComponent() {}
FauxComponent.displayName = 'OverrideDisplayName';
expect(getDisplayName(FauxComponent)).toEqual('OverrideDisplayName');
});
it('should return the fallback for anonymous functions', () => {
expect(getDisplayName(() => {}, 'Fallback')).toEqual('Fallback');
});
it('should return Anonymous for anonymous functions without a fallback', () => {
expect(getDisplayName(() => {})).toEqual('Anonymous');
});
// Simulate a reported bug:
// https://github.com/facebook/react/issues/16685
it('should return a fallback when the name prop is not a string', () => {
const FauxComponent = {name: {}};
expect(getDisplayName(FauxComponent, 'Fallback')).toEqual('Fallback');
});
});
});

View File

@@ -1,4 +1,11 @@
// @flow
/**
* 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.
*
* @flow
*/
import typeof ReactTestRenderer from 'react-test-renderer';
@@ -18,11 +25,13 @@ export function act(callback: Function): void {
});
// Flush Bridge operations
actDOM(() => {
actTestRenderer(() => {
jest.runAllTimers();
while (global.mockGetTimersCount() > 0) {
actDOM(() => {
actTestRenderer(() => {
jest.runAllTimers();
});
});
});
}
}
export async function actAsync(
@@ -40,7 +49,8 @@ export async function actAsync(
});
if (recursivelyFlush) {
while (jest.getTimerCount() > 0) {
// TODO (Jest v24) Replace with jest.getTimerCount() after we upgrade Jest
while (global.mockGetTimersCount() > 0) {
// $FlowFixMe Flow doens't know about "await act()" yet
await actDOM(async () => {
await actTestRenderer(async () => {

View File

@@ -1,4 +1,11 @@
// @flow
/**
* 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.
*
* @flow
*/
import type {BoxStyle} from './types';

View File

@@ -1,4 +1,11 @@
// @flow
/**
* 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.
*
* @flow
*/
import Agent from 'react-devtools-shared/src/backend/agent';
import resolveBoxStyle from './resolveBoxStyle';

View File

@@ -1,4 +1,11 @@
// @flow
/**
* 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.
*
* @flow
*/
export type BoxStyle = $ReadOnly<{|
bottom: number,

Some files were not shown because too many files have changed in this diff Show More