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(
{}} 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(
);
expect(rendered)
.to.equal(`
`);
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(
);
expect(rendered)
.to.equal(`
`);
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
';
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 });
});
});
});