Merge remote-tracking branch 'upstream/master' into component-tree-hook-dev
This commit is contained in:
commit
b67a29a8ab
|
@ -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/).
|
||||
|
|
|
@ -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:
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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">
|
||||
|
|
|
@ -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
|
|
@ -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>;
|
||||
}
|
||||
|
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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
|
|
@ -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} />
|
||||
→ {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>
|
||||
);
|
||||
|
|
|
@ -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;
|
||||
};
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
})
|
||||
}
|
|
@ -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"
|
||||
|
|
|
@ -103,6 +103,8 @@ var paths = {
|
|||
},
|
||||
};
|
||||
|
||||
exports.paths = paths;
|
||||
|
||||
var moduleMapBase = {'object-assign': 'object-assign'};
|
||||
|
||||
var fbjsModules = require('fbjs/module-map');
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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."
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}));
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
|
@ -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" />,
|
||||
]);
|
||||
});
|
||||
|
||||
});
|
|
@ -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;
|
|
@ -49,7 +49,7 @@ describe('ReactPropTypesProduction', function() {
|
|||
'prop'
|
||||
);
|
||||
}).toThrowError(
|
||||
'React.PropTypes type checking code is stripped in production.'
|
||||
'Minified React error #144'
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
136
yarn.lock
|
@ -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"
|
||||
|
||||
|
|
Loading…
Reference in New Issue