Compare commits

...

122 Commits

Author SHA1 Message Date
Jason Quense
f0664d552c Merge branch 'master' into radio-click 2018-08-02 17:31:34 -04:00
Dan Abramov
1609cf3432 Warn about rendering Generators (#13312)
* Warn about rendering Generators

* Fix Flow

* Add an explicit test for iterable

* Moar test coverage
2018-08-02 20:04:03 +01:00
Dan Abramov
46d5afc54d Replace console.error() with a throw in setTimeout() as last resort exception logging (#13310)
* Add a regression test for #13188

* Replace console.error() with a throw in setTimeout() as last resort

* Fix lint and comment

* Fix tests to check we throw after all

* Fix build tests
2018-08-02 18:16:47 +01:00
Yunchan Cho
b3b80a4835 Inject react-art renderer into react-devtools (#13173)
* Inject react-art renderer into react-devtools

This commit makes react-art renderer to be injected to react-devtools,
so that component tree of the renderer is presented on debug panel of browser.

* Update ReactART.js
2018-08-02 12:04:31 +01:00
Dan Abramov
5e8beec84b Add a regression test for #11602 2018-08-02 02:46:08 +01:00
Dan Abramov
470377bbdb Remove extraneous condition
It's covered by a check below.
2018-08-02 02:33:54 +01:00
Dan Abramov
ad17ca639b Fix Prettier 2018-08-02 02:29:15 +01:00
Ideveloper
6db080154b Remove irrelevant suggestion of a legacy method from a warning (#13169)
* Edit warn message what use deprecated lifecycle method

* delete setState warn message about prescriptive and deprecated life cycle

* fix lint

* Prettier

* Formatting
2018-08-02 02:11:17 +01:00
Dan Abramov
fddb23601f Tweak other fixture instructions 2018-08-02 01:36:44 +01:00
Dan Abramov
95738e5cfd Tweak fixture instructions 2018-08-02 01:34:08 +01:00
Jiawen Geng
d79238f1ee add nodejs 10 to windows test (#13241)
* add nodejs 10 to windows test

* remove node 8 for better build speed
2018-08-02 01:09:37 +01:00
Dan Abramov
ae63110335 Fix time slicing fixture (#13305)
* Fix time slicing fixture

* Remove unused option
2018-08-02 00:02:24 +01:00
Dan Abramov
e341e503b2 Move async fixtures (#13304) 2018-08-01 22:21:26 +01:00
Brian Vaughn
00cd4444e2 [WIP] Add suspense fixtures for IO and CPU demo (#13295)
Add suspense fixtures for IO and CPU demo
2018-08-01 14:10:28 -07:00
Dan Abramov
41f6d8cc7a Fix incorrect changelog entry for 16.3.3 2018-08-01 21:12:51 +01:00
Dan Abramov
0624c719f4 Add 16.4.2 and other releases to changelog 2018-08-01 20:29:05 +01:00
Dan Abramov
f60a7f722c Fix SSR crash on a hasOwnProperty attribute (#13303) 2018-08-01 20:23:19 +01:00
Dan Abramov
ff41519ec2 Sanitize unknown attribute names for SSR (#13302) 2018-08-01 20:23:10 +01:00
Dylan Cutler
c44c2a2161 More helpful message when passing an element to createElement() (#13131)
* [#13130] Add a more helpful message when passing an element to createElement()

* better conditional flow

* update after review

* move last condition inside last else clause

* Added test case

* compare 25132typeof to REACT_ELEMENT_TYPE

* runs prettier

* remove unrelated changes

* Tweak the message
2018-08-01 18:45:08 +01:00
Dan Abramov
28cd494bdf Refactor validateDOMNesting a bit (#13300) 2018-08-01 17:08:03 +01:00
Philipp Spieß
b381f41411 Allow Electrons <webview> tag (#13301)
Fixes #13299

Adds Electrons <webview> tag to the attribute whitelist.
2018-08-01 17:07:52 +01:00
Konstantin Yakushin
0182a74632 Fix a crash when using dynamic children in <option> tag (#13261)
* Make option children a text content by default

fix #11911

* Apply requested changes

- Remove meaningless comments
- revert scripts/rollup/results.json

* remove empty row

* Update comment

* Add a simple unit-test

* [WIP: no flow] Pass through hostContext

* [WIP: no flow] Give better description for test

* Fixes

* Don't pass hostContext through

It ended up being more complicated than I thought.

* Also warn on hydration
2018-08-01 16:16:34 +01:00
Andrew Clark
2a2ef7e0fd Remove unnecessary branching from updateContextProvider (#13282)
This code had gotten unnecessarily complex after some recent changes.
Cleaned it up a bit.
2018-07-27 13:42:17 -07:00
Dan Abramov
840cb1a268 Add an invariant to createRoot() to validate containers (#13279) 2018-07-27 16:50:20 +02:00
Andrew Clark
bc1ea9cd96 Handle errors thrown in gDSFP of a module-style context provider (#13269)
Context should be pushed before calling any user code, so if it errors
the stack unwinds correctly.
2018-07-25 14:22:27 -07:00
Flarnie Marchan
0154a79fed Remove 'warning' module from the JS scheduler (#13264)
* Remove 'warning' module from the JS scheduler

**what is the change?:**
See title

**why make this change?:**
Internally the 'warning' module has some dependencies which we want to
avoid pulling in during the very early stages of initial pageload. It is
creating a cyclical dependency.

And we wanted to remove this dependency anyway, because this module
should be kept small and decoupled.

**test plan:**
- Tested the exact same change internally in Facebook.com
- Ran unit tests
- Tried out the fixture

**issue:**
Internal task T31831021

* check for console existence before calling console.error

* Move DEV check into separate block
2018-07-25 08:58:18 -07:00
Brian Vaughn
dbd16c8a96 Add @flow directive to findDOMNode shim (#13265)
* Add @flow directive to findDOMNode shim
2018-07-24 14:53:54 -07:00
Andrew Clark
ca0941fce3 Add regression test for Placeholder fallbacks with lifecycle methods (#13254)
Found by @rhagigi

Co-authored-by: Royi Hagigi <rhagigi@gmail.com>
Co-authored-by: Andrew Clark <acdlite@me.com>
2018-07-23 17:47:40 -07:00
Sebastian Markbåge
a32c727f2e Optimize readContext for Subsequent Reads of All Bits (#13248)
This is likely the common case because individual component authors
will casually call read on common contexts like the cache, or cache
provider.

Where as libraries like Relay only call read once per fragment and pass
all observed bits at once.
2018-07-21 20:13:46 -07:00
Andrew Clark
2b509e2c8c [Experimental] API for reading context from within any render phase function (#13139)
* Store list of contexts on the fiber

Currently, context can only be read by a special type of component,
ContextConsumer. We want to add support to all fibers, including
classes and functional components.

Each fiber may read from one or more contexts. To enable quick, mono-
morphic access of this list, we'll store them on a fiber property.

* Context.unstable_read

unstable_read can be called anywhere within the render phase. That
includes the render method, getDerivedStateFromProps, constructors,
functional components, and context consumer render props.

If it's called outside the render phase, an error is thrown.

* Remove vestigial context cursor

Wasn't being used.

* Split fiber.expirationTime into two separate fields

Currently, the `expirationTime` field represents the pending work of
both the fiber itself — including new props, state, and context — and of
any updates in that fiber's subtree.

This commit adds a second field called `childExpirationTime`. Now
`expirationTime` only represents the pending work of the fiber itself.
The subtree's pending work is represented by `childExpirationTime`.

The biggest advantage is it requires fewer checks to bailout on already
finished work. For most types of work, if the `expirationTime` does not
match the render expiration time, we can bailout immediately without
any further checks. This won't work for fibers that have
`shouldComponentUpdate` semantics (class components), for which we still
need to check for props and state changes explicitly.

* Performance nits

Optimize `readContext` for most common case
2018-07-20 16:49:06 -07:00
Dan Abramov
5776fa3fcf Update www warning shim (#13244) 2018-07-20 16:50:43 +01:00
Dan Abramov
3d3506d37d Include Modes in the component stack (#13240)
* Add a test that StrictMode shows up in the component stack

The SSR test passes. The client one doesn't.

* Include Modes in component stack

* Update other tests to include modes
2018-07-19 22:11:59 +01:00
Andrew Clark
71b4e99901 [react-test-renderer] Jest matchers for async tests (#13236)
Adds custom Jest matchers that help with writing async tests:

- `toFlushThrough`
- `toFlushAll`
- `toFlushAndThrow`
- `toClearYields`

Each one accepts an array of expected yielded values, to prevent
false negatives.

Eventually I imagine we'll want to publish this on npm.
2018-07-19 10:26:24 -07:00
Dan Abramov
8121212f0d Fix warning extraction script 2018-07-19 13:04:56 +01:00
Dan Abramov
2a1bc3f74c Format messages in unexpected console.error() test failure 2018-07-19 12:15:01 +01:00
Dan Abramov
2c560cb995 Fix unwinding starting with a wrong Fiber on error in the complete phase (#13237)
* Add a repro case for profiler unwinding

This currently fails the tests due to an unexpected warning.

* Add a regression test for context stack

* Simplify the first test case

* Update nextUnitOfWork inside completeUnitOfWork()

The bug was caused by a structure like this:

    </Provider>
  </div>
</errorInCompletePhase>

We forgot to update nextUnitOfWork so it was still pointing at Provider when errorInCompletePhase threw. As a result, we would try to unwind from Provider (rather than from errorInCompletePhase), and thus pop the Provider twice.
2018-07-19 02:16:10 +01:00
Dan Abramov
ead08827d0 Add more flexibility in testing errors in begin/complete phases (#13235)
* Add more flexibility in testing errors in begin/complete phases

* Update too
2018-07-19 00:23:10 +01:00
Andrew Clark
e4e58343e4 Move unstable_yield to main export (#13232)
The `yield` method isn't tied to any specific root. Putting this
on the main export enables test components that are not within scope
to yield even if they don't have access to the currently rendering
root instance. This follows the pattern established by ReactNoop.

Added a `clearYields` method, too, for reading values that were yielded
out of band. This is also based on ReactNoop.
2018-07-18 16:10:56 -07:00
Mateusz Burzyński
0e235bb8f7 Removed unused state argument in unsubscribe method of <Subscription /> (#13233) 2018-07-18 13:52:59 -07:00
Dan Abramov
236f608723 Fail tests if toWarnDev() does not wrap warnings in array (#13227)
* Fail tests if toWarn() does not wrap warnings in array

* Fix newly failing tests

* Another fix
2018-07-18 02:38:39 +01:00
Dan Abramov
acbb4f93f0 Remove the use of proxies for synthetic events in DEV (#13225)
* Revert #5947 and disable the test

* Fix isDefaultPrevented and isPropagationStopped to not get nulled

This was a bug introduced by #5947. It's very confusing that they become nulled while stopPropagation/preventDefault don't.

* Add a comment

* Run Prettier

* Fix grammar
2018-07-18 00:14:13 +01:00
Nicole Levy
171e0b7d44 Fix “no onChange handler” warning to fire on falsy values ("", 0, false) too (#12628)
* throw warning for falsey `value` prop

* add nop onChange handler to tests for `value` prop

* prettier

* check for falsey checked

* fix tests for `checked` prop

* new tests for `value` prop

* test formatting

* forgot 0 (:

* test for falsey `checked` prop

* add null check

* Update ReactDOMInput-test.js

* revert unneeded change

* prettier

* Update DOMPropertyOperations-test.js

* Update ReactDOMInput-test.js

* Update ReactDOMSelect-test.js

* Fixes and tests

* Remove unnecessary changes
2018-07-17 22:46:43 +01:00
Fumiya Shibusawa
606c30aa5f fixed a typo in commentout in ReactFiberUnwindWork.js (#13172) 2018-07-17 20:21:53 +01:00
Johan Henriksson
9f78913b20 Update prettier (#13205)
* Update Prettier to 1.13.7

* Apply Prettier changes

* Pin prettier version

* EOL
2018-07-17 20:18:34 +01:00
jddxf
6d3e262880 Remove unnecessary typeof checks (#13196)
This aligns with #10351 which removed extra check on `injectInternals`.
2018-07-17 20:18:15 +01:00
Dan Abramov
82c7ca4cca Add component stacks to some warnings (#13218) 2018-07-17 20:15:03 +01:00
Thibault Malbranche
21ac62c77a Fix a portal unmounting crash for renderers with distinct Instance and Container (#13220)
* Fix Portal unmount

Before that change, currentParent is not set as a container even if it should so it break on react-native and probably other custom renderers

* Assert that *ToContainer() methods receive containers

* Add regression tests

* Add comments
2018-07-17 01:35:33 +01:00
Dan Abramov
d6a0626b38 Set current fiber during before-mutation traversal (#13219) 2018-07-17 01:11:56 +01:00
Dan Abramov
fd410f43fc Protect against passing component stack twice
This is a leftover from #13161 that I forgot to include.
It ensures we don't accidentally write code in the old way and end up passing the stack twice.
2018-07-16 22:47:41 +01:00
Dan Abramov
f9358c51c8 Change warning() to automatically inject the stack, and add warningWithoutStack() as opt-out (#13161)
* Use %s in the console calls

* Add shared/warningWithStack

* Convert some warning callsites to warningWithStack

* Use warningInStack in shared utilities and remove unnecessary checks

* Replace more warning() calls with warningWithStack()

* Fixes after rebase + use warningWithStack in react

* Make warning have stack by default; warningWithoutStack opts out

* Forbid builds that may not use internals

* Revert newly added stacks

I changed my mind and want to keep this PR without functional changes. So we won't "fix" any warnings that are already missing stacks. We'll do it in follow-ups instead.

* Fix silly find/replace mistake

* Reorder imports

* Add protection against warning argument count mismatches

* Address review
2018-07-16 22:31:59 +01:00
Dan Abramov
854c953905 Fix matcher tests to be DEV-only 2018-07-16 20:35:43 +01:00
Dan Abramov
467d139101 Enforce presence or absence of component stack in tests (#13215)
* Enforce presence or absence of stack in tests

* Rename expectNoStack to withoutStack

* Fix lint

* Add some tests for toWarnDev()
2018-07-16 20:20:18 +01:00
Andrew Clark
43ffae2d17 Suspending inside a constructor outside of strict mode (#13200)
* Suspending inside a constructor outside of strict mode

Outside of strict mode, suspended components commit in an incomplete
state, then are synchronously deleted in a subsequent commit. If a
component suspends inside the constructor, it mounts without
an instance.

This breaks at least one invariant: during deletion, we assume that
every mounted component has an instance, and check the instance for
the existence of `componentWillUnmount`.

Rather than add a redundant check to the deletion of every class
component, components that suspend inside their constructor and outside
of strict mode are turned into empty functional components before they
are mounted. This is a bit weird, but it's an edge case, and the empty
component will be synchronously unmounted regardless.

* Do not fire lifecycles of a suspended component

In non-strict mode, suspended components commit, but their lifecycles
should not fire.
2018-07-13 11:24:03 -07:00
Dan Abramov
659a29cecf Reorganize how shared internals are accessed (#13201)
* Reorganize how shared internals are accessed

* Update forks.js
2018-07-13 02:45:37 +01:00
Brian Vaughn
58f3b29d91 Added SSR/hydration tests for modes, forwardRef, and Profiler (#13195)
* Added more SSR tests for modes, profiler, and forward-ref
2018-07-12 08:35:21 -07:00
Dan Abramov
1c89cb62fd Use ReactDebugCurrentFrame.getStackAddendum() in element validator (#13198)
Instead of wrapping ReactDebugCurrentFrame.getStackAddendum() call into a custom wrapper inside ReactElementValidator, "teach" the main ReactDebugCurrentFrame.getStackAddendum() to take currently validating element into account.
2018-07-12 16:18:24 +01:00
Dan Abramov
e6076ecf48 Remove ad-hoc forks of getComponentName() and fix it (#13197)
* Fix getComponentName() for types with nested $$typeof

* Temporarily remove Profiler ID from messages

* Change getComponentName() signature to take just type

It doesn't actually need the whole Fiber.

* Remove getComponentName() forks in isomorphic and SSR

* Remove unnecessary .type access where we already have a type

* Remove unused type
2018-07-12 07:32:06 -07:00
Philipp Spieß
32f6f258ba Remove event simulation of onChange events (#13176)
* Remove event simulation of onChange events

It’s time to get rid of even more `ReactTestUtils.Simulate`s. In this PR
we remove the event simulation from all onChange tests. To do this, we
have to get a setter to the untracked value/checked props.

All remaining `ReactTestUtils.Simulate` calls are either testing
ReactTestUtils or assert that they do/don't throw.

* Use input instead of change event for all but checkbox, radio, and select
2018-07-12 12:11:35 +01:00
Sen Yang
9ca37f8431 docs: update comments (#13043) 2018-07-11 14:31:48 -07:00
Moti Zilberman
f89f25f471 Correct type of ref in forwardRef render() (#13100)
`React$ElementRef<T>` is the type of the ref _instance_ for a component of type T, whereas `React$Ref<T>` is the type of the ref _prop_ for a component of type T, which seems to be the intended type here.
2018-07-11 14:27:46 -07:00
Brian Vaughn
7b99ceabec Deprecate test utils mock component follow up (#13194)
* De-duplicate the mockComponent deprecation warning

* Added fb.me link to mockComponent
2018-07-11 11:56:44 -07:00
Dan Abramov
6ebc8f3c07 Add support for re-entrant SSR stacks (#13181)
* Add failing tests

* Fix re-entrancy in ReactDOMServer
2018-07-11 19:43:54 +01:00
Brian Vaughn
d64d1ddb57 Deprecate ReactTestUtils.mockComponent() (#13193)
Deprecate ReactTestUtils.mockComponent()
2018-07-11 10:18:49 -07:00
dongyuwei
afd46490d0 update devEngines to include nodejs 10.x (#13190) 2018-07-11 11:46:44 +01:00
Brian Vaughn
e79366d549 Link create-subscription doc to GH issue with de-opt explanation (#13187) 2018-07-10 14:00:06 -07:00
Brian Vaughn
1f32d3c6dc Test renderer flushAll method verifies an array of expected yields (#13174) 2018-07-09 09:05:13 -07:00
Dan Abramov
377e1a049e Add a test for SSR stack traces (#13180) 2018-07-09 14:41:48 +01:00
Dan Abramov
96d38d178a Fix concatenation of null to a warning message (#13166) 2018-07-09 13:56:24 +01:00
Brian Vaughn
095dd5049c Add DEV warning if forwardRef function doesn't use the ref param (#13168)
* Add DEV warning if forwardRef function doesn't use the ref param
* Fixed a forwardRef arity warning in another test
2018-07-07 08:11:30 -07:00
Dan Abramov
5662595677 Refactor stack handling (no functional changes) (#13165)
* Refactor ReactDebugCurrentFiber to use named exports

This makes the difference between it and ReactFiberCurrentFrame a bit clearer.

ReactDebugCurrentFiber is Fiber's own implementation.
ReactFiberCurrentFrame is the thing that holds a reference to the current implementation and delegates to it.

* Unify ReactFiberComponentTreeHook and ReactDebugCurrentFiber

Conceptually they're very related.

ReactFiberComponentTreeHook contains implementation details of reading Fiber's stack (both in DEV and PROD).
ReactDebugCurrentFiber contained a reference to the current fiber, and used the above utility.

It was confusing when to use which one. Colocating them makes it clearer what you could do with each method.

In the future, the plan is to stop using these methods explicitly in most places, and instead delegate to a warning system that includes stacks automatically. This change makes future refactorings simpler by colocating related logic.

* Rename methods to better reflect their meanings

Clarify which are DEV or PROD-only.
Clarify which can return null.

I believe the "work in progress only" was a mistake. I introduced it because I wasn't sure what guarantees we have around .return. But we know for sure that following a .return chain gives us an accurate stack even if we get into WIP trees because we don't have reparenting. So it's fine to relax that naming.

* Rename ReactDebugCurrentFiber -> ReactCurrentFiber

It's not completely DEV-only anymore.
Individual methods already specify whether they work in DEV or PROD in their names.
2018-07-07 01:09:41 +01:00
Brandon Dail
ebbd221432 Configure react-test-renderer as a secondary (#13164) 2018-07-06 16:04:45 -07:00
Andrew Clark
ddc91af795 Decrease nested update limit from 1000 to 50 (#13163)
An infinite update loop can occur when an update is scheduled inside a
lifecycle method, which causes a re-render, which schedules another
update, and so on. Before the Fiber rewrite, this scenario resulted in a
stack overflow.

Because Fiber does not use the JavaScript stack, we maintain our own
counter to track the number of nested, synchronous updates. We throw an
error if the limit is exceeded.

The nested update limit is currently 1000. I chose this number
arbitrarily, certain that there was no valid reason for a component to
schedule so many synchronous re-renders.

I think we can go much lower. This commit decreases the limit to 50. I
believe this is still comfortably above the reasonable number of
synchronous re-renders a component may perform.

This will make it easier for developers to debug infinite update bugs
when they occur.
2018-07-06 15:50:31 -07:00
Andrew Clark
3596e40b39 Fix nested update bug (#13160)
A recent change to the scheduler caused a regression when scheduling
many updates within a single batch. Added a test case that would
have caught this.
2018-07-06 13:55:18 -07:00
Chang Yan
449f6ddd5c create a new FeatureFlags file for test renderer on www (#13159) 2018-07-06 12:55:29 -07:00
Dan Abramov
f762b3abb1 Run react-dom SSR import test in jsdom-less environment (#13157) 2018-07-06 16:43:43 +01:00
Brian Vaughn
6f6b560a64 Renamed selfBaseTime/treeBaseTime Fiber attributes to selfBaseDuration/treeBaseDuration (#13156)
This is an unobservable change to all but the (under development) DevTools Profiler plugin. It is being done so that the plugin can safely feature detect a version of React that supports it. The profiler API has existed since the 16.4.0 release, but it did not support the DevTools plugin prior to PR #13058.

Side note: I am not a big fan of the term "base duration". Both it and "actual duration" are kind of awkward and vague. If anyone has suggestions for better names– this is the best time to bikeshed about them.
2018-07-06 08:25:29 -07:00
Dan Abramov
1386ccddd8 Fix ReferenceError when requestAnimationFrame isn't defined (#13152)
* Make the test fail

* Fix rAF detection to avoid a ReferenceError
2018-07-05 19:42:45 +01:00
Dan Abramov
f5779bbc10 Run server rendering test on bundles (#13153) 2018-07-05 19:42:22 +01:00
Brian Vaughn
9faf389e79 Reset profiler timer correctly after errors (#13123)
* Reset ReactProfilerTimer's DEV-only Fiber stack after an error

* Added ReactNoop functionality to error during "complete" phase

* Added failing profiler stack unwinding test

* Potential fix for unwinding time bug

* Renamed test

* Don't record time until complete phase succeeds. Simplifies unwinding.

* Expanded ReactProfilerDevToolsIntegration-test coverage a bit

* Added unstable_flushWithoutCommitting method to noop renderer

* Added failing multi-root/batch test to ReactProfiler-test

* Beefed up tests a bit and added some TODOs

* Profiler timer differentiates between batched commits and in-progress async work

This was a two-part change:
1) Don't count time spent working on a batched commit against yielded async work.
2) Don't assert an empty stack after processing a batched commit (because there may be yielded async work)

This is kind of a hacky solution, and may have problems that I haven't thought of yet. I need to commit this so I can mentally clock out for a bit without worrying about it. I will think about it more when I'm back from PTO. In the meanwhile, input is welcome.

* Removed TODO

* Replaced FiberRoot map with boolean

* Removed unnecessary whitespace edit
2018-07-05 11:38:06 -07:00
XuMM_12
85fe4ddce7 Fix - issue #12765 / the checked attribute is not initially set on the input (#13114) 2018-07-04 16:00:42 -04:00
Rouven Weßling
07fefe3331 Drop handling for ms and O prefixes for CSS transition and animation events. (#13133)
Internet Explorer never needed the prefix and Opera 11.5 is no longer supported by React.
2018-07-04 18:20:02 +01:00
Andrew Clark
88d7ed8bfb React.Timeout -> React.Placeholder (#13105)
Changed the API to match what we've been using in our latest discussions.

Our tentative plans are for <Placeholder> to automatically hide the timed-out
children, instead of removing them, so their state is not lost. This part is
not yet implemented. We'll likely have a lower level API that does not include
the hiding behavior. This is also not yet implemented.
2018-07-03 19:47:00 -07:00
Andrew Clark
f128fdea48 Suspending outside of strict trees and async trees (#13098)
We can support components that suspend outside of an async mode tree
by immediately committing their placeholders.

In strict mode, the Timeout acts effectively like an error boundary.
Within a single render pass, we unwind to the nearest Timeout and
re-render the placeholder view.

Outside of strict mode, it's not safe to unwind and re-render the
siblings without committing. (Technically, this is true of error
boundaries, too, though probably not a huge deal, since we don't support
using error boundaries for control flow (yet, at least)). We need to be
clever. What we do is pretend the suspended component rendered null.*
There's no unwinding. The siblings commit like normal.

Then, in the commit phase, schedule an update on the Timeout to
synchronously re-render the placeholder. Although this requires an extra
commit, it will not be observable. And because the siblings were not
blocked from committing, they don't have to be strict mode compatible.

Another caveat is that if a component suspends during an async render,
but it's captured by a non-async Timeout, we need to revert to sync
mode. In other words, if any non-async component renders, the entire
tree must complete and commit without yielding.

* The downside of rendering null is that the existing children will be
deleted. We should hide them instead. I'll work on this in a follow-up.
2018-07-03 19:44:19 -07:00
Andrew Clark
aa8266c4f7 Prepare placeholders before timing out (#13092)
* Prepare placeholders before timing out

While a tree is suspended, prepare for the timeout by pre-rendering the
placeholder state.

This simplifies the implementation a bit because every render now
results in a completed tree.

* Suspend inside an already timed out Placeholder

A component should be able to suspend inside an already timed out
placeholder. The time at which the placeholder committed is used as 
the start time for a subsequent suspend.

So, if a placeholder times out after 3 seconds, and an inner
placeholder has a threshold of 2 seconds, the inner placeholder will
not time out until 5 seconds total have elapsed.
2018-07-03 19:22:41 -07:00
Toru Kobayashi
c039c16f21 Fix this in a functional component for ShallowRenderer (#13144) 2018-07-03 17:47:40 +01:00
Joseph Lin
6731bfbed7 Update README.md (#13085)
Fix grammatical error via addition of comma.
2018-06-30 23:45:06 +01:00
Sebastian Markbåge
64e1921aab Fix Flow type that event target can be null (#13124)
We pass null sometimes when the event target has disappeared. E.g. when
touches fires on a deleted node.
2018-06-29 12:51:48 -07:00
Hilbrand Bouwkamp
bf32a3d195 Updated url to Code of Conduct page (#13126)
url is not working. Did a search on code.fb.com that returned the page I've put in the commit.
2018-06-29 13:27:24 +01:00
Dan Abramov
183aefa51f More links 2018-06-27 17:59:29 +01:00
Dan Abramov
3297102de6 Reorder sections 2018-06-27 17:12:12 +01:00
Dan Abramov
f4b6a9f8ee Just remove this sentence 2018-06-27 17:09:45 +01:00
Dan Abramov
3eedcb1fda Tweak links in README 2018-06-27 17:07:26 +01:00
Brian Vaughn
6d6de6011c Add PROFILE bundles for www+DOM and fbsource+RN/RF (#13112) 2018-06-26 13:28:41 -07:00
Dan Abramov
71a60ddb16 Add link to another article about React renderers 2018-06-26 16:45:03 +01:00
Rauno Freiberg
9e6c99ca2e Fix README typo (#13110) 2018-06-26 07:17:02 -04:00
Dan Abramov
baff5cc2f6 Update links 2018-06-26 01:31:36 +01:00
Jason Williams
6a530e3baa adding check for mousemove (#13090)
* adding check for mousemove

* adding unit test for SyntheticMouseEvent

* changing test to start with 2, removing comments
2018-06-24 10:24:54 +01:00
Dustin Masters
c35a1e7483 Fix crash during server render in react 16.4.1. (#13088)
* Fix crash during server render.

setTimeout and clearTimeout may not be available in some server-render environments (such as ChakraCore in React.NET), and loading ReactScheduler.js will cause a crash unless the existence of the variables are checked via a typeof comparison.

https://github.com/reactjs/React.NET/issues/555

The crash did not occur in 16.4.0, and the change appears to have been introduced here: https://github.com/facebook/react/pull/12931/files#diff-bbebc3357e1fb99ab13ad796e04b69a6L47

I tested this by using yarn link and running it with a local copy of React.NET. I am unsure the best way to unit test this change, since assigning null to `setTimeout` causes an immediate crash within the Node REPL.

* Fix flow errors and log warning if setTimeout / clearTimeout are
not defined / not a function.

* Use invariant to assert setTimeout / clearTimeout are functions

* Remove use of invariant

* Explain
2018-06-22 20:07:54 +01:00
Flarnie Marchan
076bbeace7 Fall back to 'setTimeout' when 'requestAnimationFrame' is not called (#13091)
* Add fixture test for schedule running when tab is backgrounded

**what is the change?:**
Just adding a test to the fixture, where we can easily see whether
scheduled callbacks are called after switching away from the fixture
tab.

**why make this change?:**
We are about to fix the schedule module so that it still runs even when
the tab is in the backround.

**test plan:**
Manually tested the fixture, verified that it works as expected and
right now callbacks are not called when the tab is in the background.

**issue:**
Internal task T30754186

* Fall back to 'setTimeout' when 'requestAnimationFrame' is not called

**what is the change?:**
If 'requestAnimationFrame' is not called for 100ms we fall back to
'setTimeout' to schedule the postmessage.

**why make this change?:**
When you start loading a page, and then switch tabs,
'requestAnimationFrame' is throttled or not called until you come back
to that tab. That means React's rendering, any any other scheduled work,
are paused.

Users expect the page to continue loading, and rendering is part of the
page load in a React app. So we need to continue calling callbacks.

**test plan:**
Manually tested using the new fixture test, observed that the callbacks
were called while switched to another tab. They were called more
slowly, but that seems like a reasonable thing.

**issue:**
Internal task T30754186

* make arguments more explicit
2018-06-22 09:13:47 -07:00
Michael Ridgway
da5c87bdfa Fixes children when using dangerouslySetInnerHtml in a selected <option> (#13078)
* Fixes children when using dangerouslySetInnerHtml in a selected <option>

This fixes an inadvertent cast of undefined children to an empty string when creating an option tag that will be selected:

```
  <select defaultValue="test">
    <option value='test' dangerouslySetInnerHTML={{ __html: '&rlm; test'}} />
  </select>
```

This causes an invariant error because both children and dangerouslySetInnerHTML are set.

* PR fix and new ReactDOMServerIntegrationForms test

* Account for null case

* Combine test cases into single test

* Add tests for failure cases

* Fix lint
2018-06-21 20:21:21 +01:00
Nathan Quarles
a960d18bc7 eliminate unnecessary do-while loop in renderRoot() (#13087) 2018-06-21 18:52:26 +01:00
Jason Williams
5b3d17a5f7 setting a flag, so that the first movement will have the correct value (#13082) 2018-06-20 22:48:53 +01:00
Brian Vaughn
b0f60895f7 Automatically Profile roots when DevTools is present (#13058)
* react-test-renderer injects itself into DevTools if present
* Fibers are always opted into ProfileMode if DevTools is present
* Added simple test for DevTools + always profiling behavior
2018-06-20 09:24:52 -07:00
Nathan Quarles
ae8c6dd534 remove some redundant lines (#13077)
* remove another couple of redundant lines

* a few more
2018-06-20 16:35:03 +01:00
Dan Abramov
0fcf92d06d Add a link to custom renderer intro article 2018-06-20 14:46:18 +01:00
Andrew Clark
97af3e1f3a Do not add additional work to a batch that is already rendering (#13072)
* Do not add additional work to a batch that is already rendering.

Otherwise, the part of the tree that hasn't rendered yet will receive
the latest state, but the already rendered part will show the state
as it was before the intervening update.

* Reduce non-helpfulness of comments
2018-06-19 10:36:56 -07:00
Andrew Clark
4fe6eec15b Always batch updates of like priority within the same event (#13071)
Expiration times are computed by adding to the current time (the start
time). However, if two updates are scheduled within the same event, we
should treat their start times as simultaneous, even if the actual clock
time has advanced between the first and second call.

In other words, because expiration times determine how updates are
batched, we want all updates of like priority that occur within the same
event to receive the same expiration time. Otherwise we get tearing.

We keep track of two separate times: the current "renderer" time and the
current "scheduler" time. The renderer time can be updated whenever; it
only exists to minimize the calls performance.now.

But the scheduler time can only be updated if there's no pending work,
or if we know for certain that we're not in the middle of an event.
2018-06-19 10:34:19 -07:00
Dan Abramov
8e87c139b4 Remove transitive dependency on fbjs (#13075) 2018-06-19 17:52:37 +01:00
Dan Abramov
aeda7b745d Remove fbjs dependency (#13069)
* Inline fbjs/lib/invariant

* Inline fbjs/lib/warning

* Remove remaining usage of fbjs in packages/*.js

* Fix lint

* Remove fbjs from dependencies

* Protect against accidental fbjs imports

* Fix broken test mocks

* Allow transitive deps on fbjs/ for UMD bundles

* Remove fbjs from release script
2018-06-19 16:03:45 +01:00
Dan Abramov
b1b3acbd6b Inline fbjs/lib/emptyObject (#13055)
* Inline fbjs/lib/emptyObject

* Explicit naming

* Compare to undefined

* Another approach for detecting whether we can mutate

Each renderer would have its own local LegacyRefsObject function.

While in general we don't want `instanceof`, here it lets us do a simple check: did *we* create the refs object?
Then we can mutate it.

If the check didn't pass, either we're attaching ref for the first time (so we know to use the constructor),
or (unlikely) we're attaching a ref to a component owned by another renderer. In this case, to avoid "losing"
refs, we assign them onto the new object. Even in that case it shouldn't "hop" between renderers anymore.

* Clearer naming

* Add test case for strings refs across renderers

* Use a shared empty object for refs by reading it from React

* Remove string refs from ReactART test

It's not currently possible to resetModules() between several renderers
without also resetting the `React` module. However, that leads to losing
the referential identity of the empty ref object, and thus subsequent
checks in the renderers for whether it is pooled fail (and cause assignments
to a frozen object).

This has always been the case, but we used to work around it by shimming
fbjs/lib/emptyObject in tests and preserving its referential identity.
This won't work anymore because we've inlined it. And preserving referential
identity of React itself wouldn't be great because it could be confusing during
testing (although we might want to revisit this in the future by moving its
stateful parts into a separate package).

For now, I'm removing string ref usage from this test because only this is
the only place in our tests where we hit this problem, and it's only
related to string refs, and not just ref mechanism in general.

* Simplify the condition
2018-06-19 13:41:42 +01:00
Dan Abramov
ae14317d68 Inline fbjs/lib/emptyFunction (#13054) 2018-06-15 18:45:14 +01:00
Dan Abramov
72434a7686 Remove or inline some fbjs dependencies (#13046) 2018-06-15 18:12:45 +01:00
Jason Williams
64c54edea4 Adding movementX and movementY to synthenticMouseEvent fixes #6723 (#9018)
* adding movementX and movementY into syntheticMouseEvent

* fixing case mistake

* Add test fixture for movementX/Y fields
2018-06-15 09:15:36 -04:00
Andrew Clark
9bd4d1fae2 Synchronously restart when an error is thrown during async rendering (#13041)
In async mode, events are interleaved with rendering. If one of those
events mutates state that is later accessed during render, it can lead
to inconsistencies/tearing.

Restarting the render from the root is often sufficient to fix the
inconsistency. We'll flush the restart synchronously to prevent yet
another mutation from happening during an interleaved event.

We'll only restart during an async render. Sync renders are already
sync, so there's no benefit in restarting. (Unless a mutation happens
during the render phase, but we don't support that.)
2018-06-14 16:37:30 -07:00
Andrew Clark
9bda7b28f3 Suspended high pri work forces lower priority work to expire early (#12965)
* onFatal, onComplete, onSuspend, onYield

For every call to renderRoot, one of onFatal, onComplete, onSuspend,
and onYield is called upon exiting. We use these in lieu of returning a
tuple. I've also chosen not to inline them into renderRoot because these
will eventually be lifted into the renderer.

* Suspended high pri work forces lower priority work to expire early

If an error is thrown, and there is lower priority pending work, we
retry at the lower priority. The lower priority work should expire
at the same time at which the high priority work would have expired.
Effectively, this increases the priority of the low priority work.

Simple example: If an error is thrown during a synchronous render, and
there's an async update, the async update should flush synchronously in
case it's able to fix the error. I've added a unit test for
this scenario.

User provided timeouts should have the same behavior, but I'll leave
that for a future PR.
2018-06-14 15:29:27 -07:00
Crux
2e75779075 Fix incorrect data in compositionend event with Korean IME on IE11 (#10217) (#12563)
* Add isUsingKoreanIME function to check if a composition event was triggered by Korean IME

* Add Korean IME check alongside useFallbackCompositionData and disable fallback mode with Korean IME
2018-06-14 16:35:05 +01:00
Sebastian Markbåge
bc963f353d setJSResponder in Fabric renderer (#13031) 2018-06-13 17:03:26 -07:00
Sebastian Markbåge
051637da61 Extract Fabric event handlers from canonical props (#13024)
We need a different "component tree" thingy for Fabric.

A lot of this doesn't really make much sense in a persistent world but
currently we can't dispatch events to memoizedProps on a Fiber since
they're pooled. Also, it's unclear what the semantics should be when we
dispatch an event that happened when the old props were in effect but now
we have new props already.

This implementation tries to use the last committed props but also fails
at that because we don't have a commit hook in the persistent mode.

However, at least it doesn't crash when dispatching. :)
2018-06-13 16:20:48 -07:00
Flarnie Marchan
2a8085980f Remove rAF fork (#12980)
* Remove rAF fork

**what is the change?:**
Undid https://github.com/facebook/react/pull/12837

**why make this change?:**
We originally forked rAF because we needed to pull in a particular
version of rAF internally at Facebook, to avoid grabbing the default
polyfilled version.

The longer term solution, until we can get rid of the global polyfill
behavior, is to initialize 'schedule' before the polyfilling happens.

Now that we have landed and synced
https://github.com/facebook/react/pull/12900 successfully, we can
initialize 'schedule' before the polyfill runs.
So we can remove the rAF fork. Here is how it will work:

1. Land this PR on Github.
2. Flarnie will quickly run a sync getting this change into www.
3. We delete the internal forked version of
   'requestAnimationFrameForReact'.
4. We require 'schedule' in the polyfill file itself, before the
   polyfilling happens.

**test plan:**
Flarnie will manually try the above steps locally and verify that things
work.

**issue:**
Internal task T29442940

* fix nits

* fix tests, fix changes from rebasing

* fix lint
2018-06-13 10:57:35 -07:00
Andrew Clark
e0c78344e2 Retry on error if there's lower priority pending work (#12957)
* Remove enableSuspense flag from PendingPriority module

We're going to use this for suspending on error, too.

* Retry on error if there's lower priority pending work

If an error is thrown, and there's lower priority work, it's possible
the lower priority work will fix the error. Retry at the lower priority.

If an error is thrown and there's no more work to try, handle the error
like we normally do (trigger the nearest error boundary).
2018-06-13 10:47:14 -07:00
Dan Abramov
74b1723df1 Update changelog for 16.4.1 2018-06-13 17:23:59 +01:00
Jason Quense
7b1bbe6bef Use change or input events for checkable inputs 2018-04-18 17:21:42 -04:00
315 changed files with 26268 additions and 4611 deletions

View File

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

View File

@@ -5,6 +5,18 @@
Click to see more.
</summary>
</details>
## 16.4.2 (August 1, 2018)
### React DOM Server
* Fix a [potential XSS vulnerability when the attacker controls an attribute name](https://reactjs.org/blog/2018/08/01/react-v-16-4-2.html) (`CVE-2018-6341`). This fix is available in the latest `react-dom@16.4.2`, as well as in previous affected minor versions: `react-dom@16.0.1`, `react-dom@16.1.2`, `react-dom@16.2.1`, and `react-dom@16.3.3`. ([@gaearon](https://github.com/gaearon) in [#13302](https://github.com/facebook/react/pull/13302))
* Fix a crash in the server renderer when an attribute is called `hasOwnProperty`. This fix is only available in `react-dom@16.4.2`. ([@gaearon](https://github.com/gaearon) in [#13303](https://github.com/facebook/react/pull/13303))
## 16.4.1 (June 13, 2018)
### React
* You can now assign `propTypes` to components returned by `React.ForwardRef`. ([@bvaughn](https://github.com/bvaughn) in [#12911](https://github.com/facebook/react/pull/12911))
@@ -27,8 +39,6 @@
* Allow multiple root children in test renderer traversal API. ([@gaearon](https://github.com/gaearon) in [#13017](https://github.com/facebook/react/pull/13017))
* Fix `getDerivedStateFromProps()` in the shallow renderer to not discard the pending state. ([@fatfisz](https://github.com/fatfisz) in [#13030](https://github.com/facebook/react/pull/13030))
</details>
## 16.4.0 (May 23, 2018)
### React
@@ -68,6 +78,12 @@
* The [new host config shape](https://github.com/facebook/react/blob/c601f7a64640290af85c9f0e33c78480656b46bc/packages/react-noop-renderer/src/createReactNoop.js#L82-L285) is flat and doesn't use nested objects. ([@gaearon](https://github.com/gaearon) in [#12792](https://github.com/facebook/react/pull/12792))
## 16.3.3 (August 1, 2018)
### React DOM Server
* Fix a [potential XSS vulnerability when the attacker controls an attribute name](https://reactjs.org/blog/2018/08/01/react-v-16-4-2.html) (`CVE-2018-6341`). This fix is available in the latest `react-dom@16.4.2`, as well as in previous affected minor versions: `react-dom@16.0.1`, `react-dom@16.1.2`, `react-dom@16.2.1`, and `react-dom@16.3.3`. ([@gaearon](https://github.com/gaearon) in [#13302](https://github.com/facebook/react/pull/13302))
## 16.3.2 (April 16, 2018)
### React
@@ -177,6 +193,12 @@
* Fix a crash on updates. ([@rmhartog](https://github.com/rmhartog) in [#11955](https://github.com/facebook/react/pull/11955))
## 16.2.1 (August 1, 2018)
### React DOM Server
* Fix a [potential XSS vulnerability when the attacker controls an attribute name](https://reactjs.org/blog/2018/08/01/react-v-16-4-2.html) (`CVE-2018-6341`). This fix is available in the latest `react-dom@16.4.2`, as well as in previous affected minor versions: `react-dom@16.0.1`, `react-dom@16.1.2`, `react-dom@16.2.1`, and `react-dom@16.3.3`. ([@gaearon](https://github.com/gaearon) in [#13302](https://github.com/facebook/react/pull/13302))
## 16.2.0 (November 28, 2017)
### React
@@ -201,6 +223,12 @@
* Many tests were rewritten against the public API. Big thanks to [everyone who contributed](https://github.com/facebook/react/issues/11299)!
## 16.1.2 (August 1, 2018)
### React DOM Server
* Fix a [potential XSS vulnerability when the attacker controls an attribute name](https://reactjs.org/blog/2018/08/01/react-v-16-4-2.html) (`CVE-2018-6341`). This fix is available in the latest `react-dom@16.4.2`, as well as in previous affected minor versions: `react-dom@16.0.1`, `react-dom@16.1.2`, `react-dom@16.2.1`, and `react-dom@16.3.3`. ([@gaearon](https://github.com/gaearon) in [#13302](https://github.com/facebook/react/pull/13302))
## 16.1.1 (November 13, 2017)
### React
@@ -291,6 +319,12 @@ Starting with 16.1.0, we will no longer be publishing new releases on Bower. You
* First release of the [new experimental package](https://github.com/facebook/react/tree/master/packages/react-call-return) for parent-child communication. ([@gaearon](https://github.com/gaearon) in [#11364](https://github.com/facebook/react/pull/11364))
## 16.0.1 (August 1, 2018)
### React DOM Server
* Fix a [potential XSS vulnerability when the attacker controls an attribute name](https://reactjs.org/blog/2018/08/01/react-v-16-4-2.html) (`CVE-2018-6341`). This fix is available in the latest `react-dom@16.4.2`, as well as in previous affected minor versions: `react-dom@16.0.1`, `react-dom@16.1.2`, `react-dom@16.2.1`, and `react-dom@16.3.3`. ([@gaearon](https://github.com/gaearon) in [#13302](https://github.com/facebook/react/pull/13302))
## 16.0.0 (September 26, 2017)
### New JS Environment Requirements

View File

@@ -1,3 +1,3 @@
# 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.facebook.com/pages/876921332402685/open-source-code-of-conduct) so that you can understand what actions will and will not be tolerated.
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.

View File

@@ -8,15 +8,28 @@ React is a JavaScript library for building user interfaces.
[Learn how to use React in your own project](https://reactjs.org/docs/getting-started.html).
## Installation
React has been designed for gradual adoption from the start, and **you can use as little or as much React as you need**:
* Use [Online Playgrounds](https://reactjs.org/docs/getting-started.html#online-playgrounds) to get a taste of React.
* [Add React to a Website](https://reactjs.org/docs/add-react-to-a-website.html) as a `<script>` tag in one minute.
* [Create a New React App](https://reactjs.org/docs/create-a-new-react-app.html) if you're looking for a powerful JavaScript toolchain.
You can use React as a `<script>` tag from a [CDN](https://reactjs.org/docs/cdn-links.html), or as a `react` package on [npm](https://www.npmjs.com/).
## Documentation
You can find the React documentation [on the website](https://reactjs.org/docs).
It is divided into several sections:
* [Quick Start](https://reactjs.org/docs/hello-world.html)
Check out the [Getting Started](https://reactjs.org/docs/getting-started.html) page for a quick overview.
The documentation is divided into several sections:
* [Tutorial](https://reactjs.org/tutorial/tutorial.html)
* [Main Concepts](https://reactjs.org/docs/hello-world.html)
* [Advanced Guides](https://reactjs.org/docs/jsx-in-depth.html)
* [API Reference](https://reactjs.org/docs/react-api.html)
* [Tutorial](https://reactjs.org/tutorial/tutorial.html)
* [Where to Get Support](https://reactjs.org/community/support.html)
* [Contributing Guide](https://reactjs.org/docs/how-to-contribute.html)
@@ -34,26 +47,14 @@ class HelloMessage extends React.Component {
}
ReactDOM.render(
<HelloMessage name="John" />,
<HelloMessage name="Taylor" />,
document.getElementById('container')
);
```
This example will render "Hello John" into a container on the page.
This example will render "Hello Taylor" into a container on the page.
You'll notice that we used an HTML-like syntax; [we call it JSX](https://reactjs.org/docs/introducing-jsx.html). JSX is not required to use React, but it makes code more readable, and writing it feels like writing HTML. We recommend using [Babel](https://babeljs.io/) with a [React preset](https://babeljs.io/docs/plugins/preset-react/) to convert JSX into native JavaScript for browsers to digest.
## Installation
React is available as the `react` package on [npm](https://www.npmjs.com/). It is also available on a [CDN](https://reactjs.org/docs/cdn-links.html).
React is flexible and can be used in a variety of projects. You can create new apps with it, but you can also gradually introduce it into an existing codebase without doing a rewrite.
The recommended way to install React depends on your project. Here you can find short guides for the most common scenarios:
* [Trying Out React](https://reactjs.org/docs/try-react.html)
* [Creating a New Application](https://reactjs.org/docs/add-react-to-a-new-app.html)
* [Adding React to an Existing Application](https://reactjs.org/docs/add-react-to-an-existing-app.html)
You'll notice that we used an HTML-like syntax; [we call it JSX](https://reactjs.org/docs/introducing-jsx.html). JSX is not required to use React, but it makes code more readable, and writing it feels like writing HTML. If you're using React as a `<script>` tag, read [this section](https://reactjs.org/docs/add-react-to-a-website.html#optional-try-react-with-jsx) on integrating JSX; otherwise, the [recommended JavaScript toolchains](https://reactjs.org/docs/create-a-new-react-app.html) handle it automatically.
## Contributing

View File

@@ -5,8 +5,9 @@ init:
- git config --global core.autocrlf input
environment:
nodejs_version: 8
JAVA_HOME: C:\Program Files\Java\jdk1.8.0
matrix:
- nodejs_version: 10
# Finish on first failed build
matrix:

View File

@@ -20,7 +20,7 @@ The left box shows the property (or attribute) assigned by React 15.\*, and the
right box shows the property (or attribute) assigned by the latest version of
React 16.
Right now we use a purple outline to call out cases where the assigned property
Right now, we use a purple outline to call out cases where the assigned property
(or attribute) has changed between React 15 and 16.
---

View File

@@ -65,6 +65,7 @@ class Header extends React.Component {
<option value="/custom-elements">Custom Elements</option>
<option value="/media-events">Media Events</option>
<option value="/pointer-events">Pointer Events</option>
<option value="/mouse-events">Mouse Events</option>
</select>
</label>
<label htmlFor="react_version">

View File

@@ -12,6 +12,7 @@ import EventPooling from './event-pooling';
import CustomElementFixtures from './custom-elements';
import MediaEventsFixtures from './media-events';
import PointerEventsFixtures from './pointer-events';
import MouseEventsFixtures from './mouse-events';
const React = window.React;
@@ -49,6 +50,8 @@ function FixturesPage() {
return <MediaEventsFixtures />;
case '/pointer-events':
return <PointerEventsFixtures />;
case '/mouse-events':
return <MouseEventsFixtures />;
default:
return <p>Please select a test fixture.</p>;
}

View File

@@ -0,0 +1,16 @@
import FixtureSet from '../../FixtureSet';
import MouseMovement from './mouse-movement';
const React = window.React;
class MouseEvents extends React.Component {
render() {
return (
<FixtureSet title="Mouse Events" description="">
<MouseMovement />
</FixtureSet>
);
}
}
export default MouseEvents;

View File

@@ -0,0 +1,48 @@
import TestCase from '../../TestCase';
const React = window.React;
class MouseMovement extends React.Component {
state = {
movement: {x: 0, y: 0},
};
onMove = event => {
this.setState({x: event.movementX, y: event.movementY});
};
render() {
const {x, y} = this.state;
const boxStyle = {
padding: '10px 20px',
border: '1px solid #d9d9d9',
margin: '10px 0 20px',
};
return (
<TestCase
title="Mouse Movement"
description="We polyfill the movementX and movementY fields."
affectedBrowsers="IE, Safari">
<TestCase.Steps>
<li>Mouse over the box below</li>
</TestCase.Steps>
<TestCase.ExpectedResult>
The reported values should equal the pixel (integer) difference
between mouse movements positions.
</TestCase.ExpectedResult>
<div style={boxStyle} onMouseMove={this.onMove}>
<p>Trace your mouse over this box.</p>
<p>
Last movement: {x},{y}
</p>
</div>
</TestCase>
);
}
}
export default MouseMovement;

View File

@@ -158,6 +158,31 @@ class SelectFixture extends React.Component {
</form>
</div>
</TestCase>
<TestCase
title="An option which contains conditional render fails"
relatedIssues="11911">
<TestCase.Steps>
<li>Select any option</li>
</TestCase.Steps>
<TestCase.ExpectedResult>
Option should be set
</TestCase.ExpectedResult>
<div className="test-fixture">
<select value={this.state.value} onChange={this.onChange}>
<option value="red">
red {this.state.value === 'red' && 'is chosen '} TextNode
</option>
<option value="blue">
blue {this.state.value === 'blue' && 'is chosen '} TextNode
</option>
<option value="green">
green {this.state.value === 'green' && 'is chosen '} TextNode
</option>
</select>
</div>
</TestCase>
</FixtureSet>
);
}

View File

@@ -23,7 +23,7 @@
<ol>
<li>
<button onClick="runTestOne()">Run Test 1</button>
<p>Calls the callback with the frame when not blocked:</p>
<p>Calls the callback within the frame when not blocked:</p>
<div><b>Expected:</b></div>
<div id="test-1-expected">
</div>
@@ -79,6 +79,15 @@
<p><b>IMPORTANT:</b> Open the console when you run this! Inspect the logs there!</p>
<button onClick="runTestSix()">Run Test 6</button>
</li>
<li>
<p>Continues calling callbacks even when user switches away from this tab</p>
<button onClick="runTestSeven()">Run Test 7</button>
<div><b>Click the button above, observe the counter, then switch to
another tab and switch back:</b></div>
<div id="test-7">
</div>
<div> If the counter advanced while you were away from this tab, it's correct.</div>
</li>
</ol>
<script src="../../build/dist/react-scheduler.development.js"></script>
<script src="https://unpkg.com/babel-standalone@6/babel.js"></script>
@@ -464,6 +473,22 @@ function runTestSix() {
console.log('scheduled cbE');
};
}
function runTestSeven() {
// Test 7
// Calls callbacks, continues calling them even when this tab is in the
// background
clearTestResult(7);
let counter = -1;
function incrementCounterAndScheduleNextCallback() {
const counterNode = document.getElementById('test-7');
counter++;
counterNode.innerHTML = counter;
waitForTimeToPass(100);
scheduleWork(incrementCounterAndScheduleNextCallback);
}
scheduleWork(incrementCounterAndScheduleNextCallback);
}
</script type="text/babel">
</body>
</html>

View File

@@ -0,0 +1,14 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.
# dependencies
node_modules
# testing
coverage
# production
build
# misc
.DS_Store
npm-debug.log

View File

@@ -0,0 +1,33 @@
# IO "suspense" demo
## What is this fixture?
This is a demo application based on [Dan Abramov's](https://github.com/gaearon) recent [JSConf Iceland talk](https://reactjs.org/blog/2018/03/01/sneak-peek-beyond-react-16.html) about React.
It depends on a local build of React and enables us to easily test async and "suspense" APIs in a more "real world app" like context.
## Can I use this code in production?
No. The APIs being tested here are unstable and some of them have still not been released to NPM. For now, this fixture is only a test harness.
## How do I run this fixture?
Clone the React repository.
Open `packages/shared/ReactFeatureFlags.js` locally (make sure you didn't open a similarly named file!) and set [the `enableSuspense` flag](https://github.com/facebook/react/blob/d79238f1eeb6634ba7a3df23c3b2709b56cbb8b2/packages/shared/ReactFeatureFlags.js#L19) to `true`.
Then follow these steps:
```shell
# 1: Build react from source
cd /path/to/react
yarn
yarn build dom,core,interaction,simple-cache-provider --type=NODE
# 2: Install fixture dependencies
cd fixtures/unstable-async/suspense/
yarn
# 3: Run the app
yarn start
```

View File

@@ -0,0 +1,41 @@
{
"name": "io-demo",
"version": "0.1.0",
"private": true,
"homepage": ".",
"devDependencies": {
"gh-pages": "^1.1.0",
"react-scripts": "^1.1.4"
},
"dependencies": {
"clipboard": "^1.7.1",
"github-fork-ribbon-css": "^0.2.1",
"react": "../../../build/node_modules/react",
"react-dom": "../../../build/node_modules/react-dom",
"react-draggable": "^3.0.5",
"simple-cache-provider": "../../../build/node_modules/simple-cache-provider"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject",
"deploy": "gh-pages -d build"
},
"eslintConfig": {
"extends": "./node_modules/react-scripts/config/eslint.js"
},
"browserslist": {
"development": [
"last 2 chrome versions",
"last 2 firefox versions",
"last 2 edge versions"
],
"production": [
">1%",
"last 4 versions",
"Firefox ESR",
"not ie < 11"
]
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View File

@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="shortcut icon" href="./src/favicon.ico">
<title>Movie List</title>
</head>
<body>
<div id="root"></div>
<div id="debugger"></div>
</body>
</html>

View File

@@ -0,0 +1,15 @@
{
"short_name": "Emoji Search",
"name": "Emoji Search Example App",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
}
],
"start_url": "./index.html",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

View File

@@ -0,0 +1,334 @@
export const coreContributorListJSON = [
{
id: 'acdlite',
name: 'Andrew Clark',
},
{
id: 'bvaughn',
name: 'Brian Vaughn',
},
{
id: 'gaearon',
name: 'Dan Abramov',
},
{
id: 'trueadm',
name: 'Dominic Gannaway',
},
{
id: 'flarnie',
name: 'Flarnie Marchan',
},
{
id: 'sebmarkbage',
name: 'Sebastian Markbåge',
},
{
id: 'sophiebits',
name: 'Sophie Alpert',
},
];
export const userProfileJSON = {
acdlite: {
id: 'acdlite',
name: 'Andrew Clark',
image: '/img/acdlite.jpeg',
location: 'Redwood City, CA',
email: 'acdlite@me.com',
tagline: 'React core at Facebook. Hi!',
},
bvaughn: {
id: 'bvaughn',
name: 'Brian Vaughn',
image: '/img/bvaughn.jpeg',
location: 'Mountain View, CA',
email: 'brian.david.vaughn@gmail.com',
tagline:
'React JS core team at @facebook; formerly at @treasure-data and @google.',
},
gaearon: {
id: 'gaearon',
name: 'Dan Abramov',
image: '/img/gaearon.jpeg',
location: 'London, UK',
email: 'dan.abramov@me.com',
tagline:
'Working on @reactjs. Co-author of Redux and Create React App. Building tools for humans.',
},
flarnie: {
id: 'flarnie',
name: 'Flarnie Marchan',
image: '/img/flarnie.jpeg',
location: 'Oakland, CA',
email: null,
tagline:
'Software Engineer at Facebook React Core Team & Co-maintainer of Draft.js',
},
sebmarkbage: {
id: 'sebmarkbage',
name: 'Sebastian Markbåge',
image: '/img/sebmarkbage.jpeg',
location: 'San Francisco',
email: 'sebastian@calyptus.eu',
tagline: null,
},
sophiebits: {
id: 'sophiebits',
name: 'Sophie Alpert',
image: '/img/sophiebits.jpeg',
location: 'California',
email: 'hi@sophiebits.com',
tagline:
'I like fixing things. eng manager of @reactjs at Facebook. ex-@khanacademy. 💎🌸 she/her. kindness, intersectional feminism, music.',
},
trueadm: {
id: 'trueadm',
name: 'Dominic Gannaway',
image: '/img/trueadm.jpeg',
location: 'London, United Kingdom',
email: null,
tagline:
'Currently an engineer on the React core team at @facebook. Author of @infernojs and t7. Enjoys coding + being a Dad.',
},
};
export const userRepositoriesListJSON = {
acdlite: [
{
name: 'recompose',
url: 'https://github.com/acdlite/recompose',
description:
'A React utility belt for function components and higher-order components.',
},
{
name: 'react-fiber-architecture',
url: 'https://github.com/acdlite/react-fiber-architecture',
description: "A description of React's new core algorithm, React Fiber",
},
{
name: 'redux-router',
url: 'https://github.com/acdlite/redux-router',
description:
'Redux bindings for React Router keep your router state inside your Redux store',
},
{
name: 'flummox',
url: 'https://github.com/acdlite/flummox',
description: 'Minimal, isomorphic Flux.',
},
{
name: 'redux-rx',
url: 'https://github.com/acdlite/redux-rx',
description: 'RxJS utilities for Redux.',
},
{
name: 'react-remarkable',
url: 'https://github.com/acdlite/react-remarkable',
description: 'A React component for rendering Markdown with remarkable',
},
],
bvaughn: [
{
name: 'react-virtualized',
url: 'https://github.com/bvaughn/react-virtualized',
description:
'React components for efficiently rendering large lists and tabular data',
},
{
name: 'redux-search',
url: 'https://github.com/bvaughn/redux-search',
description: 'Redux bindings for client-side search',
},
{
name: 'react-window',
url: 'https://github.com/bvaughn/react-window',
description:
'React components for efficiently rendering large lists and tabular data',
},
{
name: 'react-virtualized-select',
url: 'https://github.com/bvaughn/react-virtualized-select',
description:
'HOC that uses react-virtualized and react-select to display large lists of options in a drop-down',
},
{
name: 'js-search',
url: 'https://github.com/bvaughn/js-search',
description:
'JS Search is an efficient, client-side search library for JavaScript and JSON objects',
},
{
name: 'react-highlight-words',
url: 'https://github.com/bvaughn/react-highlight-words',
description:
'React component to highlight words within a larger body of text',
},
],
gaearon: [
{
name: 'facebook/react',
url: 'https://github.com/facebook/react',
description:
'A declarative, efficient, and flexible JavaScript library for building user interfaces.',
},
{
name: 'reduxjs/redux',
url: 'https://github.com/reduxjs/redux',
description: 'Predictable state container for JavaScript apps',
},
{
name: 'facebook/create-react-app',
url: 'https://github.com/facebook/create-react-app',
description: 'Create React apps with no build configuration.',
},
{
name: 'reduxjs/redux-devtools',
url: 'https://github.com/reduxjs/redux-devtools',
description:
'DevTools for Redux with hot reloading, action replay, and customizable UI',
},
{
name: 'react-dnd/react-dnd',
url: 'https://github.com/react-dnd/react-dnd',
description: 'Drag and Drop for React',
},
{
name: 'paularmstrong/normalizr',
url: 'https://github.com/paularmstrong/normalizr',
description: 'Normalizes nested JSON according to a schema',
},
],
flarnie: [
{
name: 'diffux/diffux',
url: 'https://github.com/diffux/diffux',
description: 'Perceptual diffs of responsive screenshots made simple.',
},
{
name: 'facebook/draft-js',
url: 'https://github.com/facebook/draft-js',
description: 'A React framework for building text editors.',
},
{
name: 'facebook/react',
url: 'https://github.com/facebook/react',
description:
'A declarative, efficient, and flexible JavaScript library for building user interfaces.',
},
{
name: 'facebook/jest',
url: 'https://github.com/facebook/jest',
description: '🃏 Delightful JavaScript Testing.',
},
{
name: 'Galooshi/import-js',
url: 'https://github.com/Galooshi/import-js',
description: 'A tool to simplify importing JS modules',
},
{
name: 'webpack_rails_demo',
url: 'https://github.com/flarnie/webpack_rails_demo',
description: 'Setting up webpack with Ruby on Rails: a basic demo',
},
],
sebmarkbage: [
{
name: 'art',
url: 'https://github.com/sebmarkbage/art',
description:
"Retained mode vector drawing API designed for multiple output modes. There's also a built-in SVG parser.",
},
{
name: 'ecmascript-immutable-data-structures',
url:
'https://github.com/sebmarkbage/ecmascript-immutable-data-structures',
description: null,
},
{
name: 'ocamlrun-wasm',
url: 'https://github.com/sebmarkbage/ocamlrun-wasm',
description: 'OCamlrun WebAssembly - OCaml Bytecode Interpreter in WASM',
},
{
name: 'ecmascript-generator-expression',
url: 'https://github.com/sebmarkbage/ecmascript-generator-expression',
description:
'Proposal for do Generator Expressions in ECMAScript. Work in progress. Edit Add topics',
},
{
name: 'ecmascript-undefined-propagation',
url: 'https://github.com/sebmarkbage/ecmascript-undefined-propagation',
description:
'ECMAScript proposal to relax the rules to return `undefined` for property access on `null` or `undefined` instead of throwing.',
},
{
name: 'ecmascript-shallow-equal',
url: 'https://github.com/sebmarkbage/ecmascript-shallow-equal',
description: 'A proposal for ECMAScript for Object.shallowEqual',
},
],
sophiebits: [
{
name: 'facebook/react',
url: 'https://github.com/facebook/react',
description:
'A declarative, efficient, and flexible JavaScript library for building user interfaces.',
},
{
name: 'Khan/KaTeX',
url: 'https://github.com/Khan/KaTeX',
description: 'Fast math typesetting for the web.',
},
{
name: 'facebook/react-devtools',
url: 'https://github.com/facebook/react-devtools',
description:
'An extension that allows inspection of React component hierarchy in the Chrome and Firefox Developer Tools.',
},
{
name: 'vim-awesome/vim-awesome',
url: 'https://github.com/vim-awesome/vim-awesome',
description: 'Awesome Vim plugins from across the universe',
},
{
name: 'facebook/draft-js',
url: 'https://github.com/facebook/draft-js',
description: 'A React framework for building text editors.',
},
{
name: 'es3ify',
url: 'https://github.com/sophiebits/es3ify',
description:
'Browserify transform to convert ES5 syntax to be ES3-compatible.',
},
],
trueadm: [
{
name: 'facebook/react',
url: 'https://github.com/facebook/react',
description:
'A declarative, efficient, and flexible JavaScript library for building user interfaces.',
},
{
name: 'infernojs/inferno',
url: 'https://github.com/infernojs/inferno',
description:
'An extremely fast, React-like JavaScript library for building modern user interfaces',
},
{
name: 'facebook/prepack',
url: 'https://github.com/facebook/prepack',
description: 'A JavaScript bundle optimizer.',
},
{
name: 't7',
url: 'https://github.com/trueadm/t7',
description: 'Lightweight virtual DOM templating library',
},
{
name: 'infernojs/babel-plugin-inferno',
url: 'https://github.com/infernojs/babel-plugin-inferno',
description: null,
},
],
};

View File

@@ -0,0 +1,67 @@
import {
coreContributorListJSON,
userProfileJSON,
userRepositoriesListJSON,
} from './data';
export function fetchCoreContributorListJSON() {
return makeFakeAPICall('/react/contributors', coreContributorListJSON);
}
export function fetchUserProfileJSON(id) {
return makeFakeAPICall(`/${id}/details`, userProfileJSON[id]);
}
export function fetchUserRepositoriesListJSON(id) {
return makeFakeAPICall(`/${id}/repositories`, userRepositoriesListJSON[id]);
}
let fakeRequestTime = 1000;
let onProgress = () => true;
export function setFakeRequestTime(val) {
fakeRequestTime = val;
}
export function setProgressHandler(handler) {
onProgress = handler;
}
export function setPauseNewRequests(value) {
shouldPauseNewRequests = value;
}
let shouldPauseNewRequests = false;
let notifiers = {};
let isPausedUrl = {};
export function setPaused(url, isPaused) {
const wasPaused = isPausedUrl[url];
isPausedUrl[url] = isPaused;
if (isPaused !== wasPaused) {
notifiers[url]();
}
}
function makeFakeAPICall(url, result) {
let i = 1;
return new Promise(resolve => {
isPausedUrl[url] = shouldPauseNewRequests;
function notify() {
if (!isPausedUrl[url]) {
i++;
}
onProgress(url, i, isPausedUrl[url]);
if (isPausedUrl[url]) {
return;
}
if (i === 100) {
resolve(result);
} else {
setTimeout(notify, fakeRequestTime / 100);
}
}
notifiers[url] = notify;
notify();
});
}

View File

@@ -0,0 +1,7 @@
import {createCache} from 'simple-cache-provider';
export let cache;
function initCache() {
cache = createCache(initCache);
}
initCache();

View File

@@ -0,0 +1,82 @@
import React, {Fragment, Placeholder, PureComponent} from 'react';
import {unstable_deferredUpdates} from 'react-dom';
import {createResource} from 'simple-cache-provider';
import {cache} from '../cache';
import Spinner from './Spinner';
import ContributorListPage from './ContributorListPage';
const UserPageResource = createResource(() => import('./UserPage'));
function UserPageLoader(props) {
const UserPage = UserPageResource.read(cache).default;
return <UserPage {...props} />;
}
export default class App extends PureComponent {
state = {
currentId: null,
showDetail: false,
};
componentDidUpdate(prevProps, prevState) {
if (
prevState.showDetail !== this.state.showDetail ||
prevState.currentId !== this.state.currentId
) {
window.scrollTo(0, 0);
}
}
handleUserClick = id => {
this.setState({
currentId: id,
});
unstable_deferredUpdates(() => {
this.setState({
showDetail: true,
});
});
};
handleBackClick = () =>
this.setState({
currentId: null,
showDetail: false,
});
render() {
const {currentId, showDetail} = this.state;
return showDetail
? this.renderDetail(currentId)
: this.renderList(currentId);
}
renderDetail(id) {
return (
<div>
<button
onClick={this.handleBackClick}
style={{
display: 'block',
marginBottom: '1rem',
}}>
Return to list
</button>
<Placeholder delayMs={2000} fallback={<Spinner size="large" />}>
<UserPageLoader id={id} />
</Placeholder>
</div>
);
}
renderList(loadingId) {
return (
<Placeholder delayMs={1500} fallback={<Spinner size="large" />}>
<ContributorListPage
loadingId={loadingId}
onUserClick={this.handleUserClick}
/>
</Placeholder>
);
}
}

View File

@@ -0,0 +1,62 @@
import React, {Fragment} from 'react';
import {createResource} from 'simple-cache-provider';
import {cache} from '../cache';
import Spinner from './Spinner';
import {fetchCoreContributorListJSON} from '../api';
const ContributorListResource = createResource(fetchCoreContributorListJSON);
const ContributorListPage = ({loadingId, onUserClick}) => (
<Fragment>
<h1>React Core Team</h1>
<ul
style={{
display: 'grid',
gridGap: '0.5rem',
gridTemplateColumns: 'repeat(auto-fill, 20rem)',
padding: 0,
margin: 0,
}}>
{ContributorListResource.read(cache).map(user => (
<ContributorListItem
key={user.id}
onClick={() => onUserClick(user.id)}
isLoading={loadingId && user.id === loadingId}
user={user}
/>
))}
</ul>
</Fragment>
);
const ContributorListItem = ({isLoading, onClick, user}) => (
<li
onClick={onClick}
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
padding: '1rem',
backgroundColor: 'var(--color-buttonBg)',
border: '1px solid var(--color-buttonBorder)',
borderRadius: '1rem',
opacity: isLoading === false ? 0.5 : 1,
cursor: isLoading ? 'default' : 'pointer',
}}
tabIndex="0">
<div>
<strong>{user.name}</strong>
<div style={{marginTop: '0.5rem'}}>{user.id}</div>
</div>
{isLoading ? (
<Spinner size="small" />
) : (
<svg width="24" height="24" viewBox="0 0 24 24">
<path fill="none" d="M0 0h24v24H0z" />
<path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z" />
</svg>
)}
</li>
);
export default ContributorListPage;

View File

@@ -0,0 +1,68 @@
.Spinner {
animation: rotate 1.3s linear infinite;
}
@keyframes rotate {
0% { transform: rotate(0deg); }
100% { transform: rotate(270deg); }
}
.SmallSpinnerPath {
stroke-dasharray: 100;
stroke-dashoffset: 0;
transform-origin: center;
animation:
SmallDash 1.3s ease-in-out infinite;
}
@keyframes SmallDash {
0% { stroke-dashoffset: 100; }
50% {
stroke-dashoffset: 50;
transform:rotate(135deg);
}
100% {
stroke-dashoffset: 100;
transform:rotate(450deg);
}
}
.MediumSpinnerPath {
stroke-dasharray: 150;
stroke-dashoffset: 0;
transform-origin: center;
animation:
MediumDash 1.3s ease-in-out infinite;
}
@keyframes MediumDash {
0% { stroke-dashoffset: 150; }
50% {
stroke-dashoffset: 50;
transform:rotate(135deg);
}
100% {
stroke-dashoffset: 150;
transform:rotate(450deg);
}
}
.LargeSpinnerPath {
stroke-dasharray: 200;
stroke-dashoffset: 0;
transform-origin: center;
animation:
LargeDash 1.3s ease-in-out infinite;
}
@keyframes LargeDash {
0% { stroke-dashoffset: 200; }
50% {
stroke-dashoffset: 50;
transform:rotate(135deg);
}
100% {
stroke-dashoffset: 200;
transform:rotate(450deg);
}
}

View File

@@ -0,0 +1,48 @@
import React from 'react';
import './Spinner.css';
const SPINNER_SIZES = {
small: 30,
medium: 50,
large: 70,
};
const STROKE_WIDTHS = {
small: 4,
medium: 5,
large: 6,
};
const PATH_CLASS_NAMES = {
small: 'SmallSpinnerPath',
medium: 'MediumSpinnerPath',
large: 'LargeSpinnerPath',
};
// Heavily inspired by https://codepen.io/mrrocks/pen/EiplA
export default function Spinner({size = 'small'}) {
const baseSize = SPINNER_SIZES[size];
const pathSize = baseSize / 2;
const strokeWidth = STROKE_WIDTHS[size];
const pathRadius = `${baseSize / 2 - strokeWidth}px`;
const className = PATH_CLASS_NAMES[size];
return (
<svg
className={className}
width={baseSize}
height={baseSize}
viewBox={`0 0 ${baseSize} ${baseSize}`}>
<circle
className="SpinnerPath"
fill="none"
stroke="currentColor"
strokeWidth={strokeWidth}
strokeLinecap="round"
cx={pathSize}
cy={pathSize}
r={pathRadius}
/>
</svg>
);
}

View File

@@ -0,0 +1,169 @@
import React, {Fragment, Placeholder} from 'react';
import {createResource} from 'simple-cache-provider';
import Spinner from './Spinner';
import {cache} from '../cache';
import {fetchUserProfileJSON, fetchUserRepositoriesListJSON} from '../api';
export default function UserPage({id}) {
return (
<div
style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fill, 20rem)',
gridGap: '1rem',
alignItems: 'start',
}}>
<UserDetails id={id} />
<Placeholder delayMs={1000} fallback={<Spinner size="medium" />}>
<Repositories id={id} />
</Placeholder>
</div>
);
}
const UserDetailsResource = createResource(fetchUserProfileJSON);
function UserDetails({id}) {
const user = UserDetailsResource.read(cache, id);
return (
<div
style={{
display: 'grid',
gridGap: '0.5rem',
width: '20rem',
padding: '1rem',
backgroundColor: 'var(--color-buttonBg)',
border: '1px solid var(--color-buttonBorder)',
borderRadius: '1rem',
}}>
<UserPicture source={user.image} />
<div
style={{
fontSize: '1.5rem',
fontWeight: 'bold',
color: 'var(--color-pageTextDark)',
}}>
{user.name}
</div>
<div style={{fontSize: '1.25rem'}}>{user.id}</div>
{user.tagline !== null && <div>{user.tagline}</div>}
<hr
style={{
width: '100%',
height: '1px',
border: 'none',
backgroundColor: '#ddd',
}}
/>
{user.location && <Location location={user.location} />}
{user.email && <Email email={user.email} />}
</div>
);
}
const Location = ({location}) => (
<div
style={{
display: 'flex',
alignItems: 'center',
}}>
<svg
viewBox="0 0 24 24"
style={{
width: '24px',
height: '24px',
marginRight: '0.5rem',
fill: 'currentColor',
}}>
<path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z" />
<path d="M0 0h24v24H0z" fill="none" />
</svg>
{location}
</div>
);
const Email = ({email}) => (
<div
style={{
display: 'flex',
alignItems: 'center',
}}>
<svg
viewBox="0 0 24 24"
style={{
width: '24px',
height: '24px',
marginRight: '0.5rem',
fill: 'currentColor',
}}>
<path d="M20 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 4l-8 5-8-5V6l8 5 8-5v2z" />
<path d="M0 0h24v24H0z" fill="none" />
</svg>
<a href={`mailto:${email}`}>{email}</a>
</div>
);
const ImageResource = createResource(
src =>
new Promise(resolve => {
const img = new Image();
img.onload = () => resolve(src);
img.src = src;
})
);
function Img({src, ...rest}) {
return <img src={ImageResource.read(cache, src)} {...rest} />;
}
function UserPicture({source}) {
return (
<Placeholder delayMs={1500} fallback={<img src={source} alt="poster" />}>
<Img
src={source}
alt="profile picture"
style={{
width: '100%',
height: 'auto',
borderRadius: '0.5rem',
}}
/>
</Placeholder>
);
}
const UserRepositoriesResource = createResource(fetchUserRepositoriesListJSON);
function Repositories({id}) {
const reviews = UserRepositoriesResource.read(cache, id);
return (
<ul
style={{
display: 'grid',
gridGap: '1rem',
padding: 0,
margin: 0,
}}>
{reviews.map(review => <Repository key={review.id} {...review} />)}
</ul>
);
}
function Repository({description, name, url}) {
return (
<li
style={{
display: 'grid',
gridGap: '0.5rem',
padding: '1rem',
backgroundColor: 'var(--color-buttonBg)',
border: '1px solid var(--color-buttonBorder)',
borderRadius: '1rem',
}}>
<strong>
<a href={url}>{name}</a>
</strong>
<div>{description}</div>
</li>
);
}

View File

@@ -0,0 +1,90 @@
* { box-sizing: border-box; }
:root {
--color-debuggerBg: #f7f7f7;
--color-debuggerText: #333;
--color-debuggerBorder: #e7e7e7;
--color-panelBg: #f7f7f7;
--color-panelText: #333;
--color-pageTextDark: #000;
--color-pageText: #333;
--color-pageBg: #fff;
--color-buttonBg: #f7f7f7;
--color-buttonBorder: #e7e7e7;
--pt: 8px;
}
body {
margin: 0;
padding: calc(var(--pt)*4);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
color: var(--color-pageText);
background-color: var(--color-pageBg);
}
/* -------------------------------- */
/* Debugger */
/* -------------------------------- */
#debugger {
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
/* width: 100vw; */
/* height: 100vh; */
pointer-events: none;
}
.🎛 {
position: fixed;
max-width: calc(var(--pt)*28);
border-radius: var(--pt);
padding: calc(var(--pt)*2);
background-color: var(--color-debuggerBg);
border: 1px solid var(--color-debuggerBorder);
border-radius: 0.5rem;
color: var(--color-debuggerText);
pointer-events: all;
}
.🕹 {
background-color: var(--color-buttonBg);
border: 1px solid var(--color-buttonBorder);
border-radius: var(--pt);
padding: 0;
width: calc(var(--pt)*5);
height: calc(var(--pt)*5);
font-size: calc(var(--pt)*3);
line-height: 1;
font-weight: bold;
cursor: pointer;
user-select: none;
}
.🕹.👶 {
width: calc(var(--pt)*3);
height: calc(var(--pt)*3);
font-size: calc(var(--pt)*2);
}
.🕹.🐘 {
width: auto;
height: calc(var(--pt)*7);
padding: var(--pt) calc(var(--pt)*2);
}
.🕹:hover {
background-color: white;
top: -4px;
left: -4px;
box-shadow: 4px 4px 0 var(--color-buttonBorder);
}
.🕹:active {
box-shadow: none;
top: 0;
left: 0;
}

View File

@@ -0,0 +1,272 @@
import React, {Fragment, PureComponent} from 'react';
import {unstable_createRoot, render} from 'react-dom';
import {cache} from './cache';
import {
setFakeRequestTime,
setPaused,
setPauseNewRequests,
setProgressHandler,
} from './api';
import App from './components/App';
import Draggable from 'react-draggable';
import './index.css';
let handleReset;
class Shell extends PureComponent {
state = {
iteration: 0,
};
componentDidMount() {
handleReset = this.handleReset;
}
handleReset = () =>
this.setState(prevState => ({
iteration: prevState.iteration + 1,
}));
render() {
return <App key={this.state.iteration} />;
}
}
class Debugger extends PureComponent {
state = {
iteration: 0,
strategy: 'async',
requestTime: 1,
showDebugger: false,
pauseNewRequests: false,
waitTime: 0,
requests: {},
};
componentDidMount() {
setFakeRequestTime(this.state.requestTime * 1000);
setProgressHandler(this.handleProgress);
window.addEventListener('keydown', e => {
if (e.key.toLowerCase() === '/') {
this.setState(state => ({
showDebugger: !state.showDebugger,
}));
} else if (e.key.toLowerCase() === 'p') {
this.togglePauseRequests();
}
});
}
componentDidUpdate(prevProps, prevState) {
if (prevState.requestTime !== this.state.requestTime) {
setFakeRequestTime(this.state.requestTime * 1000);
}
}
handleReset = () => {
cache.invalidate();
this.setState(state => ({
requests: {},
}));
handleReset();
};
handleProgress = (url, progress, isPaused) => {
this.setState(state => ({
requests: {
...state.requests,
[url]: {
url,
progress,
isPaused,
},
},
}));
};
togglePauseRequests = () => {
this.setState(
prevState => {
return {pauseNewRequests: !prevState.pauseNewRequests};
},
() => {
setPauseNewRequests(this.state.pauseNewRequests);
}
);
};
render() {
if (!this.state.showDebugger) {
return null;
}
return (
<Draggable cancel="input">
<div
className="🎛"
style={{
bottom: 20,
right: 20,
}}>
<div>
Latency: {this.state.requestTime} second{this.state.requestTime !==
1
? 's'
: ''}{' '}
<input
type="range"
min="0"
max="3"
step="0.5"
style={{width: '100%'}}
value={this.state.requestTime}
onChange={e => {
e.stopPropagation();
this.setState({requestTime: parseFloat(e.target.value)});
}}
/>
</div>
<label>
<input
type="checkbox"
checked={this.state.pauseNewRequests}
onChange={this.togglePauseRequests}
/>
Pause new requests
</label>
<br />
<br />
{Object.values(this.state.requests).filter(x => x.progress !== 100)
.length > 0 ? (
<Fragment>
<div style={{marginBottom: 10}}>
<b>Loading</b>
</div>
</Fragment>
) : (
<Fragment>
<div style={{marginBottom: 10}}>
<b>Loading</b>
</div>
<small style={{height: 20, display: 'block'}}>(None)</small>
</Fragment>
)}
{Object.keys(this.state.requests)
.reverse()
.map(url => {
const {progress, isPaused} = this.state.requests[url];
if (progress === 100) {
return null;
}
return (
<div
key={url}
style={{
height: 20,
width: '100%',
position: 'relative',
cursor: 'pointer',
title: isPaused ? 'Resume' : 'Pause',
}}
onClick={e => {
setPaused(url, !isPaused);
}}>
<div
style={{
height: '100%',
width: progress + '%',
position: 'absolute',
left: 0,
top: 0,
backgroundColor: isPaused ? '#fbfb0e' : '#61dafb',
zIndex: -1,
opacity: 0.8,
}}
/>
<div
style={{
fontFamily: 'monospace',
fontWeight: 'bold',
color: 'black',
}}>
{url}
</div>
</div>
);
})}
{Object.values(this.state.requests).filter(x => x.progress === 100)
.length > 0 ? (
<Fragment>
<br />
<div style={{marginBottom: 10}}>
<b>Cached</b>{' '}
<button
style={{
height: 16,
outline: 'none',
border: 'none',
background: 'none',
cursor: 'pointer',
}}
onClick={this.handleReset}>
🗑
</button>
</div>
</Fragment>
) : (
<Fragment>
<br />
<div style={{marginBottom: 10}}>
<b>Cached</b>
</div>
<small style={{height: 20, display: 'block'}}>(None)</small>
</Fragment>
)}
{Object.keys(this.state.requests)
.reverse()
.map(url => {
const {progress} = this.state.requests[url];
if (progress !== 100) {
return null;
}
return (
<div
key={url}
style={{
height: 20,
width: '100%',
position: 'relative',
}}>
<div
style={{
height: '100%',
width: progress + '%',
position: 'absolute',
left: 0,
top: 0,
backgroundColor:
progress !== 100 ? '#61dafb' : 'lightgreen',
zIndex: -1,
opacity: 0.8,
}}
/>
<div
style={{
fontFamily: 'monospace',
fontWeight: 'bold',
color: 'black',
}}>
{url}
</div>
</div>
);
})}
</div>
</Draggable>
);
}
}
unstable_createRoot(document.getElementById('root')).render(<Shell />);
render(<Debugger />, document.getElementById('debugger'));

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,21 @@
# See https://help.github.com/ignore-files/ for more about ignoring files.
# dependencies
/node_modules
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

View File

@@ -0,0 +1,29 @@
# CPU async rendering demo
## What is this fixture?
This is a demo application based on [Dan Abramov's](https://github.com/gaearon) recent [JSConf Iceland talk](https://reactjs.org/blog/2018/03/01/sneak-peek-beyond-react-16.html) about React.
It depends on a local build of React and enables us to easily test async "time slicing" APIs in a more "real world app" like context.
## Can I use this code in production?
No. The APIs being tested here are unstable and some of them have still not been released to NPM. For now, this fixture is only a test harness.
There are also known bugs and inefficiencies in master so **don't use this fixture for demonstration purposes either yet**. Until they are fixed, this fixture is **not** indicative of React async rendering performance.
## How do I run this fixture?
```shell
# 1: Build react from source
cd /path/to/react
yarn
yarn build dom,core,interaction,simple-cache-provider --type=NODE
# 2: Install fixture dependencies
cd fixtures/unstable-async/time-slicing/
yarn
# 3: Run the app
yarn start
```

View File

@@ -0,0 +1,20 @@
{
"name": "cpu-demo",
"version": "0.1.0",
"private": true,
"dependencies": {
"glamor": "^2.20.40",
"react": "../../../build/node_modules/react",
"react-dom": "../../../build/node_modules/react-dom",
"react-markdown": "^3.2.0",
"react-scripts": "^1.1.4",
"simple-cache-provider": "../../../build/node_modules/simple-cache-provider",
"victory": "^0.25.6"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@@ -0,0 +1,40 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="theme-color" content="#000000">
<!--
manifest.json provides metadata used when your web app is added to the
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

View File

@@ -0,0 +1,15 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
}
],
"start_url": "./index.html",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

View File

@@ -0,0 +1,126 @@
import React, {PureComponent} from 'react';
import {
VictoryArea,
VictoryAxis,
VictoryChart,
VictoryBar,
VictoryTheme,
VictoryScatter,
VictoryStack,
} from 'victory';
const colors = ['#fff489', '#fa57c1', '#b166cc', '#7572ff', '#69a6f9'];
export default class Charts extends PureComponent {
render() {
const streamData = this.props.data;
return (
<div>
<div style={{display: 'flex'}}>
<VictoryChart
theme={VictoryTheme.material}
width={400}
height={400}
style={{
parent: {
backgroundColor: '#222',
},
}}>
<VictoryAxis
style={{
axis: {stroke: 'white'},
tickLabels: {fill: 'white'},
}}
/>
<VictoryAxis
style={{
axis: {stroke: 'white'},
tickLabels: {fill: 'white'},
}}
dependentAxis
/>
<VictoryScatter
data={streamData[0]}
size={6}
style={{
data: {
fill: d => colors[d.x % 5],
},
}}
/>
</VictoryChart>
<VictoryChart
theme={VictoryTheme.material}
width={400}
height={400}
style={{
parent: {
backgroundColor: '#222',
},
}}
domainPadding={[20, 20]}>
<VictoryAxis
style={{
axis: {stroke: 'white'},
tickLabels: {fill: 'white'},
}}
/>
<VictoryAxis
style={{
axis: {stroke: 'white'},
tickLabels: {fill: 'white'},
}}
dependentAxis
/>
<VictoryBar
data={streamData[0]}
style={{
data: {
fill: d => colors[d.x % 5],
stroke: 'none',
padding: 5,
},
}}
/>
</VictoryChart>
</div>
<div
style={{
display: 'flex',
position: 'relative',
top: '-50px',
}}>
<VictoryChart
theme={VictoryTheme.material}
width={800}
height={350}
style={{
parent: {
backgroundColor: '#222',
},
}}>
<VictoryAxis
style={{
axis: {stroke: 'white'},
tickLabels: {fill: 'white'},
}}
/>
<VictoryAxis
style={{
axis: {stroke: 'white'},
tickLabels: {fill: 'white'},
}}
dependentAxis
/>
<VictoryStack>
{streamData.map((data, i) => (
<VictoryArea key={i} data={data} colorScale={colors} />
))}
</VictoryStack>
</VictoryChart>
</div>
</div>
);
}
}

View File

@@ -0,0 +1,105 @@
import React, {createRef, PureComponent} from 'react';
const SPEED = 0.003 / Math.PI;
const FRAMES = 10;
export default class Clock extends PureComponent {
faceRef = createRef();
arcGroupRef = createRef();
clockHandRef = createRef();
frame = null;
hitCounter = 0;
rotation = 0;
t0 = Date.now();
arcs = [];
animate = () => {
const now = Date.now();
const td = now - this.t0;
this.rotation = (this.rotation + SPEED * td) % (2 * Math.PI);
this.t0 = now;
this.arcs.push({rotation: this.rotation, td});
let lx, ly, tx, ty;
if (this.arcs.length > FRAMES) {
this.arcs.forEach(({rotation, td}, i) => {
lx = tx;
ly = ty;
const r = 145;
tx = 155 + r * Math.cos(rotation);
ty = 155 + r * Math.sin(rotation);
const bigArc = SPEED * td < Math.PI ? '0' : '1';
const path = `M${tx} ${ty}A${r} ${r} 0 ${bigArc} 0 ${lx} ${ly}L155 155`;
const hue = 120 - Math.min(120, td / 4);
const colour = `hsl(${hue}, 100%, ${60 - i * (30 / FRAMES)}%)`;
if (i !== 0) {
const arcEl = this.arcGroupRef.current.children[i - 1];
arcEl.setAttribute('d', path);
arcEl.setAttribute('fill', colour);
}
});
this.clockHandRef.current.setAttribute('d', `M155 155L${tx} ${ty}`);
this.arcs.shift();
}
if (this.hitCounter > 0) {
this.faceRef.current.setAttribute(
'fill',
`hsla(0, 0%, ${this.hitCounter}%, 0.95)`
);
this.hitCounter -= 1;
} else {
this.hitCounter = 0;
this.faceRef.current.setAttribute('fill', 'hsla(0, 0%, 5%, 0.95)');
}
this.frame = requestAnimationFrame(this.animate);
};
componentDidMount() {
this.frame = requestAnimationFrame(this.animate);
if (this.faceRef.current) {
this.faceRef.current.addEventListener('click', this.handleClick);
}
}
componentDidUpdate() {
console.log('componentDidUpdate()', this.faceRef.current);
}
componentWillUnmount() {
this.faceRef.current.removeEventListener('click', this.handleClick);
if (this.frame) {
cancelAnimationFrame(this.frame);
}
}
handleClick = e => {
e.stopPropagation();
this.hitCounter = 50;
};
render() {
const paths = new Array(FRAMES);
for (let i = 0; i < FRAMES; i++) {
paths.push(<path className="arcHand" key={i} />);
}
return (
<div className="stutterer">
<svg height="310" width="310">
<circle
className="clockFace"
onClick={this.handleClick}
cx={155}
cy={155}
r={150}
ref={this.faceRef}
/>
<g ref={this.arcGroupRef}>{paths}</g>
<path className="clockHand" ref={this.clockHandRef} />
</svg>
</div>
);
}
}

View File

@@ -0,0 +1,143 @@
html,
body {
padding: 0px;
margin: 0px;
user-select: none;
font-family: Karla, Helvetica Neue, Helvetica, sans-serif;
background: rgb(34, 34, 34);
color: white;
overflow: hidden;
}
.VictoryContainer {
opacity: 0.8;
}
* {
box-sizing: border-box;
}
#root {
height: 100vh;
}
.container {
width: 100%;
max-width: 960px;
margin: auto;
padding: 10px;
}
.rendering {
margin-top: 20px;
margin-bottom: 20px;
zoom: 1.8;
}
label {
zoom: 1;
margin-right: 50px;
font-size: 30px;
}
label.selected {
font-weight: bold;
}
label:nth-child(1).selected {
color: rgb(253, 25, 153);
}
label:nth-child(2).selected {
color: rgb(255, 240, 1);
}
label:nth-child(3).selected {
color: #61dafb;
}
.chart {
width: 100%;
height: 100%;
}
.input {
padding: 16px;
font-size: 30px;
width: 100%;
display: block;
}
.input.sync {
outline-color: rgba(253, 25, 153, 0.1);
}
.input.debounced {
outline-color: rgba(255, 240, 1, 0.1);
}
.input.async {
outline-color: rgba(97, 218, 251, 0.1);
}
label {
font-size: 20px;
}
label label {
display: 'inline-block';
margin-left: 20px;
}
.row {
flex: 1;
display: flex;
margin-top: 20px;
min-height: 100%;
}
.column {
flex: 1;
}
.demo {
position: relative;
min-height: 100vh;
}
.stutterer {
transform: scale(1.5);
height: 310px;
width: 310px;
position: absolute;
left: 0;
right: 0;
top: -256px;
bottom: 0;
margin: auto;
box-shadow: 0 0 10px 10px rgba(0, 0, 0, 0.2);
border-radius: 200px;
}
.clockHand {
stroke: white;
stroke-width: 10px;
stroke-linecap: round;
}
.clockFace {
stroke: white;
stroke-width: 10px;
}
.arcHand {
}
.innerLine {
border-radius: 6px;
position: absolute;
height: 149px;
left: 47.5%;
top: 0%;
width: 5%;
background-color: red;
transform-origin: bottom center;
}

View File

@@ -0,0 +1,156 @@
import React, {PureComponent, unstable_AsyncMode} from 'react';
import {flushSync, render, unstable_deferredUpdates} from 'react-dom';
import _ from 'lodash';
import Charts from './Charts';
import Clock from './Clock';
import './index.css';
let cachedData = new Map();
class App extends PureComponent {
state = {
value: '',
strategy: 'sync',
showDemo: true,
showClock: false,
};
// Random data for the chart
getStreamData(input) {
if (cachedData.has(input)) {
return cachedData.get(input);
}
const multiplier = input.length !== 0 ? input.length : 1;
const complexity =
(parseInt(window.location.search.substring(1), 10) / 100) * 25 || 25;
const data = _.range(5).map(t =>
_.range(complexity * multiplier).map((j, i) => {
return {
x: j,
y: (t + 1) * _.random(0, 255),
};
})
);
cachedData.set(input, data);
return data;
}
componentDidMount() {
window.addEventListener('keydown', e => {
if (e.key.toLowerCase() === '?') {
e.preventDefault();
this.setState(state => ({
showClock: !state.showClock,
}));
}
});
}
handleChartClick = e => {
if (this.state.showDemo) {
if (e.shiftKey) {
this.setState({showDemo: false});
}
return;
}
if (this.state.strategy !== 'async') {
this.setState(state => ({
showDemo: !state.showDemo,
}));
return;
}
if (this._ignoreClick) {
return;
}
this._ignoreClick = true;
// TODO: needing setTimeout here seems like a React bug.
setTimeout(() => {
unstable_deferredUpdates(() => {
this.setState({showDemo: true}, () => {
this._ignoreClick = false;
});
});
});
};
debouncedHandleChange = _.debounce(value => {
if (this.state.strategy === 'debounced') {
flushSync(() => {
this.setState({value: value});
});
}
}, 1000);
renderOption(strategy, label) {
const {strategy: currentStrategy} = this.state;
return (
<label className={strategy === currentStrategy ? 'selected' : null}>
<input
type="radio"
checked={strategy === currentStrategy}
onChange={() => this.setState({strategy})}
/>
{label}
</label>
);
}
handleChange = e => {
const value = e.target.value;
const {strategy} = this.state;
switch (strategy) {
case 'sync':
this.setState({value});
break;
case 'debounced':
this.debouncedHandleChange(value);
break;
case 'async':
// TODO: needing setTimeout here seems like a React bug.
setTimeout(() => {
unstable_deferredUpdates(() => {
this.setState({value});
});
});
break;
default:
break;
}
};
render() {
const Wrapper =
this.state.strategy === 'async' ? unstable_AsyncMode : 'div';
const {showClock} = this.state;
const data = this.getStreamData(this.state.value);
return (
<div className="container">
<div className="rendering">
{this.renderOption('sync', 'Synchronous')}
{this.renderOption('debounced', 'Debounced')}
{this.renderOption('async', 'Asynchronous')}
</div>
<input
className={'input ' + this.state.strategy}
placeholder="longer input → more components and DOM nodes"
defaultValue={this.state.input}
onChange={this.handleChange}
/>
<Wrapper>
<div className="demo" onClick={this.handleChartClick}>
{this.state.showDemo && (
<Charts data={data} onClick={this.handleChartClick} />
)}
<div style={{display: showClock ? 'block' : 'none'}}>
<Clock />
</div>
</div>
</Wrapper>
</div>
);
}
}
const container = document.getElementById('root');
render(<App />, container);

File diff suppressed because it is too large Load Diff

View File

@@ -57,7 +57,6 @@
"eslint-plugin-no-for-of-loops": "^1.0.0",
"eslint-plugin-react": "^6.7.1",
"eslint-plugin-react-internal": "link:./scripts/eslint-rules/",
"fbjs": "^0.8.16",
"fbjs-scripts": "^0.6.0",
"filesize": "^3.5.6",
"flow-bin": "^0.72.0",
@@ -78,8 +77,8 @@
"ncp": "^2.0.0",
"object-assign": "^4.1.1",
"platform": "^1.1.0",
"prettier": "1.11.1",
"prop-types": "^15.6.0",
"prettier": "1.13.7",
"prop-types": "^15.6.2",
"random-seed": "^0.3.0",
"react-lifecycles-compat": "^3.0.2",
"rimraf": "^2.6.1",
@@ -99,7 +98,7 @@
"yargs": "^6.3.0"
},
"devEngines": {
"node": "8.x || 9.x"
"node": "8.x || 9.x || 10.x"
},
"jest": {
"testRegex": "/scripts/jest/dont-run-jest-directly\\.js$"

View File

@@ -15,7 +15,7 @@ Other cases have **better long-term solutions**:
The main motivation for `create-subscription` is to provide a way for library authors to ensure compatibility with React's upcoming asynchronous rendering mode. `create-subscription` guarantees correctness in async mode, accounting for the subtle bugs and edge cases that a library author might otherwise miss.
However, it achieves correctness by sometimes de-opting to synchronous mode, obviating the benefits of async rendering. This is an inherent limitation of storing state outside of React's managed state queue and rendering in response to a change event.
However, [it achieves correctness by sometimes de-opting to synchronous mode](https://github.com/facebook/react/issues/13186#issuecomment-403959161), obviating the benefits of async rendering. This is an inherent limitation of storing state outside of React's managed state queue and rendering in response to a change event.
The effect of de-opting to sync mode is that the main thread may periodically be blocked (in the case of CPU-bound work), and placeholders may appear earlier than desired (in the case of IO-bound work).

View File

@@ -9,9 +9,6 @@
"index.js",
"cjs/"
],
"dependencies": {
"fbjs": "^0.8.16"
},
"peerDependencies": {
"react": "^16.3.0"
},

View File

@@ -450,7 +450,9 @@ describe('createSubscription', () => {
},
() => null,
);
}).toWarnDev('Subscription must specify a getCurrentValue function');
}).toWarnDev('Subscription must specify a getCurrentValue function', {
withoutStack: true,
});
});
it('should warn for invalid missing subscribe', () => {
@@ -461,7 +463,9 @@ describe('createSubscription', () => {
},
() => null,
);
}).toWarnDev('Subscription must specify a subscribe function');
}).toWarnDev('Subscription must specify a subscribe function', {
withoutStack: true,
});
});
it('should warn if subscribe does not return an unsubscribe method', () => {

View File

@@ -8,8 +8,8 @@
*/
import React from 'react';
import invariant from 'fbjs/lib/invariant';
import warning from 'fbjs/lib/warning';
import invariant from 'shared/invariant';
import warningWithoutStack from 'shared/warningWithoutStack';
type Unsubscribe = () => void;
@@ -21,7 +21,7 @@ export function createSubscription<Property, Value>(
getCurrentValue: (source: Property) => Value | void,
// Setup a subscription for the subscribable value in props, and return an unsubscribe function.
// Return false to indicate the property cannot be unsubscribed from (e.g. native Promises).
// Return empty function if the property cannot be unsubscribed from (e.g. native Promises).
// Due to the variety of change event types, subscribers should provide their own handlers.
// Those handlers should not attempt to update state though;
// They should call the callback() instead when a subscription changes.
@@ -36,11 +36,11 @@ export function createSubscription<Property, Value>(
}> {
const {getCurrentValue, subscribe} = config;
warning(
warningWithoutStack(
typeof getCurrentValue === 'function',
'Subscription must specify a getCurrentValue function',
);
warning(
warningWithoutStack(
typeof subscribe === 'function',
'Subscription must specify a subscribe function',
);
@@ -87,13 +87,13 @@ export function createSubscription<Property, Value>(
componentDidUpdate(prevProps, prevState) {
if (this.state.source !== prevState.source) {
this.unsubscribe(prevState);
this.unsubscribe();
this.subscribe();
}
}
componentWillUnmount() {
this.unsubscribe(this.state);
this.unsubscribe();
// Track mounted to avoid calling setState after unmounting
// For source like Promises that can't be unsubscribed from.
@@ -147,7 +147,7 @@ export function createSubscription<Property, Value>(
}
}
unsubscribe(state: State) {
unsubscribe() {
if (typeof this._unsubscribe === 'function') {
this._unsubscribe();
}

View File

@@ -7,7 +7,7 @@
*/
import ReactErrorUtils from 'shared/ReactErrorUtils';
import invariant from 'fbjs/lib/invariant';
import invariant from 'shared/invariant';
import {
injectEventPluginOrder,
@@ -167,7 +167,7 @@ export function getListener(inst: Fiber, registrationName: string) {
*/
function extractEvents(
topLevelType: TopLevelType,
targetInst: Fiber,
targetInst: null | Fiber,
nativeEvent: AnyNativeEvent,
nativeEventTarget: EventTarget,
): Array<ReactSyntheticEvent> | ReactSyntheticEvent | null {
@@ -229,7 +229,7 @@ export function runEventsInBatch(
export function runExtractedEventsInBatch(
topLevelType: TopLevelType,
targetInst: Fiber,
targetInst: null | Fiber,
nativeEvent: AnyNativeEvent,
nativeEventTarget: EventTarget,
) {

View File

@@ -14,7 +14,7 @@ import type {
PluginModule,
} from './PluginModuleType';
import invariant from 'fbjs/lib/invariant';
import invariant from 'shared/invariant';
type NamesToPlugins = {[key: PluginName]: PluginModule<AnyNativeEvent>};
type EventPluginOrder = null | Array<PluginName>;

View File

@@ -6,8 +6,8 @@
*/
import ReactErrorUtils from 'shared/ReactErrorUtils';
import invariant from 'fbjs/lib/invariant';
import warning from 'fbjs/lib/warning';
import invariant from 'shared/invariant';
import warningWithoutStack from 'shared/warningWithoutStack';
export let getFiberCurrentPropsFromNode = null;
export let getInstanceFromNode = null;
@@ -21,7 +21,7 @@ export const injection = {
getNodeFromInstance,
} = Injected);
if (__DEV__) {
warning(
warningWithoutStack(
getNodeFromInstance && getInstanceFromNode,
'EventPluginUtils.injection.injectComponentTree(...): Injected ' +
'module is missing getNodeFromInstance or getInstanceFromNode.',
@@ -39,14 +39,18 @@ if (__DEV__) {
const listenersIsArr = Array.isArray(dispatchListeners);
const listenersLen = listenersIsArr
? dispatchListeners.length
: dispatchListeners ? 1 : 0;
: dispatchListeners
? 1
: 0;
const instancesIsArr = Array.isArray(dispatchInstances);
const instancesLen = instancesIsArr
? dispatchInstances.length
: dispatchInstances ? 1 : 0;
: dispatchInstances
? 1
: 0;
warning(
warningWithoutStack(
instancesIsArr === listenersIsArr && instancesLen === listenersLen,
'EventPluginUtils: Invalid `event`.',
);

View File

@@ -10,7 +10,7 @@ import {
traverseTwoPhase,
traverseEnterLeave,
} from 'shared/ReactTreeTraversal';
import warning from 'fbjs/lib/warning';
import warningWithoutStack from 'shared/warningWithoutStack';
import {getListener} from './EventPluginHub';
import accumulateInto from './accumulateInto';
@@ -46,7 +46,7 @@ function listenerAtPhase(inst, event, propagationPhase: PropagationPhases) {
*/
function accumulateDirectionalDispatches(inst, phase, event) {
if (__DEV__) {
warning(inst, 'Dispatching inst must not be null');
warningWithoutStack(inst, 'Dispatching inst must not be null');
}
const listener = listenerAtPhase(inst, event, phase);
if (listener) {

View File

@@ -24,7 +24,7 @@ export type PluginModule<NativeEvent> = {
eventTypes: EventTypes,
extractEvents: (
topLevelType: TopLevelType,
targetInst: Fiber,
targetInst: null | Fiber,
nativeTarget: NativeEvent,
nativeEventTarget: EventTarget,
) => ?ReactSyntheticEvent,

View File

@@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
import invariant from 'fbjs/lib/invariant';
import invariant from 'shared/invariant';
import {
getInstanceFromNode,

View File

@@ -548,7 +548,9 @@ const ResponderEventPlugin = {
? eventTypes.responderStart
: isResponderTouchMove
? eventTypes.responderMove
: isResponderTouchEnd ? eventTypes.responderEnd : null;
: isResponderTouchEnd
? eventTypes.responderEnd
: null;
if (incrementalTouch) {
const gesture = ResponderSyntheticEvent.getPooled(
@@ -571,7 +573,9 @@ const ResponderEventPlugin = {
noResponderTouches(nativeEvent);
const finalTouch = isResponderTerminate
? eventTypes.responderTerminate
: isResponderRelease ? eventTypes.responderRelease : null;
: isResponderRelease
? eventTypes.responderRelease
: null;
if (finalTouch) {
const finalEvent = ResponderSyntheticEvent.getPooled(
finalTouch,

View File

@@ -7,8 +7,8 @@
* @flow
*/
import invariant from 'fbjs/lib/invariant';
import warning from 'fbjs/lib/warning';
import invariant from 'shared/invariant';
import warningWithoutStack from 'shared/warningWithoutStack';
import {isStartish, isMoveish, isEndish} from './ResponderTopLevelEventTypes';
@@ -95,7 +95,7 @@ function resetTouchRecord(touchRecord: TouchRecord, touch: Touch): void {
function getTouchIdentifier({identifier}: Touch): number {
invariant(identifier != null, 'Touch object is missing identifier.');
if (__DEV__) {
warning(
warningWithoutStack(
identifier <= MAX_TOUCH_BANK,
'Touch identifier %s is greater than maximum supported %s which causes ' +
'performance issues backfilling array locations for all of the indices.',
@@ -200,7 +200,7 @@ const ResponderTouchHistoryStore = {
}
if (__DEV__) {
const activeRecord = touchBank[touchHistory.indexOfSingleActiveTouch];
warning(
warningWithoutStack(
activeRecord != null && activeRecord.touchActive,
'Cannot find single active touch.',
);

View File

@@ -7,23 +7,11 @@
/* eslint valid-typeof: 0 */
import emptyFunction from 'fbjs/lib/emptyFunction';
import invariant from 'fbjs/lib/invariant';
import warning from 'fbjs/lib/warning';
import invariant from 'shared/invariant';
import warningWithoutStack from 'shared/warningWithoutStack';
let didWarnForAddedNewProperty = false;
const EVENT_POOL_SIZE = 10;
const shouldBeReleasedProperties = [
'dispatchConfig',
'_targetInst',
'nativeEvent',
'isDefaultPrevented',
'isPropagationStopped',
'_dispatchListeners',
'_dispatchInstances',
];
/**
* @interface Event
* @see http://www.w3.org/TR/DOM-Level-3-Events/
@@ -32,7 +20,9 @@ const EventInterface = {
type: null,
target: null,
// currentTarget is set when dispatching; no use in copying it here
currentTarget: emptyFunction.thatReturnsNull,
currentTarget: function() {
return null;
},
eventPhase: null,
bubbles: null,
cancelable: null,
@@ -43,6 +33,14 @@ const EventInterface = {
isTrusted: null,
};
function functionThatReturnsTrue() {
return true;
}
function functionThatReturnsFalse() {
return false;
}
/**
* Synthetic events are dispatched by event plugins, typically in response to a
* top-level event delegation handler.
@@ -72,6 +70,8 @@ function SyntheticEvent(
delete this.nativeEvent;
delete this.preventDefault;
delete this.stopPropagation;
delete this.isDefaultPrevented;
delete this.isPropagationStopped;
}
this.dispatchConfig = dispatchConfig;
@@ -103,11 +103,11 @@ function SyntheticEvent(
? nativeEvent.defaultPrevented
: nativeEvent.returnValue === false;
if (defaultPrevented) {
this.isDefaultPrevented = emptyFunction.thatReturnsTrue;
this.isDefaultPrevented = functionThatReturnsTrue;
} else {
this.isDefaultPrevented = emptyFunction.thatReturnsFalse;
this.isDefaultPrevented = functionThatReturnsFalse;
}
this.isPropagationStopped = emptyFunction.thatReturnsFalse;
this.isPropagationStopped = functionThatReturnsFalse;
return this;
}
@@ -124,7 +124,7 @@ Object.assign(SyntheticEvent.prototype, {
} else if (typeof event.returnValue !== 'unknown') {
event.returnValue = false;
}
this.isDefaultPrevented = emptyFunction.thatReturnsTrue;
this.isDefaultPrevented = functionThatReturnsTrue;
},
stopPropagation: function() {
@@ -144,7 +144,7 @@ Object.assign(SyntheticEvent.prototype, {
event.cancelBubble = true;
}
this.isPropagationStopped = emptyFunction.thatReturnsTrue;
this.isPropagationStopped = functionThatReturnsTrue;
},
/**
@@ -153,7 +153,7 @@ Object.assign(SyntheticEvent.prototype, {
* won't be added back into the pool.
*/
persist: function() {
this.isPersistent = emptyFunction.thatReturnsTrue;
this.isPersistent = functionThatReturnsTrue;
},
/**
@@ -161,7 +161,7 @@ Object.assign(SyntheticEvent.prototype, {
*
* @return {boolean} True if this should not be released, false otherwise.
*/
isPersistent: emptyFunction.thatReturnsFalse,
isPersistent: functionThatReturnsFalse,
/**
* `PooledClass` looks for `destructor` on each instance it releases.
@@ -179,24 +179,44 @@ Object.assign(SyntheticEvent.prototype, {
this[propName] = null;
}
}
for (let i = 0; i < shouldBeReleasedProperties.length; i++) {
this[shouldBeReleasedProperties[i]] = null;
}
this.dispatchConfig = null;
this._targetInst = null;
this.nativeEvent = null;
this.isDefaultPrevented = functionThatReturnsFalse;
this.isPropagationStopped = functionThatReturnsFalse;
this._dispatchListeners = null;
this._dispatchInstances = null;
if (__DEV__) {
Object.defineProperty(
this,
'nativeEvent',
getPooledWarningPropertyDefinition('nativeEvent', null),
);
Object.defineProperty(
this,
'isDefaultPrevented',
getPooledWarningPropertyDefinition(
'isDefaultPrevented',
functionThatReturnsFalse,
),
);
Object.defineProperty(
this,
'isPropagationStopped',
getPooledWarningPropertyDefinition(
'isPropagationStopped',
functionThatReturnsFalse,
),
);
Object.defineProperty(
this,
'preventDefault',
getPooledWarningPropertyDefinition('preventDefault', emptyFunction),
getPooledWarningPropertyDefinition('preventDefault', () => {}),
);
Object.defineProperty(
this,
'stopPropagation',
getPooledWarningPropertyDefinition('stopPropagation', emptyFunction),
getPooledWarningPropertyDefinition('stopPropagation', () => {}),
);
}
},
@@ -228,49 +248,6 @@ SyntheticEvent.extend = function(Interface) {
return Class;
};
/** Proxying after everything set on SyntheticEvent
* to resolve Proxy issue on some WebKit browsers
* in which some Event properties are set to undefined (GH#10010)
*/
if (__DEV__) {
const isProxySupported =
typeof Proxy === 'function' &&
// https://github.com/facebook/react/issues/12011
!Object.isSealed(new Proxy({}, {}));
if (isProxySupported) {
/*eslint-disable no-func-assign */
SyntheticEvent = new Proxy(SyntheticEvent, {
construct: function(target, args) {
return this.apply(target, Object.create(target.prototype), args);
},
apply: function(constructor, that, args) {
return new Proxy(constructor.apply(that, args), {
set: function(target, prop, value) {
if (
prop !== 'isPersistent' &&
!target.constructor.Interface.hasOwnProperty(prop) &&
shouldBeReleasedProperties.indexOf(prop) === -1
) {
warning(
didWarnForAddedNewProperty || target.isPersistent(),
"This synthetic event is reused for performance reasons. If you're " +
"seeing this, you're adding a new property in the synthetic event object. " +
'The property is never released. See ' +
'https://fb.me/react-event-pooling for more information.',
);
didWarnForAddedNewProperty = true;
}
target[prop] = value;
return true;
},
});
},
});
/*eslint-enable no-func-assign */
}
}
addEventPoolingTo(SyntheticEvent);
/**
@@ -307,7 +284,7 @@ function getPooledWarningPropertyDefinition(propName, getVal) {
function warn(action, result) {
const warningCondition = false;
warning(
warningWithoutStack(
warningCondition,
"This synthetic event is reused for performance reasons. If you're seeing this, " +
"you're %s `%s` on a released/nullified synthetic event. %s. " +
@@ -345,7 +322,7 @@ function releasePooledEvent(event) {
const EventConstructor = this;
invariant(
event instanceof EventConstructor,
'Trying to release an event instance into a pool of a different type.',
'Trying to release an event instance into a pool of a different type.',
);
event.destructor();
if (EventConstructor.eventPool.length < EVENT_POOL_SIZE) {

View File

@@ -7,7 +7,7 @@
* @flow
*/
import invariant from 'fbjs/lib/invariant';
import invariant from 'shared/invariant';
/**
* Accumulates items that must not be null or undefined.

View File

@@ -7,7 +7,7 @@
* @flow
*/
import invariant from 'fbjs/lib/invariant';
import invariant from 'shared/invariant';
/**
* Accumulates items that must not be null or undefined into the first one. This

View File

@@ -20,10 +20,9 @@
"dependencies": {
"art": "^0.10.1",
"create-react-class": "^15.6.2",
"fbjs": "^0.8.16",
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1",
"prop-types": "^15.6.0"
"prop-types": "^15.6.2"
},
"peerDependencies": {
"react": "^16.0.0"

View File

@@ -6,6 +6,7 @@
*/
import React from 'react';
import ReactVersion from 'shared/ReactVersion';
import * as ARTRenderer from 'react-reconciler/inline.art';
import Transform from 'art/core/transform';
import Mode from 'art/modes/current';
@@ -131,6 +132,13 @@ class Text extends React.Component {
}
}
ARTRenderer.injectIntoDevTools({
findFiberByHostInstance: () => null,
bundleType: __DEV__ ? 1 : 0,
version: ReactVersion,
rendererPackageName: 'react-art',
});
/** API */
export const ClippingRectangle = TYPES.CLIPPING_RECTANGLE;

View File

@@ -8,14 +8,18 @@
import * as ReactScheduler from 'shared/ReactScheduler';
import Transform from 'art/core/transform';
import Mode from 'art/modes/current';
import invariant from 'fbjs/lib/invariant';
import emptyObject from 'fbjs/lib/emptyObject';
import invariant from 'shared/invariant';
import {TYPES, EVENT_TYPES, childrenAsString} from './ReactARTInternals';
const pooledTransform = new Transform();
const NO_CONTEXT = {};
const UPDATE_SIGNAL = {};
if (__DEV__) {
Object.freeze(NO_CONTEXT);
Object.freeze(UPDATE_SIGNAL);
}
/** Helper Methods */
@@ -318,16 +322,20 @@ export function shouldDeprioritizeSubtree(type, props) {
}
export function getRootHostContext() {
return emptyObject;
return NO_CONTEXT;
}
export function getChildHostContext() {
return emptyObject;
return NO_CONTEXT;
}
export const scheduleDeferredCallback = ReactScheduler.scheduleWork;
export const cancelDeferredCallback = ReactScheduler.cancelScheduledWork;
export const scheduleTimeout = setTimeout;
export const cancelTimeout = clearTimeout;
export const noTimeout = -1;
export function shouldSetTextContent(type, props) {
return (
typeof props.children === 'string' || typeof props.children === 'number'

View File

@@ -28,6 +28,10 @@ const Circle = require('react-art/Circle');
const Rectangle = require('react-art/Rectangle');
const Wedge = require('react-art/Wedge');
// Isolate the noop renderer
jest.resetModules();
const ReactNoop = require('react-noop-renderer');
let Group;
let Shape;
let Surface;
@@ -71,6 +75,8 @@ describe('ReactART', () => {
Surface = ReactART.Surface;
TestComponent = class extends React.Component {
group = React.createRef();
render() {
const a = (
<Shape
@@ -104,7 +110,7 @@ describe('ReactART', () => {
return (
<Surface width={150} height={200}>
<Group ref="group">
<Group ref={this.group}>
{this.props.flipped ? [b, a, c] : [a, b, c]}
</Group>
</Surface>
@@ -121,7 +127,7 @@ describe('ReactART', () => {
it('should have the correct lifecycle state', () => {
let instance = <TestComponent />;
instance = ReactTestUtils.renderIntoDocument(instance);
const group = instance.refs.group;
const group = instance.group.current;
// Duck type test for an ART group
expect(typeof group.indicate).toBe('function');
});
@@ -260,15 +266,17 @@ describe('ReactART', () => {
let ref = null;
class Outer extends React.Component {
test = React.createRef();
componentDidMount() {
ref = this.refs.test;
ref = this.test.current;
}
render() {
return (
<Surface>
<Group>
<CustomShape ref="test" />
<CustomShape ref={this.test} />
</Group>
</Surface>
);
@@ -289,26 +297,28 @@ describe('ReactART', () => {
let ref = {};
class Outer extends React.Component {
test = React.createRef();
componentDidMount() {
ref = this.refs.test;
ref = this.test.current;
}
componentDidUpdate() {
ref = this.refs.test;
ref = this.test.current;
}
render() {
return (
<Surface>
<Group>
{this.props.mountCustomShape && <CustomShape ref="test" />}
{this.props.mountCustomShape && <CustomShape ref={this.test} />}
</Group>
</Surface>
);
}
}
ReactDOM.render(<Outer />, container);
expect(ref).not.toBeDefined();
expect(ref).toBe(null);
ReactDOM.render(<Outer mountCustomShape={true} />, container);
expect(ref.constructor).toBe(CustomShape);
});
@@ -348,7 +358,7 @@ describe('ReactART', () => {
const CurrentRendererContext = React.createContext(null);
function Yield(props) {
testRenderer.unstable_yield(props.value);
ReactNoop.yield(props.value);
return null;
}
@@ -366,19 +376,16 @@ describe('ReactART', () => {
// Using test renderer instead of the DOM renderer here because async
// testing APIs for the DOM renderer don't exist.
const testRenderer = ReactTestRenderer.create(
ReactNoop.render(
<CurrentRendererContext.Provider value="Test">
<Yield value="A" />
<Yield value="B" />
<LogCurrentRenderer />
<Yield value="C" />
</CurrentRendererContext.Provider>,
{
unstable_isAsync: true,
},
);
testRenderer.unstable_flushThrough(['A']);
ReactNoop.flushThrough(['A']);
ReactDOM.render(
<Surface>
@@ -393,7 +400,7 @@ describe('ReactART', () => {
expect(ops).toEqual([null, 'ART']);
ops = [];
expect(testRenderer.unstable_flushAll()).toEqual(['B', 'C']);
expect(ReactNoop.flush()).toEqual(['B', 'C']);
expect(ops).toEqual(['Test']);
});

View File

@@ -13,10 +13,9 @@
},
"homepage": "https://reactjs.org/",
"dependencies": {
"fbjs": "^0.8.16",
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1",
"prop-types": "^15.6.0"
"prop-types": "^15.6.2"
},
"peerDependencies": {
"react": "^16.0.0"

View File

@@ -79,7 +79,7 @@ describe('DOMPropertyOperations', () => {
it('should not remove empty attributes for special properties', () => {
const container = document.createElement('div');
ReactDOM.render(<input value="" />, container);
ReactDOM.render(<input value="" onChange={() => {}} />, container);
expect(container.firstChild.getAttribute('value')).toBe('');
expect(container.firstChild.value).toBe('');
});

View File

@@ -80,8 +80,8 @@ describe('ReactChildReconciler', () => {
'Keys should be unique so that components maintain their identity ' +
'across updates. Non-unique keys may cause children to be ' +
'duplicated and/or omitted — the behavior is unsupported and ' +
'could change in a future version.',
' in div (at **)\n' +
'could change in a future version.\n' +
' in div (at **)\n' +
' in Component (at **)\n' +
' in Parent (at **)\n' +
' in GrandParent (at **)',
@@ -127,8 +127,8 @@ describe('ReactChildReconciler', () => {
'Keys should be unique so that components maintain their identity ' +
'across updates. Non-unique keys may cause children to be ' +
'duplicated and/or omitted — the behavior is unsupported and ' +
'could change in a future version.',
' in div (at **)\n' +
'could change in a future version.\n' +
' in div (at **)\n' +
' in Component (at **)\n' +
' in Parent (at **)\n' +
' in GrandParent (at **)',

View File

@@ -43,19 +43,22 @@ describe('ReactComponentLifeCycle', () => {
const container = document.createElement('div');
expect(() =>
ReactDOM.render(<MyComponent x={1} />, container),
).toLowPriorityWarnDev([
'componentWillMount is deprecated and will be removed in the next major version. ' +
'Use componentDidMount instead. As a temporary workaround, ' +
'you can rename to UNSAFE_componentWillMount.' +
'\n\nPlease update the following components: MyComponent',
'componentWillReceiveProps is deprecated and will be removed in the next major version. ' +
'Use static getDerivedStateFromProps instead.' +
'\n\nPlease update the following components: MyComponent',
'componentWillUpdate is deprecated and will be removed in the next major version. ' +
'Use componentDidUpdate instead. As a temporary workaround, ' +
'you can rename to UNSAFE_componentWillUpdate.' +
'\n\nPlease update the following components: MyComponent',
]);
).toLowPriorityWarnDev(
[
'componentWillMount is deprecated and will be removed in the next major version. ' +
'Use componentDidMount instead. As a temporary workaround, ' +
'you can rename to UNSAFE_componentWillMount.' +
'\n\nPlease update the following components: MyComponent',
'componentWillReceiveProps is deprecated and will be removed in the next major version. ' +
'Use static getDerivedStateFromProps instead.' +
'\n\nPlease update the following components: MyComponent',
'componentWillUpdate is deprecated and will be removed in the next major version. ' +
'Use componentDidUpdate instead. As a temporary workaround, ' +
'you can rename to UNSAFE_componentWillUpdate.' +
'\n\nPlease update the following components: MyComponent',
],
{withoutStack: true},
);
// Dedupe check (update and instantiate new
ReactDOM.render(<MyComponent x={2} />, container);

View File

@@ -68,7 +68,8 @@ type ComponentLifeCycle =
* Mounted components have a DOM node representation and are capable of
* receiving new props.
*/
| 'MOUNTED' /**
| 'MOUNTED'
/**
* Unmounted components are inactive and cannot receive new props.
*/
| 'UNMOUNTED';
@@ -218,6 +219,7 @@ describe('ReactComponentLifeCycle', () => {
'This is a no-op, but it might indicate a bug in your application. ' +
'Instead, assign to `this.state` directly or define a `state = {};` ' +
'class property with the desired state in the StatefulComponent component.',
{withoutStack: true},
);
// Check deduplication; (no extra warnings should be logged).
@@ -248,7 +250,9 @@ describe('ReactComponentLifeCycle', () => {
expect(() => {
const instance = ReactTestUtils.renderIntoDocument(element);
expect(instance._isMounted()).toBeTruthy();
}).toWarnDev('Component is accessing isMounted inside its render()');
}).toWarnDev('Component is accessing isMounted inside its render()', {
withoutStack: true,
});
});
it('should correctly determine if a null component is mounted', () => {
@@ -275,7 +279,9 @@ describe('ReactComponentLifeCycle', () => {
expect(() => {
const instance = ReactTestUtils.renderIntoDocument(element);
expect(instance._isMounted()).toBeTruthy();
}).toWarnDev('Component is accessing isMounted inside its render()');
}).toWarnDev('Component is accessing isMounted inside its render()', {
withoutStack: true,
});
});
it('isMounted should return false when unmounted', () => {
@@ -313,7 +319,9 @@ describe('ReactComponentLifeCycle', () => {
expect(() => {
ReactTestUtils.renderIntoDocument(<Component />);
}).toWarnDev('Component is accessing findDOMNode inside its render()');
}).toWarnDev('Component is accessing findDOMNode inside its render()', {
withoutStack: true,
});
});
it('should carry through each of the phases of setup', () => {
@@ -379,6 +387,7 @@ describe('ReactComponentLifeCycle', () => {
instance = ReactDOM.render(<LifeCycleComponent />, container);
}).toWarnDev(
'LifeCycleComponent is accessing isMounted inside its render() function',
{withoutStack: true},
);
// getInitialState
@@ -674,6 +683,7 @@ describe('ReactComponentLifeCycle', () => {
const container = document.createElement('div');
expect(() => ReactDOM.render(<Component />, container)).toWarnDev(
'Unsafe legacy lifecycles will not be called for components using new component APIs.',
{withoutStack: true},
);
});
@@ -701,6 +711,7 @@ describe('ReactComponentLifeCycle', () => {
const container = document.createElement('div');
expect(() => ReactDOM.render(<Component value={1} />, container)).toWarnDev(
'Unsafe legacy lifecycles will not be called for components using new component APIs.',
{withoutStack: true},
);
ReactDOM.render(<Component value={2} />, container);
});
@@ -728,6 +739,7 @@ describe('ReactComponentLifeCycle', () => {
const container = document.createElement('div');
expect(() => ReactDOM.render(<Component value={1} />, container)).toWarnDev(
'Unsafe legacy lifecycles will not be called for components using new component APIs.',
{withoutStack: true},
);
ReactDOM.render(<Component value={2} />, container);
});
@@ -756,6 +768,7 @@ describe('ReactComponentLifeCycle', () => {
' componentWillUpdate\n\n' +
'The above lifecycles should be removed. Learn more about this warning here:\n' +
'https://fb.me/react-async-component-lifecycle-hooks',
{withoutStack: true},
);
class WillMount extends React.Component {
@@ -775,6 +788,7 @@ describe('ReactComponentLifeCycle', () => {
' UNSAFE_componentWillMount\n\n' +
'The above lifecycles should be removed. Learn more about this warning here:\n' +
'https://fb.me/react-async-component-lifecycle-hooks',
{withoutStack: true},
);
class WillMountAndUpdate extends React.Component {
@@ -796,6 +810,7 @@ describe('ReactComponentLifeCycle', () => {
' UNSAFE_componentWillUpdate\n\n' +
'The above lifecycles should be removed. Learn more about this warning here:\n' +
'https://fb.me/react-async-component-lifecycle-hooks',
{withoutStack: true},
);
class WillReceiveProps extends React.Component {
@@ -815,6 +830,7 @@ describe('ReactComponentLifeCycle', () => {
' componentWillReceiveProps\n\n' +
'The above lifecycles should be removed. Learn more about this warning here:\n' +
'https://fb.me/react-async-component-lifecycle-hooks',
{withoutStack: true},
);
});
@@ -841,6 +857,7 @@ describe('ReactComponentLifeCycle', () => {
' componentWillUpdate\n\n' +
'The above lifecycles should be removed. Learn more about this warning here:\n' +
'https://fb.me/react-async-component-lifecycle-hooks',
{withoutStack: true},
);
class WillMount extends React.Component {
@@ -859,6 +876,7 @@ describe('ReactComponentLifeCycle', () => {
' UNSAFE_componentWillMount\n\n' +
'The above lifecycles should be removed. Learn more about this warning here:\n' +
'https://fb.me/react-async-component-lifecycle-hooks',
{withoutStack: true},
);
class WillMountAndUpdate extends React.Component {
@@ -879,6 +897,7 @@ describe('ReactComponentLifeCycle', () => {
' UNSAFE_componentWillUpdate\n\n' +
'The above lifecycles should be removed. Learn more about this warning here:\n' +
'https://fb.me/react-async-component-lifecycle-hooks',
{withoutStack: true},
);
class WillReceiveProps extends React.Component {
@@ -897,6 +916,7 @@ describe('ReactComponentLifeCycle', () => {
' componentWillReceiveProps\n\n' +
'The above lifecycles should be removed. Learn more about this warning here:\n' +
'https://fb.me/react-async-component-lifecycle-hooks',
{withoutStack: true},
);
});
@@ -964,6 +984,7 @@ describe('ReactComponentLifeCycle', () => {
expect(() => ReactDOM.render(<MyComponent />, div)).toWarnDev(
'MyComponent.getDerivedStateFromProps(): A valid state object (or null) must ' +
'be returned. You have returned undefined.',
{withoutStack: true},
);
// De-duped
@@ -984,6 +1005,7 @@ describe('ReactComponentLifeCycle', () => {
expect(() => ReactDOM.render(<MyComponent />, div)).toWarnDev(
'MyComponent: Did not properly initialize state during construction. ' +
'Expected state to be an object, but it was undefined.',
{withoutStack: true},
);
// De-duped
@@ -1209,6 +1231,7 @@ describe('ReactComponentLifeCycle', () => {
expect(() => ReactDOM.render(<MyComponent value="bar" />, div)).toWarnDev(
'MyComponent.getSnapshotBeforeUpdate(): A snapshot value (or null) must ' +
'be returned. You have returned undefined.',
{withoutStack: true},
);
// De-duped
@@ -1229,6 +1252,7 @@ describe('ReactComponentLifeCycle', () => {
expect(() => ReactDOM.render(<MyComponent />, div)).toWarnDev(
'MyComponent: getSnapshotBeforeUpdate() should be used with componentDidUpdate(). ' +
'This component defines getSnapshotBeforeUpdate() only.',
{withoutStack: true},
);
// De-duped

View File

@@ -17,10 +17,50 @@ let ReactDOMServer;
let ReactCurrentOwner;
let ReactTestUtils;
let PropTypes;
let shallowEqual;
let shallowCompare;
describe('ReactCompositeComponent', () => {
const hasOwnProperty = Object.prototype.hasOwnProperty;
/**
* Performs equality by iterating through keys on an object and returning false
* when any key has values which are not strictly equal between the arguments.
* Returns true when the values of all keys are strictly equal.
*/
function shallowEqual(objA: mixed, objB: mixed): boolean {
if (Object.is(objA, objB)) {
return true;
}
if (
typeof objA !== 'object' ||
objA === null ||
typeof objB !== 'object' ||
objB === null
) {
return false;
}
const keysA = Object.keys(objA);
const keysB = Object.keys(objB);
if (keysA.length !== keysB.length) {
return false;
}
for (let i = 0; i < keysA.length; i++) {
if (
!hasOwnProperty.call(objB, keysA[i]) ||
!Object.is(objA[keysA[i]], objB[keysA[i]])
) {
return false;
}
}
return true;
}
function shallowCompare(instance, nextProps, nextState) {
return (
!shallowEqual(instance.props, nextProps) ||
!shallowEqual(instance.state, nextState)
);
}
beforeEach(() => {
jest.resetModules();
React = require('react');
@@ -30,14 +70,6 @@ describe('ReactCompositeComponent', () => {
.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner;
ReactTestUtils = require('react-dom/test-utils');
PropTypes = require('prop-types');
shallowEqual = require('fbjs/lib/shallowEqual');
shallowCompare = function(instance, nextProps, nextState) {
return (
!shallowEqual(instance.props, nextProps) ||
!shallowEqual(instance.state, nextState)
);
};
MorphingComponent = class extends React.Component {
state = {activated: false};
@@ -131,6 +163,7 @@ describe('ReactCompositeComponent', () => {
'render(): Calling ReactDOM.render() to hydrate server-rendered markup ' +
'will stop working in React v17. Replace the ReactDOM.render() call ' +
'with ReactDOM.hydrate() if you want React to attach to the server HTML.',
{withoutStack: true},
);
// New explicit API
@@ -247,6 +280,7 @@ describe('ReactCompositeComponent', () => {
'This is a no-op, but it might indicate a bug in your application. ' +
'Instead, assign to `this.state` directly or define a `state = {};` ' +
'class property with the desired state in the MyComponent component.',
{withoutStack: true},
);
// No additional warning should be recorded
@@ -271,6 +305,7 @@ describe('ReactCompositeComponent', () => {
'This is a no-op, but it might indicate a bug in your application. ' +
'Instead, assign to `this.state` directly or define a `state = {};` ' +
'class property with the desired state in the MyComponent component.',
{withoutStack: true},
);
// No additional warning should be recorded
@@ -431,9 +466,8 @@ describe('ReactCompositeComponent', () => {
instance = ReactDOM.render(<Component />, container);
}).toWarnDev(
'Cannot update during an existing state transition (such as within ' +
"`render` or another component's constructor). Render methods should " +
'be a pure function of props and state; constructor side-effects are ' +
'an anti-pattern, but can be moved to `componentWillMount`.',
'`render`). Render methods should be a pure function of props and state.',
{withoutStack: true},
);
// The setState call is queued and then executed as a second pass. This
@@ -481,6 +515,7 @@ describe('ReactCompositeComponent', () => {
instance = ReactDOM.render(<Component />, container);
}).toWarnDev(
'Warning: setState(...): Cannot call setState() inside getChildContext()',
{withoutStack: true},
);
expect(renderPasses).toBe(2);
@@ -557,6 +592,7 @@ describe('ReactCompositeComponent', () => {
expect(() => instance.setState({bogus: true})).toWarnDev(
'Warning: Component.shouldComponentUpdate(): Returned undefined instead of a ' +
'boolean value. Make sure to return true or false.',
{withoutStack: true},
);
});
@@ -573,6 +609,7 @@ describe('ReactCompositeComponent', () => {
'Warning: Component has a method called ' +
'componentDidUnmount(). But there is no such lifecycle method. ' +
'Did you mean componentWillUnmount()?',
{withoutStack: true},
);
});
@@ -591,6 +628,7 @@ describe('ReactCompositeComponent', () => {
'If you meant to update the state in response to changing props, ' +
'use componentWillReceiveProps(). If you meant to fetch data or ' +
'run side-effects or mutations after React has updated the UI, use componentDidUpdate().',
{withoutStack: true},
);
});
@@ -609,6 +647,7 @@ describe('ReactCompositeComponent', () => {
expect(() => ReactTestUtils.renderIntoDocument(<Component />)).toWarnDev(
'Warning: Setting defaultProps as an instance property on Component is not supported ' +
'and will be ignored. Instead, define defaultProps as a static property on Component.',
{withoutStack: true},
);
});
@@ -1099,6 +1138,7 @@ describe('ReactCompositeComponent', () => {
'triggering nested component updates from render is not allowed. If ' +
'necessary, trigger nested updates in componentDidUpdate.\n\nCheck the ' +
'render method of Outer.',
{withoutStack: true},
);
});
@@ -1390,6 +1430,7 @@ describe('ReactCompositeComponent', () => {
expect(() => ReactDOM.render(<Foo idx="qwe" />, container)).toWarnDev(
'Foo(...): When calling super() in `Foo`, make sure to pass ' +
"up the same props that your component's constructor was passed.",
{withoutStack: true},
);
});

View File

@@ -410,6 +410,7 @@ describe('ReactCompositeComponent-state', () => {
'Warning: Test.componentWillReceiveProps(): Assigning directly to ' +
"this.state is deprecated (except inside a component's constructor). " +
'Use setState instead.',
{withoutStack: true},
);
expect(ops).toEqual([
@@ -451,6 +452,7 @@ describe('ReactCompositeComponent-state', () => {
'Warning: Test.componentWillMount(): Assigning directly to ' +
"this.state is deprecated (except inside a component's constructor). " +
'Use setState instead.',
{withoutStack: true},
);
expect(ops).toEqual([

View File

@@ -9,11 +9,20 @@
'use strict';
let React = require('react');
let ReactDOM = require('react-dom');
const ReactTestUtils = require('react-dom/test-utils');
let React;
let ReactDOM;
let ReactDOMServer;
let ReactTestUtils;
describe('ReactDOM', () => {
beforeEach(() => {
jest.resetModules();
React = require('react');
ReactDOM = require('react-dom');
ReactDOMServer = require('react-dom/server');
ReactTestUtils = require('react-dom/test-utils');
});
// TODO: uncomment this test once we can run in phantom, which
// supports real submit events.
/*
@@ -434,38 +443,77 @@ describe('ReactDOM', () => {
}
});
it('warns when requestAnimationFrame is not polyfilled in the browser', () => {
it('warns when requestAnimationFrame is not polyfilled', () => {
const previousRAF = global.requestAnimationFrame;
try {
global.requestAnimationFrame = undefined;
delete global.requestAnimationFrame;
jest.resetModules();
expect(() => require('react-dom')).toWarnDev(
'React depends on requestAnimationFrame.',
"This browser doesn't support requestAnimationFrame.",
{withoutStack: true},
);
} finally {
global.requestAnimationFrame = previousRAF;
}
});
// We're just testing importing, not using it.
// It is important because even isomorphic components may import it.
it('can import findDOMNode in Node environment', () => {
const previousRAF = global.requestAnimationFrame;
const previousRIC = global.requestIdleCallback;
const prevWindow = global.window;
try {
global.requestAnimationFrame = undefined;
global.requestIdleCallback = undefined;
// Simulate the Node environment:
delete global.window;
jest.resetModules();
expect(() => {
require('react-dom');
}).not.toThrow();
} finally {
global.requestAnimationFrame = previousRAF;
global.requestIdleCallback = previousRIC;
global.window = prevWindow;
it('reports stacks with re-entrant renderToString() calls on the client', () => {
function Child2(props) {
return <span ariaTypo3="no">{props.children}</span>;
}
function App2() {
return (
<Child2>
{ReactDOMServer.renderToString(<blink ariaTypo2="no" />)}
</Child2>
);
}
function Child() {
return (
<span ariaTypo4="no">{ReactDOMServer.renderToString(<App2 />)}</span>
);
}
function ServerEntry() {
return ReactDOMServer.renderToString(<Child />);
}
function App() {
return (
<div>
<span ariaTypo="no" />
<ServerEntry />
<font ariaTypo5="no" />
</div>
);
}
const container = document.createElement('div');
expect(() => ReactDOM.render(<App />, container)).toWarnDev([
// ReactDOM(App > div > span)
'Invalid ARIA attribute `ariaTypo`. ARIA attributes follow the pattern aria-* and must be lowercase.\n' +
' in span (at **)\n' +
' in div (at **)\n' +
' in App (at **)',
// ReactDOM(App > div > ServerEntry) >>> ReactDOMServer(Child) >>> ReactDOMServer(App2) >>> ReactDOMServer(blink)
'Invalid ARIA attribute `ariaTypo2`. ARIA attributes follow the pattern aria-* and must be lowercase.\n' +
' in blink (at **)',
// ReactDOM(App > div > ServerEntry) >>> ReactDOMServer(Child) >>> ReactDOMServer(App2 > Child2 > span)
'Invalid ARIA attribute `ariaTypo3`. ARIA attributes follow the pattern aria-* and must be lowercase.\n' +
' in span (at **)\n' +
' in Child2 (at **)\n' +
' in App2 (at **)',
// ReactDOM(App > div > ServerEntry) >>> ReactDOMServer(Child > span)
'Invalid ARIA attribute `ariaTypo4`. ARIA attributes follow the pattern aria-* and must be lowercase.\n' +
' in span (at **)\n' +
' in Child (at **)',
// ReactDOM(App > div > font)
'Invalid ARIA attribute `ariaTypo5`. ARIA attributes follow the pattern aria-* and must be lowercase.\n' +
' in font (at **)\n' +
' in div (at **)\n' +
' in App (at **)',
]);
});
});

View File

@@ -554,39 +554,174 @@ describe('ReactDOMComponent', () => {
expect(stubStyle.color).toEqual('green');
});
it('should reject attribute key injection attack on markup', () => {
it('should reject attribute key injection attack on markup for regular DOM (SSR)', () => {
expect(() => {
for (let i = 0; i < 3; i++) {
const container = document.createElement('div');
const element = React.createElement(
const element1 = React.createElement(
'div',
{'blah" onclick="beevil" noise="hi': 'selected'},
null,
);
const element2 = React.createElement(
'div',
{'></div><script>alert("hi")</script>': 'selected'},
null,
);
let result1 = ReactDOMServer.renderToString(element1);
let result2 = ReactDOMServer.renderToString(element2);
expect(result1.toLowerCase()).not.toContain('onclick');
expect(result2.toLowerCase()).not.toContain('script');
}
}).toWarnDev([
'Warning: Invalid attribute name: `blah" onclick="beevil" noise="hi`',
'Warning: Invalid attribute name: `></div><script>alert("hi")</script>`',
]);
});
it('should reject attribute key injection attack on markup for custom elements (SSR)', () => {
expect(() => {
for (let i = 0; i < 3; i++) {
const element1 = React.createElement(
'x-foo-component',
{'blah" onclick="beevil" noise="hi': 'selected'},
null,
);
ReactDOM.render(element, container);
const element2 = React.createElement(
'x-foo-component',
{'></x-foo-component><script>alert("hi")</script>': 'selected'},
null,
);
let result1 = ReactDOMServer.renderToString(element1);
let result2 = ReactDOMServer.renderToString(element2);
expect(result1.toLowerCase()).not.toContain('onclick');
expect(result2.toLowerCase()).not.toContain('script');
}
}).toWarnDev(
}).toWarnDev([
'Warning: Invalid attribute name: `blah" onclick="beevil" noise="hi`',
);
'Warning: Invalid attribute name: `></x-foo-component><script>alert("hi")</script>`',
]);
});
it('should reject attribute key injection attack on update', () => {
it('should reject attribute key injection attack on mount for regular DOM', () => {
expect(() => {
for (let i = 0; i < 3; i++) {
const container = document.createElement('div');
ReactDOM.render(
React.createElement(
'div',
{'blah" onclick="beevil" noise="hi': 'selected'},
null,
),
container,
);
expect(container.firstChild.attributes.length).toBe(0);
ReactDOM.unmountComponentAtNode(container);
ReactDOM.render(
React.createElement(
'div',
{'></div><script>alert("hi")</script>': 'selected'},
null,
),
container,
);
expect(container.firstChild.attributes.length).toBe(0);
}
}).toWarnDev([
'Warning: Invalid attribute name: `blah" onclick="beevil" noise="hi`',
'Warning: Invalid attribute name: `></div><script>alert("hi")</script>`',
]);
});
it('should reject attribute key injection attack on mount for custom elements', () => {
expect(() => {
for (let i = 0; i < 3; i++) {
const container = document.createElement('div');
ReactDOM.render(
React.createElement(
'x-foo-component',
{'blah" onclick="beevil" noise="hi': 'selected'},
null,
),
container,
);
expect(container.firstChild.attributes.length).toBe(0);
ReactDOM.unmountComponentAtNode(container);
ReactDOM.render(
React.createElement(
'x-foo-component',
{'></x-foo-component><script>alert("hi")</script>': 'selected'},
null,
),
container,
);
expect(container.firstChild.attributes.length).toBe(0);
}
}).toWarnDev([
'Warning: Invalid attribute name: `blah" onclick="beevil" noise="hi`',
'Warning: Invalid attribute name: `></x-foo-component><script>alert("hi")</script>`',
]);
});
it('should reject attribute key injection attack on update for regular DOM', () => {
expect(() => {
for (let i = 0; i < 3; i++) {
const container = document.createElement('div');
const beforeUpdate = React.createElement('div', {}, null);
ReactDOM.render(beforeUpdate, container);
ReactDOM.render(
React.createElement(
'div',
{'blah" onclick="beevil" noise="hi': 'selected'},
null,
),
container,
);
expect(container.firstChild.attributes.length).toBe(0);
ReactDOM.render(
React.createElement(
'div',
{'></div><script>alert("hi")</script>': 'selected'},
null,
),
container,
);
expect(container.firstChild.attributes.length).toBe(0);
}
}).toWarnDev([
'Warning: Invalid attribute name: `blah" onclick="beevil" noise="hi`',
'Warning: Invalid attribute name: `></div><script>alert("hi")</script>`',
]);
});
it('should reject attribute key injection attack on update for custom elements', () => {
expect(() => {
for (let i = 0; i < 3; i++) {
const container = document.createElement('div');
const beforeUpdate = React.createElement('x-foo-component', {}, null);
ReactDOM.render(beforeUpdate, container);
const afterUpdate = React.createElement(
'x-foo-component',
{'blah" onclick="beevil" noise="hi': 'selected'},
null,
ReactDOM.render(
React.createElement(
'x-foo-component',
{'blah" onclick="beevil" noise="hi': 'selected'},
null,
),
container,
);
ReactDOM.render(afterUpdate, container);
expect(container.firstChild.attributes.length).toBe(0);
ReactDOM.render(
React.createElement(
'x-foo-component',
{'></x-foo-component><script>alert("hi")</script>': 'selected'},
null,
),
container,
);
expect(container.firstChild.attributes.length).toBe(0);
}
}).toWarnDev(
}).toWarnDev([
'Warning: Invalid attribute name: `blah" onclick="beevil" noise="hi`',
);
'Warning: Invalid attribute name: `></x-foo-component><script>alert("hi")</script>`',
]);
});
it('should update arbitrary attributes for tags containing dashes', () => {

View File

@@ -195,6 +195,7 @@ describe('ReactDOMComponentTree', () => {
'was rendered by React and is not a top-level container. You may ' +
'have accidentally passed in a React root node instead of its ' +
'container.',
{withoutStack: true},
);
});
@@ -211,6 +212,7 @@ describe('ReactDOMComponentTree', () => {
'component. If you intended to update the children of this node, ' +
'you should instead have the existing children update their state ' +
'and render the new components instead of calling ReactDOM.render.',
{withoutStack: true},
);
});
});

View File

@@ -256,6 +256,7 @@ describe('ReactDOMFiber', () => {
'and will be removed in React 17+. Update your code to use ' +
'ReactDOM.createPortal() instead. It has the exact same API, ' +
'but without the "unstable_" prefix.',
{withoutStack: true},
);
expect(portalContainer.innerHTML).toBe('<div>portal</div>');
expect(container.innerHTML).toBe('<div></div>');
@@ -979,8 +980,9 @@ describe('ReactDOMFiber', () => {
expect(() => ReactDOM.render(<Example />, container)).toWarnDev(
'Expected `onClick` listener to be a function, instead got `false`.\n\n' +
'If you used to conditionally omit it with onClick={condition && value}, ' +
'pass onClick={condition ? value : undefined} instead.\n',
' in div (at **)\n' + ' in Example (at **)',
'pass onClick={condition ? value : undefined} instead.\n' +
' in div (at **)\n' +
' in Example (at **)',
);
});
@@ -1100,6 +1102,7 @@ describe('ReactDOMFiber', () => {
'removed without using React. This is not supported and will ' +
'cause errors. Instead, call ReactDOM.unmountComponentAtNode ' +
'to empty a container.',
{withoutStack: true},
);
}).toThrowError();
});
@@ -1117,6 +1120,7 @@ describe('ReactDOMFiber', () => {
'removed without using React. This is not supported and will ' +
'cause errors. Instead, call ReactDOM.unmountComponentAtNode ' +
'to empty a container.',
{withoutStack: true},
);
});
@@ -1133,6 +1137,7 @@ describe('ReactDOMFiber', () => {
'removed without using React. This is not supported and will ' +
'cause errors. Instead, call ReactDOM.unmountComponentAtNode ' +
'to empty a container.',
{withoutStack: true},
);
});

File diff suppressed because it is too large Load Diff

View File

@@ -41,9 +41,7 @@ describe('ReactDOMOption', () => {
expect(() => {
node = ReactTestUtils.renderIntoDocument(el);
}).toWarnDev(
'<div> cannot appear as a child of <option>.\n' +
' in div (at **)\n' +
' in option (at **)',
'<div> cannot appear as a child of <option>.\n' + ' in option (at **)',
);
expect(node.innerHTML).toBe('1 2');
ReactTestUtils.renderIntoDocument(el);

View File

@@ -143,7 +143,9 @@ describe('ReactDOMRoot', () => {
<span />
</div>,
);
expect(jest.runAllTimers).toWarnDev('Extra attributes');
expect(jest.runAllTimers).toWarnDev('Extra attributes', {
withoutStack: true,
});
});
it('does not clear existing children', async () => {
@@ -358,9 +360,18 @@ describe('ReactDOMRoot', () => {
const root = ReactDOM.unstable_createRoot(container);
const batch = root.createBatch();
const InvalidType = undefined;
expect(() => batch.render(<InvalidType />)).toWarnDev([
'React.createElement: type is invalid',
]);
expect(() => batch.render(<InvalidType />)).toWarnDev(
['React.createElement: type is invalid'],
{withoutStack: true},
);
expect(() => batch.commit()).toThrow('Element type is invalid');
});
it('throws a good message on invalid containers', () => {
expect(() => {
ReactDOM.unstable_createRoot(<div>Hi</div>);
}).toThrow(
'unstable_createRoot(...): Target container is not a DOM element.',
);
});
});

View File

@@ -443,6 +443,37 @@ describe('ReactDOMSelect', () => {
expect(markup).not.toContain('<option selected="" value="gorilla"');
});
it('should support server-side rendering with dangerouslySetInnerHTML', () => {
const stub = (
<select defaultValue="giraffe">
<option
value="monkey"
dangerouslySetInnerHTML={{
__html: 'A monkey!',
}}>
{undefined}
</option>
<option
value="giraffe"
dangerouslySetInnerHTML={{
__html: 'A giraffe!',
}}>
{null}
</option>
<option
value="gorilla"
dangerouslySetInnerHTML={{
__html: 'A gorilla!',
}}
/>
</select>
);
const markup = ReactDOMServer.renderToString(stub);
expect(markup).toContain('<option selected="" value="giraffe"');
expect(markup).not.toContain('<option selected="" value="monkey"');
expect(markup).not.toContain('<option selected="" value="gorilla"');
});
it('should support server-side rendering with multiple', () => {
const stub = (
<select multiple={true} value={['giraffe', 'gorilla']} onChange={noop}>
@@ -514,6 +545,38 @@ describe('ReactDOMSelect', () => {
expect(node.options[2].selected).toBe(false); // gorilla
});
it('should support options with dynamic children', () => {
const container = document.createElement('div');
let node;
function App({value}) {
return (
<select value={value} ref={n => (node = n)} onChange={noop}>
<option key="monkey" value="monkey">
A monkey {value === 'monkey' ? 'is chosen' : null}!
</option>
<option key="giraffe" value="giraffe">
A giraffe {value === 'giraffe' && 'is chosen'}!
</option>
<option key="gorilla" value="gorilla">
A gorilla {value === 'gorilla' && 'is chosen'}!
</option>
</select>
);
}
ReactDOM.render(<App value="monkey" />, container);
expect(node.options[0].selected).toBe(true); // monkey
expect(node.options[1].selected).toBe(false); // giraffe
expect(node.options[2].selected).toBe(false); // gorilla
ReactDOM.render(<App value="giraffe" />, container);
expect(node.options[0].selected).toBe(false); // monkey
expect(node.options[1].selected).toBe(true); // giraffe
expect(node.options[2].selected).toBe(false); // gorilla
});
it('should warn if value is null', () => {
expect(() =>
ReactTestUtils.renderIntoDocument(
@@ -535,24 +598,21 @@ describe('ReactDOMSelect', () => {
});
it('should warn if selected is set on <option>', () => {
expect(() =>
ReactTestUtils.renderIntoDocument(
function App() {
return (
<select>
<option selected={true} />
<option selected={true} />
</select>,
),
).toWarnDev(
</select>
);
}
expect(() => ReactTestUtils.renderIntoDocument(<App />)).toWarnDev(
'Use the `defaultValue` or `value` props on <select> instead of ' +
'setting `selected` on <option>.',
);
ReactTestUtils.renderIntoDocument(
<select>
<option selected={true} />
<option selected={true} />
</select>,
);
ReactTestUtils.renderIntoDocument(<App />);
});
it('should warn if value is null and multiple is true', () => {
@@ -584,11 +644,20 @@ describe('ReactDOMSelect', () => {
<option value="gorilla">A gorilla!</option>
</select>
);
const node = ReactTestUtils.renderIntoDocument(stub);
const container = document.createElement('div');
document.body.appendChild(container);
ReactTestUtils.Simulate.change(node);
try {
const node = ReactDOM.render(stub, container);
expect(node.value).toBe('giraffe');
node.dispatchEvent(
new Event('change', {bubbles: true, cancelable: false}),
);
expect(node.value).toBe('giraffe');
} finally {
document.body.removeChild(container);
}
});
it('should warn if value and defaultValue props are specified', () => {
@@ -617,6 +686,13 @@ describe('ReactDOMSelect', () => {
);
});
it('should not warn about missing onChange in uncontrolled textareas', () => {
const container = document.createElement('div');
ReactDOM.render(<select />, container);
ReactDOM.unmountComponentAtNode(container);
ReactDOM.render(<select value={undefined} />, container);
});
it('should be able to safely remove select onChange', () => {
function changeView() {
ReactDOM.unmountComponentAtNode(container);

View File

@@ -12,7 +12,6 @@
let React;
let ReactDOM;
let ReactDOMSelection;
let invariant;
let getModernOffsetsFromPoints;
@@ -21,7 +20,6 @@ describe('ReactDOMSelection', () => {
React = require('react');
ReactDOM = require('react-dom');
ReactDOMSelection = require('../client/ReactDOMSelection');
invariant = require('fbjs/lib/invariant');
({getModernOffsetsFromPoints} = ReactDOMSelection);
});
@@ -68,10 +66,9 @@ describe('ReactDOMSelection', () => {
}
traverse(outerNode);
invariant(
start !== null && end !== null,
'Provided anchor/focus nodes were outside of root.',
);
if (start === null || end === null) {
throw new Error('Provided anchor/focus nodes were outside of root.');
}
return {start, end};
}

View File

@@ -885,6 +885,7 @@ describe('ReactDOMServerIntegration', () => {
'components) but got: object. You likely forgot to export your ' +
"component from the file it's defined in, or you might have mixed up " +
'default and named imports.',
{withoutStack: true},
);
await render(EmptyComponent);
},
@@ -906,6 +907,7 @@ describe('ReactDOMServerIntegration', () => {
'Warning: React.createElement: type is invalid -- expected a string ' +
'(for built-in components) or a class/function (for composite ' +
'components) but got: null.',
{withoutStack: true},
);
await render(NullComponent);
},
@@ -925,6 +927,7 @@ describe('ReactDOMServerIntegration', () => {
'components) but got: undefined. You likely forgot to export your ' +
"component from the file it's defined in, or you might have mixed up " +
'default and named imports.',
{withoutStack: true},
);
await render(UndefinedComponent);

View File

@@ -14,14 +14,12 @@ const ReactDOMServerIntegrationUtils = require('./utils/ReactDOMServerIntegratio
let React;
let ReactDOM;
let ReactDOMServer;
let ReactTestUtils;
function initModules() {
// Reset warning cache.
jest.resetModuleRegistry();
React = require('react');
ReactDOM = require('react-dom');
ReactTestUtils = require('react-dom/test-utils');
ReactDOMServer = require('react-dom/server');
// Make them available to the helpers.
@@ -35,6 +33,7 @@ const {
resetModules,
itRenders,
itClientRenders,
itThrowsWhenRendering,
renderIntoDom,
serverRender,
} = ReactDOMServerIntegrationUtils(initModules);
@@ -327,6 +326,81 @@ describe('ReactDOMServerIntegration', () => {
expectSelectValue(e, 'bar');
});
itRenders(
'a select with options that use dangerouslySetInnerHTML',
async render => {
const e = await render(
<select defaultValue="baz" value="bar" readOnly={true}>
<option
id="foo"
value="foo"
dangerouslySetInnerHTML={{
__html: 'Foo',
}}>
{undefined}
</option>
<option
id="bar"
value="bar"
dangerouslySetInnerHTML={{
__html: 'Bar',
}}>
{null}
</option>
<option
id="baz"
value="baz"
dangerouslySetInnerHTML={{
__html: 'Baz',
}}
/>
</select>,
1,
);
expectSelectValue(e, 'bar');
},
);
itThrowsWhenRendering(
'a select with option that uses dangerouslySetInnerHTML and 0 as child',
async render => {
await render(
<select defaultValue="baz" value="foo" readOnly={true}>
<option
id="foo"
value="foo"
dangerouslySetInnerHTML={{
__html: 'Foo',
}}>
{0}
</option>
</select>,
1,
);
},
'Can only set one of `children` or `props.dangerouslySetInnerHTML`.',
);
itThrowsWhenRendering(
'a select with option that uses dangerouslySetInnerHTML and empty string as child',
async render => {
await render(
<select defaultValue="baz" value="foo" readOnly={true}>
<option
id="foo"
value="foo"
dangerouslySetInnerHTML={{
__html: 'Foo',
}}>
{''}
</option>
</select>,
1,
);
},
'Can only set one of `children` or `props.dangerouslySetInnerHTML`.',
);
itRenders(
'a select value overriding defaultValue no matter the prop order',
async render => {
@@ -339,6 +413,36 @@ describe('ReactDOMServerIntegration', () => {
expectSelectValue(e, 'bar');
},
);
itRenders('a select option with flattened children', async render => {
const e = await render(
<select value="bar" readOnly={true}>
<option value="bar">A {'B'}</option>
</select>,
);
const option = e.options[0];
expect(option.childNodes.length).toBe(1);
expect(option.childNodes[0].nodeType).toBe(3);
expect(option.childNodes[0].nodeValue).toBe('A B');
});
itRenders(
'a select option with flattened children and a warning',
async render => {
const e = await render(
<select readOnly={true} value="bar">
<option value="bar">
{['Bar', false, 'Foo', <div key="1" />, 'Baz']}
</option>
</select>,
1,
);
expect(e.getAttribute('value')).toBe(null);
expect(e.getAttribute('defaultValue')).toBe(null);
expect(e.firstChild.innerHTML).toBe('BarFooBaz');
expect(e.firstChild.selected).toBe(true);
},
);
});
describe('user interaction', function() {
@@ -443,35 +547,63 @@ describe('ReactDOMServerIntegration', () => {
describe('user interaction with controlled inputs', function() {
itClientRenders('a controlled text input', async render => {
const setUntrackedValue = Object.getOwnPropertyDescriptor(
HTMLInputElement.prototype,
'value',
).set;
let changeCount = 0;
const e = await render(
<ControlledInput onChange={() => changeCount++} />,
);
expect(changeCount).toBe(0);
expect(e.value).toBe('Hello');
const container = e.parentNode;
document.body.appendChild(container);
// simulate a user typing.
e.value = 'Goodbye';
ReactTestUtils.Simulate.change(e);
try {
expect(changeCount).toBe(0);
expect(e.value).toBe('Hello');
expect(changeCount).toBe(1);
expect(e.value).toBe('Goodbye');
// simulate a user typing.
setUntrackedValue.call(e, 'Goodbye');
e.dispatchEvent(
new Event('input', {bubbles: true, cancelable: false}),
);
expect(changeCount).toBe(1);
expect(e.value).toBe('Goodbye');
} finally {
document.body.removeChild(container);
}
});
itClientRenders('a controlled textarea', async render => {
const setUntrackedValue = Object.getOwnPropertyDescriptor(
HTMLTextAreaElement.prototype,
'value',
).set;
let changeCount = 0;
const e = await render(
<ControlledTextArea onChange={() => changeCount++} />,
);
expect(changeCount).toBe(0);
expect(e.value).toBe('Hello');
const container = e.parentNode;
document.body.appendChild(container);
// simulate a user typing.
e.value = 'Goodbye';
ReactTestUtils.Simulate.change(e);
try {
expect(changeCount).toBe(0);
expect(e.value).toBe('Hello');
expect(changeCount).toBe(1);
expect(e.value).toBe('Goodbye');
// simulate a user typing.
setUntrackedValue.call(e, 'Goodbye');
e.dispatchEvent(
new Event('input', {bubbles: true, cancelable: false}),
);
expect(changeCount).toBe(1);
expect(e.value).toBe('Goodbye');
} finally {
document.body.removeChild(container);
}
});
itClientRenders('a controlled checkbox', async render => {
@@ -479,31 +611,53 @@ describe('ReactDOMServerIntegration', () => {
const e = await render(
<ControlledCheckbox onChange={() => changeCount++} />,
);
expect(changeCount).toBe(0);
expect(e.checked).toBe(true);
const container = e.parentNode;
document.body.appendChild(container);
// simulate a user typing.
e.checked = false;
ReactTestUtils.Simulate.change(e);
try {
expect(changeCount).toBe(0);
expect(e.checked).toBe(true);
expect(changeCount).toBe(1);
expect(e.checked).toBe(false);
// simulate a user clicking.
e.dispatchEvent(
new Event('click', {bubbles: true, cancelable: true}),
);
expect(changeCount).toBe(1);
expect(e.checked).toBe(false);
} finally {
document.body.removeChild(container);
}
});
itClientRenders('a controlled select', async render => {
const setUntrackedValue = Object.getOwnPropertyDescriptor(
HTMLSelectElement.prototype,
'value',
).set;
let changeCount = 0;
const e = await render(
<ControlledSelect onChange={() => changeCount++} />,
);
expect(changeCount).toBe(0);
expect(e.value).toBe('Hello');
const container = e.parentNode;
document.body.appendChild(container);
// simulate a user typing.
e.value = 'Goodbye';
ReactTestUtils.Simulate.change(e);
try {
expect(changeCount).toBe(0);
expect(e.value).toBe('Hello');
expect(changeCount).toBe(1);
expect(e.value).toBe('Goodbye');
// simulate a user typing.
setUntrackedValue.call(e, 'Goodbye');
e.dispatchEvent(
new Event('change', {bubbles: true, cancelable: false}),
);
expect(changeCount).toBe(1);
expect(e.value).toBe('Goodbye');
} finally {
document.body.removeChild(container);
}
});
});

View File

@@ -0,0 +1,68 @@
/**
* Copyright (c) 2013-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @emails react-core
*/
'use strict';
const ReactDOMServerIntegrationUtils = require('./utils/ReactDOMServerIntegrationTestUtils');
let React;
let ReactDOM;
let ReactDOMServer;
function initModules() {
// Reset warning cache.
jest.resetModuleRegistry();
React = require('react');
ReactDOM = require('react-dom');
ReactDOMServer = require('react-dom/server');
// Make them available to the helpers.
return {
ReactDOM,
ReactDOMServer,
};
}
const {resetModules, itRenders} = ReactDOMServerIntegrationUtils(initModules);
describe('ReactDOMServerIntegration', () => {
beforeEach(() => {
resetModules();
});
itRenders('a forwardedRef component and its children', async render => {
const FunctionComponent = ({label, forwardedRef}) => (
<div ref={forwardedRef}>{label}</div>
);
const WrappedFunctionComponent = React.forwardRef((props, ref) => (
<FunctionComponent {...props} forwardedRef={ref} />
));
const ref = React.createRef();
const element = await render(
<WrappedFunctionComponent ref={ref} label="Test" />,
);
const parent = element.parentNode;
const div = parent.childNodes[0];
expect(div.tagName).toBe('DIV');
expect(div.textContent).toBe('Test');
});
itRenders('a Profiler component and its children', async render => {
const element = await render(
<React.unstable_Profiler id="profiler" onRender={jest.fn()}>
<div>Test</div>
</React.unstable_Profiler>,
);
const parent = element.parentNode;
const div = parent.childNodes[0];
expect(div.tagName).toBe('DIV');
expect(div.textContent).toBe('Test');
});
});

View File

@@ -44,6 +44,7 @@ describe('ReactDOMServerLifecycles', () => {
ReactDOMServer.renderToString(<Component />),
).toLowPriorityWarnDev(
'Component: componentWillMount() is deprecated and will be removed in the next major version.',
{withoutStack: true},
);
});
@@ -62,6 +63,7 @@ describe('ReactDOMServerLifecycles', () => {
).toLowPriorityWarnDev(
'Warning: Component: componentWillMount() is deprecated and will be removed ' +
'in the next major version.',
{withoutStack: true},
);
// De-duped

View File

@@ -183,6 +183,7 @@ describe('ReactDOMServerLifecycles', () => {
expect(() => ReactDOMServer.renderToString(<Component />)).toWarnDev(
'Component.getDerivedStateFromProps(): A valid state object (or null) must ' +
'be returned. You have returned undefined.',
{withoutStack: true},
);
// De-duped
@@ -202,6 +203,7 @@ describe('ReactDOMServerLifecycles', () => {
expect(() => ReactDOMServer.renderToString(<Component />)).toWarnDev(
'Component: Did not properly initialize state during construction. ' +
'Expected state to be an object, but it was undefined.',
{withoutStack: true},
);
// De-duped
@@ -258,6 +260,7 @@ describe('ReactDOMServerLifecycles', () => {
'usually means you called setState() outside componentWillMount() on ' +
'the server. This is a no-op.\n\n' +
'Please check the code for the Outer component.',
{withoutStack: true},
);
});
});

View File

@@ -9,7 +9,7 @@
'use strict';
const emptyFunction = require('fbjs/lib/emptyFunction');
function emptyFunction() {}
describe('ReactDOMTextarea', () => {
let React;
@@ -97,7 +97,7 @@ describe('ReactDOMTextarea', () => {
});
it('should display `value` of number 0', () => {
const stub = <textarea value={0} />;
const stub = <textarea value={0} onChange={emptyFunction} />;
const node = renderTextarea(stub);
expect(node.value).toBe('0');
@@ -237,11 +237,25 @@ describe('ReactDOMTextarea', () => {
it('should properly control a value of number `0`', () => {
const stub = <textarea value={0} onChange={emptyFunction} />;
const node = renderTextarea(stub);
const setUntrackedValue = Object.getOwnPropertyDescriptor(
HTMLTextAreaElement.prototype,
'value',
).set;
node.value = 'giraffe';
ReactTestUtils.Simulate.change(node);
expect(node.value).toBe('0');
const container = document.createElement('div');
document.body.appendChild(container);
try {
const node = renderTextarea(stub, container);
setUntrackedValue.call(node, 'giraffe');
node.dispatchEvent(
new Event('input', {bubbles: true, cancelable: false}),
);
expect(node.value).toBe('0');
} finally {
document.body.removeChild(container);
}
});
it('should treat children like `defaultValue`', () => {
@@ -402,4 +416,11 @@ describe('ReactDOMTextarea', () => {
<textarea value="foo" defaultValue="bar" readOnly={true} />,
);
});
it('should not warn about missing onChange in uncontrolled textareas', () => {
const container = document.createElement('div');
ReactDOM.render(<textarea />, container);
ReactDOM.unmountComponentAtNode(container);
ReactDOM.render(<textarea value={undefined} />, container);
});
});

View File

@@ -0,0 +1,79 @@
/**
* Copyright (c) 2013-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @emails react-core
* @jest-environment node
*/
// This is a regression test for https://github.com/facebook/react/issues/13188.
// It reproduces a combination of conditions that led to a problem.
if (global.window) {
throw new Error('This test must run in a Node environment.');
}
// The issue only reproduced when React was loaded before JSDOM.
const React = require('react');
const ReactDOM = require('react-dom');
// Unlike other tests, we want to enable error logging.
// Note this is not a real Error prototype property,
// it's only set in our Jest environment.
// eslint-disable-next-line no-extend-native
Error.prototype.suppressReactErrorLogging = false;
// Initialize JSDOM separately.
// We don't use our normal JSDOM setup because we want to load React first.
const {JSDOM} = require('jsdom');
global.requestAnimationFrame = setTimeout;
global.cancelAnimationFrame = clearTimeout;
const jsdom = new JSDOM(`<div id="app-root"></div>`);
global.window = jsdom.window;
global.document = jsdom.window.document;
global.navigator = jsdom.window.navigator;
class Bad extends React.Component {
componentDidUpdate() {
throw new Error('no');
}
render() {
return null;
}
}
describe('ReactErrorLoggingRecovery', () => {
let originalConsoleError = console.error;
beforeEach(() => {
console.error = error => {
throw new Error('Buggy console.error');
};
});
afterEach(() => {
console.error = originalConsoleError;
});
it('should recover from errors in console.error', function() {
const div = document.createElement('div');
let didCatch = false;
try {
ReactDOM.render(<Bad />, div);
ReactDOM.render(<Bad />, div);
} catch (e) {
expect(e.message).toBe('no');
didCatch = true;
}
expect(didCatch).toBe(true);
ReactDOM.render(<span>Hello</span>, div);
expect(div.firstChild.textContent).toBe('Hello');
// Verify the console.error bug is surfaced
expect(() => {
jest.runAllTimers();
}).toThrow('Buggy console.error');
});
});

View File

@@ -63,6 +63,7 @@ describe('ReactMount', () => {
'Functions are not valid as a React child. ' +
'This may happen if you return a Component instead of <Component /> from render. ' +
'Or maybe you meant to call this function rather than return it.',
{withoutStack: true},
);
});
@@ -126,6 +127,7 @@ describe('ReactMount', () => {
expect(() => ReactDOM.hydrate(<div />, container)).toWarnDev(
'Did not expect server HTML to contain the text node " " in <container>.',
{withoutStack: true},
);
});
@@ -135,6 +137,7 @@ describe('ReactMount', () => {
expect(() => ReactDOM.hydrate(<div />, container)).toWarnDev(
'Did not expect server HTML to contain the text node " " in <container>.',
{withoutStack: true},
);
});
@@ -153,6 +156,7 @@ describe('ReactMount', () => {
ReactDOM.render(<div />, iFrame.contentDocument.body),
).toWarnDev(
'Rendering components directly into document.body is discouraged',
{withoutStack: true},
);
});
@@ -171,6 +175,7 @@ describe('ReactMount', () => {
).toWarnDev(
'Server: "This markup contains an nbsp entity:   server text" ' +
'Client: "This markup contains an nbsp entity:   client text"',
{withoutStack: true},
);
});
@@ -197,6 +202,7 @@ describe('ReactMount', () => {
'root component. If you intended to update the children of this node, ' +
'you should instead have the existing children update their state and ' +
'render the new components instead of calling ReactDOM.render.',
{withoutStack: true},
);
});
@@ -222,6 +228,7 @@ describe('ReactMount', () => {
expect(() => ReactDOMOther.unmountComponentAtNode(container)).toWarnDev(
"Warning: unmountComponentAtNode(): The node you're attempting to unmount " +
'was rendered by another copy of React.',
{withoutStack: true},
);
// Don't throw a warning if the correct React copy unmounts the node

View File

@@ -55,6 +55,7 @@ describe('ReactMount', () => {
'unmount was rendered by React and is not a top-level container. You ' +
'may have accidentally passed in a React root node instead of its ' +
'container.',
{withoutStack: true},
);
});
@@ -77,6 +78,7 @@ describe('ReactMount', () => {
'unmount was rendered by React and is not a top-level container. ' +
'Instead, have the parent component update its state and rerender in ' +
'order to remove this component.',
{withoutStack: true},
);
});
});

View File

@@ -209,8 +209,8 @@ describe('ReactMultiChild', () => {
'Keys should be unique so that components maintain their identity ' +
'across updates. Non-unique keys may cause children to be ' +
'duplicated and/or omitted — the behavior is unsupported and ' +
'could change in a future version.',
' in div (at **)\n' +
'could change in a future version.\n' +
' in div (at **)\n' +
' in WrapperComponent (at **)\n' +
' in div (at **)\n' +
' in Parent (at **)',
@@ -269,8 +269,8 @@ describe('ReactMultiChild', () => {
'Keys should be unique so that components maintain their identity ' +
'across updates. Non-unique keys may cause children to be ' +
'duplicated and/or omitted — the behavior is unsupported and ' +
'could change in a future version.',
' in div (at **)\n' +
'could change in a future version.\n' +
' in div (at **)\n' +
' in WrapperComponent (at **)\n' +
' in div (at **)\n' +
' in Parent (at **)',
@@ -294,6 +294,67 @@ describe('ReactMultiChild', () => {
);
});
it('should warn for using generators as children', () => {
function* Foo() {
yield <h1 key="1">Hello</h1>;
yield <h1 key="2">World</h1>;
}
const div = document.createElement('div');
expect(() => {
ReactDOM.render(<Foo />, div);
}).toWarnDev(
'Using Generators as children is unsupported and will likely yield ' +
'unexpected results because enumerating a generator mutates it. You may ' +
'convert it to an array with `Array.from()` or the `[...spread]` operator ' +
'before rendering. Keep in mind you might need to polyfill these features for older browsers.\n' +
' in Foo (at **)',
);
// Test de-duplication
ReactDOM.render(<Foo />, div);
});
it('should not warn for using generators in legacy iterables', () => {
const fooIterable = {
'@@iterator': function*() {
yield <h1 key="1">Hello</h1>;
yield <h1 key="2">World</h1>;
},
};
function Foo() {
return fooIterable;
}
const div = document.createElement('div');
ReactDOM.render(<Foo />, div);
expect(div.textContent).toBe('HelloWorld');
ReactDOM.render(<Foo />, div);
expect(div.textContent).toBe('HelloWorld');
});
it('should not warn for using generators in modern iterables', () => {
const fooIterable = {
[Symbol.iterator]: function*() {
yield <h1 key="1">Hello</h1>;
yield <h1 key="2">World</h1>;
},
};
function Foo() {
return fooIterable;
}
const div = document.createElement('div');
ReactDOM.render(<Foo />, div);
expect(div.textContent).toBe('HelloWorld');
ReactDOM.render(<Foo />, div);
expect(div.textContent).toBe('HelloWorld');
});
it('should reorder bailed-out children', () => {
class LetterInner extends React.Component {
render() {

View File

@@ -248,7 +248,7 @@ function prepareChildrenArray(childrenArray) {
return childrenArray;
}
function prepareChildrenIterable(childrenArray) {
function prepareChildrenLegacyIterable(childrenArray) {
return {
'@@iterator': function*() {
// eslint-disable-next-line no-for-of-loops/no-for-of-loops
@@ -259,9 +259,27 @@ function prepareChildrenIterable(childrenArray) {
};
}
function prepareChildrenModernIterable(childrenArray) {
return {
[Symbol.iterator]: function*() {
// eslint-disable-next-line no-for-of-loops/no-for-of-loops
for (const child of childrenArray) {
yield child;
}
},
};
}
function testPropsSequence(sequence) {
testPropsSequenceWithPreparedChildren(sequence, prepareChildrenArray);
testPropsSequenceWithPreparedChildren(sequence, prepareChildrenIterable);
testPropsSequenceWithPreparedChildren(
sequence,
prepareChildrenLegacyIterable,
);
testPropsSequenceWithPreparedChildren(
sequence,
prepareChildrenModernIterable,
);
}
describe('ReactMultiChildReconcile', () => {
@@ -311,7 +329,7 @@ describe('ReactMultiChildReconcile', () => {
);
});
it('should reset internal state if removed then readded in an iterable', () => {
it('should reset internal state if removed then readded in a legacy iterable', () => {
// Test basics.
const props = {
usernameToStatus: {
@@ -323,7 +341,7 @@ describe('ReactMultiChildReconcile', () => {
const parentInstance = ReactDOM.render(
<FriendsStatusDisplay
{...props}
prepareChildren={prepareChildrenIterable}
prepareChildren={prepareChildrenLegacyIterable}
/>,
container,
);
@@ -332,7 +350,7 @@ describe('ReactMultiChildReconcile', () => {
// Now remove the child.
ReactDOM.render(
<FriendsStatusDisplay prepareChildren={prepareChildrenIterable} />,
<FriendsStatusDisplay prepareChildren={prepareChildrenLegacyIterable} />,
container,
);
statusDisplays = parentInstance.getStatusDisplays();
@@ -342,7 +360,49 @@ describe('ReactMultiChildReconcile', () => {
ReactDOM.render(
<FriendsStatusDisplay
{...props}
prepareChildren={prepareChildrenIterable}
prepareChildren={prepareChildrenLegacyIterable}
/>,
container,
);
statusDisplays = parentInstance.getStatusDisplays();
expect(statusDisplays.jcw).toBeTruthy();
expect(statusDisplays.jcw.getInternalState()).not.toBe(
startingInternalState,
);
});
it('should reset internal state if removed then readded in a modern iterable', () => {
// Test basics.
const props = {
usernameToStatus: {
jcw: 'jcwStatus',
},
};
const container = document.createElement('div');
const parentInstance = ReactDOM.render(
<FriendsStatusDisplay
{...props}
prepareChildren={prepareChildrenModernIterable}
/>,
container,
);
let statusDisplays = parentInstance.getStatusDisplays();
const startingInternalState = statusDisplays.jcw.getInternalState();
// Now remove the child.
ReactDOM.render(
<FriendsStatusDisplay prepareChildren={prepareChildrenModernIterable} />,
container,
);
statusDisplays = parentInstance.getStatusDisplays();
expect(statusDisplays.jcw).toBeFalsy();
// Now reset the props that cause there to be a child
ReactDOM.render(
<FriendsStatusDisplay
{...props}
prepareChildren={prepareChildrenModernIterable}
/>,
container,
);

View File

@@ -39,6 +39,7 @@ describe('rendering React components at document', () => {
'render(): Calling ReactDOM.render() to hydrate server-rendered markup ' +
'will stop working in React v17. Replace the ReactDOM.render() call ' +
'with ReactDOM.hydrate() if you want React to attach to the server HTML.',
{withoutStack: true},
);
}
@@ -200,8 +201,11 @@ describe('rendering React components at document', () => {
'render(): Calling ReactDOM.render() to hydrate server-rendered markup ' +
'will stop working in React v17. Replace the ReactDOM.render() call ' +
'with ReactDOM.hydrate() if you want React to attach to the server HTML.',
{withoutStack: true},
);
}).toWarnDev('Warning: Text content did not match.');
}).toWarnDev('Warning: Text content did not match.', {
withoutStack: true,
});
});
it('should throw on full document render w/ no markup', () => {
@@ -369,6 +373,7 @@ describe('rendering React components at document', () => {
container.textContent = 'potato';
expect(() => ReactDOM.hydrate(<div>parsnip</div>, container)).toWarnDev(
'Expected server HTML to contain a matching <div> in <div>.',
{withoutStack: true},
);
expect(container.textContent).toBe('parsnip');
});
@@ -394,7 +399,9 @@ describe('rendering React components at document', () => {
expect(() =>
ReactDOM.hydrate(<Component text="Hello world" />, testDocument),
).toWarnDev('Warning: Text content did not match.');
).toWarnDev('Warning: Text content did not match.', {
withoutStack: true,
});
expect(testDocument.body.innerHTML).toBe('Hello world');
});
@@ -417,7 +424,9 @@ describe('rendering React components at document', () => {
// getTestDocument() has an extra <meta> that we didn't render.
expect(() =>
ReactDOM.hydrate(<Component text="Hello world" />, testDocument),
).toWarnDev('Did not expect server HTML to contain a <meta> in <head>.');
).toWarnDev('Did not expect server HTML to contain a <meta> in <head>.', {
withoutStack: true,
});
expect(testDocument.body.innerHTML).toBe('Hello world');
});

View File

@@ -174,6 +174,19 @@ describe('ReactDOMServer', () => {
(__DEV__ ? '\n in iframe (at **)' : ''),
);
});
it('should not crash on poisoned hasOwnProperty', () => {
let html;
expect(
() =>
(html = ReactDOMServer.renderToString(
<div hasOwnProperty="poison">
<span unknown="test" />
</div>,
)),
).toWarnDev(['React does not recognize the `hasOwnProperty` prop']);
expect(html).toContain('<span unknown="test">');
});
});
describe('renderToStaticMarkup', () => {
@@ -519,6 +532,7 @@ describe('ReactDOMServer', () => {
'Warning: setState(...): Can only update a mounting component.' +
' This usually means you called setState() outside componentWillMount() on the server.' +
' This is a no-op.\n\nPlease check the code for the Foo component.',
{withoutStack: true},
);
const markup = ReactDOMServer.renderToStaticMarkup(<Foo />);
@@ -546,6 +560,7 @@ describe('ReactDOMServer', () => {
'Warning: forceUpdate(...): Can only update a mounting component. ' +
'This usually means you called forceUpdate() outside componentWillMount() on the server. ' +
'This is a no-op.\n\nPlease check the code for the Baz component.',
{withoutStack: true},
);
const markup = ReactDOMServer.renderToStaticMarkup(<Baz />);
expect(markup).toBe('<div></div>');
@@ -643,4 +658,138 @@ describe('ReactDOMServer', () => {
ReactDOMServer.renderToString(<ClassWithRenderNotExtended />);
}).toThrow(TypeError);
});
// We're just testing importing, not using it.
// It is important because even isomorphic components may import it.
it('can import react-dom in Node environment', () => {
if (
typeof requestAnimationFrame !== 'undefined' ||
global.hasOwnProperty('requestAnimationFrame') ||
typeof requestIdleCallback !== 'undefined' ||
global.hasOwnProperty('requestIdleCallback') ||
typeof window !== 'undefined' ||
global.hasOwnProperty('window')
) {
// Don't remove this. This test is specifically checking
// what happens when they *don't* exist. It's useless otherwise.
throw new Error('Expected this test to run in a Node environment.');
}
jest.resetModules();
expect(() => {
require('react-dom');
}).not.toThrow();
});
it('includes a useful stack in warnings', () => {
function A() {
return null;
}
function B() {
return (
<font>
<C>
<span ariaTypo="no" />
</C>
</font>
);
}
class C extends React.Component {
render() {
return <b>{this.props.children}</b>;
}
}
function Child() {
return [<A key="1" />, <B key="2" />, <span ariaTypo2="no" />];
}
function App() {
return (
<div>
<section />
<span>
<Child />
</span>
</div>
);
}
expect(() => ReactDOMServer.renderToString(<App />)).toWarnDev([
'Invalid ARIA attribute `ariaTypo`. ARIA attributes follow the pattern aria-* and must be lowercase.\n' +
' in span (at **)\n' +
' in b (at **)\n' +
' in C (at **)\n' +
' in font (at **)\n' +
' in B (at **)\n' +
' in Child (at **)\n' +
' in span (at **)\n' +
' in div (at **)\n' +
' in App (at **)',
'Invalid ARIA attribute `ariaTypo2`. ARIA attributes follow the pattern aria-* and must be lowercase.\n' +
' in span (at **)\n' +
' in Child (at **)\n' +
' in span (at **)\n' +
' in div (at **)\n' +
' in App (at **)',
]);
});
it('reports stacks with re-entrant renderToString() calls', () => {
function Child2(props) {
return <span ariaTypo3="no">{props.children}</span>;
}
function App2() {
return (
<Child2>
{ReactDOMServer.renderToString(<blink ariaTypo2="no" />)}
</Child2>
);
}
function Child() {
return (
<span ariaTypo4="no">{ReactDOMServer.renderToString(<App2 />)}</span>
);
}
function App() {
return (
<div>
<span ariaTypo="no" />
<Child />
<font ariaTypo5="no" />
</div>
);
}
expect(() => ReactDOMServer.renderToString(<App />)).toWarnDev([
// ReactDOMServer(App > div > span)
'Invalid ARIA attribute `ariaTypo`. ARIA attributes follow the pattern aria-* and must be lowercase.\n' +
' in span (at **)\n' +
' in div (at **)\n' +
' in App (at **)',
// ReactDOMServer(App > div > Child) >>> ReactDOMServer(App2) >>> ReactDOMServer(blink)
'Invalid ARIA attribute `ariaTypo2`. ARIA attributes follow the pattern aria-* and must be lowercase.\n' +
' in blink (at **)',
// ReactDOMServer(App > div > Child) >>> ReactDOMServer(App2 > Child2 > span)
'Invalid ARIA attribute `ariaTypo3`. ARIA attributes follow the pattern aria-* and must be lowercase.\n' +
' in span (at **)\n' +
' in Child2 (at **)\n' +
' in App2 (at **)',
// ReactDOMServer(App > div > Child > span)
'Invalid ARIA attribute `ariaTypo4`. ARIA attributes follow the pattern aria-* and must be lowercase.\n' +
' in span (at **)\n' +
' in Child (at **)\n' +
' in div (at **)\n' +
' in App (at **)',
// ReactDOMServer(App > div > font)
'Invalid ARIA attribute `ariaTypo5`. ARIA attributes follow the pattern aria-* and must be lowercase.\n' +
' in font (at **)\n' +
' in div (at **)\n' +
' in App (at **)',
]);
});
});

View File

@@ -83,6 +83,7 @@ describe('ReactDOMServerHydration', () => {
'render(): Calling ReactDOM.render() to hydrate server-rendered markup ' +
'will stop working in React v17. Replace the ReactDOM.render() call ' +
'with ReactDOM.hydrate() if you want React to attach to the server HTML.',
{withoutStack: true},
);
expect(mountCount).toEqual(3);
expect(element.innerHTML).toBe(lastMarkup);
@@ -101,7 +102,9 @@ describe('ReactDOMServerHydration', () => {
element.innerHTML = lastMarkup;
expect(() => {
instance = ReactDOM.render(<TestComponent name="y" />, element);
}).toWarnDev('Text content did not match. Server: "x" Client: "y"');
}).toWarnDev('Text content did not match. Server: "x" Client: "y"', {
withoutStack: true,
});
expect(mountCount).toEqual(4);
expect(element.innerHTML.length > 0).toBe(true);
expect(element.innerHTML).not.toEqual(lastMarkup);
@@ -184,7 +187,9 @@ describe('ReactDOMServerHydration', () => {
element.innerHTML = lastMarkup;
expect(() => {
instance = ReactDOM.hydrate(<TestComponent name="y" />, element);
}).toWarnDev('Text content did not match. Server: "x" Client: "y"');
}).toWarnDev('Text content did not match. Server: "x" Client: "y"', {
withoutStack: true,
});
expect(mountCount).toEqual(4);
expect(element.innerHTML.length > 0).toBe(true);
expect(element.innerHTML).not.toEqual(lastMarkup);
@@ -247,6 +252,7 @@ describe('ReactDOMServerHydration', () => {
ReactDOM.hydrate(<button autoFocus={false}>client</button>, element),
).toWarnDev(
'Warning: Text content did not match. Server: "server" Client: "client"',
{withoutStack: true},
);
expect(element.firstChild.focus).not.toHaveBeenCalled();
@@ -263,4 +269,76 @@ describe('ReactDOMServerHydration', () => {
'Render them conditionally so that they only appear on the client render.',
);
});
it('should be able to render and hydrate Mode components', () => {
class ComponentWithWarning extends React.Component {
componentWillMount() {
// Expected warning
}
render() {
return 'Hi';
}
}
const markup = (
<React.StrictMode>
<ComponentWithWarning />
</React.StrictMode>
);
const element = document.createElement('div');
element.innerHTML = ReactDOMServer.renderToString(markup);
expect(element.textContent).toBe('Hi');
expect(() => ReactDOM.hydrate(markup, element)).toWarnDev(
'Please update the following components to use componentDidMount instead: ComponentWithWarning',
);
expect(element.textContent).toBe('Hi');
});
it('should be able to render and hydrate forwardRef components', () => {
const FunctionComponent = ({label, forwardedRef}) => (
<div ref={forwardedRef}>{label}</div>
);
const WrappedFunctionComponent = React.forwardRef((props, ref) => (
<FunctionComponent {...props} forwardedRef={ref} />
));
const ref = React.createRef();
const markup = <WrappedFunctionComponent ref={ref} label="Hi" />;
const element = document.createElement('div');
element.innerHTML = ReactDOMServer.renderToString(markup);
expect(element.textContent).toBe('Hi');
expect(ref.current).toBe(null);
ReactDOM.hydrate(markup, element);
expect(element.textContent).toBe('Hi');
expect(ref.current.tagName).toBe('DIV');
});
it('should be able to render and hydrate Profiler components', () => {
const callback = jest.fn();
const markup = (
<React.unstable_Profiler id="profiler" onRender={callback}>
<div>Hi</div>
</React.unstable_Profiler>
);
const element = document.createElement('div');
element.innerHTML = ReactDOMServer.renderToString(markup);
expect(element.textContent).toBe('Hi');
expect(callback).not.toHaveBeenCalled();
ReactDOM.hydrate(markup, element);
expect(element.textContent).toBe('Hi');
if (__DEV__) {
expect(callback).toHaveBeenCalledTimes(1);
const [id, phase] = callback.mock.calls[0];
expect(id).toBe('profiler');
expect(phase).toBe('mount');
} else {
expect(callback).toHaveBeenCalledTimes(0);
}
});
});

View File

@@ -111,6 +111,7 @@ describe('ReactStatelessComponent', () => {
).toWarnDev(
'StatelessComponentWithChildContext: Stateless ' +
'functional components do not support getDerivedStateFromProps.',
{withoutStack: true},
);
});
@@ -133,6 +134,7 @@ describe('ReactStatelessComponent', () => {
).toWarnDev(
'StatelessComponentWithChildContext(...): childContextTypes cannot ' +
'be defined on a functional component.',
{withoutStack: true},
);
});

View File

@@ -53,6 +53,16 @@ describe('ReactTestUtils', () => {
MockedComponent.prototype.render = jest.fn();
// Patch it up so it returns its children.
expect(() =>
ReactTestUtils.mockComponent(MockedComponent),
).toLowPriorityWarnDev(
'ReactTestUtils.mockComponent() is deprecated. ' +
'Use shallow rendering or jest.mock() instead.\n\n' +
'See https://fb.me/test-utils-mock-component for more information.',
{withoutStack: true},
);
// De-duplication check
ReactTestUtils.mockComponent(MockedComponent);
const container = document.createElement('div');

View File

@@ -1226,6 +1226,7 @@ describe('ReactUpdates', () => {
const container = document.createElement('div');
expect(() => ReactDOM.render(<Foo />, container)).toWarnDev(
'Cannot update during an existing state transition',
{withoutStack: true},
);
expect(ops).toEqual(['base: 0, memoized: 0', 'base: 1, memoized: 1']);
});
@@ -1364,4 +1365,37 @@ describe('ReactUpdates', () => {
ReactDOM.render(<NonTerminating />, container);
}).toThrow('Maximum');
});
it('can schedule ridiculously many updates within the same batch without triggering a maximum update error', () => {
const subscribers = [];
class Child extends React.Component {
state = {value: 'initial'};
componentDidMount() {
subscribers.push(this);
}
render() {
return null;
}
}
class App extends React.Component {
render() {
const children = [];
for (let i = 0; i < 1200; i++) {
children.push(<Child key={i} />);
}
return children;
}
}
const container = document.createElement('div');
ReactDOM.render(<App />, container);
ReactDOM.unstable_batchedUpdates(() => {
subscribers.forEach(s => {
s.setState({value: 'update'});
});
});
});
});

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