From 30b577138dda685f65a8529be1866afa6e321845 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Tue, 4 Oct 2016 11:50:26 +0200 Subject: Squashed 'thirdparty/preact/' content from commit b2d90cc git-subtree-dir: thirdparty/preact git-subtree-split: b2d90cc116f1d1998f7a7c98dc6986bf4c1841f4 --- test/browser/refs.js | 287 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 287 insertions(+) create mode 100644 test/browser/refs.js (limited to 'test/browser/refs.js') diff --git a/test/browser/refs.js b/test/browser/refs.js new file mode 100644 index 000000000..89678b76e --- /dev/null +++ b/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(
, 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.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 ( +
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')); + }); +}); -- cgit v1.2.3