preact-material-components
Version:
preact wrapper for "Material Components for the web"
350 lines (306 loc) • 10.2 kB
JavaScript
import React, {
render,
createClass,
createElement,
cloneElement,
Component,
PropTypes,
unstable_renderSubtreeIntoContainer,
__spread
} from '../src';
describe('preact-compat', () => {
describe('render()', () => {
it('should be exported', () => {
expect(React)
.to.have.property('render')
.that.is.a('function')
.that.equals(render);
});
it('should replace isomorphic content', () => {
let ce = (type) => document.createElement(type);
let Text = (text) => document.createTextNode(text);
let root = ce('div');
let initialChild = ce('div');
initialChild.appendChild(Text('initial content'));
root.appendChild(initialChild);
render(<div>dynamic content</div>, root);
expect(root)
.to.have.property('textContent')
.that.is.a('string')
.that.equals('dynamic content');
});
it('should remove extra elements', () => {
let ce = (type) => document.createElement(type);
let Text = (text) => document.createTextNode(text);
let root = ce('div');
let c1 = ce('div');
c1.appendChild(Text('isomorphic content'));
root.appendChild(c1);
let c2 = ce('div');
c2.appendChild(Text('extra content'));
root.appendChild(c2);
render(<div>dynamic content</div>, root);
expect(root)
.to.have.property('textContent')
.that.is.a('string')
.that.equals('dynamic content');
});
it('should remove text nodes', () => {
let ce = (type) => document.createElement(type);
let Text = (text) => document.createTextNode(text);
let root = ce('div');
root.appendChild(Text('Text Content in the root'));
root.appendChild(Text('More Text Content'));
render(<div>dynamic content</div>, root);
expect(root)
.to.have.property('textContent')
.that.is.a('string')
.that.equals('dynamic content');
});
it('should support defaultValue', () => {
let scratch = document.createElement('div');
(document.body || document.documentElement).appendChild(scratch);
render(<input defaultValue="foo"></input>, scratch);
expect(scratch.firstElementChild).to.have.property('value', 'foo');
});
});
describe('createClass()', () => {
it('should be exported', () => {
expect(React)
.to.have.property('createClass')
.that.is.a('function')
.that.equals(createClass);
});
it('should create a Component', () => {
let specState = { something: 1 };
let spec = {
foo: 'bar',
getInitialState() {
return specState;
},
method: sinon.spy()
};
const C = createClass(spec);
let inst = new C();
expect(inst).to.have.property('foo', 'bar');
expect(inst).to.have.property('state', specState);
expect(inst).to.have.property('method').that.is.a('function');
expect(inst).to.be.an.instanceof(Component);
inst.method('a','b');
expect(spec.method)
.to.have.been.calledOnce
.and.calledOn(inst)
.and.calledWithExactly('a', 'b');
});
it('should not bind blacklisted methods', () => {
let constructor = () => {};
let render = () => null;
const C = createClass({
constructor,
render
});
let c = new C();
expect(c).to.have.property('constructor').that.equals(constructor);
expect(c).to.have.property('render').not.with.property('__bound');
});
it('should copy statics', () => {
let def = {
statics: {
foo: 'bar',
baz() {}
}
};
let c = createClass(def);
expect(c).to.have.property('foo', def.statics.foo);
expect(c).to.have.property('baz', def.statics.baz);
});
it('should support mixins', () => {
let def = {
mixins: [
{
foo: sinon.spy(),
bar: sinon.spy()
},
{
bar: sinon.spy(),
componentWillMount: sinon.spy(),
render: 'nothing here'
},
{
componentWillMount: sinon.spy()
}
],
foo: sinon.spy(),
componentWillMount: sinon.spy(),
render: sinon.stub().returns(null)
};
let C = createClass(def);
let inst = new C();
inst.foo();
expect(def.foo).to.have.been.calledOnce;
expect(def.mixins[0].foo).to.have.been.calledOnce.and.calledBefore(def.foo);
inst.bar();
expect(def.mixins[0].bar).to.have.been.calledOnce;
expect(def.mixins[1].bar).to.have.been.calledOnce.and.calledAfter(def.mixins[0].bar);
let props = {},
state = {};
inst.componentWillMount(props, state);
expect(def.mixins[1].componentWillMount)
.to.have.been.calledOnce
.and.calledWithExactly(props, state);
expect(def.mixins[2].componentWillMount)
.to.have.been.calledOnce
.and.calledWithExactly(props, state)
.and.calledAfter(def.mixins[1].componentWillMount);
expect(inst.render(props, state)).to.equal(null);
});
});
describe('createElement()', () => {
it('should be exported', () => {
expect(React)
.to.have.property('createElement')
.that.is.a('function')
.that.equals(createElement);
});
it('should normalize vnodes', () => {
let vnode = <div a="b"><a>t</a></div>;
// using typeof Symbol here injects a polyfill, which ruins the test. we'll hardcode the non-symbol value for now.
let $$typeof = 0xeac7;
expect(vnode).to.have.property('$$typeof', $$typeof);
expect(vnode).to.have.property('type', 'div');
expect(vnode).to.have.property('props').that.is.an('object');
expect(vnode).to.have.deep.property('props.children[0]');
expect(vnode.props.children[0]).to.have.property('$$typeof', $$typeof);
expect(vnode.props.children[0]).to.have.property('type', 'a');
expect(vnode.props.children[0]).to.have.property('props').that.is.an('object');
expect(vnode.props.children[0].props).to.eql({ children:['t'] });
});
it('should normalize onChange', () => {
let props = { onChange(){} };
function expectToBeNormalized(vnode, desc) {
expect(vnode, desc).to.have.property('props').with.all.keys(['oninput'].concat(vnode.props.type ? 'type' : [])).and.property('oninput').that.is.a('function');
}
function expectToBeUnmodified(vnode, desc) {
expect(vnode, desc).to.have.property('props').eql({ ...props, ...(vnode.props.type ? { type:vnode.props.type } : {}) });
}
expectToBeUnmodified(<div {...props} />, '<div>');
expectToBeUnmodified(<input {...props} type="radio" />, '<input type="radio">');
expectToBeUnmodified(<input {...props} type="checkbox" />, '<input type="checkbox">');
expectToBeUnmodified(<input {...props} type="file" />, '<input type="file">');
expectToBeNormalized(<textarea {...props} />, '<textarea>');
expectToBeNormalized(<input {...props} />, '<input>');
expectToBeNormalized(<input {...props} type="text" />, '<input type="text">');
});
});
describe('Component', () => {
it('should be exported', () => {
expect(React)
.to.have.property('Component')
.that.is.a('function')
.that.equals(Component);
});
});
describe('PropTypes', () => {
it('should be exported', () => {
expect(React)
.to.have.property('PropTypes')
.that.is.an('object')
.that.equals(PropTypes);
});
});
describe('cloneElement', () => {
it('should clone elements', () => {
let element = <foo a="b" c="d">a<span>b</span></foo>;
expect(cloneElement(element)).to.eql(element);
});
it('should support props.children', () => {
let element = <foo children={<span>b</span>}></foo>;
let clone = cloneElement(element);
expect(clone).to.eql(element);
expect(cloneElement(clone).props.children).to.eql(element.props.children);
});
it('children take precedence over props.children', () => {
let element = <foo children={<span>c</span>}><div>b</div></foo>;
let clone = cloneElement(element);
expect(clone).to.eql(element);
expect(clone.children[0].nodeName).to.eql('div');
});
it('should support children in prop argument', () => {
let element = <foo></foo>;
let children = [<span>b</span>];
let clone = cloneElement(element, { children });
expect(clone.children).to.eql(children);
});
it('children argument takes precedence over props.children', () => {
let element = <foo></foo>;
let childrenA = [<span>b</span>];
let childrenB = [<div>c</div>];
let clone = cloneElement(element, { children: childrenA }, ...childrenB);
expect(clone.children).to.eql(childrenB);
});
it('children argument takes precedence over props.children even if falsey', () => {
let element = <foo></foo>;
let childrenA = [<span>b</span>];
let clone = cloneElement(element, { children: childrenA }, undefined);
expect(clone.children).to.eql(undefined);
});
});
describe('unstable_renderSubtreeIntoContainer', () => {
class Inner extends Component {
render() {
return null;
}
getNode() {
return 'inner';
}
}
it('should export instance', () => {
class App extends Component {
render() {
return null;
}
componentDidMount() {
this.renderInner();
}
renderInner() {
const wrapper = document.createElement('div');
this.inner = unstable_renderSubtreeIntoContainer(this, <Inner/>, wrapper);
}
}
const root = document.createElement('div');
const app = render(<App/>, root);
expect(typeof app.inner.getNode === 'function').to.equal(true);
});
it('should there must be a context in callback', () => {
class App extends Component {
render() {
return null;
}
componentDidMount() {
this.renderInner();
}
renderInner() {
const wrapper = document.createElement('div');
const self = this;
unstable_renderSubtreeIntoContainer(this, <Inner/>, wrapper, function() {
self.inner = this;
});
}
}
const root = document.createElement('div');
const app = render(<App/>, root);
expect(typeof app.inner.getNode === 'function').to.equal(true);
});
});
describe('Unsupported hidden internal __spread API', () => {
it('should work with multiple objects', () => {
const start = {};
const result = React.__spread(start, {one: 1, two: 3}, {two: 2});
expect(result).to.equal(start);
expect(start).to.deep.equal({ one: 1, two: 2});
});
it('should be exported on default and as __spread', () => {
expect(__spread).to.equal(React.__spread);
});
});
});