import { render, shallowRender } from '../src'; import { h, Component } from 'preact'; import chai, { expect } from 'chai'; import { spy, stub, match } from 'sinon'; import sinonChai from 'sinon-chai'; chai.use(sinonChai); describe('render', () => { describe('Basic JSX', () => { it('should render JSX', () => { let rendered = render(
bar
), expected = `
bar
`; expect(rendered).to.equal(expected); }); describe('whitespace', () => { it('should omit whitespace between elements', () => { let children = []; for (let i=0; i<1000; i++) { children.push(Math.random()>.5 ? String(i) : h('x-'+String(i), null, i)); } let rendered = render(
x a b c {children} d
); expect(rendered).not.to.contain(/\s/); }); it('should not indent when attributes contain newlines', () => { let rendered = render(
a b c
); expect(rendered).to.equal(`
abc
`); }); }); it('should omit falsey attributes', () => { let rendered = render(
), expected = `
`; expect(rendered).to.equal(expected); expect(render(
)).to.equal(`
`); }); it('should collapse collapsible attributes', () => { let rendered = render(
), expected = `
`; expect(rendered).to.equal(expected); }); it('should omit functions', () => { let rendered = render(
{}} b={function(){}} />), expected = `
`; expect(rendered).to.equal(expected); }); it('should encode entities', () => { let rendered = render(
&'}>{'"<>&'}
), expected = `
"<>&
`; expect(rendered).to.equal(expected); }); it('should omit falsey children', () => { let rendered = render(
{null}|{undefined}|{false}
), expected = `
||
`; expect(rendered).to.equal(expected); }); it('should self-close void elements', () => { let rendered = render(
), expected = `
`; expect(rendered).to.equal(expected); }); it('does not close void elements with closing tags', () => { let rendered = render(

Hello World

), expected = `

Hello World

`; expect(rendered).to.equal(expected); }); it('should serialize object styles', () => { let rendered = render(
), expected = `
`; expect(rendered).to.equal(expected); }); it('should ignore empty object styles', () => { let rendered = render(
), expected = `
`; expect(rendered).to.equal(expected); }); it('should render SVG elements', () => { let rendered = render((
)); expect(rendered).to.equal(`
`); }); }); describe('Functional Components', () => { it('should render functional components', () => { let Test = spy( ({ foo, children }) =>
{ children }
); let rendered = render(content); expect(rendered) .to.equal(`
content
`); expect(Test) .to.have.been.calledOnce .and.calledWithExactly( match({ foo: 'test', children: ['content'] }), match({}) ); }); it('should render functional components within JSX', () => { let Test = spy( ({ foo, children }) =>
{ children }
); let rendered = render(
asdf
); expect(rendered) .to.equal(`
asdf
`); expect(Test) .to.have.been.calledOnce .and.calledWithExactly( match({ foo: 1, children: [ match({ nodeName:'span', children:['asdf'] }) ] }), match({}) ); }); it('should apply defaultProps', () => { const Test = props =>
; Test.defaultProps = { foo: 'default foo', bar: 'default bar' }; expect(render(), 'defaults').to.equal('
'); expect(render(), 'partial').to.equal('
'); expect(render(), 'overridden').to.equal('
'); }); }); describe('Classical Components', () => { it('should render classical components', () => { let Test = spy(class Test extends Component { render({ foo, children }, state) { return
{ children }
; } }); spy(Test.prototype, 'render'); let rendered = render(content); const PROPS = { foo: 'test', children: ['content'] }; expect(rendered) .to.equal(`
content
`); expect(Test) .to.have.been.calledOnce .and.calledWith(match(PROPS), match({})); expect(Test.prototype.render) .to.have.been.calledOnce .and.calledWithExactly( match(PROPS), match({}), // empty state match({}) // empty context ); }); it('should render classical components within JSX', () => { let Test = spy(class Test extends Component { render({ foo, children }, state) { return
{ children }
; } }); spy(Test.prototype, 'render'); let rendered = render(
asdf
); expect(rendered) .to.equal(`
asdf
`); expect(Test).to.have.been.calledOnce; expect(Test.prototype.render) .to.have.been.calledOnce .and.calledWithExactly( match({ foo: 1, children: [ match({ nodeName:'span', children:['asdf'] }) ] }), match({}), // empty state match({}) ); }); it('should apply defaultProps', () => { class Test extends Component { static defaultProps = { foo: 'default foo', bar: 'default bar' }; render(props) { return
; } } expect(render(), 'defaults').to.equal('
'); expect(render(), 'partial').to.equal('
'); expect(render(), 'overridden').to.equal('
'); }); it('should invoke componentWillMount', () => { class Test extends Component { componentWillMount() {} render(props) { return
; } } spy(Test.prototype, 'componentWillMount'); spy(Test.prototype, 'render'); render(); expect(Test.prototype.componentWillMount) .to.have.been.calledOnce .and.to.have.been.calledBefore(Test.prototype.render); }); it('should pass context to grandchildren', () => { const CONTEXT = { a:'a' }; const PROPS = { b:'b' }; class Outer extends Component { getChildContext() { return CONTEXT; } render(props) { return
; } } spy(Outer.prototype, 'getChildContext'); class Inner extends Component { render(props, state, context) { return
{ context && context.a }
; } } spy(Inner.prototype, 'render'); render(); expect(Outer.prototype.getChildContext).to.have.been.calledOnce; expect(Inner.prototype.render).to.have.been.calledWith(match({}), {}, CONTEXT); CONTEXT.foo = 'bar'; render(); expect(Outer.prototype.getChildContext).to.have.been.calledTwice; expect(Inner.prototype.render).to.have.been.calledWith(match(PROPS), {}, CONTEXT); }); it('should pass context to direct children', () => { const CONTEXT = { a:'a' }; const PROPS = { b:'b' }; class Outer extends Component { getChildContext() { return CONTEXT; } render(props) { return ; } } spy(Outer.prototype, 'getChildContext'); class Inner extends Component { render(props, state, context) { return
{ context && context.a }
; } } spy(Inner.prototype, 'render'); render(); expect(Outer.prototype.getChildContext).to.have.been.calledOnce; expect(Inner.prototype.render).to.have.been.calledWith(match({}), {}, CONTEXT); CONTEXT.foo = 'bar'; render(); expect(Outer.prototype.getChildContext).to.have.been.calledTwice; expect(Inner.prototype.render).to.have.been.calledWith(match(PROPS), {}, CONTEXT); // make sure render() could make use of context.a expect(Inner.prototype.render).to.have.returned(match({ children:['a'] })); }); it('should preserve existing context properties when creating child contexts', () => { let outerContext = { outer:true }, innerContext = { inner:true }; class Outer extends Component { getChildContext() { return { outerContext }; } render() { return
; } } class Inner extends Component { getChildContext() { return { innerContext }; } render() { return ; } } class InnerMost extends Component { render() { return test; } } spy(Inner.prototype, 'render'); spy(InnerMost.prototype, 'render'); render(); expect(Inner.prototype.render).to.have.been.calledWith(match({}), {}, { outerContext }); expect(InnerMost.prototype.render).to.have.been.calledWith(match({}), {}, { outerContext, innerContext }); }); }); describe('High-order components', () => { class Outer extends Component { render({ children, ...props }) { return child { children }; } } class Inner extends Component { render({ children, ...props }) { return
{ children }
; } } it('should resolve+render high order components', () => { let rendered = render(foo); expect(rendered).to.equal('
child foo
'); }); it('should render child inline when shallow=true', () => { let rendered = shallowRender(foo); expect(rendered).to.equal('child foo'); }); it('should render nested high order components when shallowHighOrder=false', () => { // using functions for meaningful generation of displayName function Outer() { return ; } function Middle() { return
; } function Inner() { return 'hi'; } let rendered = render(); expect(rendered).to.equal('
hi
'); rendered = render(, null, { shallow:true }); expect(rendered, '{shallow:true}').to.equal(''); rendered = render(, null, { shallow:true, shallowHighOrder:false }); expect(rendered, '{shallow:true,shallowHighOrder:false}').to.equal('
', 'but it should never render nested grandchild components'); }); }); describe('dangerouslySetInnerHTML', () => { it('should support dangerouslySetInnerHTML', () => { // some invalid HTML to make sure we're being flakey: let html = 'asdf some text
  • foo
  • bar
'; let rendered = render(
); expect(rendered).to.equal(`
${html}
`); }); it('should override children', () => { let rendered = render(
bar
); expect(rendered).to.equal('
foo
'); }); }); describe('className / class massaging', () => { it('should render class using className', () => { let rendered = render(
); expect(rendered).to.equal('
'); }); it('should render class using class', () => { let rendered = render(
); expect(rendered).to.equal('
'); }); it('should prefer class over className', () => { let rendered = render(
); expect(rendered).to.equal('
'); }); it('should stringify object classNames', () => { let rendered = render(
); expect(rendered, 'class').to.equal('
'); rendered = render(
); expect(rendered, 'className').to.equal('
'); }); }); describe('sortAttributes', () => { it('should leave attributes unsorted by default', () => { let rendered = render(
); expect(rendered).to.equal('
'); }); it('should sort attributes lexicographically if enabled', () => { let rendered = render(
, null, { sortAttributes:true }); expect(rendered).to.equal('
'); }); }); describe('xml:true', () => { let renderXml = jsx => render(jsx, null, { xml:true }); it('should render end-tags', () => { expect(renderXml(
)).to.equal(`
`); expect(renderXml()).to.equal(``); expect(renderXml(b)).to.equal(`b`); }); it('should render boolean attributes with named values', () => { expect(renderXml(
)).to.equal(`
`); }); it('should exclude falsey attributes', () => { expect(renderXml(
)).to.equal(`
`); }); }); describe('state locking', () => { it('should set _disable and __x to true', () => { let inst; class Foo extends Component { constructor(props, context) { super(props, context); inst = this; } render() { return
; } } expect(render()).to.equal('
'); expect(inst).to.have.property('_disable', true); expect(inst).to.have.property('__x', true); }); it('should prevent re-rendering', () => { const Bar = stub().returns(
); let count = 0; class Foo extends Component { componentWillMount() { this.forceUpdate(); } render() { return ; } } expect(render()).to.equal('
'); expect(Bar).to.have.been.calledOnce.and.calledWithMatch({ count: 1 }); }); }); });