Added warning if owner-based and parent-based contexts differ.

This commit is contained in:
Jim
2014-11-17 17:02:20 -08:00
parent fba8be3b98
commit 081feeb2dd
15 changed files with 254 additions and 93 deletions
+9 -4
View File
@@ -17,14 +17,17 @@ var ReactMarkupChecksum = require('ReactMarkupChecksum');
var ReactServerRenderingTransaction =
require('ReactServerRenderingTransaction');
var emptyObject = require('emptyObject');
var instantiateReactComponent = require('instantiateReactComponent');
var invariant = require('invariant');
/**
* @param {ReactElement} element
* @param {?object} context
* @return {string} the HTML markup
*/
function renderToString(element) {
function renderToString(element, context) {
if(context === undefined) context = emptyObject;
invariant(
ReactElement.isValidElement(element),
'renderToString(): You must pass a valid ReactElement.'
@@ -37,7 +40,7 @@ function renderToString(element) {
return transaction.perform(function() {
var componentInstance = instantiateReactComponent(element, null);
var markup = componentInstance.mountComponent(id, transaction, 0);
var markup = componentInstance.mountComponent(id, transaction, 0, context);
return ReactMarkupChecksum.addChecksumToMarkup(markup);
}, null);
} finally {
@@ -47,10 +50,12 @@ function renderToString(element) {
/**
* @param {ReactElement} element
* @param {?object} context
* @return {string} the HTML markup, without the extra React ID and checksum
* (for generating static pages)
*/
function renderToStaticMarkup(element) {
function renderToStaticMarkup(element, context) {
if(context === undefined) context = emptyObject;
invariant(
ReactElement.isValidElement(element),
'renderToStaticMarkup(): You must pass a valid ReactElement.'
@@ -63,7 +68,7 @@ function renderToStaticMarkup(element) {
return transaction.perform(function() {
var componentInstance = instantiateReactComponent(element, null);
return componentInstance.mountComponent(id, transaction, 0);
return componentInstance.mountComponent(id, transaction, 0, context);
}, null);
} finally {
ReactServerRenderingTransaction.release(transaction);
+22 -13
View File
@@ -168,19 +168,21 @@ ReactDOMComponent.Mixin = {
mountComponent: ReactPerf.measure(
'ReactDOMComponent',
'mountComponent',
function(rootID, transaction, mountDepth) {
function(rootID, transaction, mountDepth, context) {
invariant(context !== undefined, "Context is required parameter");
ReactComponent.Mixin.mountComponent.call(
this,
rootID,
transaction,
mountDepth
mountDepth,
context
);
this._previousStyleCopy = null;
assertValidProps(this._currentElement.props);
var closeTag = omittedCloseTags[this._tag] ? '' : '</' + this._tag + '>';
return (
this._createOpenTagMarkupAndPutListeners(transaction) +
this._createContentMarkup(transaction) +
this._createContentMarkup(transaction, context) +
closeTag
);
}
@@ -242,9 +244,10 @@ ReactDOMComponent.Mixin = {
*
* @private
* @param {ReactReconcileTransaction|ReactServerRenderingTransaction} transaction
* @param {object} context
* @return {string} Content markup.
*/
_createContentMarkup: function(transaction) {
_createContentMarkup: function(transaction, context) {
var prefix = '';
if (this._tag === 'listing' ||
this._tag === 'pre' ||
@@ -272,7 +275,8 @@ ReactDOMComponent.Mixin = {
} else if (childrenToUse != null) {
var mountImages = this.mountChildren(
childrenToUse,
transaction
transaction,
context
);
return prefix + mountImages.join('');
}
@@ -280,7 +284,8 @@ ReactDOMComponent.Mixin = {
return prefix;
},
receiveComponent: function(nextElement, transaction) {
receiveComponent: function(nextElement, transaction, context) {
invariant(context !== undefined, "Context is required parameter");
if (nextElement === this._currentElement &&
nextElement._owner != null) {
// Since elements are immutable after the owner is rendered,
@@ -295,7 +300,7 @@ ReactDOMComponent.Mixin = {
var prevElement = this._currentElement;
this._currentElement = nextElement;
this.updateComponent(transaction, prevElement, nextElement);
this.updateComponent(transaction, prevElement, nextElement, context);
},
/**
@@ -311,16 +316,19 @@ ReactDOMComponent.Mixin = {
updateComponent: ReactPerf.measure(
'ReactDOMComponent',
'updateComponent',
function(transaction, prevElement, nextElement) {
function(transaction, prevElement, nextElement, context) {
if(context === undefined) throw new Error("Context required for mounting");
if(context === null) throw new Error("Assert: context is not null");
assertValidProps(this._currentElement.props);
ReactComponent.Mixin.updateComponent.call(
this,
transaction,
prevElement,
nextElement
nextElement,
context
);
this._updateDOMProperties(prevElement.props, transaction);
this._updateDOMChildren(prevElement.props, transaction);
this._updateDOMChildren(prevElement.props, transaction, context);
}
),
@@ -428,7 +436,8 @@ ReactDOMComponent.Mixin = {
* @param {object} lastProps
* @param {ReactReconcileTransaction} transaction
*/
_updateDOMChildren: function(lastProps, transaction) {
_updateDOMChildren: function(lastProps, transaction, context) {
invariant(context !== undefined, "Context is required parameter");
var nextProps = this._currentElement.props;
var lastContent =
@@ -452,7 +461,7 @@ ReactDOMComponent.Mixin = {
var lastHasContentOrHtml = lastContent != null || lastHtml != null;
var nextHasContentOrHtml = nextContent != null || nextHtml != null;
if (lastChildren != null && nextChildren == null) {
this.updateChildren(null, transaction);
this.updateChildren(null, transaction, context);
} else if (lastHasContentOrHtml && !nextHasContentOrHtml) {
this.updateTextContent('');
}
@@ -469,7 +478,7 @@ ReactDOMComponent.Mixin = {
);
}
} else if (nextChildren != null) {
this.updateChildren(nextChildren, transaction);
this.updateChildren(nextChildren, transaction, context);
}
},
+1 -1
View File
@@ -66,7 +66,7 @@ assign(ReactDOMTextComponent.prototype, {
* @return {string} Markup for this text node.
* @internal
*/
mountComponent: function(rootID, transaction, mountDepth) {
mountComponent: function(rootID, transaction, mountDepth, context) {
this._rootNodeID = rootID;
var escapedText = escapeTextForBrowser(this._stringText);
+2 -1
View File
@@ -22,6 +22,7 @@ var ReactMarkupChecksum = require('ReactMarkupChecksum');
var ReactPerf = require('ReactPerf');
var ReactUpdates = require('ReactUpdates');
var emptyObject = require('emptyObject');
var containsNode = require('containsNode');
var deprecated = require('deprecated');
var getReactRootElementInContainer = require('getReactRootElementInContainer');
@@ -224,7 +225,7 @@ function mountComponentIntoNode(
container,
transaction,
shouldReuseMarkup) {
var markup = this.mountComponent(rootID, transaction, 0);
var markup = this.mountComponent(rootID, transaction, 0, emptyObject);
ReactMount._mountImageIntoNode(markup, container, shouldReuseMarkup);
}
@@ -283,7 +283,7 @@ describe('ReactDOMComponent', function() {
genMarkup = function(props) {
var transaction = new ReactReconcileTransaction();
return (new NodeStub(props))._createContentMarkup(transaction);
return (new NodeStub(props))._createContentMarkup(transaction, {});
};
this.addMatchers({
@@ -328,7 +328,7 @@ describe('ReactDOMComponent', function() {
_owner: null,
_context: null
});
return stubComponent.mountComponent('test', transaction, 0);
return stubComponent.mountComponent('test', transaction, 0, {});
};
});
+3 -3
View File
@@ -32,7 +32,7 @@ describe('Danger', function() {
it('should render markup', function() {
var markup = instantiateReactComponent(
<div />
).mountComponent('.rX', transaction, 0);
).mountComponent('.rX', transaction, 0, {});
var output = Danger.dangerouslyRenderMarkup([markup])[0];
expect(output.nodeName).toBe('DIV');
@@ -44,7 +44,7 @@ describe('Danger', function() {
).mountComponent(
'.rX',
transaction,
0
0, {}
);
var output = Danger.dangerouslyRenderMarkup([markup])[0];
@@ -55,7 +55,7 @@ describe('Danger', function() {
it('should render wrapped markup', function() {
var markup = instantiateReactComponent(
<th />
).mountComponent('.rX', transaction, 0);
).mountComponent('.rX', transaction, 0, {});
var output = Danger.dangerouslyRenderMarkup([markup])[0];
expect(output.nodeName).toBe('TH');
+4 -3
View File
@@ -114,7 +114,6 @@ var ReactComponent = {
// We keep the old element and a reference to the pending element
// to track updates.
this._currentElement = element;
this._rootNodeID = null;
this._mountIndex = 0;
this._mountDepth = 0;
@@ -134,7 +133,8 @@ var ReactComponent = {
* @return {?string} Rendered markup to be inserted into the DOM.
* @internal
*/
mountComponent: function(rootID, transaction, mountDepth) {
mountComponent: function(rootID, transaction, mountDepth, context) {
invariant(context !== undefined, "Context is required parameter");
var ref = this._currentElement.ref;
if (ref != null) {
var owner = this._currentElement._owner;
@@ -173,7 +173,8 @@ var ReactComponent = {
* @param {object} nextElement
* @internal
*/
updateComponent: function(transaction, prevElement, nextElement) {
updateComponent: function(transaction, prevElement, nextElement, context) {
invariant(context !== undefined, "Context is required parameter");
// If either the owner or a `ref` has changed, make sure the newest owner
// has stored a reference to `this`, and the previous owner (if different)
// has forgotten the reference to `this`. We use the element instead
+95 -27
View File
@@ -20,10 +20,12 @@ var ReactPerf = require('ReactPerf');
var ReactPropTypeLocations = require('ReactPropTypeLocations');
var ReactUpdates = require('ReactUpdates');
var emptyObject = require('emptyObject');
var assign = require('Object.assign');
var emptyObject = require('emptyObject');
var invariant = require('invariant');
var keyMirror = require('keyMirror');
var monitorCodeUse = require('monitorCodeUse');
var shouldUpdateReactComponent = require('shouldUpdateReactComponent');
var warning = require('warning');
@@ -116,6 +118,7 @@ var ReactCompositeComponentMixin = assign({},
this._instance.refs = emptyObject;
this._pendingElement = null;
this._pendingContext = null;
this._pendingState = null;
this._pendingForceUpdate = false;
this._compositeLifeCycleState = null;
@@ -125,6 +128,8 @@ var ReactCompositeComponentMixin = assign({},
// Children can be either an array or more than one argument
ReactComponent.Mixin.construct.apply(this, arguments);
this._context = null;
// See ReactUpdates.
this._pendingCallbacks = null;
},
@@ -152,14 +157,18 @@ var ReactCompositeComponentMixin = assign({},
mountComponent: ReactPerf.measure(
'ReactCompositeComponent',
'mountComponent',
function(rootID, transaction, mountDepth) {
function(rootID, transaction, mountDepth, context) {
invariant(context !== undefined, "Context is required parameter");
ReactComponent.Mixin.mountComponent.call(
this,
rootID,
transaction,
mountDepth
mountDepth,
context
);
this._context = context;
var inst = this._instance;
// Store a reference from the instance back to the internal representation
@@ -168,6 +177,9 @@ var ReactCompositeComponentMixin = assign({},
this._compositeLifeCycleState = CompositeLifeCycle.MOUNTING;
inst.context = this._processContext(this._currentElement._context);
if (__DEV__) {
this._warnIfContextsDiffer(this._currentElement._context, context);
}
inst.props = this._processProps(this._currentElement.props);
var initialState = inst.getInitialState ? inst.getInitialState() : null;
@@ -211,7 +223,8 @@ var ReactCompositeComponentMixin = assign({},
var markup = this._renderedComponent.mountComponent(
rootID,
transaction,
mountDepth + 1
mountDepth + 1,
this._processChildContext(context)
);
if (inst.componentDidMount) {
transaction.getReactMountReady().enqueue(inst.componentDidMount, inst);
@@ -246,6 +259,8 @@ var ReactCompositeComponentMixin = assign({},
ReactComponent.Mixin.unmountComponent.call(this);
this._context = null;
// Delete the reference from the instance to this internal representation
// which allow the internals to be properly cleaned up even if the user
// leaks a reference to the public instance.
@@ -403,18 +418,17 @@ var ReactCompositeComponentMixin = assign({},
_processContext: function(context) {
var maskedContext = null;
var contextTypes = this._instance.constructor.contextTypes;
if (contextTypes) {
maskedContext = {};
for (var contextName in contextTypes) {
maskedContext[contextName] = context[contextName];
}
if (__DEV__) {
this._checkPropTypes(
contextTypes,
maskedContext,
ReactPropTypeLocations.context
);
}
if (!contextTypes) return emptyObject;
maskedContext = {};
for (var contextName in contextTypes) {
maskedContext[contextName] = context[contextName];
}
if (__DEV__) {
this._checkPropTypes(
contextTypes,
maskedContext,
ReactPropTypeLocations.context
);
}
return maskedContext;
},
@@ -502,7 +516,8 @@ var ReactCompositeComponentMixin = assign({},
}
},
receiveComponent: function(nextElement, transaction) {
receiveComponent: function(nextElement, transaction, context) {
invariant(context !== undefined, "Context is required parameter");
if (nextElement === this._currentElement &&
nextElement._owner != null) {
// Since elements are immutable after the owner is rendered,
@@ -516,6 +531,7 @@ var ReactCompositeComponentMixin = assign({},
}
this._pendingElement = nextElement;
this._pendingContext = context;
this.performUpdateIfNecessary(transaction);
},
@@ -537,6 +553,7 @@ var ReactCompositeComponentMixin = assign({},
if (this._pendingElement == null &&
this._pendingState == null &&
this._pendingContext == null &&
!this._pendingForceUpdate) {
return;
}
@@ -548,13 +565,46 @@ var ReactCompositeComponentMixin = assign({},
this._pendingElement = null;
}
var prevContext = this._context;
var nextContext = prevContext;
if (this._pendingContext != null) {
nextContext = this._pendingContext;
this._pendingContext = null;
}
this.updateComponent(
transaction,
prevElement,
nextElement
nextElement,
prevContext,
nextContext
);
},
/**
* Compare two contexts, warning if they are different
* TODO: Remove this check when owner-context is removed
*/
_warnIfContextsDiffer: function(ownerBasedContext, parentBasedContext) {
invariant(ownerBasedContext !== undefined, "Owner based context is required parameter");
invariant(parentBasedContext !== undefined, "Parent based ontext is required parameter");
var ownerKeys = Object.keys(ownerBasedContext).sort();
var parentKeys = Object.keys(parentBasedContext).sort();
if (ownerKeys.length != parentKeys.length || ownerKeys.toString() != parentKeys.toString()) {
var message = ("owner based context (keys: " +
Object.keys(ownerBasedContext) + ") does not equal parent based" +
" context (keys: "+Object.keys(parentBasedContext)+")" +
" while mounting " +
(this._instance.constructor.displayName || 'ReactCompositeComponent'));
console.warn(message);
monitorCodeUse('contexts_differ', {
ownerBasedKeys: Object.keys(ownerBasedContext),
parentBasedKeys: Object.keys(parentBasedContext),
mounting: (this._instance.constructor.displayName || 'ReactCompositeComponent')
});
}
},
/**
* Perform an update to a mounted component. The componentWillReceiveProps and
* shouldComponentUpdate methods are called, then (assuming the update isn't
@@ -573,13 +623,20 @@ var ReactCompositeComponentMixin = assign({},
updateComponent: ReactPerf.measure(
'ReactCompositeComponent',
'updateComponent',
function(transaction, prevParentElement, nextParentElement) {
function(
transaction,
prevParentElement,
nextParentElement,
prevUnmaskedContext,
nextUnmaskedContext) {
// Update refs regardless of what shouldComponentUpdate returns
ReactComponent.Mixin.updateComponent.call(
this,
transaction,
prevParentElement,
nextParentElement
nextParentElement,
prevUnmaskedContext,
nextUnmaskedContext
);
var inst = this._instance;
@@ -623,6 +680,7 @@ var ReactCompositeComponentMixin = assign({},
// If it's determined that a component should not update, we still want
// to set props and state but we shortcut the rest of the update.
this._currentElement = nextParentElement;
this._context = nextUnmaskedContext;
inst.props = nextProps;
inst.state = nextState;
inst.context = nextContext;
@@ -636,7 +694,8 @@ var ReactCompositeComponentMixin = assign({},
nextProps,
nextState,
nextContext,
transaction
transaction,
nextUnmaskedContext
);
}),
@@ -649,6 +708,7 @@ var ReactCompositeComponentMixin = assign({},
* @param {?object} nextState Next object to set as state.
* @param {?object} nextContext Next public object to set as context.
* @param {ReactReconcileTransaction} transaction
* @param {?object} unmaskedContext
* @private
*/
_performComponentUpdate: function(
@@ -656,8 +716,11 @@ var ReactCompositeComponentMixin = assign({},
nextProps,
nextState,
nextContext,
transaction
transaction,
unmaskedContext
) {
invariant(unmaskedContext !== undefined, "Context required for mounting");
invariant(unmaskedContext !== null, "Context must be non-null");
var inst = this._instance;
var prevProps = inst.props;
@@ -669,11 +732,12 @@ var ReactCompositeComponentMixin = assign({},
}
this._currentElement = nextElement;
this._context = unmaskedContext;
inst.props = nextProps;
inst.state = nextState;
inst.context = nextContext;
this._updateRenderedComponent(transaction);
this._updateRenderedComponent(transaction, nextContext);
if (inst.componentDidUpdate) {
transaction.getReactMountReady().enqueue(
@@ -689,14 +753,16 @@ var ReactCompositeComponentMixin = assign({},
* @param {ReactReconcileTransaction} transaction
* @internal
*/
_updateRenderedComponent: function(transaction) {
_updateRenderedComponent: function(transaction, context) {
invariant(context !== undefined, "Context is required parameter");
var prevComponentInstance = this._renderedComponent;
var prevRenderedElement = prevComponentInstance._currentElement;
var nextRenderedElement = this._renderValidatedComponent();
if (shouldUpdateReactComponent(prevRenderedElement, nextRenderedElement)) {
prevComponentInstance.receiveComponent(
nextRenderedElement,
transaction
transaction,
this._processChildContext(context)
);
} else {
// These two IDs are actually the same! But nothing should rely on that.
@@ -711,7 +777,8 @@ var ReactCompositeComponentMixin = assign({},
var nextMarkup = this._renderedComponent.mountComponent(
thisID,
transaction,
this._mountDepth + 1
this._mountDepth + 1,
context
);
ReactComponent.BackendIDOperations.dangerouslyReplaceNodeWithMarkupByID(
prevComponentID,
@@ -828,12 +895,13 @@ var ShallowMixin = assign({},
* @final
* @internal
*/
mountComponent: function(rootID, transaction, mountDepth) {
mountComponent: function(rootID, transaction, mountDepth, context) {
ReactComponent.Mixin.mountComponent.call(
this,
rootID,
transaction,
mountDepth
mountDepth,
context
);
var inst = this._instance;
+2 -1
View File
@@ -12,6 +12,7 @@
"use strict";
var assign = require('Object.assign');
var emptyObject = require('emptyObject');
/**
* Keeps track of the current context.
@@ -25,7 +26,7 @@ var ReactContext = {
* @internal
* @type {object}
*/
current: {},
current: emptyObject,
/**
* Temporarily extends the current context while executing scopedCallback.
+21 -9
View File
@@ -17,6 +17,7 @@ var ReactMultiChildUpdateTypes = require('ReactMultiChildUpdateTypes');
var flattenChildren = require('flattenChildren');
var instantiateReactComponent = require('instantiateReactComponent');
var invariant = require('invariant');
var shouldUpdateReactComponent = require('shouldUpdateReactComponent');
/**
@@ -178,7 +179,8 @@ var ReactMultiChild = {
* @return {array} An array of mounted representations.
* @internal
*/
mountChildren: function(nestedChildren, transaction) {
mountChildren: function(nestedChildren, transaction, context) {
invariant(context !== undefined, "Context is required parameter");
var children = flattenChildren(nestedChildren);
var mountImages = [];
var index = 0;
@@ -195,7 +197,8 @@ var ReactMultiChild = {
var mountImage = childInstance.mountComponent(
rootID,
transaction,
this._mountDepth + 1
this._mountDepth + 1,
context
);
childInstance._mountIndex = index;
mountImages.push(mountImage);
@@ -240,11 +243,12 @@ var ReactMultiChild = {
* @param {ReactReconcileTransaction} transaction
* @internal
*/
updateChildren: function(nextNestedChildren, transaction) {
updateChildren: function(nextNestedChildren, transaction, context) {
invariant(context !== undefined, "Context is required parameter");
updateDepth++;
var errorThrown = true;
try {
this._updateChildren(nextNestedChildren, transaction);
this._updateChildren(nextNestedChildren, transaction, context);
errorThrown = false;
} finally {
updateDepth--;
@@ -263,7 +267,8 @@ var ReactMultiChild = {
* @final
* @protected
*/
_updateChildren: function(nextNestedChildren, transaction) {
_updateChildren: function(nextNestedChildren, transaction, context) {
invariant(context !== undefined, "Context is required parameter");
var nextChildren = flattenChildren(nextNestedChildren);
var prevChildren = this._renderedChildren;
if (!nextChildren && !prevChildren) {
@@ -284,7 +289,7 @@ var ReactMultiChild = {
if (shouldUpdateReactComponent(prevElement, nextElement)) {
this.moveChild(prevChild, nextIndex, lastIndex);
lastIndex = Math.max(prevChild._mountIndex, lastIndex);
prevChild.receiveComponent(nextElement, transaction);
prevChild.receiveComponent(nextElement, transaction, context);
prevChild._mountIndex = nextIndex;
} else {
if (prevChild) {
@@ -298,7 +303,7 @@ var ReactMultiChild = {
null
);
this._mountChildByNameAtIndex(
nextChildInstance, name, nextIndex, transaction
nextChildInstance, name, nextIndex, transaction, context
);
}
nextIndex++;
@@ -389,13 +394,20 @@ var ReactMultiChild = {
* @param {ReactReconcileTransaction} transaction
* @private
*/
_mountChildByNameAtIndex: function(child, name, index, transaction) {
_mountChildByNameAtIndex: function(
child,
name,
index,
transaction,
context) {
invariant(context !== undefined, "Context is required parameter");
// Inlined for performance, see `ReactInstanceHandles.createReactID`.
var rootID = this._rootNodeID + name;
var mountImage = child.mountComponent(
rootID,
transaction,
this._mountDepth + 1
this._mountDepth + 1,
context
);
child._mountIndex = index;
this.createChild(child, mountImage);
+1
View File
@@ -140,6 +140,7 @@ function runBatchedUpdates(transaction) {
// be here, but we assume that it has cleared its _pendingCallbacks and
// that performUpdateIfNecessary is a noop.
var component = dirtyComponents[i];
// If performUpdateIfNecessary happens to enqueue any new updates, we
// shouldn't execute the callbacks until the next render happens, so
// stash the callbacks first
@@ -961,6 +961,29 @@ describe('ReactCompositeComponent', function() {
reactComponentExpect(grandchildInstance).scalarContextEqual({foo: 'bar', depth: 1});
});
it('warn if contexts differ', function() {
var Component = React.createClass({
contextTypes: {
foo: ReactPropTypes.string.isRequired
},
render: function() {
return <div />;
}
});
React.withContext({foo: 'bar'}, function() {
ReactTestUtils.renderIntoDocument(<Component />);
});
expect(console.warn.mock.calls.length).toBe(2);
expect(console.warn.mock.calls[1][0]).toBe(
'owner based context (keys: foo) does not equal parent based ' +
'context (keys: ) while mounting ReactCompositeComponent'
);
});
it('should check context types', function() {
var Component = React.createClass({
contextTypes: {
@@ -979,21 +1002,50 @@ describe('ReactCompositeComponent', function() {
'Warning: Required context `foo` was not specified in `Component`.'
);
React.withContext({foo: 'bar'}, function() {
ReactTestUtils.renderIntoDocument(<Component />);
var ComponentInFooStringContext = React.createClass({
childContextTypes: {
foo: ReactPropTypes.string
},
getChildContext: function() {
return {
foo: this.props.fooValue
};
},
render: function() {
return <Component />;
}
});
ReactTestUtils.renderIntoDocument(<ComponentInFooStringContext fooValue={'bar'} />);
// Previous call should not error
expect(console.warn.mock.calls.length).toBe(1);
React.withContext({foo: 123}, function() {
ReactTestUtils.renderIntoDocument(<Component />);
var ComponentInFooNumberContext = React.createClass({
childContextTypes: {
foo: ReactPropTypes.number
},
getChildContext: function() {
return {
foo: this.props.fooValue
};
},
render: function() {
return <Component />;
}
});
ReactTestUtils.renderIntoDocument(<ComponentInFooNumberContext fooValue={123} />);
expect(console.warn.mock.calls.length).toBe(2);
expect(console.warn.mock.calls[1][0]).toBe(
'Warning: Invalid context `foo` of type `number` supplied ' +
'to `Component`, expected `string`.'
'to `Component`, expected `string`.' +
' Check the render method of `ComponentInFooNumberContext`.'
);
});
@@ -1014,16 +1066,18 @@ describe('ReactCompositeComponent', function() {
});
ReactTestUtils.renderIntoDocument(<Component testContext={{bar: 123}} />);
expect(console.warn.mock.calls.length).toBe(1);
expect(console.warn.mock.calls.length).toBe(2);
expect(console.warn.mock.calls[0][0]).toBe(
'Warning: Required child context `foo` was not specified in `Component`.'
);
expect(console.warn.mock.calls[1][0]).toBe(
'Warning: Required child context `foo` was not specified in `Component`.'
);
ReactTestUtils.renderIntoDocument(<Component testContext={{foo: 123}} />);
expect(console.warn.mock.calls.length).toBe(2);
expect(console.warn.mock.calls[1][0]).toBe(
expect(console.warn.mock.calls.length).toBe(4);
expect(console.warn.mock.calls[3][0]).toBe(
'Warning: Invalid child context `foo` of type `number` ' +
'supplied to `Component`, expected `string`.'
);
@@ -1037,7 +1091,7 @@ describe('ReactCompositeComponent', function() {
);
// Previous calls should not log errors
expect(console.warn.mock.calls.length).toBe(2);
expect(console.warn.mock.calls.length).toBe(4);
});
it('should filter out context not in contextTypes', function() {
@@ -1051,11 +1105,26 @@ describe('ReactCompositeComponent', function() {
}
});
var instance = React.withContext({foo: 'abc', bar: 123}, function() {
return <Component />;
var ComponentInFooBarContext = React.createClass({
childContextTypes: {
foo: ReactPropTypes.string,
bar: ReactPropTypes.number
},
getChildContext: function() {
return {
foo: 'abc',
bar: 123
};
},
render: function() {
return <Component />;
}
});
instance = ReactTestUtils.renderIntoDocument(instance);
reactComponentExpect(instance).scalarContextEqual({foo: 'abc'});
var instance = ReactTestUtils.renderIntoDocument(<ComponentInFooBarContext />);
reactComponentExpect(instance).expectRenderedChild().scalarContextEqual({foo: 'abc'});
});
it('should filter context properly in callbacks', function() {
+1 -7
View File
@@ -81,7 +81,7 @@ describe('ReactElement', function() {
expect(element.props).toEqual({foo:'56'});
});
it('preserves the context on the element', function() {
it('preserves the legacy context on the element', function() {
var Component = React.createFactory(ComponentClass);
var element;
@@ -108,12 +108,6 @@ describe('ReactElement', function() {
var element;
var Wrapper = React.createClass({
childContextTypes: {
foo: React.PropTypes.string
},
getChildContext: function() {
return { foo: 'bar' };
},
render: function() {
element = Component();
return element;
+5 -5
View File
@@ -328,23 +328,23 @@ assign(
ReactCompositeComponent.ShallowMixin
);
ReactShallowRenderer.prototype.render = function(element) {
ReactShallowRenderer.prototype.render = function(element, context) {
var transaction = ReactUpdates.ReactReconcileTransaction.getPooled();
this._render(element, transaction);
this._render(element, transaction, context);
ReactUpdates.ReactReconcileTransaction.release(transaction);
};
ReactShallowRenderer.prototype._render = function(element, transaction) {
ReactShallowRenderer.prototype._render = function(element, transaction, context) {
if (!this._instance) {
var rootID = ReactInstanceHandles.createReactRootID();
var instance = new ShallowComponentWrapper(new element.type(element.props));
instance.construct(element);
instance.mountComponent(rootID, transaction, 0);
instance.mountComponent(rootID, transaction, 0, context);
this._instance = instance;
} else {
this._instance.receiveComponent(element, transaction);
this._instance.receiveComponent(element, transaction, context);
}
};
+3 -3
View File
@@ -46,7 +46,7 @@ describe('ReactTestUtils', function() {
});
var shallowRenderer = ReactTestUtils.createRenderer();
shallowRenderer.render(<SomeComponent />);
shallowRenderer.render(<SomeComponent />, {});
var result = shallowRenderer.getRenderOutput();
@@ -91,7 +91,7 @@ describe('ReactTestUtils', function() {
});
var shallowRenderer = ReactTestUtils.createRenderer();
shallowRenderer.render(<SomeComponent />);
shallowRenderer.render(<SomeComponent />, {});
var result = shallowRenderer.getRenderOutput();
expect(result.type).toBe('div');
expect(result.props.children).toEqual([
@@ -99,7 +99,7 @@ describe('ReactTestUtils', function() {
<span className="child2" />
]);
shallowRenderer.render(<SomeComponent aNew="prop" />);
shallowRenderer.render(<SomeComponent aNew="prop" />, {});
var updatedResult = shallowRenderer.getRenderOutput();
expect(updatedResult.type).toBe('a');