Compare commits

..

196 Commits

Author SHA1 Message Date
Jason Quense
efe8e670af add isNode to react-is 2018-06-27 12:38:51 -04: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
Dan Abramov
9725065eb4 Update bundle sizes for 16.4.1 release 2018-06-13 17:20:35 +01:00
Dan Abramov
a5957bf296 Update error codes for 16.4.1 release 2018-06-13 17:20:35 +01:00
Dan Abramov
0b87b27906 Updating package versions for release 16.4.1 2018-06-13 17:16:10 +01:00
Dan Abramov
65eb6b94ac Updating yarn.lock file for 16.4.1 release 2018-06-13 17:13:29 +01:00
Dan Abramov
c469e3b422 Add unreleased changelog 2018-06-13 16:57:46 +01:00
Philipp Spieß
036ae3c6e2 Use native event dispatching instead of Simulate or SimulateNative (#13023)
* Use native event dispatching instead of Simulate or SimulateNative

In #12629 @gaearon suggested that it would be better to drop usage of
`ReactTestUtils.Simulate` and `ReactTestUtils.SimulateNative`. In this
PR I’m attempting at removing it from a lot of places with only a few
leftovers.

Those leftovers can be categorized into three groups:

1. Anything that tests that `SimulateNative` throws. This is a property
   that native event dispatching doesn’t have so I can’t convert that
   easily. Affected test suites: `EventPluginHub-test`,
   `ReactBrowserEventEmitter-test`.
2. Anything that tests `ReactTestUtils` directly. Affected test suites:
   `ReactBrowserEventEmitter-test` (this file has one test that reads
    "should have mouse enter simulated by test utils"),
    `ReactTestUtils-test`.
3. Anything that dispatches a `change` event. The reason here goes a bit
   deeper and is rooted in the way we shim onChange. Usually when using
   native event dispatching, you would set the node’s `.value` and then
   dispatch the event. However inside [`inputValueTracking.js`][] we
   install a setter on the node’s `.value` that will ignore the next
   `change` event (I found [this][near-perfect-oninput-shim] article
   from Sophie that explains that this is to avoid onChange when
   updating the value via JavaScript).

All remaining usages of `Simulate` or `SimulateNative` can be avoided
by mounting the containers inside the `document` and dispatching native
events.

Here some remarks:

1. I’m using `Element#click()` instead of `dispatchEvent`. In the jsdom
   changelog I read that `click()` now properly sets the correct values
   (you can also verify it does the same thing by looking at the
   [source][jsdom-source]).
2. I had to update jsdom in order to get `TouchEvent` constructors
   working (and while doing so also updated jest). There was one
   unexpected surprise: `ReactScheduler-test` was relying on not having
   `window.performance` available. I’ve recreated the previous
   environment by deleting this property from the global object.
3. I was a bit confused that `ReactTestUtils.renderIntoDocument()` does
   not render into the document 🤷‍

[`inputValueTracking.js`]: 392530104c/packages/react-dom/src/client/inputValueTracking.js (L79)
[near-perfect-oninput-shim]: https://sophiebits.com/2013/06/18/a-near-perfect-oninput-shim-for-ie-8-and-9.html
[jsdom-source]: 45b77f5d21/lib/jsdom/living/nodes/HTMLElement-impl.js (L43-L76)

* Make sure contains are unlinked from the document even if the test fails

* Remove unnecessary findDOMNode calls
2018-06-13 12:41:23 +01:00
Rafał Ruciński
945fc1bfce Call gDSFP with the right state in react-test-render (#13030)
* Call gDSFP with the right state in react-test-render

* Change the test
2018-06-12 23:36:50 +01:00
Flarnie Marchan
392530104c Remove feature flag around 'getDerivedStateFromProps' bug fix (#13022)
**what is the change?:**
Basically undoes 4b2e65d32e (diff-904ceabd8a1e9a07ab1d876d843d62e1)

**why make this change?:**
We rolled out this fix internally and in open source weeks ago, and now
we're cleaning up.

**test plan:**
Ran tests and lint, and really we have been testing this because the
flag is open internally as of last week or so.

**issue:**
Internal task T29948812 has some info.
2018-06-11 16:31:07 -07:00
Dan Abramov
1594409fab Scheduler depends on common packages (#13020) 2018-06-11 22:13:05 +01:00
Brian Vaughn
d5c11193e2 Added production profiling bundle type (#12886)
* Added profiling bundle
* Turned profiling on for React Fabric OSS profiling and dev bundles
* Added new global var "__PROFILE__" for profiling DCE
2018-06-11 13:16:27 -07:00
Dan Abramov
ec60457bcd Popping context is O(1) in SSR (#13019) 2018-06-11 20:52:39 +01:00
Dan Abramov
30bc8ef792 Allow multiple root children in test renderer traversal API (#13017) 2018-06-11 20:03:51 +01:00
Philipp Spieß
d480782c41 Don’t error when returning an empty Fragment (#12966)
* Don’t error when returning an empty Fragment

When a fragment is reconciled, we directly move onto it’s children.
Since an empty `<React.Fragment/>` will have children of `undefined`,
this would always throw.

To fix this, we bail out in those cases.

* Test the update path as well

* Reuse existing code path

* An even more explicit solution that also fixes Flow
2018-06-11 14:43:30 +01:00
Nathan Hunzaker
4ac6f133af Fallback to event.srcElement for IE9 (#12976)
It looks like we accidentally removed a fallback condition for the
event target in IE9 when we dropped some support for IE8. This commit
adds the event target specific support code back to getEventTarget.js

Fixes #12506
2018-06-11 14:35:42 +01:00
Eric Soderberg
23be4102df Fixed an issue with nested contexts unwinding when server rendering. Issue #12984 (#12985)
* Fixed an issue with nested contexts unwinding when server rendering. GitHub issue #12984

* Fixed an issue with search direction and stricter false checking

* Use decrement infix operator

* Streamlined existence checks

* Streamlined assignment. Removed redundant comment. Use null for array values

* Made prettier

* Relaxed type checking and improved comment

* Improve test coverage
2018-06-11 14:25:18 +01:00
Nathan Hunzaker
d0d4280640 Remove old reference to inst._wrapperState (#12987)
This commit removes a reference to inst._wrapperState, which was the
old way of tracking input state in the stack renderer.

This means we no longer need to pass the instance into the associated
function, allowing us to eliminate an exception for IE (and a TODO).
2018-06-11 14:16:50 +01:00
Jifa Jiang
c78957eac8 Fix an SVG focusing crash in IE11 (#12996)
* revert #11800

because #12763

* use try/catch for SVG in IE11

* use focusNode(element) when element.focus isn't a function.

* revert #11800
2018-06-11 03:39:29 +01:00
Nathan Quarles
bfb12ebb52 delete a couple of redundant lines in performWorkOnRoot() in ReactFiberScheduler.js (#13003) 2018-06-09 22:56:21 +01:00
Dan Abramov
394b17eede Update custom renderer docs 2018-06-09 22:19:12 +01:00
Ivan Babak
188c4252a2 Fix react-dom ReferenceError requestAnimationFrame in non-browser env (#13000) (#13001)
* Fix react-dom ReferenceError requestAnimationFrame in non-browser env (#13000)

The https://github.com/facebook/react/pull/12931 ( 79a740c6e3 ) broke the server-side rendering: in the `fixtures/ssr` the following error appeared from the server-side when `localhost:3000` is requested:
```
ReferenceError: requestAnimationFrame is not defined
    at /__CENSORED__/react/build/node_modules/react-dom/cjs/react-dom.development.js:5232:34
    at Object.<anonymous> (/__CENSORED__/react/build/node_modules/react-dom/cjs/react-dom.development.js:17632:5)
    at Module._compile (module.js:624:30)
    at Module._extensions..js (module.js:635:10)
    at Object.require.extensions.(anonymous function) [as .js] (/__CENSORED__/react/fixtures/ssr/node_modules/babel-register/lib/node.js:152:7)
    at Module.load (module.js:545:32)
    at tryModuleLoad (module.js:508:12)
    at Function.Module._load (module.js:500:3)
    at Module.require (module.js:568:17)
    at require (internal/module.js:11:18)
```

The exception pointed to this line:
```js
// We capture a local reference to any global, in case it gets polyfilled after
// this module is initially evaluated.
// We want to be using a consistent implementation.
const localRequestAnimationFrame = requestAnimationFrame;
```

**Test plan**

1. In `react` repo root, `yarn && yarn build`.
2. In `fixtures/ssr`, `yarn && yarn start`,
3. In browser, go to `http://localhost:3000`.
4. Observe the fixture page, not the exception message.

* Move the requestAnimationFrameForReact check and warning to callsites (#13000)

According to the comment by @gaearon: https://github.com/facebook/react/pull/13001#issuecomment-395803076

* Use `invariant` instead of `throw new Error`, use the same message (#13000)

According to the comment by @gaearon: https://github.com/facebook/react/pull/13001#discussion_r194133355
2018-06-08 23:12:20 +01:00
Ivan Babak
d3e0a3aaf3 Fix jest/matchers/toWarnDev expected, actual order for jest-diff (#12285) (#12288)
`toWarnDev` calls `jestDiff(a, b)` which calls `diffStrings(a, b)` where by default `a` is annotated as `'Expected'` (green), `b` as `'Received'` (red).

So the first argument passed into `jestDiff` should be the expected message, the second should be the actual message.
It was vice versa previously.

- 457776b288/packages/jest-diff/src/index.js (L54)
- 457776b288/packages/jest-diff/src/index.js (L93)
- 457776b288/packages/jest-diff/src/diff_strings.js (L249-L251)
2018-06-08 13:18:22 +01:00
Nathan Quarles
9cf3733a9a update comment in computeAsyncExpiration() to reflect code (#12994) 2018-06-07 21:12:08 +01:00
Demian Dekoninck
52fbe7612e use --frozen-lockfile in AppVeyor (#12950) 2018-06-06 15:19:33 +02:00
Ende93
c5a733e1e3 Fix links of docs on the comment (#12795) 2018-06-05 08:03:03 -04:00
Maxime Nory
36546b5137 Set the correct initial value on input range (#12939)
* Set the correct initial value on input range

* Add description and update value diff check for input range

* add isHydrating argument and tests

* update node value according to isHydrating
2018-05-31 17:23:26 -04:00
Héctor Ramos
65ab53694f Update token (#12956) 2018-05-31 21:36:55 +01:00
Flarnie Marchan
15767a8f8f [scheduler] 5/n Error handling in scheduler (#12920)
* Initial failing unit test for error handling in schedule

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

**why make this change?:**
Adding tests for the error handling behavior we are about to add. This
test is failing, which gives us the chance to make it pass.

Wrote skeletons of some other tests to add.

Unit testing this way is really hacky, and I'm also adding to the
fixture to test this in the real browser environment.

**test plan:**
Ran new test, saw it fail!

* Add fixture for testing error handling in scheduler

**what is the change?:**
Added a fixture which does the following -
logs in the console to show what happens when you use
`requestAnimationFrame` to schedule a series of callbacks and some of
them throw errors.

Then does the same actions with the `scheduler` and verifies that it
behaves in a similar way.

Hard to really verify the errors get thrown at the proper time without
looking at the console.

**why make this change?:**
We want the most authentic, accurate test of how errors are handled in
the scheduler. That's what this fixture should be.

**test plan:**
Manually verified that this test does what I expect - right now it's
failing but follow up commits will fix that.

* Handle errors in scheduler

**what is the change?:**
We set a flag before calling any callback, and then use a 'try/finally'
block to wrap it. Note that we *do not* catch the error, if one is
thrown. But, we only unset the flag after the callback successfully
finishes.

If we reach the 'finally' block and the flag was not unset, then it
means an error was thrown.

In that case we start a new postMessage callback, to finish calling any
other pending callbacks if there is time.

**why make this change?:**
We need to make sure that an error thrown from one callback doesn't stop
other callbacks from firing, but we also don't want to catch or swallow
the error because we want engineers to still be able to log and debug
errors.

**test plan:**
New tests added are passing, and we verified that they fail without this
change.

* Add more tests for error handling in scheduler

**what is the change?:**
Added tests for more situations where error handling may come up.

**why make this change?:**
To get additional protection against this being broken in the future.

**test plan:**
Ran new tests and verified that they fail when error handling fails.

* callSafely -> callUnsafely

* Fix bugs with error handling in schedule

**what is the change?:**
- ensure that we properly remove the callback from the linked list, even
if it throws an error and is timed out.
- ensure that you can call 'cancelScheduledWork' more than once and it
is idempotent.

**why make this change?:**
To fix bugs :)

**test plan:**
Existing tests pass, and we'll add more tests in a follow up commit.

* Unit tests for error handling with timed out callbacks

**what is the change?:**
More unit tests, to cover behavior which we missed; error handling of
timed out callbacks.

**why make this change?:**
To protect the future!~

**test plan:**
Run the new tests.

* Adds fixture to test timed out callbacks with scheduler

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

In the other error handling fixture we compare 'scheduleWork' error
handling to 'requestAnimationFrame' and try to get as close as possible.
There is no 'timing out' feature with 'requestAnimationFrame' but
effectively the 'timing out' feature changes the order in which things
are called. So we just changed the order in the 'requestAnimationFrame'
version and that works well for illustrating the behavior we expect in
the 'scheduleWork' test.

**why make this change?:**
We need more test coverage of timed out callbacks.

**test plan:**
Executed the fixture manually in Firefox and Chrome. Results looked
good.

* fix rebase problems

* make fixture compensate for chrome JS speed

* ran prettier

* Remove 'cancelled' flag on callbackConfig in scheduler, add test

**what is the change?:**
- Instead of using a 'cancelled' flag on the callbackConfig, it's easier
to just check the state of the callbackConfig inside
'cancelScheduledWork' to determine if it's already been cancelled. That
way we don't have to remember to set the 'cancelled' flag every time we
call a callback or cancel it. One less thing to remember.
- We added a test for calling 'cancelScheduledWork' more than once,
which would have failed before.

Thanks @acdlite for suggesting this in code review. :)

**why make this change?:**
To increase stability of the schedule module, increase test coverage.

**test plan:**
Existing tests pass and we added a new test to cover this behavior.

* fix typo
2018-05-30 15:38:48 -07:00
Andrew Clark
3118ed9d64 Expose unstable_interactiveUpdates on ReactDOM (#12943) 2018-05-30 15:31:59 -07:00
Flarnie Marchan
524a743313 Fix for Flow issues in SimpleCacheProvider (#12942)
* Fix for Flow issues in SimpleCacheProvider

**what is the change?:**
- Fixed some flow errors which were somehow swallowed when CI
originally
- Loosen flow types to avoid issue with recursive loop in Flow; https://github.com/facebook/flow/issues/5870

**why make this change?:**
To unbreak master and unblock other changes we want to make.

**test plan:**
Flow passes!

**issue:**
https://github.com/facebook/react/issues/12941

* Fix lints
2018-05-30 15:31:41 -07:00
Andrew Clark
ae57b125c7 [simple-cache-provider] Use LRU cache eviction (#12851)
* [simple-cache-provider] Use LRU cache eviction

Max size is hard-coded to 500. In the future, we should make this
configurable per resource.

* Evict PAGE_SIZE records from cache when max limit is reached
2018-05-30 13:12:29 -07:00
Spyros Ioakeimidis
e0a03c1b4d Extend input type check in selection capabilities (#12062) (#12135)
* Do not set selection when prior selection is undefined (#12062)

`restoreSelection` did not account for input elements that have changed
type after the commit phase. The new `text` input supported selection
but the old `email` did not and `setSelection` was incorrectly trying to
restore `null` selection state.

We also extend input type check in selection capabilities to cover cases
where input type is `search`, `tel`, `url`, or `password`.

* Add link to HTML spec for element types and selection

* Add reset button to ReplaceEmailInput

This commit adds a button to restore the original state of the
ReplaceEmailInput fixture so that it can be run multiple times without
refreshing the page.
2018-05-30 07:08:21 -04:00
Flarnie Marchan
79a740c6e3 Rename variables to remove references to 'global' global (#12931)
**what is the change?:**
In a recent PR we were referencing some global variables and storing
local references to them.

To make things more natural, we co-opted the original name of the global
for our local reference. To make this work with Flow, we get the
original reference from 'window.requestAnimationFrame' and assign it to
'const requestAnimationFrame'.

Sometimes React is used in an environment where 'window' is not defined
- in that case we need to use something else, or hide the 'window'
reference somewhere.

We opted to use 'global' thinking that Babel transforms would fill that
in with the proper thing.

But for some of our fixtures we are not doing that transform on the
bundle.

**why make this change?:**
I want to unbreak this on master and then investigate more about what we
should do to fix this.

**test plan:**
run `yarn build` and open the fixtures.

**issue:**
https://github.com/facebook/react/issues/12930
2018-05-29 17:54:38 -07:00
Flarnie Marchan
ff724d3c28 [scheduler] 4/n Allow splitting out schedule in fb-www, prepare to fix polyfill issue internally (#12900)
* Use local references to global things inside 'scheduler'

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

**why make this change?:**
We want to avoid initially calling one version of an API and then later
accessing a polyfilled version.

**test plan:**
Run existing tests.

* Shim ReactScheduler for www

**what is the change?:**
In 'www' we want to reference the separate build of ReactScheduler,
which allows treating it as a separate module internally.

**why make this change?:**
We need to require the ReactScheduler before our rAF polyfill activates,
in order to customize which custom behaviors we want.

This is also a step towards being able to experiment with using it
outside of React.

**test plan:**
Ran tests, ran the build, and ran `test-build`.

* Generate a bundle for fb-www

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

**why make this change?:**
Splitting out the 'schedule' module allows us to load it before
polyfills kick in for rAF and other APIs.

And long term we want to split this into a separate module anyway, this
is a step towards that.

**test plan:**
I'll run the sync next week and verify that this all works. :)

* ran prettier

* fix rebase issues

* Change names of variables used for holding globals
2018-05-29 13:30:04 -07:00
Brian Vaughn
001f9ef471 Release script prompts for NPM 2FA code (#12908)
* Release script prompts for NPM 2fa code
2018-05-29 12:50:04 -07:00
Brian Vaughn
83f76e4db9 ForwardRefs supports propTypes (#12911)
* Moved some internal forwardRef tests to not be internal
* ForwardRef supports propTypes
2018-05-29 09:50:49 -07:00
Dan Abramov
4f1f909b5b Disable Flow on AppVeyor again
It runs out of memory.
2018-05-29 15:47:14 +01:00
Nathan Hunzaker
8aeea5afa2 Do not assign node.value on input creation if no change will occur (#12925)
This commit fixes an issue where assigning an empty string to required
text inputs triggers the invalid state in Firefox (~60.0.1).

It does this by first comparing the initial state value to the current
value property on the text element. This:

1. Prevents the validation issue
2. Avoids an extra DOM Mutation in some cases
2018-05-29 14:48:58 +01:00
Simen Bekkhus
aa85b0fd5f Upgrade to Jest 23 (#12894)
* Upgrade to Jest 23 beta

* prefer `.toHaveBeenCalledTimes`

* 23 stable
2018-05-28 23:03:15 +01:00
Daniel Lo Nigro
a32f857ac7 Use --frozen-lockfile for Yarn in CI build (#12914)
CI builds should always use the `--frozen-lockfile` option. It will fail the build if the lockfile is out-of-date:

> If you need reproducible dependencies, which is usually the case with the continuous integration systems, you should pass --frozen-lockfile flag.

(https://yarnpkg.com/en/docs/cli/install/)
2018-05-28 19:52:42 +01:00
Flarnie Marchan
61777a78f6 [scheduler] 3/n Use a linked list instead of map and queue for callback storage (#12893)
* [schedule] Use linked list instead of queue and map for storing cbs

NOTE: This PR depends on https://github.com/facebook/react/pull/12880
and https://github.com/facebook/react/pull/12884
Please review those first, and after they land Flarnie will rebase on
top of them.

---

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

**why make this change?:**
This seems to make the code simpler, and potentially saves space of
having an array and object around holding references to the callbacks.

**test plan:**
Run existing tests

* minor style improvements

* refactor conditionals in cancelScheduledWork for increased clarity

* Remove 'canUseDOM' condition and fix some flow issues w/callbackID type

**what is the change?:**
- Removed conditional which fell back to 'setTimeout' when the
environment doesn't have DOM. This appears to be an old polyfill used
for test environments and we don't use it any more.
- Fixed type definitions around the callbackID to be more accurate in
the scheduler itself, and more loose in the React code.

**why make this change?:**
To get Flow passing, simplify the scheduler code, make things accurate.

**test plan:**
Run tests and flow.

* Rewrite 'cancelScheduledWork' so that Flow accepts it

**what is the change?:**
Adding verification that 'previousCallbackConfig' and
'nextCallbackConfig' are not null before accessing properties on them.

Slightly concerned because this implementation relies on these
properties being untouched and correct on the config which is passed to
'cancelScheduledWork' but I guess we already rely heavily on that for
this whole approach. :\

**why make this change?:**
To get Flow passing.

Not sure why it passed earlier and in CI, but now it's not.

**test plan:**
`yarn flow dom` and other flow tests, lint, tests, etc.

* ran prettier

* Put back the fallback implementation of scheduler for node environment

**what is the change?:**
We had tried removing the fallback implementation of `scheduler` but
tests reminded us that this is important for supporting isomorphic uses
of React.

Long term we will move this out of the `schedule` module but for now
let's keep things simple.

**why make this change?:**
Keep things working!

**test plan:**
Ran tests and flow

* Shorten properties stored in objects by sheduler

**what is the change?:**
`previousScheduledCallback` -> `prev`
`nextScheduledCallback` -> `next`

**why make this change?:**
We want this package to be smaller, and less letters means less code
means smaller!

**test plan:**
ran existing tests

* further remove extra lines in scheduler
2018-05-26 15:55:57 -07:00
Sebastian Markbåge
e7bd3d59a9 No longer expose ReactNativeComponentTree (#12904) 2018-05-25 21:17:37 -07:00
Brian Vaughn
f35d989bea TestRenderer warns if flushThrough is passed the wrong params (#12909)
TestRenderer throws if flushThrough is passed the expected yield values that don't match actual yield values.
2018-05-25 14:53:24 -07:00
Brian Vaughn
5578700671 Record "actual" times for all Fibers within a Profiler tree (alt) (#12910)
* Moved actual time fields from Profiler stateNode to Fiber

* Record actual time for all Fibers within a ProfileMode tree

* Changed how profiler accumulates time

This change gives up on accumulating time across renders of different priority, but in exchange- simplifies how the commit phase (reset) code works, and perhaps also makes the profiling code more compatible with future resuming behavior
2018-05-25 14:51:13 -07:00
Flarnie Marchan
76e07071a1 [scheduler] 2/n Adding 'schedule' fixture (#12884)
* Adding 'schedule' fixture

**what is the change?:**
We need to test the `schedule` module against real live browser APIs. As
a quick solution we're writing a fixture for using in manual testing.
Later we plan on adding automated browser testing, using this or a
similar fixture as the test page.

**why make this change?:**
To further solidify test coverage for `schedule` before making further
improvements/refactors to the module.

**test plan:**
`open fixtures/schedule/index.html` and inspect the results. It should
be clear that things pass.

We also temporarily broke the scheduler and verified that this fixture
demonstrates the problems.

**issue:**
Internal task T29442940

* Made fixture tests display red or green border depending on pass/fail

**what is the change?:**
Added red/green solid/dashed border for test results when using the
schedule fixture.

We also tweaked the timing of the last test because it was on the line
in terms of whether it passed or failed.

**why make this change?:**
To make it faster to use the fixture - it takes more time to read
through the results line by line and check that they match what is
expected.

**test plan:**
Looked at the fixture, and also tried modifying a test to show what it
looks like when something fails.
2018-05-24 14:11:25 -07:00
Flarnie Marchan
345e0a71ac Improve tests for 'schedule' module (#12880)
**what is the change?:**
Renamed some methods, and made a method to advance a frame in the test
environment.

**why make this change?:**
We often need to simulate a frame passing with some amount of idle time
or lack of idle time, and the new method makes it easier to write that
out.

**test plan:**
Run the updated tests.
Also temporarily tried breaking the scheduler and verified that the
tests will fail.

**issue:**
See internal task T29442940
2018-05-24 10:27:23 -07:00
Andrew Clark
fa7fa812c7 Update CHANGELOG for 16.4.0 2018-05-23 18:20:27 -07:00
Andrew Clark
8765d60893 Update bundle sizes for 16.4.0 release 2018-05-23 17:35:31 -07:00
Andrew Clark
d31e753f89 Update error codes for 16.4.0 release 2018-05-23 17:35:31 -07:00
Andrew Clark
d427a563d5 Updating package versions for release 16.4.0 2018-05-23 17:30:33 -07:00
Andrew Clark
eca59ec1b3 Updating yarn.lock file for 16.4.0 release 2018-05-23 17:26:46 -07:00
Chang Yan
53852a887b add functional components warning about legacy context api (#12892) 2018-05-23 14:16:39 -07:00
Toru Kobayashi
fe747a51c1 Add React.Timeout to getComponentName (#12890) 2018-05-23 18:39:20 +01:00
Toru Kobayashi
3df157480a Fix a typo (#12889) 2018-05-23 17:47:23 +01:00
Dan Abramov
6f4fb4a059 Tweak the changelog 2018-05-23 17:04:42 +01:00
Dan Abramov
d1be01f079 Add upcoming 16.4.0 changelog 2018-05-23 16:40:35 +01:00
Chang Yan
c601f7a646 add siblings Timeout components test case (#12862) 2018-05-22 15:39:40 -07:00
Chang Yan
7350358374 add legacy context API warning in strict mode (#12849)
* add legacy context APIs warning in strict mode

* refactor if statement and the warning message

* add other flags for type check

* add component stack tree and refactor wording

* fix the nits
2018-05-22 15:38:02 -07:00
Dan Abramov
e885791842 Fix a regression that caused us to listen to extra events at the top (#12878)
* Rewrite to a switch

I find it a bit easier to follow than many comparison conditions.

* Remove unnecessary assignments

They are being assigned below anyway. This is likely a copypasta from the FOCUS/BLUR special case (which *does* need those assignments).

* Unify "cancel" and "close" cases

Their logic is identical.

* Don't listen to media events at the top

* Add a unit test for double-invoking form events

* Remove an unused case and document it in a test

The case I added was wrong (just like including this event in the top level list was always wrong).

In fact it never bubbles, even for <img>. And since we don't special case it in the <img> event
attachment logic when we create it, we never supported <img onLoadStart> at all.

We could fix it. But Chrome doesn't support it either: https://bugs.chromium.org/p/chromium/issues/detail?id=458851.
Nobody asked us for it yet. And supporting it would require attaching an extra listener to every <img>.

So maybe we don't need it? Let's document the existing state of things.

* Add a test verifying we don't attach unnecessary listeners

* Add a comment

* Add a test for submit (bubbles: false)
2018-05-22 19:50:36 +01:00
Brian Vaughn
7c0aca289d Rollup freeze: false (#12879)
* Tell Rollup not to freeze bundles
* Only freeze bundles for DEV builds
2018-05-22 08:16:59 -07:00
Flarnie Marchan
33289b530c Tests and fixes for 'timing out' behavior (#12858)
**what is the change?:**
Test coverage checking that callbacks are called when they time out.

This test surfaced a bug and this commit includes the fix.

I want to refine this approach, but basically we can simulate time outs
by controlling the return value of 'now()' and the argument passed to
the rAF callback.

Next we will write a browser fixture to further test this against real
browser APIs.

**why make this change?:**
Better tests will keep this module working smoothly while we continue
refactoring and improving it.

**test plan:**
Run the new tests, see that it fails without the bug fix.
2018-05-22 08:03:55 -07:00
Sophie Alpert
ad27845ccd Fix double-firing submit events (#12877)
We were adding a listener at the root when we weren't meant to. Blames to e96dc14059.

This now alerts once (at FORM) instead of twice (at FORM, #document):

```
var Hello = class extends React.Component {
  render() {
    return (
      <form onSubmit={(e) => {e.preventDefault(); alert('hi ' + e.nativeEvent.currentTarget.nodeName);}}>
        <button>hi</button>
      </form>
    );
  }
};
```
2018-05-21 17:47:56 -07:00
Dan Abramov
60853f09f3 Try to reenable Flow on Windows CI
We should have more memory now
2018-05-21 18:53:00 +01:00
Dan Abramov
dd5fad2961 Update Flow to 0.70 (#12875)
* Update Flow to 0.70

* Remove unnecessary condition

* Fix wrong assertion

* Strict check
2018-05-21 17:54:48 +01:00
Brian Vaughn
13003654e7 Pass "start time" and "commit time" to Profiler callback (#12852)
* Added start time parameter to Profiler onRender callback
* Profiler also captures commit time
* Only init Profiler stateNode if enableProfilerTimer feature flag enabled
2018-05-21 09:49:22 -07:00
Dan Abramov
12c8a88cd9 Update Jest (#12874) 2018-05-21 16:17:11 +01:00
Dan Abramov
dc3b144f41 Treat Rollup "warnings" as errors (#12868) 2018-05-21 15:38:46 +01:00
Dan Abramov
0442e8275f Add a clear error when renderers clash in tests (#12867) 2018-05-21 15:38:35 +01:00
Kevin Lamping
089d2deb20 add netlify toml file (#12350) 2018-05-20 21:03:51 +01:00
Kevin (Kun) "Kassimo" Qian
d7b9b4921b Fix react native example links in README of 'react-reconciler' (#12871) 2018-05-20 12:01:00 +01:00
Sophie Alpert
9bed4a6aee https in reactProdInvariant text (#12869) 2018-05-19 17:29:41 -07:00
Dan Abramov
397d6115b7 Temporarily disable Flow on AppVeyor
I think it runs out of memory. I’ll reenable if we can bump the limit.
2018-05-19 13:54:44 +01:00
Dan Abramov
47b003a828 Resolve host configs at build time (#12792)
* Extract base Jest config

This makes it easier to change the source config without affecting the build test config.

* Statically import the host config

This changes react-reconciler to import HostConfig instead of getting it through a function argument.

Rather than start with packages like ReactDOM that want to inline it, I started with React Noop and ensured that *custom* renderers using react-reconciler package still work. To do this, I'm making HostConfig module in the reconciler look at a global variable by default (which, in case of the react-reconciler npm package, ends up being the host config argument in the top-level scope).

This is still very broken.

* Add scaffolding for importing an inlined renderer

* Fix the build

* ES exports for renderer methods

* ES modules for host configs

* Remove closures from the reconciler

* Check each renderer's config with Flow

* Fix uncovered Flow issue

We know nextHydratableInstance doesn't get mutated inside this function, but Flow doesn't so it thinks it may be null.
Help Flow.

* Prettier

* Get rid of enable*Reconciler flags

They are not as useful anymore because for almost all cases (except third party renderers) we *know* whether it supports mutation or persistence.

This refactoring means react-reconciler and react-reconciler/persistent third-party packages now ship the same thing.
Not ideal, but this seems worth how simpler the code becomes. We can later look into addressing it by having a single toggle instead.

* Prettier again

* Fix Flow config creation issue

* Fix imprecise Flow typing

* Revert accidental changes
2018-05-19 11:29:11 +01:00
Royi Hagigi
c0fe8d6f69 Adds ReactScheduler red->green unit test for bug fixed in #12834 (#12861)
* Scheduler red->green unit test for bug

* fix lint issue

* ran prettier
2018-05-18 15:05:21 -07:00
Flarnie Marchan
5e80d81f37 High pri - ensure we call timed out callbacks in schedule (#12857)
**what is the change?:**
Fix a typo which caused timed out callbacks to not be called.

**why make this change?:**
This is a bug caught by tests I'm in the process of writing, and we
should fix it asap.

**test plan:**
Tests in a WIP PR - will push and share the WIP test in comments on this
PR.
2018-05-18 10:05:49 -07:00
Brian Vaughn
17908c8ac9 Add test to ensure no duplicate values in ReactSymbols (#12845) 2018-05-18 07:57:25 -07:00
Dan
96992f2a6c Try to fix Windows CI 2018-05-18 09:25:50 +01:00
Pete Nykänen
972d209dcc Fix sample command in scripts/bench/README.md (#12853) 2018-05-18 09:18:35 +01:00
Dan Abramov
40addbd110 Run Flow for each renderer separately (#12846)
* Generate Flow config on install

We'll need to do pre-renderer Flow passes with different configs.
This is the first step to get it working. We only want the original version checked in.

* Create multiple Flow configs from a template

* Run Flow per renderer

* Lint

* Revert the environment consolidation

I thought this would be a bit cleaner at first because we now have non-environment files in this directory.
But Sebastian is changing these files at the same time so I want to avoid conflicts and keep the PR more tightly scoped. Undo.

* Misc
2018-05-18 02:05:19 +01:00
Sebastian Markbåge
40ea053bac Remove incorrect comment
Better to not have it than it being wrong.
2018-05-17 15:47:10 -07:00
Sebastian Markbåge
c5a8dae025 [Fabric] Wire up event emitters (#12847)
I'm exposing a new native method to wire up the event emitter. This will
use a straight fiber pointer instead of react tags to do the dispatching.
2018-05-17 12:38:50 -07:00
Dan Abramov
9d71ef26c3 Run the CI script on Windows 2018-05-17 19:18:47 +01:00
Gary Wang
1a0afed771 getPeerGlobals should check bundleType instead of moduleType (#12839) 2018-05-17 17:16:03 +01:00
Dan Abramov
b245795de3 Re-enable Flow for ReactFiber and fix Flow issues (#12842)
* Lint for untyped imports and enable Flow typing in ReactFiber

* Re-enable Flow for ReactFiber and fix Flow issues

* Avoid an invariant in DEV-only code

I just introduced it, but on a second thought, it's better to keep it as a warning.

* Address review
2018-05-17 17:14:12 +01:00
Flarnie Marchan
7ccb37161f Temporary fix for grabbing wrong rAF polyfill in ReactScheduler (#12837)
* Temporary fix for grabbing wrong rAF polyfill in ReactScheduler

**what is the change?:**
For now...
We need to grab a slightly different implementation of rAF internally at
FB than in Open Source. Making rAF a dependency of the ReactScheduler
module allows us to fork the dependency at FB.

NOTE: After this lands we have an alternative plan to make this module
separate from React and require it before our Facebook timer polyfills
are applied. But want to land this now to keep master in a working state
and fix bugs folks are seeing at Facebook.

Thanks @sebmarkbage @acdlite and @sophiebits for discussing the options
and trade-offs for solving this issue.

**why make this change?:**
This fixes a problem we're running into when experimenting with
ReactScheduler internally at Facebook, **and* it's part of our long term
plan to use dependency injection with the scheduler to make it easier to
test and adjust.

**test plan:**
Ran tests, lint, flow, and will manually test when syncing into
Facebook's codebase.

**issue:**
See internal task T29442940

* ran prettier
2018-05-17 08:57:45 -07:00
Brian Vaughn
4b8510be0f Make REACT_PROFILER_TYPE numeric value unique (#12843) 2018-05-17 08:55:41 -07:00
Dan Abramov
2d20dc47a3 Separate yarn flow and yarn flow-ci (#12841) 2018-05-17 14:29:37 +01:00
Sebastian Markbåge
d4123b4784 Relax current renderer warning (#12838)
If you use an older version of `react` this won't get initialized to null. We don't really need it to be initialized to work.
2018-05-16 17:31:56 -07:00
Brian Vaughn
2ace49362a Removed duplicate feature flag in test (#12836) 2018-05-16 15:39:32 -07:00
Flarnie Marchan
2da155a4c3 Quick fix for minor typo in ReactScheduler (#12834)
**what is the change?:**
We were setting a flag after some early returns, should have set it
right away.

To be fair, it's not clear how you can hit a problem with the current
state of things. Even if a callback is cancelled, it's still in the
'pendingCallbacks' queue until the rAF runs, and we only schedule a rAF
when there are pendingCallbacks in the queue.

But since this is obviously wrong, going to fix it.

We will be adding a regression test in a follow-up PR.

**why make this change?:**
To fix a random bug which was popping up.

**test plan:**
Adding a regression unit test in a follow-up PR.
2018-05-16 14:18:22 -07:00
Brian Vaughn
d6f304e889 Remove Timeout export on React object unless enableSuspense flag (#12833) 2018-05-16 14:02:34 -07:00
Flarnie Marchan
8227e54ccf Quick fix for ReactScheduler type inconsistency (#12828)
**what is the change?:**
In some cases we had defined the 'callback' as taking two arguments,
when really we meant to indicate the second argument passed to
'scheduleWork'.

**why make this change?:**
For correctness and to unblock something @gaearon is working on. A bit
surprised Flow didn't catch this in the first place.

**test plan:**
Ran tests, flow, lint.
2018-05-16 08:07:42 -07:00
Flarnie Marchan
ef294ed6fc Rename Scheduler methods more accurately (#12770)
* Rename Scheduler methods more accurately

**what is the change?:**
```
rIC -> scheduleCallback
```
We will later expose a second method for different priority level, name
TBD. Since we only have one priority right now we can delay the
bikeshedding about the priority names.

cIC -> cancelScheduledCallback
This method can be used to cancel callbacks scheduled at any priority
level, and will remain named this way.

why make this change?:
Originally this module contained a polyfill for requestIdleCallback
and cancelIdleCallback but we are changing the behavior so it's no
longer just a polyfill. The new names are more semantic and distinguish
this from the original polyfill functionality.

**test plan:**
Ran the tests

**why make this change?:**
Getting this out of the way so things are more clear.

**Coming Up Next:**
- Switching from a Map of ids and an array to a linked list for storing
callbacks.
- Error handling

* callback -> work

* update callsites in new places after rebase

* fix typo
2018-05-16 06:36:06 -07:00
Philipp Spieß
49979bbf52 Support Pointer Events (#12507)
* Support Pointer Events

* Add Pointer Events DOM Fixture
2018-05-16 14:34:33 +01:00
Brian Vaughn
de84d5c107 Enable Profiler timing for DOM and RN dev bundles (#12823)
* Enable Profiler timing for DOM and RN dev bundles
* Disable enableProfilerTimer feature flag for ReactIncrementalPerf-test
2018-05-15 15:26:46 -07:00
Sebastian Markbåge
f792275972 Pass instance handle to all Fabric clone methods (#12824)
We might need this in the future if we want to ensure event handler
consistency when an event handler target has been removed before it is
called.
2018-05-15 14:35:13 -07:00
Andrew Clark
a5184b215d Add FB www build of simple-cache-provider (#12822) 2018-05-15 13:21:07 -07:00
Brian Vaughn
103503eb69 Only measure "base" times within ProfileMode (#12821)
* Conditionally start/stop base timer only within Profile mode tree
* Added test to ensure ProfilerTimer not called outside of Profiler root
2018-05-15 12:43:42 -07:00
Dan Abramov
9097f3cdf0 Delete React Call/Return experiment (#12820) 2018-05-15 19:16:29 +01:00
Dan Abramov
d758960116 Tweak comments 2018-05-15 15:42:43 +01:00
Dan Abramov
025d867dce Try another approach at fixing Windows Flow issues 2018-05-15 15:20:15 +01:00
Dan Abramov
fe7890d569 Revert recent Flow changes 2018-05-15 15:03:21 +01:00
Dan Abramov
7ba1abecaa Try to fix Flow issue on Windows (part 5) 2018-05-15 14:55:38 +01:00
Dan Abramov
f2252a2ad4 Try to fix Flow issue on Windows (part 4) 2018-05-15 14:46:58 +01:00
Dan Abramov
b998357f9d Try to fix Flow issue on Windows (part 3) 2018-05-15 14:26:32 +01:00
Dan Abramov
7631024722 Try to fix Flow issue on Windows 2018-05-15 14:07:01 +01:00
Dan Abramov
bb44feb05d Try to fix Flow circular dependency 2018-05-15 13:55:01 +01:00
Dan Abramov
7dc1a176b5 Skip special nodes when reading TestInstance.parent (#12813) 2018-05-15 11:11:19 +01:00
Philipp Spieß
e96dc14059 Use browser event names for top-level event types in React DOM (#12629)
* Add TopLevelEventTypes

* Fix `ReactBrowserEventEmitter`

* Fix EventPluginUtils

* Fix TapEventPlugin

* Fix ResponderEventPlugin

* Update ReactDOMFiberComponent

* Fix BeforeInputEventPlugin

* Fix ChangeEventPlugin

* Fix EnterLeaveEventPlugin

* Add missing non top event type used in ChangeEventPlugin

* Fix SelectEventPlugin

* Fix SimpleEventPlugin

* Fix outstanding Flow issues and move TopLevelEventTypes

* Inline a list of all events in `ReactTestUtils`

* Fix tests

* Make it pretty

* Fix completly unrelated typo

* Don’t use map constructor because of IE11

* Update typings, revert changes to native code

* Make topLevelTypes in ResponderEventPlugin injectable and create DOM and ReactNative variant

* Set proper dependencies for DOMResponderEventPlugin

* Prettify

* Make some react dom tests no longer depend on internal API

* Use factories to create top level speific generic event modules

* Remove unused dependency

* Revert exposed module renaming, hide store creation, and inline dependency decleration

* Add Flow types to createResponderEventPlugin and its consumers

* Remove unused dependency

* Use opaque flow type for TopLevelType

* Add missing semis

* Use raw event names as top level identifer

* Upgrade baylon

This is required for parsing opaque flow types in our CI tests.

* Clean up flow types

* Revert Map changes of ReactBrowserEventEmitter

* Upgrade babel-* packages

Apparently local unit tests also have issues with parsing JavaScript
modules that contain opaque types (not sure why I didn't notice
earlier!?).

* Revert Map changes of SimpleEventPlugin

* Clean up ReactTestUtils

* Add missing semi

* Fix Flow issue

* Make TopLevelType clearer

* Favor for loops

* Explain the new DOMTopLevelEventTypes concept

* Use static injection for Responder plugin types

* Remove null check and rely on flow checks

* Add missing ResponderEventPlugin dependencies
2018-05-15 10:38:50 +01:00
Maxim
1047980dca Remove unused context param from countChildren (#12787) 2018-05-15 10:18:35 +01:00
Timothy Yung
bde4b1659f Delete ReactPerf and ReactDebugTool Stubs (#12809) 2018-05-14 20:28:55 -07:00
Andrew Clark
73f59e6f31 Use global state for hasForceUpdate instead of persisting to queue (#12808)
* Use global state for `hasForceUpdate` instead of persisting to queue

Fixes a bug where `hasForceUpdate` was not reset on commit.

Ideally we'd use a tuple and return `hasForceUpdate` from
`processUpdateQueue`.

* Remove underscore and add comment

* Remove temporary variables
2018-05-14 19:18:47 -07:00
Sophie Alpert
8c747d01cb Use ReactFiberErrorDialog fork for Fabric renderer (#12807) 2018-05-14 18:47:40 -07:00
Timothy Yung
369dd4fb17 Update headers for React Native shims (#12806) 2018-05-15 01:47:47 +01:00
Dan Abramov
45b90d4866 Move renderer host configs into separate modules (#12791)
* Separate test renderer host config

* Separate ART renderer host config

* Separate ReactDOM host config

* Extract RN Fabric host config

* Extract RN host config
2018-05-15 01:12:28 +01:00
Timothy Yung
b2d16047ae Fix Type for ReactNative.NativeComponent (#12805) 2018-05-14 16:36:50 -07:00
Brian Vaughn
c802d29bd1 Use HostContext to warn about invalid View/Text nesting (#12766) 2018-05-14 15:34:01 -07:00
Andrew Clark
c5d3104fc0 Do not fire getDerivedStateFromProps unless props or state have changed (#12802)
Fixes an oversight from #12600. getDerivedStateFromProps should fire
if either props *or* state have changed, but not if *neither* have
changed. This prevents a parent from re-rendering if a deep child
receives an update.
2018-05-14 14:56:48 -07:00
Brian Vaughn
0ba63aa141 Mark React Native and Fabric renderers as @generated (#12801)
Mark React Native and Fabric renderers as @generated
2018-05-14 10:39:30 -07:00
Sophie Alpert
c4abfa4015 Add context provider/consumer to getComponentName (#12778)
RN Inspector uses these.
2018-05-14 10:10:36 -07:00
Sophie Alpert
2a4d2ca7fc Set owner correctly inside forwardRef and context consumer (#12777)
Previously, _owner would be null if you create an element inside forwardRef or inside a context consumer. This is used by ReactNativeFiberInspector when traversing the hierarchy and also to give more info in some warning texts. This also means you'll now correctly get a warning if you call setState inside one of these.

Test Plan: Tim tried it in the RN inspector.
2018-05-14 10:07:31 -07:00
Dan Abramov
72542030cf Use Java version of Google Closure Compiler (#12800)
* makes closure compiler threaded

* Dans PR with a closure compiler java version

* Remove unused dep

* Pin GCC

* Prettier

* Nit rename

* Fix error handling

* Name plugins consistently

* Fix lint

* Maybe this works?

* or this

* AppVeyor

* Fix lint
2018-05-14 17:49:41 +01:00
Dan Abramov
37d12e2916 Update lockfile 2018-05-14 16:20:33 +01:00
Dan Abramov
0470854f55 Split ReactNoop into normal and persistent exports (#12793)
* Copy-paste ReactNoop into ReactNoopPersistent

* Split ReactNoop into normal and persistent exports

* ReactNoopShared -> createReactNoop
2018-05-14 13:57:33 +01:00
Toru Kobayashi
d430e13582 Fix a typo (#12798) 2018-05-14 12:35:20 +01:00
Bartosz Kaszubowski
8506062975 remove unused ES3-specific packages - refs #12716 (#12797) 2018-05-14 11:18:31 +01:00
Dan
7b19f93ab9 Record sizes 2018-05-13 21:12:25 +01:00
Andrew Clark
4b2e65d32e Put recent change to getDerivedStateFromProps behind a feature flag (#12788)
This will allow us to safely ship it at Facebook and get a better idea
for if/how it breaks existing product code.
2018-05-11 18:45:00 -07:00
Filipp Riabchun
4f459bb144 Shallow renderer: pass component instance to setState updater as this (#12784)
* Shallow renderer: pass component instance to setState updater as `this`

* Run prettier
2018-05-11 18:03:08 +01:00
Andrew Clark
b0726e9947 Support sharing context objects between concurrent renderers (#12779)
* Support concurrent primary and secondary renderers.

As a workaround to support multiple concurrent renderers, we categorize
some renderers as primary and others as secondary. We only expect
there to be two concurrent renderers at most: React Native (primary) and
Fabric (secondary); React DOM (primary) and React ART (secondary).
Secondary renderers store their context values on separate fields.

* Add back concurrent renderer warning

Only warn for two concurrent primary or two concurrent secondary renderers.

* Change "_secondary" suffix to "2"

#EveryBitCounts
2018-05-10 18:34:01 -07:00
Andrew Clark
6565795377 Suspense (#12279)
* Timeout component

Adds Timeout component. If a promise is thrown from inside a Timeout component,
React will suspend the in-progress render from committing. When the promise
resolves, React will retry. If the render is suspended for longer than the
maximum threshold, the Timeout switches to a placeholder state.

The timeout threshold is defined as the minimum of:
- The expiration time of the current render
- The `ms` prop given to each Timeout component in the ancestor path of the
thrown promise.

* Add a test for nested fallbacks

Co-authored-by: Andrew Clark <acdlite@fb.com>

* Resume on promise rejection

React should resume rendering regardless of whether it resolves
or rejects.

* Wrap Suspense code in feature flag

* Children of a Timeout must be strict mode compatible

Async is not required for Suspense, but strict mode is.

* Simplify list of pending work

Some of this was added with "soft expiration" in mind, but now with our revised
model for how soft expiration will work, this isn't necessary.

It would be nice to remove more of this, but I think the list itself is inherent
because we need a way to track the start times, for <Timeout ms={ms} />.

* Only use the Timeout update queue to store promises, not for state

It already worked this way in practice.

* Wrap more Suspense-only paths in the feature flag

* Attach promise listener immediately on suspend

Instead of waiting for commit phase.

* Infer approximate start time using expiration time

* Remove list of pending priority levels

We can replicate almost all the functionality by tracking just five
separate levels: the highest/lowest priority pending levels, the
highest/lowest priority suspended levels, and the lowest pinged level.

We lose a bit of granularity, in that if there are multiple levels of
pending updates, only the first and last ones are known. But in practice
this likely isn't a big deal.

These heuristics are almost entirely isolated to a single module and
can be adjusted later, without API changes, if necessary.

Non-IO-bound work is not affected at all.

* ReactFiberPendingWork -> ReactFiberPendingPriority

* Renaming method names from "pending work" to "pending priority"

* Get rid of SuspenseThenable module

Idk why I thought this was neccessary

* Nits based on Sebastian's feedback

* More naming nits + comments

* Add test for hiding a suspended tree to unblock

* Revert change to expiration time rounding

This means you have to account for the start time approximation
heuristic when writing Suspense tests, but that's going to be
true regardless.

When updating the tests, I also made a fix related to offscreen
priority. We should never timeout inside a hidden tree.

* palceholder -> placeholder
2018-05-10 18:09:10 -07:00
Andrew Clark
42a1262375 Update sizes 2018-05-10 18:08:11 -07:00
Brian Vaughn
fc3777b1fe Add Profiler component for collecting new render timing info (#12745)
Add a new component type, Profiler, that can be used to collect new render time metrics. Since this is a new, experimental API, it will be exported as React.unstable_Profiler initially.

Most of the functionality for this component has been added behind a feature flag, enableProfileModeMetrics. When the feature flag is disabled, the component will just render its children with no additional behavior. When the flag is enabled, React will also collect timing information and pass it to the onRender function (as described below).
2018-05-10 15:25:32 -07:00
Flarnie Marchan
a9abd27e4f [schedule] Support multiple callbacks in scheduler (#12746)
* Support using id to cancel scheduled callback

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

**why make this change?:**
Once we support multiple callbacks you will need to use the id to
specify which callback you mean.

**test plan:**
Added a test, ran all tests, lint, etc.

* ran prettier

* fix lint

* Use object for storing callback info in scheduler

* Wrap initial test in a describe block

* Support multiple callbacks in `ReactScheduler`

**what is the change?:**
We keep a queue of callbacks instead of just one at a time, and call
them in order first by their timeoutTime and then by the order which
they were scheduled in.

**why make this change?:**
We plan on using this module to coordinate JS outside of React, so we
will need to schedule more than one callback at a time.

**test plan:**
Added a boatload of shiny new tests. :)

Plus ran all the old ones.

NOTE: The tests do not yet cover the vital logic of callbacks timing
out, and later commits will add the missing test coverage.

* Heuristic to avoid looking for timed out callbacks when none timed out

**what is the change?:**
Tracks the current soonest timeOut time for all scheduled callbacks.

**why make this change?:**
We were checking every scheduled callback to see if it timed out on
every tick. It's more efficient to skip that O(n) check if we know that
none have timed out.

**test plan:**
Ran existing tests.

Will write new tests to cover timeout behavior in more detail soon.

* Put multiple callback support under a disabled feature flag

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

**why make this change?:**
We don't have error handling in place yet, so should maintain the old
behavior until that is in place.

But want to get this far to continue making incremental changes.

**test plan:**
Updated and ran tests.

* Hide support for multiple callbacks under a feature flag

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

**why make this change?:**
We haven't added error handling yet, so should not expose this feature.

**test plan:**
Ran all tests, temporarily split out the tests for multiple callbacks
into separate file. Will recombine once we remove the flag.

* Fix nits from code review

See comments on https://github.com/facebook/react/pull/12743

* update checklist in comments

* Remove nested loop which calls additional timed out callbacks

**what is the change?:**
We used to re-run any callbacks which time out whilst other callbacks
are running, but now we will only check once for timed out callbacks
then then run them.

**why make this change?:**
To simplify the code and the behavior of this module.

**test plan:**
Ran all existing tests.

* Remove feature flag

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

**why make this change?:**
Because only React is using this, and it sounds like async. rendering
won't hit any different behavior due to these changes.

**test plan:**
Existing tests pass, and this allowed us to recombine all tests to run
in both 'test' and 'test-build' modes.

* remove outdated file

* fix typo
2018-05-09 15:28:13 -07:00
bee0060
3fb8be5c30 Minor fix params description for addPercent function (#12669) 2018-05-07 17:46:42 -07:00
Toru Kobayashi
0bf24cc83e setState returning null and undefined is no-op on the ShallowRenderer (#12756) 2018-05-07 17:31:33 -07:00
Dan Abramov
25dda90c1e Mark context consumers with PerformedWork effect on render (#12729)
* Mark new component types with PerformedWork effect

* Don't do it for ForwardRef

Since this has some overhead and ForwardRef is likely going to be used around context, let's skip it.
We don't highlight ForwardRef alone in DevTools anyway.
2018-05-02 16:35:16 +01:00
Dan Abramov
ad7cd68667 Rename internal property to fix React DevTools (#12727) 2018-05-01 21:04:20 +01:00
Sophie Alpert
200357596a Add error when running jest directly (#12726)
```
$ jest
 FAIL  scripts/jest/dont-run-jest-directly.js
  ● Test suite failed to run

    Don't run `jest` directly. Run `yarn test` instead.

    > 1 | throw new Error("Don't run `jest` directly. Run `yarn test` instead.");
      2 |

      at Object.<anonymous> (scripts/jest/dont-run-jest-directly.js:1:96)

Test Suites: 1 failed, 1 total
Tests:       0 total
Snapshots:   0 total
Time:        0.866s
Ran all test suites.
```
2018-05-01 12:46:17 -07:00
Dan Abramov
e0ca51a85d Make React.forwardRef() components discoverable by TestRenderer traversal (#12725) 2018-05-01 19:55:06 +01:00
Toru Kobayashi
7dd4ca2911 Call getDerivedStateFromProps even for setState of ShallowRenderer (#12676) 2018-04-30 17:04:40 +01:00
Dan Abramov
9a9f54720f Remove ES3-specific transforms (#12716) 2018-04-30 14:30:37 +01:00
Airam
dcc854bcc3 prevent removing attributes on custom component tags (#12702) 2018-04-28 20:52:30 +01:00
Dan Abramov
045d4f166d Fix a context propagation bug (#12708)
* Fix a context propagation bug

* Add a regression test
2018-04-28 01:52:48 +01:00
Dan Abramov
7c39328571 Don't bail on new context Provider if a legacy provider rendered above (#12586)
* Don't bail on new context Provider if a legacy provider rendered above

* Avoid an extra variable
2018-04-26 20:59:17 +01:00
Dan Abramov
d883d59863 forwardRef() components should not re-render on deep setState() (#12690)
* Add a failing test for forwardRef memoization

* Memoize forwardRef props and bail out on strict equality

* Bail out only when ref matches the current ref
2018-04-26 19:47:34 +01:00
Heaven
ec57d29941 Remove redundant feature flag in the test due to https://github.com/facebook/react/pull/12117 (#12696) 2018-04-26 18:39:11 +01:00
Flarnie Marchan
9c77ffb444 Dedup conditional in ReactScheduler (#12680)
**what is the change?:**
We had a condition to set either 'performance.now' or 'Date.now' as the
'now' function.

Then later we had another conditional checking again if
'performance.now' was supported, and using it if so, otherwise falling
back to 'Date.now'.

More efficient to just use the 'now' shortcut defined above.

**why make this change?:**
Fewer lines, clearer code.

**test plan:**
Now that we have tests we can run them :)
2018-04-24 08:54:31 -07:00
Andrew Clark
09a14eacd4 Update bundle sizes 2018-04-23 19:38:07 -07:00
Andrew Clark
1673485720 Revert stray console.log 2018-04-23 18:44:14 -07:00
Flarnie Marchan
1e3cd332a0 Remove the 'alwaysUseRequestIdleCallbackPolyfill' feature flag (#12648)
* Remove the 'alwaysUseRequestIdleCallbackPolyfill' feature flag

**what is the change?:**
Removes the feature flag 'alwaysUseRequestIdleCallbackPolyfill', such
that we **always** use the polyfill for requestIdleCallback.

**why make this change?:**
We have been testing this feature flag at 100% for some time internally,
and determined it works better for React than the native implementation.
Looks like RN was overriding the flag to use the native when possible,
but since no RN products are using 'async' mode it should be safe to
switch this flag over for RN as well.

**test plan:**
We have already been testing this internally for some time.

**issue:**
internal task t28128480

* fix mistaken conditional

* Add mocking of rAF, postMessage, and initial test for ReactScheduler

**what is the change?:**
- In all tests where we previously mocked rIC or relied on native
mocking which no longer works, we are now mocking rAF and postMessage.
- Also adds a basic initial test for ReactScheduler.
NOTE -> we do plan to write headless browser tests for ReactScheduler!
This is just an initial test, to verify that it works with the mocked
out browser APIs as expected.

**why make this change?:**
We need to mock out the browser APIs more completely for the new
'ReactScheduler' to work in our tests. Many tests are depending on it,
since it's used at a low level.

By mocking the browser APIs rather than the 'react-scheduler' module, we
enable testing the production bundles. This approach is trading
isolation for accuracy. These tests will be closer to a real use.

**test plan:**
run the tests :)

**issue:**
internal task T28128480
2018-04-23 15:25:46 -07:00
Brian Vaughn
149a34f735 Exposed flushSync on the test renderer (#12672) 2018-04-23 10:27:39 -07:00
Andrew Clark
b548b3cd64 Decouple update queue from Fiber type (#12600)
* Decouple update queue from Fiber type

The update queue is in need of a refactor. Recent bugfixes (#12528) have
exposed some flaws in how it's modeled. Upcoming features like Suspense
and [redacted] also rely on the update queue in ways that weren't
anticipated in the original design.

Major changes:

- Instead of boolean flags for `isReplace` and `isForceUpdate`, updates
have a `tag` field (like Fiber). This lowers the cost for adding new
types of updates.
- Render phase updates are special cased. Updates scheduled during
the render phase are dropped if the work-in-progress does not commit.
This is used for `getDerivedStateFrom{Props,Catch}`.
- `callbackList` has been replaced with a generic effect list. Aside
from callbacks, this is also used for `componentDidCatch`.

* Remove first class UpdateQueue types and use closures instead

I tried to avoid this at first, since we avoid it everywhere else in the Fiber
codebase, but since updates are not in a hot path, the trade off with file size
seems worth it.

* Store captured errors on a separate part of the update queue

This way they can be reused independently of updates like
getDerivedStateFromProps. This will be important for resuming.

* Revert back to storing hasForceUpdate on the update queue

Instead of using the effect tag. Ideally, this would be part of the
return type of processUpdateQueue.

* Rename UpdateQueue effect type back to Callback

I don't love this name either, but it's less confusing than UpdateQueue
I suppose. Conceptually, this is usually a callback: setState callbacks,
componentDidCatch. The only case that feels a bit weird is Timeouts,
which use this effect to attach a promise listener. I guess that kinda
fits, too.

* Call getDerivedStateFromProps every render, even if props did not change

Rather than enqueue a new setState updater for every props change, we
can skip the update queue entirely and merge the result into state at
the end. This makes more sense, since "receiving props" is not an event
that should be observed. It's still a bit weird, since eventually we do
persist the derived state (in other words, it accumulates).

* Store captured effects on separate list from "own" effects (callbacks)

For resuming, we need the ability to discard the "own" effects while
reusing the captured effects.

* Optimize for class components

Change `process` and `callback` to match the expected payload types
for class components. I had intended for the update queue to be reusable
for both class components and a future React API, but we'll likely have
to fork anyway.

* Only double-invoke render phase lifecycles functions in DEV

* Use global state to track currently processing queue in DEV
2018-04-22 23:05:28 -07:00
Nicole Levy
5dcf93d146 Validate props on context providers (#12658)
* checkPropTypes in updateContextProvider

* invalid “prop”

* `type not `types` .. :l

* test

* don’t need extra check with no spelling mistake (:

* change error message to specifically address provider

* don’t need class, add extra render to make sure good props go through

* nitpicky rename

* prettier

* switch to `Context.Provider`

* add stack to warning, add extra undefined check

* separate dev check

* add stack to test

* more efficient

* remove unused function

* prettier

* const to top
2018-04-22 13:39:38 +01:00
Dan Abramov
c040bcbea8 Add server integration tests for new context (#12654)
* Add server integration tests for new context

* Pretty please

* Remove unused
2018-04-21 21:21:05 +01:00
Flarnie Marchan
999b656ed1 Initial commit (#12624)
This is the first step - pulling the ReactDOMFrameScheduling module out
into a separate package.

Co-authored-by: Brandon Dail <aweary@users.noreply.github.com>
2018-04-19 09:29:08 -07:00
Brian Vaughn
f80bbf88e5 StrictMode should not warn about polyfilled getSnapshotBeforeUpdate (#12647)
* Installed 3.x release of react-lifecycles-compat
* Updated ReactComponentLifeCycle-test and ReactDOMServerLifecycles-test to cover both polyfilled lifecycles in StrictMode
* Updated StrictMode warnings to not warn about polyfilled getSnapshotBeforeUpdate
2018-04-19 09:08:44 -07:00
Brian Vaughn
920f30ef77 Add forwardRef DEV warning for prop-types on render function (#12644) 2018-04-18 16:36:31 -07:00
Brian Vaughn
0887c7d56c Fork React Native renderer into FB and OSS bundles (#12625)
* Added new "native-fb" and "native-fabric-fb" bundles.
* Split RN_DEV and RN_PROD bundle types into RN_OSS_DEV, RN_OSS_PROD, RN_FB_DEV, and RN_FB_PROD. (This is a bit redundant but it seemed the least intrusive way of supporting a forked feature flags file for these bundles.)
* Renamed FB_DEV and FB_PROD bundle types to be more explicitly for www (FB_WWW_DEV and FB_WWW_PROD)
* Removed Haste @providesModule headers from the RB-specific RN renderer bundles to avoid a duplicate name conflicts.
* Remove dynamic values from OSS RN feature flags. (Leave them in FB RN feature flags.)
* Updated the sync script(s) to account for new renderer type.
* Move ReactFeatureFlags.js shim to FB bundle only (since OSS bundle no longer needs dynamic values).
2018-04-18 13:16:50 -07:00
Sebastian Markbåge
039695cc01 [RN] Update Secret Types (#12635) 2018-04-17 19:21:46 -07:00
Dan Abramov
b05e67e36a Bump Prettier (#12622) 2018-04-17 01:43:55 +01:00
Brian Vaughn
77ebeb1b09 Don't git commit noop-renderer unless package deps change (#12623) 2018-04-16 09:46:39 -07:00
Heaven
b85c5cd188 remove duplicate code in test (#12620) 2018-04-16 16:36:49 +01:00
Dan Abramov
01402f4ad9 Add 16.3.2 changelog (#12621) 2018-04-16 16:31:48 +01:00
356 changed files with 24517 additions and 14534 deletions

View File

@@ -18,8 +18,6 @@
["transform-es2015-spread", { "loose": true }],
"transform-es2015-parameters",
["transform-es2015-destructuring", { "loose": true }],
["transform-es2015-block-scoping", { "throwIfClosureRequired": true }],
"transform-es3-member-expression-literals",
"transform-es3-property-literals"
["transform-es2015-block-scoping", { "throwIfClosureRequired": true }]
]
}

View File

@@ -3,7 +3,7 @@ jobs:
build:
docker:
- image: circleci/node:8
- image: circleci/openjdk:8-jdk-node-browsers
environment:
TZ: /usr/share/zoneinfo/America/Los_Angeles
@@ -29,7 +29,7 @@ jobs:
- run:
name: Install Packages
command: yarn install
command: yarn install --frozen-lockfile
- run:
name: Test Packages

View File

@@ -81,5 +81,6 @@ module.exports = {
spyOnDev: true,
spyOnDevAndProd: true,
spyOnProd: true,
__PROFILE__: true,
},
};

View File

@@ -1,42 +0,0 @@
[ignore]
<PROJECT_ROOT>/fixtures/.*
<PROJECT_ROOT>/build/.*
<PROJECT_ROOT>/scripts/bench/.*
# These shims are copied into external projects:
<PROJECT_ROOT>/scripts/rollup/shims/facebook-www/.*
<PROJECT_ROOT>/scripts/rollup/shims/react-native/.*
<PROJECT_ROOT>/.*/node_modules/y18n/.*
<PROJECT_ROOT>/node_modules/chrome-devtools-frontend/.*
<PROJECT_ROOT>/node_modules/devtools-timeline-model/.*
<PROJECT_ROOT>/node_modules/create-react-class/.*
<PROJECT_ROOT>/.*/__mocks__/.*
<PROJECT_ROOT>/.*/__tests__/.*
[include]
[libs]
./node_modules/fbjs/flow/lib/dev.js
./scripts/flow
[options]
esproposal.class_static_fields=enable
esproposal.class_instance_fields=enable
unsafe.enable_getters_and_setters=true
munge_underscores=false
suppress_type=$FlowIssue
suppress_type=$FlowFixMe
suppress_type=$FixMe
suppress_type=$FlowExpectedError
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue
suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError
[version]
^0.61.0

3
.gitignore vendored
View File

@@ -1,5 +1,6 @@
.DS_STORE
node_modules
scripts/flow/*/.flowconfig
*~
*.pyc
.grunt
@@ -21,4 +22,4 @@ chrome-user-data
*.iml
.vscode
*.swp
*.swo
*.swo

View File

@@ -4,8 +4,90 @@
Changes that have landed in master but are not yet released.
Click to see more.
</summary>
</details>
## 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))
### React DOM
* Fix a crash when the input `type` changes from some other types to `text`. ([@spirosikmd](https://github.com/spirosikmd) in [#12135](https://github.com/facebook/react/pull/12135))
* Fix a crash in IE11 when restoring focus to an SVG element. ([@ThaddeusJiang](https://github.com/ThaddeusJiang) in [#12996](https://github.com/facebook/react/pull/12996))
* Fix a range input not updating in some cases. ([@Illu](https://github.com/Illu) in [#12939](https://github.com/facebook/react/pull/12939))
* Fix input validation triggering unnecessarily in Firefox. ([@nhunzaker](https://github.com/nhunzaker) in [#12925](https://github.com/facebook/react/pull/12925))
* Fix an incorrect `event.target` value for the `onChange` event in IE9. ([@nhunzaker](https://github.com/nhunzaker) in [#12976](https://github.com/facebook/react/pull/12976))
* Fix a false positive error when returning an empty `<React.Fragment />` from a component. ([@philipp-spiess](https://github.com/philipp-spiess) in [#12966](https://github.com/facebook/react/pull/12966))
### React DOM Server
* Fix an incorrect value being provided by new context API. ([@ericsoderberghp](https://github.com/ericsoderberghp) in [#12985](https://github.com/facebook/react/pull/12985), [@gaearon](https://github.com/gaearon) in [#13019](https://github.com/facebook/react/pull/13019))
### React Test Renderer
* Allow multiple root children in test renderer traversal API. ([@gaearon](https://github.com/gaearon) in [#13017](https://github.com/facebook/react/pull/13017))
* Fix `getDerivedStateFromProps()` in the shallow renderer to not discard the pending state. ([@fatfisz](https://github.com/fatfisz) in [#13030](https://github.com/facebook/react/pull/13030))
## 16.4.0 (May 23, 2018)
### React
* Add a new [experimental](https://github.com/reactjs/rfcs/pull/51) `React.unstable_Profiler` component for measuring performance. ([@bvaughn](https://github.com/bvaughn) in [#12745](https://github.com/facebook/react/pull/12745))
### React DOM
* Add support for the Pointer Events specification. ([@philipp-spiess](https://github.com/philipp-spiess) in [#12507](https://github.com/facebook/react/pull/12507))
* Properly call `getDerivedStateFromProps()` regardless of the reason for re-rendering. ([@acdlite](https://github.com/acdlite) in [#12600](https://github.com/facebook/react/pull/12600) and [#12802](https://github.com/facebook/react/pull/12802))
* Fix a bug that prevented context propagation in some cases. ([@gaearon](https://github.com/gaearon) in [#12708](https://github.com/facebook/react/pull/12708))
* Fix re-rendering of components using `forwardRef()` on a deeper `setState()`. ([@gaearon](https://github.com/gaearon) in [#12690](https://github.com/facebook/react/pull/12690))
* Fix some attributes incorrectly getting removed from custom element nodes. ([@airamrguez](https://github.com/airamrguez) in [#12702](https://github.com/facebook/react/pull/12702))
* Fix context providers to not bail out on children if there's a legacy context provider above. ([@gaearon](https://github.com/gaearon) in [#12586](https://github.com/facebook/react/pull/12586))
* Add the ability to specify `propTypes` on a context provider component. ([@nicolevy](https://github.com/nicolevy) in [#12658](https://github.com/facebook/react/pull/12658))
* Fix a false positive warning when using `react-lifecycles-compat` in `<StrictMode>`. ([@bvaughn](https://github.com/bvaughn) in [#12644](https://github.com/facebook/react/pull/12644))
* Warn when the `forwardRef()` render function has `propTypes` or `defaultProps`. ([@bvaughn](https://github.com/bvaughn) in [#12644](https://github.com/facebook/react/pull/12644))
* Improve how `forwardRef()` and context consumers are displayed in the component stack. ([@sophiebits](https://github.com/sophiebits) in [#12777](https://github.com/facebook/react/pull/12777))
* Change internal event names. This can break third-party packages that rely on React internals in unsupported ways. ([@philipp-spiess](https://github.com/philipp-spiess) in [#12629](https://github.com/facebook/react/pull/12629))
### React Test Renderer
* Fix the `getDerivedStateFromProps()` support to match the new React DOM behavior. ([@koba04](https://github.com/koba04) in [#12676](https://github.com/facebook/react/pull/12676))
* Fix a `testInstance.parent` crash when the parent is a fragment or another special node. ([@gaearon](https://github.com/gaearon) in [#12813](https://github.com/facebook/react/pull/12813))
* `forwardRef()` components are now discoverable by the test renderer traversal methods. ([@gaearon](https://github.com/gaearon) in [#12725](https://github.com/facebook/react/pull/12725))
* Shallow renderer now ignores `setState()` updaters that return `null` or `undefined`. ([@koba04](https://github.com/koba04) in [#12756](https://github.com/facebook/react/pull/12756))
### React ART
* Fix reading context provided from the tree managed by React DOM. ([@acdlite](https://github.com/acdlite) in [#12779](https://github.com/facebook/react/pull/12779))
### React Call Return (Experimental)
* This experiment was deleted because it was affecting the bundle size and the API wasn't good enough. It's likely to come back in the future in some other form. ([@gaearon](https://github.com/gaearon) in [#12820](https://github.com/facebook/react/pull/12820))
### React Reconciler (Experimental)
* The [new host config shape](https://github.com/facebook/react/blob/c601f7a64640290af85c9f0e33c78480656b46bc/packages/react-noop-renderer/src/createReactNoop.js#L82-L285) is flat and doesn't use nested objects. ([@gaearon](https://github.com/gaearon) in [#12792](https://github.com/facebook/react/pull/12792))
## 16.3.2 (April 16, 2018)
### React
* Improve the error message when passing `null` or `undefined` to `React.cloneElement`. ([@nicolevy](https://github.com/nicolevy) in [#12534](https://github.com/facebook/react/pull/12534))
### React DOM
* Fix an IE crash in development when using `<StrictMode>`. ([@bvaughn](https://github.com/bvaughn) in [#12546](https://github.com/facebook/react/pull/12546))
* Fix labels in User Timing measurements for new component types. ([@bvaughn](https://github.com/bvaughn) in [#12609](https://github.com/facebook/react/pull/12609))
* Improve the warning about wrong component type casing. ([@nicolevy](https://github.com/nicolevy) in [#12533](https://github.com/facebook/react/pull/12533))
* Improve general performance in development mode. ([@gaearon](https://github.com/gaearon) in [#12537](https://github.com/facebook/react/pull/12537))
* Improve performance of the experimental `unstable_observedBits` API with nesting. ([@gaearon](https://github.com/gaearon) in [#12543](https://github.com/facebook/react/pull/12543))
### React Test Renderer
* Add a UMD build. ([@bvaughn](https://github.com/bvaughn) in [#12594](https://github.com/facebook/react/pull/12594))
## 16.3.1 (April 3, 2018)
### React

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.
## Contributing

View File

@@ -5,8 +5,8 @@ init:
- git config --global core.autocrlf input
environment:
matrix:
- nodejs_version: 8
nodejs_version: 8
JAVA_HOME: C:\Program Files\Java\jdk1.8.0
# Finish on first failed build
matrix:
@@ -25,12 +25,12 @@ deploy: off
install:
- ps: Install-Product node $env:nodejs_version $env:platform
- yarn install
- yarn install --frozen-lockfile
test_script:
- node --version
- yarn lint
- yarn flow
# - yarn flow-ci
- yarn build
- yarn test
- yarn prettier

View File

@@ -53,7 +53,8 @@ function generateMDTable(headers, body) {
/**
* Generates a user-readable string from a percentage change
* @param {string[]} headers
* @param {number} change
* @param {boolean} includeEmoji
*/
function addPercent(change, includeEmoji) {
if (!isFinite(change)) {

View File

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

View File

@@ -11,6 +11,8 @@ import ErrorHandling from './error-handling';
import EventPooling from './event-pooling';
import CustomElementFixtures from './custom-elements';
import MediaEventsFixtures from './media-events';
import PointerEventsFixtures from './pointer-events';
import MouseEventsFixtures from './mouse-events';
const React = window.React;
@@ -46,6 +48,10 @@ function FixturesPage() {
return <CustomElementFixtures />;
case '/media-events':
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

@@ -15,7 +15,7 @@ function NumberInputs() {
`}
affectedBrowsers="IE Edge, IE 11">
<TestCase.Steps>
<li>Type any string (not an actual password</li>
<li>Type any string (not an actual password)</li>
</TestCase.Steps>
<TestCase.ExpectedResult>

View File

@@ -0,0 +1,90 @@
const React = window.React;
const CIRCLE_SIZE = 80;
class DragBox extends React.Component {
state = {
hasCapture: false,
circleLeft: 80,
circleTop: 80,
};
isDragging = false;
previousLeft = 0;
previousTop = 0;
onDown = event => {
this.isDragging = true;
event.target.setPointerCapture(event.pointerId);
// We store the initial coordinates to be able to calculate the changes
// later on.
this.extractPositionDelta(event);
};
onMove = event => {
if (!this.isDragging) {
return;
}
const {left, top} = this.extractPositionDelta(event);
this.setState(({circleLeft, circleTop}) => ({
circleLeft: circleLeft + left,
circleTop: circleTop + top,
}));
};
onUp = event => (this.isDragging = false);
onGotCapture = event => this.setState({hasCapture: true});
onLostCapture = event => this.setState({hasCapture: false});
extractPositionDelta = event => {
const left = event.pageX;
const top = event.pageY;
const delta = {
left: left - this.previousLeft,
top: top - this.previousTop,
};
this.previousLeft = left;
this.previousTop = top;
return delta;
};
render() {
const {hasCapture, circleLeft, circleTop} = this.state;
const boxStyle = {
border: '1px solid #d9d9d9',
margin: '10px 0 20px',
minHeight: 400,
width: '100%',
position: 'relative',
};
const circleStyle = {
width: CIRCLE_SIZE,
height: CIRCLE_SIZE,
borderRadius: CIRCLE_SIZE / 2,
position: 'absolute',
left: circleLeft,
top: circleTop,
backgroundColor: hasCapture ? 'blue' : 'green',
touchAction: 'none',
};
return (
<div style={boxStyle}>
<div
style={circleStyle}
onPointerDown={this.onDown}
onPointerMove={this.onMove}
onPointerUp={this.onUp}
onPointerCancel={this.onUp}
onGotPointerCapture={this.onGotCapture}
onLostPointerCapture={this.onLostCapture}
/>
</div>
);
}
}
export default DragBox;

View File

@@ -0,0 +1,25 @@
import TestCase from '../../TestCase';
import DragBox from './drag-box';
const React = window.React;
class Drag extends React.Component {
render() {
return (
<TestCase title="Drag" description="">
<TestCase.Steps>
<li>Drag the circle below with any pointer tool</li>
</TestCase.Steps>
<TestCase.ExpectedResult>
While dragging, the circle must have turn blue to indicate that a
pointer capture was received.
</TestCase.ExpectedResult>
<DragBox />
</TestCase>
);
}
}
export default Drag;

View File

@@ -0,0 +1,34 @@
const React = window.React;
class DrawBox extends React.Component {
render() {
const boxStyle = {
border: '1px solid #d9d9d9',
margin: '10px 0 20px',
padding: '20px 20px',
touchAction: 'none',
};
const obstacleStyle = {
border: '1px solid #d9d9d9',
width: '25%',
height: '200px',
margin: '12.5%',
display: 'inline-block',
};
return (
<div
style={boxStyle}
onPointerOver={this.props.onOver}
onPointerOut={this.props.onOut}
onPointerEnter={this.props.onEnter}
onPointerLeave={this.props.onLeave}>
<div style={obstacleStyle} />
<div style={obstacleStyle} />
</div>
);
}
}
export default DrawBox;

View File

@@ -0,0 +1,51 @@
import TestCase from '../../TestCase';
import HoverBox from './hover-box';
const React = window.React;
class Hover extends React.Component {
state = {
overs: 0,
outs: 0,
enters: 0,
leaves: 0,
};
onOver = () => this.setState({overs: this.state.overs + 1});
onOut = () => this.setState({outs: this.state.outs + 1});
onEnter = () => this.setState({enters: this.state.enters + 1});
onLeave = () => this.setState({leaves: this.state.leaves + 1});
render() {
const {overs, outs, enters, leaves} = this.state;
return (
<TestCase title="Hover" description="">
<TestCase.Steps>
<li>Hover over the above box and the obstacles</li>
</TestCase.Steps>
<TestCase.ExpectedResult>
Overs and outs should increase when moving over the obstacles but
enters and leaves should not.
</TestCase.ExpectedResult>
<HoverBox
onOver={this.onOver}
onOut={this.onOut}
onEnter={this.onEnter}
onLeave={this.onLeave}
/>
<p>
Pointer Overs: <b>{overs}</b> <br />
Pointer Outs: <b>{outs}</b> <br />
Pointer Enters: <b>{enters}</b> <br />
Pointer Leaves: <b>{leaves}</b> <br />
</p>
</TestCase>
);
}
}
export default Hover;

View File

@@ -0,0 +1,20 @@
import FixtureSet from '../../FixtureSet';
import Drag from './drag';
import Hover from './hover';
const React = window.React;
class PointerEvents extends React.Component {
render() {
return (
<FixtureSet
title="Pointer Events"
description="Pointer Events are not supported in every browser. The examples below might not work in every browser. To test pointer events, make sure to use Google Chrome, Firefox, Internet Explorer, or Edge.">
<Drag />
<Hover />
</FixtureSet>
);
}
}
export default PointerEvents;

View File

@@ -0,0 +1,40 @@
import Fixture from '../../Fixture';
const React = window.React;
class ReplaceEmailInput extends React.Component {
state = {
formSubmitted: false,
};
onReset = () => {
this.setState({formSubmitted: false});
};
onSubmit = event => {
event.preventDefault();
this.setState({formSubmitted: true});
};
render() {
return (
<Fixture>
<form className="control-box" onSubmit={this.onSubmit}>
<fieldset>
<legend>Email</legend>
{!this.state.formSubmitted ? (
<input type="email" />
) : (
<input type="text" disabled={true} />
)}
</fieldset>
</form>
<button type="button" onClick={this.onReset}>
Reset
</button>
</Fixture>
);
}
}
export default ReplaceEmailInput;

View File

@@ -2,6 +2,7 @@ import Fixture from '../../Fixture';
import FixtureSet from '../../FixtureSet';
import TestCase from '../../TestCase';
import InputTestCase from './InputTestCase';
import ReplaceEmailInput from './ReplaceEmailInput';
const React = window.React;
@@ -64,21 +65,36 @@ class TextInputFixtures extends React.Component {
<Fixture>
<form className="control-box">
<fieldset>
<legend>Text</legend>
<legend>Empty value prop string</legend>
<input value="" required={true} />
</fieldset>
<fieldset>
<legend>No value prop</legend>
<input required={true} />
</fieldset>
<fieldset>
<legend>Date</legend>
<legend>Empty defaultValue prop string</legend>
<input required={true} defaultValue="" />
</fieldset>
<fieldset>
<legend>No value prop date input</legend>
<input type="date" required={true} />
</fieldset>
<fieldset>
<legend>Empty value prop date input</legend>
<input type="date" value="" required={true} />
</fieldset>
</form>
</Fixture>
<p className="footnote">
Checking the date type is also important because of a prior fix for
iOS Safari that involved assigning over value/defaultValue
properties of the input to prevent a display bug. This also
triggered input validation.
properties of the input to prevent a display bug. This also triggers
input validation.
</p>
<p className="footnote">
The date inputs should be blank in iOS. This is not a bug.
</p>
</TestCase>
@@ -110,6 +126,21 @@ class TextInputFixtures extends React.Component {
<InputTestCase type="url" defaultValue="" />
</TestCase>
<TestCase
title="Replacing email input with text disabled input"
relatedIssues="12062">
<TestCase.Steps>
<li>Type "test@test.com"</li>
<li>Press enter</li>
</TestCase.Steps>
<TestCase.ExpectedResult>
There should be no selection-related error in the console.
</TestCase.ExpectedResult>
<ReplaceEmailInput />
</TestCase>
<TestCase title="All inputs" description="General test of all inputs">
<InputTestCase type="text" defaultValue="Text" />
<InputTestCase type="email" defaultValue="user@example.com" />

View File

@@ -0,0 +1,494 @@
<!DOCTYPE html>
<html style="width: 100%; height: 100%;">
<head>
<meta charset="utf-8">
<title>Schedule Test Page</title>
<style>
.correct {
border: solid green 2px;
}
.incorrect {
border: dashed red 2px;
}
</style>
</head>
<body>
<h1>Schedule Fixture</h1>
<p>
This fixture is for manual testing purposes, and the patterns used in
implementing it should not be used as a model. This is mainly for anyone
working on making changes to the `schedule` module.
</p>
<h2>Tests:</h2>
<ol>
<li>
<button onClick="runTestOne()">Run Test 1</button>
<p>Calls the callback within the frame when not blocked:</p>
<div><b>Expected:</b></div>
<div id="test-1-expected">
</div>
<div> -------------------------------------------------</div>
<div> If you see the same above and below it's correct.
<div> -------------------------------------------------</div>
<div><b>Actual:</b></div>
<div id="test-1"></div>
</li>
<li>
<p>Accepts multiple callbacks and calls within frame when not blocked</p>
<button onClick="runTestTwo()">Run Test 2</button>
<div><b>Expected:</b></div>
<div id="test-2-expected">
</div>
<div> -------------------------------------------------</div>
<div> If you see the same above and below it's correct.
<div> -------------------------------------------------</div>
<div><b>Actual:</b></div>
<div id="test-2"></div>
</li>
<li>
<p>Schedules callbacks in correct order when they use scheduleWork to schedule themselves</p>
<button onClick="runTestThree()">Run Test 3</button>
<div><b>Expected:</b></div>
<div id="test-3-expected">
</div>
<div> -------------------------------------------------</div>
<div> If you see the same above and below it's correct.
<div> -------------------------------------------------</div>
<div><b>Actual:</b></div>
<div id="test-3"></div>
</li>
<li>
<p>Calls timed out callbacks and then any more pending callbacks, defers others if time runs out</p>
<button onClick="runTestFour()">Run Test 4</button>
<div><b>Expected:</b></div>
<div id="test-4-expected">
</div>
<div> -------------------------------------------------</div>
<div> If you see the same above and below it's correct.
<div> -------------------------------------------------</div>
<div><b>Actual:</b></div>
<div id="test-4"></div>
</li>
<li>
<p>When some callbacks throw errors, still calls them all within the same frame</p>
<p><b>IMPORTANT:</b> Open the console when you run this! Inspect the logs there!</p>
<button onClick="runTestFive()">Run Test 5</button>
</li>
<li>
<p>When some callbacks throw errors <b> and some also time out</b>, still calls them all within the same frame</p>
<p><b>IMPORTANT:</b> Open the console when you run this! Inspect the logs there!</p>
<button onClick="runTestSix()">Run Test 6</button>
</li>
<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>
<script type="text/babel">
const {scheduleWork, cancelWork, now} = ReactScheduler;
function displayTestResult(testNumber) {
const expectationNode = document.getElementById('test-' + testNumber + '-expected');
const resultNode = document.getElementById('test-' + testNumber);
resultNode.innerHTML = latestResults[testNumber - 1].join('<br />');
expectationNode.innerHTML = expectedResults[testNumber - 1].join('<br />');
}
function clearTestResult(testNumber) {
const resultNode = document.getElementById('test-' + testNumber);
resultNode.innerHTML = '';
latestResults[testNumber - 1] = [];
}
function updateTestResult(testNumber, textToAddToResult) {
latestResults[testNumber - 1].push(textToAddToResult);
};
function checkTestResult(testNumber) {
let correct = true;
const expected = expectedResults[testNumber - 1]; // zero indexing
const result = latestResults[testNumber - 1]; // zero indexing
if (expected.length !== result.length) {
correct = false;
} else {
for (let i = 0, len = expected.length; i < len; i++) {
if (expected[i] !== result[i]) {
correct = false;
break;
}
}
}
const currentClass = correct ? 'correct' : 'incorrect';
const previousClass = correct ? 'incorrect' : 'correct';
document.getElementById('test-' + testNumber).classList.remove(previousClass);
document.getElementById('test-' + testNumber).classList.add(currentClass);
}
function logWhenFramesStart(testNumber, cb) {
requestAnimationFrame(() => {
updateTestResult(testNumber, 'frame 1 started');
requestAnimationFrame(() => {
updateTestResult(testNumber, 'frame 2 started');
requestAnimationFrame(() => {
updateTestResult(testNumber, 'frame 3 started... we stop counting now.');
cb();
});
});
});
}
// push in results when we run the test
const latestResults = [
// test 1
[
],
// test 2
[
],
// test 3
[
],
// test 4
[
],
// test 5
[
],
];
const expectedResults = [
// test 1
[
'scheduled Cb1',
'frame 1 started',
'cb1 called with argument of {"didTimeout":false}',
'frame 2 started',
'frame 3 started... we stop counting now.',
],
// test 2
[
'scheduled CbA',
'scheduled CbB',
'frame 1 started',
'cbA called with argument of {"didTimeout":false}',
'cbB called with argument of {"didTimeout":false}',
'frame 2 started',
'frame 3 started... we stop counting now.',
],
// test 3
[
'scheduled CbA',
'scheduled CbB',
'frame 1 started',
'scheduled CbA again',
'cbA0 called with argument of {"didTimeout":false}',
'cbB called with argument of {"didTimeout":false}',
'cbA1 called with argument of {"didTimeout":false}',
'frame 2 started',
'frame 3 started... we stop counting now.',
],
// test 4
[
'scheduled cbA',
'scheduled cbB',
'scheduled cbC',
'scheduled cbD',
'frame 1 started',
'cbC called with argument of {"didTimeout":true}',
'cbA called with argument of {"didTimeout":false}',
'cbA running and taking some time',
'frame 2 started',
'cbB called with argument of {"didTimeout":false}',
'cbD called with argument of {"didTimeout":false}',
'frame 3 started... we stop counting now.',
],
// test 5
[
// ... TODO
],
];
function runTestOne() {
// Test 1
// Calls the callback with the frame when not blocked
clearTestResult(1);
const test1Log = [];
const cb1Arguments = [];
const cb1 = (x) => {
updateTestResult(1, 'cb1 called with argument of ' + JSON.stringify(x));
}
scheduleWork(cb1);
updateTestResult(1, 'scheduled Cb1');
logWhenFramesStart(1, () => {
displayTestResult(1);
checkTestResult(1);
});
};
function runTestTwo() {
// Test 2
// accepts multiple callbacks and calls within frame when not blocked
clearTestResult(2);
const cbA = (x) => {
updateTestResult(2, 'cbA called with argument of ' + JSON.stringify(x));
}
const cbB = (x) => {
updateTestResult(2, 'cbB called with argument of ' + JSON.stringify(x));
}
scheduleWork(cbA);
updateTestResult(2, 'scheduled CbA');
scheduleWork(cbB);
updateTestResult(2, 'scheduled CbB');
logWhenFramesStart(2, () => {
displayTestResult(2);
checkTestResult(2);
});
}
function runTestThree() {
// Test 3
// Schedules callbacks in correct order when they use scheduleWork to schedule themselves
clearTestResult(3);
let callbackAIterations = 0;
const cbA = (x) => {
if (callbackAIterations < 1) {
scheduleWork(cbA);
updateTestResult(3, 'scheduled CbA again');
}
updateTestResult(3, 'cbA' + callbackAIterations + ' called with argument of ' + JSON.stringify(x));
callbackAIterations++;
}
const cbB = (x) => {
updateTestResult(3, 'cbB called with argument of ' + JSON.stringify(x));
}
scheduleWork(cbA);
updateTestResult(3, 'scheduled CbA');
scheduleWork(cbB);
updateTestResult(3, 'scheduled CbB');
logWhenFramesStart(3, () => {
displayTestResult(3);
checkTestResult(3);
});
}
function waitForTimeToPass(timeInMs) {
const startTime = Date.now();
const endTime = startTime + timeInMs;
while (Date.now() < endTime) {
// wait...
}
}
function runTestFour() {
// Test 4
// Calls timed out callbacks and then any more pending callbacks, defers others if time runs out
clearTestResult(4);
const cbA = (x) => {
updateTestResult(4, 'cbA called with argument of ' + JSON.stringify(x));
updateTestResult(4, 'cbA running and taking some time');
waitForTimeToPass(35);
}
const cbB = (x) => {
updateTestResult(4, 'cbB called with argument of ' + JSON.stringify(x));
}
const cbC = (x) => {
updateTestResult(4, 'cbC called with argument of ' + JSON.stringify(x));
}
const cbD = (x) => {
updateTestResult(4, 'cbD called with argument of ' + JSON.stringify(x));
}
scheduleWork(cbA); // won't time out
updateTestResult(4, 'scheduled cbA');
scheduleWork(cbB, {timeout: 100}); // times out later
updateTestResult(4, 'scheduled cbB');
scheduleWork(cbC, {timeout: 1}); // will time out fast
updateTestResult(4, 'scheduled cbC');
scheduleWork(cbD); // won't time out
updateTestResult(4, 'scheduled cbD');
// should have run in order of C, A, B, D
logWhenFramesStart(4, () => {
displayTestResult(4);
checkTestResult(4);
});
}
// Error handling
function runTestFive() {
// Test 5
// When some callbacks throw errors, still calls them all within the same frame
const cbA = (x) => {
console.log('cbA called with argument of ' + JSON.stringify(x));
}
const cbB = (x) => {
console.log('cbB called with argument of ' + JSON.stringify(x));
console.log('cbB is about to throw an error!');
throw new Error('error B');
}
const cbC = (x) => {
console.log('cbC called with argument of ' + JSON.stringify(x));
}
const cbD = (x) => {
console.log('cbD called with argument of ' + JSON.stringify(x));
console.log('cbD is about to throw an error!');
throw new Error('error D');
}
const cbE = (x) => {
console.log('cbE called with argument of ' + JSON.stringify(x));
console.log('This was the last callback! ------------------');
}
console.log('We are aiming to roughly emulate the way ' +
'`requestAnimationFrame` handles errors from callbacks.');
console.log('about to run the simulation of what it should look like...:');
requestAnimationFrame(() => {
console.log('frame 1 started');
requestAnimationFrame(() => {
console.log('frame 2 started');
requestAnimationFrame(() => {
console.log('frame 3 started... we stop counting now.');
console.log('about to wait a moment and start this again but ' +
'with the scheduler instead of requestAnimationFrame');
setTimeout(runSchedulerCode, 1000);
});
});
});
requestAnimationFrame(cbA);
console.log('scheduled cbA');
requestAnimationFrame(cbB); // will throw error
console.log('scheduled cbB');
requestAnimationFrame(cbC);
console.log('scheduled cbC');
requestAnimationFrame(cbD); // will throw error
console.log('scheduled cbD');
requestAnimationFrame(cbE);
console.log('scheduled cbE');
function runSchedulerCode() {
console.log('-------------------------------------------------------------');
console.log('now lets see what it looks like using the scheduler...:');
requestAnimationFrame(() => {
console.log('frame 1 started');
requestAnimationFrame(() => {
console.log('frame 2 started');
requestAnimationFrame(() => {
console.log('frame 3 started... we stop counting now.');
});
});
});
scheduleWork(cbA);
console.log('scheduled cbA');
scheduleWork(cbB); // will throw error
console.log('scheduled cbB');
scheduleWork(cbC);
console.log('scheduled cbC');
scheduleWork(cbD); // will throw error
console.log('scheduled cbD');
scheduleWork(cbE);
console.log('scheduled cbE');
};
}
function runTestSix() {
// Test 6
// When some callbacks throw errors, still calls them all within the same frame
const cbA = (x) => {
console.log('cbA called with argument of ' + JSON.stringify(x));
console.log('cbA is about to throw an error!');
throw new Error('error A');
}
const cbB = (x) => {
console.log('cbB called with argument of ' + JSON.stringify(x));
}
const cbC = (x) => {
console.log('cbC called with argument of ' + JSON.stringify(x));
}
const cbD = (x) => {
console.log('cbD called with argument of ' + JSON.stringify(x));
console.log('cbD is about to throw an error!');
throw new Error('error D');
}
const cbE = (x) => {
console.log('cbE called with argument of ' + JSON.stringify(x));
console.log('This was the last callback! ------------------');
}
console.log('We are aiming to roughly emulate the way ' +
'`requestAnimationFrame` handles errors from callbacks.');
console.log('about to run the simulation of what it should look like...:');
requestAnimationFrame(() => {
console.log('frame 1 started');
requestAnimationFrame(() => {
console.log('frame 2 started');
requestAnimationFrame(() => {
console.log('frame 3 started... we stop counting now.');
console.log('about to wait a moment and start this again but ' +
'with the scheduler instead of requestAnimationFrame');
setTimeout(runSchedulerCode, 1000);
});
});
});
requestAnimationFrame(cbC);
console.log('scheduled cbC first; simulating timing out');
requestAnimationFrame(cbD); // will throw error
console.log('scheduled cbD first; simulating timing out');
requestAnimationFrame(cbE);
console.log('scheduled cbE first; simulating timing out');
requestAnimationFrame(cbA);
console.log('scheduled cbA'); // will throw error
requestAnimationFrame(cbB);
console.log('scheduled cbB');
function runSchedulerCode() {
console.log('-------------------------------------------------------------');
console.log('now lets see what it looks like using the scheduler...:');
requestAnimationFrame(() => {
console.log('frame 1 started');
requestAnimationFrame(() => {
console.log('frame 2 started');
requestAnimationFrame(() => {
console.log('frame 3 started... we stop counting now.');
});
});
});
scheduleWork(cbA);
console.log('scheduled cbA');
scheduleWork(cbB); // will throw error
console.log('scheduled cbB');
scheduleWork(cbC, {timeout: 1});
console.log('scheduled cbC');
scheduleWork(cbD, {timeout: 1}); // will throw error
console.log('scheduled cbD');
scheduleWork(cbE, {timeout: 1});
console.log('scheduled cbE');
};
}
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>

9
netlify.toml Normal file
View File

@@ -0,0 +1,9 @@
[build]
base = ""
publish = "fixtures/dom/build"
command = "yarn build --type=UMD_DEV && cd fixtures/dom/ && yarn && yarn prestart && yarn build"
[[redirects]]
from = "/*"
to = "/index.html"
status = 200

View File

@@ -1,6 +1,6 @@
{
"private": true,
"version": "16.3.2",
"version": "16.4.1",
"workspaces": [
"packages/*"
],
@@ -11,7 +11,7 @@
"babel-code-frame": "^6.26.0",
"babel-core": "^6.0.0",
"babel-eslint": "^8.0.0",
"babel-jest": "^22.0.6",
"babel-jest": "^23.0.1",
"babel-plugin-check-es2015-constants": "^6.5.0",
"babel-plugin-external-helpers": "^6.22.0",
"babel-plugin-syntax-trailing-function-commas": "^6.5.0",
@@ -31,14 +31,12 @@
"babel-plugin-transform-es2015-shorthand-properties": "^6.5.0",
"babel-plugin-transform-es2015-spread": "^6.5.2",
"babel-plugin-transform-es2015-template-literals": "^6.5.2",
"babel-plugin-transform-es3-member-expression-literals": "^6.5.0",
"babel-plugin-transform-es3-property-literals": "^6.5.0",
"babel-plugin-transform-object-rest-spread": "^6.6.5",
"babel-plugin-transform-react-jsx-source": "^6.8.0",
"babel-plugin-transform-regenerator": "^6.26.0",
"babel-preset-react": "^6.5.0",
"babel-traverse": "^6.9.0",
"babylon": "6.15.0",
"babylon": "6.18.0",
"bundle-collapser": "^1.1.1",
"chalk": "^1.1.3",
"cli-table": "^0.3.1",
@@ -59,19 +57,19 @@
"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.61.0",
"flow-bin": "^0.72.0",
"flow-coverage-report": "^0.4.0",
"git-branch": "^0.3.0",
"glob": "^6.0.4",
"glob-stream": "^6.1.0",
"google-closure-compiler": "20180506.0.0",
"gzip-js": "~0.3.2",
"gzip-size": "^3.0.0",
"jasmine-check": "^1.0.0-rc.0",
"jest": "^22.0.6",
"jest-diff": "^22.1.0",
"jest": "^23.1.0",
"jest-diff": "^23.0.1",
"merge-stream": "^1.0.0",
"minimatch": "^3.0.4",
"minimist": "^1.2.0",
@@ -79,14 +77,13 @@
"ncp": "^2.0.0",
"object-assign": "^4.1.1",
"platform": "^1.1.0",
"prettier": "1.8.1",
"prop-types": "^15.6.0",
"prettier": "1.11.1",
"prop-types": "^15.6.2",
"random-seed": "^0.3.0",
"react-lifecycles-compat": "^1.0.2",
"react-lifecycles-compat": "^3.0.2",
"rimraf": "^2.6.1",
"rollup": "^0.52.1",
"rollup-plugin-babel": "^3.0.1",
"rollup-plugin-closure-compiler-js": "^1.0.6",
"rollup-plugin-commonjs": "^8.2.6",
"rollup-plugin-node-resolve": "^2.1.1",
"rollup-plugin-prettier": "^0.3.0",
@@ -103,13 +100,16 @@
"devEngines": {
"node": "8.x || 9.x"
},
"jest": {
"testRegex": "/scripts/jest/dont-run-jest-directly\\.js$"
},
"scripts": {
"build": "npm run version-check && node ./scripts/rollup/build.js",
"flow-coverage": "flow-coverage-report --config ./.flowcoverage",
"linc": "node ./scripts/tasks/linc.js",
"lint": "node ./scripts/tasks/eslint.js",
"lint-build": "node ./scripts/rollup/validate/index.js",
"postinstall": "node node_modules/fbjs-scripts/node/check-dev-engines.js package.json",
"postinstall": "node node_modules/fbjs-scripts/node/check-dev-engines.js package.json && node ./scripts/flow/createFlowConfigs.js",
"debug-test": "cross-env NODE_ENV=development node --inspect-brk node_modules/.bin/jest --config ./scripts/jest/config.source.js --runInBand",
"test": "cross-env NODE_ENV=development jest --config ./scripts/jest/config.source.js",
"test-prod": "cross-env NODE_ENV=production jest --config ./scripts/jest/config.source.js",
@@ -117,6 +117,7 @@
"test-build": "cross-env NODE_ENV=development jest --config ./scripts/jest/config.build.js",
"test-build-prod": "cross-env NODE_ENV=production jest --config ./scripts/jest/config.build.js",
"flow": "node ./scripts/tasks/flow.js",
"flow-ci": "node ./scripts/tasks/flow-ci.js",
"prettier": "node ./scripts/prettier/index.js write-changed",
"prettier-all": "node ./scripts/prettier/index.js write",
"version-check": "node ./scripts/tasks/version-check.js"

View File

@@ -1,7 +1,7 @@
{
"name": "create-subscription",
"description": "utility for subscribing to external data sources inside React components",
"version": "16.3.2",
"version": "16.4.1",
"repository": "facebook/react",
"files": [
"LICENSE",
@@ -9,9 +9,6 @@
"index.js",
"cjs/"
],
"dependencies": {
"fbjs": "^0.8.16"
},
"peerDependencies": {
"react": "^16.3.0"
},

View File

@@ -264,7 +264,6 @@ describe('createSubscription', () => {
it('should ignore values emitted by a new subscribable until the commit phase', () => {
const log = [];
let parentInstance;
function Child({value}) {
ReactNoop.yield('Child: ' + value);
@@ -301,8 +300,6 @@ describe('createSubscription', () => {
}
render() {
parentInstance = this;
return (
<Subscription source={this.state.observed}>
{(value = 'default') => {
@@ -331,8 +328,8 @@ describe('createSubscription', () => {
observableB.next('b-2');
observableB.next('b-3');
// Mimic a higher-priority interruption
parentInstance.setState({observed: observableA});
// Update again
ReactNoop.render(<Parent observed={observableA} />);
// Flush everything and ensure that the correct subscribable is used
// We expect the last emitted update to be rendered (because of the commit phase value check)
@@ -354,7 +351,6 @@ describe('createSubscription', () => {
it('should not drop values emitted between updates', () => {
const log = [];
let parentInstance;
function Child({value}) {
ReactNoop.yield('Child: ' + value);
@@ -391,8 +387,6 @@ describe('createSubscription', () => {
}
render() {
parentInstance = this;
return (
<Subscription source={this.state.observed}>
{(value = 'default') => {
@@ -420,8 +414,8 @@ describe('createSubscription', () => {
observableA.next('a-1');
observableA.next('a-2');
// Mimic a higher-priority interruption
parentInstance.setState({observed: observableA});
// Update again
ReactNoop.render(<Parent observed={observableA} />);
// Flush everything and ensure that the correct subscribable is used
// We expect the new subscribable to finish rendering,

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 warning from 'shared/warning';
type Unsubscribe = () => void;

View File

@@ -7,7 +7,7 @@
*/
import ReactErrorUtils from 'shared/ReactErrorUtils';
import invariant from 'fbjs/lib/invariant';
import invariant from 'shared/invariant';
import {
injectEventPluginOrder,
@@ -25,6 +25,7 @@ import type {PluginModule} from './PluginModuleType';
import type {ReactSyntheticEvent} from './ReactSyntheticEventType';
import type {Fiber} from 'react-reconciler/src/ReactFiber';
import type {AnyNativeEvent} from './PluginModuleType';
import type {TopLevelType} from './TopLevelEventTypes';
/**
* Internal queue of events that have accumulated their dispatches and are
@@ -165,7 +166,7 @@ export function getListener(inst: Fiber, registrationName: string) {
* @internal
*/
function extractEvents(
topLevelType: string,
topLevelType: TopLevelType,
targetInst: Fiber,
nativeEvent: AnyNativeEvent,
nativeEventTarget: EventTarget,
@@ -227,7 +228,7 @@ export function runEventsInBatch(
}
export function runExtractedEventsInBatch(
topLevelType: string,
topLevelType: TopLevelType,
targetInst: Fiber,
nativeEvent: AnyNativeEvent,
nativeEventTarget: EventTarget,

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 warning from 'shared/warning';
export let getFiberCurrentPropsFromNode = null;
export let getInstanceFromNode = null;
@@ -30,21 +30,6 @@ export const injection = {
},
};
export function isEndish(topLevelType) {
return (
topLevelType === 'topMouseUp' ||
topLevelType === 'topTouchEnd' ||
topLevelType === 'topTouchCancel'
);
}
export function isMoveish(topLevelType) {
return topLevelType === 'topMouseMove' || topLevelType === 'topTouchMove';
}
export function isStartish(topLevelType) {
return topLevelType === 'topMouseDown' || topLevelType === 'topTouchStart';
}
let validateEventDispatches;
if (__DEV__) {
validateEventDispatches = function(event) {

View File

@@ -10,7 +10,7 @@ import {
traverseTwoPhase,
traverseEnterLeave,
} from 'shared/ReactTreeTraversal';
import warning from 'fbjs/lib/warning';
import warning from 'shared/warning';
import {getListener} from './EventPluginHub';
import accumulateInto from './accumulateInto';

View File

@@ -12,6 +12,7 @@ import type {
DispatchConfig,
ReactSyntheticEvent,
} from './ReactSyntheticEventType';
import type {TopLevelType} from './TopLevelEventTypes';
export type EventTypes = {[key: string]: DispatchConfig};
@@ -22,7 +23,7 @@ export type PluginName = string;
export type PluginModule<NativeEvent> = {
eventTypes: EventTypes,
extractEvents: (
topLevelType: string,
topLevelType: TopLevelType,
targetInst: Fiber,
nativeTarget: NativeEvent,
nativeEventTarget: EventTarget,

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

@@ -9,9 +9,10 @@
*/
import type {Fiber} from 'react-reconciler/src/ReactFiber';
import type {TopLevelType} from './TopLevelEventTypes';
export type DispatchConfig = {
dependencies: Array<string>,
dependencies: Array<TopLevelType>,
phasedRegistrationNames?: {
bubbled: string,
captured: string,

View File

@@ -8,9 +8,6 @@
import {getLowestCommonAncestor, isAncestor} from 'shared/ReactTreeTraversal';
import {
isStartish,
isMoveish,
isEndish,
executeDirectDispatch,
hasDispatches,
executeDispatchesInOrderStopAtTrue,
@@ -24,6 +21,17 @@ import {
import ResponderSyntheticEvent from './ResponderSyntheticEvent';
import ResponderTouchHistoryStore from './ResponderTouchHistoryStore';
import accumulate from './accumulate';
import {
TOP_SCROLL,
TOP_SELECTION_CHANGE,
TOP_TOUCH_CANCEL,
isStartish,
isMoveish,
isEndish,
startDependencies,
moveDependencies,
endDependencies,
} from './ResponderTopLevelEventTypes';
/**
* Instance of element that should respond to touch/move types of interactions,
@@ -37,11 +45,6 @@ let responderInst = null;
*/
let trackedTouchCount = 0;
/**
* Last reported number of active touches.
*/
let previousActiveTouches = 0;
const changeResponder = function(nextResponderInst, blockHostResponder) {
const oldResponderInst = responderInst;
responderInst = nextResponderInst;
@@ -64,6 +67,7 @@ const eventTypes = {
bubbled: 'onStartShouldSetResponder',
captured: 'onStartShouldSetResponderCapture',
},
dependencies: startDependencies,
},
/**
@@ -80,6 +84,7 @@ const eventTypes = {
bubbled: 'onScrollShouldSetResponder',
captured: 'onScrollShouldSetResponderCapture',
},
dependencies: [TOP_SCROLL],
},
/**
@@ -94,6 +99,7 @@ const eventTypes = {
bubbled: 'onSelectionChangeShouldSetResponder',
captured: 'onSelectionChangeShouldSetResponderCapture',
},
dependencies: [TOP_SELECTION_CHANGE],
},
/**
@@ -105,21 +111,44 @@ const eventTypes = {
bubbled: 'onMoveShouldSetResponder',
captured: 'onMoveShouldSetResponderCapture',
},
dependencies: moveDependencies,
},
/**
* Direct responder events dispatched directly to responder. Do not bubble.
*/
responderStart: {registrationName: 'onResponderStart'},
responderMove: {registrationName: 'onResponderMove'},
responderEnd: {registrationName: 'onResponderEnd'},
responderRelease: {registrationName: 'onResponderRelease'},
responderStart: {
registrationName: 'onResponderStart',
dependencies: startDependencies,
},
responderMove: {
registrationName: 'onResponderMove',
dependencies: moveDependencies,
},
responderEnd: {
registrationName: 'onResponderEnd',
dependencies: endDependencies,
},
responderRelease: {
registrationName: 'onResponderRelease',
dependencies: endDependencies,
},
responderTerminationRequest: {
registrationName: 'onResponderTerminationRequest',
dependencies: [],
},
responderGrant: {
registrationName: 'onResponderGrant',
dependencies: [],
},
responderReject: {
registrationName: 'onResponderReject',
dependencies: [],
},
responderTerminate: {
registrationName: 'onResponderTerminate',
dependencies: [],
},
responderGrant: {registrationName: 'onResponderGrant'},
responderReject: {registrationName: 'onResponderReject'},
responderTerminate: {registrationName: 'onResponderTerminate'},
};
/**
@@ -322,7 +351,7 @@ function setResponderAndExtractTransfer(
? eventTypes.startShouldSetResponder
: isMoveish(topLevelType)
? eventTypes.moveShouldSetResponder
: topLevelType === 'topSelectionChange'
: topLevelType === TOP_SELECTION_CHANGE
? eventTypes.selectionChangeShouldSetResponder
: eventTypes.scrollShouldSetResponder;
@@ -427,8 +456,8 @@ function canTriggerTransfer(topLevelType, topLevelInst, nativeEvent) {
// responderIgnoreScroll: We are trying to migrate away from specifically
// tracking native scroll events here and responderIgnoreScroll indicates we
// will send topTouchCancel to handle canceling touch events instead
((topLevelType === 'topScroll' && !nativeEvent.responderIgnoreScroll) ||
(trackedTouchCount > 0 && topLevelType === 'topSelectionChange') ||
((topLevelType === TOP_SCROLL && !nativeEvent.responderIgnoreScroll) ||
(trackedTouchCount > 0 && topLevelType === TOP_SELECTION_CHANGE) ||
isStartish(topLevelType) ||
isMoveish(topLevelType))
);
@@ -534,7 +563,7 @@ const ResponderEventPlugin = {
}
const isResponderTerminate =
responderInst && topLevelType === 'topTouchCancel';
responderInst && topLevelType === TOP_TOUCH_CANCEL;
const isResponderRelease =
responderInst &&
!isResponderTerminate &&
@@ -556,23 +585,10 @@ const ResponderEventPlugin = {
changeResponder(null);
}
const numberActiveTouches =
ResponderTouchHistoryStore.touchHistory.numberActiveTouches;
if (
ResponderEventPlugin.GlobalInteractionHandler &&
numberActiveTouches !== previousActiveTouches
) {
ResponderEventPlugin.GlobalInteractionHandler.onChange(
numberActiveTouches,
);
}
previousActiveTouches = numberActiveTouches;
return extracted;
},
GlobalResponderHandler: null,
GlobalInteractionHandler: null,
injection: {
/**
@@ -580,17 +596,9 @@ const ResponderEventPlugin = {
* Object that handles any change in responder. Use this to inject
* integration with an existing touch handling system etc.
*/
injectGlobalResponderHandler: function(GlobalResponderHandler) {
injectGlobalResponderHandler(GlobalResponderHandler) {
ResponderEventPlugin.GlobalResponderHandler = GlobalResponderHandler;
},
/**
* @param {{onChange: (numberActiveTouches) => void} GlobalInteractionHandler
* Object that handles any change in the number of active touches.
*/
injectGlobalInteractionHandler: function(GlobalInteractionHandler) {
ResponderEventPlugin.GlobalInteractionHandler = GlobalInteractionHandler;
},
},
};

View File

@@ -0,0 +1,31 @@
/**
* Copyright (c) 2013-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
export const TOP_TOUCH_START = 'topTouchStart';
export const TOP_TOUCH_MOVE = 'topTouchMove';
export const TOP_TOUCH_END = 'topTouchEnd';
export const TOP_TOUCH_CANCEL = 'topTouchCancel';
export const TOP_SCROLL = 'topScroll';
export const TOP_SELECTION_CHANGE = 'topSelectionChange';
export function isStartish(topLevelType: mixed): boolean {
return topLevelType === TOP_TOUCH_START;
}
export function isMoveish(topLevelType: mixed): boolean {
return topLevelType === TOP_TOUCH_MOVE;
}
export function isEndish(topLevelType: mixed): boolean {
return topLevelType === TOP_TOUCH_END || topLevelType === TOP_TOUCH_CANCEL;
}
export const startDependencies = [TOP_TOUCH_START];
export const moveDependencies = [TOP_TOUCH_MOVE];
export const endDependencies = [TOP_TOUCH_CANCEL, TOP_TOUCH_END];

View File

@@ -7,10 +7,10 @@
* @flow
*/
import invariant from 'fbjs/lib/invariant';
import warning from 'fbjs/lib/warning';
import invariant from 'shared/invariant';
import warning from 'shared/warning';
import {isEndish, isMoveish, isStartish} from './EventPluginUtils';
import {isStartish, isMoveish, isEndish} from './ResponderTopLevelEventTypes';
/**
* Tracks the position and time of each active touch by `touch.identifier`. We

View File

@@ -7,9 +7,8 @@
/* 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 warning from 'shared/warning';
let didWarnForAddedNewProperty = false;
const EVENT_POOL_SIZE = 10;
@@ -32,7 +31,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 +44,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.
@@ -103,11 +112,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 +133,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 +153,7 @@ Object.assign(SyntheticEvent.prototype, {
event.cancelBubble = true;
}
this.isPropagationStopped = emptyFunction.thatReturnsTrue;
this.isPropagationStopped = functionThatReturnsTrue;
},
/**
@@ -153,7 +162,7 @@ Object.assign(SyntheticEvent.prototype, {
* won't be added back into the pool.
*/
persist: function() {
this.isPersistent = emptyFunction.thatReturnsTrue;
this.isPersistent = functionThatReturnsTrue;
},
/**
@@ -161,7 +170,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.
@@ -191,12 +200,12 @@ Object.assign(SyntheticEvent.prototype, {
Object.defineProperty(
this,
'preventDefault',
getPooledWarningPropertyDefinition('preventDefault', emptyFunction),
getPooledWarningPropertyDefinition('preventDefault', () => {}),
);
Object.defineProperty(
this,
'stopPropagation',
getPooledWarningPropertyDefinition('stopPropagation', emptyFunction),
getPooledWarningPropertyDefinition('stopPropagation', () => {}),
);
}
},

View File

@@ -0,0 +1,39 @@
/**
* Copyright (c) 2013-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
export type RNTopLevelEventType =
| 'topMouseDown'
| 'topMouseMove'
| 'topMouseUp'
| 'topScroll'
| 'topSelectionChange'
| 'topTouchCancel'
| 'topTouchEnd'
| 'topTouchMove'
| 'topTouchStart';
export opaque type DOMTopLevelEventType = string;
// Do not uses the below two methods directly!
// Instead use constants exported from DOMTopLevelEventTypes in ReactDOM.
// (It is the only module that is allowed to access these methods.)
export function unsafeCastStringToDOMTopLevelType(
topLevelType: string,
): DOMTopLevelEventType {
return topLevelType;
}
export function unsafeCastDOMTopLevelTypeToString(
topLevelType: DOMTopLevelEventType,
): string {
return topLevelType;
}
export type TopLevelType = DOMTopLevelEventType | RNTopLevelEventType;

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

@@ -0,0 +1,41 @@
/**
* Copyright (c) 2013-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
// Note: ideally these would be imported from DOMTopLevelEventTypes,
// but our build system currently doesn't let us do that from a fork.
export const TOP_TOUCH_START = 'touchstart';
export const TOP_TOUCH_MOVE = 'touchmove';
export const TOP_TOUCH_END = 'touchend';
export const TOP_TOUCH_CANCEL = 'touchcancel';
export const TOP_SCROLL = 'scroll';
export const TOP_SELECTION_CHANGE = 'selectionchange';
export const TOP_MOUSE_DOWN = 'mousedown';
export const TOP_MOUSE_MOVE = 'mousemove';
export const TOP_MOUSE_UP = 'mouseup';
export function isStartish(topLevelType: mixed): boolean {
return topLevelType === TOP_TOUCH_START || topLevelType === TOP_MOUSE_DOWN;
}
export function isMoveish(topLevelType: mixed): boolean {
return topLevelType === TOP_TOUCH_MOVE || topLevelType === TOP_MOUSE_MOVE;
}
export function isEndish(topLevelType: mixed): boolean {
return (
topLevelType === TOP_TOUCH_END ||
topLevelType === TOP_TOUCH_CANCEL ||
topLevelType === TOP_MOUSE_UP
);
}
export const startDependencies = [TOP_TOUCH_START, TOP_MOUSE_DOWN];
export const moveDependencies = [TOP_TOUCH_MOVE, TOP_MOUSE_MOVE];
export const endDependencies = [TOP_TOUCH_CANCEL, TOP_TOUCH_END, TOP_MOUSE_UP];

View File

@@ -1,7 +1,7 @@
{
"name": "react-art",
"description": "React ART is a JavaScript library for drawing vector graphics using React. It provides declarative and reactive bindings to the ART library. Using the same declarative API you can render the output to either Canvas, SVG or VML (IE8).",
"version": "16.3.2",
"version": "16.4.1",
"main": "index.js",
"repository": "facebook/react",
"keywords": [
@@ -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,268 +6,18 @@
*/
import React from 'react';
import ReactFiberReconciler from 'react-reconciler';
import * as ReactDOMFrameScheduling from 'shared/ReactDOMFrameScheduling';
import * as ARTRenderer from 'react-reconciler/inline.art';
import Transform from 'art/core/transform';
import Mode from 'art/modes/current';
import FastNoSideEffects from 'art/modes/fast-noSideEffects';
import Transform from 'art/core/transform';
import invariant from 'fbjs/lib/invariant';
import emptyObject from 'fbjs/lib/emptyObject';
import {TYPES, childrenAsString} from './ReactARTInternals';
Mode.setCurrent(
// Change to 'art/modes/dom' for easier debugging via SVG
FastNoSideEffects,
);
const pooledTransform = new Transform();
const EVENT_TYPES = {
onClick: 'click',
onMouseMove: 'mousemove',
onMouseOver: 'mouseover',
onMouseOut: 'mouseout',
onMouseUp: 'mouseup',
onMouseDown: 'mousedown',
};
const TYPES = {
CLIPPING_RECTANGLE: 'ClippingRectangle',
GROUP: 'Group',
SHAPE: 'Shape',
TEXT: 'Text',
};
const UPDATE_SIGNAL = {};
/** Helper Methods */
function addEventListeners(instance, type, listener) {
// We need to explicitly unregister before unmount.
// For this reason we need to track subscriptions.
if (!instance._listeners) {
instance._listeners = {};
instance._subscriptions = {};
}
instance._listeners[type] = listener;
if (listener) {
if (!instance._subscriptions[type]) {
instance._subscriptions[type] = instance.subscribe(
type,
createEventHandler(instance),
instance,
);
}
} else {
if (instance._subscriptions[type]) {
instance._subscriptions[type]();
delete instance._subscriptions[type];
}
}
}
function childrenAsString(children) {
if (!children) {
return '';
} else if (typeof children === 'string') {
return children;
} else if (children.length) {
return children.join('');
} else {
return '';
}
}
function createEventHandler(instance) {
return function handleEvent(event) {
const listener = instance._listeners[event.type];
if (!listener) {
// Noop
} else if (typeof listener === 'function') {
listener.call(instance, event);
} else if (listener.handleEvent) {
listener.handleEvent(event);
}
};
}
function destroyEventListeners(instance) {
if (instance._subscriptions) {
for (let type in instance._subscriptions) {
instance._subscriptions[type]();
}
}
instance._subscriptions = null;
instance._listeners = null;
}
function getScaleX(props) {
if (props.scaleX != null) {
return props.scaleX;
} else if (props.scale != null) {
return props.scale;
} else {
return 1;
}
}
function getScaleY(props) {
if (props.scaleY != null) {
return props.scaleY;
} else if (props.scale != null) {
return props.scale;
} else {
return 1;
}
}
function isSameFont(oldFont, newFont) {
if (oldFont === newFont) {
return true;
} else if (typeof newFont === 'string' || typeof oldFont === 'string') {
return false;
} else {
return (
newFont.fontSize === oldFont.fontSize &&
newFont.fontStyle === oldFont.fontStyle &&
newFont.fontVariant === oldFont.fontVariant &&
newFont.fontWeight === oldFont.fontWeight &&
newFont.fontFamily === oldFont.fontFamily
);
}
}
/** Render Methods */
function applyClippingRectangleProps(instance, props, prevProps = {}) {
applyNodeProps(instance, props, prevProps);
instance.width = props.width;
instance.height = props.height;
}
function applyGroupProps(instance, props, prevProps = {}) {
applyNodeProps(instance, props, prevProps);
instance.width = props.width;
instance.height = props.height;
}
function applyNodeProps(instance, props, prevProps = {}) {
const scaleX = getScaleX(props);
const scaleY = getScaleY(props);
pooledTransform
.transformTo(1, 0, 0, 1, 0, 0)
.move(props.x || 0, props.y || 0)
.rotate(props.rotation || 0, props.originX, props.originY)
.scale(scaleX, scaleY, props.originX, props.originY);
if (props.transform != null) {
pooledTransform.transform(props.transform);
}
if (
instance.xx !== pooledTransform.xx ||
instance.yx !== pooledTransform.yx ||
instance.xy !== pooledTransform.xy ||
instance.yy !== pooledTransform.yy ||
instance.x !== pooledTransform.x ||
instance.y !== pooledTransform.y
) {
instance.transformTo(pooledTransform);
}
if (props.cursor !== prevProps.cursor || props.title !== prevProps.title) {
instance.indicate(props.cursor, props.title);
}
if (instance.blend && props.opacity !== prevProps.opacity) {
instance.blend(props.opacity == null ? 1 : props.opacity);
}
if (props.visible !== prevProps.visible) {
if (props.visible == null || props.visible) {
instance.show();
} else {
instance.hide();
}
}
for (let type in EVENT_TYPES) {
addEventListeners(instance, EVENT_TYPES[type], props[type]);
}
}
function applyRenderableNodeProps(instance, props, prevProps = {}) {
applyNodeProps(instance, props, prevProps);
if (prevProps.fill !== props.fill) {
if (props.fill && props.fill.applyFill) {
props.fill.applyFill(instance);
} else {
instance.fill(props.fill);
}
}
if (
prevProps.stroke !== props.stroke ||
prevProps.strokeWidth !== props.strokeWidth ||
prevProps.strokeCap !== props.strokeCap ||
prevProps.strokeJoin !== props.strokeJoin ||
// TODO: Consider deep check of stokeDash; may benefit VML in IE.
prevProps.strokeDash !== props.strokeDash
) {
instance.stroke(
props.stroke,
props.strokeWidth,
props.strokeCap,
props.strokeJoin,
props.strokeDash,
);
}
}
function applyShapeProps(instance, props, prevProps = {}) {
applyRenderableNodeProps(instance, props, prevProps);
const path = props.d || childrenAsString(props.children);
const prevDelta = instance._prevDelta;
const prevPath = instance._prevPath;
if (
path !== prevPath ||
path.delta !== prevDelta ||
prevProps.height !== props.height ||
prevProps.width !== props.width
) {
instance.draw(path, props.width, props.height);
instance._prevDelta = path.delta;
instance._prevPath = path;
}
}
function applyTextProps(instance, props, prevProps = {}) {
applyRenderableNodeProps(instance, props, prevProps);
const string = props.children;
if (
instance._currentString !== string ||
!isSameFont(props.font, prevProps.font) ||
props.alignment !== prevProps.alignment ||
props.path !== prevProps.path
) {
instance.draw(string, props.font, props.alignment, props.path);
instance._currentString = string;
}
}
/** Declarative fill-type objects; API design not finalized */
const slice = Array.prototype.slice;
@@ -381,158 +131,6 @@ class Text extends React.Component {
}
}
/** ART Renderer */
const ARTRenderer = ReactFiberReconciler({
appendInitialChild(parentInstance, child) {
if (typeof child === 'string') {
// Noop for string children of Text (eg <Text>{'foo'}{'bar'}</Text>)
invariant(false, 'Text children should already be flattened.');
return;
}
child.inject(parentInstance);
},
createInstance(type, props, internalInstanceHandle) {
let instance;
switch (type) {
case TYPES.CLIPPING_RECTANGLE:
instance = Mode.ClippingRectangle();
instance._applyProps = applyClippingRectangleProps;
break;
case TYPES.GROUP:
instance = Mode.Group();
instance._applyProps = applyGroupProps;
break;
case TYPES.SHAPE:
instance = Mode.Shape();
instance._applyProps = applyShapeProps;
break;
case TYPES.TEXT:
instance = Mode.Text(
props.children,
props.font,
props.alignment,
props.path,
);
instance._applyProps = applyTextProps;
break;
}
invariant(instance, 'ReactART does not support the type "%s"', type);
instance._applyProps(instance, props);
return instance;
},
createTextInstance(text, rootContainerInstance, internalInstanceHandle) {
return text;
},
finalizeInitialChildren(domElement, type, props) {
return false;
},
getPublicInstance(instance) {
return instance;
},
prepareForCommit() {
// Noop
},
prepareUpdate(domElement, type, oldProps, newProps) {
return UPDATE_SIGNAL;
},
resetAfterCommit() {
// Noop
},
resetTextContent(domElement) {
// Noop
},
shouldDeprioritizeSubtree(type, props) {
return false;
},
getRootHostContext() {
return emptyObject;
},
getChildHostContext() {
return emptyObject;
},
scheduleDeferredCallback: ReactDOMFrameScheduling.rIC,
shouldSetTextContent(type, props) {
return (
typeof props.children === 'string' || typeof props.children === 'number'
);
},
now: ReactDOMFrameScheduling.now,
mutation: {
appendChild(parentInstance, child) {
if (child.parentNode === parentInstance) {
child.eject();
}
child.inject(parentInstance);
},
appendChildToContainer(parentInstance, child) {
if (child.parentNode === parentInstance) {
child.eject();
}
child.inject(parentInstance);
},
insertBefore(parentInstance, child, beforeChild) {
invariant(
child !== beforeChild,
'ReactART: Can not insert node before itself',
);
child.injectBefore(beforeChild);
},
insertInContainerBefore(parentInstance, child, beforeChild) {
invariant(
child !== beforeChild,
'ReactART: Can not insert node before itself',
);
child.injectBefore(beforeChild);
},
removeChild(parentInstance, child) {
destroyEventListeners(child);
child.eject();
},
removeChildFromContainer(parentInstance, child) {
destroyEventListeners(child);
child.eject();
},
commitTextUpdate(textInstance, oldText, newText) {
// Noop
},
commitMount(instance, type, newProps) {
// Noop
},
commitUpdate(instance, updatePayload, type, oldProps, newProps) {
instance._applyProps(instance, newProps, oldProps);
},
},
});
/** API */
export const ClippingRectangle = TYPES.CLIPPING_RECTANGLE;

View File

@@ -0,0 +1,404 @@
/**
* Copyright (c) 2013-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import * as ReactScheduler from 'shared/ReactScheduler';
import Transform from 'art/core/transform';
import Mode from 'art/modes/current';
import invariant from '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 */
function addEventListeners(instance, type, listener) {
// We need to explicitly unregister before unmount.
// For this reason we need to track subscriptions.
if (!instance._listeners) {
instance._listeners = {};
instance._subscriptions = {};
}
instance._listeners[type] = listener;
if (listener) {
if (!instance._subscriptions[type]) {
instance._subscriptions[type] = instance.subscribe(
type,
createEventHandler(instance),
instance,
);
}
} else {
if (instance._subscriptions[type]) {
instance._subscriptions[type]();
delete instance._subscriptions[type];
}
}
}
function createEventHandler(instance) {
return function handleEvent(event) {
const listener = instance._listeners[event.type];
if (!listener) {
// Noop
} else if (typeof listener === 'function') {
listener.call(instance, event);
} else if (listener.handleEvent) {
listener.handleEvent(event);
}
};
}
function destroyEventListeners(instance) {
if (instance._subscriptions) {
for (let type in instance._subscriptions) {
instance._subscriptions[type]();
}
}
instance._subscriptions = null;
instance._listeners = null;
}
function getScaleX(props) {
if (props.scaleX != null) {
return props.scaleX;
} else if (props.scale != null) {
return props.scale;
} else {
return 1;
}
}
function getScaleY(props) {
if (props.scaleY != null) {
return props.scaleY;
} else if (props.scale != null) {
return props.scale;
} else {
return 1;
}
}
function isSameFont(oldFont, newFont) {
if (oldFont === newFont) {
return true;
} else if (typeof newFont === 'string' || typeof oldFont === 'string') {
return false;
} else {
return (
newFont.fontSize === oldFont.fontSize &&
newFont.fontStyle === oldFont.fontStyle &&
newFont.fontVariant === oldFont.fontVariant &&
newFont.fontWeight === oldFont.fontWeight &&
newFont.fontFamily === oldFont.fontFamily
);
}
}
/** Render Methods */
function applyClippingRectangleProps(instance, props, prevProps = {}) {
applyNodeProps(instance, props, prevProps);
instance.width = props.width;
instance.height = props.height;
}
function applyGroupProps(instance, props, prevProps = {}) {
applyNodeProps(instance, props, prevProps);
instance.width = props.width;
instance.height = props.height;
}
function applyNodeProps(instance, props, prevProps = {}) {
const scaleX = getScaleX(props);
const scaleY = getScaleY(props);
pooledTransform
.transformTo(1, 0, 0, 1, 0, 0)
.move(props.x || 0, props.y || 0)
.rotate(props.rotation || 0, props.originX, props.originY)
.scale(scaleX, scaleY, props.originX, props.originY);
if (props.transform != null) {
pooledTransform.transform(props.transform);
}
if (
instance.xx !== pooledTransform.xx ||
instance.yx !== pooledTransform.yx ||
instance.xy !== pooledTransform.xy ||
instance.yy !== pooledTransform.yy ||
instance.x !== pooledTransform.x ||
instance.y !== pooledTransform.y
) {
instance.transformTo(pooledTransform);
}
if (props.cursor !== prevProps.cursor || props.title !== prevProps.title) {
instance.indicate(props.cursor, props.title);
}
if (instance.blend && props.opacity !== prevProps.opacity) {
instance.blend(props.opacity == null ? 1 : props.opacity);
}
if (props.visible !== prevProps.visible) {
if (props.visible == null || props.visible) {
instance.show();
} else {
instance.hide();
}
}
for (let type in EVENT_TYPES) {
addEventListeners(instance, EVENT_TYPES[type], props[type]);
}
}
function applyRenderableNodeProps(instance, props, prevProps = {}) {
applyNodeProps(instance, props, prevProps);
if (prevProps.fill !== props.fill) {
if (props.fill && props.fill.applyFill) {
props.fill.applyFill(instance);
} else {
instance.fill(props.fill);
}
}
if (
prevProps.stroke !== props.stroke ||
prevProps.strokeWidth !== props.strokeWidth ||
prevProps.strokeCap !== props.strokeCap ||
prevProps.strokeJoin !== props.strokeJoin ||
// TODO: Consider deep check of stokeDash; may benefit VML in IE.
prevProps.strokeDash !== props.strokeDash
) {
instance.stroke(
props.stroke,
props.strokeWidth,
props.strokeCap,
props.strokeJoin,
props.strokeDash,
);
}
}
function applyShapeProps(instance, props, prevProps = {}) {
applyRenderableNodeProps(instance, props, prevProps);
const path = props.d || childrenAsString(props.children);
const prevDelta = instance._prevDelta;
const prevPath = instance._prevPath;
if (
path !== prevPath ||
path.delta !== prevDelta ||
prevProps.height !== props.height ||
prevProps.width !== props.width
) {
instance.draw(path, props.width, props.height);
instance._prevDelta = path.delta;
instance._prevPath = path;
}
}
function applyTextProps(instance, props, prevProps = {}) {
applyRenderableNodeProps(instance, props, prevProps);
const string = props.children;
if (
instance._currentString !== string ||
!isSameFont(props.font, prevProps.font) ||
props.alignment !== prevProps.alignment ||
props.path !== prevProps.path
) {
instance.draw(string, props.font, props.alignment, props.path);
instance._currentString = string;
}
}
export * from 'shared/HostConfigWithNoPersistence';
export * from 'shared/HostConfigWithNoHydration';
export function appendInitialChild(parentInstance, child) {
if (typeof child === 'string') {
// Noop for string children of Text (eg <Text>{'foo'}{'bar'}</Text>)
invariant(false, 'Text children should already be flattened.');
return;
}
child.inject(parentInstance);
}
export function createInstance(type, props, internalInstanceHandle) {
let instance;
switch (type) {
case TYPES.CLIPPING_RECTANGLE:
instance = Mode.ClippingRectangle();
instance._applyProps = applyClippingRectangleProps;
break;
case TYPES.GROUP:
instance = Mode.Group();
instance._applyProps = applyGroupProps;
break;
case TYPES.SHAPE:
instance = Mode.Shape();
instance._applyProps = applyShapeProps;
break;
case TYPES.TEXT:
instance = Mode.Text(
props.children,
props.font,
props.alignment,
props.path,
);
instance._applyProps = applyTextProps;
break;
}
invariant(instance, 'ReactART does not support the type "%s"', type);
instance._applyProps(instance, props);
return instance;
}
export function createTextInstance(
text,
rootContainerInstance,
internalInstanceHandle,
) {
return text;
}
export function finalizeInitialChildren(domElement, type, props) {
return false;
}
export function getPublicInstance(instance) {
return instance;
}
export function prepareForCommit() {
// Noop
}
export function prepareUpdate(domElement, type, oldProps, newProps) {
return UPDATE_SIGNAL;
}
export function resetAfterCommit() {
// Noop
}
export function resetTextContent(domElement) {
// Noop
}
export function shouldDeprioritizeSubtree(type, props) {
return false;
}
export function getRootHostContext() {
return NO_CONTEXT;
}
export function getChildHostContext() {
return NO_CONTEXT;
}
export const scheduleDeferredCallback = ReactScheduler.scheduleWork;
export const cancelDeferredCallback = ReactScheduler.cancelScheduledWork;
export function shouldSetTextContent(type, props) {
return (
typeof props.children === 'string' || typeof props.children === 'number'
);
}
export const now = ReactScheduler.now;
// The ART renderer is secondary to the React DOM renderer.
export const isPrimaryRenderer = false;
export const supportsMutation = true;
export function appendChild(parentInstance, child) {
if (child.parentNode === parentInstance) {
child.eject();
}
child.inject(parentInstance);
}
export function appendChildToContainer(parentInstance, child) {
if (child.parentNode === parentInstance) {
child.eject();
}
child.inject(parentInstance);
}
export function insertBefore(parentInstance, child, beforeChild) {
invariant(
child !== beforeChild,
'ReactART: Can not insert node before itself',
);
child.injectBefore(beforeChild);
}
export function insertInContainerBefore(parentInstance, child, beforeChild) {
invariant(
child !== beforeChild,
'ReactART: Can not insert node before itself',
);
child.injectBefore(beforeChild);
}
export function removeChild(parentInstance, child) {
destroyEventListeners(child);
child.eject();
}
export function removeChildFromContainer(parentInstance, child) {
destroyEventListeners(child);
child.eject();
}
export function commitTextUpdate(textInstance, oldText, newText) {
// Noop
}
export function commitMount(instance, type, newProps) {
// Noop
}
export function commitUpdate(
instance,
updatePayload,
type,
oldProps,
newProps,
) {
instance._applyProps(instance, newProps, oldProps);
}

View File

@@ -0,0 +1,34 @@
/**
* Copyright (c) 2013-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
export const TYPES = {
CLIPPING_RECTANGLE: 'ClippingRectangle',
GROUP: 'Group',
SHAPE: 'Shape',
TEXT: 'Text',
};
export const EVENT_TYPES = {
onClick: 'click',
onMouseMove: 'mousemove',
onMouseOver: 'mouseover',
onMouseOut: 'mouseout',
onMouseUp: 'mouseup',
onMouseDown: 'mousedown',
};
export function childrenAsString(children) {
if (!children) {
return '';
} else if (typeof children === 'string') {
return children;
} else if (children.length) {
return children.join('');
} else {
return '';
}
}

View File

@@ -15,6 +15,19 @@ const React = require('react');
const ReactDOM = require('react-dom');
const ReactTestUtils = require('react-dom/test-utils');
// Isolate test renderer.
jest.resetModules();
const ReactTestRenderer = require('react-test-renderer');
// Isolate ART renderer.
jest.resetModules();
const ReactART = require('react-art');
const ARTSVGMode = require('art/modes/svg');
const ARTCurrentMode = require('art/modes/current');
const Circle = require('react-art/Circle');
const Rectangle = require('react-art/Rectangle');
const Wedge = require('react-art/Wedge');
let Group;
let Shape;
let Surface;
@@ -22,15 +35,6 @@ let TestComponent;
const Missing = {};
const ReactART = require('react-art');
const ARTSVGMode = require('art/modes/svg');
const ARTCurrentMode = require('art/modes/current');
const renderer = require('react-test-renderer');
const Circle = require('react-art/Circle');
const Rectangle = require('react-art/Rectangle');
const Wedge = require('react-art/Wedge');
function testDOMNodeStructure(domNode, expectedStructure) {
expect(domNode).toBeDefined();
expect(domNode.nodeName).toBe(expectedStructure.nodeName);
@@ -67,6 +71,8 @@ describe('ReactART', () => {
Surface = ReactART.Surface;
TestComponent = class extends React.Component {
group = React.createRef();
render() {
const a = (
<Shape
@@ -100,7 +106,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>
@@ -117,7 +123,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');
});
@@ -256,15 +262,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>
);
@@ -285,26 +293,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);
});
@@ -339,11 +349,65 @@ describe('ReactART', () => {
doClick(instance);
expect(onClick2).toBeCalled();
});
it('can concurrently render with a "primary" renderer while sharing context', () => {
const CurrentRendererContext = React.createContext(null);
function Yield(props) {
testRenderer.unstable_yield(props.value);
return null;
}
let ops = [];
function LogCurrentRenderer() {
return (
<CurrentRendererContext.Consumer>
{currentRenderer => {
ops.push(currentRenderer);
return null;
}}
</CurrentRendererContext.Consumer>
);
}
// Using test renderer instead of the DOM renderer here because async
// testing APIs for the DOM renderer don't exist.
const testRenderer = ReactTestRenderer.create(
<CurrentRendererContext.Provider value="Test">
<Yield value="A" />
<Yield value="B" />
<LogCurrentRenderer />
<Yield value="C" />
</CurrentRendererContext.Provider>,
{
unstable_isAsync: true,
},
);
testRenderer.unstable_flushThrough(['A']);
ReactDOM.render(
<Surface>
<LogCurrentRenderer />
<CurrentRendererContext.Provider value="ART">
<LogCurrentRenderer />
</CurrentRendererContext.Provider>
</Surface>,
container,
);
expect(ops).toEqual([null, 'ART']);
ops = [];
expect(testRenderer.unstable_flushAll()).toEqual(['B', 'C']);
expect(ops).toEqual(['Test']);
});
});
describe('ReactARTComponents', () => {
it('should generate a <Shape> with props for drawing the Circle', () => {
const circle = renderer.create(
const circle = ReactTestRenderer.create(
<Circle radius={10} stroke="green" strokeWidth={3} fill="blue" />,
);
expect(circle.toJSON()).toMatchSnapshot();
@@ -351,7 +415,9 @@ describe('ReactARTComponents', () => {
it('should warn if radius is missing on a Circle component', () => {
expect(() =>
renderer.create(<Circle stroke="green" strokeWidth={3} fill="blue" />),
ReactTestRenderer.create(
<Circle stroke="green" strokeWidth={3} fill="blue" />,
),
).toWarnDev(
'Warning: Failed prop type: The prop `radius` is marked as required in `Circle`, ' +
'but its value is `undefined`.' +
@@ -360,7 +426,7 @@ describe('ReactARTComponents', () => {
});
it('should generate a <Shape> with props for drawing the Rectangle', () => {
const rectangle = renderer.create(
const rectangle = ReactTestRenderer.create(
<Rectangle width={50} height={50} stroke="green" fill="blue" />,
);
expect(rectangle.toJSON()).toMatchSnapshot();
@@ -368,7 +434,7 @@ describe('ReactARTComponents', () => {
it('should warn if width/height is missing on a Rectangle component', () => {
expect(() =>
renderer.create(<Rectangle stroke="green" fill="blue" />),
ReactTestRenderer.create(<Rectangle stroke="green" fill="blue" />),
).toWarnDev([
'Warning: Failed prop type: The prop `width` is marked as required in `Rectangle`, ' +
'but its value is `undefined`.' +
@@ -380,21 +446,21 @@ describe('ReactARTComponents', () => {
});
it('should generate a <Shape> with props for drawing the Wedge', () => {
const wedge = renderer.create(
const wedge = ReactTestRenderer.create(
<Wedge outerRadius={50} startAngle={0} endAngle={360} fill="blue" />,
);
expect(wedge.toJSON()).toMatchSnapshot();
});
it('should return null if startAngle equals to endAngle on Wedge', () => {
const wedge = renderer.create(
const wedge = ReactTestRenderer.create(
<Wedge outerRadius={50} startAngle={0} endAngle={0} fill="blue" />,
);
expect(wedge.toJSON()).toBeNull();
});
it('should warn if outerRadius/startAngle/endAngle is missing on a Wedge component', () => {
expect(() => renderer.create(<Wedge fill="blue" />)).toWarnDev([
expect(() => ReactTestRenderer.create(<Wedge fill="blue" />)).toWarnDev([
'Warning: Failed prop type: The prop `outerRadius` is marked as required in `Wedge`, ' +
'but its value is `undefined`.' +
'\n in Wedge (at **)',

View File

@@ -1,32 +0,0 @@
# react-call-return
This is an experimental package for multi-pass rendering in React.
**Its API is not as stable as that of React, React Native, or React DOM, and does not follow the common versioning scheme.**
**Use it at your own risk.**
# No, Really, It Is Unstable
This is **an experiment**.
We **will** replace this with a different API in the future.
It can break between patch versions of React.
We also know that **it has bugs**.
Don't rely on this for anything except experiments.
Even in experiments, make sure to lock the versions so that an update doesn't break your app.
Don't publish third party components relying on this unless you clearly mark them as experimental too.
They will break.
Have fun! Let us know if you find interesting use cases for it.
# API
See the test case in `src/__tests__/ReactCallReturn.js` for an example.
# What and Why
The API is not very intuitive right now, but [this is a good overview](https://cdb.reacttraining.com/react-call-return-what-and-why-7e7761f81843) of why it might be useful in some cases. We are very open to better API ideas for this concept.

View File

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

View File

@@ -1,19 +0,0 @@
{
"name": "react-call-return",
"description": "Experimental APIs for multi-pass rendering in React.",
"version": "0.8.0",
"repository": "facebook/react",
"files": [
"LICENSE",
"README.md",
"index.js",
"cjs/"
],
"dependencies": {
"fbjs": "^0.8.16",
"object-assign": "^4.1.1"
},
"peerDependencies": {
"react": "^16.0.0"
}
}

View File

@@ -1,95 +0,0 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import {
REACT_CALL_TYPE,
REACT_RETURN_TYPE,
REACT_ELEMENT_TYPE,
} from 'shared/ReactSymbols';
import type {ReactCall, ReactNodeList, ReactReturn} from 'shared/ReactTypes';
type CallHandler<T, V> = (props: T, returns: Array<V>) => ReactNodeList;
export function unstable_createCall<T, V>(
children: ReactNodeList,
handler: CallHandler<T, V>,
props: T,
key: ?string = null,
): ReactCall<V> {
const call = {
// This tag allow us to uniquely identify this as a React Call
$$typeof: REACT_ELEMENT_TYPE,
type: REACT_CALL_TYPE,
key: key == null ? null : '' + key,
ref: null,
props: {
props,
handler,
children: children,
},
};
if (__DEV__) {
// TODO: Add _store property for marking this as validated.
if (Object.freeze) {
Object.freeze(call.props);
Object.freeze(call);
}
}
return call;
}
export function unstable_createReturn<V>(value: V): ReactReturn<V> {
const returnNode = {
// This tag allow us to uniquely identify this as a React Call
$$typeof: REACT_ELEMENT_TYPE,
type: REACT_RETURN_TYPE,
key: null,
ref: null,
props: {
value,
},
};
if (__DEV__) {
// TODO: Add _store property for marking this as validated.
if (Object.freeze) {
Object.freeze(returnNode);
}
}
return returnNode;
}
/**
* Verifies the object is a call object.
*/
export function unstable_isCall(object: mixed): boolean {
return (
typeof object === 'object' &&
object !== null &&
object.type === REACT_CALL_TYPE
);
}
/**
* Verifies the object is a return object.
*/
export function unstable_isReturn(object: mixed): boolean {
return (
typeof object === 'object' &&
object !== null &&
object.type === REACT_RETURN_TYPE
);
}
export const unstable_REACT_RETURN_TYPE = REACT_RETURN_TYPE;
export const unstable_REACT_CALL_TYPE = REACT_CALL_TYPE;

View File

@@ -1,326 +0,0 @@
/**
* Copyright (c) 2013-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @emails react-core
*/
'use strict';
let React;
let ReactFeatureFlags;
let ReactNoop;
let ReactCallReturn;
describe('ReactCallReturn', () => {
beforeEach(() => {
jest.resetModules();
ReactFeatureFlags = require('shared/ReactFeatureFlags');
ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
React = require('react');
ReactNoop = require('react-noop-renderer');
ReactCallReturn = require('react-call-return');
});
function div(...children) {
children = children.map(c => (typeof c === 'string' ? {text: c} : c));
return {type: 'div', children, prop: undefined};
}
function span(prop) {
return {type: 'span', children: [], prop};
}
it('should render a call', () => {
const ops = [];
function Continuation({isSame}) {
ops.push(['Continuation', isSame]);
return <span prop={isSame ? 'foo==bar' : 'foo!=bar'} />;
}
// An alternative API could mark Continuation as something that needs
// returning. E.g. Continuation.returnType = 123;
function Child({bar}) {
ops.push(['Child', bar]);
return ReactCallReturn.unstable_createReturn({
props: {
bar: bar,
},
continuation: Continuation,
});
}
function Indirection() {
ops.push('Indirection');
return [<Child key="a" bar={true} />, <Child key="b" bar={false} />];
}
function HandleReturns(props, returns) {
ops.push('HandleReturns');
return returns.map((y, i) => (
<y.continuation key={i} isSame={props.foo === y.props.bar} />
));
}
// An alternative API could mark Parent as something that needs
// returning. E.g. Parent.handler = HandleReturns;
function Parent(props) {
ops.push('Parent');
return ReactCallReturn.unstable_createCall(
props.children,
HandleReturns,
props,
);
}
function App() {
return (
<div>
<Parent foo={true}>
<Indirection />
</Parent>
</div>
);
}
ReactNoop.render(<App />);
ReactNoop.flush();
expect(ops).toEqual([
'Parent',
'Indirection',
['Child', true],
// Return
['Child', false],
// Return
'HandleReturns',
// Call continuations
['Continuation', true],
['Continuation', false],
]);
expect(ReactNoop.getChildren()).toEqual([
div(span('foo==bar'), span('foo!=bar')),
]);
});
it('should update a call', () => {
function Continuation({isSame}) {
return <span prop={isSame ? 'foo==bar' : 'foo!=bar'} />;
}
function Child({bar}) {
return ReactCallReturn.unstable_createReturn({
props: {
bar: bar,
},
continuation: Continuation,
});
}
function Indirection() {
return [<Child key="a" bar={true} />, <Child key="b" bar={false} />];
}
function HandleReturns(props, returns) {
return returns.map((y, i) => (
<y.continuation key={i} isSame={props.foo === y.props.bar} />
));
}
function Parent(props) {
return ReactCallReturn.unstable_createCall(
props.children,
HandleReturns,
props,
);
}
function App(props) {
return (
<div>
<Parent foo={props.foo}>
<Indirection />
</Parent>
</div>
);
}
ReactNoop.render(<App foo={true} />);
ReactNoop.flush();
expect(ReactNoop.getChildren()).toEqual([
div(span('foo==bar'), span('foo!=bar')),
]);
ReactNoop.render(<App foo={false} />);
ReactNoop.flush();
expect(ReactNoop.getChildren()).toEqual([
div(span('foo!=bar'), span('foo==bar')),
]);
});
it('should unmount a composite in a call', () => {
let ops = [];
class Continuation extends React.Component {
render() {
ops.push('Continuation');
return <div />;
}
componentWillUnmount() {
ops.push('Unmount Continuation');
}
}
class Child extends React.Component {
render() {
ops.push('Child');
return ReactCallReturn.unstable_createReturn(Continuation);
}
componentWillUnmount() {
ops.push('Unmount Child');
}
}
function HandleReturns(props, returns) {
ops.push('HandleReturns');
return returns.map((ContinuationComponent, i) => (
<ContinuationComponent key={i} />
));
}
class Parent extends React.Component {
render() {
ops.push('Parent');
return ReactCallReturn.unstable_createCall(
this.props.children,
HandleReturns,
this.props,
);
}
componentWillUnmount() {
ops.push('Unmount Parent');
}
}
ReactNoop.render(
<Parent>
<Child />
</Parent>,
);
ReactNoop.flush();
expect(ops).toEqual(['Parent', 'Child', 'HandleReturns', 'Continuation']);
ops = [];
ReactNoop.render(<div />);
ReactNoop.flush();
expect(ops).toEqual([
'Unmount Parent',
'Unmount Child',
'Unmount Continuation',
]);
});
it('should handle deep updates in call', () => {
let instances = {};
class Counter extends React.Component {
state = {value: 5};
render() {
instances[this.props.id] = this;
return ReactCallReturn.unstable_createReturn(this.state.value);
}
}
function App(props) {
return ReactCallReturn.unstable_createCall(
[
<Counter key="a" id="a" />,
<Counter key="b" id="b" />,
<Counter key="c" id="c" />,
],
(p, returns) => returns.map((y, i) => <span key={i} prop={y * 100} />),
{},
);
}
ReactNoop.render(<App />);
ReactNoop.flush();
expect(ReactNoop.getChildren()).toEqual([span(500), span(500), span(500)]);
instances.a.setState({value: 1});
instances.b.setState({value: 2});
ReactNoop.flush();
expect(ReactNoop.getChildren()).toEqual([span(100), span(200), span(500)]);
});
it('should unmount and remount children', () => {
let ops = [];
class Call extends React.Component {
render() {
return ReactCallReturn.unstable_createCall(
this.props.children,
(p, returns) => returns,
{},
);
}
}
class Return extends React.Component {
render() {
ops.push(`Return ${this.props.value}`);
return ReactCallReturn.unstable_createReturn(this.props.children);
}
UNSAFE_componentWillMount() {
ops.push(`Mount Return ${this.props.value}`);
}
componentWillUnmount() {
ops.push(`Unmount Return ${this.props.value}`);
}
}
ReactNoop.render(
<Call>
<Return value={1} />
<Return value={2} />
</Call>,
);
expect(ReactNoop.flush).toWarnDev(
'componentWillMount: Please update the following components ' +
'to use componentDidMount instead: Return',
);
expect(ops).toEqual([
'Mount Return 1',
'Return 1',
'Mount Return 2',
'Return 2',
]);
ops = [];
ReactNoop.render(<Call />);
ReactNoop.flush();
expect(ops).toEqual(['Unmount Return 1', 'Unmount Return 2']);
ops = [];
ReactNoop.render(
<Call>
<Return value={3} />
</Call>,
);
ReactNoop.flush();
expect(ops).toEqual(['Mount Return 3', 'Return 3']);
});
});

View File

@@ -1,6 +1,6 @@
{
"name": "react-dom",
"version": "16.3.2",
"version": "16.4.1",
"description": "React package for working with the DOM.",
"main": "index.js",
"repository": "facebook/react",
@@ -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

@@ -123,7 +123,7 @@ describe('DOMPropertyOperations', () => {
spyOnDevAndProd(container.firstChild, 'setAttribute');
ReactDOM.render(<progress value={30} />, container);
ReactDOM.render(<progress value="30" />, container);
expect(container.firstChild.setAttribute.calls.count()).toBe(2);
expect(container.firstChild.setAttribute).toHaveBeenCalledTimes(2);
});
});
@@ -155,5 +155,11 @@ describe('DOMPropertyOperations', () => {
expect(container.firstChild.getAttribute('value')).toBe('foo');
expect(container.firstChild.value).toBe('foo');
});
it('should not remove attributes for custom component tag', () => {
const container = document.createElement('div');
ReactDOM.render(<my-icon size="5px" />, container);
expect(container.firstChild.getAttribute('size')).toBe('5px');
});
});
});

View File

@@ -42,6 +42,8 @@ let getListener;
let putListener;
let deleteAllListeners;
let container;
function registerSimpleTestHandler() {
putListener(CHILD, ON_CLICK_KEY, LISTENER);
const listener = getListener(CHILD, ON_CLICK_KEY);
@@ -63,7 +65,8 @@ describe('ReactBrowserEventEmitter', () => {
ReactBrowserEventEmitter = require('../events/ReactBrowserEventEmitter');
ReactTestUtils = require('react-dom/test-utils');
const container = document.createElement('div');
container = document.createElement('div');
document.body.appendChild(container);
let GRANDPARENT_PROPS = {};
let PARENT_PROPS = {};
@@ -129,6 +132,11 @@ describe('ReactBrowserEventEmitter', () => {
idCallOrder = [];
});
afterEach(() => {
document.body.removeChild(container);
container = null;
});
it('should store a listener correctly', () => {
registerSimpleTestHandler();
const listener = getListener(CHILD, ON_CLICK_KEY);
@@ -150,25 +158,25 @@ describe('ReactBrowserEventEmitter', () => {
it('should invoke a simple handler registered on a node', () => {
registerSimpleTestHandler();
ReactTestUtils.Simulate.click(CHILD);
expect(LISTENER.mock.calls.length).toBe(1);
CHILD.click();
expect(LISTENER).toHaveBeenCalledTimes(1);
});
it('should not invoke handlers if ReactBrowserEventEmitter is disabled', () => {
registerSimpleTestHandler();
ReactBrowserEventEmitter.setEnabled(false);
ReactTestUtils.SimulateNative.click(CHILD);
expect(LISTENER.mock.calls.length).toBe(0);
CHILD.click();
expect(LISTENER).toHaveBeenCalledTimes(0);
ReactBrowserEventEmitter.setEnabled(true);
ReactTestUtils.SimulateNative.click(CHILD);
expect(LISTENER.mock.calls.length).toBe(1);
CHILD.click();
expect(LISTENER).toHaveBeenCalledTimes(1);
});
it('should bubble simply', () => {
putListener(CHILD, ON_CLICK_KEY, recordID.bind(null, CHILD));
putListener(PARENT, ON_CLICK_KEY, recordID.bind(null, PARENT));
putListener(GRANDPARENT, ON_CLICK_KEY, recordID.bind(null, GRANDPARENT));
ReactTestUtils.Simulate.click(CHILD);
CHILD.click();
expect(idCallOrder.length).toBe(3);
expect(idCallOrder[0]).toBe(CHILD);
expect(idCallOrder[1]).toBe(PARENT);
@@ -179,7 +187,7 @@ describe('ReactBrowserEventEmitter', () => {
putListener(GRANDPARENT, ON_CLICK_KEY, recordID.bind(null, 'GRANDPARENT'));
putListener(PARENT, ON_CLICK_KEY, recordID.bind(null, 'PARENT'));
putListener(CHILD, ON_CLICK_KEY, recordID.bind(null, 'CHILD'));
ReactTestUtils.Simulate.click(CHILD);
CHILD.click();
expect(idCallOrder).toEqual(['CHILD', 'PARENT', 'GRANDPARENT']);
idCallOrder = [];
@@ -191,7 +199,7 @@ describe('ReactBrowserEventEmitter', () => {
recordID.bind(null, 'UPDATED_GRANDPARENT'),
);
ReactTestUtils.Simulate.click(CHILD);
CHILD.click();
expect(idCallOrder).toEqual(['CHILD', 'PARENT', 'UPDATED_GRANDPARENT']);
});
@@ -224,7 +232,7 @@ describe('ReactBrowserEventEmitter', () => {
recordID(GRANDPARENT);
expect(event.currentTarget).toBe(GRANDPARENT);
});
ReactTestUtils.Simulate.click(CHILD);
CHILD.click();
expect(idCallOrder.length).toBe(3);
expect(idCallOrder[0]).toBe(CHILD);
expect(idCallOrder[1]).toBe(PARENT);
@@ -239,7 +247,7 @@ describe('ReactBrowserEventEmitter', () => {
recordIDAndStopPropagation.bind(null, PARENT),
);
putListener(GRANDPARENT, ON_CLICK_KEY, recordID.bind(null, GRANDPARENT));
ReactTestUtils.Simulate.click(CHILD);
CHILD.click();
expect(idCallOrder.length).toBe(2);
expect(idCallOrder[0]).toBe(CHILD);
expect(idCallOrder[1]).toBe(PARENT);
@@ -254,7 +262,7 @@ describe('ReactBrowserEventEmitter', () => {
e.isPropagationStopped = () => true;
});
putListener(GRANDPARENT, ON_CLICK_KEY, recordID.bind(null, GRANDPARENT));
ReactTestUtils.Simulate.click(CHILD);
CHILD.click();
expect(idCallOrder.length).toBe(2);
expect(idCallOrder[0]).toBe(CHILD);
expect(idCallOrder[1]).toBe(PARENT);
@@ -268,7 +276,7 @@ describe('ReactBrowserEventEmitter', () => {
);
putListener(PARENT, ON_CLICK_KEY, recordID.bind(null, PARENT));
putListener(GRANDPARENT, ON_CLICK_KEY, recordID.bind(null, GRANDPARENT));
ReactTestUtils.Simulate.click(CHILD);
CHILD.click();
expect(idCallOrder.length).toBe(1);
expect(idCallOrder[0]).toBe(CHILD);
});
@@ -277,7 +285,7 @@ describe('ReactBrowserEventEmitter', () => {
putListener(CHILD, ON_CLICK_KEY, recordIDAndReturnFalse.bind(null, CHILD));
putListener(PARENT, ON_CLICK_KEY, recordID.bind(null, PARENT));
putListener(GRANDPARENT, ON_CLICK_KEY, recordID.bind(null, GRANDPARENT));
ReactTestUtils.Simulate.click(CHILD);
CHILD.click();
expect(idCallOrder.length).toBe(3);
expect(idCallOrder[0]).toBe(CHILD);
expect(idCallOrder[1]).toBe(PARENT);
@@ -300,8 +308,8 @@ describe('ReactBrowserEventEmitter', () => {
};
putListener(CHILD, ON_CLICK_KEY, handleChildClick);
putListener(PARENT, ON_CLICK_KEY, handleParentClick);
ReactTestUtils.Simulate.click(CHILD);
expect(handleParentClick.mock.calls.length).toBe(1);
CHILD.click();
expect(handleParentClick).toHaveBeenCalledTimes(1);
});
it('should not invoke newly inserted handlers while bubbling', () => {
@@ -310,8 +318,8 @@ describe('ReactBrowserEventEmitter', () => {
putListener(PARENT, ON_CLICK_KEY, handleParentClick);
};
putListener(CHILD, ON_CLICK_KEY, handleChildClick);
ReactTestUtils.Simulate.click(CHILD);
expect(handleParentClick.mock.calls.length).toBe(0);
CHILD.click();
expect(handleParentClick).toHaveBeenCalledTimes(0);
});
it('should have mouse enter simulated by test utils', () => {
@@ -325,7 +333,7 @@ describe('ReactBrowserEventEmitter', () => {
spyOnDevAndProd(EventTarget.prototype, 'addEventListener');
ReactBrowserEventEmitter.listenTo(ON_CLICK_KEY, document);
ReactBrowserEventEmitter.listenTo(ON_CLICK_KEY, document);
expect(EventTarget.prototype.addEventListener.calls.count()).toBe(1);
expect(EventTarget.prototype.addEventListener).toHaveBeenCalledTimes(1);
});
it('should work with event plugins without dependencies', () => {

View File

@@ -278,7 +278,7 @@ describe('ReactComponent', () => {
componentDidMount() {
// Check .props.title to make sure we got the right elements back
expect(this.wrapperRef.getTitle()).toBe('wrapper');
expect(ReactDOM.findDOMNode(this.innerRef).className).toBe('inner');
expect(this.innerRef.className).toBe('inner');
mounted = true;
}
}
@@ -387,11 +387,11 @@ describe('ReactComponent', () => {
const callback = jest.fn();
const container = document.createElement('div');
ReactDOM.render(<div />, container, callback);
expect(callback.mock.calls.length).toBe(1);
expect(callback).toHaveBeenCalledTimes(1);
ReactDOM.render(<div className="foo" />, container, callback);
expect(callback.mock.calls.length).toBe(2);
expect(callback).toHaveBeenCalledTimes(2);
ReactDOM.render(<span />, container, callback);
expect(callback.mock.calls.length).toBe(3);
expect(callback).toHaveBeenCalledTimes(3);
});
it('throws usefully when rendering badly-typed elements', () => {

View File

@@ -63,28 +63,9 @@ describe('ReactComponentLifeCycle', () => {
});
describe('react-lifecycles-compat', () => {
const polyfill = require('react-lifecycles-compat');
it('should not warn about deprecated cWM/cWRP for polyfilled components', () => {
class PolyfilledComponent extends React.Component {
state = {};
static getDerivedStateFromProps() {
return null;
}
render() {
return null;
}
}
polyfill(PolyfilledComponent);
const container = document.createElement('div');
ReactDOM.render(<PolyfilledComponent />, container);
});
it('should not warn about unsafe lifecycles within "strict" tree for polyfilled components', () => {
const {StrictMode} = React;
const {polyfill} = require('react-lifecycles-compat');
it('should not warn for components with polyfilled getDerivedStateFromProps', () => {
class PolyfilledComponent extends React.Component {
state = {};
static getDerivedStateFromProps() {
@@ -99,9 +80,31 @@ describe('ReactComponentLifeCycle', () => {
const container = document.createElement('div');
ReactDOM.render(
<StrictMode>
<React.StrictMode>
<PolyfilledComponent />
</StrictMode>,
</React.StrictMode>,
container,
);
});
it('should not warn for components with polyfilled getSnapshotBeforeUpdate', () => {
class PolyfilledComponent extends React.Component {
getSnapshotBeforeUpdate() {
return null;
}
componentDidUpdate() {}
render() {
return null;
}
}
polyfill(PolyfilledComponent);
const container = document.createElement('div');
ReactDOM.render(
<React.StrictMode>
<PolyfilledComponent />
</React.StrictMode>,
container,
);
});

View File

@@ -1065,16 +1065,22 @@ describe('ReactComponentLifeCycle', () => {
}
}
ReactTestUtils.renderIntoDocument(<Parent />);
expect(divRef.current.textContent).toBe('remote:0, local:0');
const container = document.createElement('div');
document.body.appendChild(container);
try {
ReactDOM.render(<Parent />, container);
expect(divRef.current.textContent).toBe('remote:0, local:0');
// Trigger setState() calls
childInstance.updateState();
expect(divRef.current.textContent).toBe('remote:1, local:1');
// Trigger setState() calls
childInstance.updateState();
expect(divRef.current.textContent).toBe('remote:1, local:1');
// Trigger batched setState() calls
ReactTestUtils.Simulate.click(divRef.current);
expect(divRef.current.textContent).toBe('remote:2, local:2');
// Trigger batched setState() calls
divRef.current.click();
expect(divRef.current.textContent).toBe('remote:2, local:2');
} finally {
document.body.removeChild(container);
}
});
it('should pass the return value from getSnapshotBeforeUpdate to componentDidUpdate', () => {

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};
@@ -140,23 +172,28 @@ describe('ReactCompositeComponent', () => {
});
it('should react to state changes from callbacks', () => {
const instance = ReactTestUtils.renderIntoDocument(<MorphingComponent />);
let el = ReactDOM.findDOMNode(instance);
expect(el.tagName).toBe('A');
ReactTestUtils.Simulate.click(el);
el = ReactDOM.findDOMNode(instance);
expect(el.tagName).toBe('B');
const container = document.createElement('div');
document.body.appendChild(container);
try {
const instance = ReactDOM.render(<MorphingComponent />, container);
let el = ReactDOM.findDOMNode(instance);
expect(el.tagName).toBe('A');
el.click();
el = ReactDOM.findDOMNode(instance);
expect(el.tagName).toBe('B');
} finally {
document.body.removeChild(container);
}
});
it('should rewire refs when rendering to different child types', () => {
const instance = ReactTestUtils.renderIntoDocument(<MorphingComponent />);
expect(ReactDOM.findDOMNode(instance.refs.x).tagName).toBe('A');
expect(instance.refs.x.tagName).toBe('A');
instance._toggleActivatedState();
expect(ReactDOM.findDOMNode(instance.refs.x).tagName).toBe('B');
expect(instance.refs.x.tagName).toBe('B');
instance._toggleActivatedState();
expect(ReactDOM.findDOMNode(instance.refs.x).tagName).toBe('A');
expect(instance.refs.x.tagName).toBe('A');
});
it('should not cache old DOM nodes when switching constructors', () => {

View File

@@ -11,13 +11,11 @@
let React;
let ReactDOM;
let ReactTestUtils;
describe('ReactCompositeComponentNestedState-state', () => {
beforeEach(() => {
React = require('react');
ReactDOM = require('react-dom');
ReactTestUtils = require('react-dom/test-utils');
});
it('should provide up to date values for props', () => {
@@ -102,7 +100,7 @@ describe('ReactCompositeComponentNestedState-state', () => {
void ReactDOM.render(<ParentComponent logger={logger} />, container);
// click "light green"
ReactTestUtils.Simulate.click(container.childNodes[0].childNodes[3]);
container.childNodes[0].childNodes[3].click();
expect(logger.mock.calls).toEqual([
['parent-render', 'blue'],

View File

@@ -45,23 +45,21 @@ describe('ReactDOM', () => {
it('allows a DOM element to be used with a string', () => {
const element = React.createElement('div', {className: 'foo'});
const instance = ReactTestUtils.renderIntoDocument(element);
expect(ReactDOM.findDOMNode(instance).tagName).toBe('DIV');
const node = ReactTestUtils.renderIntoDocument(element);
expect(node.tagName).toBe('DIV');
});
it('should allow children to be passed as an argument', () => {
const argDiv = ReactTestUtils.renderIntoDocument(
const argNode = ReactTestUtils.renderIntoDocument(
React.createElement('div', null, 'child'),
);
const argNode = ReactDOM.findDOMNode(argDiv);
expect(argNode.innerHTML).toBe('child');
});
it('should overwrite props.children with children argument', () => {
const conflictDiv = ReactTestUtils.renderIntoDocument(
const conflictNode = ReactTestUtils.renderIntoDocument(
React.createElement('div', {children: 'fakechild'}, 'child'),
);
const conflictNode = ReactDOM.findDOMNode(conflictDiv);
expect(conflictNode.innerHTML).toBe('child');
});
@@ -103,8 +101,7 @@ describe('ReactDOM', () => {
<div key="theBird" className="bird" />,
</div>,
);
const root = ReactDOM.findDOMNode(myDiv);
const dog = root.childNodes[0];
const dog = myDiv.childNodes[0];
expect(dog.className).toBe('bigdog');
});
@@ -242,34 +239,37 @@ describe('ReactDOM', () => {
const log = [];
const container = document.createElement('div');
document.body.appendChild(container);
ReactDOM.render(<A showTwo={false} />, container);
input.focus();
try {
ReactDOM.render(<A showTwo={false} />, container);
input.focus();
// When the second input is added, let's simulate losing focus, which is
// something that could happen when manipulating DOM nodes (but is hard to
// deterministically force without relying intensely on React DOM
// implementation details)
const div = container.firstChild;
['appendChild', 'insertBefore'].forEach(name => {
const mutator = div[name];
div[name] = function() {
if (input) {
input.blur();
expect(document.activeElement.tagName).toBe('BODY');
log.push('input2 inserted');
}
return mutator.apply(this, arguments);
};
});
// When the second input is added, let's simulate losing focus, which is
// something that could happen when manipulating DOM nodes (but is hard to
// deterministically force without relying intensely on React DOM
// implementation details)
const div = container.firstChild;
['appendChild', 'insertBefore'].forEach(name => {
const mutator = div[name];
div[name] = function() {
if (input) {
input.blur();
expect(document.activeElement.tagName).toBe('BODY');
log.push('input2 inserted');
}
return mutator.apply(this, arguments);
};
});
expect(document.activeElement.id).toBe('one');
ReactDOM.render(<A showTwo={true} />, container);
// input2 gets added, which causes input to get blurred. Then
// componentDidUpdate focuses input2 and that should make it down to here,
// not get overwritten by focus restoration.
expect(document.activeElement.id).toBe('two');
expect(log).toEqual(['input2 inserted', 'input2 focused']);
document.body.removeChild(container);
expect(document.activeElement.id).toBe('one');
ReactDOM.render(<A showTwo={true} />, container);
// input2 gets added, which causes input to get blurred. Then
// componentDidUpdate focuses input2 and that should make it down to here,
// not get overwritten by focus restoration.
expect(document.activeElement.id).toBe('two');
expect(log).toEqual(['input2 inserted', 'input2 focused']);
} finally {
document.body.removeChild(container);
}
});
it('calls focus() on autoFocus elements after they have been mounted to the DOM', () => {
@@ -308,20 +308,9 @@ describe('ReactDOM', () => {
it("shouldn't fire duplicate event handler while handling other nested dispatch", () => {
const actual = [];
function click(node) {
const fakeNativeEvent = function() {};
fakeNativeEvent.target = node;
fakeNativeEvent.path = [node, container];
ReactTestUtils.simulateNativeEventOnNode(
'topClick',
node,
fakeNativeEvent,
);
}
class Wrapper extends React.Component {
componentDidMount() {
click(this.ref1);
this.ref1.click();
}
render() {
@@ -330,7 +319,7 @@ describe('ReactDOM', () => {
<div
onClick={() => {
actual.push('1st node clicked');
click(this.ref2);
this.ref2.click();
}}
ref={ref => (this.ref1 = ref)}
/>
@@ -346,13 +335,18 @@ describe('ReactDOM', () => {
}
const container = document.createElement('div');
ReactDOM.render(<Wrapper />, container);
document.body.appendChild(container);
try {
ReactDOM.render(<Wrapper />, container);
const expected = [
'1st node clicked',
"2nd node clicked imperatively from 1st's handler",
];
expect(actual).toEqual(expected);
const expected = [
'1st node clicked',
"2nd node clicked imperatively from 1st's handler",
];
expect(actual).toEqual(expected);
} finally {
document.body.removeChild(container);
}
});
it('should not crash with devtools installed', () => {
@@ -439,4 +433,39 @@ describe('ReactDOM', () => {
Object.defineProperty(global, 'document', documentDescriptor);
}
});
it('warns when requestAnimationFrame is not polyfilled in the browser', () => {
const previousRAF = global.requestAnimationFrame;
try {
global.requestAnimationFrame = undefined;
jest.resetModules();
expect(() => require('react-dom')).toWarnDev(
"This browser doesn't support requestAnimationFrame.",
);
} finally {
global.requestAnimationFrame = previousRAF;
}
});
// We're just testing importing, not using it.
// It is important because even isomorphic components may import it.
it('can import findDOMNode in Node environment', () => {
const previousRAF = global.requestAnimationFrame;
const previousRIC = global.requestIdleCallback;
const prevWindow = global.window;
try {
global.requestAnimationFrame = undefined;
global.requestIdleCallback = undefined;
// Simulate the Node environment:
delete global.window;
jest.resetModules();
expect(() => {
require('react-dom');
}).not.toThrow();
} finally {
global.requestAnimationFrame = previousRAF;
global.requestIdleCallback = previousRIC;
global.window = prevWindow;
}
});
});

View File

@@ -738,28 +738,28 @@ describe('ReactDOMComponent', () => {
node.removeAttribute.mockImplementation(nodeRemoveAttribute);
ReactDOM.render(<div id="" />, container);
expect(node.setAttribute.mock.calls.length).toBe(0);
expect(node.removeAttribute.mock.calls.length).toBe(0);
expect(node.setAttribute).toHaveBeenCalledTimes(0);
expect(node.removeAttribute).toHaveBeenCalledTimes(0);
ReactDOM.render(<div id="foo" />, container);
expect(node.setAttribute.mock.calls.length).toBe(1);
expect(node.removeAttribute.mock.calls.length).toBe(0);
expect(node.setAttribute).toHaveBeenCalledTimes(1);
expect(node.removeAttribute).toHaveBeenCalledTimes(0);
ReactDOM.render(<div id="foo" />, container);
expect(node.setAttribute.mock.calls.length).toBe(1);
expect(node.removeAttribute.mock.calls.length).toBe(0);
expect(node.setAttribute).toHaveBeenCalledTimes(1);
expect(node.removeAttribute).toHaveBeenCalledTimes(0);
ReactDOM.render(<div />, container);
expect(node.setAttribute.mock.calls.length).toBe(1);
expect(node.removeAttribute.mock.calls.length).toBe(1);
expect(node.setAttribute).toHaveBeenCalledTimes(1);
expect(node.removeAttribute).toHaveBeenCalledTimes(1);
ReactDOM.render(<div id="" />, container);
expect(node.setAttribute.mock.calls.length).toBe(2);
expect(node.removeAttribute.mock.calls.length).toBe(1);
expect(node.setAttribute).toHaveBeenCalledTimes(2);
expect(node.removeAttribute).toHaveBeenCalledTimes(1);
ReactDOM.render(<div />, container);
expect(node.setAttribute.mock.calls.length).toBe(2);
expect(node.removeAttribute.mock.calls.length).toBe(2);
expect(node.setAttribute).toHaveBeenCalledTimes(2);
expect(node.removeAttribute).toHaveBeenCalledTimes(2);
});
it('should not incur unnecessary DOM mutations for string properties', () => {
@@ -768,7 +768,7 @@ describe('ReactDOMComponent', () => {
const node = container.firstChild;
const nodeValueSetter = jest.genMockFn();
const nodeValueSetter = jest.fn();
const oldSetAttribute = node.setAttribute.bind(node);
node.setAttribute = function(key, value) {
@@ -777,22 +777,22 @@ describe('ReactDOMComponent', () => {
};
ReactDOM.render(<div value="foo" />, container);
expect(nodeValueSetter.mock.calls.length).toBe(1);
expect(nodeValueSetter).toHaveBeenCalledTimes(1);
ReactDOM.render(<div value="foo" />, container);
expect(nodeValueSetter.mock.calls.length).toBe(1);
expect(nodeValueSetter).toHaveBeenCalledTimes(1);
ReactDOM.render(<div />, container);
expect(nodeValueSetter.mock.calls.length).toBe(1);
expect(nodeValueSetter).toHaveBeenCalledTimes(1);
ReactDOM.render(<div value={null} />, container);
expect(nodeValueSetter.mock.calls.length).toBe(1);
expect(nodeValueSetter).toHaveBeenCalledTimes(1);
ReactDOM.render(<div value="" />, container);
expect(nodeValueSetter.mock.calls.length).toBe(2);
expect(nodeValueSetter).toHaveBeenCalledTimes(2);
ReactDOM.render(<div />, container);
expect(nodeValueSetter.mock.calls.length).toBe(2);
expect(nodeValueSetter).toHaveBeenCalledTimes(2);
});
it('should not incur unnecessary DOM mutations for boolean properties', () => {
@@ -812,16 +812,16 @@ describe('ReactDOMComponent', () => {
});
ReactDOM.render(<div checked={true} />, container);
expect(nodeValueSetter.mock.calls.length).toBe(0);
expect(nodeValueSetter).toHaveBeenCalledTimes(0);
ReactDOM.render(<div />, container);
expect(nodeValueSetter.mock.calls.length).toBe(1);
expect(nodeValueSetter).toHaveBeenCalledTimes(1);
ReactDOM.render(<div checked={false} />, container);
expect(nodeValueSetter.mock.calls.length).toBe(2);
expect(nodeValueSetter).toHaveBeenCalledTimes(2);
ReactDOM.render(<div checked={true} />, container);
expect(nodeValueSetter.mock.calls.length).toBe(3);
expect(nodeValueSetter).toHaveBeenCalledTimes(3);
});
it('should ignore attribute whitelist for elements with the "is" attribute', () => {
@@ -850,9 +850,9 @@ describe('ReactDOMComponent', () => {
ReactDOM.render(<div dir={null} />, container);
ReactDOM.render(<div dir={undefined} />, container);
ReactDOM.render(<div />, container);
expect(setter.mock.calls.length).toBe(0);
expect(setter).toHaveBeenCalledTimes(0);
ReactDOM.render(<div dir="ltr" />, container);
expect(setter.mock.calls.length).toBe(1);
expect(setter).toHaveBeenCalledTimes(1);
});
it('handles multiple child updates without interference', () => {
@@ -984,7 +984,7 @@ describe('ReactDOMComponent', () => {
container.getElementsByTagName('source')[0].dispatchEvent(errorEvent);
if (__DEV__) {
expect(console.log.calls.count()).toBe(1);
expect(console.log).toHaveBeenCalledTimes(1);
expect(console.log.calls.argsFor(0)[0]).toContain('onError called');
}
});
@@ -1315,7 +1315,7 @@ describe('ReactDOMComponent', () => {
container.getElementsByTagName('image')[0].dispatchEvent(loadEvent);
if (__DEV__) {
expect(console.log.calls.count()).toBe(2);
expect(console.log).toHaveBeenCalledTimes(2);
expect(console.log.calls.argsFor(0)[0]).toContain('onError called');
expect(console.log.calls.argsFor(1)[0]).toContain('onLoad called');
}

View File

@@ -61,7 +61,7 @@ describe('ReactDOMEventListener', () => {
childNode.dispatchEvent(nativeEvent);
expect(mouseOut).toBeCalled();
expect(mouseOut.mock.calls.length).toBe(2);
expect(mouseOut).toHaveBeenCalledTimes(2);
expect(mouseOut.mock.calls[0][0]).toEqual(childNode);
expect(mouseOut.mock.calls[1][0]).toEqual(parentNode);
@@ -97,7 +97,7 @@ describe('ReactDOMEventListener', () => {
childNode.dispatchEvent(nativeEvent);
expect(mouseOut).toBeCalled();
expect(mouseOut.mock.calls.length).toBe(3);
expect(mouseOut).toHaveBeenCalledTimes(3);
expect(mouseOut.mock.calls[0][0]).toEqual(childNode);
expect(mouseOut.mock.calls[1][0]).toEqual(parentNode);
expect(mouseOut.mock.calls[2][0]).toEqual(grandParentNode);
@@ -169,7 +169,7 @@ describe('ReactDOMEventListener', () => {
childNode.dispatchEvent(nativeEvent);
// Child and parent should both call from event handlers.
expect(mock.mock.calls.length).toBe(2);
expect(mock).toHaveBeenCalledTimes(2);
// The first call schedules a render of '1' into the 'Child'.
// However, we're batching so it isn't flushed yet.
expect(mock.mock.calls[0][0]).toBe('Child');
@@ -213,8 +213,169 @@ describe('ReactDOMEventListener', () => {
instance.getInner().dispatchEvent(nativeEvent);
expect(mouseOut).toBeCalled();
expect(mouseOut.mock.calls.length).toBe(1);
expect(mouseOut).toHaveBeenCalledTimes(1);
expect(mouseOut.mock.calls[0][0]).toEqual(instance.getInner());
document.body.removeChild(container);
});
// Regression test for https://github.com/facebook/react/pull/12877
it('should not fire form events twice', () => {
const container = document.createElement('div');
document.body.appendChild(container);
const formRef = React.createRef();
const inputRef = React.createRef();
const handleInvalid = jest.fn();
const handleReset = jest.fn();
const handleSubmit = jest.fn();
ReactDOM.render(
<form ref={formRef} onReset={handleReset} onSubmit={handleSubmit}>
<input ref={inputRef} onInvalid={handleInvalid} />
</form>,
container,
);
inputRef.current.dispatchEvent(
new Event('invalid', {
// https://developer.mozilla.org/en-US/docs/Web/Events/invalid
bubbles: false,
}),
);
expect(handleInvalid).toHaveBeenCalledTimes(1);
formRef.current.dispatchEvent(
new Event('reset', {
// https://developer.mozilla.org/en-US/docs/Web/Events/reset
bubbles: true,
}),
);
expect(handleReset).toHaveBeenCalledTimes(1);
formRef.current.dispatchEvent(
new Event('submit', {
// https://developer.mozilla.org/en-US/docs/Web/Events/submit
bubbles: true,
}),
);
expect(handleSubmit).toHaveBeenCalledTimes(1);
formRef.current.dispatchEvent(
new Event('submit', {
// Might happen on older browsers.
bubbles: true,
}),
);
expect(handleSubmit).toHaveBeenCalledTimes(2); // It already fired in this test.
document.body.removeChild(container);
});
it('should dispatch loadstart only for media elements', () => {
const container = document.createElement('div');
document.body.appendChild(container);
const imgRef = React.createRef();
const videoRef = React.createRef();
const handleImgLoadStart = jest.fn();
const handleVideoLoadStart = jest.fn();
ReactDOM.render(
<div>
<img ref={imgRef} onLoadStart={handleImgLoadStart} />
<video ref={videoRef} onLoadStart={handleVideoLoadStart} />
</div>,
container,
);
// Note for debugging: loadstart currently doesn't fire in Chrome.
// https://bugs.chromium.org/p/chromium/issues/detail?id=458851
imgRef.current.dispatchEvent(
new ProgressEvent('loadstart', {
bubbles: false,
}),
);
// Historically, we happened to not support onLoadStart
// on <img>, and this test documents that lack of support.
// If we decide to support it in the future, we should change
// this line to expect 1 call. Note that fixing this would
// be simple but would require attaching a handler to each
// <img>. So far nobody asked us for it.
expect(handleImgLoadStart).toHaveBeenCalledTimes(0);
videoRef.current.dispatchEvent(
new ProgressEvent('loadstart', {
bubbles: false,
}),
);
expect(handleVideoLoadStart).toHaveBeenCalledTimes(1);
document.body.removeChild(container);
});
it('should not attempt to listen to unnecessary events on the top level', () => {
const container = document.createElement('div');
document.body.appendChild(container);
const videoRef = React.createRef();
const handleVideoPlay = jest.fn(); // We'll test this one.
const mediaEvents = {
onAbort() {},
onCanPlay() {},
onCanPlayThrough() {},
onDurationChange() {},
onEmptied() {},
onEncrypted() {},
onEnded() {},
onError() {},
onLoadedData() {},
onLoadedMetadata() {},
onLoadStart() {},
onPause() {},
onPlay() {},
onPlaying() {},
onProgress() {},
onRateChange() {},
onSeeked() {},
onSeeking() {},
onStalled() {},
onSuspend() {},
onTimeUpdate() {},
onVolumeChange() {},
onWaiting() {},
};
const originalAddEventListener = document.addEventListener;
document.addEventListener = function(type) {
throw new Error(
`Did not expect to add a top-level listener for the "${type}" event.`,
);
};
try {
// We expect that mounting this tree will
// *not* attach handlers for any top-level events.
ReactDOM.render(
<div>
<video ref={videoRef} {...mediaEvents} onPlay={handleVideoPlay} />
<audio {...mediaEvents}>
<source {...mediaEvents} />
</audio>
<form onReset={() => {}} onSubmit={() => {}} />
</div>,
container,
);
// Also verify dispatching one of them works
videoRef.current.dispatchEvent(
new Event('play', {
bubbles: false,
}),
);
expect(handleVideoPlay).toHaveBeenCalledTimes(1);
} finally {
document.addEventListener = originalAddEventListener;
document.body.removeChild(container);
}
});
});

View File

@@ -11,7 +11,6 @@
const React = require('react');
const ReactDOM = require('react-dom');
const ReactTestUtils = require('react-dom/test-utils');
const PropTypes = require('prop-types');
describe('ReactDOMFiber', () => {
@@ -171,6 +170,31 @@ describe('ReactDOMFiber', () => {
expect(firstNode.tagName).toBe('DIV');
});
it('renders an empty fragment', () => {
const Div = () => <div />;
const EmptyFragment = () => <React.Fragment />;
const NonEmptyFragment = () => (
<React.Fragment>
<Div />
</React.Fragment>
);
ReactDOM.render(<EmptyFragment />, container);
expect(container.firstChild).toBe(null);
ReactDOM.render(<NonEmptyFragment />, container);
expect(container.firstChild.tagName).toBe('DIV');
ReactDOM.render(<EmptyFragment />, container);
expect(container.firstChild).toBe(null);
ReactDOM.render(<Div />, container);
expect(container.firstChild.tagName).toBe('DIV');
ReactDOM.render(<EmptyFragment />, container);
expect(container.firstChild).toBe(null);
});
let svgEls, htmlEls, mathEls;
const expectSVG = {ref: el => svgEls.push(el)};
const expectHTML = {ref: el => htmlEls.push(el)};
@@ -818,38 +842,39 @@ describe('ReactDOMFiber', () => {
it('should bubble events from the portal to the parent', () => {
const portalContainer = document.createElement('div');
document.body.appendChild(portalContainer);
try {
const ops = [];
let portal = null;
const ops = [];
let portal = null;
ReactDOM.render(
<div onClick={() => ops.push('parent clicked')}>
{ReactDOM.createPortal(
<div
onClick={() => ops.push('portal clicked')}
ref={n => (portal = n)}>
portal
</div>,
portalContainer,
)}
</div>,
container,
);
ReactDOM.render(
<div onClick={() => ops.push('parent clicked')}>
{ReactDOM.createPortal(
<div
onClick={() => ops.push('portal clicked')}
ref={n => (portal = n)}>
portal
</div>,
portalContainer,
)}
</div>,
container,
);
expect(portal.tagName).toBe('DIV');
expect(portal.tagName).toBe('DIV');
portal.click();
const fakeNativeEvent = {};
ReactTestUtils.simulateNativeEventOnNode(
'topClick',
portal,
fakeNativeEvent,
);
expect(ops).toEqual(['portal clicked', 'parent clicked']);
expect(ops).toEqual(['portal clicked', 'parent clicked']);
} finally {
document.body.removeChild(portalContainer);
}
});
it('should not onMouseLeave when staying in the portal', () => {
const portalContainer = document.createElement('div');
document.body.appendChild(container);
document.body.appendChild(portalContainer);
let ops = [];
let firstTarget = null;
@@ -858,58 +883,69 @@ describe('ReactDOMFiber', () => {
function simulateMouseMove(from, to) {
if (from) {
ReactTestUtils.simulateNativeEventOnNode('topMouseOut', from, {
target: from,
relatedTarget: to,
});
from.dispatchEvent(
new MouseEvent('mouseout', {
bubbles: true,
cancelable: true,
relatedTarget: to,
}),
);
}
if (to) {
ReactTestUtils.simulateNativeEventOnNode('topMouseOver', to, {
target: to,
relatedTarget: from,
});
to.dispatchEvent(
new MouseEvent('mouseover', {
bubbles: true,
cancelable: true,
relatedTarget: from,
}),
);
}
}
ReactDOM.render(
<div>
<div
onMouseEnter={() => ops.push('enter parent')}
onMouseLeave={() => ops.push('leave parent')}>
<div ref={n => (firstTarget = n)} />
{ReactDOM.createPortal(
<div
onMouseEnter={() => ops.push('enter portal')}
onMouseLeave={() => ops.push('leave portal')}
ref={n => (secondTarget = n)}>
portal
</div>,
portalContainer,
)}
</div>
<div ref={n => (thirdTarget = n)} />
</div>,
container,
);
try {
ReactDOM.render(
<div>
<div
onMouseEnter={() => ops.push('enter parent')}
onMouseLeave={() => ops.push('leave parent')}>
<div ref={n => (firstTarget = n)} />
{ReactDOM.createPortal(
<div
onMouseEnter={() => ops.push('enter portal')}
onMouseLeave={() => ops.push('leave portal')}
ref={n => (secondTarget = n)}>
portal
</div>,
portalContainer,
)}
</div>
<div ref={n => (thirdTarget = n)} />
</div>,
container,
);
simulateMouseMove(null, firstTarget);
expect(ops).toEqual(['enter parent']);
simulateMouseMove(null, firstTarget);
expect(ops).toEqual(['enter parent']);
ops = [];
ops = [];
simulateMouseMove(firstTarget, secondTarget);
expect(ops).toEqual([
// Parent did not invoke leave because we're still inside the portal.
'enter portal',
]);
simulateMouseMove(firstTarget, secondTarget);
expect(ops).toEqual([
// Parent did not invoke leave because we're still inside the portal.
'enter portal',
]);
ops = [];
ops = [];
simulateMouseMove(secondTarget, thirdTarget);
expect(ops).toEqual([
'leave portal',
'leave parent', // Only when we leave the portal does onMouseLeave fire.
]);
simulateMouseMove(secondTarget, thirdTarget);
expect(ops).toEqual([
'leave portal',
'leave parent', // Only when we leave the portal does onMouseLeave fire.
]);
} finally {
document.body.removeChild(container);
document.body.removeChild(portalContainer);
}
});
it('should throw on bad createPortal argument', () => {
@@ -949,86 +985,82 @@ describe('ReactDOMFiber', () => {
});
it('should not update event handlers until commit', () => {
let ops = [];
const handlerA = () => ops.push('A');
const handlerB = () => ops.push('B');
document.body.appendChild(container);
try {
let ops = [];
const handlerA = () => ops.push('A');
const handlerB = () => ops.push('B');
class Example extends React.Component {
state = {flip: false, count: 0};
flip() {
this.setState({flip: true, count: this.state.count + 1});
class Example extends React.Component {
state = {flip: false, count: 0};
flip() {
this.setState({flip: true, count: this.state.count + 1});
}
tick() {
this.setState({count: this.state.count + 1});
}
render() {
const useB = !this.props.forceA && this.state.flip;
return <div onClick={useB ? handlerB : handlerA} />;
}
}
tick() {
this.setState({count: this.state.count + 1});
}
render() {
const useB = !this.props.forceA && this.state.flip;
return <div onClick={useB ? handlerB : handlerA} />;
}
}
class Click extends React.Component {
constructor() {
super();
click(node);
class Click extends React.Component {
constructor() {
super();
node.click();
}
render() {
return null;
}
}
render() {
return null;
}
}
let inst;
ReactDOM.render([<Example key="a" ref={n => (inst = n)} />], container);
const node = container.firstChild;
expect(node.tagName).toEqual('DIV');
let inst;
ReactDOM.render([<Example key="a" ref={n => (inst = n)} />], container);
const node = container.firstChild;
expect(node.tagName).toEqual('DIV');
function click(target) {
const fakeNativeEvent = {};
ReactTestUtils.simulateNativeEventOnNode(
'topClick',
target,
fakeNativeEvent,
node.click();
expect(ops).toEqual(['A']);
ops = [];
// Render with the other event handler.
inst.flip();
node.click();
expect(ops).toEqual(['B']);
ops = [];
// Rerender without changing any props.
inst.tick();
node.click();
expect(ops).toEqual(['B']);
ops = [];
// Render a flip back to the A handler. The second component invokes the
// click handler during render to simulate a click during an aborted
// render. I use this hack because at current time we don't have a way to
// test aborted ReactDOM renders.
ReactDOM.render(
[<Example key="a" forceA={true} />, <Click key="b" />],
container,
);
// Because the new click handler has not yet committed, we should still
// invoke B.
expect(ops).toEqual(['B']);
ops = [];
// Any click that happens after commit, should invoke A.
node.click();
expect(ops).toEqual(['A']);
} finally {
document.body.removeChild(container);
}
click(node);
expect(ops).toEqual(['A']);
ops = [];
// Render with the other event handler.
inst.flip();
click(node);
expect(ops).toEqual(['B']);
ops = [];
// Rerender without changing any props.
inst.tick();
click(node);
expect(ops).toEqual(['B']);
ops = [];
// Render a flip back to the A handler. The second component invokes the
// click handler during render to simulate a click during an aborted
// render. I use this hack because at current time we don't have a way to
// test aborted ReactDOM renders.
ReactDOM.render(
[<Example key="a" forceA={true} />, <Click key="b" />],
container,
);
// Because the new click handler has not yet committed, we should still
// invoke B.
expect(ops).toEqual(['B']);
ops = [];
// Any click that happens after commit, should invoke A.
click(node);
expect(ops).toEqual(['A']);
});
it('should not crash encountering low-priority tree', () => {

View File

@@ -20,6 +20,31 @@ describe('ReactDOMFiberAsync', () => {
let container;
beforeEach(() => {
// TODO pull this into helper method, reduce repetition.
// mock the browser APIs which are used in react-scheduler:
// - requestAnimationFrame should pass the DOMHighResTimeStamp argument
// - calling 'window.postMessage' should actually fire postmessage handlers
global.requestAnimationFrame = function(cb) {
return setTimeout(() => {
cb(Date.now());
});
};
const originalAddEventListener = global.addEventListener;
let postMessageCallback;
global.addEventListener = function(eventName, callback, useCapture) {
if (eventName === 'message') {
postMessageCallback = callback;
} else {
originalAddEventListener(eventName, callback, useCapture);
}
};
global.postMessage = function(messageKey, targetOrigin) {
const postMessageEvent = {source: window, data: messageKey};
if (postMessageCallback) {
postMessageCallback(postMessageEvent);
}
};
jest.resetModules();
container = document.createElement('div');
ReactDOM = require('react-dom');
});

View File

@@ -11,24 +11,22 @@
describe('ReactDOMIframe', () => {
let React;
let ReactDOM;
let ReactTestUtils;
beforeEach(() => {
React = require('react');
ReactDOM = require('react-dom');
ReactTestUtils = require('react-dom/test-utils');
});
it('should trigger load events', () => {
const onLoadSpy = jasmine.createSpy();
const onLoadSpy = jest.fn();
let iframe = React.createElement('iframe', {onLoad: onLoadSpy});
iframe = ReactTestUtils.renderIntoDocument(iframe);
const loadEvent = document.createEvent('Event');
loadEvent.initEvent('load', false, false);
ReactDOM.findDOMNode(iframe).dispatchEvent(loadEvent);
iframe.dispatchEvent(loadEvent);
expect(onLoadSpy).toHaveBeenCalled();
});

View File

@@ -9,7 +9,7 @@
'use strict';
const emptyFunction = require('fbjs/lib/emptyFunction');
function emptyFunction() {}
describe('ReactDOMInput', () => {
let React;
@@ -38,27 +38,27 @@ describe('ReactDOMInput', () => {
it('should properly control a value even if no event listener exists', () => {
const container = document.createElement('div');
let stub;
let node;
expect(() => {
stub = ReactDOM.render(<input type="text" value="lion" />, container);
node = ReactDOM.render(<input type="text" value="lion" />, container);
}).toWarnDev(
'Failed prop type: You provided a `value` prop to a form field without an `onChange` handler.',
);
document.body.appendChild(container);
const node = ReactDOM.findDOMNode(stub);
try {
setUntrackedValue.call(node, 'giraffe');
setUntrackedValue.call(node, 'giraffe');
// This must use the native event dispatching. If we simulate, we will
// bypass the lazy event attachment system so we won't actually test this.
dispatchEventOnNode(node, 'change');
// This must use the native event dispatching. If we simulate, we will
// bypass the lazy event attachment system so we won't actually test this.
dispatchEventOnNode(node, 'change');
expect(node.value).toBe('lion');
document.body.removeChild(container);
expect(node.value).toBe('lion');
} finally {
document.body.removeChild(container);
}
});
it('should control a value in reentrant events', () => {
@@ -169,26 +169,24 @@ describe('ReactDOMInput', () => {
describe('switching text inputs between numeric and string numbers', () => {
it('does change the number 2 to "2.0" with no change handler', () => {
let stub = <input type="text" value={2} onChange={jest.fn()} />;
stub = ReactTestUtils.renderIntoDocument(stub);
const node = ReactDOM.findDOMNode(stub);
const stub = <input type="text" value={2} onChange={jest.fn()} />;
const node = ReactTestUtils.renderIntoDocument(stub);
node.value = '2.0';
ReactTestUtils.Simulate.change(stub);
ReactTestUtils.Simulate.change(node);
expect(node.getAttribute('value')).toBe('2');
expect(node.value).toBe('2');
});
it('does change the string "2" to "2.0" with no change handler', () => {
let stub = <input type="text" value={'2'} onChange={jest.fn()} />;
stub = ReactTestUtils.renderIntoDocument(stub);
const node = ReactDOM.findDOMNode(stub);
const stub = <input type="text" value={'2'} onChange={jest.fn()} />;
const node = ReactTestUtils.renderIntoDocument(stub);
node.value = '2.0';
ReactTestUtils.Simulate.change(stub);
ReactTestUtils.Simulate.change(node);
expect(node.getAttribute('value')).toBe('2');
expect(node.value).toBe('2');
@@ -315,8 +313,7 @@ describe('ReactDOMInput', () => {
it('should display `defaultValue` of number 0', () => {
let stub = <input type="text" defaultValue={0} />;
stub = ReactTestUtils.renderIntoDocument(stub);
const node = ReactDOM.findDOMNode(stub);
const node = ReactTestUtils.renderIntoDocument(stub);
expect(node.getAttribute('value')).toBe('0');
expect(node.value).toBe('0');
@@ -348,16 +345,14 @@ describe('ReactDOMInput', () => {
it('should display "true" for `defaultValue` of `true`', () => {
let stub = <input type="text" defaultValue={true} />;
stub = ReactTestUtils.renderIntoDocument(stub);
const node = ReactDOM.findDOMNode(stub);
const node = ReactTestUtils.renderIntoDocument(stub);
expect(node.value).toBe('true');
});
it('should display "false" for `defaultValue` of `false`', () => {
let stub = <input type="text" defaultValue={false} />;
stub = ReactTestUtils.renderIntoDocument(stub);
const node = ReactDOM.findDOMNode(stub);
const node = ReactTestUtils.renderIntoDocument(stub);
expect(node.value).toBe('false');
});
@@ -467,17 +462,15 @@ describe('ReactDOMInput', () => {
},
};
let stub = <input type="text" defaultValue={objToString} />;
stub = ReactTestUtils.renderIntoDocument(stub);
const node = ReactDOM.findDOMNode(stub);
const stub = <input type="text" defaultValue={objToString} />;
const node = ReactTestUtils.renderIntoDocument(stub);
expect(node.value).toBe('foobar');
});
it('should display `value` of number 0', () => {
let stub = <input type="text" value={0} />;
stub = ReactTestUtils.renderIntoDocument(stub);
const node = ReactDOM.findDOMNode(stub);
const stub = <input type="text" value={0} />;
const node = ReactTestUtils.renderIntoDocument(stub);
expect(node.value).toBe('0');
});
@@ -535,7 +528,7 @@ describe('ReactDOMInput', () => {
const node = container.firstChild;
let nodeValue = 'a';
const nodeValueSetter = jest.genMockFn();
const nodeValueSetter = jest.fn();
Object.defineProperty(node, 'value', {
get: function() {
return nodeValue;
@@ -546,10 +539,10 @@ describe('ReactDOMInput', () => {
});
ReactDOM.render(<input value="a" onChange={() => {}} />, container);
expect(nodeValueSetter.mock.calls.length).toBe(0);
expect(nodeValueSetter).toHaveBeenCalledTimes(0);
ReactDOM.render(<input value="b" onChange={() => {}} />, container);
expect(nodeValueSetter.mock.calls.length).toBe(1);
expect(nodeValueSetter).toHaveBeenCalledTimes(1);
});
it('should not incur unnecessary DOM mutations for numeric type conversion', () => {
@@ -558,7 +551,7 @@ describe('ReactDOMInput', () => {
const node = container.firstChild;
let nodeValue = '0';
const nodeValueSetter = jest.genMockFn();
const nodeValueSetter = jest.fn();
Object.defineProperty(node, 'value', {
get: function() {
return nodeValue;
@@ -569,7 +562,7 @@ describe('ReactDOMInput', () => {
});
ReactDOM.render(<input value={0} onChange={() => {}} />, container);
expect(nodeValueSetter.mock.calls.length).toBe(0);
expect(nodeValueSetter).toHaveBeenCalledTimes(0);
});
it('should not incur unnecessary DOM mutations for the boolean type conversion', () => {
@@ -578,7 +571,7 @@ describe('ReactDOMInput', () => {
const node = container.firstChild;
let nodeValue = 'true';
const nodeValueSetter = jest.genMockFn();
const nodeValueSetter = jest.fn();
Object.defineProperty(node, 'value', {
get: function() {
return nodeValue;
@@ -589,13 +582,12 @@ describe('ReactDOMInput', () => {
});
ReactDOM.render(<input value={true} onChange={() => {}} />, container);
expect(nodeValueSetter.mock.calls.length).toBe(0);
expect(nodeValueSetter).toHaveBeenCalledTimes(0);
});
it('should properly control a value of number `0`', () => {
let stub = <input type="text" value={0} onChange={emptyFunction} />;
stub = ReactTestUtils.renderIntoDocument(stub);
const node = ReactDOM.findDOMNode(stub);
const stub = <input type="text" value={0} onChange={emptyFunction} />;
const node = ReactTestUtils.renderIntoDocument(stub);
node.value = 'giraffe';
ReactTestUtils.Simulate.change(node);
@@ -603,9 +595,8 @@ describe('ReactDOMInput', () => {
});
it('should properly control 0.0 for a text input', () => {
let stub = <input type="text" value={0} onChange={emptyFunction} />;
stub = ReactTestUtils.renderIntoDocument(stub);
const node = ReactDOM.findDOMNode(stub);
const stub = <input type="text" value={0} onChange={emptyFunction} />;
const node = ReactTestUtils.renderIntoDocument(stub);
node.value = '0.0';
ReactTestUtils.Simulate.change(node, {target: {value: '0.0'}});
@@ -613,9 +604,8 @@ describe('ReactDOMInput', () => {
});
it('should properly control 0.0 for a number input', () => {
let stub = <input type="number" value={0} onChange={emptyFunction} />;
stub = ReactTestUtils.renderIntoDocument(stub);
const node = ReactDOM.findDOMNode(stub);
const stub = <input type="number" value={0} onChange={emptyFunction} />;
const node = ReactTestUtils.renderIntoDocument(stub);
node.value = '0.0';
ReactTestUtils.Simulate.change(node, {target: {value: '0.0'}});
@@ -694,18 +684,16 @@ describe('ReactDOMInput', () => {
setUntrackedValue.call(node, 'giraffe');
const fakeNativeEvent = function() {};
fakeNativeEvent.target = node;
fakeNativeEvent.path = [node, container];
ReactTestUtils.simulateNativeEventOnNode('topInput', node, fakeNativeEvent);
ReactTestUtils.SimulateNative.input(node, {
path: [node, container],
});
expect(handled).toBe(true);
});
it('should not set a value for submit buttons unnecessarily', () => {
let stub = <input type="submit" />;
stub = ReactTestUtils.renderIntoDocument(stub);
const node = ReactDOM.findDOMNode(stub);
const stub = <input type="submit" />;
const node = ReactTestUtils.renderIntoDocument(stub);
// The value shouldn't be '', or else the button will have no text; it
// should have the default "Submit" or "Submit Query" label. Most browsers
@@ -1461,10 +1449,9 @@ describe('ReactDOMInput', () => {
});
it('an uncontrolled number input will not update the value attribute on blur', () => {
const stub = ReactTestUtils.renderIntoDocument(
const node = ReactTestUtils.renderIntoDocument(
<input type="number" defaultValue="1" />,
);
const node = ReactDOM.findDOMNode(stub);
node.value = 4;
@@ -1474,10 +1461,9 @@ describe('ReactDOMInput', () => {
});
it('an uncontrolled text input will not update the value attribute on blur', () => {
const stub = ReactTestUtils.renderIntoDocument(
const node = ReactTestUtils.renderIntoDocument(
<input type="text" defaultValue="1" />,
);
const node = ReactDOM.findDOMNode(stub);
node.value = 4;

View File

@@ -26,8 +26,7 @@ describe('ReactDOMOption', () => {
{1} {'foo'}
</option>
);
stub = ReactTestUtils.renderIntoDocument(stub);
const node = ReactDOM.findDOMNode(stub);
const node = ReactTestUtils.renderIntoDocument(stub);
expect(node.innerHTML).toBe('1 foo');
});
@@ -59,17 +58,15 @@ describe('ReactDOMOption', () => {
{undefined} {2}
</option>
);
stub = ReactTestUtils.renderIntoDocument(stub);
const node = ReactTestUtils.renderIntoDocument(stub);
const node = ReactDOM.findDOMNode(stub);
expect(node.innerHTML).toBe('1 2');
});
it('should be able to use dangerouslySetInnerHTML on option', () => {
let stub = <option dangerouslySetInnerHTML={{__html: 'foobar'}} />;
stub = ReactTestUtils.renderIntoDocument(stub);
const node = ReactTestUtils.renderIntoDocument(stub);
const node = ReactDOM.findDOMNode(stub);
expect(node.innerHTML).toBe('foobar');
});
@@ -95,8 +92,7 @@ describe('ReactDOMOption', () => {
);
const options = stub.props.children;
const container = document.createElement('div');
stub = ReactDOM.render(stub, container);
const node = ReactDOM.findDOMNode(stub);
const node = ReactDOM.render(stub, container);
expect(node.selectedIndex).toBe(1);

View File

@@ -17,52 +17,46 @@ let AsyncMode = React.unstable_AsyncMode;
describe('ReactDOMRoot', () => {
let container;
let scheduledCallback;
let flush;
let now;
let expire;
let advanceCurrentTime;
beforeEach(() => {
container = document.createElement('div');
// Override requestIdleCallback
scheduledCallback = null;
flush = function(units = Infinity) {
if (scheduledCallback !== null) {
let didStop = false;
while (scheduledCallback !== null && !didStop) {
const cb = scheduledCallback;
scheduledCallback = null;
cb({
timeRemaining() {
if (units > 0) {
return 999;
}
didStop = true;
return 0;
},
});
units--;
}
// TODO pull this into helper method, reduce repetition.
// mock the browser APIs which are used in react-scheduler:
// - requestAnimationFrame should pass the DOMHighResTimeStamp argument
// - calling 'window.postMessage' should actually fire postmessage handlers
// - must allow artificially changing time returned by Date.now
// Performance.now is not supported in the test environment
const originalDateNow = Date.now;
let advancedTime = null;
global.Date.now = function() {
if (advancedTime) {
return originalDateNow() + advancedTime;
}
return originalDateNow();
};
advanceCurrentTime = function(amount) {
advancedTime = amount;
};
global.requestAnimationFrame = function(cb) {
return setTimeout(() => {
cb(Date.now());
});
};
const originalAddEventListener = global.addEventListener;
let postMessageCallback;
global.addEventListener = function(eventName, callback, useCapture) {
if (eventName === 'message') {
postMessageCallback = callback;
} else {
originalAddEventListener(eventName, callback, useCapture);
}
};
global.performance = {
now() {
return now;
},
};
global.requestIdleCallback = function(cb) {
scheduledCallback = cb;
};
now = 0;
expire = function(ms) {
now += ms;
};
global.performance = {
now() {
return now;
},
global.postMessage = function(messageKey, targetOrigin) {
const postMessageEvent = {source: window, data: messageKey};
if (postMessageCallback) {
postMessageCallback(postMessageEvent);
}
};
jest.resetModules();
@@ -75,17 +69,17 @@ describe('ReactDOMRoot', () => {
it('renders children', () => {
const root = ReactDOM.unstable_createRoot(container);
root.render(<div>Hi</div>);
flush();
jest.runAllTimers();
expect(container.textContent).toEqual('Hi');
});
it('unmounts children', () => {
const root = ReactDOM.unstable_createRoot(container);
root.render(<div>Hi</div>);
flush();
jest.runAllTimers();
expect(container.textContent).toEqual('Hi');
root.unmount();
flush();
jest.runAllTimers();
expect(container.textContent).toEqual('');
});
@@ -97,7 +91,7 @@ describe('ReactDOMRoot', () => {
ops.push('inside callback: ' + container.textContent);
});
ops.push('before committing: ' + container.textContent);
flush();
jest.runAllTimers();
ops.push('after committing: ' + container.textContent);
expect(ops).toEqual([
'before committing: ',
@@ -110,7 +104,7 @@ describe('ReactDOMRoot', () => {
it('resolves `work.then` callback synchronously if the work already committed', () => {
const root = ReactDOM.unstable_createRoot(container);
const work = root.render(<AsyncMode>Hi</AsyncMode>);
flush();
jest.runAllTimers();
let ops = [];
work.then(() => {
ops.push('inside callback');
@@ -138,7 +132,7 @@ describe('ReactDOMRoot', () => {
<span />
</div>,
);
flush();
jest.runAllTimers();
// Accepts `hydrate` option
const container2 = document.createElement('div');
@@ -149,7 +143,7 @@ describe('ReactDOMRoot', () => {
<span />
</div>,
);
expect(flush).toWarnDev('Extra attributes');
expect(jest.runAllTimers).toWarnDev('Extra attributes');
});
it('does not clear existing children', async () => {
@@ -161,7 +155,7 @@ describe('ReactDOMRoot', () => {
<span>d</span>
</div>,
);
flush();
jest.runAllTimers();
expect(container.textContent).toEqual('abcd');
root.render(
<div>
@@ -169,7 +163,7 @@ describe('ReactDOMRoot', () => {
<span>c</span>
</div>,
);
flush();
jest.runAllTimers();
expect(container.textContent).toEqual('abdc');
});
@@ -205,7 +199,7 @@ describe('ReactDOMRoot', () => {
</AsyncMode>,
);
flush();
jest.runAllTimers();
// Hasn't updated yet
expect(container.textContent).toEqual('');
@@ -234,7 +228,7 @@ describe('ReactDOMRoot', () => {
const batch = root.createBatch();
batch.render(<Foo>Hi</Foo>);
// Flush all async work.
flush();
jest.runAllTimers();
// Root should complete without committing.
expect(ops).toEqual(['Foo']);
expect(container.textContent).toEqual('');
@@ -252,7 +246,7 @@ describe('ReactDOMRoot', () => {
const batch = root.createBatch();
batch.render(<AsyncMode>Foo</AsyncMode>);
flush();
jest.runAllTimers();
// Hasn't updated yet
expect(container.textContent).toEqual('');
@@ -292,7 +286,7 @@ describe('ReactDOMRoot', () => {
const root = ReactDOM.unstable_createRoot(container);
root.render(<AsyncMode>1</AsyncMode>);
expire(2000);
advanceCurrentTime(2000);
// This batch has a later expiration time than the earlier update.
const batch = root.createBatch();
@@ -300,7 +294,7 @@ describe('ReactDOMRoot', () => {
batch.commit();
expect(container.textContent).toEqual('');
flush();
jest.runAllTimers();
expect(container.textContent).toEqual('1');
});
@@ -327,7 +321,7 @@ describe('ReactDOMRoot', () => {
batch1.render(1);
// This batch has a later expiration time
expire(2000);
advanceCurrentTime(2000);
const batch2 = root.createBatch();
batch2.render(2);
@@ -346,7 +340,7 @@ describe('ReactDOMRoot', () => {
batch1.render(1);
// This batch has a later expiration time
expire(2000);
advanceCurrentTime(2000);
const batch2 = root.createBatch();
batch2.render(2);
@@ -356,7 +350,7 @@ describe('ReactDOMRoot', () => {
expect(container.textContent).toEqual('2');
batch1.commit();
flush();
jest.runAllTimers();
expect(container.textContent).toEqual('1');
});

View File

@@ -35,8 +35,7 @@ describe('ReactDOMSelect', () => {
);
const options = stub.props.children;
const container = document.createElement('div');
stub = ReactDOM.render(stub, container);
const node = ReactDOM.findDOMNode(stub);
const node = ReactDOM.render(stub, container);
expect(node.value).toBe('giraffe');
@@ -65,8 +64,7 @@ describe('ReactDOMSelect', () => {
</select>
);
const container = document.createElement('div');
const stub = ReactDOM.render(el, container);
const node = ReactDOM.findDOMNode(stub);
const node = ReactDOM.render(el, container);
expect(node.value).toBe('giraffe');
@@ -86,8 +84,7 @@ describe('ReactDOMSelect', () => {
);
const options = stub.props.children;
const container = document.createElement('div');
stub = ReactDOM.render(stub, container);
const node = ReactDOM.findDOMNode(stub);
const node = ReactDOM.render(stub, container);
expect(node.options[0].selected).toBe(false); // monkey
expect(node.options[1].selected).toBe(true); // giraffe
@@ -116,8 +113,7 @@ describe('ReactDOMSelect', () => {
);
const options = stub.props.children;
const container = document.createElement('div');
stub = ReactDOM.render(stub, container);
const node = ReactDOM.findDOMNode(stub);
const node = ReactDOM.render(stub, container);
expect(node.value).toBe('giraffe');
@@ -141,8 +137,7 @@ describe('ReactDOMSelect', () => {
</select>
);
const container = document.createElement('div');
stub = ReactDOM.render(stub, container);
const node = ReactDOM.findDOMNode(stub);
const node = ReactDOM.render(stub, container);
expect(node.options[0].selected).toBe(false);
expect(node.options[2].selected).toBe(true);
});
@@ -157,8 +152,7 @@ describe('ReactDOMSelect', () => {
);
const options = stub.props.children;
const container = document.createElement('div');
stub = ReactDOM.render(stub, container);
const node = ReactDOM.findDOMNode(stub);
const node = ReactDOM.render(stub, container);
expect(node.value).toBe('__proto__');
@@ -190,8 +184,7 @@ describe('ReactDOMSelect', () => {
);
const options = stub.props.children;
const container = document.createElement('div');
stub = ReactDOM.render(stub, container);
const node = ReactDOM.findDOMNode(stub);
const node = ReactDOM.render(stub, container);
expect(node.options[0].selected).toBe(false); // monkey
expect(node.options[1].selected).toBe(true); // giraffe
@@ -220,8 +213,7 @@ describe('ReactDOMSelect', () => {
);
const options = stub.props.children;
const container = document.createElement('div');
stub = ReactDOM.render(stub, container);
const node = ReactDOM.findDOMNode(stub);
const node = ReactDOM.render(stub, container);
expect(node.options[0].selected).toBe(false); // monkey
expect(node.options[1].selected).toBe(true); // __proto__
@@ -248,8 +240,7 @@ describe('ReactDOMSelect', () => {
<option value="12">twelve</option>
</select>
);
stub = ReactTestUtils.renderIntoDocument(stub);
const node = ReactDOM.findDOMNode(stub);
const node = ReactTestUtils.renderIntoDocument(stub);
expect(node.options[0].selected).toBe(false); // one
expect(node.options[1].selected).toBe(false); // two
@@ -259,7 +250,7 @@ describe('ReactDOMSelect', () => {
it('should reset child options selected when they are changed and `value` is set', () => {
let stub = <select multiple={true} value={['a', 'b']} onChange={noop} />;
const container = document.createElement('div');
stub = ReactDOM.render(stub, container);
const node = ReactDOM.render(stub, container);
ReactDOM.render(
<select multiple={true} value={['a', 'b']} onChange={noop}>
@@ -270,8 +261,6 @@ describe('ReactDOMSelect', () => {
container,
);
const node = ReactDOM.findDOMNode(stub);
expect(node.options[0].selected).toBe(true); // a
expect(node.options[1].selected).toBe(true); // b
expect(node.options[2].selected).toBe(false); // c
@@ -293,8 +282,7 @@ describe('ReactDOMSelect', () => {
</select>
);
const container = document.createElement('div');
const stub = ReactDOM.render(el, container);
const node = ReactDOM.findDOMNode(stub);
const node = ReactDOM.render(el, container);
expect(node.options[0].selected).toBe(false); // monkey
expect(node.options[1].selected).toBe(true); // giraffe
@@ -327,8 +315,7 @@ describe('ReactDOMSelect', () => {
);
const options = stub.props.children;
const container = document.createElement('div');
stub = ReactDOM.render(stub, container);
const node = ReactDOM.findDOMNode(stub);
const node = ReactDOM.render(stub, container);
expect(node.options[0].selected).toBe(false); // monkey
expect(node.options[1].selected).toBe(true); // giraffe
@@ -357,8 +344,7 @@ describe('ReactDOMSelect', () => {
);
const options = stub.props.children;
const container = document.createElement('div');
stub = ReactDOM.render(stub, container);
const node = ReactDOM.findDOMNode(stub);
const node = ReactDOM.render(stub, container);
expect(node.options[0].selected).toBe(false); // monkey
expect(node.options[1].selected).toBe(true); // giraffe
@@ -386,8 +372,7 @@ describe('ReactDOMSelect', () => {
);
const options = stub.props.children;
const container = document.createElement('div');
stub = ReactDOM.render(stub, container);
const node = ReactDOM.findDOMNode(stub);
const node = ReactDOM.render(stub, container);
expect(node.options[0].selected).toBe(false); // monkey
expect(node.options[1].selected).toBe(true); // giraffe
@@ -410,8 +395,7 @@ describe('ReactDOMSelect', () => {
);
const options = stub.props.children;
const container = document.createElement('div');
stub = ReactDOM.render(stub, container);
const node = ReactDOM.findDOMNode(stub);
const node = ReactDOM.render(stub, container);
ReactDOM.render(
<select value="gorilla" onChange={noop}>
@@ -459,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}>
@@ -476,7 +491,7 @@ describe('ReactDOMSelect', () => {
it('should not control defaultValue if readding options', () => {
const container = document.createElement('div');
const select = ReactDOM.render(
const node = ReactDOM.render(
<select multiple={true} defaultValue={['giraffe']}>
<option key="monkey" value="monkey">
A monkey!
@@ -490,7 +505,6 @@ describe('ReactDOMSelect', () => {
</select>,
container,
);
const node = ReactDOM.findDOMNode(select);
expect(node.options[0].selected).toBe(false); // monkey
expect(node.options[1].selected).toBe(true); // giraffe
@@ -601,8 +615,7 @@ describe('ReactDOMSelect', () => {
<option value="gorilla">A gorilla!</option>
</select>
);
stub = ReactTestUtils.renderIntoDocument(stub);
const node = ReactDOM.findDOMNode(stub);
const node = ReactTestUtils.renderIntoDocument(stub);
ReactTestUtils.Simulate.change(node);
@@ -648,8 +661,7 @@ describe('ReactDOMSelect', () => {
<option value="gorilla">A gorilla!</option>
</select>
);
stub = ReactDOM.render(stub, container);
const node = ReactDOM.findDOMNode(stub);
const node = ReactDOM.render(stub, container);
expect(() => ReactTestUtils.Simulate.change(node)).not.toThrow();
});

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

@@ -35,6 +35,7 @@ const {
resetModules,
itRenders,
itClientRenders,
itThrowsWhenRendering,
renderIntoDom,
serverRender,
} = ReactDOMServerIntegrationUtils(initModules);
@@ -327,6 +328,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 => {
@@ -348,9 +424,13 @@ describe('ReactDOMServerIntegration', () => {
ControlledSelect;
beforeEach(() => {
ControlledInput = class extends React.Component {
static defaultProps = {
type: 'text',
initialValue: 'Hello',
};
constructor() {
super();
this.state = {value: 'Hello'};
super(...arguments);
this.state = {value: this.props.initialValue};
}
handleChange(event) {
if (this.props.onChange) {
@@ -361,6 +441,7 @@ describe('ReactDOMServerIntegration', () => {
render() {
return (
<input
type={this.props.type}
value={this.state.value}
onChange={this.handleChange.bind(this)}
/>
@@ -551,6 +632,27 @@ describe('ReactDOMServerIntegration', () => {
expect(changeCount).toBe(0);
});
it('should not blow away user-interaction on successful reconnect to an uncontrolled range input', () =>
testUserInteractionBeforeClientRender(
<input type="text" defaultValue="0.5" />,
'0.5',
'1',
));
it('should not blow away user-interaction on successful reconnect to a controlled range input', async () => {
let changeCount = 0;
await testUserInteractionBeforeClientRender(
<ControlledInput
type="range"
initialValue="0.25"
onChange={() => changeCount++}
/>,
'0.25',
'1',
);
expect(changeCount).toBe(0);
});
it('should not blow away user-entered text on successful reconnect to an uncontrolled checkbox', () =>
testUserInteractionBeforeClientRender(
<input type="checkbox" defaultChecked={true} />,

View File

@@ -42,7 +42,7 @@ describe('ReactDOMServerIntegration', () => {
resetModules();
});
describe('context', function() {
describe('legacy context', function() {
let PurpleContext, RedContext;
beforeEach(() => {
class Parent extends React.Component {

View File

@@ -0,0 +1,253 @@
/**
* Copyright (c) 2013-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @emails react-core
*/
'use strict';
const ReactDOMServerIntegrationUtils = require('./utils/ReactDOMServerIntegrationTestUtils');
let React;
let ReactDOM;
let ReactDOMServer;
function initModules() {
// Reset warning cache.
jest.resetModuleRegistry();
React = require('react');
ReactDOM = require('react-dom');
ReactDOMServer = require('react-dom/server');
// Make them available to the helpers.
return {
ReactDOM,
ReactDOMServer,
};
}
const {resetModules, itRenders} = ReactDOMServerIntegrationUtils(initModules);
describe('ReactDOMServerIntegration', () => {
beforeEach(() => {
resetModules();
});
describe('context', function() {
let PurpleContext, RedContext, Consumer;
beforeEach(() => {
let Context = React.createContext('none');
class Parent extends React.Component {
render() {
return (
<Context.Provider value={this.props.text}>
{this.props.children}
</Context.Provider>
);
}
}
Consumer = Context.Consumer;
PurpleContext = props => <Parent text="purple">{props.children}</Parent>;
RedContext = props => <Parent text="red">{props.children}</Parent>;
});
itRenders('class child with context', async render => {
class ClassChildWithContext extends React.Component {
render() {
return (
<div>
<Consumer>{text => text}</Consumer>
</div>
);
}
}
const e = await render(
<PurpleContext>
<ClassChildWithContext />
</PurpleContext>,
);
expect(e.textContent).toBe('purple');
});
itRenders('stateless child with context', async render => {
function StatelessChildWithContext(props) {
return <Consumer>{text => text}</Consumer>;
}
const e = await render(
<PurpleContext>
<StatelessChildWithContext />
</PurpleContext>,
);
expect(e.textContent).toBe('purple');
});
itRenders('class child with default context', async render => {
class ClassChildWithWrongContext extends React.Component {
render() {
return (
<div id="classWrongChild">
<Consumer>{text => text}</Consumer>
</div>
);
}
}
const e = await render(<ClassChildWithWrongContext />);
expect(e.textContent).toBe('none');
});
itRenders('stateless child with wrong context', async render => {
function StatelessChildWithWrongContext(props) {
return (
<div id="statelessWrongChild">
<Consumer>{text => text}</Consumer>
</div>
);
}
const e = await render(<StatelessChildWithWrongContext />);
expect(e.textContent).toBe('none');
});
itRenders('with context passed through to a grandchild', async render => {
function Grandchild(props) {
return (
<div>
<Consumer>{text => text}</Consumer>
</div>
);
}
const Child = props => <Grandchild />;
const e = await render(
<PurpleContext>
<Child />
</PurpleContext>,
);
expect(e.textContent).toBe('purple');
});
itRenders('a child context overriding a parent context', async render => {
const Grandchild = props => {
return (
<div>
<Consumer>{text => text}</Consumer>
</div>
);
};
const e = await render(
<PurpleContext>
<RedContext>
<Grandchild />
</RedContext>
</PurpleContext>,
);
expect(e.textContent).toBe('red');
});
itRenders('multiple contexts', async render => {
const Theme = React.createContext('dark');
const Language = React.createContext('french');
class Parent extends React.Component {
render() {
return (
<Theme.Provider value="light">
<Child />
</Theme.Provider>
);
}
}
function Child() {
return (
<Language.Provider value="english">
<Grandchild />
</Language.Provider>
);
}
const Grandchild = props => {
return (
<div>
<Theme.Consumer>
{theme => <div id="theme">{theme}</div>}
</Theme.Consumer>
<Language.Consumer>
{language => <div id="language">{language}</div>}
</Language.Consumer>
</div>
);
};
const e = await render(<Parent />);
expect(e.querySelector('#theme').textContent).toBe('light');
expect(e.querySelector('#language').textContent).toBe('english');
});
itRenders('nested context unwinding', async render => {
const Theme = React.createContext('dark');
const Language = React.createContext('french');
const App = () => (
<div>
<Theme.Provider value="light">
<Language.Provider value="english">
<Theme.Provider value="dark">
<Theme.Consumer>
{theme => <div id="theme1">{theme}</div>}
</Theme.Consumer>
</Theme.Provider>
<Theme.Consumer>
{theme => <div id="theme2">{theme}</div>}
</Theme.Consumer>
<Language.Provider value="sanskrit">
<Theme.Provider value="blue">
<Theme.Provider value="red">
<Language.Consumer>
{() => (
<Language.Provider value="chinese">
<Language.Provider value="hungarian" />
<Language.Consumer>
{language => <div id="language1">{language}</div>}
</Language.Consumer>
</Language.Provider>
)}
</Language.Consumer>
</Theme.Provider>
<Language.Consumer>
{language => (
<React.Fragment>
<Theme.Consumer>
{theme => <div id="theme3">{theme}</div>}
</Theme.Consumer>
<div id="language2">{language}</div>
</React.Fragment>
)}
</Language.Consumer>
</Theme.Provider>
</Language.Provider>
</Language.Provider>
</Theme.Provider>
<Language.Consumer>
{language => <div id="language3">{language}</div>}
</Language.Consumer>
</div>
);
let e = await render(<App />);
expect(e.querySelector('#theme1').textContent).toBe('dark');
expect(e.querySelector('#theme2').textContent).toBe('light');
expect(e.querySelector('#theme3').textContent).toBe('blue');
expect(e.querySelector('#language1').textContent).toBe('chinese');
expect(e.querySelector('#language2').textContent).toBe('sanskrit');
expect(e.querySelector('#language3').textContent).toBe('french');
});
});
});

View File

@@ -69,9 +69,9 @@ describe('ReactDOMServerLifecycles', () => {
});
describe('react-lifecycles-compat', () => {
const polyfill = require('react-lifecycles-compat');
const {polyfill} = require('react-lifecycles-compat');
it('should not warn about deprecated cWM/cWRP for polyfilled components', () => {
it('should not warn for components with polyfilled getDerivedStateFromProps', () => {
class PolyfilledComponent extends React.Component {
state = {};
static getDerivedStateFromProps() {
@@ -84,7 +84,35 @@ describe('ReactDOMServerLifecycles', () => {
polyfill(PolyfilledComponent);
ReactDOMServer.renderToString(<PolyfilledComponent />);
const container = document.createElement('div');
ReactDOMServer.renderToString(
<React.StrictMode>
<PolyfilledComponent />
</React.StrictMode>,
container,
);
});
it('should not warn for components with polyfilled getSnapshotBeforeUpdate', () => {
class PolyfilledComponent extends React.Component {
getSnapshotBeforeUpdate() {
return null;
}
componentDidUpdate() {}
render() {
return null;
}
}
polyfill(PolyfilledComponent);
const container = document.createElement('div');
ReactDOMServer.renderToString(
<React.StrictMode>
<PolyfilledComponent />
</React.StrictMode>,
container,
);
});
});
});

View File

@@ -39,7 +39,7 @@ describe('ReactDOMTextComponent', () => {
</div>,
el,
);
let nodes = filterOutComments(ReactDOM.findDOMNode(inst).childNodes);
let nodes = filterOutComments(inst.childNodes);
let foo = nodes[1];
let bar = nodes[2];
@@ -56,7 +56,7 @@ describe('ReactDOMTextComponent', () => {
);
// After the update, the text nodes should have stayed in place (as opposed
// to getting unmounted and remounted)
nodes = filterOutComments(ReactDOM.findDOMNode(inst).childNodes);
nodes = filterOutComments(inst.childNodes);
expect(nodes[1]).toBe(foo);
expect(nodes[2]).toBe(bar);
expect(foo.data).toBe('baz');
@@ -74,8 +74,7 @@ describe('ReactDOMTextComponent', () => {
el,
);
let container = ReactDOM.findDOMNode(inst);
let childNodes = filterOutComments(container.childNodes);
let childNodes = filterOutComments(inst.childNodes);
let childDiv = childNodes[1];
inst = ReactDOM.render(
@@ -86,8 +85,7 @@ describe('ReactDOMTextComponent', () => {
</div>,
el,
);
container = ReactDOM.findDOMNode(inst);
childNodes = filterOutComments(container.childNodes);
childNodes = filterOutComments(inst.childNodes);
expect(childNodes.length).toBe(1);
expect(childNodes[0]).toBe(childDiv);
@@ -99,8 +97,7 @@ describe('ReactDOMTextComponent', () => {
</div>,
el,
);
container = ReactDOM.findDOMNode(inst);
childNodes = filterOutComments(container.childNodes);
childNodes = filterOutComments(inst.childNodes);
expect(childNodes.length).toBe(3);
expect(childNodes[0].data).toBe('foo');
expect(childNodes[1]).toBe(childDiv);
@@ -125,8 +122,7 @@ describe('ReactDOMTextComponent', () => {
el,
);
let container = ReactDOM.findDOMNode(inst);
container.normalize();
inst.normalize();
inst = ReactDOM.render(
<div>
@@ -138,8 +134,7 @@ describe('ReactDOMTextComponent', () => {
</div>,
el,
);
container = ReactDOM.findDOMNode(inst);
expect(container.textContent).toBe('barbazquxfoo');
expect(inst.textContent).toBe('barbazquxfoo');
});
xit('can reconcile text merged by Node.normalize()', () => {
@@ -153,8 +148,7 @@ describe('ReactDOMTextComponent', () => {
el,
);
let container = ReactDOM.findDOMNode(inst);
container.normalize();
inst.normalize();
inst = ReactDOM.render(
<div>
@@ -164,8 +158,7 @@ describe('ReactDOMTextComponent', () => {
</div>,
el,
);
container = ReactDOM.findDOMNode(inst);
expect(container.textContent).toBe('barbazqux');
expect(inst.textContent).toBe('barbazqux');
});
it('can reconcile text from pre-rendered markup', () => {
@@ -207,15 +200,14 @@ describe('ReactDOMTextComponent', () => {
el,
);
let container = ReactDOM.findDOMNode(inst);
let childNodes = filterOutComments(ReactDOM.findDOMNode(inst).childNodes);
let childNodes = filterOutComments(inst.childNodes);
let textNode = childNodes[1];
textNode.textContent = 'foo';
container.insertBefore(
inst.insertBefore(
document.createTextNode('bar'),
childNodes[1].nextSibling,
);
container.insertBefore(
inst.insertBefore(
document.createTextNode('baz'),
childNodes[1].nextSibling,
);
@@ -227,8 +219,7 @@ describe('ReactDOMTextComponent', () => {
</div>,
el,
);
container = ReactDOM.findDOMNode(inst);
expect(container.textContent).toBe('barbazqux');
expect(inst.textContent).toBe('barbazqux');
});
xit('can reconcile text arbitrarily split into multiple nodes on some substitutions only', () => {
@@ -246,21 +237,20 @@ describe('ReactDOMTextComponent', () => {
el,
);
let container = ReactDOM.findDOMNode(inst);
let childNodes = filterOutComments(ReactDOM.findDOMNode(inst).childNodes);
let childNodes = filterOutComments(inst.childNodes);
let textNode = childNodes[3];
textNode.textContent = 'foo';
container.insertBefore(
inst.insertBefore(
document.createTextNode('bar'),
childNodes[3].nextSibling,
);
container.insertBefore(
inst.insertBefore(
document.createTextNode('baz'),
childNodes[3].nextSibling,
);
let secondTextNode = childNodes[5];
secondTextNode.textContent = 'bar';
container.insertBefore(
inst.insertBefore(
document.createTextNode('foo'),
childNodes[5].nextSibling,
);
@@ -277,8 +267,7 @@ describe('ReactDOMTextComponent', () => {
</div>,
el,
);
container = ReactDOM.findDOMNode(inst);
expect(container.textContent).toBe('bazbarbazquxbarbazbar');
expect(inst.textContent).toBe('bazbarbazquxbarbazbar');
});
xit('can unmount normalized text nodes', () => {

View File

@@ -9,7 +9,7 @@
'use strict';
const emptyFunction = require('fbjs/lib/emptyFunction');
function emptyFunction() {}
describe('ReactDOMTextarea', () => {
let React;
@@ -218,7 +218,7 @@ describe('ReactDOMTextarea', () => {
const node = container.firstChild;
let nodeValue = 'a';
const nodeValueSetter = jest.genMockFn();
const nodeValueSetter = jest.fn();
Object.defineProperty(node, 'value', {
get: function() {
return nodeValue;
@@ -229,10 +229,10 @@ describe('ReactDOMTextarea', () => {
});
ReactDOM.render(<textarea value="a" onChange={emptyFunction} />, container);
expect(nodeValueSetter.mock.calls.length).toBe(0);
expect(nodeValueSetter).toHaveBeenCalledTimes(0);
ReactDOM.render(<textarea value="b" onChange={emptyFunction} />, container);
expect(nodeValueSetter.mock.calls.length).toBe(1);
expect(nodeValueSetter).toHaveBeenCalledTimes(1);
});
it('should properly control a value of number `0`', () => {

View File

@@ -24,7 +24,7 @@ describe('ReactEmptyComponent', () => {
ReactDOM = require('react-dom');
ReactTestUtils = require('react-dom/test-utils');
log = jasmine.createSpy();
log = jest.fn();
TogglingComponent = class extends React.Component {
state = {component: this.props.firstComponent};
@@ -91,11 +91,17 @@ describe('ReactEmptyComponent', () => {
ReactTestUtils.renderIntoDocument(instance1);
ReactTestUtils.renderIntoDocument(instance2);
expect(log.calls.count()).toBe(4);
expect(log.calls.argsFor(0)[0]).toBe(null);
expect(log.calls.argsFor(1)[0].tagName).toBe('DIV');
expect(log.calls.argsFor(2)[0].tagName).toBe('DIV');
expect(log.calls.argsFor(3)[0]).toBe(null);
expect(log).toHaveBeenCalledTimes(4);
expect(log).toHaveBeenNthCalledWith(1, null);
expect(log).toHaveBeenNthCalledWith(
2,
expect.objectContaining({tagName: 'DIV'}),
);
expect(log).toHaveBeenNthCalledWith(
3,
expect.objectContaining({tagName: 'DIV'}),
);
expect(log).toHaveBeenNthCalledWith(4, null);
});
it('should be able to switch in a list of children', () => {
@@ -111,13 +117,22 @@ describe('ReactEmptyComponent', () => {
</div>,
);
expect(log.calls.count()).toBe(6);
expect(log.calls.argsFor(0)[0]).toBe(null);
expect(log.calls.argsFor(1)[0]).toBe(null);
expect(log.calls.argsFor(2)[0]).toBe(null);
expect(log.calls.argsFor(3)[0].tagName).toBe('DIV');
expect(log.calls.argsFor(4)[0].tagName).toBe('DIV');
expect(log.calls.argsFor(5)[0].tagName).toBe('DIV');
expect(log).toHaveBeenCalledTimes(6);
expect(log).toHaveBeenNthCalledWith(1, null);
expect(log).toHaveBeenNthCalledWith(2, null);
expect(log).toHaveBeenNthCalledWith(3, null);
expect(log).toHaveBeenNthCalledWith(
4,
expect.objectContaining({tagName: 'DIV'}),
);
expect(log).toHaveBeenNthCalledWith(
5,
expect.objectContaining({tagName: 'DIV'}),
);
expect(log).toHaveBeenNthCalledWith(
6,
expect.objectContaining({tagName: 'DIV'}),
);
});
it('should distinguish between a script placeholder and an actual script tag', () => {
@@ -135,11 +150,17 @@ describe('ReactEmptyComponent', () => {
ReactTestUtils.renderIntoDocument(instance2);
}).not.toThrow();
expect(log.calls.count()).toBe(4);
expect(log.calls.argsFor(0)[0]).toBe(null);
expect(log.calls.argsFor(1)[0].tagName).toBe('SCRIPT');
expect(log.calls.argsFor(2)[0].tagName).toBe('SCRIPT');
expect(log.calls.argsFor(3)[0]).toBe(null);
expect(log).toHaveBeenCalledTimes(4);
expect(log).toHaveBeenNthCalledWith(1, null);
expect(log).toHaveBeenNthCalledWith(
2,
expect.objectContaining({tagName: 'SCRIPT'}),
);
expect(log).toHaveBeenNthCalledWith(
3,
expect.objectContaining({tagName: 'SCRIPT'}),
);
expect(log).toHaveBeenNthCalledWith(4, null);
});
it(
@@ -172,11 +193,17 @@ describe('ReactEmptyComponent', () => {
ReactTestUtils.renderIntoDocument(instance2);
}).not.toThrow();
expect(log.calls.count()).toBe(4);
expect(log.calls.argsFor(0)[0].tagName).toBe('DIV');
expect(log.calls.argsFor(1)[0]).toBe(null);
expect(log.calls.argsFor(2)[0]).toBe(null);
expect(log.calls.argsFor(3)[0].tagName).toBe('DIV');
expect(log).toHaveBeenCalledTimes(4);
expect(log).toHaveBeenNthCalledWith(
1,
expect.objectContaining({tagName: 'DIV'}),
);
expect(log).toHaveBeenNthCalledWith(2, null);
expect(log).toHaveBeenNthCalledWith(3, null);
expect(log).toHaveBeenNthCalledWith(
4,
expect.objectContaining({tagName: 'DIV'}),
);
},
);

View File

@@ -11,7 +11,6 @@
let React;
let ReactDOM;
let ReactTestUtils;
describe('ReactEventIndependence', () => {
beforeEach(() => {
@@ -19,49 +18,66 @@ describe('ReactEventIndependence', () => {
React = require('react');
ReactDOM = require('react-dom');
ReactTestUtils = require('react-dom/test-utils');
});
it('does not crash with other react inside', () => {
let clicks = 0;
const div = ReactTestUtils.renderIntoDocument(
<div
onClick={() => clicks++}
dangerouslySetInnerHTML={{
__html: '<button data-reactid=".z">click me</div>',
}}
/>,
);
ReactTestUtils.SimulateNative.click(div.firstChild);
expect(clicks).toBe(1);
const container = document.createElement('div');
document.body.appendChild(container);
try {
const div = ReactDOM.render(
<div
onClick={() => clicks++}
dangerouslySetInnerHTML={{
__html: '<button data-reactid=".z">click me</div>',
}}
/>,
container,
);
div.firstChild.click();
expect(clicks).toBe(1);
} finally {
document.body.removeChild(container);
}
});
it('does not crash with other react outside', () => {
let clicks = 0;
const outer = document.createElement('div');
outer.setAttribute('data-reactid', '.z');
const inner = ReactDOM.render(
<button onClick={() => clicks++}>click me</button>,
outer,
);
ReactTestUtils.SimulateNative.click(inner);
expect(clicks).toBe(1);
document.body.appendChild(outer);
try {
outer.setAttribute('data-reactid', '.z');
const inner = ReactDOM.render(
<button onClick={() => clicks++}>click me</button>,
outer,
);
inner.click();
expect(clicks).toBe(1);
} finally {
document.body.removeChild(outer);
}
});
it('does not when event fired on unmounted tree', () => {
let clicks = 0;
const container = document.createElement('div');
const button = ReactDOM.render(
<button onClick={() => clicks++}>click me</button>,
container,
);
document.body.appendChild(container);
try {
const button = ReactDOM.render(
<button onClick={() => clicks++}>click me</button>,
container,
);
// Now we unmount the component, as if caused by a non-React event handler
// for the same click we're about to simulate, like closing a layer:
ReactDOM.unmountComponentAtNode(container);
ReactTestUtils.SimulateNative.click(button);
// Now we unmount the component, as if caused by a non-React event handler
// for the same click we're about to simulate, like closing a layer:
ReactDOM.unmountComponentAtNode(container);
button.click();
// Since the tree is unmounted, we don't dispatch the click event.
expect(clicks).toBe(0);
// Since the tree is unmounted, we don't dispatch the click event.
expect(clicks).toBe(0);
} finally {
document.body.removeChild(container);
}
});
});

View File

@@ -78,7 +78,7 @@ describe('ReactIdentity', () => {
const instance = ReactDOM.render(<Wrapper />, container);
const span = instance.refs.span;
expect(ReactDOM.findDOMNode(span)).not.toBe(null);
expect(span).not.toBe(null);
}
it('should allow any character as a key, in a detached parent', () => {

View File

@@ -91,25 +91,25 @@ describe('ReactMount', () => {
}
}
expect(mockMount.mock.calls.length).toBe(0);
expect(mockUnmount.mock.calls.length).toBe(0);
expect(mockMount).toHaveBeenCalledTimes(0);
expect(mockUnmount).toHaveBeenCalledTimes(0);
ReactDOM.render(<Component text="orange" key="A" />, container);
expect(container.firstChild.innerHTML).toBe('orange');
expect(mockMount.mock.calls.length).toBe(1);
expect(mockUnmount.mock.calls.length).toBe(0);
expect(mockMount).toHaveBeenCalledTimes(1);
expect(mockUnmount).toHaveBeenCalledTimes(0);
// If we change the key, the component is unmounted and remounted
ReactDOM.render(<Component text="green" key="B" />, container);
expect(container.firstChild.innerHTML).toBe('green');
expect(mockMount.mock.calls.length).toBe(2);
expect(mockUnmount.mock.calls.length).toBe(1);
expect(mockMount).toHaveBeenCalledTimes(2);
expect(mockUnmount).toHaveBeenCalledTimes(1);
// But if we don't change the key, the component instance is reused
ReactDOM.render(<Component text="blue" key="B" />, container);
expect(container.firstChild.innerHTML).toBe('blue');
expect(mockMount.mock.calls.length).toBe(2);
expect(mockUnmount.mock.calls.length).toBe(1);
expect(mockMount).toHaveBeenCalledTimes(2);
expect(mockUnmount).toHaveBeenCalledTimes(1);
});
it('should reuse markup if rendering to the same target twice', () => {

View File

@@ -36,9 +36,9 @@ describe('ReactMultiChild', () => {
}
}
expect(mockMount.mock.calls.length).toBe(0);
expect(mockUpdate.mock.calls.length).toBe(0);
expect(mockUnmount.mock.calls.length).toBe(0);
expect(mockMount).toHaveBeenCalledTimes(0);
expect(mockUpdate).toHaveBeenCalledTimes(0);
expect(mockUnmount).toHaveBeenCalledTimes(0);
ReactDOM.render(
<div>
@@ -47,9 +47,9 @@ describe('ReactMultiChild', () => {
container,
);
expect(mockMount.mock.calls.length).toBe(1);
expect(mockUpdate.mock.calls.length).toBe(0);
expect(mockUnmount.mock.calls.length).toBe(0);
expect(mockMount).toHaveBeenCalledTimes(1);
expect(mockUpdate).toHaveBeenCalledTimes(0);
expect(mockUnmount).toHaveBeenCalledTimes(0);
ReactDOM.render(
<div>
@@ -58,9 +58,9 @@ describe('ReactMultiChild', () => {
container,
);
expect(mockMount.mock.calls.length).toBe(1);
expect(mockUpdate.mock.calls.length).toBe(1);
expect(mockUnmount.mock.calls.length).toBe(0);
expect(mockMount).toHaveBeenCalledTimes(1);
expect(mockUpdate).toHaveBeenCalledTimes(1);
expect(mockUnmount).toHaveBeenCalledTimes(0);
});
it('should replace children with different constructors', () => {
@@ -77,8 +77,8 @@ describe('ReactMultiChild', () => {
}
}
expect(mockMount.mock.calls.length).toBe(0);
expect(mockUnmount.mock.calls.length).toBe(0);
expect(mockMount).toHaveBeenCalledTimes(0);
expect(mockUnmount).toHaveBeenCalledTimes(0);
ReactDOM.render(
<div>
@@ -87,8 +87,8 @@ describe('ReactMultiChild', () => {
container,
);
expect(mockMount.mock.calls.length).toBe(1);
expect(mockUnmount.mock.calls.length).toBe(0);
expect(mockMount).toHaveBeenCalledTimes(1);
expect(mockUnmount).toHaveBeenCalledTimes(0);
ReactDOM.render(
<div>
@@ -97,8 +97,8 @@ describe('ReactMultiChild', () => {
container,
);
expect(mockMount.mock.calls.length).toBe(1);
expect(mockUnmount.mock.calls.length).toBe(1);
expect(mockMount).toHaveBeenCalledTimes(1);
expect(mockUnmount).toHaveBeenCalledTimes(1);
});
it('should NOT replace children with different owners', () => {
@@ -121,13 +121,13 @@ describe('ReactMultiChild', () => {
}
}
expect(mockMount.mock.calls.length).toBe(0);
expect(mockUnmount.mock.calls.length).toBe(0);
expect(mockMount).toHaveBeenCalledTimes(0);
expect(mockUnmount).toHaveBeenCalledTimes(0);
ReactDOM.render(<WrapperComponent />, container);
expect(mockMount.mock.calls.length).toBe(1);
expect(mockUnmount.mock.calls.length).toBe(0);
expect(mockMount).toHaveBeenCalledTimes(1);
expect(mockUnmount).toHaveBeenCalledTimes(0);
ReactDOM.render(
<WrapperComponent>
@@ -136,8 +136,8 @@ describe('ReactMultiChild', () => {
container,
);
expect(mockMount.mock.calls.length).toBe(1);
expect(mockUnmount.mock.calls.length).toBe(0);
expect(mockMount).toHaveBeenCalledTimes(1);
expect(mockUnmount).toHaveBeenCalledTimes(0);
});
it('should replace children with different keys', () => {
@@ -154,8 +154,8 @@ describe('ReactMultiChild', () => {
}
}
expect(mockMount.mock.calls.length).toBe(0);
expect(mockUnmount.mock.calls.length).toBe(0);
expect(mockMount).toHaveBeenCalledTimes(0);
expect(mockUnmount).toHaveBeenCalledTimes(0);
ReactDOM.render(
<div>
@@ -164,8 +164,8 @@ describe('ReactMultiChild', () => {
container,
);
expect(mockMount.mock.calls.length).toBe(1);
expect(mockUnmount.mock.calls.length).toBe(0);
expect(mockMount).toHaveBeenCalledTimes(1);
expect(mockUnmount).toHaveBeenCalledTimes(0);
ReactDOM.render(
<div>
@@ -174,8 +174,8 @@ describe('ReactMultiChild', () => {
container,
);
expect(mockMount.mock.calls.length).toBe(2);
expect(mockUnmount.mock.calls.length).toBe(1);
expect(mockMount).toHaveBeenCalledTimes(2);
expect(mockUnmount).toHaveBeenCalledTimes(1);
});
it('should warn for duplicated array keys with component stack info', () => {

View File

@@ -11,7 +11,6 @@
'use strict';
let React;
let ReactCallReturn;
let ReactDOMServer;
let PropTypes;
@@ -23,7 +22,6 @@ describe('ReactDOMServer', () => {
beforeEach(() => {
jest.resetModules();
React = require('react');
ReactCallReturn = require('react-call-return');
PropTypes = require('prop-types');
ReactDOMServer = require('react-dom/server');
});
@@ -623,25 +621,6 @@ describe('ReactDOMServer', () => {
);
});
it('should throw rendering call/return on the server', () => {
expect(() => {
ReactDOMServer.renderToString(
<div>{ReactCallReturn.unstable_createReturn(42)}</div>,
);
}).toThrow(
'The experimental Call and Return types are not currently supported by the server renderer.',
);
expect(() => {
ReactDOMServer.renderToString(
<div>
{ReactCallReturn.unstable_createCall(null, function() {}, {})}
</div>,
);
}).toThrow(
'The experimental Call and Return types are not currently supported by the server renderer.',
);
});
it('should warn when server rendering a class with a render method that does not extend React.Component', () => {
class ClassWithRenderNotExtended {
render() {

View File

@@ -12,7 +12,6 @@
let React;
let ReactDOM;
let ReactDOMServer;
let ReactTestUtils;
// These tests rely both on ReactDOMServer and ReactDOM.
// If a test only needs ReactDOMServer, put it in ReactServerRendering-test instead.
@@ -21,7 +20,6 @@ describe('ReactDOMServerHydration', () => {
jest.resetModules();
React = require('react');
ReactDOM = require('react-dom');
ReactTestUtils = require('react-dom/test-utils');
ReactDOMServer = require('react-dom/server');
});
@@ -48,67 +46,73 @@ describe('ReactDOMServerHydration', () => {
}
const element = document.createElement('div');
ReactDOM.render(<TestComponent />, element);
document.body.appendChild(element);
try {
ReactDOM.render(<TestComponent />, element);
let lastMarkup = element.innerHTML;
let lastMarkup = element.innerHTML;
// Exercise the update path. Markup should not change,
// but some lifecycle methods should be run again.
ReactDOM.render(<TestComponent name="x" />, element);
expect(mountCount).toEqual(1);
// Exercise the update path. Markup should not change,
// but some lifecycle methods should be run again.
ReactDOM.render(<TestComponent name="x" />, element);
expect(mountCount).toEqual(1);
// Unmount and remount. We should get another mount event and
// we should get different markup, as the IDs are unique each time.
ReactDOM.unmountComponentAtNode(element);
expect(element.innerHTML).toEqual('');
ReactDOM.render(<TestComponent name="x" />, element);
expect(mountCount).toEqual(2);
expect(element.innerHTML).not.toEqual(lastMarkup);
// Unmount and remount. We should get another mount event and
// we should get different markup, as the IDs are unique each time.
ReactDOM.unmountComponentAtNode(element);
expect(element.innerHTML).toEqual('');
ReactDOM.render(<TestComponent name="x" />, element);
expect(mountCount).toEqual(2);
expect(element.innerHTML).not.toEqual(lastMarkup);
// Now kill the node and render it on top of server-rendered markup, as if
// we used server rendering. We should mount again, but the markup should
// be unchanged. We will append a sentinel at the end of innerHTML to be
// sure that innerHTML was not changed.
ReactDOM.unmountComponentAtNode(element);
expect(element.innerHTML).toEqual('');
// Now kill the node and render it on top of server-rendered markup, as if
// we used server rendering. We should mount again, but the markup should
// be unchanged. We will append a sentinel at the end of innerHTML to be
// sure that innerHTML was not changed.
ReactDOM.unmountComponentAtNode(element);
expect(element.innerHTML).toEqual('');
lastMarkup = ReactDOMServer.renderToString(<TestComponent name="x" />);
element.innerHTML = lastMarkup;
lastMarkup = ReactDOMServer.renderToString(<TestComponent name="x" />);
element.innerHTML = lastMarkup;
let instance;
let instance;
expect(() => {
instance = ReactDOM.render(<TestComponent name="x" />, element);
}).toLowPriorityWarnDev(
'render(): Calling ReactDOM.render() to hydrate server-rendered markup ' +
'will stop working in React v17. Replace the ReactDOM.render() call ' +
'with ReactDOM.hydrate() if you want React to attach to the server HTML.',
);
expect(mountCount).toEqual(3);
expect(element.innerHTML).toBe(lastMarkup);
expect(() => {
instance = ReactDOM.render(<TestComponent name="x" />, element);
}).toLowPriorityWarnDev(
'render(): Calling ReactDOM.render() to hydrate server-rendered markup ' +
'will stop working in React v17. Replace the ReactDOM.render() call ' +
'with ReactDOM.hydrate() if you want React to attach to the server HTML.',
);
expect(mountCount).toEqual(3);
expect(element.innerHTML).toBe(lastMarkup);
// Ensure the events system works after mount into server markup
expect(numClicks).toEqual(0);
ReactTestUtils.Simulate.click(ReactDOM.findDOMNode(instance.refs.span));
expect(numClicks).toEqual(1);
// Ensure the events system works after mount into server markup
expect(numClicks).toEqual(0);
ReactDOM.unmountComponentAtNode(element);
expect(element.innerHTML).toEqual('');
instance.refs.span.click();
expect(numClicks).toEqual(1);
// Now simulate a situation where the app is not idempotent. React should
// warn but do the right thing.
element.innerHTML = lastMarkup;
expect(() => {
instance = ReactDOM.render(<TestComponent name="y" />, element);
}).toWarnDev('Text content did not match. Server: "x" Client: "y"');
expect(mountCount).toEqual(4);
expect(element.innerHTML.length > 0).toBe(true);
expect(element.innerHTML).not.toEqual(lastMarkup);
ReactDOM.unmountComponentAtNode(element);
expect(element.innerHTML).toEqual('');
// Ensure the events system works after markup mismatch.
expect(numClicks).toEqual(1);
ReactTestUtils.Simulate.click(ReactDOM.findDOMNode(instance.refs.span));
expect(numClicks).toEqual(2);
// Now simulate a situation where the app is not idempotent. React should
// warn but do the right thing.
element.innerHTML = lastMarkup;
expect(() => {
instance = ReactDOM.render(<TestComponent name="y" />, element);
}).toWarnDev('Text content did not match. Server: "x" Client: "y"');
expect(mountCount).toEqual(4);
expect(element.innerHTML.length > 0).toBe(true);
expect(element.innerHTML).not.toEqual(lastMarkup);
// Ensure the events system works after markup mismatch.
expect(numClicks).toEqual(1);
instance.refs.span.click();
expect(numClicks).toEqual(2);
} finally {
document.body.removeChild(element);
}
});
it('should have the correct mounting behavior (new hydrate API)', () => {
@@ -134,59 +138,64 @@ describe('ReactDOMServerHydration', () => {
}
const element = document.createElement('div');
ReactDOM.render(<TestComponent />, element);
document.body.appendChild(element);
try {
ReactDOM.render(<TestComponent />, element);
let lastMarkup = element.innerHTML;
let lastMarkup = element.innerHTML;
// Exercise the update path. Markup should not change,
// but some lifecycle methods should be run again.
ReactDOM.render(<TestComponent name="x" />, element);
expect(mountCount).toEqual(1);
// Exercise the update path. Markup should not change,
// but some lifecycle methods should be run again.
ReactDOM.render(<TestComponent name="x" />, element);
expect(mountCount).toEqual(1);
// Unmount and remount. We should get another mount event and
// we should get different markup, as the IDs are unique each time.
ReactDOM.unmountComponentAtNode(element);
expect(element.innerHTML).toEqual('');
ReactDOM.render(<TestComponent name="x" />, element);
expect(mountCount).toEqual(2);
expect(element.innerHTML).not.toEqual(lastMarkup);
// Unmount and remount. We should get another mount event and
// we should get different markup, as the IDs are unique each time.
ReactDOM.unmountComponentAtNode(element);
expect(element.innerHTML).toEqual('');
ReactDOM.render(<TestComponent name="x" />, element);
expect(mountCount).toEqual(2);
expect(element.innerHTML).not.toEqual(lastMarkup);
// Now kill the node and render it on top of server-rendered markup, as if
// we used server rendering. We should mount again, but the markup should
// be unchanged. We will append a sentinel at the end of innerHTML to be
// sure that innerHTML was not changed.
ReactDOM.unmountComponentAtNode(element);
expect(element.innerHTML).toEqual('');
// Now kill the node and render it on top of server-rendered markup, as if
// we used server rendering. We should mount again, but the markup should
// be unchanged. We will append a sentinel at the end of innerHTML to be
// sure that innerHTML was not changed.
ReactDOM.unmountComponentAtNode(element);
expect(element.innerHTML).toEqual('');
lastMarkup = ReactDOMServer.renderToString(<TestComponent name="x" />);
element.innerHTML = lastMarkup;
lastMarkup = ReactDOMServer.renderToString(<TestComponent name="x" />);
element.innerHTML = lastMarkup;
let instance = ReactDOM.hydrate(<TestComponent name="x" />, element);
expect(mountCount).toEqual(3);
expect(element.innerHTML).toBe(lastMarkup);
let instance = ReactDOM.hydrate(<TestComponent name="x" />, element);
expect(mountCount).toEqual(3);
expect(element.innerHTML).toBe(lastMarkup);
// Ensure the events system works after mount into server markup
expect(numClicks).toEqual(0);
ReactTestUtils.Simulate.click(ReactDOM.findDOMNode(instance.refs.span));
expect(numClicks).toEqual(1);
// Ensure the events system works after mount into server markup
expect(numClicks).toEqual(0);
instance.refs.span.click();
expect(numClicks).toEqual(1);
ReactDOM.unmountComponentAtNode(element);
expect(element.innerHTML).toEqual('');
ReactDOM.unmountComponentAtNode(element);
expect(element.innerHTML).toEqual('');
// Now simulate a situation where the app is not idempotent. React should
// warn but do the right thing.
element.innerHTML = lastMarkup;
expect(() => {
instance = ReactDOM.hydrate(<TestComponent name="y" />, element);
}).toWarnDev('Text content did not match. Server: "x" Client: "y"');
expect(mountCount).toEqual(4);
expect(element.innerHTML.length > 0).toBe(true);
expect(element.innerHTML).not.toEqual(lastMarkup);
// Now simulate a situation where the app is not idempotent. React should
// warn but do the right thing.
element.innerHTML = lastMarkup;
expect(() => {
instance = ReactDOM.hydrate(<TestComponent name="y" />, element);
}).toWarnDev('Text content did not match. Server: "x" Client: "y"');
expect(mountCount).toEqual(4);
expect(element.innerHTML.length > 0).toBe(true);
expect(element.innerHTML).not.toEqual(lastMarkup);
// Ensure the events system works after markup mismatch.
expect(numClicks).toEqual(1);
ReactTestUtils.Simulate.click(ReactDOM.findDOMNode(instance.refs.span));
expect(numClicks).toEqual(2);
// Ensure the events system works after markup mismatch.
expect(numClicks).toEqual(1);
instance.refs.span.click();
expect(numClicks).toEqual(2);
} finally {
document.body.removeChild(element);
}
});
// We have a polyfill for autoFocus on the client, but we intentionally don't

View File

@@ -280,17 +280,16 @@ describe('ReactTestUtils', () => {
};
spyOnDevAndProd(obj, 'handler').and.callThrough();
const container = document.createElement('div');
const instance = ReactDOM.render(
const node = ReactDOM.render(
<input type="text" onChange={obj.handler} />,
container,
);
const node = ReactDOM.findDOMNode(instance);
node.value = 'giraffe';
ReactTestUtils.Simulate.change(node);
expect(obj.handler).toHaveBeenCalledWith(
jasmine.objectContaining({target: node}),
expect.objectContaining({target: node}),
);
});
@@ -321,12 +320,12 @@ describe('ReactTestUtils', () => {
container,
);
const node = ReactDOM.findDOMNode(instance.refs.input);
const node = instance.refs.input;
node.value = 'zebra';
ReactTestUtils.Simulate.change(node);
expect(obj.handler).toHaveBeenCalledWith(
jasmine.objectContaining({target: node}),
expect.objectContaining({target: node}),
);
});
@@ -337,7 +336,7 @@ describe('ReactTestUtils', () => {
}
}
const handler = jasmine.createSpy('spy');
const handler = jest.fn().mockName('spy');
const shallowRenderer = createRenderer();
const result = shallowRenderer.render(
<SomeComponent handleClick={handler} />,
@@ -358,7 +357,7 @@ describe('ReactTestUtils', () => {
}
}
const handler = jasmine.createSpy('spy');
const handler = jest.fn().mockName('spy');
const container = document.createElement('div');
const instance = ReactDOM.render(
<SomeComponent handleClick={handler} />,
@@ -394,7 +393,7 @@ describe('ReactTestUtils', () => {
it('should set the type of the event', () => {
let event;
const stub = jest.genMockFn().mockImplementation(e => {
const stub = jest.fn().mockImplementation(e => {
e.persist();
event = e;
});
@@ -431,7 +430,7 @@ describe('ReactTestUtils', () => {
ReactTestUtils.Simulate.change(input);
expect(onChange).toHaveBeenCalledWith(
jasmine.objectContaining({target: input}),
expect.objectContaining({target: input}),
);
});
});

View File

@@ -35,6 +35,7 @@ Array [
"ended",
"error",
"focus",
"gotPointerCapture",
"input",
"invalid",
"keyDown",
@@ -44,6 +45,7 @@ Array [
"loadStart",
"loadedData",
"loadedMetadata",
"lostPointerCapture",
"mouseDown",
"mouseEnter",
"mouseLeave",
@@ -55,6 +57,14 @@ Array [
"pause",
"play",
"playing",
"pointerCancel",
"pointerDown",
"pointerEnter",
"pointerLeave",
"pointerMove",
"pointerOut",
"pointerOver",
"pointerUp",
"progress",
"rateChange",
"reset",

View File

@@ -10,6 +10,7 @@
'use strict';
let React = require('react');
let ReactDOM = require('react-dom');
let ReactTestUtils = require('react-dom/test-utils');
/**
@@ -81,24 +82,6 @@ class TestRefsComponent extends React.Component {
}
}
/**
* Render a TestRefsComponent and ensure that the main refs are wired up.
*/
const renderTestRefsComponent = function() {
const testRefsComponent = ReactTestUtils.renderIntoDocument(
<TestRefsComponent />,
);
expect(testRefsComponent instanceof TestRefsComponent).toBe(true);
const generalContainer = testRefsComponent.refs.myContainer;
expect(generalContainer instanceof GeneralContainerComponent).toBe(true);
const counter = testRefsComponent.refs.myCounter;
expect(counter instanceof ClickCounter).toBe(true);
return testRefsComponent;
};
const expectClickLogsLengthToBe = function(instance, length) {
const clickLogs = ReactTestUtils.scryRenderedDOMComponentsWithClass(
instance,
@@ -109,12 +92,40 @@ const expectClickLogsLengthToBe = function(instance, length) {
};
describe('reactiverefs', () => {
let container;
beforeEach(() => {
jest.resetModules();
React = require('react');
ReactDOM = require('react-dom');
ReactTestUtils = require('react-dom/test-utils');
});
afterEach(() => {
if (container) {
document.body.removeChild(container);
container = null;
}
});
/**
* Render a TestRefsComponent and ensure that the main refs are wired up.
*/
const renderTestRefsComponent = function() {
container = document.createElement('div');
document.body.appendChild(container);
const testRefsComponent = ReactDOM.render(<TestRefsComponent />, container);
expect(testRefsComponent instanceof TestRefsComponent).toBe(true);
const generalContainer = testRefsComponent.refs.myContainer;
expect(generalContainer instanceof GeneralContainerComponent).toBe(true);
const counter = testRefsComponent.refs.myCounter;
expect(counter instanceof ClickCounter).toBe(true);
return testRefsComponent;
};
/**
* Ensure that for every click log there is a corresponding ref (from the
* perspective of the injected ClickCounter component.
@@ -129,18 +140,18 @@ describe('reactiverefs', () => {
expectClickLogsLengthToBe(testRefsComponent, 1);
// After clicking the reset, there should still only be one click log ref.
ReactTestUtils.Simulate.click(testRefsComponent.refs.resetDiv);
testRefsComponent.refs.resetDiv.click();
expectClickLogsLengthToBe(testRefsComponent, 1);
// Begin incrementing clicks (and therefore refs).
ReactTestUtils.Simulate.click(clickIncrementer);
clickIncrementer.click();
expectClickLogsLengthToBe(testRefsComponent, 2);
ReactTestUtils.Simulate.click(clickIncrementer);
clickIncrementer.click();
expectClickLogsLengthToBe(testRefsComponent, 3);
// Now reset again
ReactTestUtils.Simulate.click(testRefsComponent.refs.resetDiv);
testRefsComponent.refs.resetDiv.click();
expectClickLogsLengthToBe(testRefsComponent, 1);
});
});
@@ -168,6 +179,7 @@ describe('ref swapping', () => {
beforeEach(() => {
jest.resetModules();
React = require('react');
ReactDOM = require('react-dom');
ReactTestUtils = require('react-dom/test-utils');
RefHopsAround = class extends React.Component {
@@ -293,7 +305,6 @@ describe('ref swapping', () => {
describe('root level refs', () => {
it('attaches and detaches root refs', () => {
const ReactDOM = require('react-dom');
let inst = null;
// host node
@@ -401,3 +412,46 @@ describe('creating element with ref in constructor', () => {
);
});
});
describe('strings refs across renderers', () => {
it('does not break', () => {
class Parent extends React.Component {
render() {
// This component owns both refs.
return (
<Indirection
child1={<div ref="child1" />}
child2={<div ref="child2" />}
/>
);
}
}
class Indirection extends React.Component {
componentDidUpdate() {
// One ref is being rendered later using another renderer copy.
jest.resetModules();
const AnotherCopyOfReactDOM = require('react-dom');
AnotherCopyOfReactDOM.render(this.props.child2, div2);
}
render() {
// The other one is being rendered directly.
return this.props.child1;
}
}
const div1 = document.createElement('div');
const div2 = document.createElement('div');
const inst = ReactDOM.render(<Parent />, div1);
// Only the first ref has rendered yet.
expect(inst.refs.child1.tagName).toBe('DIV');
expect(inst.refs.child1).toBe(div1.firstChild);
// Now both refs should be rendered.
ReactDOM.render(<Parent />, div1);
expect(inst.refs.child1.tagName).toBe('DIV');
expect(inst.refs.child1).toBe(div1.firstChild);
expect(inst.refs.child2.tagName).toBe('DIV');
expect(inst.refs.child2).toBe(div2.firstChild);
});
});

View File

@@ -53,9 +53,7 @@ module.exports = function(initModules) {
console.error.calls.count() !== 0
) {
console.log(
`We expected ${
count
} warning(s), but saw ${console.error.calls.count()} warning(s).`,
`We expected ${count} warning(s), but saw ${console.error.calls.count()} warning(s).`,
);
if (console.error.calls.count() > 0) {
console.log(`We saw these warnings:`);
@@ -65,7 +63,7 @@ module.exports = function(initModules) {
}
}
if (__DEV__) {
expect(console.error.calls.count()).toBe(count);
expect(console.error).toHaveBeenCalledTimes(count);
}
return result;
}
@@ -237,9 +235,7 @@ module.exports = function(initModules) {
testFn(clientCleanRender));
it(`renders ${desc} with client render on top of good server markup`, () =>
testFn(clientRenderOnServerString));
it(`renders ${
desc
} with client render on top of bad server markup`, async () => {
it(`renders ${desc} with client render on top of bad server markup`, async () => {
try {
await testFn(clientRenderOnBadMarkup);
} catch (x) {

View File

@@ -14,14 +14,14 @@ import type {
FiberRoot,
Batch as FiberRootBatch,
} from 'react-reconciler/src/ReactFiberRoot';
import type {Container} from './ReactDOMHostConfig';
import '../shared/checkReact';
import './ReactDOMClientInjection';
import ReactFiberReconciler from 'react-reconciler';
// TODO: direct imports like some-package/src/* are bad. Fix me.
import * as DOMRenderer from 'react-reconciler/inline.dom';
import * as ReactPortal from 'shared/ReactPortal';
import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment';
import {canUseDOM} from 'shared/ExecutionEnvironment';
import * as ReactGenericBatching from 'events/ReactGenericBatching';
import * as ReactControlledComponent from 'events/ReactControlledComponent';
import * as EventPluginHub from 'events/EventPluginHub';
@@ -29,58 +29,35 @@ import * as EventPluginRegistry from 'events/EventPluginRegistry';
import * as EventPropagators from 'events/EventPropagators';
import * as ReactInstanceMap from 'shared/ReactInstanceMap';
import ReactVersion from 'shared/ReactVersion';
import * as ReactDOMFrameScheduling from 'shared/ReactDOMFrameScheduling';
import {ReactCurrentOwner} from 'shared/ReactGlobalSharedState';
import getComponentName from 'shared/getComponentName';
import invariant from 'fbjs/lib/invariant';
import invariant from 'shared/invariant';
import lowPriorityWarning from 'shared/lowPriorityWarning';
import warning from 'fbjs/lib/warning';
import warning from 'shared/warning';
import * as ReactDOMComponentTree from './ReactDOMComponentTree';
import * as ReactDOMFiberComponent from './ReactDOMFiberComponent';
import * as ReactInputSelection from './ReactInputSelection';
import setTextContent from './setTextContent';
import validateDOMNesting from './validateDOMNesting';
import * as ReactBrowserEventEmitter from '../events/ReactBrowserEventEmitter';
import * as ReactDOMEventListener from '../events/ReactDOMEventListener';
import {getChildNamespace} from '../shared/DOMNamespaces';
import {
ELEMENT_NODE,
TEXT_NODE,
COMMENT_NODE,
DOCUMENT_NODE,
DOCUMENT_FRAGMENT_NODE,
} from '../shared/HTMLNodeType';
import {ROOT_ATTRIBUTE_NAME} from '../shared/DOMProperty';
const {
createElement,
createTextNode,
setInitialProperties,
diffProperties,
updateProperties,
diffHydratedProperties,
diffHydratedText,
warnForUnmatchedText,
warnForDeletedHydratableElement,
warnForDeletedHydratableText,
warnForInsertedHydratedElement,
warnForInsertedHydratedText,
} = ReactDOMFiberComponent;
const {updatedAncestorInfo} = validateDOMNesting;
const {precacheFiberNode, updateFiberProps} = ReactDOMComponentTree;
let SUPPRESS_HYDRATION_WARNING;
let topLevelUpdateWarnings;
let warnOnInvalidCallback;
let didWarnAboutUnstableCreatePortal = false;
if (__DEV__) {
SUPPRESS_HYDRATION_WARNING = 'suppressHydrationWarning';
if (
typeof Map !== 'function' ||
// $FlowIssue Flow incorrectly thinks Map has no prototype
Map.prototype == null ||
typeof Map.prototype.forEach !== 'function' ||
typeof Set !== 'function' ||
// $FlowIssue Flow incorrectly thinks Set has no prototype
Set.prototype == null ||
typeof Set.prototype.clear !== 'function' ||
typeof Set.prototype.forEach !== 'function'
@@ -157,26 +134,6 @@ type DOMContainer =
_reactRootContainer: ?Root,
});
type Container = Element | Document;
type Props = {
autoFocus?: boolean,
children?: mixed,
hidden?: boolean,
suppressHydrationWarning?: boolean,
};
type Instance = Element;
type TextInstance = Text;
type HostContextDev = {
namespace: string,
ancestorInfo: mixed,
};
type HostContextProd = string;
type HostContext = HostContextDev | HostContextProd;
let eventsEnabled: ?boolean = null;
let selectionInformation: ?mixed = null;
type Batch = FiberRootBatch & {
render(children: ReactNodeList): Work,
then(onComplete: () => mixed): void,
@@ -491,503 +448,6 @@ function shouldHydrateDueToLegacyHeuristic(container) {
);
}
function shouldAutoFocusHostComponent(type: string, props: Props): boolean {
switch (type) {
case 'button':
case 'input':
case 'select':
case 'textarea':
return !!props.autoFocus;
}
return false;
}
const DOMRenderer = ReactFiberReconciler({
getRootHostContext(rootContainerInstance: Container): HostContext {
let type;
let namespace;
const nodeType = rootContainerInstance.nodeType;
switch (nodeType) {
case DOCUMENT_NODE:
case DOCUMENT_FRAGMENT_NODE: {
type = nodeType === DOCUMENT_NODE ? '#document' : '#fragment';
let root = (rootContainerInstance: any).documentElement;
namespace = root ? root.namespaceURI : getChildNamespace(null, '');
break;
}
default: {
const container: any =
nodeType === COMMENT_NODE
? rootContainerInstance.parentNode
: rootContainerInstance;
const ownNamespace = container.namespaceURI || null;
type = container.tagName;
namespace = getChildNamespace(ownNamespace, type);
break;
}
}
if (__DEV__) {
const validatedTag = type.toLowerCase();
const ancestorInfo = updatedAncestorInfo(null, validatedTag, null);
return {namespace, ancestorInfo};
}
return namespace;
},
getChildHostContext(
parentHostContext: HostContext,
type: string,
): HostContext {
if (__DEV__) {
const parentHostContextDev = ((parentHostContext: any): HostContextDev);
const namespace = getChildNamespace(parentHostContextDev.namespace, type);
const ancestorInfo = updatedAncestorInfo(
parentHostContextDev.ancestorInfo,
type,
null,
);
return {namespace, ancestorInfo};
}
const parentNamespace = ((parentHostContext: any): HostContextProd);
return getChildNamespace(parentNamespace, type);
},
getPublicInstance(instance) {
return instance;
},
prepareForCommit(): void {
eventsEnabled = ReactBrowserEventEmitter.isEnabled();
selectionInformation = ReactInputSelection.getSelectionInformation();
ReactBrowserEventEmitter.setEnabled(false);
},
resetAfterCommit(): void {
ReactInputSelection.restoreSelection(selectionInformation);
selectionInformation = null;
ReactBrowserEventEmitter.setEnabled(eventsEnabled);
eventsEnabled = null;
},
createInstance(
type: string,
props: Props,
rootContainerInstance: Container,
hostContext: HostContext,
internalInstanceHandle: Object,
): Instance {
let parentNamespace: string;
if (__DEV__) {
// TODO: take namespace into account when validating.
const hostContextDev = ((hostContext: any): HostContextDev);
validateDOMNesting(type, null, hostContextDev.ancestorInfo);
if (
typeof props.children === 'string' ||
typeof props.children === 'number'
) {
const string = '' + props.children;
const ownAncestorInfo = updatedAncestorInfo(
hostContextDev.ancestorInfo,
type,
null,
);
validateDOMNesting(null, string, ownAncestorInfo);
}
parentNamespace = hostContextDev.namespace;
} else {
parentNamespace = ((hostContext: any): HostContextProd);
}
const domElement: Instance = createElement(
type,
props,
rootContainerInstance,
parentNamespace,
);
precacheFiberNode(internalInstanceHandle, domElement);
updateFiberProps(domElement, props);
return domElement;
},
appendInitialChild(
parentInstance: Instance,
child: Instance | TextInstance,
): void {
parentInstance.appendChild(child);
},
finalizeInitialChildren(
domElement: Instance,
type: string,
props: Props,
rootContainerInstance: Container,
): boolean {
setInitialProperties(domElement, type, props, rootContainerInstance);
return shouldAutoFocusHostComponent(type, props);
},
prepareUpdate(
domElement: Instance,
type: string,
oldProps: Props,
newProps: Props,
rootContainerInstance: Container,
hostContext: HostContext,
): null | Array<mixed> {
if (__DEV__) {
const hostContextDev = ((hostContext: any): HostContextDev);
if (
typeof newProps.children !== typeof oldProps.children &&
(typeof newProps.children === 'string' ||
typeof newProps.children === 'number')
) {
const string = '' + newProps.children;
const ownAncestorInfo = updatedAncestorInfo(
hostContextDev.ancestorInfo,
type,
null,
);
validateDOMNesting(null, string, ownAncestorInfo);
}
}
return diffProperties(
domElement,
type,
oldProps,
newProps,
rootContainerInstance,
);
},
shouldSetTextContent(type: string, props: Props): boolean {
return (
type === 'textarea' ||
typeof props.children === 'string' ||
typeof props.children === 'number' ||
(typeof props.dangerouslySetInnerHTML === 'object' &&
props.dangerouslySetInnerHTML !== null &&
typeof props.dangerouslySetInnerHTML.__html === 'string')
);
},
shouldDeprioritizeSubtree(type: string, props: Props): boolean {
return !!props.hidden;
},
createTextInstance(
text: string,
rootContainerInstance: Container,
hostContext: HostContext,
internalInstanceHandle: Object,
): TextInstance {
if (__DEV__) {
const hostContextDev = ((hostContext: any): HostContextDev);
validateDOMNesting(null, text, hostContextDev.ancestorInfo);
}
const textNode: TextInstance = createTextNode(text, rootContainerInstance);
precacheFiberNode(internalInstanceHandle, textNode);
return textNode;
},
now: ReactDOMFrameScheduling.now,
mutation: {
commitMount(
domElement: Instance,
type: string,
newProps: Props,
internalInstanceHandle: Object,
): void {
// Despite the naming that might imply otherwise, this method only
// fires if there is an `Update` effect scheduled during mounting.
// This happens if `finalizeInitialChildren` returns `true` (which it
// does to implement the `autoFocus` attribute on the client). But
// there are also other cases when this might happen (such as patching
// up text content during hydration mismatch). So we'll check this again.
if (shouldAutoFocusHostComponent(type, newProps)) {
((domElement: any):
| HTMLButtonElement
| HTMLInputElement
| HTMLSelectElement
| HTMLTextAreaElement).focus();
}
},
commitUpdate(
domElement: Instance,
updatePayload: Array<mixed>,
type: string,
oldProps: Props,
newProps: Props,
internalInstanceHandle: Object,
): void {
// Update the props handle so that we know which props are the ones with
// with current event handlers.
updateFiberProps(domElement, newProps);
// Apply the diff to the DOM node.
updateProperties(domElement, updatePayload, type, oldProps, newProps);
},
resetTextContent(domElement: Instance): void {
setTextContent(domElement, '');
},
commitTextUpdate(
textInstance: TextInstance,
oldText: string,
newText: string,
): void {
textInstance.nodeValue = newText;
},
appendChild(
parentInstance: Instance,
child: Instance | TextInstance,
): void {
parentInstance.appendChild(child);
},
appendChildToContainer(
container: Container,
child: Instance | TextInstance,
): void {
if (container.nodeType === COMMENT_NODE) {
(container.parentNode: any).insertBefore(child, container);
} else {
container.appendChild(child);
}
},
insertBefore(
parentInstance: Instance,
child: Instance | TextInstance,
beforeChild: Instance | TextInstance,
): void {
parentInstance.insertBefore(child, beforeChild);
},
insertInContainerBefore(
container: Container,
child: Instance | TextInstance,
beforeChild: Instance | TextInstance,
): void {
if (container.nodeType === COMMENT_NODE) {
(container.parentNode: any).insertBefore(child, beforeChild);
} else {
container.insertBefore(child, beforeChild);
}
},
removeChild(
parentInstance: Instance,
child: Instance | TextInstance,
): void {
parentInstance.removeChild(child);
},
removeChildFromContainer(
container: Container,
child: Instance | TextInstance,
): void {
if (container.nodeType === COMMENT_NODE) {
(container.parentNode: any).removeChild(child);
} else {
container.removeChild(child);
}
},
},
hydration: {
canHydrateInstance(
instance: Instance | TextInstance,
type: string,
props: Props,
): null | Instance {
if (
instance.nodeType !== ELEMENT_NODE ||
type.toLowerCase() !== instance.nodeName.toLowerCase()
) {
return null;
}
// This has now been refined to an element node.
return ((instance: any): Instance);
},
canHydrateTextInstance(
instance: Instance | TextInstance,
text: string,
): null | TextInstance {
if (text === '' || instance.nodeType !== TEXT_NODE) {
// Empty strings are not parsed by HTML so there won't be a correct match here.
return null;
}
// This has now been refined to a text node.
return ((instance: any): TextInstance);
},
getNextHydratableSibling(
instance: Instance | TextInstance,
): null | Instance | TextInstance {
let node = instance.nextSibling;
// Skip non-hydratable nodes.
while (
node &&
node.nodeType !== ELEMENT_NODE &&
node.nodeType !== TEXT_NODE
) {
node = node.nextSibling;
}
return (node: any);
},
getFirstHydratableChild(
parentInstance: Container | Instance,
): null | Instance | TextInstance {
let next = parentInstance.firstChild;
// Skip non-hydratable nodes.
while (
next &&
next.nodeType !== ELEMENT_NODE &&
next.nodeType !== TEXT_NODE
) {
next = next.nextSibling;
}
return (next: any);
},
hydrateInstance(
instance: Instance,
type: string,
props: Props,
rootContainerInstance: Container,
hostContext: HostContext,
internalInstanceHandle: Object,
): null | Array<mixed> {
precacheFiberNode(internalInstanceHandle, instance);
// TODO: Possibly defer this until the commit phase where all the events
// get attached.
updateFiberProps(instance, props);
let parentNamespace: string;
if (__DEV__) {
const hostContextDev = ((hostContext: any): HostContextDev);
parentNamespace = hostContextDev.namespace;
} else {
parentNamespace = ((hostContext: any): HostContextProd);
}
return diffHydratedProperties(
instance,
type,
props,
parentNamespace,
rootContainerInstance,
);
},
hydrateTextInstance(
textInstance: TextInstance,
text: string,
internalInstanceHandle: Object,
): boolean {
precacheFiberNode(internalInstanceHandle, textInstance);
return diffHydratedText(textInstance, text);
},
didNotMatchHydratedContainerTextInstance(
parentContainer: Container,
textInstance: TextInstance,
text: string,
) {
if (__DEV__) {
warnForUnmatchedText(textInstance, text);
}
},
didNotMatchHydratedTextInstance(
parentType: string,
parentProps: Props,
parentInstance: Instance,
textInstance: TextInstance,
text: string,
) {
if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
warnForUnmatchedText(textInstance, text);
}
},
didNotHydrateContainerInstance(
parentContainer: Container,
instance: Instance | TextInstance,
) {
if (__DEV__) {
if (instance.nodeType === 1) {
warnForDeletedHydratableElement(parentContainer, (instance: any));
} else {
warnForDeletedHydratableText(parentContainer, (instance: any));
}
}
},
didNotHydrateInstance(
parentType: string,
parentProps: Props,
parentInstance: Instance,
instance: Instance | TextInstance,
) {
if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
if (instance.nodeType === 1) {
warnForDeletedHydratableElement(parentInstance, (instance: any));
} else {
warnForDeletedHydratableText(parentInstance, (instance: any));
}
}
},
didNotFindHydratableContainerInstance(
parentContainer: Container,
type: string,
props: Props,
) {
if (__DEV__) {
warnForInsertedHydratedElement(parentContainer, type, props);
}
},
didNotFindHydratableContainerTextInstance(
parentContainer: Container,
text: string,
) {
if (__DEV__) {
warnForInsertedHydratedText(parentContainer, text);
}
},
didNotFindHydratableInstance(
parentType: string,
parentProps: Props,
parentInstance: Instance,
type: string,
props: Props,
) {
if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
warnForInsertedHydratedElement(parentInstance, type, props);
}
},
didNotFindHydratableTextInstance(
parentType: string,
parentProps: Props,
parentInstance: Instance,
text: string,
) {
if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
warnForInsertedHydratedText(parentInstance, text);
}
},
},
scheduleDeferredCallback: ReactDOMFrameScheduling.rIC,
cancelDeferredCallback: ReactDOMFrameScheduling.cIC,
});
ReactGenericBatching.injection.injectRenderer(DOMRenderer);
let warnedAboutHydrateAPI = false;
@@ -1270,6 +730,8 @@ const ReactDOM: Object = {
unstable_deferredUpdates: DOMRenderer.deferredUpdates,
unstable_interactiveUpdates: DOMRenderer.interactiveUpdates,
flushSync: DOMRenderer.flushSync,
unstable_flushControlled: DOMRenderer.flushControlled,
@@ -1306,11 +768,7 @@ const foundDevTools = DOMRenderer.injectIntoDevTools({
});
if (__DEV__) {
if (
!foundDevTools &&
ExecutionEnvironment.canUseDOM &&
window.top === window.self
) {
if (!foundDevTools && canUseDOM && window.top === window.self) {
// If we're in Chrome or Firefox, provide a download link if not installed.
if (
(navigator.userAgent.indexOf('Chrome') > -1 &&

View File

@@ -6,7 +6,7 @@
*/
import {HostComponent, HostText} from 'shared/ReactTypeOfWork';
import invariant from 'fbjs/lib/invariant';
import invariant from 'shared/invariant';
const randomKey = Math.random()
.toString(36)

View File

@@ -10,8 +10,7 @@
// TODO: direct imports like some-package/src/* are bad. Fix me.
import ReactDebugCurrentFiber from 'react-reconciler/src/ReactDebugCurrentFiber';
import {registrationNameModules} from 'events/EventPluginRegistry';
import emptyFunction from 'fbjs/lib/emptyFunction';
import warning from 'fbjs/lib/warning';
import warning from 'shared/warning';
import * as DOMPropertyOperations from './DOMPropertyOperations';
import * as ReactDOMFiberInput from './ReactDOMFiberInput';
@@ -21,8 +20,16 @@ import * as ReactDOMFiberTextarea from './ReactDOMFiberTextarea';
import * as inputValueTracking from './inputValueTracking';
import setInnerHTML from './setInnerHTML';
import setTextContent from './setTextContent';
import {
TOP_ERROR,
TOP_INVALID,
TOP_LOAD,
TOP_RESET,
TOP_SUBMIT,
TOP_TOGGLE,
} from '../events/DOMTopLevelEventTypes';
import {listenTo, trapBubbledEvent} from '../events/ReactBrowserEventEmitter';
import {mediaEventTypes} from '../events/BrowserEventConstants';
import {mediaEventTypes} from '../events/DOMTopLevelEventTypes';
import * as CSSPropertyOperations from '../shared/CSSPropertyOperations';
import {Namespaces, getIntrinsicNamespace} from '../shared/DOMNamespaces';
import {
@@ -55,7 +62,7 @@ const HTML = '__html';
const {html: HTML_NAMESPACE} = Namespaces;
let getStack = emptyFunction.thatReturns('');
let getStack = () => '';
let warnedUnknownTags;
let suppressHydrationWarning;
@@ -224,6 +231,8 @@ function getOwnerDocumentFromRootContainer(
: rootContainerElement.ownerDocument;
}
function noop() {}
function trapClickOnNonInteractiveElement(node: HTMLElement) {
// Mobile Safari does not fire properly bubble click events on
// non-interactive elements, which means delegated click listeners do not
@@ -234,7 +243,7 @@ function trapClickOnNonInteractiveElement(node: HTMLElement) {
// bookkeeping for it. Not sure if we need to clear it when the listener is
// removed.
// TODO: Only do this for the relevant Safaris maybe?
node.onclick = emptyFunction;
node.onclick = noop;
}
function setInitialDOMProperties(
@@ -424,7 +433,11 @@ export function setInitialProperties(
const isCustomComponentTag = isCustomComponent(tag, rawProps);
if (__DEV__) {
validatePropertiesInDevelopment(tag, rawProps);
if (isCustomComponentTag && !didWarnShadyDOM && domElement.shadyRoot) {
if (
isCustomComponentTag &&
!didWarnShadyDOM &&
(domElement: any).shadyRoot
) {
warning(
false,
'%s is using shady DOM. Using shady DOM with React can ' +
@@ -440,43 +453,41 @@ export function setInitialProperties(
switch (tag) {
case 'iframe':
case 'object':
trapBubbledEvent('topLoad', 'load', domElement);
trapBubbledEvent(TOP_LOAD, domElement);
props = rawProps;
break;
case 'video':
case 'audio':
// Create listener for each media event
for (const event in mediaEventTypes) {
if (mediaEventTypes.hasOwnProperty(event)) {
trapBubbledEvent(event, mediaEventTypes[event], domElement);
}
for (let i = 0; i < mediaEventTypes.length; i++) {
trapBubbledEvent(mediaEventTypes[i], domElement);
}
props = rawProps;
break;
case 'source':
trapBubbledEvent('topError', 'error', domElement);
trapBubbledEvent(TOP_ERROR, domElement);
props = rawProps;
break;
case 'img':
case 'image':
case 'link':
trapBubbledEvent('topError', 'error', domElement);
trapBubbledEvent('topLoad', 'load', domElement);
trapBubbledEvent(TOP_ERROR, domElement);
trapBubbledEvent(TOP_LOAD, domElement);
props = rawProps;
break;
case 'form':
trapBubbledEvent('topReset', 'reset', domElement);
trapBubbledEvent('topSubmit', 'submit', domElement);
trapBubbledEvent(TOP_RESET, domElement);
trapBubbledEvent(TOP_SUBMIT, domElement);
props = rawProps;
break;
case 'details':
trapBubbledEvent('topToggle', 'toggle', domElement);
trapBubbledEvent(TOP_TOGGLE, domElement);
props = rawProps;
break;
case 'input':
ReactDOMFiberInput.initWrapperState(domElement, rawProps);
props = ReactDOMFiberInput.getHostProps(domElement, rawProps);
trapBubbledEvent('topInvalid', 'invalid', domElement);
trapBubbledEvent(TOP_INVALID, domElement);
// For controlled components we always need to ensure we're listening
// to onChange. Even if there is no listener.
ensureListeningTo(rootContainerElement, 'onChange');
@@ -488,7 +499,7 @@ export function setInitialProperties(
case 'select':
ReactDOMFiberSelect.initWrapperState(domElement, rawProps);
props = ReactDOMFiberSelect.getHostProps(domElement, rawProps);
trapBubbledEvent('topInvalid', 'invalid', domElement);
trapBubbledEvent(TOP_INVALID, domElement);
// For controlled components we always need to ensure we're listening
// to onChange. Even if there is no listener.
ensureListeningTo(rootContainerElement, 'onChange');
@@ -496,7 +507,7 @@ export function setInitialProperties(
case 'textarea':
ReactDOMFiberTextarea.initWrapperState(domElement, rawProps);
props = ReactDOMFiberTextarea.getHostProps(domElement, rawProps);
trapBubbledEvent('topInvalid', 'invalid', domElement);
trapBubbledEvent(TOP_INVALID, domElement);
// For controlled components we always need to ensure we're listening
// to onChange. Even if there is no listener.
ensureListeningTo(rootContainerElement, 'onChange');
@@ -520,7 +531,7 @@ export function setInitialProperties(
// TODO: Make sure we check if this is still unmounted or do any clean
// up necessary since we never stop tracking anymore.
inputValueTracking.track((domElement: any));
ReactDOMFiberInput.postMountWrapper(domElement, rawProps);
ReactDOMFiberInput.postMountWrapper(domElement, rawProps, false);
break;
case 'textarea':
// TODO: Make sure we check if this is still unmounted or do any clean
@@ -814,7 +825,11 @@ export function diffHydratedProperties(
suppressHydrationWarning = rawProps[SUPPRESS_HYDRATION_WARNING] === true;
isCustomComponentTag = isCustomComponent(tag, rawProps);
validatePropertiesInDevelopment(tag, rawProps);
if (isCustomComponentTag && !didWarnShadyDOM && domElement.shadyRoot) {
if (
isCustomComponentTag &&
!didWarnShadyDOM &&
(domElement: any).shadyRoot
) {
warning(
false,
'%s is using shady DOM. Using shady DOM with React can ' +
@@ -829,36 +844,34 @@ export function diffHydratedProperties(
switch (tag) {
case 'iframe':
case 'object':
trapBubbledEvent('topLoad', 'load', domElement);
trapBubbledEvent(TOP_LOAD, domElement);
break;
case 'video':
case 'audio':
// Create listener for each media event
for (const event in mediaEventTypes) {
if (mediaEventTypes.hasOwnProperty(event)) {
trapBubbledEvent(event, mediaEventTypes[event], domElement);
}
for (let i = 0; i < mediaEventTypes.length; i++) {
trapBubbledEvent(mediaEventTypes[i], domElement);
}
break;
case 'source':
trapBubbledEvent('topError', 'error', domElement);
trapBubbledEvent(TOP_ERROR, domElement);
break;
case 'img':
case 'image':
case 'link':
trapBubbledEvent('topError', 'error', domElement);
trapBubbledEvent('topLoad', 'load', domElement);
trapBubbledEvent(TOP_ERROR, domElement);
trapBubbledEvent(TOP_LOAD, domElement);
break;
case 'form':
trapBubbledEvent('topReset', 'reset', domElement);
trapBubbledEvent('topSubmit', 'submit', domElement);
trapBubbledEvent(TOP_RESET, domElement);
trapBubbledEvent(TOP_SUBMIT, domElement);
break;
case 'details':
trapBubbledEvent('topToggle', 'toggle', domElement);
trapBubbledEvent(TOP_TOGGLE, domElement);
break;
case 'input':
ReactDOMFiberInput.initWrapperState(domElement, rawProps);
trapBubbledEvent('topInvalid', 'invalid', domElement);
trapBubbledEvent(TOP_INVALID, domElement);
// For controlled components we always need to ensure we're listening
// to onChange. Even if there is no listener.
ensureListeningTo(rootContainerElement, 'onChange');
@@ -868,14 +881,14 @@ export function diffHydratedProperties(
break;
case 'select':
ReactDOMFiberSelect.initWrapperState(domElement, rawProps);
trapBubbledEvent('topInvalid', 'invalid', domElement);
trapBubbledEvent(TOP_INVALID, domElement);
// For controlled components we always need to ensure we're listening
// to onChange. Even if there is no listener.
ensureListeningTo(rootContainerElement, 'onChange');
break;
case 'textarea':
ReactDOMFiberTextarea.initWrapperState(domElement, rawProps);
trapBubbledEvent('topInvalid', 'invalid', domElement);
trapBubbledEvent(TOP_INVALID, domElement);
// For controlled components we always need to ensure we're listening
// to onChange. Even if there is no listener.
ensureListeningTo(rootContainerElement, 'onChange');
@@ -1065,7 +1078,7 @@ export function diffHydratedProperties(
// TODO: Make sure we check if this is still unmounted or do any clean
// up necessary since we never stop tracking anymore.
inputValueTracking.track((domElement: any));
ReactDOMFiberInput.postMountWrapper(domElement, rawProps);
ReactDOMFiberInput.postMountWrapper(domElement, rawProps, true);
break;
case 'textarea':
// TODO: Make sure we check if this is still unmounted or do any clean

View File

@@ -9,8 +9,8 @@
// TODO: direct imports like some-package/src/* are bad. Fix me.
import ReactDebugCurrentFiber from 'react-reconciler/src/ReactDebugCurrentFiber';
import invariant from 'fbjs/lib/invariant';
import warning from 'fbjs/lib/warning';
import invariant from 'shared/invariant';
import warning from 'shared/warning';
import * as DOMPropertyOperations from './DOMPropertyOperations';
import {getFiberCurrentPropsFromNode} from './ReactDOMComponentTree';
@@ -205,20 +205,32 @@ export function updateWrapper(element: Element, props: Object) {
}
}
export function postMountWrapper(element: Element, props: Object) {
export function postMountWrapper(
element: Element,
props: Object,
isHydrating: boolean,
) {
const node = ((element: any): InputWithWrapperState);
if (props.hasOwnProperty('value') || props.hasOwnProperty('defaultValue')) {
const initialValue = '' + node._wrapperState.initialValue;
const currentValue = node.value;
// Do not assign value if it is already set. This prevents user text input
// from being lost during SSR hydration.
if (node.value === '') {
node.value = '' + node._wrapperState.initialValue;
if (!isHydrating) {
// Do not re-assign the value property if there is no change. This
// potentially avoids a DOM write and prevents Firefox (~60.0.1) from
// prematurely marking required inputs as invalid
if (initialValue !== currentValue) {
node.value = initialValue;
}
}
// value must be assigned before defaultValue. This fixes an issue where the
// visually displayed value of date inputs disappears on mobile Safari and Chrome:
// https://github.com/facebook/react/issues/7233
node.defaultValue = '' + node._wrapperState.initialValue;
node.defaultValue = initialValue;
}
// Normally, we'd just do `node.checked = node.checked` upon initial mount, less this bug

View File

@@ -8,7 +8,7 @@
*/
import React from 'react';
import warning from 'fbjs/lib/warning';
import warning from 'shared/warning';
let didWarnSelectedSetOnOption = false;

View File

@@ -9,7 +9,7 @@
// TODO: direct imports like some-package/src/* are bad. Fix me.
import ReactDebugCurrentFiber from 'react-reconciler/src/ReactDebugCurrentFiber';
import warning from 'fbjs/lib/warning';
import warning from 'shared/warning';
import ReactControlledValuePropTypes from '../shared/ReactControlledValuePropTypes';

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 warning from 'shared/warning';
// TODO: direct imports like some-package/src/* are bad. Fix me.
import ReactDebugCurrentFiber from 'react-reconciler/src/ReactDebugCurrentFiber';

View File

@@ -0,0 +1,581 @@
/**
* Copyright (c) 2013-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import * as ReactScheduler from 'shared/ReactScheduler';
import * as ReactDOMComponentTree from './ReactDOMComponentTree';
import * as ReactDOMFiberComponent from './ReactDOMFiberComponent';
import * as ReactInputSelection from './ReactInputSelection';
import setTextContent from './setTextContent';
import validateDOMNesting from './validateDOMNesting';
import * as ReactBrowserEventEmitter from '../events/ReactBrowserEventEmitter';
import {getChildNamespace} from '../shared/DOMNamespaces';
import {
ELEMENT_NODE,
TEXT_NODE,
COMMENT_NODE,
DOCUMENT_NODE,
DOCUMENT_FRAGMENT_NODE,
} from '../shared/HTMLNodeType';
export type Type = string;
export type Props = {
autoFocus?: boolean,
children?: mixed,
hidden?: boolean,
suppressHydrationWarning?: boolean,
dangerouslySetInnerHTML?: mixed,
};
export type Container = Element | Document;
export type Instance = Element;
export type TextInstance = Text;
export type HydratableInstance = Element | Text;
export type PublicInstance = Element | Text;
type HostContextDev = {
namespace: string,
ancestorInfo: mixed,
};
type HostContextProd = string;
export type HostContext = HostContextDev | HostContextProd;
export type UpdatePayload = Array<mixed>;
export type ChildSet = void; // Unused
const {
createElement,
createTextNode,
setInitialProperties,
diffProperties,
updateProperties,
diffHydratedProperties,
diffHydratedText,
warnForUnmatchedText,
warnForDeletedHydratableElement,
warnForDeletedHydratableText,
warnForInsertedHydratedElement,
warnForInsertedHydratedText,
} = ReactDOMFiberComponent;
const {updatedAncestorInfo} = validateDOMNesting;
const {precacheFiberNode, updateFiberProps} = ReactDOMComponentTree;
let SUPPRESS_HYDRATION_WARNING;
if (__DEV__) {
SUPPRESS_HYDRATION_WARNING = 'suppressHydrationWarning';
}
let eventsEnabled: ?boolean = null;
let selectionInformation: ?mixed = null;
function shouldAutoFocusHostComponent(type: string, props: Props): boolean {
switch (type) {
case 'button':
case 'input':
case 'select':
case 'textarea':
return !!props.autoFocus;
}
return false;
}
export * from 'shared/HostConfigWithNoPersistence';
export function getRootHostContext(
rootContainerInstance: Container,
): HostContext {
let type;
let namespace;
const nodeType = rootContainerInstance.nodeType;
switch (nodeType) {
case DOCUMENT_NODE:
case DOCUMENT_FRAGMENT_NODE: {
type = nodeType === DOCUMENT_NODE ? '#document' : '#fragment';
let root = (rootContainerInstance: any).documentElement;
namespace = root ? root.namespaceURI : getChildNamespace(null, '');
break;
}
default: {
const container: any =
nodeType === COMMENT_NODE
? rootContainerInstance.parentNode
: rootContainerInstance;
const ownNamespace = container.namespaceURI || null;
type = container.tagName;
namespace = getChildNamespace(ownNamespace, type);
break;
}
}
if (__DEV__) {
const validatedTag = type.toLowerCase();
const ancestorInfo = updatedAncestorInfo(null, validatedTag, null);
return {namespace, ancestorInfo};
}
return namespace;
}
export function getChildHostContext(
parentHostContext: HostContext,
type: string,
rootContainerInstance: Container,
): HostContext {
if (__DEV__) {
const parentHostContextDev = ((parentHostContext: any): HostContextDev);
const namespace = getChildNamespace(parentHostContextDev.namespace, type);
const ancestorInfo = updatedAncestorInfo(
parentHostContextDev.ancestorInfo,
type,
null,
);
return {namespace, ancestorInfo};
}
const parentNamespace = ((parentHostContext: any): HostContextProd);
return getChildNamespace(parentNamespace, type);
}
export function getPublicInstance(instance: Instance): * {
return instance;
}
export function prepareForCommit(containerInfo: Container): void {
eventsEnabled = ReactBrowserEventEmitter.isEnabled();
selectionInformation = ReactInputSelection.getSelectionInformation();
ReactBrowserEventEmitter.setEnabled(false);
}
export function resetAfterCommit(containerInfo: Container): void {
ReactInputSelection.restoreSelection(selectionInformation);
selectionInformation = null;
ReactBrowserEventEmitter.setEnabled(eventsEnabled);
eventsEnabled = null;
}
export function createInstance(
type: string,
props: Props,
rootContainerInstance: Container,
hostContext: HostContext,
internalInstanceHandle: Object,
): Instance {
let parentNamespace: string;
if (__DEV__) {
// TODO: take namespace into account when validating.
const hostContextDev = ((hostContext: any): HostContextDev);
validateDOMNesting(type, null, hostContextDev.ancestorInfo);
if (
typeof props.children === 'string' ||
typeof props.children === 'number'
) {
const string = '' + props.children;
const ownAncestorInfo = updatedAncestorInfo(
hostContextDev.ancestorInfo,
type,
null,
);
validateDOMNesting(null, string, ownAncestorInfo);
}
parentNamespace = hostContextDev.namespace;
} else {
parentNamespace = ((hostContext: any): HostContextProd);
}
const domElement: Instance = createElement(
type,
props,
rootContainerInstance,
parentNamespace,
);
precacheFiberNode(internalInstanceHandle, domElement);
updateFiberProps(domElement, props);
return domElement;
}
export function appendInitialChild(
parentInstance: Instance,
child: Instance | TextInstance,
): void {
parentInstance.appendChild(child);
}
export function finalizeInitialChildren(
domElement: Instance,
type: string,
props: Props,
rootContainerInstance: Container,
hostContext: HostContext,
): boolean {
setInitialProperties(domElement, type, props, rootContainerInstance);
return shouldAutoFocusHostComponent(type, props);
}
export function prepareUpdate(
domElement: Instance,
type: string,
oldProps: Props,
newProps: Props,
rootContainerInstance: Container,
hostContext: HostContext,
): null | Array<mixed> {
if (__DEV__) {
const hostContextDev = ((hostContext: any): HostContextDev);
if (
typeof newProps.children !== typeof oldProps.children &&
(typeof newProps.children === 'string' ||
typeof newProps.children === 'number')
) {
const string = '' + newProps.children;
const ownAncestorInfo = updatedAncestorInfo(
hostContextDev.ancestorInfo,
type,
null,
);
validateDOMNesting(null, string, ownAncestorInfo);
}
}
return diffProperties(
domElement,
type,
oldProps,
newProps,
rootContainerInstance,
);
}
export function shouldSetTextContent(type: string, props: Props): boolean {
return (
type === 'textarea' ||
typeof props.children === 'string' ||
typeof props.children === 'number' ||
(typeof props.dangerouslySetInnerHTML === 'object' &&
props.dangerouslySetInnerHTML !== null &&
typeof props.dangerouslySetInnerHTML.__html === 'string')
);
}
export function shouldDeprioritizeSubtree(type: string, props: Props): boolean {
return !!props.hidden;
}
export function createTextInstance(
text: string,
rootContainerInstance: Container,
hostContext: HostContext,
internalInstanceHandle: Object,
): TextInstance {
if (__DEV__) {
const hostContextDev = ((hostContext: any): HostContextDev);
validateDOMNesting(null, text, hostContextDev.ancestorInfo);
}
const textNode: TextInstance = createTextNode(text, rootContainerInstance);
precacheFiberNode(internalInstanceHandle, textNode);
return textNode;
}
export const now = ReactScheduler.now;
export const isPrimaryRenderer = true;
export const scheduleDeferredCallback = ReactScheduler.scheduleWork;
export const cancelDeferredCallback = ReactScheduler.cancelScheduledWork;
// -------------------
// Mutation
// -------------------
export const supportsMutation = true;
export function commitMount(
domElement: Instance,
type: string,
newProps: Props,
internalInstanceHandle: Object,
): void {
// Despite the naming that might imply otherwise, this method only
// fires if there is an `Update` effect scheduled during mounting.
// This happens if `finalizeInitialChildren` returns `true` (which it
// does to implement the `autoFocus` attribute on the client). But
// there are also other cases when this might happen (such as patching
// up text content during hydration mismatch). So we'll check this again.
if (shouldAutoFocusHostComponent(type, newProps)) {
((domElement: any):
| HTMLButtonElement
| HTMLInputElement
| HTMLSelectElement
| HTMLTextAreaElement).focus();
}
}
export function commitUpdate(
domElement: Instance,
updatePayload: Array<mixed>,
type: string,
oldProps: Props,
newProps: Props,
internalInstanceHandle: Object,
): void {
// Update the props handle so that we know which props are the ones with
// with current event handlers.
updateFiberProps(domElement, newProps);
// Apply the diff to the DOM node.
updateProperties(domElement, updatePayload, type, oldProps, newProps);
}
export function resetTextContent(domElement: Instance): void {
setTextContent(domElement, '');
}
export function commitTextUpdate(
textInstance: TextInstance,
oldText: string,
newText: string,
): void {
textInstance.nodeValue = newText;
}
export function appendChild(
parentInstance: Instance,
child: Instance | TextInstance,
): void {
parentInstance.appendChild(child);
}
export function appendChildToContainer(
container: Container,
child: Instance | TextInstance,
): void {
if (container.nodeType === COMMENT_NODE) {
(container.parentNode: any).insertBefore(child, container);
} else {
container.appendChild(child);
}
}
export function insertBefore(
parentInstance: Instance,
child: Instance | TextInstance,
beforeChild: Instance | TextInstance,
): void {
parentInstance.insertBefore(child, beforeChild);
}
export function insertInContainerBefore(
container: Container,
child: Instance | TextInstance,
beforeChild: Instance | TextInstance,
): void {
if (container.nodeType === COMMENT_NODE) {
(container.parentNode: any).insertBefore(child, beforeChild);
} else {
container.insertBefore(child, beforeChild);
}
}
export function removeChild(
parentInstance: Instance,
child: Instance | TextInstance,
): void {
parentInstance.removeChild(child);
}
export function removeChildFromContainer(
container: Container,
child: Instance | TextInstance,
): void {
if (container.nodeType === COMMENT_NODE) {
(container.parentNode: any).removeChild(child);
} else {
container.removeChild(child);
}
}
// -------------------
// Hydration
// -------------------
export const supportsHydration = true;
export function canHydrateInstance(
instance: Instance | TextInstance,
type: string,
props: Props,
): null | Instance {
if (
instance.nodeType !== ELEMENT_NODE ||
type.toLowerCase() !== instance.nodeName.toLowerCase()
) {
return null;
}
// This has now been refined to an element node.
return ((instance: any): Instance);
}
export function canHydrateTextInstance(
instance: Instance | TextInstance,
text: string,
): null | TextInstance {
if (text === '' || instance.nodeType !== TEXT_NODE) {
// Empty strings are not parsed by HTML so there won't be a correct match here.
return null;
}
// This has now been refined to a text node.
return ((instance: any): TextInstance);
}
export function getNextHydratableSibling(
instance: Instance | TextInstance,
): null | Instance | TextInstance {
let node = instance.nextSibling;
// Skip non-hydratable nodes.
while (
node &&
node.nodeType !== ELEMENT_NODE &&
node.nodeType !== TEXT_NODE
) {
node = node.nextSibling;
}
return (node: any);
}
export function getFirstHydratableChild(
parentInstance: Container | Instance,
): null | Instance | TextInstance {
let next = parentInstance.firstChild;
// Skip non-hydratable nodes.
while (
next &&
next.nodeType !== ELEMENT_NODE &&
next.nodeType !== TEXT_NODE
) {
next = next.nextSibling;
}
return (next: any);
}
export function hydrateInstance(
instance: Instance,
type: string,
props: Props,
rootContainerInstance: Container,
hostContext: HostContext,
internalInstanceHandle: Object,
): null | Array<mixed> {
precacheFiberNode(internalInstanceHandle, instance);
// TODO: Possibly defer this until the commit phase where all the events
// get attached.
updateFiberProps(instance, props);
let parentNamespace: string;
if (__DEV__) {
const hostContextDev = ((hostContext: any): HostContextDev);
parentNamespace = hostContextDev.namespace;
} else {
parentNamespace = ((hostContext: any): HostContextProd);
}
return diffHydratedProperties(
instance,
type,
props,
parentNamespace,
rootContainerInstance,
);
}
export function hydrateTextInstance(
textInstance: TextInstance,
text: string,
internalInstanceHandle: Object,
): boolean {
precacheFiberNode(internalInstanceHandle, textInstance);
return diffHydratedText(textInstance, text);
}
export function didNotMatchHydratedContainerTextInstance(
parentContainer: Container,
textInstance: TextInstance,
text: string,
) {
if (__DEV__) {
warnForUnmatchedText(textInstance, text);
}
}
export function didNotMatchHydratedTextInstance(
parentType: string,
parentProps: Props,
parentInstance: Instance,
textInstance: TextInstance,
text: string,
) {
if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
warnForUnmatchedText(textInstance, text);
}
}
export function didNotHydrateContainerInstance(
parentContainer: Container,
instance: Instance | TextInstance,
) {
if (__DEV__) {
if (instance.nodeType === 1) {
warnForDeletedHydratableElement(parentContainer, (instance: any));
} else {
warnForDeletedHydratableText(parentContainer, (instance: any));
}
}
}
export function didNotHydrateInstance(
parentType: string,
parentProps: Props,
parentInstance: Instance,
instance: Instance | TextInstance,
) {
if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
if (instance.nodeType === 1) {
warnForDeletedHydratableElement(parentInstance, (instance: any));
} else {
warnForDeletedHydratableText(parentInstance, (instance: any));
}
}
}
export function didNotFindHydratableContainerInstance(
parentContainer: Container,
type: string,
props: Props,
) {
if (__DEV__) {
warnForInsertedHydratedElement(parentContainer, type, props);
}
}
export function didNotFindHydratableContainerTextInstance(
parentContainer: Container,
text: string,
) {
if (__DEV__) {
warnForInsertedHydratedText(parentContainer, text);
}
}
export function didNotFindHydratableInstance(
parentType: string,
parentProps: Props,
parentInstance: Instance,
type: string,
props: Props,
) {
if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
warnForInsertedHydratedElement(parentInstance, type, props);
}
}
export function didNotFindHydratableTextInstance(
parentType: string,
parentProps: Props,
parentInstance: Instance,
text: string,
) {
if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
warnForInsertedHydratedText(parentInstance, text);
}
}

View File

@@ -5,11 +5,48 @@
* LICENSE file in the root directory of this source tree.
*/
import containsNode from 'fbjs/lib/containsNode';
import getActiveElement from 'fbjs/lib/getActiveElement';
import getActiveElement from './getActiveElement';
import * as ReactDOMSelection from './ReactDOMSelection';
import {ELEMENT_NODE} from '../shared/HTMLNodeType';
import {ELEMENT_NODE, TEXT_NODE} from '../shared/HTMLNodeType';
// TODO: this code is originally inlined from fbjs.
// It is likely that we don't actually need all these checks
// for the particular use case in this file.
function isNode(object) {
const doc = object ? object.ownerDocument || object : document;
const defaultView = doc.defaultView || window;
return !!(
object &&
(typeof defaultView.Node === 'function'
? object instanceof defaultView.Node
: typeof object === 'object' &&
typeof object.nodeType === 'number' &&
typeof object.nodeName === 'string')
);
}
function isTextNode(object) {
return isNode(object) && object.nodeType === TEXT_NODE;
}
function containsNode(outerNode, innerNode) {
if (!outerNode || !innerNode) {
return false;
} else if (outerNode === innerNode) {
return true;
} else if (isTextNode(outerNode)) {
return false;
} else if (isTextNode(innerNode)) {
return containsNode(outerNode, innerNode.parentNode);
} else if ('contains' in outerNode) {
return outerNode.contains(innerNode);
} else if (outerNode.compareDocumentPosition) {
return !!(outerNode.compareDocumentPosition(innerNode) & 16);
} else {
return false;
}
}
function isInDocument(node) {
return containsNode(document.documentElement, node);
@@ -22,11 +59,21 @@ function isInDocument(node) {
* Input selection module for React.
*/
/**
* @hasSelectionCapabilities: we get the element types that support selection
* from https://html.spec.whatwg.org/#do-not-apply, looking at `selectionStart`
* and `selectionEnd` rows.
*/
export function hasSelectionCapabilities(elem) {
const nodeName = elem && elem.nodeName && elem.nodeName.toLowerCase();
return (
nodeName &&
((nodeName === 'input' && elem.type === 'text') ||
((nodeName === 'input' &&
(elem.type === 'text' ||
elem.type === 'search' ||
elem.type === 'tel' ||
elem.type === 'url' ||
elem.type === 'password')) ||
nodeName === 'textarea' ||
elem.contentEditable === 'true')
);
@@ -52,7 +99,10 @@ export function restoreSelection(priorSelectionInformation) {
const priorFocusedElem = priorSelectionInformation.focusedElem;
const priorSelectionRange = priorSelectionInformation.selectionRange;
if (curFocusedElem !== priorFocusedElem && isInDocument(priorFocusedElem)) {
if (hasSelectionCapabilities(priorFocusedElem)) {
if (
priorSelectionRange !== null &&
hasSelectionCapabilities(priorFocusedElem)
) {
setSelection(priorFocusedElem, priorSelectionRange);
}
@@ -69,7 +119,9 @@ export function restoreSelection(priorSelectionInformation) {
}
}
priorFocusedElem.focus();
if (typeof priorFocusedElem.focus === 'function') {
priorFocusedElem.focus();
}
for (let i = 0; i < ancestors.length; i++) {
const info = ancestors[i];

View File

@@ -0,0 +1,20 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
export default function getActiveElement(doc: ?Document): ?Element {
doc = doc || (typeof document !== 'undefined' ? document : undefined);
if (typeof doc === 'undefined') {
return null;
}
try {
return doc.activeElement || doc.body;
} catch (e) {
return doc.body;
}
}

View File

@@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment';
import {canUseDOM} from 'shared/ExecutionEnvironment';
let contentKey = null;
@@ -16,7 +16,7 @@ let contentKey = null;
* @internal
*/
function getTextContentAccessor() {
if (!contentKey && ExecutionEnvironment.canUseDOM) {
if (!contentKey && canUseDOM) {
// Prefer textContent to innerText because many browsers support both but
// SVG <text> elements don't support innerText even when <div> does.
contentKey =

View File

@@ -63,20 +63,21 @@ function trackValueOnNode(node: any): ?ValueTracker {
// (needed for certain tests that spyOn input values and Safari)
if (
node.hasOwnProperty(valueField) ||
typeof descriptor === 'undefined' ||
typeof descriptor.get !== 'function' ||
typeof descriptor.set !== 'function'
) {
return;
}
const {get, set} = descriptor;
Object.defineProperty(node, valueField, {
configurable: true,
get: function() {
return descriptor.get.call(this);
return get.call(this);
},
set: function(value) {
currentValue = '' + value;
descriptor.set.call(this, value);
set.call(this, value);
},
});
// We could've passed this the first time

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