wallet-core/thirdparty/preact/test/browser/refs.js
2016-11-08 15:19:39 +01:00

306 lines
8.0 KiB
JavaScript

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.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 (
<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'));
});
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: <div><div></div></div>
let ref = spy('ref');
function Wrapper() {
return <div></div>;
}
render(<div><Wrapper ref={ref} /></div>, scratch, scratch.firstChild);
expect(ref).to.have.been.calledOnce.and.calledWith(scratch.firstChild.firstChild);
});
});