Compare commits

..

79 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
233 changed files with 6347 additions and 3212 deletions

View File

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

@@ -5,6 +5,34 @@
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))
@@ -42,8 +70,6 @@
* 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))
</details>
## 16.3.2 (April 16, 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

@@ -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-ci
# - yarn flow-ci
- yarn build
- yarn test
- yarn prettier

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
{
"private": true,
"version": "16.4.0",
"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.4.4",
"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",
@@ -57,7 +57,6 @@
"eslint-plugin-no-for-of-loops": "^1.0.0",
"eslint-plugin-react": "^6.7.1",
"eslint-plugin-react-internal": "link:./scripts/eslint-rules/",
"fbjs": "^0.8.16",
"fbjs-scripts": "^0.6.0",
"filesize": "^3.5.6",
"flow-bin": "^0.72.0",
@@ -69,8 +68,8 @@
"gzip-js": "~0.3.2",
"gzip-size": "^3.0.0",
"jasmine-check": "^1.0.0-rc.0",
"jest": "^22.4.4",
"jest-diff": "^22.4.3",
"jest": "^23.1.0",
"jest-diff": "^23.0.1",
"merge-stream": "^1.0.0",
"minimatch": "^3.0.4",
"minimist": "^1.2.0",
@@ -79,7 +78,7 @@
"object-assign": "^4.1.1",
"platform": "^1.1.0",
"prettier": "1.11.1",
"prop-types": "^15.6.0",
"prop-types": "^15.6.2",
"random-seed": "^0.3.0",
"react-lifecycles-compat": "^3.0.2",
"rimraf": "^2.6.1",

View File

@@ -1,7 +1,7 @@
{
"name": "create-subscription",
"description": "utility for subscribing to external data sources inside React components",
"version": "16.4.0",
"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

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

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;

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

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

@@ -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';
import {isStartish, isMoveish, isEndish} from './ResponderTopLevelEventTypes';

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

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

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

@@ -5,17 +5,21 @@
* LICENSE file in the root directory of this source tree.
*/
import * as ReactScheduler from 'react-scheduler';
import * as ReactScheduler from 'shared/ReactScheduler';
import Transform from 'art/core/transform';
import Mode from 'art/modes/current';
import invariant from 'fbjs/lib/invariant';
import emptyObject from 'fbjs/lib/emptyObject';
import invariant from 'shared/invariant';
import {TYPES, EVENT_TYPES, childrenAsString} from './ReactARTInternals';
const pooledTransform = new Transform();
const NO_CONTEXT = {};
const UPDATE_SIGNAL = {};
if (__DEV__) {
Object.freeze(NO_CONTEXT);
Object.freeze(UPDATE_SIGNAL);
}
/** Helper Methods */
@@ -318,11 +322,11 @@ export function shouldDeprioritizeSubtree(type, props) {
}
export function getRootHostContext() {
return emptyObject;
return NO_CONTEXT;
}
export function getChildHostContext() {
return emptyObject;
return NO_CONTEXT;
}
export const scheduleDeferredCallback = ReactScheduler.scheduleWork;

View File

@@ -71,6 +71,8 @@ describe('ReactART', () => {
Surface = ReactART.Surface;
TestComponent = class extends React.Component {
group = React.createRef();
render() {
const a = (
<Shape
@@ -104,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>
@@ -121,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');
});
@@ -260,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>
);
@@ -289,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);
});

View File

@@ -1,6 +1,6 @@
{
"name": "react-dom",
"version": "16.4.0",
"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);
});
});

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

@@ -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,15 +308,9 @@ describe('ReactDOM', () => {
it("shouldn't fire duplicate event handler while handling other nested dispatch", () => {
const actual = [];
function click(node) {
ReactTestUtils.Simulate.click(node, {
path: [node, container],
});
}
class Wrapper extends React.Component {
componentDidMount() {
click(this.ref1);
this.ref1.click();
}
render() {
@@ -325,7 +319,7 @@ describe('ReactDOM', () => {
<div
onClick={() => {
actual.push('1st node clicked');
click(this.ref2);
this.ref2.click();
}}
ref={ref => (this.ref1 = ref)}
/>
@@ -341,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', () => {
@@ -441,7 +440,7 @@ describe('ReactDOM', () => {
global.requestAnimationFrame = undefined;
jest.resetModules();
expect(() => require('react-dom')).toWarnDev(
'React depends on requestAnimationFrame.',
"This browser doesn't support requestAnimationFrame.",
);
} finally {
global.requestAnimationFrame = previousRAF;

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

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,33 +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();
ReactTestUtils.Simulate.click(portal);
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;
@@ -853,56 +883,69 @@ describe('ReactDOMFiber', () => {
function simulateMouseMove(from, to) {
if (from) {
ReactTestUtils.SimulateNative.mouseOut(from, {
relatedTarget: to,
});
from.dispatchEvent(
new MouseEvent('mouseout', {
bubbles: true,
cancelable: true,
relatedTarget: to,
}),
);
}
if (to) {
ReactTestUtils.SimulateNative.mouseOver(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', () => {
@@ -942,81 +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();
node.click();
}
render() {
return null;
}
}
let inst;
ReactDOM.render([<Example key="a" ref={n => (inst = n)} />], container);
const node = container.firstChild;
expect(node.tagName).toEqual('DIV');
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);
}
class Click extends React.Component {
constructor() {
super();
click(node);
}
render() {
return null;
}
}
let inst;
ReactDOM.render([<Example key="a" ref={n => (inst = n)} />], container);
const node = container.firstChild;
expect(node.tagName).toEqual('DIV');
function click(target) {
ReactTestUtils.Simulate.click(target);
}
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

@@ -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'}});
@@ -702,9 +692,8 @@ describe('ReactDOMInput', () => {
});
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
@@ -1460,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;
@@ -1473,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

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

@@ -191,5 +191,63 @@ describe('ReactDOMServerIntegration', () => {
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

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

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

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

@@ -63,7 +63,7 @@ module.exports = function(initModules) {
}
}
if (__DEV__) {
expect(console.error.calls.count()).toBe(count);
expect(console.error).toHaveBeenCalledTimes(count);
}
return result;
}

View File

@@ -21,7 +21,7 @@ import './ReactDOMClientInjection';
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';
@@ -31,9 +31,9 @@ import * as ReactInstanceMap from 'shared/ReactInstanceMap';
import ReactVersion from 'shared/ReactVersion';
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';
@@ -730,6 +730,8 @@ const ReactDOM: Object = {
unstable_deferredUpdates: DOMRenderer.deferredUpdates,
unstable_interactiveUpdates: DOMRenderer.interactiveUpdates,
flushSync: DOMRenderer.flushSync,
unstable_flushControlled: DOMRenderer.flushControlled,
@@ -766,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';
@@ -63,7 +62,7 @@ const HTML = '__html';
const {html: HTML_NAMESPACE} = Namespaces;
let getStack = emptyFunction.thatReturns('');
let getStack = () => '';
let warnedUnknownTags;
let suppressHydrationWarning;
@@ -232,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
@@ -242,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(
@@ -530,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
@@ -1077,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

@@ -7,7 +7,7 @@
* @flow
*/
import * as ReactScheduler from 'react-scheduler';
import * as ReactScheduler from 'shared/ReactScheduler';
import * as ReactDOMComponentTree from './ReactDOMComponentTree';
import * as ReactDOMFiberComponent from './ReactDOMFiberComponent';

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

@@ -5,13 +5,12 @@
* LICENSE file in the root directory of this source tree.
*/
import emptyFunction from 'fbjs/lib/emptyFunction';
import warning from 'fbjs/lib/warning';
import warning from 'shared/warning';
// TODO: direct imports like some-package/src/* are bad. Fix me.
import ReactDebugCurrentFiber from 'react-reconciler/src/ReactDebugCurrentFiber';
const {getCurrentFiberStackAddendum} = ReactDebugCurrentFiber;
let validateDOMNesting = emptyFunction;
let validateDOMNesting = () => {};
if (__DEV__) {
// This validation code was written based on the HTML5 parsing spec:

View File

@@ -8,7 +8,7 @@
import type {TopLevelType} from 'events/TopLevelEventTypes';
import {accumulateTwoPhaseDispatches} from 'events/EventPropagators';
import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment';
import {canUseDOM} from 'shared/ExecutionEnvironment';
import {
TOP_BLUR,
@@ -29,11 +29,10 @@ import SyntheticInputEvent from './SyntheticInputEvent';
const END_KEYCODES = [9, 13, 27, 32]; // Tab, Return, Esc, Space
const START_KEYCODE = 229;
const canUseCompositionEvent =
ExecutionEnvironment.canUseDOM && 'CompositionEvent' in window;
const canUseCompositionEvent = canUseDOM && 'CompositionEvent' in window;
let documentMode = null;
if (ExecutionEnvironment.canUseDOM && 'documentMode' in document) {
if (canUseDOM && 'documentMode' in document) {
documentMode = document.documentMode;
}
@@ -41,13 +40,13 @@ if (ExecutionEnvironment.canUseDOM && 'documentMode' in document) {
// directly represent `beforeInput`. The IE `textinput` event is not as
// useful, so we don't use it.
const canUseTextInputEvent =
ExecutionEnvironment.canUseDOM && 'TextEvent' in window && !documentMode;
canUseDOM && 'TextEvent' in window && !documentMode;
// In IE9+, we have access to composition events, but the data supplied
// by the native compositionend event may be incorrect. Japanese ideographic
// spaces, for instance (\u3000) are not recorded correctly.
const useFallbackCompositionData =
ExecutionEnvironment.canUseDOM &&
canUseDOM &&
(!canUseCompositionEvent ||
(documentMode && documentMode > 8 && documentMode <= 11));
@@ -200,6 +199,20 @@ function getDataFromCustomEvent(nativeEvent) {
return null;
}
/**
* Check if a composition event was triggered by Korean IME.
* Our fallback mode does not work well with IE's Korean IME,
* so just use native composition events when Korean IME is used.
* Although CompositionEvent.locale property is deprecated,
* it is available in IE, where our fallback mode is enabled.
*
* @param {object} nativeEvent
* @return {boolean}
*/
function isUsingKoreanIME(nativeEvent) {
return nativeEvent.locale === 'ko';
}
// Track the current IME composition status, if any.
let isComposing = false;
@@ -229,7 +242,7 @@ function extractCompositionEvent(
return null;
}
if (useFallbackCompositionData) {
if (useFallbackCompositionData && !isUsingKoreanIME(nativeEvent)) {
// The current composition is stored statically and must not be
// overwritten while composition continues.
if (!isComposing && eventType === eventTypes.compositionStart) {
@@ -378,7 +391,9 @@ function getFallbackBeforeInputChars(topLevelType: TopLevelType, nativeEvent) {
}
return null;
case TOP_COMPOSITION_END:
return useFallbackCompositionData ? null : nativeEvent.data;
return useFallbackCompositionData && !isUsingKoreanIME(nativeEvent)
? null
: nativeEvent.data;
default:
return null;
}

View File

@@ -11,7 +11,7 @@ import {enqueueStateRestore} from 'events/ReactControlledComponent';
import {batchedUpdates} from 'events/ReactGenericBatching';
import SyntheticEvent from 'events/SyntheticEvent';
import isTextInputElement from 'shared/isTextInputElement';
import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment';
import {canUseDOM} from 'shared/ExecutionEnvironment';
import {
TOP_BLUR,
@@ -119,7 +119,7 @@ function getTargetInstForChangeEvent(topLevelType, targetInst) {
* SECTION: handle `input` event
*/
let isInputEventSupported = false;
if (ExecutionEnvironment.canUseDOM) {
if (canUseDOM) {
// IE9 claims to support the input event but fails to trigger it when
// deleting text, so we ignore its input events.
isInputEventSupported =
@@ -231,14 +231,8 @@ function getTargetInstForInputOrChangeEvent(topLevelType, targetInst) {
}
}
function handleControlledInputBlur(inst, node) {
// TODO: In IE, inst is occasionally null. Why?
if (inst == null) {
return;
}
// Fiber and ReactDOM keep wrapper state in separate places
let state = inst._wrapperState || node._wrapperState;
function handleControlledInputBlur(node) {
let state = node._wrapperState;
if (!state || !state.controlled || node.type !== 'number') {
return;
@@ -303,7 +297,7 @@ const ChangeEventPlugin = {
// When blurring, set the value attribute for number inputs
if (topLevelType === TOP_BLUR) {
handleControlledInputBlur(targetInst, targetNode);
handleControlledInputBlur(targetNode);
}
},
};

View File

@@ -6,11 +6,10 @@
*/
import {accumulateTwoPhaseDispatches} from 'events/EventPropagators';
import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment';
import {canUseDOM} from 'shared/ExecutionEnvironment';
import SyntheticEvent from 'events/SyntheticEvent';
import isTextInputElement from 'shared/isTextInputElement';
import getActiveElement from 'fbjs/lib/getActiveElement';
import shallowEqual from 'fbjs/lib/shallowEqual';
import shallowEqual from 'shared/shallowEqual';
import {
TOP_BLUR,
@@ -23,14 +22,13 @@ import {
TOP_SELECTION_CHANGE,
} from './DOMTopLevelEventTypes';
import {isListeningToAllDependencies} from './ReactBrowserEventEmitter';
import getActiveElement from '../client/getActiveElement';
import {getNodeFromInstance} from '../client/ReactDOMComponentTree';
import * as ReactInputSelection from '../client/ReactInputSelection';
import {DOCUMENT_NODE} from '../shared/HTMLNodeType';
const skipSelectionChangeEvent =
ExecutionEnvironment.canUseDOM &&
'documentMode' in document &&
document.documentMode <= 11;
canUseDOM && 'documentMode' in document && document.documentMode <= 11;
const eventTypes = {
select: {

View File

@@ -22,7 +22,7 @@ import {accumulateTwoPhaseDispatches} from 'events/EventPropagators';
import SyntheticEvent from 'events/SyntheticEvent';
import * as DOMTopLevelEventTypes from './DOMTopLevelEventTypes';
import warning from 'fbjs/lib/warning';
import warning from 'shared/warning';
import SyntheticAnimationEvent from './SyntheticAnimationEvent';
import SyntheticClipboardEvent from './SyntheticClipboardEvent';

View File

@@ -8,6 +8,12 @@
import SyntheticUIEvent from './SyntheticUIEvent';
import getEventModifierState from './getEventModifierState';
let previousScreenX = 0;
let previousScreenY = 0;
// Use flags to signal movementX/Y has already been set
let isMovementXSet = false;
let isMovementYSet = false;
/**
* @interface MouseEvent
* @see http://www.w3.org/TR/DOM-Level-3-Events/
@@ -34,6 +40,36 @@ const SyntheticMouseEvent = SyntheticUIEvent.extend({
: event.fromElement)
);
},
movementX: function(event) {
if ('movementX' in event) {
return event.movementX;
}
const screenX = previousScreenX;
previousScreenX = event.screenX;
if (!isMovementXSet) {
isMovementXSet = true;
return 0;
}
return event.type === 'mousemove' ? event.screenX - screenX : 0;
},
movementY: function(event) {
if ('movementY' in event) {
return event.movementY;
}
const screenY = previousScreenY;
previousScreenY = event.screenY;
if (!isMovementYSet) {
isMovementYSet = true;
return 0;
}
return event.type === 'mousemove' ? event.screenY - screenY : 0;
},
});
export default SyntheticMouseEvent;

View File

@@ -8,7 +8,6 @@
*/
import {accumulateTwoPhaseDispatches} from 'events/EventPropagators';
import TouchEventUtils from 'fbjs/lib/TouchEventUtils';
import type {TopLevelType} from 'events/TopLevelEventTypes';
import {
@@ -85,11 +84,24 @@ const Axis: AxisType = {
y: {page: 'pageY', client: 'clientY', envScroll: 'currentPageScrollTop'},
};
function extractSingleTouch(nativeEvent) {
// $FlowFixMe: figure out why this is missing
const touches = nativeEvent.touches;
// $FlowFixMe: figure out why this is missing
const changedTouches = nativeEvent.changedTouches;
const hasTouches = touches && touches.length > 0;
const hasChangedTouches = changedTouches && changedTouches.length > 0;
return !hasTouches && hasChangedTouches
? changedTouches[0]
: hasTouches ? touches[0] : nativeEvent;
}
function getAxisCoordOfEvent(
axis: AxisCoordinateData,
nativeEvent: _Touch,
): number {
const singleTouch = TouchEventUtils.extractSingleTouch(nativeEvent);
const singleTouch = extractSingleTouch(nativeEvent);
if (singleTouch) {
return singleTouch[axis.page];
}

View File

@@ -200,9 +200,9 @@ describe('BeforeInputEventPlugin', () => {
spyOnBeforeInput,
spyOnCompositionStart,
}) => {
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
expect(beforeInputEvent).toBeNull();
expect(spyOnCompositionStart.mock.calls.length).toBe(1);
expect(spyOnCompositionStart).toHaveBeenCalledTimes(1);
expect(compositionStartEvent.type).toBe('compositionstart');
expect(compositionStartEvent.data).toBe('test');
},
@@ -214,116 +214,116 @@ describe('BeforeInputEventPlugin', () => {
spyOnBeforeInput,
spyOnCompositionUpdate,
}) => {
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
expect(beforeInputEvent).toBeNull();
expect(spyOnCompositionUpdate.mock.calls.length).toBe(1);
expect(spyOnCompositionUpdate).toHaveBeenCalledTimes(1);
expect(compositionUpdateEvent.type).toBe('compositionupdate');
expect(compositionUpdateEvent.data).toBe('test string');
},
},
{
run: ({beforeInputEvent, spyOnBeforeInput}) => {
expect(spyOnBeforeInput.mock.calls.length).toBe(1);
expect(spyOnBeforeInput).toHaveBeenCalledTimes(1);
expect(beforeInputEvent.type).toBe('compositionend');
expect(beforeInputEvent.data).toBe('test string 3');
},
},
{
run: ({beforeInputEvent, spyOnBeforeInput}) => {
expect(spyOnBeforeInput.mock.calls.length).toBe(1);
expect(spyOnBeforeInput).toHaveBeenCalledTimes(1);
expect(beforeInputEvent.type).toBe('textInput');
expect(beforeInputEvent.data).toBe('abcß');
},
},
{
run: ({beforeInputEvent, spyOnBeforeInput}) => {
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
expect(beforeInputEvent).toBeNull();
},
},
{
run: ({beforeInputEvent, spyOnBeforeInput}) => {
expect(spyOnBeforeInput.mock.calls.length).toBe(1);
expect(spyOnBeforeInput).toHaveBeenCalledTimes(1);
expect(beforeInputEvent.type).toBe('keypress');
expect(beforeInputEvent.data).toBe(' ');
},
},
{
run: ({beforeInputEvent, spyOnBeforeInput}) => {
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
expect(beforeInputEvent).toBeNull();
},
},
{
run: ({beforeInputEvent, spyOnBeforeInput}) => {
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
expect(beforeInputEvent).toBeNull();
},
},
{
run: ({beforeInputEvent, spyOnBeforeInput}) => {
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
expect(beforeInputEvent).toBeNull();
},
},
{
run: ({beforeInputEvent, spyOnBeforeInput}) => {
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
expect(beforeInputEvent).toBeNull();
},
},
{
run: ({beforeInputEvent, spyOnBeforeInput}) => {
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
expect(beforeInputEvent).toBeNull();
},
},
{
run: ({beforeInputEvent, spyOnBeforeInput}) => {
expect(spyOnBeforeInput.mock.calls.length).toBe(1);
expect(spyOnBeforeInput).toHaveBeenCalledTimes(1);
expect(beforeInputEvent.type).toBe('textInput');
expect(beforeInputEvent.data).toBe('\uD83D\uDE0A');
},
},
{
run: ({beforeInputEvent, spyOnBeforeInput}) => {
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
expect(beforeInputEvent).toBeNull();
},
},
{
run: ({beforeInputEvent, spyOnBeforeInput}) => {
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
expect(beforeInputEvent).toBeNull();
},
},
{
run: ({beforeInputEvent, spyOnBeforeInput}) => {
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
expect(beforeInputEvent).toBeNull();
},
},
{
run: ({beforeInputEvent, spyOnBeforeInput}) => {
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
expect(beforeInputEvent).toBeNull();
},
},
{
run: ({beforeInputEvent, spyOnBeforeInput}) => {
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
expect(beforeInputEvent).toBeNull();
},
},
{
run: ({beforeInputEvent, spyOnBeforeInput}) => {
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
expect(beforeInputEvent).toBeNull();
},
},
{
run: ({beforeInputEvent, spyOnBeforeInput}) => {
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
expect(beforeInputEvent).toBeNull();
},
},
@@ -334,119 +334,119 @@ describe('BeforeInputEventPlugin', () => {
assertions: [
{
run: ({beforeInputEvent, spyOnBeforeInput}) => {
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
expect(beforeInputEvent).toBeNull();
},
},
{
run: ({beforeInputEvent, spyOnBeforeInput}) => {
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
expect(beforeInputEvent).toBeNull();
},
},
{
run: ({beforeInputEvent, spyOnBeforeInput}) => {
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
expect(beforeInputEvent).toBeNull();
},
},
{
run: ({beforeInputEvent, spyOnBeforeInput}) => {
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
expect(beforeInputEvent).toBeNull();
},
},
{
run: ({beforeInputEvent, spyOnBeforeInput}) => {
expect(spyOnBeforeInput.mock.calls.length).toBe(1);
expect(spyOnBeforeInput).toHaveBeenCalledTimes(1);
expect(beforeInputEvent.type).toBe('keypress');
expect(beforeInputEvent.data).toBe('a');
},
},
{
run: ({beforeInputEvent, spyOnBeforeInput}) => {
expect(spyOnBeforeInput.mock.calls.length).toBe(1);
expect(spyOnBeforeInput).toHaveBeenCalledTimes(1);
expect(beforeInputEvent.type).toBe('keypress');
expect(beforeInputEvent.data).toBe(' ');
},
},
{
run: ({beforeInputEvent, spyOnBeforeInput}) => {
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
expect(beforeInputEvent).toBeNull();
},
},
{
run: ({beforeInputEvent, spyOnBeforeInput}) => {
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
expect(beforeInputEvent).toBeNull();
},
},
{
run: ({beforeInputEvent, spyOnBeforeInput}) => {
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
expect(beforeInputEvent).toBeNull();
},
},
{
run: ({beforeInputEvent, spyOnBeforeInput}) => {
expect(spyOnBeforeInput.mock.calls.length).toBe(1);
expect(spyOnBeforeInput).toHaveBeenCalledTimes(1);
expect(beforeInputEvent.type).toBe('keypress');
expect(beforeInputEvent.data).toBe('c');
},
},
{
run: ({beforeInputEvent, spyOnBeforeInput}) => {
expect(spyOnBeforeInput.mock.calls.length).toBe(1);
expect(spyOnBeforeInput).toHaveBeenCalledTimes(1);
expect(beforeInputEvent.type).toBe('keypress');
expect(beforeInputEvent.data).toBe('\uD83D\uDE0A');
},
},
{
run: ({beforeInputEvent, spyOnBeforeInput}) => {
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
expect(beforeInputEvent).toBeNull();
},
},
{
run: ({beforeInputEvent, spyOnBeforeInput}) => {
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
expect(beforeInputEvent).toBeNull();
},
},
{
run: ({beforeInputEvent, spyOnBeforeInput}) => {
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
expect(beforeInputEvent).toBeNull();
},
},
{
run: ({beforeInputEvent, spyOnBeforeInput}) => {
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
expect(beforeInputEvent).toBeNull();
},
},
{
run: ({beforeInputEvent, spyOnBeforeInput}) => {
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
expect(beforeInputEvent).toBeNull();
},
},
{
run: ({beforeInputEvent, spyOnBeforeInput}) => {
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
expect(beforeInputEvent).toBeNull();
},
},
{
run: ({beforeInputEvent, spyOnBeforeInput}) => {
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
expect(beforeInputEvent).toBeNull();
},
},
{
run: ({beforeInputEvent, spyOnBeforeInput}) => {
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
expect(beforeInputEvent).toBeNull();
},
},
@@ -457,122 +457,122 @@ describe('BeforeInputEventPlugin', () => {
assertions: [
{
run: ({beforeInputEvent, spyOnBeforeInput}) => {
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
expect(beforeInputEvent).toBeNull();
},
},
{
run: ({beforeInputEvent, spyOnBeforeInput}) => {
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
expect(beforeInputEvent).toBeNull();
},
},
{
run: ({beforeInputEvent, spyOnBeforeInput}) => {
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
expect(beforeInputEvent).toBeNull();
},
},
{
run: ({beforeInputEvent, spyOnBeforeInput}) => {
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
expect(beforeInputEvent).toBeNull();
},
},
{
run: ({beforeInputEvent, spyOnBeforeInput}) => {
expect(spyOnBeforeInput.mock.calls.length).toBe(1);
expect(spyOnBeforeInput).toHaveBeenCalledTimes(1);
expect(beforeInputEvent.type).toBe('keypress');
expect(beforeInputEvent.data).toBe('a');
},
},
{
run: ({beforeInputEvent, spyOnBeforeInput}) => {
expect(spyOnBeforeInput.mock.calls.length).toBe(1);
expect(spyOnBeforeInput).toHaveBeenCalledTimes(1);
expect(beforeInputEvent.type).toBe('keypress');
expect(beforeInputEvent.data).toBe(' ');
},
},
{
run: ({beforeInputEvent, spyOnBeforeInput}) => {
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
expect(beforeInputEvent).toBeNull();
},
},
{
run: ({beforeInputEvent, spyOnBeforeInput}) => {
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
expect(beforeInputEvent).toBeNull();
},
},
{
run: ({beforeInputEvent, spyOnBeforeInput}) => {
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
expect(beforeInputEvent).toBeNull();
},
},
{
run: ({beforeInputEvent, spyOnBeforeInput}) => {
expect(spyOnBeforeInput.mock.calls.length).toBe(1);
expect(spyOnBeforeInput).toHaveBeenCalledTimes(1);
expect(beforeInputEvent.type).toBe('keypress');
expect(beforeInputEvent.data).toBe('c');
},
},
{
run: ({beforeInputEvent, spyOnBeforeInput}) => {
expect(spyOnBeforeInput.mock.calls.length).toBe(1);
expect(spyOnBeforeInput).toHaveBeenCalledTimes(1);
expect(beforeInputEvent.type).toBe('keypress');
expect(beforeInputEvent.data).toBe('\uD83D\uDE0A');
},
},
{
run: ({beforeInputEvent, spyOnBeforeInput}) => {
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
expect(beforeInputEvent).toBeNull();
},
},
{
run: ({beforeInputEvent, spyOnBeforeInput}) => {
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
expect(beforeInputEvent).toBeNull();
},
},
{
run: ({beforeInputEvent, spyOnBeforeInput}) => {
expect(spyOnBeforeInput.mock.calls.length).toBe(1);
expect(spyOnBeforeInput).toHaveBeenCalledTimes(1);
expect(beforeInputEvent.type).toBe('keydown');
expect(beforeInputEvent.data).toBe('bar');
},
},
{
run: ({beforeInputEvent, spyOnBeforeInput}) => {
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
expect(beforeInputEvent).toBeNull();
},
},
{
run: ({beforeInputEvent, spyOnBeforeInput}) => {
expect(spyOnBeforeInput.mock.calls.length).toBe(1);
expect(spyOnBeforeInput).toHaveBeenCalledTimes(1);
expect(beforeInputEvent.type).toBe('keyup');
expect(beforeInputEvent.data).toBe('BAR');
},
},
{
run: ({beforeInputEvent, spyOnBeforeInput}) => {
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
expect(beforeInputEvent).toBeNull();
},
},
{
run: ({beforeInputEvent, spyOnBeforeInput}) => {
expect(spyOnBeforeInput.mock.calls.length).toBe(1);
expect(spyOnBeforeInput).toHaveBeenCalledTimes(1);
expect(beforeInputEvent.type).toBe('keypress');
expect(beforeInputEvent.data).toBe('Bar');
},
},
{
run: ({beforeInputEvent, spyOnBeforeInput}) => {
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
expect(beforeInputEvent).toBeNull();
},
},
@@ -583,120 +583,120 @@ describe('BeforeInputEventPlugin', () => {
assertions: [
{
run: ({beforeInputEvent, spyOnBeforeInput}) => {
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
expect(beforeInputEvent).toBeNull();
},
},
{
run: ({beforeInputEvent, spyOnBeforeInput}) => {
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
expect(beforeInputEvent).toBeNull();
},
},
{
run: ({beforeInputEvent, spyOnBeforeInput}) => {
expect(spyOnBeforeInput.mock.calls.length).toBe(1);
expect(spyOnBeforeInput).toHaveBeenCalledTimes(1);
expect(beforeInputEvent.type).toBe('compositionend');
expect(beforeInputEvent.data).toBe('test string 3');
},
},
{
run: ({beforeInputEvent, spyOnBeforeInput}) => {
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
expect(beforeInputEvent).toBeNull();
},
},
{
run: ({beforeInputEvent, spyOnBeforeInput}) => {
expect(spyOnBeforeInput.mock.calls.length).toBe(1);
expect(spyOnBeforeInput).toHaveBeenCalledTimes(1);
expect(beforeInputEvent.type).toBe('keypress');
expect(beforeInputEvent.data).toBe('a');
},
},
{
run: ({beforeInputEvent, spyOnBeforeInput}) => {
expect(spyOnBeforeInput.mock.calls.length).toBe(1);
expect(spyOnBeforeInput).toHaveBeenCalledTimes(1);
expect(beforeInputEvent.type).toBe('keypress');
expect(beforeInputEvent.data).toBe(' ');
},
},
{
run: ({beforeInputEvent, spyOnBeforeInput}) => {
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
expect(beforeInputEvent).toBeNull();
},
},
{
run: ({beforeInputEvent, spyOnBeforeInput}) => {
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
expect(beforeInputEvent).toBeNull();
},
},
{
run: ({beforeInputEvent, spyOnBeforeInput}) => {
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
expect(beforeInputEvent).toBeNull();
},
},
{
run: ({beforeInputEvent, spyOnBeforeInput}) => {
expect(spyOnBeforeInput.mock.calls.length).toBe(1);
expect(spyOnBeforeInput).toHaveBeenCalledTimes(1);
expect(beforeInputEvent.type).toBe('keypress');
expect(beforeInputEvent.data).toBe('c');
},
},
{
run: ({beforeInputEvent, spyOnBeforeInput}) => {
expect(spyOnBeforeInput.mock.calls.length).toBe(1);
expect(spyOnBeforeInput).toHaveBeenCalledTimes(1);
expect(beforeInputEvent.type).toBe('keypress');
expect(beforeInputEvent.data).toBe('\uD83D\uDE0A');
},
},
{
run: ({beforeInputEvent, spyOnBeforeInput}) => {
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
expect(beforeInputEvent).toBeNull();
},
},
{
run: ({beforeInputEvent, spyOnBeforeInput}) => {
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
expect(beforeInputEvent).toBeNull();
},
},
{
run: ({beforeInputEvent, spyOnBeforeInput}) => {
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
expect(beforeInputEvent).toBeNull();
},
},
{
run: ({beforeInputEvent, spyOnBeforeInput}) => {
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
expect(beforeInputEvent).toBeNull();
},
},
{
run: ({beforeInputEvent, spyOnBeforeInput}) => {
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
expect(beforeInputEvent).toBeNull();
},
},
{
run: ({beforeInputEvent, spyOnBeforeInput}) => {
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
expect(beforeInputEvent).toBeNull();
},
},
{
run: ({beforeInputEvent, spyOnBeforeInput}) => {
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
expect(beforeInputEvent).toBeNull();
},
},
{
run: ({beforeInputEvent, spyOnBeforeInput}) => {
expect(spyOnBeforeInput.mock.calls.length).toBe(0);
expect(spyOnBeforeInput).toHaveBeenCalledTimes(0);
expect(beforeInputEvent).toBeNull();
},
},

View File

@@ -72,7 +72,7 @@ describe('SelectEventPlugin', () => {
// Verify that it doesn't get "stuck" waiting for
// a `mouseup` event that it has "missed" because
// a top-level listener didn't exist yet.
expect(select.mock.calls.length).toBe(1);
expect(select).toHaveBeenCalledTimes(1);
});
it('should fire `onSelect` when a listener is present', () => {
@@ -95,17 +95,17 @@ describe('SelectEventPlugin', () => {
cancelable: true,
});
node.dispatchEvent(nativeEvent);
expect(select.mock.calls.length).toBe(0);
expect(select).toHaveBeenCalledTimes(0);
nativeEvent = new MouseEvent('mousedown', {
bubbles: true,
cancelable: true,
});
node.dispatchEvent(nativeEvent);
expect(select.mock.calls.length).toBe(0);
expect(select).toHaveBeenCalledTimes(0);
nativeEvent = new MouseEvent('mouseup', {bubbles: true, cancelable: true});
node.dispatchEvent(nativeEvent);
expect(select.mock.calls.length).toBe(1);
expect(select).toHaveBeenCalledTimes(1);
});
});

View File

@@ -12,23 +12,25 @@
describe('SimpleEventPlugin', function() {
let React;
let ReactDOM;
let ReactTestUtils;
let ReactFeatureFlags;
let onClick;
let container;
function expectClickThru(element) {
ReactTestUtils.SimulateNative.click(ReactDOM.findDOMNode(element));
expect(onClick.mock.calls.length).toBe(1);
element.click();
expect(onClick).toHaveBeenCalledTimes(1);
}
function expectNoClickThru(element) {
ReactTestUtils.SimulateNative.click(ReactDOM.findDOMNode(element));
expect(onClick.mock.calls.length).toBe(0);
element.click();
expect(onClick).toHaveBeenCalledTimes(0);
}
function mounted(element) {
element = ReactTestUtils.renderIntoDocument(element);
container = document.createElement('div');
document.body.appendChild(container);
element = ReactDOM.render(element, container);
return element;
}
@@ -60,76 +62,81 @@ describe('SimpleEventPlugin', function() {
jest.resetModules();
React = require('react');
ReactDOM = require('react-dom');
ReactTestUtils = require('react-dom/test-utils');
onClick = jest.fn();
});
afterEach(() => {
if (container && document.body.contains(container)) {
document.body.removeChild(container);
container = null;
}
});
it('A non-interactive tags click when disabled', function() {
const element = <div onClick={onClick} />;
expectClickThru(mounted(element));
});
it('A non-interactive tags clicks bubble when disabled', function() {
const element = ReactTestUtils.renderIntoDocument(
const element = mounted(
<div onClick={onClick}>
<div />
</div>,
);
const child = ReactDOM.findDOMNode(element).firstChild;
ReactTestUtils.SimulateNative.click(child);
expect(onClick.mock.calls.length).toBe(1);
const child = element.firstChild;
child.click();
expect(onClick).toHaveBeenCalledTimes(1);
});
it('does not register a click when clicking a child of a disabled element', function() {
const element = ReactTestUtils.renderIntoDocument(
const element = mounted(
<button onClick={onClick} disabled={true}>
<span />
</button>,
);
const child = ReactDOM.findDOMNode(element).querySelector('span');
const child = element.querySelector('span');
ReactTestUtils.SimulateNative.click(child);
expect(onClick.mock.calls.length).toBe(0);
child.click();
expect(onClick).toHaveBeenCalledTimes(0);
});
it('triggers click events for children of disabled elements', function() {
const element = ReactTestUtils.renderIntoDocument(
const element = mounted(
<button disabled={true}>
<span onClick={onClick} />
</button>,
);
const child = ReactDOM.findDOMNode(element).querySelector('span');
const child = element.querySelector('span');
ReactTestUtils.SimulateNative.click(child);
expect(onClick.mock.calls.length).toBe(1);
child.click();
expect(onClick).toHaveBeenCalledTimes(1);
});
it('triggers parent captured click events when target is a child of a disabled elements', function() {
const element = ReactTestUtils.renderIntoDocument(
const element = mounted(
<div onClickCapture={onClick}>
<button disabled={true}>
<span />
</button>
</div>,
);
const child = ReactDOM.findDOMNode(element).querySelector('span');
const child = element.querySelector('span');
ReactTestUtils.SimulateNative.click(child);
expect(onClick.mock.calls.length).toBe(1);
child.click();
expect(onClick).toHaveBeenCalledTimes(1);
});
it('triggers captured click events for children of disabled elements', function() {
const element = ReactTestUtils.renderIntoDocument(
const element = mounted(
<button disabled={true}>
<span onClickCapture={onClick} />
</button>,
);
const child = ReactDOM.findDOMNode(element).querySelector('span');
const child = element.querySelector('span');
ReactTestUtils.SimulateNative.click(child);
expect(onClick.mock.calls.length).toBe(1);
child.click();
expect(onClick).toHaveBeenCalledTimes(1);
});
['button', 'input', 'select', 'textarea'].forEach(function(tagName) {
@@ -152,7 +159,8 @@ describe('SimpleEventPlugin', function() {
});
it('should forward clicks when it becomes not disabled', () => {
const container = document.createElement('div');
container = document.createElement('div');
document.body.appendChild(container);
let element = ReactDOM.render(
React.createElement(tagName, {onClick: onClick, disabled: true}),
container,
@@ -165,7 +173,8 @@ describe('SimpleEventPlugin', function() {
});
it('should not forward clicks when it becomes disabled', () => {
const container = document.createElement('div');
container = document.createElement('div');
document.body.appendChild(container);
let element = ReactDOM.render(
React.createElement(tagName, {onClick: onClick}),
container,
@@ -178,7 +187,8 @@ describe('SimpleEventPlugin', function() {
});
it('should work correctly if the listener is changed', () => {
const container = document.createElement('div');
container = document.createElement('div');
document.body.appendChild(container);
let element = ReactDOM.render(
React.createElement(tagName, {onClick: onClick, disabled: true}),
container,
@@ -193,7 +203,7 @@ describe('SimpleEventPlugin', function() {
});
it('batches updates that occur as a result of a nested event dispatch', () => {
const container = document.createElement('div');
container = document.createElement('div');
document.body.appendChild(container);
let ops = [];
@@ -251,7 +261,7 @@ describe('SimpleEventPlugin', function() {
});
it('flushes pending interactive work before extracting event handler', () => {
const container = document.createElement('div');
container = document.createElement('div');
const root = ReactDOM.unstable_createRoot(container);
document.body.appendChild(container);
@@ -331,7 +341,7 @@ describe('SimpleEventPlugin', function() {
});
it('end result of many interactive updates is deterministic', () => {
const container = document.createElement('div');
container = document.createElement('div');
const root = ReactDOM.unstable_createRoot(container);
document.body.appendChild(container);
@@ -386,7 +396,7 @@ describe('SimpleEventPlugin', function() {
});
it('flushes lowest pending interactive priority', () => {
const container = document.createElement('div');
container = document.createElement('div');
document.body.appendChild(container);
let button;
@@ -459,7 +469,7 @@ describe('SimpleEventPlugin', function() {
// See http://www.quirksmode.org/blog/archives/2010/09/click_event_del.html
it('does not add a local click to interactive elements', function() {
const container = document.createElement('div');
container = document.createElement('div');
ReactDOM.render(<button onClick={onClick} />, container);
@@ -467,11 +477,11 @@ describe('SimpleEventPlugin', function() {
node.dispatchEvent(new MouseEvent('click'));
expect(onClick.mock.calls.length).toBe(0);
expect(onClick).toHaveBeenCalledTimes(0);
});
it('adds a local click listener to non-interactive elements', function() {
const container = document.createElement('div');
container = document.createElement('div');
ReactDOM.render(<div onClick={onClick} />, container);
@@ -479,7 +489,7 @@ describe('SimpleEventPlugin', function() {
node.dispatchEvent(new MouseEvent('click'));
expect(onClick.mock.calls.length).toBe(0);
expect(onClick).toHaveBeenCalledTimes(0);
});
});
});

View File

@@ -11,7 +11,6 @@
let React;
let ReactDOM;
let ReactTestUtils;
describe('SyntheticEvent', () => {
let container;
@@ -19,7 +18,6 @@ describe('SyntheticEvent', () => {
beforeEach(() => {
React = require('react');
ReactDOM = require('react-dom');
ReactTestUtils = require('react-dom/test-utils');
container = document.createElement('div');
document.body.appendChild(container);
@@ -245,17 +243,13 @@ describe('SyntheticEvent', () => {
expect(expectedCount).toBe(1);
});
// TODO: reenable this test. We are currently silencing these warnings when
// using TestUtils.Simulate to avoid spurious warnings that result from the
// way we simulate events.
xit('should properly log warnings when events simulated with rendered components', () => {
it('should properly log warnings when events simulated with rendered components', () => {
let event;
const element = document.createElement('div');
function assignEvent(e) {
event = e;
}
const node = ReactDOM.render(<div onClick={assignEvent} />, element);
ReactTestUtils.Simulate.click(ReactDOM.findDOMNode(node));
const node = ReactDOM.render(<div onClick={assignEvent} />, container);
node.click();
// access a property to cause the warning
expect(() => {

View File

@@ -0,0 +1,80 @@
/**
* 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 ReactDOM;
describe('SyntheticMouseEvent', () => {
let container;
beforeEach(() => {
React = require('react');
ReactDOM = require('react-dom');
// The container has to be attached for events to fire.
container = document.createElement('div');
document.body.appendChild(container);
});
afterEach(() => {
document.body.removeChild(container);
container = null;
});
it('should only use values from movementX/Y when event type is mousemove', () => {
const events = [];
const onMouseMove = event => {
events.push(event.movementX);
};
const onMouseDown = event => {
events.push(event.movementX);
};
const node = ReactDOM.render(
<div onMouseMove={onMouseMove} onMouseDown={onMouseDown} />,
container,
);
let event = new MouseEvent('mousemove', {
relatedTarget: null,
bubbles: true,
screenX: 2,
screenY: 2,
});
node.dispatchEvent(event);
event = new MouseEvent('mousemove', {
relatedTarget: null,
bubbles: true,
screenX: 8,
screenY: 8,
});
node.dispatchEvent(event);
// Now trigger a mousedown event to see if movementX has changed back to 0
event = new MouseEvent('mousedown', {
relatedTarget: null,
bubbles: true,
screenX: 25,
screenY: 65,
});
node.dispatchEvent(event);
expect(events.length).toBe(3);
expect(events[0]).toBe(0);
expect(events[1]).toBe(6);
expect(events[2]).toBe(0); // mousedown event should have movementX at 0
});
});

View File

@@ -28,6 +28,27 @@ let PARENT;
let CHILD;
let putListener;
let container;
function touchStart(element, touchEventInit) {
element.dispatchEvent(
new TouchEvent('touchstart', {
bubbles: true,
cancelable: true,
...touchEventInit,
}),
);
}
function touchEnd(element, touchEventInit) {
element.dispatchEvent(
new TouchEvent('touchend', {
bubbles: true,
cancelable: true,
...touchEventInit,
}),
);
}
describe('TapEventPlugin', () => {
beforeEach(() => {
@@ -40,7 +61,8 @@ describe('TapEventPlugin', () => {
ReactTestUtils = require('react-dom/test-utils');
TapEventPlugin = require('react-dom/src/events/TapEventPlugin').default;
const container = document.createElement('div');
container = document.createElement('div');
document.body.appendChild(container);
const GRANDPARENT_PROPS = {};
const PARENT_PROPS = {};
@@ -91,6 +113,11 @@ describe('TapEventPlugin', () => {
});
});
afterEach(() => {
document.body.removeChild(container);
container = null;
});
/**
* The onTouchTap inject is ignore future,
* we should always test the deprecated message correct.
@@ -99,42 +126,24 @@ describe('TapEventPlugin', () => {
it('should infer onTouchTap from a touchStart/End', () => {
putListener(CHILD, ON_TOUCH_TAP_KEY, recordID.bind(null, CHILD));
ReactTestUtils.SimulateNative.touchStart(
CHILD,
ReactTestUtils.nativeTouchData(0, 0),
);
ReactTestUtils.SimulateNative.touchEnd(
CHILD,
ReactTestUtils.nativeTouchData(0, 0),
);
touchStart(CHILD, ReactTestUtils.nativeTouchData(0, 0));
touchEnd(CHILD, ReactTestUtils.nativeTouchData(0, 0));
expect(idCallOrder.length).toBe(1);
expect(idCallOrder[0]).toBe(CHILD);
});
it('should infer onTouchTap from when dragging below threshold', () => {
putListener(CHILD, ON_TOUCH_TAP_KEY, recordID.bind(null, CHILD));
ReactTestUtils.SimulateNative.touchStart(
CHILD,
ReactTestUtils.nativeTouchData(0, 0),
);
ReactTestUtils.SimulateNative.touchEnd(
CHILD,
ReactTestUtils.nativeTouchData(0, tapMoveThreshold - 1),
);
touchStart(CHILD, ReactTestUtils.nativeTouchData(0, 0));
touchEnd(CHILD, ReactTestUtils.nativeTouchData(0, tapMoveThreshold - 1));
expect(idCallOrder.length).toBe(1);
expect(idCallOrder[0]).toBe(CHILD);
});
it('should not onTouchTap from when dragging beyond threshold', () => {
putListener(CHILD, ON_TOUCH_TAP_KEY, recordID.bind(null, CHILD));
ReactTestUtils.SimulateNative.touchStart(
CHILD,
ReactTestUtils.nativeTouchData(0, 0),
);
ReactTestUtils.SimulateNative.touchEnd(
CHILD,
ReactTestUtils.nativeTouchData(0, tapMoveThreshold + 1),
);
touchStart(CHILD, ReactTestUtils.nativeTouchData(0, 0));
touchEnd(CHILD, ReactTestUtils.nativeTouchData(0, tapMoveThreshold + 1));
expect(idCallOrder.length).toBe(0);
});
@@ -146,14 +155,8 @@ describe('TapEventPlugin', () => {
ON_TOUCH_TAP_KEY,
recordID.bind(null, GRANDPARENT),
);
ReactTestUtils.SimulateNative.touchStart(
CHILD,
ReactTestUtils.nativeTouchData(0, 0),
);
ReactTestUtils.SimulateNative.touchEnd(
CHILD,
ReactTestUtils.nativeTouchData(0, 0),
);
touchStart(CHILD, ReactTestUtils.nativeTouchData(0, 0));
touchEnd(CHILD, ReactTestUtils.nativeTouchData(0, 0));
expect(idCallOrder.length).toBe(3);
expect(idCallOrder[0] === CHILD).toBe(true);
expect(idCallOrder[1] === PARENT).toBe(true);

View File

@@ -15,7 +15,9 @@ import {TEXT_NODE} from '../shared/HTMLNodeType';
* @return {DOMEventTarget} Target node.
*/
function getEventTarget(nativeEvent) {
let target = nativeEvent.target || window;
// Fallback to nativeEvent.srcElement for IE9
// https://github.com/facebook/react/issues/12506
let target = nativeEvent.target || nativeEvent.srcElement || window;
// Normalize SVG <use> element events #4963
if (target.correspondingUseElement) {

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';
/**
* Generate a mapping of standard vendor prefixes using the defined style property and event name.
@@ -49,7 +49,7 @@ let style = {};
/**
* Bootstrap if a DOM exists.
*/
if (ExecutionEnvironment.canUseDOM) {
if (canUseDOM) {
style = document.createElement('div').style;
// On some platforms, in particular some releases of Android 4.x,

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';
/**
* Checks if an event is supported in the current execution environment.
@@ -22,10 +22,7 @@ import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment';
* @license Modernizr 3.0.0pre (Custom Build) | MIT
*/
function isEventSupported(eventNameSuffix, capture) {
if (
!ExecutionEnvironment.canUseDOM ||
(capture && !('addEventListener' in document))
) {
if (!canUseDOM || (capture && !('addEventListener' in document))) {
return false;
}

View File

@@ -6,7 +6,7 @@
*/
import ReactVersion from 'shared/ReactVersion';
import invariant from 'fbjs/lib/invariant';
import invariant from 'shared/invariant';
import {renderToString, renderToStaticMarkup} from './ReactDOMStringRenderer';

View File

@@ -15,13 +15,9 @@ import type {
} from 'shared/ReactTypes';
import React from 'react';
import emptyFunction from 'fbjs/lib/emptyFunction';
import emptyObject from 'fbjs/lib/emptyObject';
import hyphenateStyleName from 'fbjs/lib/hyphenateStyleName';
import invariant from 'fbjs/lib/invariant';
import invariant from 'shared/invariant';
import lowPriorityWarning from 'shared/lowPriorityWarning';
import memoizeStringOnly from 'fbjs/lib/memoizeStringOnly';
import warning from 'fbjs/lib/warning';
import warning from 'shared/warning';
import checkPropTypes from 'prop-types/checkPropTypes';
import describeComponentFrame from 'shared/describeComponentFrame';
import {ReactDebugCurrentFrame} from 'shared/ReactGlobalSharedState';
@@ -51,6 +47,7 @@ import {
import ReactControlledValuePropTypes from '../shared/ReactControlledValuePropTypes';
import assertValidProps from '../shared/assertValidProps';
import dangerousStyleValue from '../shared/dangerousStyleValue';
import hyphenateStyleName from '../shared/hyphenateStyleName';
import isCustomComponent from '../shared/isCustomComponent';
import omittedCloseTags from '../shared/omittedCloseTags';
import warnValidStyle from '../shared/warnValidStyle';
@@ -67,8 +64,8 @@ const toArray = ((React.Children.toArray: any): toArrayType);
let currentDebugStack;
let currentDebugElementStack;
let getStackAddendum = emptyFunction.thatReturns('');
let describeStackFrame = emptyFunction.thatReturns('');
let getStackAddendum = () => '';
let describeStackFrame = element => '';
let validatePropertiesInDevelopment = (type, props) => {};
let setCurrentDebugStack = (stack: Array<Frame>) => {};
@@ -162,9 +159,15 @@ function validateDangerousTag(tag) {
}
}
const processStyleName = memoizeStringOnly(function(styleName) {
return hyphenateStyleName(styleName);
});
const styleNameCache = {};
const processStyleName = function(styleName) {
if (styleNameCache.hasOwnProperty(styleName)) {
return styleNameCache[styleName];
}
const result = hyphenateStyleName(styleName);
styleNameCache[styleName] = result;
return result;
};
function createMarkupForStyles(styles): string | null {
let serialized = '';
@@ -255,7 +258,10 @@ function flattenTopLevelChildren(children: mixed): FlatReactChildren {
return [fragmentChildElement];
}
function flattenOptionChildren(children: mixed): string {
function flattenOptionChildren(children: mixed): ?string {
if (children === undefined || children === null) {
return children;
}
let content = '';
// Flatten children and warn if they aren't strings or numbers;
// invalid types are ignored.
@@ -280,6 +286,11 @@ function flattenOptionChildren(children: mixed): string {
return content;
}
const emptyObject = {};
if (__DEV__) {
Object.freeze(emptyObject);
}
function maskContext(type, context) {
const contextTypes = type.contextTypes;
if (!contextTypes) {
@@ -641,8 +652,10 @@ class ReactDOMServerRenderer {
previousWasTextNode: boolean;
makeStaticMarkup: boolean;
providerStack: Array<?ReactProvider<any>>;
providerIndex: number;
contextIndex: number;
contextStack: Array<ReactContext<any>>;
contextValueStack: Array<any>;
contextProviderStack: ?Array<ReactProvider<any>>; // DEV-only
constructor(children: mixed, makeStaticMarkup: boolean) {
const flatChildren = flattenTopLevelChildren(children);
@@ -667,37 +680,65 @@ class ReactDOMServerRenderer {
this.makeStaticMarkup = makeStaticMarkup;
// Context (new API)
this.providerStack = []; // Stack of provider objects
this.providerIndex = -1;
this.contextIndex = -1;
this.contextStack = [];
this.contextValueStack = [];
if (__DEV__) {
this.contextProviderStack = [];
}
}
/**
* Note: We use just two stacks regardless of how many context providers you have.
* Providers are always popped in the reverse order to how they were pushed
* so we always know on the way down which provider you'll encounter next on the way up.
* On the way down, we push the current provider, and its context value *before*
* we mutated it, onto the stacks. Therefore, on the way up, we always know which
* provider needs to be "restored" to which value.
* https://github.com/facebook/react/pull/12985#issuecomment-396301248
*/
pushProvider<T>(provider: ReactProvider<T>): void {
this.providerIndex += 1;
this.providerStack[this.providerIndex] = provider;
const index = ++this.contextIndex;
const context: ReactContext<any> = provider.type._context;
const previousValue = context._currentValue;
// Remember which value to restore this context to on our way up.
this.contextStack[index] = context;
this.contextValueStack[index] = previousValue;
if (__DEV__) {
// Only used for push/pop mismatch warnings.
(this.contextProviderStack: any)[index] = provider;
}
// Mutate the current value.
context._currentValue = provider.props.value;
}
popProvider<T>(provider: ReactProvider<T>): void {
const index = this.contextIndex;
if (__DEV__) {
warning(
this.providerIndex > -1 &&
provider === this.providerStack[this.providerIndex],
index > -1 && provider === (this.contextProviderStack: any)[index],
'Unexpected pop.',
);
}
this.providerStack[this.providerIndex] = null;
this.providerIndex -= 1;
const context: ReactContext<any> = provider.type._context;
if (this.providerIndex < 0) {
context._currentValue = context._defaultValue;
} else {
// We assume this type is correct because of the index check above.
const previousProvider: ReactProvider<any> = (this.providerStack[
this.providerIndex
]: any);
context._currentValue = previousProvider.props.value;
const context: ReactContext<any> = this.contextStack[index];
const previousValue = this.contextValueStack[index];
// "Hide" these null assignments from Flow by using `any`
// because conceptually they are deletions--as long as we
// promise to never access values beyond `this.contextIndex`.
this.contextStack[index] = (null: any);
this.contextValueStack[index] = (null: any);
if (__DEV__) {
(this.contextProviderStack: any)[index] = (null: any);
}
this.contextIndex--;
// Restore to the previous value we stored as we were walking down.
context._currentValue = previousValue;
}
read(bytes: number): string | null {

View File

@@ -6,7 +6,7 @@
*/
import dangerousStyleValue from './dangerousStyleValue';
import hyphenateStyleName from 'fbjs/lib/hyphenateStyleName';
import hyphenateStyleName from './hyphenateStyleName';
import warnValidStyle from './warnValidStyle';
/**

View File

@@ -7,7 +7,7 @@
* @flow
*/
import warning from 'fbjs/lib/warning';
import warning from 'shared/warning';
type PropertyType = 0 | 1 | 2 | 3 | 4 | 5 | 6;

View File

@@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
import warning from 'fbjs/lib/warning';
import warning from 'shared/warning';
import {ReactDebugCurrentFrame} from 'shared/ReactGlobalSharedState';
import {ATTRIBUTE_NAME_CHAR} from './DOMProperty';

View File

@@ -6,7 +6,7 @@
*/
import {ReactDebugCurrentFrame} from 'shared/ReactGlobalSharedState';
import warning from 'fbjs/lib/warning';
import warning from 'shared/warning';
let didWarnValueNull = false;

View File

@@ -10,7 +10,7 @@ import {
possibleRegistrationNames,
} from 'events/EventPluginRegistry';
import {ReactDebugCurrentFrame} from 'shared/ReactGlobalSharedState';
import warning from 'fbjs/lib/warning';
import warning from 'shared/warning';
import {
ATTRIBUTE_NAME_CHAR,

View File

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

View File

@@ -8,7 +8,7 @@
*/
import React from 'react';
import invariant from 'fbjs/lib/invariant';
import invariant from 'shared/invariant';
invariant(
React,

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
*/
const uppercasePattern = /([A-Z])/g;
const msPattern = /^ms-/;
/**
* Hyphenates a camelcased CSS property name, for example:
*
* > hyphenateStyleName('backgroundColor')
* < "background-color"
* > hyphenateStyleName('MozTransition')
* < "-moz-transition"
* > hyphenateStyleName('msTransition')
* < "-ms-transition"
*
* As Modernizr suggests (http://modernizr.com/docs/#prefixed), an `ms` prefix
* is converted to `-ms-`.
*/
export default function hyphenateStyleName(name: string): string {
return name
.replace(uppercasePattern, '-$1')
.toLowerCase()
.replace(msPattern, '-ms-');
}

View File

@@ -5,15 +5,15 @@
* LICENSE file in the root directory of this source tree.
*/
import emptyFunction from 'fbjs/lib/emptyFunction';
import camelizeStyleName from 'fbjs/lib/camelizeStyleName';
import warning from 'fbjs/lib/warning';
import warning from 'shared/warning';
let warnValidStyle = emptyFunction;
let warnValidStyle = () => {};
if (__DEV__) {
// 'msTransform' is correct, but the other prefixes should be capitalized
const badVendoredStyleNamePattern = /^(?:webkit|moz|o)[A-Z]/;
const msPattern = /^-ms-/;
const hyphenPattern = /-(.)/g;
// style values shouldn't contain a semicolon
const badStyleValueWithSemicolonPattern = /;\s*$/;
@@ -23,6 +23,12 @@ if (__DEV__) {
let warnedForNaNValue = false;
let warnedForInfinityValue = false;
const camelize = function(string) {
return string.replace(hyphenPattern, function(_, character) {
return character.toUpperCase();
});
};
const warnHyphenatedStyleName = function(name, getStack) {
if (warnedStyleNames.hasOwnProperty(name) && warnedStyleNames[name]) {
return;
@@ -33,7 +39,10 @@ if (__DEV__) {
false,
'Unsupported style property %s. Did you mean %s?%s',
name,
camelizeStyleName(name),
// As Andi Smith suggests
// (http://www.andismith.com/blog/2012/02/modernizr-prefixed/), an `-ms` prefix
// is converted to lowercase `ms`.
camelize(name.replace(msPattern, 'ms-')),
getStack(),
);
};

View File

@@ -16,7 +16,7 @@ import {
HostText,
} from 'shared/ReactTypeOfWork';
import SyntheticEvent from 'events/SyntheticEvent';
import invariant from 'fbjs/lib/invariant';
import invariant from 'shared/invariant';
import * as DOMTopLevelEventTypes from '../events/DOMTopLevelEventTypes';

View File

@@ -1,6 +1,6 @@
{
"name": "react-is",
"version": "16.4.0",
"version": "16.4.1",
"description": "Brand checking of React Elements.",
"main": "index.js",
"repository": "facebook/react",

View File

@@ -90,6 +90,23 @@ export function isForwardRef(object: any) {
export function isFragment(object: any) {
return typeOf(object) === REACT_FRAGMENT_TYPE;
}
export function isNode(object: any) {
switch (typeof object) {
case 'number':
case 'string':
case 'undefined':
return true;
case 'boolean':
return !object;
case 'object':
if (Array.isArray(object)) {
return object.every(isNode);
}
return object === null || isElement(object);
default:
return false;
}
}
export function isProfiler(object: any) {
return typeOf(object) === REACT_PROFILER_TYPE;
}

View File

@@ -85,6 +85,26 @@ describe('ReactIs', () => {
expect(ReactIs.isContextConsumer(<div />)).toBe(false);
});
it('should identify nodes', () => {
expect(ReactIs.isNode(<div />)).toBe(true);
expect(ReactIs.isNode('div')).toBe(true);
expect(ReactIs.isNode(false)).toBe(true);
expect(ReactIs.isNode(true)).toBe(false);
expect(ReactIs.isNode(123)).toBe(true);
expect(ReactIs.isNode(null)).toBe(true);
expect(ReactIs.isNode(undefined)).toBe(true);
expect(ReactIs.isNode([1, 'string', <div />])).toBe(true);
expect(ReactIs.isNode({})).toBe(false);
// It should also identify more specific types as nodes
const Context = React.createContext(false);
expect(ReactIs.isNode(<Context.Provider />)).toBe(true);
expect(ReactIs.isNode(<Context.Consumer />)).toBe(true);
expect(ReactIs.isNode(<React.Fragment />)).toBe(true);
expect(ReactIs.isNode(<React.unstable_AsyncMode />)).toBe(true);
expect(ReactIs.isNode(<React.StrictMode />)).toBe(true);
});
it('should identify context providers', () => {
const Context = React.createContext(false);
expect(ReactIs.typeOf(<Context.Provider />)).toBe(ReactIs.ContextProvider);

View File

@@ -4,9 +4,8 @@
"private": true,
"repository": "facebook/react",
"dependencies": {
"fbjs": "^0.8.16",
"object-assign": "^4.1.1",
"prop-types": "^15.6.0"
"prop-types": "^15.6.2"
},
"peerDependencies": {
"react": "^16.0.0"

View File

@@ -15,7 +15,7 @@ import type {
ReactNativeBaseComponentViewConfig,
} from './ReactNativeTypes';
import invariant from 'fbjs/lib/invariant';
import invariant from 'shared/invariant';
// Modules provided by RN:
import TextInputState from 'TextInputState';
import UIManager from 'UIManager';

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