Skip to content

Commit

Permalink
Merge pull request #789 from jwbay/disableLifecycleMethods
Browse files Browse the repository at this point in the history
add disableLifecycleMethods for shallow
  • Loading branch information
blainekasten committed Mar 17, 2017
2 parents e831c22 + 765ec95 commit 595d15f
Show file tree
Hide file tree
Showing 3 changed files with 195 additions and 2 deletions.
5 changes: 4 additions & 1 deletion docs/api/shallow.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,10 @@ describe('<MyComponent />', () => {

1. `node` (`ReactElement`): The node to render
2. `options` (`Object` [optional]):
- `options.context`: (`Object` [optional]): Context to be passed into the component
- `options.context`: (`Object` [optional]): Context to be passed into the component
- `options.disableLifecycleMethods`: (`Boolean` [optional]): If set to true, `componentDidMount`
is not called on the component, and `componentDidUpdate` is not called after
[`setProps`](ShallowWrapper/setProps.md) and [`setContext`](ShallowWrapper/setContext.md).

#### Returns

Expand Down
36 changes: 36 additions & 0 deletions src/ShallowWrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,48 @@ function filterWhereUnwrapped(wrapper, predicate) {
return wrapper.wrap(compact(wrapper.getNodes().filter(predicate)));
}

/**
* Ensure options passed to ShallowWrapper are valid. Throws otherwise.
* @param {Object} options
*/
function validateOptions(options) {
const { lifecycleExperimental, disableLifecycleMethods } = options;
if (
typeof lifecycleExperimental !== 'undefined' &&
typeof lifecycleExperimental !== 'boolean'
) {
throw new Error(
'lifecycleExperimental must be either true or false if provided',
);
}

if (
typeof disableLifecycleMethods !== 'undefined' &&
typeof disableLifecycleMethods !== 'boolean'
) {
throw new Error(
'disableLifecycleMethods must be either true or false if provided',
);
}

if (
lifecycleExperimental != null &&
disableLifecycleMethods != null &&
lifecycleExperimental === disableLifecycleMethods
) {
throw new Error(
'lifecycleExperimental and disableLifecycleMethods cannot be set to the same value',
);
}
}

/**
* @class ShallowWrapper
*/
class ShallowWrapper {

constructor(nodes, root, options = {}) {
validateOptions(options);
if (!root) {
this.root = this;
this.unrendered = nodes;
Expand Down
156 changes: 155 additions & 1 deletion test/ShallowWrapper-spec.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import sinon from 'sinon';
import { shallow, render, ShallowWrapper } from '../src/';
import { describeIf, itIf, itWithData, generateEmptyRenderData } from './_helpers';
import { ITERATOR_SYMBOL, withSetStateAllowed } from '../src/Utils';
import { REACT013, REACT15 } from '../src/version';
import { REACT013, REACT014, REACT15 } from '../src/version';

describe('shallow', () => {
describe('context', () => {
Expand Down Expand Up @@ -2683,7 +2683,161 @@ describe('shallow', () => {
});
});

describe('disableLifecycleMethods', () => {
describe('validation', () => {
it('throws for a non-boolean value', () => {
['value', 42, null].forEach((value) => {
expect(() => shallow(<div />, {
disableLifecycleMethods: value,
})).to.throw(/true or false/);
});
});

it('does not throw for a boolean value or undefined', () => {
[true, false, undefined].forEach((value) => {
expect(() => shallow(<div />, {
disableLifecycleMethods: value,
})).not.to.throw();
});
});

it('does not throw when no lifecycle flags are provided in options', () => {
expect(() => shallow(<div />, {})).not.to.throw();
});

it('throws when used with lifecycleExperimental in invalid combinations', () => {
[true, false].forEach((value) => {
expect(() => shallow(<div />, {
lifecycleExperimental: value,
disableLifecycleMethods: value,
})).to.throw(/same value/);
});
});
});

describe('when set to true', () => {
let wrapper;
const spy = sinon.spy();
class Foo extends React.Component {
componentWillMount() {
spy('componentWillMount');
}
componentDidMount() {
spy('componentDidMount');
}
componentWillReceiveProps() {
spy('componentWillReceiveProps');
}
shouldComponentUpdate() {
spy('shouldComponentUpdate');
return true;
}
componentWillUpdate() {
spy('componentWillUpdate');
}
componentDidUpdate() {
spy('componentDidUpdate');
}
componentWillUnmount() {
spy('componentWillUnmount');
}
render() {
spy('render');
return <div>foo</div>;
}
}

const options = {
disableLifecycleMethods: true,
context: {
foo: 'foo',
},
};

beforeEach(() => {
wrapper = shallow(<Foo />, options);
spy.reset();
});

it('does not call componentDidMount when mounting', () => {
wrapper = shallow(<Foo />, options);
expect(spy.args).to.deep.equal([
['componentWillMount'],
['render'],
]);
});

it('calls expected methods when receiving new props', () => {
wrapper.setProps({ foo: 'foo' });
expect(spy.args).to.deep.equal([
['componentWillReceiveProps'],
['shouldComponentUpdate'],
['componentWillUpdate'],
['render'],
]);
});

describeIf(REACT013 || REACT15, 'setContext', () => {
it('calls expected methods when receiving new context', () => {
wrapper.setContext({ foo: 'foo' });
expect(spy.args).to.deep.equal([
['componentWillReceiveProps'],
['shouldComponentUpdate'],
['componentWillUpdate'],
['render'],
]);
});
});

describeIf(REACT014, 'setContext', () => {
it('calls expected methods when receiving new context', () => {
wrapper.setContext({ foo: 'foo' });
expect(spy.args).to.deep.equal([
['shouldComponentUpdate'],
['componentWillUpdate'],
['render'],
]);
});
});

it('calls expected methods for setState', () => {
wrapper.setState({ bar: 'bar' });
expect(spy.args).to.deep.equal([
['shouldComponentUpdate'],
['componentWillUpdate'],
['render'],
['componentDidUpdate'],
]);
});

it('calls expected methods when unmounting', () => {
wrapper.unmount();
expect(spy.args).to.deep.equal([
['componentWillUnmount'],
]);
});
});
});

describe('lifecycleExperimental', () => {
describe('validation', () => {
it('throws for a non-boolean value', () => {
['value', 42, null].forEach((value) => {
expect(() => shallow(<div />, {
lifecycleExperimental: value,
})).to.throw(/true or false/);
});
});

it('does not throw for a boolean value or when not provided', () => {
[true, false, undefined].forEach((value) => {
expect(() => shallow(<div />, {
lifecycleExperimental: value,
})).not.to.throw();
});
});
});

context('mounting phase', () => {
it('should call componentWillMount and componentDidMount', () => {
const spy = sinon.spy();
Expand Down

0 comments on commit 595d15f

Please sign in to comment.