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 (
);
}
}
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;
});
});
});