diff --git a/packages/enzyme-test-suite/test/ReactWrapper-spec.jsx b/packages/enzyme-test-suite/test/ReactWrapper-spec.jsx index d902bd494..12af2e7d0 100644 --- a/packages/enzyme-test-suite/test/ReactWrapper-spec.jsx +++ b/packages/enzyme-test-suite/test/ReactWrapper-spec.jsx @@ -1353,7 +1353,7 @@ describeWithDOM('mount', () => { ]); }); - itIf(!REACT16, 'should throw if an exception occurs during render', () => { + it('should throw if an exception occurs during render', () => { class Trainwreck extends React.Component { render() { const { user } = this.props; @@ -1413,6 +1413,62 @@ describeWithDOM('mount', () => { }); }); + it('should call componentWillReceiveProps, shouldComponentUpdate, componentWillUpdate, and componentDidUpdate with merged newProps', () => { + const spy = sinon.spy(); + + class Foo extends React.Component { + componentWillReceiveProps(nextProps) { + spy('componentWillReceiveProps', this.props, nextProps); + } + + shouldComponentUpdate(nextProps) { + spy('shouldComponentUpdate', this.props, nextProps); + return true; + } + + componentWillUpdate(nextProps) { + spy('componentWillUpdate', this.props, nextProps); + } + + componentDidUpdate(prevProps) { + spy('componentDidUpdate', prevProps, this.props); + } + + render() { + return ( +
+ ); + } + } + + const wrapper = mount(); + + wrapper.setProps({ b: 'c', d: 'e' }); + + expect(spy.args).to.deep.equal([ + [ + 'componentWillReceiveProps', + { a: 'a', b: 'b' }, + { a: 'a', b: 'c', d: 'e' }, + ], + [ + 'shouldComponentUpdate', + { a: 'a', b: 'b' }, + { a: 'a', b: 'c', d: 'e' }, + ], + [ + 'componentWillUpdate', + { a: 'a', b: 'b' }, + { a: 'a', b: 'c', d: 'e' }, + ], + [ + 'componentDidUpdate', + { a: 'a', b: 'b' }, + { a: 'a', b: 'c', d: 'e' }, + ], + ]); + }); + describeIf(is('> 0.13'), 'stateless function components', () => { it('should set props for a component multiple times', () => { const Foo = props => ( @@ -1445,7 +1501,21 @@ describeWithDOM('mount', () => { expect(wrapper.props().d).to.equal('e'); }); - itIf(!REACT16, 'should throw if an exception occurs during render', () => { + it('should pass in old context', () => { + const Foo = (props, context) => ( +
{context.x}
+ ); + Foo.contextTypes = { x: PropTypes.string }; + + const context = { x: 'yolo' }; + const wrapper = mount(, { context }); + expect(wrapper.first('div').text()).to.equal('yolo'); + + wrapper.setProps({ x: 5 }); // Just force a re-render + expect(wrapper.first('div').text()).to.equal('yolo'); + }); + + it('should throw if an exception occurs during render', () => { const Trainwreck = ({ user }) => (
{user.name.givenName} diff --git a/packages/enzyme-test-suite/test/ShallowWrapper-spec.jsx b/packages/enzyme-test-suite/test/ShallowWrapper-spec.jsx index 1894b18ce..ca1c3647a 100644 --- a/packages/enzyme-test-suite/test/ShallowWrapper-spec.jsx +++ b/packages/enzyme-test-suite/test/ShallowWrapper-spec.jsx @@ -1240,6 +1240,66 @@ describe('shallow', () => { ]); }); + it('should throw if an exception occurs during render', () => { + class Trainwreck extends React.Component { + render() { + const { user } = this.props; + return ( +
+ {user.name.givenName} +
+ ); + } + } + + const similarException = ((() => { + const user = {}; + try { + return user.name.givenName; + } catch (e) { + return e; + } + })()); + + const validUser = { + name: { + givenName: 'Brian', + }, + }; + + const wrapper = shallow(); + + const setInvalidProps = () => { + wrapper.setProps({ + user: {}, + }); + }; + + expect(setInvalidProps).to.throw(TypeError, similarException.message); + }); + + it('should call the callback when setProps has completed', () => { + class Foo extends React.Component { + render() { + const { id } = this.props; + return ( +
+ {id} +
+ ); + } + } + const wrapper = shallow(); + expect(wrapper.find('.foo')).to.have.lengthOf(1); + + wrapper[sym('__renderer__')].batchedUpdates(() => { + wrapper.setProps({ id: 'bar', foo: 'bla' }, () => { + expect(wrapper.find('.bar')).to.have.lengthOf(1); + }); + }); + expect(wrapper.find('.foo')).to.have.lengthOf(0); + }); + it('should call componentWillReceiveProps, shouldComponentUpdate, componentWillUpdate, and componentDidUpdate with merged newProps', () => { const spy = sinon.spy(); @@ -1341,6 +1401,42 @@ describe('shallow', () => { wrapper.setProps({ x: 5 }); // Just force a re-render expect(wrapper.first('div').text()).to.equal('yolo'); }); + + it('should throw if an exception occurs during render', () => { + const Trainwreck = ({ user }) => ( +
+ {user.name.givenName} +
+ ); + + const validUser = { + name: { + givenName: 'Brian', + }, + }; + + const similarException = ((() => { + const user = {}; + try { + return user.name.givenName; + } catch (e) { + return e; + } + })()); + + const wrapper = shallow(); + + const setInvalidProps = () => { + wrapper.setProps({ + user: {}, + }); + }; + + expect(setInvalidProps).to.throw( + TypeError, + similarException.message, + ); + }); }); }); diff --git a/packages/enzyme/src/ShallowWrapper.js b/packages/enzyme/src/ShallowWrapper.js index c6602a514..edba7dcd1 100644 --- a/packages/enzyme/src/ShallowWrapper.js +++ b/packages/enzyme/src/ShallowWrapper.js @@ -30,6 +30,8 @@ import { } from './RSTTraversal'; import { buildPredicate, reduceTreesBySelector } from './selectors'; +const noop = () => {}; + const NODE = sym('__node__'); const NODES = sym('__nodes__'); const RENDERER = sym('__renderer__'); @@ -376,13 +378,19 @@ class ShallowWrapper { * NOTE: can only be called on a wrapper instance that is also the root instance. * * @param {Object} props object + * @param {Function} cb - callback function * @returns {ShallowWrapper} */ - setProps(props) { + setProps(props, callback = noop) { if (this[ROOT] !== this) { throw new Error('ShallowWrapper::setProps() can only be called on the root'); } - return this.rerender(props); + if (typeof callback !== 'function') { + throw new TypeError('ShallowWrapper::setProps() expects a function as its second argument'); + } + this.rerender(props); + callback(); + return this; } /**