Merge remote-tracking branch 'upstream/master' into component-tree-hook-dev

This commit is contained in:
Dominic Gannaway 2017-03-06 14:55:33 +00:00
commit b67a29a8ab
40 changed files with 1076 additions and 610 deletions

View File

@ -99,6 +99,10 @@ By default, React includes many helpful warnings. These warnings are very useful
If you use [Create React App](https://github.com/facebookincubator/create-react-app), `npm run build` will create an optimized build of your app in the `build` folder.
#### Brunch
To create an optimized production build with Brunch, just add the `-p` flag to the build command. See the [Brunch docs](http://brunch.io/docs/commands) for more details.
#### Webpack
Include both `DefinePlugin` and `UglifyJsPlugin` into your production Webpack configuration as described in [this guide](https://webpack.js.org/guides/production-build/).

View File

@ -13,6 +13,7 @@ If you're benchmarking or experiencing performance problems in your React apps,
* For Create React App, you need to run `npm run build` and follow the instructions.
* For single-file builds, we offer production-ready `.min.js` versions.
* For Brunch, you need to add the `-p` flag to the `build` command.
* For Browserify, you need to run it with `NODE_ENV=production`.
* For Webpack, you need to add this to plugins in your production config:

View File

@ -145,7 +145,7 @@ function CustomTextInput(props) {
Your first inclination may be to use refs to "make things happen" in your app. If this is the case, take a moment and think more critically about where state should be owned in the component hierarchy. Often, it becomes clear that the proper place to "own" that state is at a higher level in the hierarchy. See the [Lifting State Up](/react/docs/lifting-state-up.html) guide for examples of this.
### Legacy API: String Refs
### Legacy API: String Refs
If you worked with React before, you might be familiar with an older API where the `ref` attribute is a string, like `"textInput"`, and the DOM node is accessed as `this.refs.textInput`. We advise against it because string refs have [some issues](https://github.com/facebook/react/pull/8333#issuecomment-271648615), are considered legacy, and **are likely to be removed in one of the future releases**. If you're currently using `this.refs.textInput` to access refs, we recommend the callback pattern instead.

View File

@ -6,9 +6,11 @@
"react-scripts": "0.8.4"
},
"dependencies": {
"classnames": "^2.2.5",
"query-string": "^4.2.3",
"react": "^15.4.1",
"react-dom": "^15.4.1"
"react-dom": "^15.4.1",
"semver": "^5.3.0"
},
"scripts": {
"start": "react-scripts start",

View File

@ -0,0 +1,21 @@
const React = window.React;
const propTypes = {
children: React.PropTypes.node.isRequired,
};
class Fixture extends React.Component {
render() {
const { children } = this.props;
return (
<div className="test-fixture">
{children}
</div>
);
}
}
Fixture.propTypes = propTypes;
export default Fixture

View File

@ -0,0 +1,28 @@
import React from 'react';
const propTypes = {
title: React.PropTypes.node.isRequired,
description: React.PropTypes.node.isRequired,
};
class FixtureSet extends React.Component {
render() {
const { title, description, children } = this.props;
return (
<div>
<h1>{title}</h1>
{description && (
<p>{description}</p>
)}
{children}
</div>
);
}
}
FixtureSet.propTypes = propTypes;
export default FixtureSet

View File

@ -1,4 +1,5 @@
import { parse, stringify } from 'query-string';
import getVersionTags from '../tags';
const React = window.React;
const Header = React.createClass({
@ -9,13 +10,12 @@ const Header = React.createClass({
return { version, versions };
},
componentWillMount() {
fetch('https://api.github.com/repos/facebook/react/tags', { mode: 'cors' })
.then(res => res.json())
getVersionTags()
.then(tags => {
let versions = tags.map(tag => tag.name.slice(1));
versions = ['local', ...versions];
versions = [`local`, ...versions];
this.setState({ versions });
});
})
},
handleVersionChange(event) {
const query = parse(window.location.search);
@ -46,6 +46,7 @@ const Header = React.createClass({
<option value="/text-inputs">Text Inputs</option>
<option value="/selects">Selects</option>
<option value="/textareas">Textareas</option>
<option value="/input-change-events">Input change events</option>
</select>
</label>
<label htmlFor="react_version">

View File

@ -0,0 +1,145 @@
import cn from 'classnames';
import semver from 'semver';
import React from 'react';
import { parse } from 'query-string';
import { semverString } from './propTypes'
const propTypes = {
children: React.PropTypes.node.isRequired,
title: React.PropTypes.node.isRequired,
resolvedIn: semverString,
resolvedBy: React.PropTypes.string
};
class TestCase extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
complete: false,
};
}
handleChange = (e) => {
this.setState({
complete: e.target.checked
})
};
render() {
const {
title,
description,
resolvedIn,
resolvedBy,
affectedBrowsers,
children,
} = this.props;
let { complete } = this.state;
const { version } = parse(window.location.search);
const isTestRelevant = (
!version ||
!resolvedIn ||
semver.gte(version, resolvedIn)
);
complete = !isTestRelevant || complete;
return (
<section
className={cn(
"test-case",
complete && 'test-case--complete'
)}
>
<h2 className="test-case__title type-subheading">
<label>
<input
className="test-case__title__check"
type="checkbox"
checked={complete}
onChange={this.handleChange}
/>
{' '}{title}
</label>
</h2>
<dl className="test-case__details">
{resolvedIn && (
<dt>First supported in: </dt>)}
{resolvedIn && (
<dd>
<a href={'https://github.com/facebook/react/tag/v' + resolvedIn}>
<code>{resolvedIn}</code>
</a>
</dd>
)}
{resolvedBy && (
<dt>Fixed by: </dt>)}
{resolvedBy && (
<dd>
<a href={'https://github.com/facebook/react/pull/' + resolvedBy.slice(1)}>
<code>{resolvedBy}</code>
</a>
</dd>
)}
{affectedBrowsers &&
<dt>Affected browsers: </dt>}
{affectedBrowsers &&
<dd>{affectedBrowsers}</dd>
}
</dl>
<p className="test-case__desc">
{description}
</p>
<div className="test-case__body">
{!isTestRelevant &&(
<p className="test-case__invalid-version">
<strong>Note:</strong> This test case was fixed in a later version of React.
This test is not expected to pass for the selected version, and that's ok!
</p>
)}
{children}
</div>
</section>
);
}
}
TestCase.propTypes = propTypes;
TestCase.Steps = class extends React.Component {
render() {
const { children } = this.props;
return (
<div>
<h3>Steps to reproduce:</h3>
<ol>
{children}
</ol>
</div>
)
}
}
TestCase.ExpectedResult = class extends React.Component {
render() {
const { children } = this.props
return (
<div>
<h3>Expected Result:</h3>
<p>
{children}
</p>
</div>
)
}
}
export default TestCase

View File

@ -2,7 +2,8 @@ const React = window.React;
import RangeInputFixtures from './range-inputs';
import TextInputFixtures from './text-inputs';
import SelectFixtures from './selects';
import TextAreaFixtures from './textareas/';
import TextAreaFixtures from './textareas';
import InputChangeEvents from './input-change-events';
/**
* A simple routing component that renders the appropriate
@ -19,6 +20,8 @@ const FixturesPage = React.createClass({
return <SelectFixtures />;
case '/textareas':
return <TextAreaFixtures />;
case '/input-change-events':
return <InputChangeEvents />;
default:
return <p>Please select a test fixture.</p>;
}

View File

@ -0,0 +1,61 @@
import React from 'react';
import Fixture from '../../Fixture';
class InputPlaceholderFixture extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
placeholder: 'A placeholder',
changeCount: 0,
};
}
handleChange = () => {
this.setState(({ changeCount }) => {
return {
changeCount: changeCount + 1
}
})
}
handleGeneratePlaceholder = () => {
this.setState({
placeholder: `A placeholder: ${Math.random() * 100}`
})
}
handleReset = () => {
this.setState({
changeCount: 0,
})
}
render() {
const { placeholder, changeCount } = this.state;
const color = changeCount === 0 ? 'green' : 'red';
return (
<Fixture>
<input
type='text'
placeholder={placeholder}
onChange={this.handleChange}
/>
{' '}
<button onClick={this.handleGeneratePlaceholder}>
Change placeholder
</button>
<p style={{ color }}>
<code>onChange</code>{' calls: '}<strong>{changeCount}</strong>
</p>
<button onClick={this.handleReset}>Reset count</button>
</Fixture>
)
}
}
export default InputPlaceholderFixture;

View File

@ -0,0 +1,52 @@
import React from 'react';
import Fixture from '../../Fixture';
class RadioClickFixture extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
changeCount: 0,
};
}
handleChange = () => {
this.setState(({ changeCount }) => {
return {
changeCount: changeCount + 1
}
})
}
handleReset = () => {
this.setState({
changeCount: 0,
})
}
render() {
const { changeCount } = this.state;
const color = changeCount === 0 ? 'green' : 'red';
return (
<Fixture>
<label>
<input
defaultChecked
type='radio'
onChange={this.handleChange}
/>
Test case radio input
</label>
{' '}
<p style={{ color }}>
<code>onChange</code>{' calls: '}<strong>{changeCount}</strong>
</p>
<button onClick={this.handleReset}>Reset count</button>
</Fixture>
)
}
}
export default RadioClickFixture;

View File

@ -0,0 +1,75 @@
import React from 'react';
import Fixture from '../../Fixture';
class RangeKeyboardFixture extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
keydownCount: 0,
changeCount: 0,
};
}
componentDidMount() {
this.input.addEventListener('keydown', this.handleKeydown, false)
}
componentWillUnmount() {
this.input.removeEventListener('keydown', this.handleKeydown, false)
}
handleChange = () => {
this.setState(({ changeCount }) => {
return {
changeCount: changeCount + 1
}
})
}
handleKeydown = (e) => {
// only interesting in arrow key events
if (![37, 38, 39, 40].includes(e.keyCode))
return;
this.setState(({ keydownCount }) => {
return {
keydownCount: keydownCount + 1
}
})
}
handleReset = () => {
this.setState({
keydownCount: 0,
changeCount: 0,
})
}
render() {
const { keydownCount, changeCount } = this.state;
const color = keydownCount === changeCount ? 'green' : 'red';
return (
<Fixture>
<input
type='range'
ref={r => this.input = r}
onChange={this.handleChange}
/>
{' '}
<p style={{ color }}>
<code>onKeyDown</code>{' calls: '}<strong>{keydownCount}</strong>
{' vs '}
<code>onChange</code>{' calls: '}<strong>{changeCount}</strong>
</p>
<button onClick={this.handleReset}>Reset counts</button>
</Fixture>
)
}
}
export default RangeKeyboardFixture;

View File

@ -0,0 +1,81 @@
import React from 'react';
import FixtureSet from '../../FixtureSet';
import TestCase from '../../TestCase';
import RangeKeyboardFixture from './RangeKeyboardFixture';
import RadioClickFixture from './RadioClickFixture';
import InputPlaceholderFixture from './InputPlaceholderFixture';
class InputChangeEvents extends React.Component {
render() {
return (
<FixtureSet
title="Input change events"
description="Tests proper behavior of the onChange event for inputs"
>
<TestCase
title="Range keyboard changes"
description={`
Range inputs should fire onChange events for keyboard events
`}
>
<TestCase.Steps>
<li>Focus range input</li>
<li>change value via the keyboard arrow keys</li>
</TestCase.Steps>
<TestCase.ExpectedResult>
The <code>onKeyDown</code> call count should be equal to
the <code>onChange</code> call count.
</TestCase.ExpectedResult>
<RangeKeyboardFixture />
</TestCase>
<TestCase
title="Radio input clicks"
description={`
Radio inputs should only fire change events when the checked
state changes.
`}
resolvedIn="16.0.0"
>
<TestCase.Steps>
<li>Click on the Radio input (or label text)</li>
</TestCase.Steps>
<TestCase.ExpectedResult>
The <code>onChange</code> call count should remain at 0
</TestCase.ExpectedResult>
<RadioClickFixture />
</TestCase>
<TestCase
title="Inputs with placeholders"
description={`
Text inputs with placeholders should not trigger changes
when the placeholder is altered
`}
resolvedIn="15.0.0"
resolvedBy="#5004"
affectedBrowsers="IE9+"
>
<TestCase.Steps>
<li>Click on the Text input</li>
<li>Click on the "Change placeholder" button</li>
</TestCase.Steps>
<TestCase.ExpectedResult>
The <code>onChange</code> call count should remain at 0
</TestCase.ExpectedResult>
<InputPlaceholderFixture />
</TestCase>
</FixtureSet>
);
}
}
export default InputChangeEvents

View File

@ -24,7 +24,7 @@ const TextInputFixtures = React.createClass({
return (
<div key={type} className="field">
<label htmlFor={id}>{type}</label>
<label className="control-label" htmlFor={id}>{type}</label>
<input id={id} type={type} value={state} onChange={onChange} />
&nbsp; &rarr; {JSON.stringify(state)}
</div>
@ -35,7 +35,7 @@ const TextInputFixtures = React.createClass({
let id = `uncontrolled_${type}`;
return (
<div key={type} className="field">
<label htmlFor={id}>{type}</label>
<label className="control-label" htmlFor={id}>{type}</label>
<input id={id} type={type} />
</div>
);

View File

@ -0,0 +1,16 @@
import semver from 'semver';
const React = window.React;
export function semverString (props, propName, componentName) {
let version = props[propName];
let error = React.PropTypes.string(...arguments);
if (!error && version != null && !semver.valid(version))
error = new Error(
`\`${propName}\` should be a valid "semantic version" matching ` +
'an existing React version'
);
return error || null;
};

View File

@ -4,22 +4,28 @@
box-sizing: border-box;
}
html {
font-size: 10px;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
font-size: 1.4rem;
margin: 0;
padding: 0;
}
select {
width: 120px;
width: 12rem;
}
.header {
background: #222;
box-shadow: inset 0 -1px 3px #000;
line-height: 32px;
font-size: 1.6rem;
line-height: 3.2rem;
overflow: hidden;
padding: 8px 16px;
padding: .8rem 1.6rem;
}
.header__inner {
@ -40,7 +46,7 @@ select {
.header__logo img {
display: inline-block;
margin-right: 8px;
margin-right: 0.8rem;
vertical-align: middle;
}
@ -63,24 +69,142 @@ select {
margin: 0 auto;
max-width: 900px;
overflow: hidden;
padding: 20px;
padding: 2rem;
}
label {
.control-label {
display: block;
font-size: 12px;
font-size: 1.2rem;
letter-spacing: 0.01em;
margin-bottom: 4px;
margin-bottom: 0.4rem;
text-transform: uppercase;
}
.field {
padding: 8px;
padding: 0.8rem;
}
fieldset {
border: 1px solid #aaa;
float: left;
padding: 16px;
padding: 1.6rem;
width: 49%;
}
.control-box {
overflow: hidden;
}
ul, ol {
margin: 0 0 2rem 0;
}
li {
margin-bottom: 0.4rem;
}
.type-subheading {
font-size: 1.8rem;
font-weight: 600;
line-height: 1.5;
margin: 0 0 1.6rem;
}
.hint {
font-style: italic;
line-height: 1.5;
text-size: 1.4rem;
}
.footnote {
border-left: 4px solid #aaa;
color: #444;
font-style: italic;
line-height: 1.5;
margin-bottom: 2.4rem;
margin-left: 0.4rem;
padding-left: 1.6rem;
text-size: 1.3rem;
}
.test-case {
border-radius: 0.2rem;
border: 1px solid #d9d9d9;
margin: 3.2rem 0 3.2rem;
}
.test-case__title {
padding: 10px 15px 8px;
line-height: 16px;
font-size: 18px;
border-bottom: 1px dashed #d9d9d9;
margin: 0 0 -1px;
}
.test-case__title__check {
display: inline-block;
margin-right: 8px;
vertical-align: middle;
}
.test-case__body {
padding: 0 15px;
}
.test-case__desc {
font-style: italic;
margin: 15px 0;
padding: 0 15px;
}
.test-case--complete {
border-color: #5cb85c;
}
.test-case--complete .test-case__title {
color: #5cb85c;
}
.test-case__details {
border-bottom: 1px dashed #d9d9d9;
font-size: 80%;
text-transform: uppercase;
letter-spacing: 0.02em;
margin: 0;
padding: 0 15px;
}
.test-case__details > * {
display: inline-block;
}
.test-case__details > dt,
.test-case__details > dd {
padding: 8px 0 6px;
}
.test-case__details > dt {
color: #464a4c;
font-weight: 600;
}
.test-case__details > dd {
margin-left: 1rem;
}
.test-case__details > dd + dt {
margin-left: 1.5rem;
}
.test-case__invalid-version {
font-style: italic;
font-size: 1.6rem;
color: #5cb85c;
}
.test-fixture {
padding: 20px;
margin: 0 -15px; /* opposite of .test-case padding */
background-color: #f4f4f4;
border-top: 1px solid #d9d9d9;
}

70
fixtures/dom/src/tags.js Normal file
View File

@ -0,0 +1,70 @@
/**
* Version tags are loaded from the Github API. Since the Github API is rate-limited
* we attempt to save and load the tags in sessionStorage when possible. Since its unlikely
* that versions will change during a single session this should be safe.
*/
const TAGS_CACHE_KEY = '@react-dom-fixtures/tags';
/**
* Its possible that users will be testing changes frequently
* in a browser that does not support sessionStorage. If the API does
* get rate limited this hardcoded fallback will be loaded instead.
* This way users can still switch between ~some versions while testing.
* If there's a specific version they need to test that is not here, they
* can manually load it by editing the URL (`?version={whatever}`)
*/
const fallbackTags = [
'15.4.2',
'15.3.2',
'15.2.1',
'15.1.0',
'15.0.2',
'0.14.8',
'0.13.0'
].map(version => ({
name: `v${version}`
}))
let canUseSessionStorage = true;
try {
sessionStorage.setItem('foo', '')
} catch (err) {
canUseSessionStorage = false;
}
/**
* Attempts to load tags from sessionStorage. In cases where
* sessionStorage is not available (Safari private browsing) or the
* tags are cached a fetch request is made to the Github API.
*
* Returns a promise so that the consuming module can always assume
* the request is async, even if its loaded from sessionStorage.
*/
export default function getVersionTags() {
return new Promise((resolve) => {
let cachedTags;
if (canUseSessionStorage) {
cachedTags = sessionStorage.getItem(TAGS_CACHE_KEY);
}
if (cachedTags) {
cachedTags = JSON.parse(cachedTags);
resolve(cachedTags);
} else {
fetch('https://api.github.com/repos/facebook/react/tags', { mode: 'cors' })
.then(res => res.json())
.then(tags => {
// A message property indicates an error was sent from the API
if (tags.message) {
return resolve(fallbackTags)
}
if (canUseSessionStorage) {
sessionStorage.setItem(TAGS_CACHE_KEY, JSON.stringify(tags))
}
resolve(tags)
})
.catch(() => resolve(fallbackTags))
}
})
}

View File

@ -1136,6 +1136,10 @@ clap@^1.0.9:
dependencies:
chalk "^1.1.3"
classnames@^2.2.5:
version "2.2.5"
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.5.tgz#fb3801d453467649ef3603c7d61a02bd129bde6d"
clean-css@3.4.x:
version "3.4.23"
resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-3.4.23.tgz#604fbbca24c12feb59b02f00b84f1fb7ded6d001"

View File

@ -103,6 +103,8 @@ var paths = {
},
};
exports.paths = paths;
var moduleMapBase = {'object-assign': 'object-assign'};
var fbjsModules = require('fbjs/module-map');

View File

@ -12,6 +12,7 @@
"babel-jest": "^19.0.0",
"babel-plugin-check-es2015-constants": "^6.5.0",
"babel-plugin-syntax-trailing-function-commas": "^6.5.0",
"babel-plugin-transform-async-to-generator": "^6.22.0",
"babel-plugin-transform-class-properties": "^6.11.5",
"babel-plugin-transform-es2015-arrow-functions": "^6.5.2",
"babel-plugin-transform-es2015-block-scoped-functions": "^6.5.0",
@ -51,6 +52,7 @@
"fbjs-scripts": "^0.6.0",
"flow-bin": "^0.37.0",
"glob": "^6.0.1",
"glob-stream": "^6.1.0",
"grunt": "^0.4.5",
"grunt-cli": "^0.1.13",
"grunt-compare-size": "^0.4.0",

View File

@ -4,3 +4,7 @@ set -e
./node_modules/.bin/gulp react:extract-errors
git checkout -- scripts/error-codes/codes.json
WARNINGS=$(node scripts/error-codes/print-warnings.js)
echo "$WARNINGS"
test ! -z "$WARNINGS"

View File

@ -142,5 +142,6 @@
"140": "Expected hook events to fire for the child before its parent includes it in onSetChildren().",
"141": "Expected onSetChildren() to fire for a container child before its parent includes it in onSetChildren().",
"142": "Expected onBeforeMountComponent() parent and onSetChildren() to be consistent (%s has parents %s and %s).",
"143": "React.Children.only expected to receive a single React element child."
"143": "React.Children.only expected to receive a single React element child.",
"144": "React.PropTypes type checking code is stripped in production."
}

View File

@ -0,0 +1,77 @@
/**
* Copyright (c) 2013-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
'use strict';
const babylon = require('babylon');
const fs = require('fs');
const through = require('through2');
const traverse = require('babel-traverse').default;
const gs = require('glob-stream');
const evalToString = require('./evalToString');
const paths = require('../../gulpfile').paths;
const babylonOptions = {
sourceType: 'module',
// As a parser, babylon has its own options and we can't directly
// import/require a babel preset. It should be kept **the same** as
// the `babel-plugin-syntax-*` ones specified in
// https://github.com/facebook/fbjs/blob/master/babel-preset/configure.js
plugins: [
'classProperties',
'flow',
'jsx',
'trailingFunctionCommas',
'objectRestSpread',
],
};
const warnings = new Set();
function transform(file, enc, cb) {
fs.readFile(file.path, 'utf8', function(err, source) {
if (err) {
cb(err);
return;
}
const ast = babylon.parse(source, babylonOptions);
traverse(ast, {
CallExpression: {
exit: function(astPath) {
if (astPath.get('callee').isIdentifier({name: 'warning'})) {
const node = astPath.node;
// warning messages can be concatenated (`+`) at runtime, so here's
// a trivial partial evaluator that interprets the literal value
const warningMsgLiteral = evalToString(node.arguments[1]);
warnings.add(JSON.stringify(warningMsgLiteral));
}
},
},
});
cb(null);
});
}
const sourcePaths = [].concat(
paths.react.src,
paths.reactDOM.src,
paths.reactNative.src,
paths.reactTestRenderer.src
);
gs(sourcePaths)
.pipe(through.obj(transform, cb => {
process.stdout.write(Array.from(warnings).sort().join('\n') + '\n');
cb();
}));

View File

@ -157,13 +157,6 @@ src/isomorphic/children/__tests__/onlyChild-test.js
* should not fail when passed interpolated single child
* should return the only child
src/isomorphic/children/__tests__/sliceChildren-test.js
* should render the whole set if start zero is supplied
* should render the remaining set if no end index is supplied
* should exclude everything at or after the end index
* should allow static children to be sliced
* should slice nested children
src/isomorphic/classic/__tests__/ReactContextValidator-test.js
* should filter out context not in contextTypes
* should pass next context to lifecycles
@ -784,14 +777,6 @@ src/renderers/__tests__/ReactPerf-test.js
* should not print errant warnings if portal throws in componentWillMount()
* should not print errant warnings if portal throws in componentDidMount()
src/renderers/__tests__/ReactStateSetters-test.js
* createStateSetter should update state
* createStateKeySetter should update state
* createStateKeySetter is memoized
* createStateSetter should update state from mixin
* createStateKeySetter should update state with mixin
* createStateKeySetter is memoized with mixin
src/renderers/__tests__/ReactStatelessComponent-test.js
* should render stateless component
* should update stateless component
@ -1656,7 +1641,6 @@ src/renderers/shared/shared/event/__tests__/EventPluginRegistry-test.js
* should publish registration names of injected plugins
* should throw if multiple registration names collide
* should throw if an invalid event is published
* should be able to get the plugin from synthetic events
src/renderers/shared/shared/event/eventPlugins/__tests__/ResponderEventPlugin-test.js
* should do nothing when no one wants to respond
@ -1695,6 +1679,7 @@ src/renderers/shared/utils/__tests__/ReactErrorUtils-test.js
* should return null if no error is thrown (development)
* can nest with same debug name (development)
* does not return nested errors (development)
* can be shimmed (development)
* it should rethrow errors caught by invokeGuardedCallbackAndCatchFirstError (production)
* should call the callback the passed arguments (production)
* should call the callback with the provided context (production)
@ -1702,6 +1687,7 @@ src/renderers/shared/utils/__tests__/ReactErrorUtils-test.js
* should return null if no error is thrown (production)
* can nest with same debug name (production)
* does not return nested errors (production)
* can be shimmed (production)
src/renderers/shared/utils/__tests__/accumulateInto-test.js
* throws if the second item is null

View File

@ -22,6 +22,7 @@ var pathToBabel = path.join(require.resolve('babel-core'), '..', 'package.json')
var pathToModuleMap = require.resolve('fbjs/module-map');
var pathToBabelPluginDevWithCode = require.resolve('../error-codes/dev-expression-with-codes');
var pathToBabelPluginModules = require.resolve('fbjs-scripts/babel-6/rewrite-modules');
var pathToBabelPluginAsyncToGenerator = require.resolve('babel-plugin-transform-async-to-generator');
var pathToBabelrc = path.join(__dirname, '..', '..', '.babelrc');
var pathToErrorCodes = require.resolve('../error-codes/codes.json');
@ -54,11 +55,17 @@ module.exports = {
!filePath.match(/\/node_modules\//) &&
!filePath.match(/\/third_party\//)
) {
// for test files, we also apply the async-await transform, but we want to
// make sure we don't accidentally apply that transform to product code.
var isTestFile = !!filePath.match(/\/__tests__\//);
return babel.transform(
src,
Object.assign(
{filename: path.relative(process.cwd(), filePath)},
babelOptions
babelOptions,
isTestFile ? {
plugins: [pathToBabelPluginAsyncToGenerator].concat(babelOptions.plugins),
} : {}
)
).code;
}

View File

@ -1,104 +0,0 @@
/**
* Copyright 2013-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule ReactStateSetters
*/
'use strict';
var ReactStateSetters = {
/**
* Returns a function that calls the provided function, and uses the result
* of that to set the component's state.
*
* @param {ReactCompositeComponent} component
* @param {function} funcReturningState Returned callback uses this to
* determine how to update state.
* @return {function} callback that when invoked uses funcReturningState to
* determined the object literal to setState.
*/
createStateSetter: function(component, funcReturningState) {
return function(a, b, c, d, e, f) {
var partialState = funcReturningState.call(component, a, b, c, d, e, f);
if (partialState) {
component.setState(partialState);
}
};
},
/**
* Returns a single-argument callback that can be used to update a single
* key in the component's state.
*
* Note: this is memoized function, which makes it inexpensive to call.
*
* @param {ReactCompositeComponent} component
* @param {string} key The key in the state that you should update.
* @return {function} callback of 1 argument which calls setState() with
* the provided keyName and callback argument.
*/
createStateKeySetter: function(component, key) {
// Memoize the setters.
var cache = component.__keySetters || (component.__keySetters = {});
return cache[key] || (cache[key] = createStateKeySetter(component, key));
},
};
function createStateKeySetter(component, key) {
// Partial state is allocated outside of the function closure so it can be
// reused with every call, avoiding memory allocation when this function
// is called.
var partialState = {};
return function stateKeySetter(value) {
partialState[key] = value;
component.setState(partialState);
};
}
ReactStateSetters.Mixin = {
/**
* Returns a function that calls the provided function, and uses the result
* of that to set the component's state.
*
* For example, these statements are equivalent:
*
* this.setState({x: 1});
* this.createStateSetter(function(xValue) {
* return {x: xValue};
* })(1);
*
* @param {function} funcReturningState Returned callback uses this to
* determine how to update state.
* @return {function} callback that when invoked uses funcReturningState to
* determined the object literal to setState.
*/
createStateSetter: function(funcReturningState) {
return ReactStateSetters.createStateSetter(this, funcReturningState);
},
/**
* Returns a single-argument callback that can be used to update a single
* key in the component's state.
*
* For example, these statements are equivalent:
*
* this.setState({x: 1});
* this.createStateKeySetter('x')(1);
*
* Note: this is memoized function, which makes it inexpensive to call.
*
* @param {string} key The key in the state that you should update.
* @return {function} callback of 1 argument which calls setState() with
* the provided keyName and callback argument.
*/
createStateKeySetter: function(key) {
return ReactStateSetters.createStateKeySetter(this, key);
},
};
module.exports = ReactStateSetters;

View File

@ -1,93 +0,0 @@
/**
* Copyright 2013-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @emails react-core
*/
'use strict';
describe('sliceChildren', () => {
var React;
var sliceChildren;
beforeEach(() => {
React = require('react');
sliceChildren = require('sliceChildren');
});
it('should render the whole set if start zero is supplied', () => {
var fullSet = [
<div key="A" />,
<div key="B" />,
<div key="C" />,
];
var children = sliceChildren(fullSet, 0);
expect(children).toEqual([
<div key=".$A" />,
<div key=".$B" />,
<div key=".$C" />,
]);
});
it('should render the remaining set if no end index is supplied', () => {
var fullSet = [
<div key="A" />,
<div key="B" />,
<div key="C" />,
];
var children = sliceChildren(fullSet, 1);
expect(children).toEqual([
<div key=".$B" />,
<div key=".$C" />,
]);
});
it('should exclude everything at or after the end index', () => {
var fullSet = [
<div key="A" />,
<div key="B" />,
<div key="C" />,
<div key="D" />,
];
var children = sliceChildren(fullSet, 1, 2);
expect(children).toEqual([
<div key=".$B" />,
]);
});
it('should allow static children to be sliced', () => {
var a = <a />;
var b = <b />;
var c = <i />;
var el = <div>{a}{b}{c}</div>;
var children = sliceChildren(el.props.children, 1, 2);
expect(children).toEqual([
<b key=".1" />,
]);
});
it('should slice nested children', () => {
var fullSet = [
<div key="A" />,
[
<div key="B" />,
<div key="C" />,
],
<div key="D" />,
];
var children = sliceChildren(fullSet, 1, 2);
expect(children).toEqual([
<div key=".1:$B" />,
]);
});
});

View File

@ -1,34 +0,0 @@
/**
* Copyright 2013-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule sliceChildren
*/
'use strict';
var ReactChildren = require('ReactChildren');
/**
* Slice children that are typically specified as `props.children`. This version
* of slice children ignores empty child components.
*
* @param {*} children The children set to filter.
* @param {number} start The first zero-based index to include in the subset.
* @param {?number} end The non-inclusive last index of the subset.
* @return {object} mirrored array with mapped children
*/
function sliceChildren(children, start, end) {
if (children == null) {
return children;
}
var array = ReactChildren.toArray(children);
return array.slice(start, end);
}
module.exports = sliceChildren;

View File

@ -49,7 +49,7 @@ describe('ReactPropTypesProduction', function() {
'prop'
);
}).toThrowError(
'React.PropTypes type checking code is stripped in production.'
'Minified React error #144'
);
}

View File

@ -1,152 +0,0 @@
/**
* Copyright 2013-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @emails react-core
*/
'use strict';
var React = require('react');
var ReactStateSetters = require('ReactStateSetters');
var ReactTestUtils = require('ReactTestUtils');
var TestComponent;
var TestComponentWithMixin;
describe('ReactStateSetters', () => {
beforeEach(() => {
jest.resetModules();
TestComponent = class extends React.Component {
state = {foo: 'foo'};
render() {
return <div />;
}
};
TestComponentWithMixin = React.createClass({
mixins: [ReactStateSetters.Mixin],
getInitialState: function() {
return {foo: 'foo'};
},
render: function() {
return <div />;
},
});
});
it('createStateSetter should update state', () => {
var instance = <TestComponent />;
instance = ReactTestUtils.renderIntoDocument(instance);
expect(instance.state).toEqual({foo: 'foo'});
var setter = ReactStateSetters.createStateSetter(
instance,
function(a, b, c) {
return {
foo: a + b + c,
bar: a * b * c,
};
}
);
expect(instance.state).toEqual({foo: 'foo'});
setter(1, 2, 3);
expect(instance.state).toEqual({foo: 6, bar: 6});
setter(10, 11, 12);
expect(instance.state).toEqual({foo: 33, bar: 1320});
});
it('createStateKeySetter should update state', () => {
var instance = <TestComponent />;
instance = ReactTestUtils.renderIntoDocument(instance);
expect(instance.state).toEqual({foo: 'foo'});
var setter = ReactStateSetters.createStateKeySetter(instance, 'foo');
expect(instance.state).toEqual({foo: 'foo'});
setter('bar');
expect(instance.state).toEqual({foo: 'bar'});
setter('baz');
expect(instance.state).toEqual({foo: 'baz'});
});
it('createStateKeySetter is memoized', () => {
var instance = <TestComponent />;
instance = ReactTestUtils.renderIntoDocument(instance);
expect(instance.state).toEqual({foo: 'foo'});
var foo1 = ReactStateSetters.createStateKeySetter(instance, 'foo');
var bar1 = ReactStateSetters.createStateKeySetter(instance, 'bar');
var foo2 = ReactStateSetters.createStateKeySetter(instance, 'foo');
var bar2 = ReactStateSetters.createStateKeySetter(instance, 'bar');
expect(foo2).toBe(foo1);
expect(bar2).toBe(bar1);
});
it('createStateSetter should update state from mixin', () => {
var instance = <TestComponentWithMixin />;
instance = ReactTestUtils.renderIntoDocument(instance);
expect(instance.state).toEqual({foo: 'foo'});
var setter = instance.createStateSetter(
function(a, b, c) {
return {
foo: a + b + c,
bar: a * b * c,
};
}
);
expect(instance.state).toEqual({foo: 'foo'});
setter(1, 2, 3);
expect(instance.state).toEqual({foo: 6, bar: 6});
setter(10, 11, 12);
expect(instance.state).toEqual({foo: 33, bar: 1320});
});
it('createStateKeySetter should update state with mixin', () => {
var instance = <TestComponentWithMixin />;
instance = ReactTestUtils.renderIntoDocument(instance);
expect(instance.state).toEqual({foo: 'foo'});
var setter = instance.createStateKeySetter('foo');
expect(instance.state).toEqual({foo: 'foo'});
setter('bar');
expect(instance.state).toEqual({foo: 'bar'});
setter('baz');
expect(instance.state).toEqual({foo: 'baz'});
});
it('createStateKeySetter is memoized with mixin', () => {
var instance = <TestComponentWithMixin />;
instance = ReactTestUtils.renderIntoDocument(instance);
expect(instance.state).toEqual({foo: 'foo'});
var foo1 = instance.createStateKeySetter('foo');
var bar1 = instance.createStateKeySetter('bar');
var foo2 = instance.createStateKeySetter('foo');
var bar2 = instance.createStateKeySetter('bar');
expect(foo2).toBe(foo1);
expect(bar2).toBe(bar1);
});
});

View File

@ -19,41 +19,44 @@ let ReactDOMServer;
// Helper functions for rendering tests
// ====================================
// promisified version of ReactDOM.render()
function asyncReactDOMRender(reactElement, domElement) {
return new Promise(resolve =>
ReactDOM.render(reactElement, domElement, resolve));
}
// performs fn asynchronously and expects count errors logged to console.error.
// will fail the test if the count of errors logged is not equal to count.
function expectErrors(fn, count) {
async function expectErrors(fn, count) {
if (console.error.calls && console.error.calls.reset) {
console.error.calls.reset();
} else {
spyOn(console, 'error');
}
return fn().then((result) => {
if (console.error.calls.count() !== count) {
console.log(`We expected ${count} warning(s), but saw ${console.error.calls.count()} warning(s).`);
if (console.error.calls.count() > 0) {
console.log(`We saw these warnings:`);
for (var i = 0; i < console.error.calls.count(); i++) {
console.log(console.error.calls.argsFor(i)[0]);
}
const result = await fn();
if (console.error.calls.count() !== count && console.error.calls.count() !== 0) {
console.log(`We expected ${count} warning(s), but saw ${console.error.calls.count()} warning(s).`);
if (console.error.calls.count() > 0) {
console.log(`We saw these warnings:`);
for (var i = 0; i < console.error.calls.count(); i++) {
console.log(console.error.calls.argsFor(i)[0]);
}
}
expectDev(console.error.calls.count()).toBe(count);
return result;
});
}
expectDev(console.error.calls.count()).toBe(count);
return result;
}
// renders the reactElement into domElement, and expects a certain number of errors.
// returns a Promise that resolves when the render is complete.
function renderIntoDom(reactElement, domElement, errorCount = 0) {
return expectErrors(
() => new Promise((resolve) => {
async () => {
ExecutionEnvironment.canUseDOM = true;
ReactDOM.render(reactElement, domElement, () => {
ExecutionEnvironment.canUseDOM = false;
resolve(domElement.firstChild);
});
}),
await asyncReactDOMRender(reactElement, domElement);
ExecutionEnvironment.canUseDOM = false;
return domElement.firstChild;
},
errorCount
);
}
@ -61,15 +64,14 @@ function renderIntoDom(reactElement, domElement, errorCount = 0) {
// Renders text using SSR and then stuffs it into a DOM node; returns the DOM
// element that corresponds with the reactElement.
// Does not render on client or perform client-side revival.
function serverRender(reactElement, errorCount = 0) {
return expectErrors(
async function serverRender(reactElement, errorCount = 0) {
const markup = await expectErrors(
() => Promise.resolve(ReactDOMServer.renderToString(reactElement)),
errorCount)
.then((markup) => {
var domElement = document.createElement('div');
domElement.innerHTML = markup;
return domElement.firstChild;
});
errorCount
);
var domElement = document.createElement('div');
domElement.innerHTML = markup;
return domElement.firstChild;
}
const clientCleanRender = (element, errorCount = 0) => {
@ -77,12 +79,11 @@ const clientCleanRender = (element, errorCount = 0) => {
return renderIntoDom(element, div, errorCount);
};
const clientRenderOnServerString = (element, errorCount = 0) => {
return serverRender(element, errorCount).then((markup) => {
var domElement = document.createElement('div');
domElement.innerHTML = markup;
return renderIntoDom(element, domElement, errorCount);
});
const clientRenderOnServerString = async (element, errorCount = 0) => {
const markup = await serverRender(element, errorCount);
var domElement = document.createElement('div');
domElement.innerHTML = markup;
return renderIntoDom(element, domElement, errorCount);
};
const clientRenderOnBadMarkup = (element, errorCount = 0) => {
@ -143,24 +144,26 @@ describe('ReactDOMServerIntegration', () => {
});
describe('basic rendering', function() {
itRenders('a blank div', render =>
render(<div />).then(e => expect(e.tagName).toBe('DIV')));
itRenders('a blank div', async render => {
const e = await render(<div />);
expect(e.tagName).toBe('DIV');
});
itRenders('a div with inline styles', render =>
render(<div style={{color:'red', width:'30px'}} />).then(e => {
expect(e.style.color).toBe('red');
expect(e.style.width).toBe('30px');
})
);
itRenders('a div with inline styles', async render => {
const e = await render(<div style={{color:'red', width:'30px'}} />);
expect(e.style.color).toBe('red');
expect(e.style.width).toBe('30px');
});
itRenders('a self-closing tag', render =>
render(<br />).then(e => expect(e.tagName).toBe('BR')));
itRenders('a self-closing tag', async render => {
const e = await render(<br />);
expect(e.tagName).toBe('BR');
});
itRenders('a self-closing tag as a child', render =>
render(<div><br /></div>).then(e => {
expect(e.childNodes.length).toBe(1);
expect(e.firstChild.tagName).toBe('BR');
})
);
itRenders('a self-closing tag as a child', async render => {
const e = await render(<div><br /></div>);
expect(e.childNodes.length).toBe(1);
expect(e.firstChild.tagName).toBe('BR');
});
});
});

View File

@ -227,22 +227,6 @@ module.exports = function(
}
}
function markUpdate(workInProgress) {
workInProgress.effectTag |= Update;
}
function markUpdateIfAlreadyInProgress(current: Fiber | null, workInProgress : Fiber) {
// If an update was already in progress, we should schedule an Update
// effect even though we're bailing out, so that cWU/cDU are called.
if (current !== null) {
if (workInProgress.memoizedProps !== current.memoizedProps ||
workInProgress.memoizedState !== current.memoizedState) {
markUpdate(workInProgress);
}
}
}
function resetInputPointers(workInProgress : Fiber, instance : any) {
instance.props = workInProgress.memoizedProps;
instance.state = workInProgress.memoizedState;
@ -276,7 +260,6 @@ module.exports = function(
// Invokes the mount life-cycles on a previously never rendered instance.
function mountClassInstance(workInProgress : Fiber, priorityLevel : PriorityLevel) : void {
markUpdate(workInProgress);
const instance = workInProgress.stateNode;
const state = instance.state || null;
@ -310,12 +293,14 @@ module.exports = function(
);
}
}
if (typeof instance.componentDidMount === 'function') {
workInProgress.effectTag |= Update;
}
}
// Called on a preexisting class instance. Returns false if a resumed render
// could be reused.
function resumeMountClassInstance(workInProgress : Fiber, priorityLevel : PriorityLevel) : boolean {
markUpdate(workInProgress);
const instance = workInProgress.stateNode;
resetInputPointers(workInProgress, instance);
@ -378,6 +363,9 @@ module.exports = function(
priorityLevel
);
}
if (typeof instance.componentDidMount === 'function') {
workInProgress.effectTag |= Update;
}
return true;
}
@ -447,7 +435,14 @@ module.exports = function(
oldState === newState &&
!hasContextChanged() &&
!(updateQueue !== null && updateQueue.hasForceUpdate)) {
markUpdateIfAlreadyInProgress(current, workInProgress);
// If an update was already in progress, we should schedule an Update
// effect even though we're bailing out, so that cWU/cDU are called.
if (typeof instance.componentDidUpdate === 'function') {
if (oldProps !== current.memoizedProps ||
oldState !== current.memoizedState) {
workInProgress.effectTag |= Update;
}
}
return false;
}
@ -461,12 +456,21 @@ module.exports = function(
);
if (shouldUpdate) {
markUpdate(workInProgress);
if (typeof instance.componentWillUpdate === 'function') {
instance.componentWillUpdate(newProps, newState, newContext);
}
if (typeof instance.componentDidUpdate === 'function') {
workInProgress.effectTag |= Update;
}
} else {
markUpdateIfAlreadyInProgress(current, workInProgress);
// If an update was already in progress, we should schedule an Update
// effect even though we're bailing out, so that cWU/cDU are called.
if (typeof instance.componentDidUpdate === 'function') {
if (oldProps !== current.memoizedProps ||
oldState !== current.memoizedState) {
workInProgress.effectTag |= Update;
}
}
// If shouldComponentUpdate returned false, we should still update the
// memoized props/state to indicate that this work can be reused.

View File

@ -87,16 +87,6 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
}
}
// Only called during update. It's ok to throw.
function detachRefIfNeeded(current : Fiber | null, finishedWork : Fiber) {
if (current) {
const currentRef = current.ref;
if (currentRef !== null && currentRef !== finishedWork.ref) {
currentRef(null);
}
}
}
function getHostParent(fiber : Fiber) : I | C {
let parent = fiber.return;
while (parent !== null) {
@ -381,7 +371,6 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
function commitWork(current : Fiber | null, finishedWork : Fiber) : void {
switch (finishedWork.tag) {
case ClassComponent: {
detachRefIfNeeded(current, finishedWork);
return;
}
case HostComponent: {
@ -398,7 +387,6 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
commitUpdate(instance, updatePayload, type, oldProps, newProps, finishedWork);
}
}
detachRefIfNeeded(current, finishedWork);
return;
}
case HostText: {
@ -435,15 +423,11 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
const instance = finishedWork.stateNode;
if (finishedWork.effectTag & Update) {
if (current === null) {
if (typeof instance.componentDidMount === 'function') {
instance.componentDidMount();
}
instance.componentDidMount();
} else {
if (typeof instance.componentDidUpdate === 'function') {
const prevProps = current.memoizedProps;
const prevState = current.memoizedState;
instance.componentDidUpdate(prevProps, prevState);
}
const prevProps = current.memoizedProps;
const prevState = current.memoizedState;
instance.componentDidUpdate(prevProps, prevState);
}
}
if ((finishedWork.effectTag & Callback) && finishedWork.updateQueue !== null) {
@ -495,10 +479,7 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
}
}
function commitRef(finishedWork : Fiber) {
if (finishedWork.tag !== ClassComponent && finishedWork.tag !== HostComponent) {
return;
}
function commitAttachRef(finishedWork : Fiber) {
const ref = finishedWork.ref;
if (ref !== null) {
const instance = getPublicInstance(finishedWork.stateNode);
@ -506,12 +487,20 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
}
}
function commitDetachRef(current : Fiber) {
const currentRef = current.ref;
if (currentRef !== null) {
currentRef(null);
}
}
return {
commitPlacement,
commitDeletion,
commitWork,
commitLifeCycles,
commitRef,
commitAttachRef,
commitDetachRef,
};
};

View File

@ -85,6 +85,10 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
workInProgress.effectTag |= Update;
}
function markRef(workInProgress : Fiber) {
workInProgress.effectTag |= Ref;
}
function appendAllYields(yields : Array<mixed>, workInProgress : Fiber) {
let node = workInProgress.stateNode;
if (node) {
@ -231,9 +235,12 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
workInProgress.updateQueue = (updatePayload : any);
// If the update payload indicates that there is a change or if there
// is a new ref we mark this as an update.
if (updatePayload || current.ref !== workInProgress.ref) {
if (updatePayload) {
markUpdate(workInProgress);
}
if (current.ref !== workInProgress.ref) {
markRef(workInProgress);
}
} else {
if (!newProps) {
invariant(
@ -270,7 +277,7 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
workInProgress.stateNode = instance;
if (workInProgress.ref !== null) {
// If there is a ref on a host node we need to schedule a callback
workInProgress.effectTag |= Ref;
markRef(workInProgress);
}
}
return null;

View File

@ -146,7 +146,8 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(config : HostConfig<T, P,
commitDeletion,
commitWork,
commitLifeCycles,
commitRef,
commitAttachRef,
commitDetachRef,
} = ReactFiberCommitWork(config, captureError);
const {
scheduleAnimationCallback: hostScheduleAnimationCallback,
@ -294,16 +295,23 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(config : HostConfig<T, P,
ReactDebugCurrentFiber.current = nextEffect;
}
if (nextEffect.effectTag & ContentReset) {
const effectTag = nextEffect.effectTag;
if (effectTag & ContentReset) {
config.resetTextContent(nextEffect.stateNode);
}
if (effectTag & Ref) {
const current = nextEffect.alternate;
if (current !== null) {
commitDetachRef(current);
}
}
// The following switch statement is only concerned about placement,
// updates, and deletions. To avoid needing to add a case for every
// possible bitmap value, we remove the secondary effects from the
// effect tag and switch on that value.
let primaryEffectTag =
nextEffect.effectTag & ~(Callback | Err | ContentReset | Ref);
let primaryEffectTag = effectTag & ~(Callback | Err | ContentReset | Ref);
switch (primaryEffectTag) {
case Placement: {
commitPlacement(nextEffect);
@ -349,17 +357,19 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(config : HostConfig<T, P,
function commitAllLifeCycles() {
while (nextEffect !== null) {
const current = nextEffect.alternate;
const effectTag = nextEffect.effectTag;
// Use Task priority for lifecycle updates
if (nextEffect.effectTag & (Update | Callback)) {
if (effectTag & (Update | Callback)) {
const current = nextEffect.alternate;
commitLifeCycles(current, nextEffect);
}
if (nextEffect.effectTag & Ref) {
commitRef(nextEffect);
if (effectTag & Ref) {
commitAttachRef(nextEffect);
}
if (nextEffect.effectTag & Err) {
if (effectTag & Err) {
commitErrorHandling(nextEffect);
}

View File

@ -14,7 +14,6 @@
import type {
DispatchConfig,
ReactSyntheticEvent,
} from 'ReactSyntheticEventType';
import type {
@ -257,41 +256,6 @@ var EventPluginRegistry = {
}
},
/**
* Looks up the plugin for the supplied event.
*
* @param {object} event A synthetic event.
* @return {?object} The plugin that created the supplied event.
* @internal
*/
getPluginModuleForEvent: function(
event: ReactSyntheticEvent,
): null | PluginModule<AnyNativeEvent> {
var dispatchConfig = event.dispatchConfig;
if (dispatchConfig.registrationName) {
return EventPluginRegistry.registrationNameModules[
dispatchConfig.registrationName
] || null;
}
if (dispatchConfig.phasedRegistrationNames !== undefined) {
// pulling phasedRegistrationNames out of dispatchConfig helps Flow see
// that it is not undefined.
var {phasedRegistrationNames} = dispatchConfig;
for (var phase in phasedRegistrationNames) {
if (!phasedRegistrationNames.hasOwnProperty(phase)) {
continue;
}
var pluginModule = EventPluginRegistry.registrationNameModules[
phasedRegistrationNames[phase]
];
if (pluginModule) {
return pluginModule;
}
}
}
return null;
},
/**
* Exposed for unit testing.
* @private

View File

@ -225,40 +225,4 @@ describe('EventPluginRegistry', () => {
'`one`.'
);
});
it('should be able to get the plugin from synthetic events', () => {
var clickDispatchConfig = {
registrationName: 'onClick',
};
var magicDispatchConfig = {
phasedRegistrationNames: {
bubbled: 'onMagicBubble',
captured: 'onMagicCapture',
},
};
var OnePlugin = createPlugin({
eventTypes: {
click: clickDispatchConfig,
magic: magicDispatchConfig,
},
});
var clickEvent = {dispatchConfig: clickDispatchConfig};
var magicEvent = {dispatchConfig: magicDispatchConfig};
expect(EventPluginRegistry.getPluginModuleForEvent(clickEvent)).toBe(null);
expect(EventPluginRegistry.getPluginModuleForEvent(magicEvent)).toBe(null);
EventPluginRegistry.injectEventPluginsByName({one: OnePlugin});
EventPluginRegistry.injectEventPluginOrder(['one']);
expect(
EventPluginRegistry.getPluginModuleForEvent(clickEvent)
).toBe(OnePlugin);
expect(
EventPluginRegistry.getPluginModuleForEvent(magicEvent)
).toBe(OnePlugin);
});
});

View File

@ -26,7 +26,7 @@ let caughtError = null;
const ReactErrorUtils = {
invokeGuardedCallback: function<A, B, C, D, E, F, Context>(
name: string | null,
func: (A, B, C, D, E, F) => void,
func: (a: A, b: B, c: C, d: D, e: E, f: F) => void,
context: Context,
a: A,
b: B,
@ -55,7 +55,7 @@ const ReactErrorUtils = {
*/
invokeGuardedCallbackAndCatchFirstError: function<A, B, C, D, E, F, Context>(
name: string | null,
func: (A, B, C, D, E, F) => void,
func: (a: A, b: B, c: C, d: D, e: E, f: F) => void,
context: Context,
a: A,
b: B,

View File

@ -106,5 +106,30 @@ describe('ReactErrorUtils', () => {
expect(err3).toBe(null); // Returns null because inner error was already captured
expect(err2).toBe(err1);
});
it(`can be shimmed (${environment})`, () => {
const ops = [];
// Override the original invokeGuardedCallback
ReactErrorUtils.invokeGuardedCallback = function(name, func, context, a) {
ops.push(a);
try {
func.call(context, a);
} catch (error) {
return error;
}
return null;
};
var err = new Error('foo');
var callback = function() {
throw err;
};
ReactErrorUtils.invokeGuardedCallbackAndCatchFirstError('foo', callback, null, 'somearg');
expect(() => ReactErrorUtils.rethrowCaughtError()).toThrow(err);
// invokeGuardedCallbackAndCatchFirstError and rethrowCaughtError close
// over ReactErrorUtils.invokeGuardedCallback so should use the
// shimmed version.
expect(ops).toEqual(['somearg']);
});
}
});

136
yarn.lock
View File

@ -1700,6 +1700,15 @@ duplexer2@^0.1.2, duplexer2@~0.1.0, duplexer2@~0.1.2:
dependencies:
readable-stream "^2.0.2"
duplexify@^3.1.2:
version "3.5.0"
resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.5.0.tgz#1aa773002e1578457e9d9d4a50b0ccaaebcbd604"
dependencies:
end-of-stream "1.0.0"
inherits "^2.0.1"
readable-stream "^2.0.0"
stream-shift "^1.0.0"
ecc-jsbn@~0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz#0fc73a9ed5f0d53c38193398523ef7e543777505"
@ -1721,6 +1730,18 @@ encoding@^0.1.11:
dependencies:
iconv-lite "~0.4.13"
end-of-stream@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.0.0.tgz#d4596e702734a93e40e9af864319eabd99ff2f0e"
dependencies:
once "~1.3.0"
end-of-stream@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.1.0.tgz#e9353258baa9108965efc41cb0ef8ade2f3cfb07"
dependencies:
once "~1.3.0"
end-of-stream@~0.1.5:
version "0.1.5"
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-0.1.5.tgz#8e177206c3c80837d85632e8b9359dfe8b2f6eaf"
@ -1849,9 +1870,6 @@ eslint-plugin-flowtype@^2.25.0:
"eslint-plugin-react-internal@file:./eslint-rules":
version "0.0.0"
"eslint-plugin-react-internal@file:eslint-rules":
version "0.0.0"
eslint-plugin-react@^6.7.1:
version "6.9.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-6.9.0.tgz#54c2e9906b76f9d10142030bdc34e9d6840a0bb2"
@ -1998,6 +2016,12 @@ expand-tilde@^1.2.1, expand-tilde@^1.2.2:
dependencies:
os-homedir "^1.0.1"
extend-shallow@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f"
dependencies:
is-extendable "^0.1.0"
extend@^1.2.1:
version "1.3.0"
resolved "https://registry.yarnpkg.com/extend/-/extend-1.3.0.tgz#d1516fb0ff5624d2ebf9123ea1dac5a1994004f8"
@ -2312,6 +2336,13 @@ glob-parent@^2.0.0:
dependencies:
is-glob "^2.0.0"
glob-parent@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae"
dependencies:
is-glob "^3.1.0"
path-dirname "^1.0.0"
glob-stream@^3.1.5:
version "3.1.18"
resolved "https://registry.yarnpkg.com/glob-stream/-/glob-stream-3.1.18.tgz#9170a5f12b790306fdfe598f313f8f7954fd143b"
@ -2323,6 +2354,21 @@ glob-stream@^3.1.5:
through2 "^0.6.1"
unique-stream "^1.0.0"
glob-stream@^6.1.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/glob-stream/-/glob-stream-6.1.0.tgz#7045c99413b3eb94888d83ab46d0b404cc7bdde4"
dependencies:
extend "^3.0.0"
glob "^7.1.1"
glob-parent "^3.1.0"
is-negated-glob "^1.0.0"
ordered-read-streams "^1.0.0"
pumpify "^1.3.5"
readable-stream "^2.1.5"
remove-trailing-separator "^1.0.1"
to-absolute-glob "^2.0.0"
unique-stream "^2.0.2"
glob-watcher@^0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/glob-watcher/-/glob-watcher-0.0.6.tgz#b95b4a8df74b39c83298b0c05c978b4d9a3b710b"
@ -2364,7 +2410,7 @@ glob@^6.0.1:
once "^1.3.0"
path-is-absolute "^1.0.0"
glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.0:
glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.0, glob@^7.1.1:
version "7.1.1"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8"
dependencies:
@ -2842,7 +2888,7 @@ invert-kv@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6"
is-absolute@^0.2.3:
is-absolute@^0.2.3, is-absolute@^0.2.5:
version "0.2.6"
resolved "https://registry.yarnpkg.com/is-absolute/-/is-absolute-0.2.6.tgz#20de69f3db942ef2d87b9c2da36f172235b1b5eb"
dependencies:
@ -2893,7 +2939,7 @@ is-equal-shallow@^0.1.3:
dependencies:
is-primitive "^2.0.0"
is-extendable@^0.1.1:
is-extendable@^0.1.0, is-extendable@^0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89"
@ -2901,6 +2947,10 @@ is-extglob@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-1.0.0.tgz#ac468177c4943405a092fc8f29760c6ffc6206c0"
is-extglob@^2.1.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
is-finite@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.0.2.tgz#cc6677695602be550ef11e8b4aa6305342b6d0aa"
@ -2923,6 +2973,12 @@ is-glob@^2.0.0, is-glob@^2.0.1:
dependencies:
is-extglob "^1.0.0"
is-glob@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a"
dependencies:
is-extglob "^2.1.0"
is-integer@^1.0.4:
version "1.0.6"
resolved "https://registry.yarnpkg.com/is-integer/-/is-integer-1.0.6.tgz#5273819fada880d123e1ac00a938e7172dd8d95e"
@ -2938,6 +2994,10 @@ is-my-json-valid@^2.10.0, is-my-json-valid@^2.12.4:
jsonpointer "^4.0.0"
xtend "^4.0.0"
is-negated-glob@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-negated-glob/-/is-negated-glob-1.0.0.tgz#6910bca5da8c95e784b5751b976cf5a10fee36d2"
is-number@^2.0.2, is-number@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f"
@ -3922,7 +3982,7 @@ object.omit@^2.0.0:
for-own "^0.1.4"
is-extendable "^0.1.1"
once@^1.3.0, once@^1.4.0:
once@^1.3.0, once@^1.3.1, once@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
dependencies:
@ -3968,6 +4028,13 @@ ordered-read-streams@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/ordered-read-streams/-/ordered-read-streams-0.1.0.tgz#fd565a9af8eb4473ba69b6ed8a34352cb552f126"
ordered-read-streams@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/ordered-read-streams/-/ordered-read-streams-1.0.0.tgz#d674a86ffcedf83d0ae06afa2918855e96d4033a"
dependencies:
is-stream "^1.0.1"
readable-stream "^2.0.1"
os-browserify@~0.1.1:
version "0.1.2"
resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.1.2.tgz#49ca0293e0b19590a5f5de10c7f265a617d8fe54"
@ -4063,6 +4130,10 @@ path-browserify@~0.0.0:
version "0.0.0"
resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.0.tgz#a0b870729aae214005b7d5032ec2cbbb0fb4451a"
path-dirname@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0"
path-exists@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-1.0.0.tgz#d5a8998eb71ef37a74c34eb0d9eba6e878eea081"
@ -4197,6 +4268,21 @@ public-encrypt@^4.0.0:
parse-asn1 "^5.0.0"
randombytes "^2.0.1"
pump@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/pump/-/pump-1.0.2.tgz#3b3ee6512f94f0e575538c17995f9f16990a5d51"
dependencies:
end-of-stream "^1.1.0"
once "^1.3.1"
pumpify@^1.3.5:
version "1.3.5"
resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.3.5.tgz#1b671c619940abcaeac0ad0e3a3c164be760993b"
dependencies:
duplexify "^3.1.2"
inherits "^2.0.1"
pump "^1.0.0"
punycode@1.3.2:
version "1.3.2"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d"
@ -4280,7 +4366,7 @@ read-pkg@^1.0.0:
isarray "0.0.1"
string_decoder "~0.10.x"
"readable-stream@^2.0.0 || ^1.1.13", readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.1.0, readable-stream@^2.1.5:
readable-stream@^2.0.0, "readable-stream@^2.0.0 || ^1.1.13", readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.1.0, readable-stream@^2.1.5:
version "2.2.2"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.2.2.tgz#a9e6fec3c7dda85f8bb1b3ba7028604556fc825e"
dependencies:
@ -4402,6 +4488,10 @@ regjsparser@^0.1.4:
dependencies:
jsesc "~0.5.0"
remove-trailing-separator@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.0.1.tgz#615ebb96af559552d4bf4057c8436d486ab63cc4"
repeat-element@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.2.tgz#ef089a178d1483baae4d93eb98b4f9e4e11d990a"
@ -4751,6 +4841,10 @@ stream-http@^2.0.0:
to-arraybuffer "^1.0.0"
xtend "^4.0.0"
stream-shift@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952"
stream-splicer@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/stream-splicer/-/stream-splicer-2.0.0.tgz#1b63be438a133e4b671cc1935197600175910d83"
@ -4902,6 +4996,13 @@ throat@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/throat/-/throat-3.0.0.tgz#e7c64c867cbb3845f10877642f7b60055b8ec0d6"
through2-filter@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/through2-filter/-/through2-filter-2.0.0.tgz#60bc55a0dacb76085db1f9dae99ab43f83d622ec"
dependencies:
through2 "~2.0.0"
xtend "~4.0.0"
through2@^0.6.1:
version "0.6.5"
resolved "https://registry.yarnpkg.com/through2/-/through2-0.6.5.tgz#41ab9c67b29d57209071410e1d7a7a968cd3ad48"
@ -4916,7 +5017,7 @@ through2@^1.0.0:
readable-stream ">=1.1.13-1 <1.2.0-0"
xtend ">=4.0.0 <4.1.0-0"
through2@^2.0.0:
through2@^2.0.0, through2@~2.0.0:
version "2.0.3"
resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.3.tgz#0004569b37c7c74ba39c43f3ced78d1ad94140be"
dependencies:
@ -4953,6 +5054,14 @@ tmpl@1.0.x:
version "1.0.4"
resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1"
to-absolute-glob@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/to-absolute-glob/-/to-absolute-glob-2.0.1.tgz#70c375805b9e3105e899ee8dbdd6a9aa108f407b"
dependencies:
extend-shallow "^2.0.1"
is-absolute "^0.2.5"
is-negated-glob "^1.0.0"
to-arraybuffer@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43"
@ -5072,6 +5181,13 @@ unique-stream@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/unique-stream/-/unique-stream-1.0.0.tgz#d59a4a75427447d9aa6c91e70263f8d26a4b104b"
unique-stream@^2.0.2:
version "2.2.1"
resolved "https://registry.yarnpkg.com/unique-stream/-/unique-stream-2.2.1.tgz#5aa003cfbe94c5ff866c4e7d668bb1c4dbadb369"
dependencies:
json-stable-stringify "^1.0.0"
through2-filter "^2.0.0"
url@~0.11.0:
version "0.11.0"
resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1"
@ -5261,7 +5377,7 @@ xml-name-validator@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-2.0.1.tgz#4d8b8f1eccd3419aa362061becef515e1e559635"
"xtend@>=4.0.0 <4.1.0-0", xtend@^4.0.0, xtend@~4.0.1:
"xtend@>=4.0.0 <4.1.0-0", xtend@^4.0.0, xtend@~4.0.0, xtend@~4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"