packages/core/__tests__/ReactContextValidator-test.js
// This test doesn't really have a good home yet. I'm leaving it here since this
// behavior belongs to the old propTypes system yet is currently implemented
// in the core ReactCompositeComponent. It should technically live in core's
// test suite but I'll leave it here to indicate that this is an issue that
// needs to be fixed.
'use strict';
let PropTypes;
let React;
let ReactDOM;
let ReactTestUtils;
describe('ReactContextValidator', () => {
beforeEach(() => {
jest.resetModules();
PropTypes = require('prop-types');
React = require('react');
ReactDOM = require('react-dom');
ReactTestUtils = require('test-utils');
});
// TODO: This behavior creates a runtime dependency on propTypes. We should
// ensure that this is not required for ES6 classes with Flow.
it('should filter out context not in contextTypes', () => {
class Component extends React.Component {
render() {
return <div />;
}
}
Component.contextTypes = {
foo: PropTypes.string,
};
class ComponentInFooBarContext extends React.Component {
getChildContext() {
return {
foo: 'abc',
bar: 123,
};
}
render() {
return <Component ref="child" />;
}
}
ComponentInFooBarContext.childContextTypes = {
foo: PropTypes.string,
bar: PropTypes.number,
};
const instance = ReactTestUtils.renderIntoDocument(<ComponentInFooBarContext />);
expect(instance.refs.child.context).toEqual({ foo: 'abc' });
});
it('should pass next context to lifecycles', () => {
let actualComponentWillReceiveProps;
let actualShouldComponentUpdate;
let actualComponentWillUpdate;
class Parent extends React.Component {
getChildContext() {
return {
foo: this.props.foo,
bar: 'bar',
};
}
render() {
return <Component />;
}
}
Parent.childContextTypes = {
foo: PropTypes.string.isRequired,
bar: PropTypes.string.isRequired,
};
class Component extends React.Component {
UNSAFE_componentWillReceiveProps(nextProps, nextContext) {
actualComponentWillReceiveProps = nextContext;
return true;
}
shouldComponentUpdate(nextProps, nextState, nextContext) {
actualShouldComponentUpdate = nextContext;
return true;
}
UNSAFE_componentWillUpdate(nextProps, nextState, nextContext) {
actualComponentWillUpdate = nextContext;
}
render() {
return <div />;
}
}
Component.contextTypes = {
foo: PropTypes.string,
};
const container = document.createElement('div');
ReactDOM.render(<Parent foo="abc" />, container);
ReactDOM.render(<Parent foo="def" />, container);
expect(actualComponentWillReceiveProps).toEqual({ foo: 'def' });
expect(actualShouldComponentUpdate).toEqual({ foo: 'def' });
expect(actualComponentWillUpdate).toEqual({ foo: 'def' });
});
it('React.createContext 通过 Provider 传入数据', () => {
const container = document.createElement('div');
const ThemeContext = React.createContext('light');
class CreateContext extends React.Component {
render() {
// Use a Provider to pass the current theme to the tree below.
// Any component can read it, no matter how deep it is.
// In this example, we're passing "dark" as the current value.
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
}
// A component in the middle doesn't have to
// pass the theme down explicitly anymore.
function Toolbar(props) {
return (
<div class="toolbar">
<ThemedButton />
</div>
);
}
var theme = "light"
function ThemedButton(props) {
// Use a Consumer to read the current theme context.
// React will find the closest theme Provider above and use its value.
// In this example, the current theme is "dark".
return <ThemeContext.Consumer>{theme => <Button {...props} theme={theme} />}</ThemeContext.Consumer>;
}
function Button(props) {
theme = props.theme;
return (
<div>
正确值应该是传入的dark,现在传入值为:
<span style={{ color: 'red' }}>{props.theme}</span>
</div>
);
}
ReactDOM.render(<CreateContext />, container);
expect(theme).toBe("dark")
});
it('should check context types', () => {
class Component extends React.Component {
render() {
return <div />;
}
}
Component.contextTypes = {
foo: PropTypes.string.isRequired,
};
expect(() => ReactTestUtils.renderIntoDocument(<Component />)).toWarnDev(
'Warning: Failed context type: ' +
'The context `foo` is marked as required in `Component`, but its value ' +
'is `undefined`.\n' +
' in Component (at **)'
);
class ComponentInFooStringContext extends React.Component {
getChildContext() {
return {
foo: this.props.fooValue,
};
}
render() {
return <Component />;
}
}
ComponentInFooStringContext.childContextTypes = {
foo: PropTypes.string,
};
// No additional errors expected
ReactTestUtils.renderIntoDocument(<ComponentInFooStringContext fooValue={'bar'} />);
class ComponentInFooNumberContext extends React.Component {
getChildContext() {
return {
foo: this.props.fooValue,
};
}
render() {
return <Component />;
}
}
ComponentInFooNumberContext.childContextTypes = {
foo: PropTypes.number,
};
expect(() => ReactTestUtils.renderIntoDocument(<ComponentInFooNumberContext fooValue={123} />)).toWarnDev(
'Warning: Failed context type: ' +
'Invalid context `foo` of type `number` supplied ' +
'to `Component`, expected `string`.\n' +
' in Component (at **)\n' +
' in ComponentInFooNumberContext (at **)'
);
});
it('should check child context types', () => {
class Component extends React.Component {
getChildContext() {
return this.props.testContext;
}
render() {
return <div />;
}
}
Component.childContextTypes = {
foo: PropTypes.string.isRequired,
bar: PropTypes.number,
};
expect(() => ReactTestUtils.renderIntoDocument(<Component testContext={{ bar: 123 }} />)).toWarnDev(
'Warning: Failed child context type: ' +
'The child context `foo` is marked as required in `Component`, but its ' +
'value is `undefined`.\n' +
' in Component (at **)'
);
expect(() => ReactTestUtils.renderIntoDocument(<Component testContext={{ foo: 123 }} />)).toWarnDev(
'Warning: Failed child context type: ' +
'Invalid child context `foo` of type `number` ' +
'supplied to `Component`, expected `string`.\n' +
' in Component (at **)'
);
// No additional errors expected
ReactTestUtils.renderIntoDocument(<Component testContext={{ foo: 'foo', bar: 123 }} />);
ReactTestUtils.renderIntoDocument(<Component testContext={{ foo: 'foo' }} />);
});
// TODO (bvaughn) Remove this test and the associated behavior in the future.
// It has only been added in Fiber to match the (unintentional) behavior in Stack.
it('should warn (but not error) if getChildContext method is missing', () => {
class ComponentA extends React.Component {
static childContextTypes = {
foo: PropTypes.string.isRequired,
};
render() {
return <div />;
}
}
class ComponentB extends React.Component {
static childContextTypes = {
foo: PropTypes.string.isRequired,
};
render() {
return <div />;
}
}
expect(() => ReactTestUtils.renderIntoDocument(<ComponentA />)).toWarnDev(
'Warning: ComponentA.childContextTypes is specified but there is no ' +
'getChildContext() method on the instance. You can either define ' +
'getChildContext() on ComponentA or remove childContextTypes from it.'
);
// Warnings should be deduped by component type
ReactTestUtils.renderIntoDocument(<ComponentA />);
expect(() => ReactTestUtils.renderIntoDocument(<ComponentB />)).toWarnDev(
'Warning: ComponentB.childContextTypes is specified but there is no ' +
'getChildContext() method on the instance. You can either define ' +
'getChildContext() on ComponentB or remove childContextTypes from it.'
);
});
// TODO (bvaughn) Remove this test and the associated behavior in the future.
// It has only been added in Fiber to match the (unintentional) behavior in Stack.
it('should pass parent context if getChildContext method is missing', () => {
class ParentContextProvider extends React.Component {
static childContextTypes = {
foo: PropTypes.string,
};
getChildContext() {
return {
foo: 'FOO',
};
}
render() {
return <MiddleMissingContext />;
}
}
class MiddleMissingContext extends React.Component {
static childContextTypes = {
bar: PropTypes.string.isRequired,
};
render() {
return <ChildContextConsumer />;
}
}
let childContext;
class ChildContextConsumer extends React.Component {
render() {
childContext = this.context;
return <div />;
}
}
ChildContextConsumer.contextTypes = {
bar: PropTypes.string.isRequired,
foo: PropTypes.string.isRequired,
};
expect(() => ReactTestUtils.renderIntoDocument(<ParentContextProvider />)).toWarnDev([
'Warning: MiddleMissingContext.childContextTypes is specified but there is no getChildContext() method on the ' +
'instance. You can either define getChildContext() on MiddleMissingContext or remove childContextTypes from ' +
'it.',
'Warning: Failed context type: The context `bar` is marked as required in `ChildContextConsumer`, but its ' +
'value is `undefined`.',
]);
expect(childContext.bar).toBeUndefined();
expect(childContext.foo).toBe('FOO');
});
it('setState后子组件的context处理', () => {
const container = document.createElement('div');
class App extends React.Component {
state = {
text: 111,
};
getChildContext() {
return {
table: {
title: 'title',
},
};
}
static childContextTypes = {
table: function() {
return null;
},
};
render() {
return (
<div>
<Head ref="head" className="xxx" />
</div>
);
}
}
class Head extends React.Component {
constructor(a, b) {
super(a, b);
this.state = {
a: 3333,
};
}
render() {
return (
<strong>
{this.state.a}
<b>{this.context.table.title}</b>
</strong>
);
}
}
Head.contextTypes = {
table: function() {
return null;
},
};
var a = ReactDOM.render(<App id="app" />, container);
expect(container.textContent.trim()).toBe('3333title');
a.refs.head.setState({
a: 4444,
});
expect(container.textContent.trim()).toBe('4444title');
});
});