diff options
Diffstat (limited to 'thirdparty/preact/test/browser/refs.js')
-rw-r--r-- | thirdparty/preact/test/browser/refs.js | 287 |
1 files changed, 287 insertions, 0 deletions
diff --git a/thirdparty/preact/test/browser/refs.js b/thirdparty/preact/test/browser/refs.js new file mode 100644 index 000000000..89678b76e --- /dev/null +++ b/thirdparty/preact/test/browser/refs.js @@ -0,0 +1,287 @@ +import { h, render, Component } from '../../src/preact'; +/** @jsx h */ + +// gives call count and argument errors names (otherwise sinon just uses "spy"): +let spy = (name, ...args) => { + let spy = sinon.spy(...args); + spy.displayName = `spy('${name}')`; + return spy; +}; + +describe('refs', () => { + let scratch; + + before( () => { + scratch = document.createElement('div'); + (document.body || document.documentElement).appendChild(scratch); + }); + + beforeEach( () => { + scratch.innerHTML = ''; + }); + + after( () => { + scratch.parentNode.removeChild(scratch); + scratch = null; + }); + + it('should invoke refs in render()', () => { + let ref = spy('ref'); + render(<div ref={ref} />, scratch); + expect(ref).to.have.been.calledOnce.and.calledWith(scratch.firstChild); + }); + + it('should invoke refs in Component.render()', () => { + let outer = spy('outer'), + inner = spy('inner'); + class Foo extends Component { + render() { + return ( + <div ref={outer}> + <span ref={inner} /> + </div> + ); + } + } + render(<Foo />, scratch); + + expect(outer).to.have.been.calledWith(scratch.firstChild); + expect(inner).to.have.been.calledWith(scratch.firstChild.firstChild); + }); + + it('should pass components to ref functions', () => { + let ref = spy('ref'), + instance; + class Foo extends Component { + constructor() { + super(); + instance = this; + } + render() { + return <div />; + } + } + render(<Foo ref={ref} />, scratch); + + expect(ref).to.have.been.calledOnce.and.calledWith(instance); + }); + + it('should pass rendered DOM from functional components to ref functions', () => { + let ref = spy('ref'); + + const Foo = () => <div />; + + let root = render(<Foo ref={ref} />, scratch); + expect(ref).to.have.been.calledOnce.and.calledWith(scratch.firstChild); + + ref.reset(); + render(<Foo ref={ref} />, scratch, root); + expect(ref).to.have.been.calledOnce.and.calledWith(scratch.firstChild); + + ref.reset(); + render(<span />, scratch, root); + expect(ref).to.have.been.calledOnce.and.calledWith(null); + }); + + it('should pass children to ref functions', () => { + let outer = spy('outer'), + inner = spy('inner'), + rerender, inst; + class Outer extends Component { + constructor() { + super(); + rerender = () => this.forceUpdate(); + } + render() { + return ( + <div> + <Inner ref={outer} /> + </div> + ); + } + } + class Inner extends Component { + constructor() { + super(); + inst = this; + } + render() { + return <span ref={inner} />; + } + } + + let root = render(<Outer />, scratch); + + expect(outer).to.have.been.calledOnce.and.calledWith(inst); + expect(inner).to.have.been.calledOnce.and.calledWith(inst.base); + + outer.reset(); + inner.reset(); + + rerender(); + + expect(outer).to.have.been.calledOnce.and.calledWith(inst); + expect(inner).to.have.been.calledOnce.and.calledWith(inst.base); + + outer.reset(); + inner.reset(); + + render(<div />, scratch, root); + + expect(outer).to.have.been.calledOnce.and.calledWith(null); + expect(inner).to.have.been.calledOnce.and.calledWith(null); + }); + + it('should pass high-order children to ref functions', () => { + let outer = spy('outer'), + inner = spy('inner'), + innermost = spy('innermost'), + outerInst, + innerInst; + class Outer extends Component { + constructor() { + super(); + outerInst = this; + } + render() { + return <Inner ref={inner} />; + } + } + class Inner extends Component { + constructor() { + super(); + innerInst = this; + } + render() { + return <span ref={innermost} />; + } + } + + let root = render(<Outer ref={outer} />, scratch); + + expect(outer, 'outer initial').to.have.been.calledOnce.and.calledWith(outerInst); + expect(inner, 'inner initial').to.have.been.calledOnce.and.calledWith(innerInst); + expect(innermost, 'innerMost initial').to.have.been.calledOnce.and.calledWith(innerInst.base); + + outer.reset(); + inner.reset(); + innermost.reset(); + root = render(<Outer ref={outer} />, scratch, root); + + expect(outer, 'outer update').to.have.been.calledOnce.and.calledWith(outerInst); + expect(inner, 'inner update').to.have.been.calledOnce.and.calledWith(innerInst); + expect(innermost, 'innerMost update').to.have.been.calledOnce.and.calledWith(innerInst.base); + + outer.reset(); + inner.reset(); + innermost.reset(); + root = render(<div />, scratch, root); + + expect(outer, 'outer unmount').to.have.been.calledOnce.and.calledWith(null); + expect(inner, 'inner unmount').to.have.been.calledOnce.and.calledWith(null); + expect(innermost, 'innerMost unmount').to.have.been.calledOnce.and.calledWith(null); + }); + + it('should not pass ref into component as a prop', () => { + let foo = spy('foo'), + bar = spy('bar'); + + class Foo extends Component { + render(){ return <div />; } + } + const Bar = spy('Bar', () => <div />); + + sinon.spy(Foo.prototype, 'render'); + + render(( + <div> + <Foo ref={foo} a="a" /> + <Bar ref={bar} b="b" /> + </div> + ), scratch); + + expect(Foo.prototype.render).to.have.been.calledWithExactly({ a:'a' }, { }, { }); + expect(Bar).to.have.been.calledWithExactly({ b:'b', ref:bar }, { }); + }); + + // Test for #232 + it('should only null refs after unmount', () => { + let root, outer, inner; + + class TestUnmount extends Component { + componentWillUnmount() { + expect(this).to.have.property('outer', outer); + expect(this).to.have.property('inner', inner); + } + + componentDidUnmount() { + expect(this).to.have.property('outer', null); + expect(this).to.have.property('inner', null); + } + + render() { + return ( + <div id="outer" ref={ c => this.outer=c }> + <div id="inner" ref={ c => this.inner=c } /> + </div> + ); + } + } + + sinon.spy(TestUnmount.prototype, 'componentWillUnmount'); + sinon.spy(TestUnmount.prototype, 'componentDidUnmount'); + + root = render(<div><TestUnmount /></div>, scratch, root); + outer = scratch.querySelector('#outer'); + inner = scratch.querySelector('#inner'); + + expect(TestUnmount.prototype.componentWillUnmount).not.to.have.been.called; + expect(TestUnmount.prototype.componentDidUnmount).not.to.have.been.called; + + root = render(<div />, scratch, root); + + expect(TestUnmount.prototype.componentWillUnmount).to.have.been.calledOnce; + expect(TestUnmount.prototype.componentDidUnmount).to.have.been.calledOnce; + }); + + it('should null and re-invoke refs when swapping component root element type', () => { + let inst; + + class App extends Component { + render() { + return <div><Child /></div>; + } + } + + class Child extends Component { + constructor(props, context) { + super(props, context); + this.state = { show:false }; + inst = this; + } + handleMount(){} + render(_, { show }) { + if (!show) return <div id="div" ref={this.handleMount}></div>; + return <span id="span" ref={this.handleMount}>some test content</span>; + } + } + sinon.spy(Child.prototype, 'handleMount'); + + render(<App />, scratch); + expect(inst.handleMount).to.have.been.calledOnce.and.calledWith(scratch.querySelector('#div')); + inst.handleMount.reset(); + + inst.setState({ show:true }); + inst.forceUpdate(); + expect(inst.handleMount).to.have.been.calledTwice; + expect(inst.handleMount.firstCall).to.have.been.calledWith(null); + expect(inst.handleMount.secondCall).to.have.been.calledWith(scratch.querySelector('#span')); + inst.handleMount.reset(); + + inst.setState({ show:false }); + inst.forceUpdate(); + expect(inst.handleMount).to.have.been.calledTwice; + expect(inst.handleMount.firstCall).to.have.been.calledWith(null); + expect(inst.handleMount.secondCall).to.have.been.calledWith(scratch.querySelector('#div')); + }); +}); |