aboutsummaryrefslogtreecommitdiff
path: root/thirdparty/preact/test/browser/lifecycle.js
diff options
context:
space:
mode:
Diffstat (limited to 'thirdparty/preact/test/browser/lifecycle.js')
-rw-r--r--thirdparty/preact/test/browser/lifecycle.js493
1 files changed, 493 insertions, 0 deletions
diff --git a/thirdparty/preact/test/browser/lifecycle.js b/thirdparty/preact/test/browser/lifecycle.js
new file mode 100644
index 000000000..d6204ca8f
--- /dev/null
+++ b/thirdparty/preact/test/browser/lifecycle.js
@@ -0,0 +1,493 @@
+import { h, render, rerender, Component } from '../../src/preact';
+/** @jsx h */
+
+let spyAll = obj => Object.keys(obj).forEach( key => sinon.spy(obj,key) );
+
+describe('Lifecycle methods', () => {
+ let scratch;
+
+ before( () => {
+ scratch = document.createElement('div');
+ (document.body || document.documentElement).appendChild(scratch);
+ });
+
+ beforeEach( () => {
+ scratch.innerHTML = '';
+ });
+
+ after( () => {
+ scratch.parentNode.removeChild(scratch);
+ scratch = null;
+ });
+
+
+ describe('#componentWillUpdate', () => {
+ it('should NOT be called on initial render', () => {
+ class ReceivePropsComponent extends Component {
+ componentWillUpdate() {}
+ render() {
+ return <div />;
+ }
+ }
+ sinon.spy(ReceivePropsComponent.prototype, 'componentWillUpdate');
+ render(<ReceivePropsComponent />, scratch);
+ expect(ReceivePropsComponent.prototype.componentWillUpdate).not.to.have.been.called;
+ });
+
+ 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 <Inner i={i} {...props} />;
+ }
+ }
+ class Inner extends Component {
+ componentWillUpdate(nextProps, nextState) {
+ expect(nextProps).to.be.deep.equal({i: 1});
+ expect(nextState).to.be.deep.equal({});
+ }
+ render() {
+ return <div />;
+ }
+ }
+ sinon.spy(Inner.prototype, 'componentWillUpdate');
+ sinon.spy(Outer.prototype, 'componentDidMount');
+
+ // Initial render
+ render(<Outer />, scratch);
+ expect(Inner.prototype.componentWillUpdate).not.to.have.been.called;
+
+ // Rerender inner with new props
+ doRender();
+ rerender();
+ expect(Inner.prototype.componentWillUpdate).to.have.been.called;
+ });
+
+ it('should be called on new state', () => {
+ let doRender;
+ class ReceivePropsComponent extends Component {
+ componentWillUpdate() {}
+ componentDidMount() {
+ doRender = () => this.setState({ i: this.state.i + 1 });
+ }
+ render() {
+ return <div />;
+ }
+ }
+ sinon.spy(ReceivePropsComponent.prototype, 'componentWillUpdate');
+ render(<ReceivePropsComponent />, scratch);
+ expect(ReceivePropsComponent.prototype.componentWillUpdate).not.to.have.been.called;
+
+ doRender();
+ rerender();
+ expect(ReceivePropsComponent.prototype.componentWillUpdate).to.have.been.called;
+ });
+ });
+
+ describe('#componentWillReceiveProps', () => {
+ it('should NOT be called on initial render', () => {
+ class ReceivePropsComponent extends Component {
+ componentWillReceiveProps() {}
+ render() {
+ return <div />;
+ }
+ }
+ sinon.spy(ReceivePropsComponent.prototype, 'componentWillReceiveProps');
+ render(<ReceivePropsComponent />, scratch);
+ expect(ReceivePropsComponent.prototype.componentWillReceiveProps).not.to.have.been.called;
+ });
+
+ 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 <Inner i={i} {...props} />;
+ }
+ }
+ class Inner extends Component {
+ componentWillMount() {
+ expect(this.props.i).to.be.equal(0);
+ }
+ componentWillReceiveProps(nextProps) {
+ expect(nextProps.i).to.be.equal(1);
+ }
+ render() {
+ return <div />;
+ }
+ }
+ sinon.spy(Inner.prototype, 'componentWillReceiveProps');
+ sinon.spy(Outer.prototype, 'componentDidMount');
+
+ // Initial render
+ render(<Outer />, 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 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 <Inner i={i} {...props} />;
+ }
+ }
+ 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;
+ }
+ render() {
+ return <div />;
+ }
+ }
+ sinon.spy(Inner.prototype, 'componentWillReceiveProps');
+ sinon.spy(Inner.prototype, 'componentDidUpdate');
+ sinon.spy(Inner.prototype, 'componentWillUpdate');
+ sinon.spy(Outer.prototype, 'componentDidMount');
+
+ render(<Outer />, scratch);
+ doRender();
+ rerender();
+
+ expect(Inner.prototype.componentWillReceiveProps).to.have.been.calledBefore(Inner.prototype.componentWillUpdate);
+ expect(Inner.prototype.componentWillUpdate).to.have.been.calledBefore(Inner.prototype.componentDidUpdate);
+ });
+ });
+
+
+ let _it = it;
+ describe('#constructor and component(Did|Will)(Mount|Unmount)', () => {
+ /* global DISABLE_FLAKEY */
+ let it = DISABLE_FLAKEY ? xit : _it;
+
+ let setState;
+ class Outer extends Component {
+ constructor(p, c) {
+ super(p, c);
+ this.state = { show:true };
+ setState = s => this.setState(s);
+ }
+ render(props, { show }) {
+ return (
+ <div>
+ { show && (
+ <Inner {...props} />
+ ) }
+ </div>
+ );
+ }
+ }
+
+ class LifecycleTestComponent extends Component {
+ constructor(p, c) { super(p, c); this._constructor(); }
+ _constructor() {}
+ componentWillMount() {}
+ componentDidMount() {}
+ componentWillUnmount() {}
+ componentDidUnmount() {}
+ render() { return <div />; }
+ }
+
+ class Inner extends LifecycleTestComponent {
+ render() {
+ return (
+ <div>
+ <InnerMost />
+ </div>
+ );
+ }
+ }
+
+ class InnerMost extends LifecycleTestComponent {
+ render() { return <div />; }
+ }
+
+ let spies = ['_constructor', 'componentWillMount', 'componentDidMount', 'componentWillUnmount', 'componentDidUnmount'];
+
+ let verifyLifycycleMethods = (TestComponent) => {
+ let proto = TestComponent.prototype;
+ spies.forEach( s => sinon.spy(proto, s) );
+ let reset = () => spies.forEach( s => proto[s].reset() );
+
+ it('should be invoked for components on initial render', () => {
+ reset();
+ render(<Outer />, scratch);
+ expect(proto._constructor).to.have.been.called;
+ expect(proto.componentDidMount).to.have.been.called;
+ expect(proto.componentWillMount).to.have.been.calledBefore(proto.componentDidMount);
+ expect(proto.componentDidMount).to.have.been.called;
+ });
+
+ it('should be invoked for components on unmount', () => {
+ reset();
+ setState({ show:false });
+ rerender();
+
+ expect(proto.componentDidUnmount).to.have.been.called;
+ expect(proto.componentWillUnmount).to.have.been.calledBefore(proto.componentDidUnmount);
+ expect(proto.componentDidUnmount).to.have.been.called;
+ });
+
+ it('should be invoked for components on re-render', () => {
+ reset();
+ setState({ show:true });
+ rerender();
+
+ expect(proto._constructor).to.have.been.called;
+ expect(proto.componentDidMount).to.have.been.called;
+ expect(proto.componentWillMount).to.have.been.calledBefore(proto.componentDidMount);
+ expect(proto.componentDidMount).to.have.been.called;
+ });
+ };
+
+ describe('inner components', () => {
+ verifyLifycycleMethods(Inner);
+ });
+
+ describe('innermost components', () => {
+ verifyLifycycleMethods(InnerMost);
+ });
+
+ describe('when shouldComponentUpdate() returns false', () => {
+ let setState;
+
+ class Outer extends Component {
+ constructor() {
+ super();
+ this.state = { show:true };
+ setState = s => this.setState(s);
+ }
+ render(props, { show }) {
+ return (
+ <div>
+ { show && (
+ <div>
+ <Inner {...props} />
+ </div>
+ ) }
+ </div>
+ );
+ }
+ }
+
+ class Inner extends Component {
+ shouldComponentUpdate(){ return false; }
+ componentWillMount() {}
+ componentDidMount() {}
+ componentWillUnmount() {}
+ componentDidUnmount() {}
+ render() {
+ return <div />;
+ }
+ }
+
+ let proto = Inner.prototype;
+ let spies = ['componentWillMount', 'componentDidMount', 'componentWillUnmount', 'componentDidUnmount'];
+ spies.forEach( s => sinon.spy(proto, s) );
+
+ let reset = () => spies.forEach( s => proto[s].reset() );
+
+ beforeEach( () => reset() );
+
+ it('should be invoke normally on initial mount', () => {
+ render(<Outer />, scratch);
+ expect(proto.componentWillMount).to.have.been.called;
+ expect(proto.componentWillMount).to.have.been.calledBefore(proto.componentDidMount);
+ expect(proto.componentDidMount).to.have.been.called;
+ });
+
+ it('should be invoked normally on unmount', () => {
+ setState({ show:false });
+ rerender();
+
+ expect(proto.componentWillUnmount).to.have.been.called;
+ expect(proto.componentWillUnmount).to.have.been.calledBefore(proto.componentDidUnmount);
+ expect(proto.componentDidUnmount).to.have.been.called;
+ });
+
+ it('should still invoke mount for shouldComponentUpdate():false', () => {
+ setState({ show:true });
+ rerender();
+
+ expect(proto.componentWillMount).to.have.been.called;
+ expect(proto.componentWillMount).to.have.been.calledBefore(proto.componentDidMount);
+ expect(proto.componentDidMount).to.have.been.called;
+ });
+
+ it('should still invoke unmount for shouldComponentUpdate():false', () => {
+ setState({ show:false });
+ rerender();
+
+ expect(proto.componentWillUnmount).to.have.been.called;
+ expect(proto.componentWillUnmount).to.have.been.calledBefore(proto.componentDidUnmount);
+ expect(proto.componentDidUnmount).to.have.been.called;
+ });
+ });
+ });
+
+ describe('Lifecycle DOM Timing', () => {
+ it('should be invoked when dom does (DidMount, WillUnmount) or does not (WillMount, DidUnmount) exist', () => {
+ let setState;
+ class Outer extends Component {
+ constructor() {
+ super();
+ this.state = { show:true };
+ setState = s => {
+ this.setState(s);
+ this.forceUpdate();
+ };
+ }
+ componentWillMount() {
+ expect(document.getElementById('OuterDiv'), 'Outer componentWillMount').to.not.exist;
+ }
+ componentDidMount() {
+ expect(document.getElementById('OuterDiv'), 'Outer componentDidMount').to.exist;
+ }
+ componentWillUnmount() {
+ expect(document.getElementById('OuterDiv'), 'Outer componentWillUnmount').to.exist;
+ }
+ componentDidUnmount() {
+ expect(document.getElementById('OuterDiv'), 'Outer componentDidUnmount').to.not.exist;
+ }
+ render(props, { show }) {
+ return (
+ <div id="OuterDiv">
+ { show && (
+ <div>
+ <Inner {...props} />
+ </div>
+ ) }
+ </div>
+ );
+ }
+ }
+
+ class Inner extends Component {
+ componentWillMount() {
+ expect(document.getElementById('InnerDiv'), 'Inner componentWillMount').to.not.exist;
+ }
+ componentDidMount() {
+ expect(document.getElementById('InnerDiv'), 'Inner componentDidMount').to.exist;
+ }
+ componentWillUnmount() {
+ // @TODO Component mounted into elements (non-components)
+ // are currently unmounted after those elements, so their
+ // DOM is unmounted prior to the method being called.
+ //expect(document.getElementById('InnerDiv'), 'Inner componentWillUnmount').to.exist;
+ }
+ componentDidUnmount() {
+ expect(document.getElementById('InnerDiv'), 'Inner componentDidUnmount').to.not.exist;
+ }
+
+ render() {
+ return <div id="InnerDiv" />;
+ }
+ }
+
+ let proto = Inner.prototype;
+ let spies = ['componentWillMount', 'componentDidMount', 'componentWillUnmount', 'componentDidUnmount'];
+ spies.forEach( s => sinon.spy(proto, s) );
+
+ let reset = () => spies.forEach( s => proto[s].reset() );
+
+ render(<Outer />, scratch);
+ expect(proto.componentWillMount).to.have.been.called;
+ expect(proto.componentWillMount).to.have.been.calledBefore(proto.componentDidMount);
+ expect(proto.componentDidMount).to.have.been.called;
+
+ reset();
+ setState({ show:false });
+
+ expect(proto.componentWillUnmount).to.have.been.called;
+ expect(proto.componentWillUnmount).to.have.been.calledBefore(proto.componentDidUnmount);
+ expect(proto.componentDidUnmount).to.have.been.called;
+
+ reset();
+ setState({ show:true });
+
+ expect(proto.componentWillMount).to.have.been.called;
+ expect(proto.componentWillMount).to.have.been.calledBefore(proto.componentDidMount);
+ expect(proto.componentDidMount).to.have.been.called;
+ });
+
+ it('should remove this.base for HOC', () => {
+ let createComponent = (name, fn) => {
+ class C extends Component {
+ componentWillUnmount() {
+ expect(this.base, `${name}.componentWillUnmount`).to.exist;
+ }
+ componentDidUnmount() {
+ expect(this.base, `${name}.componentDidUnmount`).not.to.exist;
+ }
+ render(props) { return fn(props); }
+ }
+ spyAll(C.prototype);
+ return C;
+ };
+
+ class Wrapper extends Component {
+ render({ children }) {
+ return <div class="wrapper">{children}</div>;
+ }
+ }
+
+ let One = createComponent('One', () => <Wrapper>one</Wrapper> );
+ let Two = createComponent('Two', () => <Wrapper>two</Wrapper> );
+ let Three = createComponent('Three', () => <Wrapper>three</Wrapper> );
+
+ let components = [One, Two, Three];
+
+ let Selector = createComponent('Selector', ({ page }) => {
+ let Child = components[page];
+ return <Child />;
+ });
+
+ class App extends Component {
+ render(_, { page }) {
+ return <Selector page={page} />;
+ }
+ }
+
+ let app;
+ render(<App ref={ c => app=c } />, scratch);
+
+ for (let i=0; i<20; i++) {
+ app.setState({ page: i%components.length });
+ app.forceUpdate();
+ }
+ });
+ });
+});