import { setupRerender } from 'preact/test-utils';
import { createElement, render, Component } from 'preact';
import { setupScratch, teardown } from '../../_util/helpers';
/** @jsx createElement */
describe('Lifecycle methods', () => {
/** @type {HTMLDivElement} */
let scratch;
/** @type {() => void} */
let rerender;
beforeEach(() => {
scratch = setupScratch();
rerender = setupRerender();
});
afterEach(() => {
teardown(scratch);
});
describe('#componentWillReceiveProps', () => {
it('should update state when called setState in componentWillReceiveProps', () => {
let componentState;
class Foo extends Component {
constructor(props) {
super(props);
this.state = {
dummy: 0
};
}
componentDidMount() {
// eslint-disable-next-line react/no-did-mount-set-state
this.setState({ dummy: 1 });
}
render() {
return ;
}
}
class Bar extends Component {
constructor(props) {
super(props);
this.state = {
value: 0
};
}
componentWillReceiveProps() {
this.setState({ value: 1 });
}
render() {
componentState = this.state;
return
;
}
}
render(, scratch);
rerender();
expect(componentState).to.deep.equal({ value: 1 });
const cWRP = Foo.prototype.componentWillReceiveProps;
delete Foo.prototype.componentWillReceiveProps;
Foo.prototype.shouldComponentUpdate = cWRP;
render(null, scratch);
render(, scratch);
rerender();
expect(componentState, 'via shouldComponentUpdate').to.deep.equal({
value: 1
});
delete Foo.prototype.shouldComponentUpdate;
Foo.prototype.componentWillUpdate = cWRP;
render(null, scratch);
render(, scratch);
rerender();
expect(componentState, 'via componentWillUpdate').to.deep.equal({
value: 1
});
});
it('should NOT be called on initial render', () => {
class ReceivePropsComponent extends Component {
componentWillReceiveProps() {}
render() {
return ;
}
}
sinon.spy(ReceivePropsComponent.prototype, 'componentWillReceiveProps');
render(, scratch);
expect(ReceivePropsComponent.prototype.componentWillReceiveProps).not.to
.have.been.called;
});
// See last paragraph of cWRP section https://reactjs.org/docs/react-component.html#unsafe_componentwillreceiveprops
it('should not be called on setState or forceUpdate', () => {
let spy = sinon.spy();
let spyInner = sinon.spy();
let c;
class Inner extends Component {
componentWillReceiveProps() {
spyInner();
}
render() {
return foo
;
}
}
class Outer extends Component {
constructor() {
super();
c = this;
}
componentWillReceiveProps() {
spy();
}
render() {
return ;
}
}
render(, scratch);
expect(spy).to.not.be.called;
c.setState({});
rerender();
expect(spy).to.not.be.called;
expect(spyInner).to.be.calledOnce;
spy.resetHistory();
spyInner.resetHistory();
c.forceUpdate();
rerender();
expect(spy).to.not.be.called;
expect(spyInner).to.be.calledOnce;
});
it('should be called when rerender with new props from parent', () => {
let doRender;
class Outer extends Component {
constructor(p, c) {
super(p, c);
this.state = { i: 0 };
}
componentDidMount() {
doRender = () => this.setState({ i: this.state.i + 1 });
}
render(props, { i }) {
return ;
}
}
class Inner extends Component {
componentWillMount() {
expect(this.props.i).to.be.equal(0);
}
componentWillReceiveProps(nextProps) {
expect(nextProps.i).to.be.equal(1);
}
render() {
return ;
}
}
sinon.spy(Inner.prototype, 'componentWillReceiveProps');
sinon.spy(Outer.prototype, 'componentDidMount');
// Initial render
render(, scratch);
expect(Inner.prototype.componentWillReceiveProps).not.to.have.been.called;
// Rerender inner with new props
doRender();
rerender();
expect(Inner.prototype.componentWillReceiveProps).to.have.been.called;
});
it('should be called when rerender with new props from parent even with setState/forceUpdate in child', () => {
let setStateAndUpdateProps;
let forceUpdateAndUpdateProps;
let cWRPSpy = sinon.spy();
class Outer extends Component {
constructor(p, c) {
super(p, c);
this.state = { i: 0 };
this.update = this.update.bind(this);
}
update() {
this.setState({ i: this.state.i + 1 });
}
render(props, { i }) {
return ;
}
}
class Inner extends Component {
componentDidMount() {
expect(this.props.i).to.be.equal(0);
setStateAndUpdateProps = () => {
this.setState({});
this.props.update();
};
forceUpdateAndUpdateProps = () => {
this.forceUpdate();
this.props.update();
};
}
componentWillReceiveProps(nextProps) {
cWRPSpy(nextProps.i);
}
render() {
return ;
}
}
// Initial render
render(, scratch);
expect(cWRPSpy).not.to.have.been.called;
// setState in inner component and update with new props
setStateAndUpdateProps();
rerender();
expect(cWRPSpy).to.have.been.calledWith(1);
// forceUpdate in inner component and update with new props
forceUpdateAndUpdateProps();
rerender();
expect(cWRPSpy).to.have.been.calledWith(2);
});
it('should be called in right execution order', () => {
let doRender;
class Outer extends Component {
constructor(p, c) {
super(p, c);
this.state = { i: 0 };
}
componentDidMount() {
doRender = () => this.setState({ i: this.state.i + 1 });
}
render(props, { i }) {
return ;
}
}
class Inner extends Component {
componentDidUpdate() {
expect(Inner.prototype.componentWillReceiveProps).to.have.been.called;
expect(Inner.prototype.componentWillUpdate).to.have.been.called;
}
componentWillReceiveProps() {
expect(Inner.prototype.componentWillUpdate).not.to.have.been.called;
expect(Inner.prototype.componentDidUpdate).not.to.have.been.called;
}
componentWillUpdate() {
expect(Inner.prototype.componentWillReceiveProps).to.have.been.called;
expect(Inner.prototype.componentDidUpdate).not.to.have.been.called;
}
shouldComponentUpdate() {
expect(Inner.prototype.componentWillReceiveProps).to.have.been.called;
expect(Inner.prototype.componentWillUpdate).not.to.have.been.called;
return true;
}
render() {
return ;
}
}
sinon.spy(Inner.prototype, 'componentWillReceiveProps');
sinon.spy(Inner.prototype, 'componentDidUpdate');
sinon.spy(Inner.prototype, 'componentWillUpdate');
sinon.spy(Inner.prototype, 'shouldComponentUpdate');
sinon.spy(Outer.prototype, 'componentDidMount');
render(, scratch);
doRender();
rerender();
expect(
Inner.prototype.componentWillReceiveProps
).to.have.been.calledBefore(Inner.prototype.componentWillUpdate);
expect(
Inner.prototype.componentWillReceiveProps
).to.have.been.calledBefore(Inner.prototype.shouldComponentUpdate);
expect(Inner.prototype.componentWillUpdate).to.have.been.calledBefore(
Inner.prototype.componentDidUpdate
);
});
});
});