mirror of
https://github.com/facebook/react.git
synced 2026-02-26 18:58:05 +00:00
Implement basic support for context
This commit is contained in:
@@ -70,11 +70,10 @@ describe('ReactContextValidator', () => {
|
||||
expect(instance.refs.child.context).toEqual({foo: 'abc'});
|
||||
});
|
||||
|
||||
it('should filter context properly in callbacks', () => {
|
||||
it('should pass next context to lifecycles', () => {
|
||||
var actualComponentWillReceiveProps;
|
||||
var actualShouldComponentUpdate;
|
||||
var actualComponentWillUpdate;
|
||||
var actualComponentDidUpdate;
|
||||
|
||||
var Parent = React.createClass({
|
||||
childContextTypes: {
|
||||
@@ -113,6 +112,45 @@ describe('ReactContextValidator', () => {
|
||||
actualComponentWillUpdate = nextContext;
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return <div />;
|
||||
},
|
||||
});
|
||||
|
||||
var 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('should pass previous context to lifecycles', () => {
|
||||
var actualComponentDidUpdate;
|
||||
|
||||
var Parent = React.createClass({
|
||||
childContextTypes: {
|
||||
foo: React.PropTypes.string.isRequired,
|
||||
bar: React.PropTypes.string.isRequired,
|
||||
},
|
||||
|
||||
getChildContext: function() {
|
||||
return {
|
||||
foo: this.props.foo,
|
||||
bar: 'bar',
|
||||
};
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return <Component />;
|
||||
},
|
||||
});
|
||||
|
||||
var Component = React.createClass({
|
||||
contextTypes: {
|
||||
foo: React.PropTypes.string,
|
||||
},
|
||||
|
||||
componentDidUpdate: function(prevProps, prevState, prevContext) {
|
||||
actualComponentDidUpdate = prevContext;
|
||||
},
|
||||
@@ -125,9 +163,6 @@ describe('ReactContextValidator', () => {
|
||||
var 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'});
|
||||
expect(actualComponentDidUpdate).toEqual({foo: 'abc'});
|
||||
});
|
||||
|
||||
|
||||
@@ -23,7 +23,13 @@ var {
|
||||
reconcileChildFibersInPlace,
|
||||
cloneChildFibers,
|
||||
} = require('ReactChildFiber');
|
||||
|
||||
var ReactTypeOfWork = require('ReactTypeOfWork');
|
||||
var {
|
||||
getMaskedContext,
|
||||
pushContextProvider,
|
||||
resetContext,
|
||||
} = require('ReactFiberContext');
|
||||
var {
|
||||
IndeterminateComponent,
|
||||
FunctionalComponent,
|
||||
@@ -144,6 +150,7 @@ module.exports = function<T, P, I, TI, C>(
|
||||
function updateFunctionalComponent(current, workInProgress) {
|
||||
var fn = workInProgress.type;
|
||||
var props = workInProgress.pendingProps;
|
||||
var context = getMaskedContext(workInProgress);
|
||||
|
||||
// TODO: Disable this before release, since it is not part of the public API
|
||||
// I use this for testing to compare the relative overhead of classes.
|
||||
@@ -159,9 +166,9 @@ module.exports = function<T, P, I, TI, C>(
|
||||
|
||||
if (__DEV__) {
|
||||
ReactCurrentOwner.current = workInProgress;
|
||||
nextChildren = fn(props);
|
||||
nextChildren = fn(props, context);
|
||||
} else {
|
||||
nextChildren = fn(props);
|
||||
nextChildren = fn(props, context);
|
||||
}
|
||||
reconcileChildren(current, workInProgress, nextChildren);
|
||||
return workInProgress.child;
|
||||
@@ -182,11 +189,14 @@ module.exports = function<T, P, I, TI, C>(
|
||||
} else {
|
||||
shouldUpdate = updateClassInstance(current, workInProgress);
|
||||
}
|
||||
const instance = workInProgress.stateNode;
|
||||
if (typeof instance.getChildContext === 'function') {
|
||||
pushContextProvider(workInProgress);
|
||||
}
|
||||
if (!shouldUpdate) {
|
||||
return bailoutOnAlreadyFinishedWork(current, workInProgress);
|
||||
}
|
||||
// Rerender
|
||||
const instance = workInProgress.stateNode;
|
||||
ReactCurrentOwner.current = workInProgress;
|
||||
const nextChildren = instance.render();
|
||||
reconcileChildren(current, workInProgress, nextChildren);
|
||||
@@ -249,13 +259,15 @@ module.exports = function<T, P, I, TI, C>(
|
||||
}
|
||||
var fn = workInProgress.type;
|
||||
var props = workInProgress.pendingProps;
|
||||
var context = getMaskedContext(workInProgress);
|
||||
|
||||
var value;
|
||||
|
||||
if (__DEV__) {
|
||||
ReactCurrentOwner.current = workInProgress;
|
||||
value = fn(props);
|
||||
value = fn(props, context);
|
||||
} else {
|
||||
value = fn(props);
|
||||
value = fn(props, context);
|
||||
}
|
||||
|
||||
if (typeof value === 'object' && value && typeof value.render === 'function') {
|
||||
@@ -355,6 +367,9 @@ module.exports = function<T, P, I, TI, C>(
|
||||
}
|
||||
|
||||
function beginWork(current : ?Fiber, workInProgress : Fiber, priorityLevel : PriorityLevel) : ?Fiber {
|
||||
if (!workInProgress.return) {
|
||||
resetContext();
|
||||
}
|
||||
if (workInProgress.pendingWorkPriority === NoWork ||
|
||||
workInProgress.pendingWorkPriority > priorityLevel) {
|
||||
return bailoutOnLowPriority(current, workInProgress);
|
||||
|
||||
@@ -15,19 +15,21 @@
|
||||
import type { Fiber } from 'ReactFiber';
|
||||
import type { UpdateQueue } from 'ReactFiberUpdateQueue';
|
||||
|
||||
var {
|
||||
getMaskedContext,
|
||||
} = require('ReactFiberContext');
|
||||
var {
|
||||
createUpdateQueue,
|
||||
addToQueue,
|
||||
addCallbackToQueue,
|
||||
mergeUpdateQueue,
|
||||
} = require('ReactFiberUpdateQueue');
|
||||
var { isMounted } = require('ReactFiberTreeReflection');
|
||||
var { getComponentName, isMounted } = require('ReactFiberTreeReflection');
|
||||
var ReactInstanceMap = require('ReactInstanceMap');
|
||||
var shallowEqual = require('shallowEqual');
|
||||
var warning = require('warning');
|
||||
var invariant = require('invariant');
|
||||
|
||||
|
||||
const isArray = Array.isArray;
|
||||
|
||||
module.exports = function(scheduleUpdate : (fiber: Fiber) => void) {
|
||||
@@ -74,7 +76,7 @@ module.exports = function(scheduleUpdate : (fiber: Fiber) => void) {
|
||||
},
|
||||
};
|
||||
|
||||
function checkShouldComponentUpdate(workInProgress, oldProps, newProps, newState) {
|
||||
function checkShouldComponentUpdate(workInProgress, oldProps, newProps, newState, newContext) {
|
||||
const updateQueue = workInProgress.updateQueue;
|
||||
if (oldProps === null || (updateQueue && updateQueue.isForced)) {
|
||||
return true;
|
||||
@@ -82,14 +84,14 @@ module.exports = function(scheduleUpdate : (fiber: Fiber) => void) {
|
||||
|
||||
const instance = workInProgress.stateNode;
|
||||
if (typeof instance.shouldComponentUpdate === 'function') {
|
||||
const shouldUpdate = instance.shouldComponentUpdate(newProps, newState);
|
||||
const shouldUpdate = instance.shouldComponentUpdate(newProps, newState, newContext);
|
||||
|
||||
if (__DEV__) {
|
||||
warning(
|
||||
shouldUpdate !== undefined,
|
||||
'%s.shouldComponentUpdate(): Returned undefined instead of a ' +
|
||||
'boolean value. Make sure to return true or false.',
|
||||
getName(workInProgress, instance)
|
||||
getComponentName(workInProgress)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -107,19 +109,9 @@ module.exports = function(scheduleUpdate : (fiber: Fiber) => void) {
|
||||
return true;
|
||||
}
|
||||
|
||||
function getName(workInProgress: Fiber, inst: any): string {
|
||||
const type = workInProgress.type;
|
||||
const constructor = inst && inst.constructor;
|
||||
return (
|
||||
type.displayName || (constructor && constructor.displayName) ||
|
||||
type.name || (constructor && constructor.name) ||
|
||||
'A Component'
|
||||
);
|
||||
}
|
||||
|
||||
function checkClassInstance(workInProgress: Fiber, inst: any) {
|
||||
if (__DEV__) {
|
||||
const name = getName(workInProgress, inst);
|
||||
const name = getComponentName(workInProgress);
|
||||
const renderPresent = inst.render;
|
||||
warning(
|
||||
renderPresent,
|
||||
@@ -194,7 +186,15 @@ module.exports = function(scheduleUpdate : (fiber: Fiber) => void) {
|
||||
invariant(
|
||||
false,
|
||||
'%s.state: must be set to an object or null',
|
||||
getName(workInProgress, inst)
|
||||
getComponentName(workInProgress)
|
||||
);
|
||||
}
|
||||
if (typeof inst.getChildContext === 'function') {
|
||||
invariant(
|
||||
typeof workInProgress.type.childContextTypes === 'object',
|
||||
'%s.getChildContext(): childContextTypes must be defined in order to ' +
|
||||
'use getChildContext().',
|
||||
getComponentName(workInProgress)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -209,7 +209,8 @@ module.exports = function(scheduleUpdate : (fiber: Fiber) => void) {
|
||||
function constructClassInstance(workInProgress : Fiber) : any {
|
||||
const ctor = workInProgress.type;
|
||||
const props = workInProgress.pendingProps;
|
||||
const instance = new ctor(props);
|
||||
const context = getMaskedContext(workInProgress);
|
||||
const instance = new ctor(props, context);
|
||||
checkClassInstance(workInProgress, instance);
|
||||
adoptClassInstance(workInProgress, instance);
|
||||
return instance;
|
||||
@@ -218,7 +219,6 @@ module.exports = function(scheduleUpdate : (fiber: Fiber) => void) {
|
||||
// Invokes the mount life-cycles on a previously never rendered instance.
|
||||
function mountClassInstance(workInProgress : Fiber) : void {
|
||||
const instance = workInProgress.stateNode;
|
||||
|
||||
const state = instance.state || null;
|
||||
|
||||
let props = workInProgress.pendingProps;
|
||||
@@ -228,6 +228,7 @@ module.exports = function(scheduleUpdate : (fiber: Fiber) => void) {
|
||||
|
||||
instance.props = props;
|
||||
instance.state = state;
|
||||
instance.context = getMaskedContext(workInProgress);
|
||||
|
||||
if (typeof instance.componentWillMount === 'function') {
|
||||
instance.componentWillMount();
|
||||
@@ -253,6 +254,7 @@ module.exports = function(scheduleUpdate : (fiber: Fiber) => void) {
|
||||
throw new Error('There should always be pending or memoized props.');
|
||||
}
|
||||
}
|
||||
const newContext = getMaskedContext(workInProgress);
|
||||
|
||||
// TODO: Should we deal with a setState that happened after the last
|
||||
// componentWillMount and before this componentWillMount? Probably
|
||||
@@ -262,7 +264,8 @@ module.exports = function(scheduleUpdate : (fiber: Fiber) => void) {
|
||||
workInProgress,
|
||||
workInProgress.memoizedProps,
|
||||
newProps,
|
||||
newState
|
||||
newState,
|
||||
newContext
|
||||
)) {
|
||||
return false;
|
||||
}
|
||||
@@ -272,6 +275,7 @@ module.exports = function(scheduleUpdate : (fiber: Fiber) => void) {
|
||||
const newInstance = constructClassInstance(workInProgress);
|
||||
newInstance.props = newProps;
|
||||
newInstance.state = newState = newInstance.state || null;
|
||||
newInstance.context = getMaskedContext(workInProgress);
|
||||
|
||||
if (typeof newInstance.componentWillMount === 'function') {
|
||||
newInstance.componentWillMount();
|
||||
@@ -300,14 +304,16 @@ module.exports = function(scheduleUpdate : (fiber: Fiber) => void) {
|
||||
throw new Error('There should always be pending or memoized props.');
|
||||
}
|
||||
}
|
||||
const oldContext = instance.context;
|
||||
const newContext = getMaskedContext(workInProgress);
|
||||
|
||||
// Note: During these life-cycles, instance.props/instance.state are what
|
||||
// ever the previously attempted to render - not the "current". However,
|
||||
// during componentDidUpdate we pass the "current" props.
|
||||
|
||||
if (oldProps !== newProps) {
|
||||
if (oldProps !== newProps || oldContext !== newContext) {
|
||||
if (typeof instance.componentWillReceiveProps === 'function') {
|
||||
instance.componentWillReceiveProps(newProps);
|
||||
instance.componentWillReceiveProps(newProps, newContext);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -328,6 +334,7 @@ module.exports = function(scheduleUpdate : (fiber: Fiber) => void) {
|
||||
|
||||
if (oldProps === newProps &&
|
||||
previousState === newState &&
|
||||
oldContext === newContext &&
|
||||
updateQueue && !updateQueue.isForced) {
|
||||
return false;
|
||||
}
|
||||
@@ -336,18 +343,20 @@ module.exports = function(scheduleUpdate : (fiber: Fiber) => void) {
|
||||
workInProgress,
|
||||
oldProps,
|
||||
newProps,
|
||||
newState
|
||||
newState,
|
||||
newContext
|
||||
)) {
|
||||
// TODO: Should this get the new props/state updated regardless?
|
||||
return false;
|
||||
}
|
||||
|
||||
if (typeof instance.componentWillUpdate === 'function') {
|
||||
instance.componentWillUpdate(newProps, newState);
|
||||
instance.componentWillUpdate(newProps, newState, newContext);
|
||||
}
|
||||
|
||||
instance.props = newProps;
|
||||
instance.state = newState;
|
||||
instance.context = newContext;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import type { HostConfig } from 'ReactFiberReconciler';
|
||||
import type { ReifiedYield } from 'ReactReifiedYield';
|
||||
|
||||
var { reconcileChildFibers } = require('ReactChildFiber');
|
||||
var { popContextProvider } = require('ReactFiberContext');
|
||||
var ReactTypeOfWork = require('ReactTypeOfWork');
|
||||
var ReactTypeOfSideEffect = require('ReactTypeOfSideEffect');
|
||||
var {
|
||||
@@ -120,10 +121,11 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
|
||||
|
||||
function completeWork(current : ?Fiber, workInProgress : Fiber) : ?Fiber {
|
||||
switch (workInProgress.tag) {
|
||||
case FunctionalComponent:
|
||||
case FunctionalComponent: {
|
||||
transferOutput(workInProgress.child, workInProgress);
|
||||
return null;
|
||||
case ClassComponent:
|
||||
}
|
||||
case ClassComponent: {
|
||||
transferOutput(workInProgress.child, workInProgress);
|
||||
// Don't use the state queue to compute the memoized state. We already
|
||||
// merged it and assigned it to the instance. Transfer it from there.
|
||||
@@ -148,8 +150,13 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
|
||||
workInProgress.callbackList = updateQueue;
|
||||
markCallback(workInProgress);
|
||||
}
|
||||
const instance = workInProgress.stateNode;
|
||||
if (typeof instance.getChildContext === 'function') {
|
||||
popContextProvider();
|
||||
}
|
||||
return null;
|
||||
case HostContainer:
|
||||
}
|
||||
case HostContainer: {
|
||||
transferOutput(workInProgress.child, workInProgress);
|
||||
// We don't know if a container has updated any children so we always
|
||||
// need to update it right now. We schedule this side-effect before
|
||||
@@ -158,7 +165,8 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
|
||||
// are invoked.
|
||||
markUpdate(workInProgress);
|
||||
return null;
|
||||
case HostComponent:
|
||||
}
|
||||
case HostComponent: {
|
||||
let newProps = workInProgress.pendingProps;
|
||||
if (current && workInProgress.stateNode != null) {
|
||||
// If we have an alternate, that means this is an update and we need to
|
||||
@@ -200,7 +208,8 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
|
||||
}
|
||||
workInProgress.memoizedProps = newProps;
|
||||
return null;
|
||||
case HostText:
|
||||
}
|
||||
case HostText: {
|
||||
let newText = workInProgress.pendingProps;
|
||||
if (current && workInProgress.stateNode != null) {
|
||||
// If we have an alternate, that means this is an update and we need to
|
||||
@@ -222,25 +231,32 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
|
||||
}
|
||||
workInProgress.memoizedProps = newText;
|
||||
return null;
|
||||
case CoroutineComponent:
|
||||
}
|
||||
case CoroutineComponent: {
|
||||
return moveCoroutineToHandlerPhase(current, workInProgress);
|
||||
case CoroutineHandlerPhase:
|
||||
}
|
||||
case CoroutineHandlerPhase: {
|
||||
transferOutput(workInProgress.stateNode, workInProgress);
|
||||
// Reset the tag to now be a first phase coroutine.
|
||||
workInProgress.tag = CoroutineComponent;
|
||||
return null;
|
||||
case YieldComponent:
|
||||
}
|
||||
case YieldComponent: {
|
||||
// Does nothing.
|
||||
return null;
|
||||
case Fragment:
|
||||
}
|
||||
case Fragment: {
|
||||
transferOutput(workInProgress.child, workInProgress);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Error cases
|
||||
case IndeterminateComponent:
|
||||
case IndeterminateComponent: {
|
||||
throw new Error('An indeterminate component should have become determinate before completing.');
|
||||
default:
|
||||
}
|
||||
default: {
|
||||
throw new Error('Unknown unit of work tag');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
91
src/renderers/shared/fiber/ReactFiberContext.js
Normal file
91
src/renderers/shared/fiber/ReactFiberContext.js
Normal file
@@ -0,0 +1,91 @@
|
||||
/**
|
||||
* 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 ReactFiberContext
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import type { Fiber } from 'ReactFiber';
|
||||
|
||||
var emptyObject = require('emptyObject');
|
||||
var invariant = require('invariant');
|
||||
var {
|
||||
getComponentName,
|
||||
} = require('ReactFiberTreeReflection');
|
||||
|
||||
if (__DEV__) {
|
||||
var checkReactTypeSpec = require('checkReactTypeSpec');
|
||||
}
|
||||
|
||||
let index = -1;
|
||||
const stack = [];
|
||||
|
||||
function getUnmaskedContext() {
|
||||
if (index === -1) {
|
||||
return emptyObject;
|
||||
}
|
||||
return stack[index];
|
||||
}
|
||||
|
||||
exports.getMaskedContext = function(fiber : Fiber) {
|
||||
const type = fiber.type;
|
||||
const contextTypes = type.contextTypes;
|
||||
if (!contextTypes) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const unmaskedContext = getUnmaskedContext();
|
||||
const context = {};
|
||||
for (let key in contextTypes) {
|
||||
context[key] = unmaskedContext[key];
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
const name = getComponentName(fiber);
|
||||
const debugID = 0; // TODO: pass a real ID
|
||||
checkReactTypeSpec(contextTypes, context, 'context', name, null, debugID);
|
||||
}
|
||||
|
||||
return context;
|
||||
};
|
||||
|
||||
exports.popContextProvider = function() {
|
||||
stack[index] = emptyObject;
|
||||
index--;
|
||||
};
|
||||
|
||||
exports.pushContextProvider = function(fiber : Fiber) {
|
||||
const instance = fiber.stateNode;
|
||||
const childContextTypes = fiber.type.childContextTypes;
|
||||
const childContext = instance.getChildContext();
|
||||
|
||||
for (let contextKey in childContext) {
|
||||
invariant(
|
||||
contextKey in childContextTypes,
|
||||
'%s.getChildContext(): key "%s" is not defined in childContextTypes.',
|
||||
getComponentName(fiber),
|
||||
contextKey
|
||||
);
|
||||
}
|
||||
if (__DEV__) {
|
||||
const name = getComponentName(fiber);
|
||||
const debugID = 0; // TODO: pass a real ID
|
||||
checkReactTypeSpec(childContextTypes, childContext, 'childContext', name, null, debugID);
|
||||
}
|
||||
|
||||
const mergedContext = Object.assign({}, getUnmaskedContext(), childContext);
|
||||
index++;
|
||||
stack[index] = mergedContext;
|
||||
};
|
||||
|
||||
exports.resetContext = function() {
|
||||
index = -1;
|
||||
};
|
||||
|
||||
@@ -114,3 +114,14 @@ exports.findCurrentHostFiber = function(component : ReactComponent<any, any, any
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
exports.getComponentName = function(fiber: Fiber): string {
|
||||
const type = fiber.type;
|
||||
const instance = fiber.stateNode;
|
||||
const constructor = instance && instance.constructor;
|
||||
return (
|
||||
type.displayName || (constructor && constructor.displayName) ||
|
||||
type.name || (constructor && constructor.name) ||
|
||||
'A Component'
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1509,4 +1509,162 @@ describe('ReactIncremental', () => {
|
||||
]);
|
||||
expect(instance.state.n).toEqual(3);
|
||||
});
|
||||
|
||||
it('merges and masks context', () => {
|
||||
var ops = [];
|
||||
|
||||
class Intl extends React.Component {
|
||||
static childContextTypes = {
|
||||
locale: React.PropTypes.string,
|
||||
};
|
||||
getChildContext() {
|
||||
return {
|
||||
locale: this.props.locale,
|
||||
};
|
||||
}
|
||||
render() {
|
||||
ops.push('Intl ' + JSON.stringify(this.context));
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
class Router extends React.Component {
|
||||
static childContextTypes = {
|
||||
route: React.PropTypes.string,
|
||||
};
|
||||
getChildContext() {
|
||||
return {
|
||||
route: this.props.route,
|
||||
};
|
||||
}
|
||||
render() {
|
||||
ops.push('Router ' + JSON.stringify(this.context));
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
class ShowLocale extends React.Component {
|
||||
static contextTypes = {
|
||||
locale: React.PropTypes.string,
|
||||
};
|
||||
|
||||
render() {
|
||||
ops.push('ShowLocale ' + JSON.stringify(this.context));
|
||||
return this.context.locale;
|
||||
}
|
||||
}
|
||||
|
||||
class ShowRoute extends React.Component {
|
||||
static contextTypes = {
|
||||
route: React.PropTypes.string,
|
||||
};
|
||||
|
||||
render() {
|
||||
ops.push('ShowRoute ' + JSON.stringify(this.context));
|
||||
return this.context.route;
|
||||
}
|
||||
}
|
||||
|
||||
function ShowBoth(props, context) {
|
||||
ops.push('ShowBoth ' + JSON.stringify(context));
|
||||
return `${context.route} in ${context.locale}`;
|
||||
}
|
||||
ShowBoth.contextTypes = {
|
||||
locale: React.PropTypes.string,
|
||||
route: React.PropTypes.string,
|
||||
};
|
||||
|
||||
class ShowNeither extends React.Component {
|
||||
render() {
|
||||
ops.push('ShowNeither ' + JSON.stringify(this.context));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
class Indirection extends React.Component {
|
||||
render() {
|
||||
ops.push('Indirection ' + JSON.stringify(this.context));
|
||||
return [
|
||||
<ShowLocale />,
|
||||
<ShowRoute />,
|
||||
<ShowNeither />,
|
||||
<Intl locale="ru">
|
||||
<ShowBoth />
|
||||
</Intl>,
|
||||
<ShowBoth />,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
ops.length = [];
|
||||
ReactNoop.render(
|
||||
<Intl locale="fr">
|
||||
<ShowLocale />
|
||||
<div>
|
||||
<ShowBoth />
|
||||
</div>
|
||||
</Intl>
|
||||
);
|
||||
ReactNoop.flush();
|
||||
expect(ops).toEqual([
|
||||
'Intl null',
|
||||
'ShowLocale {"locale":"fr"}',
|
||||
'ShowBoth {"locale":"fr"}',
|
||||
]);
|
||||
|
||||
ops.length = [];
|
||||
ReactNoop.render(
|
||||
<Intl locale="de">
|
||||
<ShowLocale />
|
||||
<div>
|
||||
<ShowBoth />
|
||||
</div>
|
||||
</Intl>
|
||||
);
|
||||
ReactNoop.flush();
|
||||
expect(ops).toEqual([
|
||||
'Intl null',
|
||||
'ShowLocale {"locale":"de"}',
|
||||
'ShowBoth {"locale":"de"}',
|
||||
]);
|
||||
|
||||
ops.length = [];
|
||||
ReactNoop.render(
|
||||
<Intl locale="sv">
|
||||
<ShowLocale />
|
||||
<div>
|
||||
<ShowBoth />
|
||||
</div>
|
||||
</Intl>
|
||||
);
|
||||
ReactNoop.flushDeferredPri(15);
|
||||
expect(ops).toEqual([
|
||||
'Intl null',
|
||||
]);
|
||||
|
||||
ops.length = [];
|
||||
ReactNoop.render(
|
||||
<Intl locale="en">
|
||||
<ShowLocale />
|
||||
<Router route="/about">
|
||||
<Indirection />
|
||||
</Router>
|
||||
<ShowBoth />
|
||||
</Intl>
|
||||
);
|
||||
ReactNoop.flush();
|
||||
expect(ops).toEqual([
|
||||
'Intl null',
|
||||
'ShowLocale {"locale":"en"}',
|
||||
'Router null',
|
||||
'Indirection null',
|
||||
'ShowLocale {"locale":"en"}',
|
||||
'ShowRoute {"route":"/about"}',
|
||||
'ShowNeither null',
|
||||
'Intl null',
|
||||
'ShowBoth {"locale":"ru","route":"/about"}',
|
||||
'ShowBoth {"locale":"en","route":"/about"}',
|
||||
'ShowBoth {"locale":"en"}',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user