import renderToString from 'preact-render-to-string'; import { rerender } from 'preact'; import React from '../src'; describe('components', () => { 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 be sane', () => { let props; class Demo extends React.Component { render() { let { a, b } = this.props; props = this.props; return
{this.props.children}
; } } let html = renderToString( inner ); expect(props).to.exist.and.deep.equal({ a: 'b', c: 'd', children: 'inner' }); expect(html).to.equal('
inner
'); }); it('should support replaceState()', done => { class Demo extends React.Component { render() { return
; } } let render = sinon.spy(Demo.prototype, 'render'), inst; React.render( inst = c} />, scratch); inst.setState({ foo: 'bar', baz: 'bat' }); setTimeout(() => { expect(inst.state).to.eql({ foo: 'bar', baz: 'bat' }); let callbackState; let callback = sinon.spy(() => { callbackState = inst.state; }); inst.replaceState({}, callback); setTimeout(() => { expect(callback).to.have.been.calledOnce; expect(callbackState).to.eql({}); expect(inst.state).to.eql({}); done(); }, 10); }, 10); }); it('should alias props.children', () => { class Foo extends React.Component { render() { return
{this.props.children}
; } } let children = ['a', b, c], foo; React.render(( foo = c}> {children} ), scratch); expect(foo.props).to.exist.and.have.property('children').eql(children); }); it('should single out children before componentWillReceiveProps', () => { let props; class Child extends React.Component { componentWillReceiveProps(newProps) { props = newProps; } } class Parent extends React.Component { render() { return second; } } let a = React.render(, scratch); a.forceUpdate(); expect(props).to.exist.and.deep.equal({ children: 'second' }); }); it('should support array[object] children', () => { let children; class Foo extends React.Component { render() { children = this.props.children; return
; } } const data = [{ a: '' }]; React.render({data}, scratch); expect(children).to.exist.and.deep.equal(data); }); describe('getInitialState', () => { it('should be invoked for new components', () => { class Foo extends React.Component { getInitialState() { return { foo: 'bar' }; } render() { return
; } } sinon.spy(Foo.prototype, 'getInitialState'); let a = React.render(, scratch); expect(Foo.prototype.getInitialState).to.have.been.calledOnce; expect(a.state).to.eql({ foo: 'bar' }); }); }); describe('defaultProps', () => { it('should support defaultProps for components', () => { let render = sinon.stub().returns(
); const Foo = React.createClass({ defaultProps: { foo: 'default foo', bar: 'default bar' }, render }); React.render(, scratch); expect(render).to.have.been.calledWithMatch(Foo.defaultProps); render.resetHistory(); React.render(, scratch); expect(render).to.have.been.calledWithMatch({ foo: 'default foo', bar: 'bar' }); }); it('should support defaultProps for pure components', () => { const Foo = sinon.stub().returns(
); Foo.defaultProps = { foo: 'default foo', bar: 'default bar' }; React.render(, scratch); expect(Foo).to.have.been.calledWithMatch(Foo.defaultProps); Foo.resetHistory(); React.render(, scratch); expect(Foo).to.have.been.calledWithMatch({ foo: 'default foo', bar: 'bar' }); }); }); describe('propTypes', () => { function checkPropTypes(Foo, name = 'Foo') { sinon.stub(console, 'error'); React.render(, scratch); expect(console.error).to.have.been.calledWithMatch( 'Warning: Failed prop type: The prop `func` is marked as required in `' + name + '`, but its value is `undefined`.' ); expect(console.error).to.have.been.called; console.error.resetHistory(); React.render( { }} />, scratch); expect(console.error).not.to.have.been.called; React.render( { }} bool="one" />, scratch); expect(console.error).to.have.been.calledWithMatch( 'Warning: Failed prop type: Invalid prop `bool` of type `string` supplied to `' + name + '`, expected `boolean`.' ); console.error.restore(); } it('should support propTypes for ES Class components', () => { class Foo extends React.Component { static propTypes = { func: React.PropTypes.func.isRequired, bool: React.PropTypes.bool }; render() { return
; } } checkPropTypes(Foo); }); it('should support propTypes for createClass components', () => { const Bar = React.createClass({ propTypes: { func: React.PropTypes.func.isRequired, bool: React.PropTypes.bool }, render: () =>
}); checkPropTypes(Bar, 'Bar'); }); it('should support propTypes for pure components', () => { function Baz() { return
; } Baz.propTypes = { func: React.PropTypes.func.isRequired, bool: React.PropTypes.bool }; checkPropTypes(Baz, 'Baz'); const Bip = () =>
; Bip.displayName = 'Bip'; Bip.propTypes = { func: React.PropTypes.func.isRequired, bool: React.PropTypes.bool }; checkPropTypes(Bip, 'Bip'); }); }); describe("mixins", () => { describe("getDefaultProps", () => { it('should use a mixin', () => { const Foo = React.createClass({ mixins: [ { getDefaultProps: () => ({ a: true }) } ], render() { return
; } }); expect(Foo.defaultProps).to.eql({ a: true }); }); it('should combine the results', () => { const Foo = React.createClass({ mixins: [ { getDefaultProps: () => ({ a: true }) }, { getDefaultProps: () => ({ b: true }) } ], getDefaultProps() { return { c: true }; }, render() { return
; } }); expect(Foo.defaultProps).to.eql({ a: true, b: true, c: true }); }); it('should work with statics', () => { const Foo = React.createClass({ statics: { a: false }, getDefaultProps() { return { b: true, c: this.a }; }, render() { return
; } }); expect(Foo.defaultProps).to.eql({ b: true, c: false }); }); // Disabled to save bytes xit('should throw an error for duplicate keys', () => { expect(() => { const Foo = React.createClass({ mixins: [ { getDefaultProps: () => ({ a: true }) } ], getDefaultProps() { return { a: true }; }, render() { return
; } }); }).to.throw(); }); }); describe("getInitialState", () => { it('should combine the results', () => { const Foo = React.createClass({ mixins: [ { getInitialState: () => ({ a: true }) }, { getInitialState: () => ({ b: true }) } ], getInitialState() { return { c: true }; }, render() { return
; } }); let a = React.render(, scratch); expect(a.state).to.eql({ a: true, b: true, c: true }); }); // Disabled to save bytes xit('should throw an error for duplicate keys', () => { const Foo = React.createClass({ mixins: [ { getInitialState: () => ({ a: true }) } ], getInitialState() { return { a: true }; }, render() { return
; } }); expect(() => { React.render(, scratch); }).to.throw(); }); }); }); describe('refs', () => { it('should support string refs', () => { let inst, innerInst; class Foo extends React.Component { constructor() { super(); inst = this; } render() { return (

h1

text

); } } const Inner = React.createClass({ render() { innerInst = this; return (

h2

); } }); React.render(, scratch); expect(inst).to.exist; expect(inst.refs).to.be.an('object'); expect(inst.refs).to.have.property('top', scratch.firstChild); expect(inst.refs).to.have.property('h1', scratch.querySelector('h1')); expect(inst.refs).to.have.property('p', scratch.querySelector('p')); expect(inst.refs).to.have.property('span', scratch.querySelector('span')); expect(inst.refs).to.have.property('inner', innerInst, 'ref to child component'); expect(inst.refs).not.to.have.property('contained'); expect(inst.refs).not.to.have.property('inner-h2'); expect(innerInst.refs).to.have.all.keys(['contained', 'inner-h2']); expect(innerInst.refs).to.have.property('contained', scratch.querySelector('.contained')); expect(innerInst.refs).to.have.property('inner-h2', scratch.querySelector('h2')); }); it('should retain support for function refs', () => { let ref1 = sinon.spy(), ref2 = sinon.spy(), componentRef = sinon.spy(), innerInst; class Foo extends React.Component { render() { return this.props.empty ? () : (

h1

); } } const Inner = React.createClass({ render() { innerInst = this; return
; } }); React.render(, scratch); expect(ref1).to.have.have.been.calledOnce.and.calledWith(scratch.firstChild); expect(ref2).to.have.have.been.calledOnce.and.calledWith(scratch.querySelector('h1')); expect(componentRef).to.have.been.calledOnce.and.calledWith(innerInst); React.render(, scratch); // React.unmountComponentAtNode(scratch); expect(ref1, 'ref1').to.have.have.been.calledTwice.and.calledWith(null); expect(ref2, 'ref2').to.have.have.been.calledTwice.and.calledWith(null); expect(componentRef, 'componentRef').to.have.been.calledTwice.and.calledWith(null); }); it('should support string refs via cloneElement()', () => { let inner, outer; const Inner = React.createClass({ render() { inner = this; return (
{React.cloneElement(React.Children.only(this.props.children), { id: 'one' })} {React.cloneElement(React.Children.only(this.props.children), { id: 'two', ref: 'two' })}
); } }); const Outer = React.createClass({ render() { outer = this; return ( foo ); } }); React.render(, scratch); let one = scratch.firstElementChild.children[0]; let two = scratch.firstElementChild.children[1]; expect(outer).to.have.property('refs').eql({ one }); expect(inner).to.have.property('refs').eql({ two }); }); }); describe('PureComponent', () => { it('should be a class', () => { expect(React).to.have.property('PureComponent').that.is.a('function'); }); it('should only re-render when props or state change', () => { class C extends React.PureComponent { render() { return
; } } let spy = sinon.spy(C.prototype, 'render'); let inst = React.render(, scratch); expect(spy).to.have.been.calledOnce; spy.resetHistory(); inst = React.render(, scratch); expect(spy).not.to.have.been.called; let b = { foo: 'bar' }; inst = React.render(, scratch); expect(spy).to.have.been.calledOnce; spy.resetHistory(); inst = React.render(, scratch); expect(spy).not.to.have.been.called; inst.setState({}); rerender(); expect(spy).not.to.have.been.called; inst.setState({ a: 'a', b }); rerender(); expect(spy).to.have.been.calledOnce; spy.resetHistory(); inst.setState({ a: 'a', b }); rerender(); expect(spy).not.to.have.been.called; }); }); });