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(
, 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 (
); } } render(, 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
; } } render(, 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 = () =>
; let root = render(, scratch); expect(ref).to.have.been.calledOnce.and.calledWith(scratch.firstChild); ref.reset(); render(, scratch, root); expect(ref).to.have.been.calledOnce.and.calledWith(scratch.firstChild); ref.reset(); render(, 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 (
); } } class Inner extends Component { constructor() { super(); inst = this; } render() { return ; } } let root = render(, 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(
, 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 ; } } class Inner extends Component { constructor() { super(); innerInst = this; } render() { return ; } } let root = render(, 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(, 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(
, 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
; } } const Bar = spy('Bar', () =>
); sinon.spy(Foo.prototype, 'render'); render((
), scratch); expect(Foo.prototype.render).to.have.been.calledWithMatch({ ref:sinon.match.falsy, a:'a' }, { }, { }); expect(Bar).to.have.been.calledWithMatch({ 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 (
this.outer=c }>
this.inner=c } />
); } } sinon.spy(TestUnmount.prototype, 'componentWillUnmount'); sinon.spy(TestUnmount.prototype, 'componentDidUnmount'); root = render(
, 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(
, 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
; } } class Child extends Component { constructor(props, context) { super(props, context); this.state = { show:false }; inst = this; } handleMount(){} render(_, { show }) { if (!show) return
; return some test content; } } sinon.spy(Child.prototype, 'handleMount'); render(, 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')); }); it('should add refs to components representing DOM nodes with no attributes if they have been pre-rendered', () => { // Simulate pre-render let parent = document.createElement('div'); let child = document.createElement('div'); parent.appendChild(child); scratch.appendChild(parent); // scratch contains:
let ref = spy('ref'); function Wrapper() { return
; } render(
, scratch, scratch.firstChild); expect(ref).to.have.been.calledOnce.and.calledWith(scratch.firstChild.firstChild); }); });