mirror of
https://github.com/zebrajr/react.git
synced 2026-01-15 12:15:22 +00:00
Merge pull request #2497 from graue/2393-v1
Basic shallow rendering support (#2393)
This commit is contained in:
@@ -650,6 +650,25 @@ var ReactCompositeComponentMixin = assign({},
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_renderValidatedComponentWithoutOwnerOrContext: function() {
|
||||
var inst = this._instance;
|
||||
var renderedComponent = inst.render();
|
||||
if (__DEV__) {
|
||||
// We allow auto-mocks to proceed as if they're returning null.
|
||||
if (typeof renderedComponent === 'undefined' &&
|
||||
inst.render._isMockFunction) {
|
||||
// This is probably bad practice. Consider warning here and
|
||||
// deprecating this convenience.
|
||||
renderedComponent = null;
|
||||
}
|
||||
}
|
||||
|
||||
return renderedComponent;
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
@@ -665,16 +684,8 @@ var ReactCompositeComponentMixin = assign({},
|
||||
ReactCurrentOwner.current = this;
|
||||
var inst = this._instance;
|
||||
try {
|
||||
renderedComponent = inst.render();
|
||||
if (__DEV__) {
|
||||
// We allow auto-mocks to proceed as if they're returning null.
|
||||
if (typeof renderedComponent === 'undefined' &&
|
||||
inst.render._isMockFunction) {
|
||||
// This is probably bad practice. Consider warning here and
|
||||
// deprecating this convenience.
|
||||
renderedComponent = null;
|
||||
}
|
||||
}
|
||||
renderedComponent =
|
||||
this._renderValidatedComponentWithoutOwnerOrContext();
|
||||
} finally {
|
||||
ReactContext.current = previousContext;
|
||||
ReactCurrentOwner.current = null;
|
||||
@@ -734,11 +745,124 @@ var ReactCompositeComponentMixin = assign({},
|
||||
|
||||
});
|
||||
|
||||
var ShallowMixin = assign({},
|
||||
ReactCompositeComponentMixin, {
|
||||
|
||||
/**
|
||||
* Initializes the component, renders markup, and registers event listeners.
|
||||
*
|
||||
* @param {string} rootID DOM ID of the root node.
|
||||
* @param {ReactReconcileTransaction|ReactServerRenderingTransaction} transaction
|
||||
* @param {number} mountDepth number of components in the owner hierarchy
|
||||
* @return {ReactElement} Shallow rendering of the component.
|
||||
* @final
|
||||
* @internal
|
||||
*/
|
||||
mountComponent: function(rootID, transaction, mountDepth) {
|
||||
ReactComponent.Mixin.mountComponent.call(
|
||||
this,
|
||||
rootID,
|
||||
transaction,
|
||||
mountDepth
|
||||
);
|
||||
|
||||
var inst = this._instance;
|
||||
|
||||
// Store a reference from the instance back to the internal representation
|
||||
ReactInstanceMap.set(inst, this);
|
||||
|
||||
this._compositeLifeCycleState = CompositeLifeCycle.MOUNTING;
|
||||
|
||||
// No context for shallow-mounted components.
|
||||
inst.props = this._processProps(this._currentElement.props);
|
||||
|
||||
var initialState = inst.getInitialState ? inst.getInitialState() : null;
|
||||
if (__DEV__) {
|
||||
// We allow auto-mocks to proceed as if they're returning null.
|
||||
if (typeof initialState === 'undefined' &&
|
||||
inst.getInitialState._isMockFunction) {
|
||||
// This is probably bad practice. Consider warning here and
|
||||
// deprecating this convenience.
|
||||
initialState = null;
|
||||
}
|
||||
}
|
||||
invariant(
|
||||
typeof initialState === 'object' && !Array.isArray(initialState),
|
||||
'%s.getInitialState(): must return an object or null',
|
||||
inst.constructor.displayName || 'ReactCompositeComponent'
|
||||
);
|
||||
inst.state = initialState;
|
||||
|
||||
this._pendingState = null;
|
||||
this._pendingForceUpdate = false;
|
||||
|
||||
if (inst.componentWillMount) {
|
||||
inst.componentWillMount();
|
||||
// When mounting, calls to `setState` by `componentWillMount` will set
|
||||
// `this._pendingState` without triggering a re-render.
|
||||
if (this._pendingState) {
|
||||
inst.state = this._pendingState;
|
||||
this._pendingState = null;
|
||||
}
|
||||
}
|
||||
|
||||
// No recursive call to instantiateReactComponent for shallow rendering.
|
||||
this._renderedComponent =
|
||||
this._renderValidatedComponentWithoutOwnerOrContext();
|
||||
|
||||
// Done with mounting, `setState` will now trigger UI changes.
|
||||
this._compositeLifeCycleState = null;
|
||||
|
||||
// No call to this._renderedComponent.mountComponent for shallow
|
||||
// rendering.
|
||||
|
||||
if (inst.componentDidMount) {
|
||||
transaction.getReactMountReady().enqueue(inst.componentDidMount, inst);
|
||||
}
|
||||
|
||||
return this._renderedComponent;
|
||||
},
|
||||
|
||||
/**
|
||||
* Call the component's `render` method and update the DOM accordingly.
|
||||
*
|
||||
* @param {ReactReconcileTransaction} transaction
|
||||
* @internal
|
||||
*/
|
||||
_updateRenderedComponent: function(transaction) {
|
||||
var prevComponentInstance = this._renderedComponent;
|
||||
var prevRenderedElement = prevComponentInstance._currentElement;
|
||||
// Use the without-owner-or-context variant of _rVC below:
|
||||
var nextRenderedElement = this._renderValidatedComponentWithoutOwnerOrContext();
|
||||
if (shouldUpdateReactComponent(prevRenderedElement, nextRenderedElement)) {
|
||||
prevComponentInstance.receiveComponent(
|
||||
nextRenderedElement,
|
||||
transaction
|
||||
);
|
||||
} else {
|
||||
// These two IDs are actually the same! But nothing should rely on that.
|
||||
var thisID = this._rootNodeID;
|
||||
var prevComponentID = prevComponentInstance._rootNodeID;
|
||||
// Don't unmount previous instance since it was never mounted, due to
|
||||
// shallow render.
|
||||
//prevComponentInstance.unmountComponent();
|
||||
this._renderedComponent = nextRenderedElement;
|
||||
// ^ no instantiateReactComponent
|
||||
//
|
||||
// no recursive mountComponent
|
||||
return nextRenderedElement;
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
var ReactCompositeComponent = {
|
||||
|
||||
LifeCycle: CompositeLifeCycle,
|
||||
|
||||
Mixin: ReactCompositeComponentMixin
|
||||
Mixin: ReactCompositeComponentMixin,
|
||||
|
||||
ShallowMixin: ShallowMixin
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -106,7 +106,21 @@ var ReactElement = function(type, key, ref, owner, context, props) {
|
||||
// an external backing store so that we can freeze the whole object.
|
||||
// This can be replaced with a WeakMap once they are implemented in
|
||||
// commonly used development environments.
|
||||
this._store = { validated: false, props: props };
|
||||
this._store = { props: props };
|
||||
|
||||
// To make comparing ReactElements easier for testing purposes, we make
|
||||
// the validation flag non-enumerable (where possible, which should
|
||||
// include every environment we run tests in), so the test framework
|
||||
// ignores it.
|
||||
try {
|
||||
Object.defineProperty(this._store, 'validated', {
|
||||
configurable: false,
|
||||
enumerable: false,
|
||||
writable: true
|
||||
});
|
||||
} catch (x) {
|
||||
}
|
||||
this._store.validated = false;
|
||||
|
||||
// We're not allowed to set props directly on the object so we early
|
||||
// return and rely on the prototype membrane to forward to the backing
|
||||
|
||||
@@ -17,12 +17,15 @@ var EventPropagators = require('EventPropagators');
|
||||
var React = require('React');
|
||||
var ReactElement = require('ReactElement');
|
||||
var ReactBrowserEventEmitter = require('ReactBrowserEventEmitter');
|
||||
var ReactCompositeComponent = require('ReactCompositeComponent');
|
||||
var ReactInstanceHandles = require('ReactInstanceHandles');
|
||||
var ReactInstanceMap = require('ReactInstanceMap');
|
||||
var ReactMount = require('ReactMount');
|
||||
var ReactUpdates = require('ReactUpdates');
|
||||
var SyntheticEvent = require('SyntheticEvent');
|
||||
|
||||
var assign = require('Object.assign');
|
||||
var instantiateReactComponent = require('instantiateReactComponent');
|
||||
|
||||
var topLevelTypes = EventConstants.topLevelTypes;
|
||||
|
||||
@@ -298,10 +301,53 @@ var ReactTestUtils = {
|
||||
};
|
||||
},
|
||||
|
||||
createRenderer: function() {
|
||||
return new ReactShallowRenderer();
|
||||
},
|
||||
|
||||
Simulate: null,
|
||||
SimulateNative: {}
|
||||
};
|
||||
|
||||
/**
|
||||
* @class ReactShallowRenderer
|
||||
*/
|
||||
var ReactShallowRenderer = function() {
|
||||
this._instance = null;
|
||||
};
|
||||
|
||||
ReactShallowRenderer.prototype.getRenderOutput = function() {
|
||||
return (this._instance && this._instance._renderedComponent) || null;
|
||||
};
|
||||
|
||||
var ShallowComponentWrapper = function(inst) {
|
||||
this._instance = inst;
|
||||
}
|
||||
assign(
|
||||
ShallowComponentWrapper.prototype,
|
||||
ReactCompositeComponent.ShallowMixin
|
||||
);
|
||||
|
||||
ReactShallowRenderer.prototype.render = function(element) {
|
||||
var transaction = ReactUpdates.ReactReconcileTransaction.getPooled();
|
||||
this._render(element, transaction);
|
||||
ReactUpdates.ReactReconcileTransaction.release(transaction);
|
||||
};
|
||||
|
||||
ReactShallowRenderer.prototype._render = function(element, transaction) {
|
||||
if (!this._instance) {
|
||||
var rootID = ReactInstanceHandles.createReactRootID();
|
||||
var instance = new ShallowComponentWrapper(new element.type(element.props));
|
||||
instance.construct(element);
|
||||
|
||||
instance.mountComponent(rootID, transaction, 0);
|
||||
|
||||
this._instance = instance;
|
||||
} else {
|
||||
this._instance.receiveComponent(element, transaction);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Exports:
|
||||
*
|
||||
|
||||
113
src/test/__tests__/ReactTestUtils-test.js
Normal file
113
src/test/__tests__/ReactTestUtils-test.js
Normal file
@@ -0,0 +1,113 @@
|
||||
/**
|
||||
* Copyright 2013-2014, 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;
|
||||
var ReactTestUtils;
|
||||
|
||||
var mocks;
|
||||
var warn;
|
||||
|
||||
describe('ReactTestUtils', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
mocks = require('mocks');
|
||||
|
||||
React = require('React');
|
||||
ReactTestUtils = require('ReactTestUtils');
|
||||
|
||||
warn = console.warn;
|
||||
console.warn = mocks.getMockFunction();
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
console.warn = warn;
|
||||
});
|
||||
|
||||
it('should have shallow rendering', function() {
|
||||
var SomeComponent = React.createClass({
|
||||
render: function() {
|
||||
return (
|
||||
<div>
|
||||
<span className="child1" />
|
||||
<span className="child2" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var shallowRenderer = ReactTestUtils.createRenderer();
|
||||
shallowRenderer.render(<SomeComponent />);
|
||||
|
||||
var result = shallowRenderer.getRenderOutput();
|
||||
|
||||
expect(result.type).toBe('div');
|
||||
expect(result.props.children).toEqual([
|
||||
<span className="child1" />,
|
||||
<span className="child2" />
|
||||
]);
|
||||
});
|
||||
|
||||
it('lets you update shallowly rendered components', function() {
|
||||
var SomeComponent = React.createClass({
|
||||
getInitialState: function() {
|
||||
return {clicked: false};
|
||||
},
|
||||
|
||||
onClick: function() {
|
||||
this.setState({clicked: true});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var className = this.state.clicked ? 'was-clicked' : '';
|
||||
|
||||
if (this.props.aNew === 'prop') {
|
||||
return (
|
||||
<a
|
||||
href="#"
|
||||
onClick={this.onClick}
|
||||
className={className}>
|
||||
Test link
|
||||
</a>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div>
|
||||
<span className="child1" />
|
||||
<span className="child2" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var shallowRenderer = ReactTestUtils.createRenderer();
|
||||
shallowRenderer.render(<SomeComponent />);
|
||||
var result = shallowRenderer.getRenderOutput();
|
||||
expect(result.type).toBe('div');
|
||||
expect(result.props.children).toEqual([
|
||||
<span className="child1" />,
|
||||
<span className="child2" />
|
||||
]);
|
||||
|
||||
shallowRenderer.render(<SomeComponent aNew="prop" />);
|
||||
var updatedResult = shallowRenderer.getRenderOutput();
|
||||
expect(updatedResult.type).toBe('a');
|
||||
|
||||
var mockEvent = {};
|
||||
updatedResult.props.onClick(mockEvent);
|
||||
|
||||
var updatedResultCausedByClick = shallowRenderer.getRenderOutput();
|
||||
expect(updatedResultCausedByClick.type).toBe('a');
|
||||
expect(updatedResultCausedByClick.props.className).toBe('was-clicked');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user