this.outer=c }>
251 |
this.inner=c } />
252 |
253 | );
254 | }
255 | }
256 |
257 | sinon.spy(TestUnmount.prototype, 'componentWillUnmount');
258 |
259 | root = render(
, scratch, root);
260 | outer = scratch.querySelector('#outer');
261 | inner = scratch.querySelector('#inner');
262 |
263 | expect(TestUnmount.prototype.componentWillUnmount).not.to.have.been.called;
264 |
265 | root = render(
, scratch, root);
266 |
267 | expect(TestUnmount.prototype.componentWillUnmount).to.have.been.calledOnce;
268 | });
269 |
270 | it('should null and re-invoke refs when swapping component root element type', () => {
271 | let inst;
272 |
273 | class App extends Component {
274 | render() {
275 | return
;
276 | }
277 | }
278 |
279 | class Child extends Component {
280 | constructor(props, context) {
281 | super(props, context);
282 | this.state = { show:false };
283 | inst = this;
284 | }
285 | handleMount(){
286 | }
287 | render(_, { show }) {
288 | if (!show) return
;
289 | return
some test content;
290 | }
291 | }
292 | sinon.spy(Child.prototype, 'handleMount');
293 |
294 | const dom = render(
, scratch);
295 | expect(inst.handleMount).to.have.been.calledOnce.and.calledWith(findVDom(dom.firstChild));
296 | inst.handleMount.resetHistory();
297 |
298 | inst.setState({ show:true });
299 | inst.forceUpdate();
300 | expect(inst.handleMount).to.have.been.calledTwice;
301 | expect(inst.handleMount.firstCall).to.have.been.calledWith(null);
302 | expect(inst.handleMount.secondCall).to.have.been.calledWith(findVDom(dom.firstChild));
303 | inst.handleMount.resetHistory();
304 |
305 | inst.setState({ show:false });
306 | inst.forceUpdate();
307 | expect(inst.handleMount).to.have.been.calledTwice;
308 | expect(inst.handleMount.firstCall).to.have.been.calledWith(null);
309 | expect(inst.handleMount.secondCall).to.have.been.calledWith(findVDom(dom.firstChild));
310 | });
311 |
312 |
313 | it('should add refs to components representing DOM nodes with no attributes if they have been pre-rendered', () => {
314 | // Simulate pre-render
315 | let parent = document.createElement('div');
316 | let child = document.createElement('div');
317 | parent.appendChild(child);
318 | scratch.appendChild(parent); // scratch contains:
319 |
320 | let ref = spy('ref');
321 |
322 |
323 | class Wrapper {
324 | render() {
325 | return
;
326 | }
327 | }
328 |
329 | const dom = render(
ref(findVDom(c)) } />
, scratch, scratch.firstChild);
330 | expect(ref).to.have.been.calledOnce.and.calledWith(findVDom(dom.firstChild));
331 | });
332 |
333 | it('createRef -> should add refs to components representing DOM nodes with no attributes if they have been pre-rendered', () => {
334 | // Simulate pre-render
335 | let parent = document.createElement('div');
336 | let child = document.createElement('div');
337 | parent.appendChild(child);
338 | scratch.appendChild(parent); // scratch contains:
339 |
340 | let ref = createRef() //spy('ref');
341 | class Wrapper {
342 | render() {
343 | return
;
344 | }
345 | }
346 |
347 | let dom = render(
, scratch, scratch.firstChild);
348 | expect(findDOMNode(ref.current)).to.equal(dom.firstChild);
349 | dom = render(
, scratch, scratch.firstChild);
350 | expect(ref.current).to.equal(null);
351 | });
352 | });
353 |
--------------------------------------------------------------------------------
/test/browser/spec.js:
--------------------------------------------------------------------------------
1 | import { h, render, rerender, Component } from 'zreact';
2 | /** @jsx h */
3 |
4 | const EMPTY_CHILDREN = null;
5 |
6 | describe('Component spec', () => {
7 | let scratch;
8 |
9 | before( () => {
10 | scratch = document.createElement('div');
11 | (document.body || document.documentElement).appendChild(scratch);
12 | });
13 |
14 | beforeEach( () => {
15 | scratch.innerHTML = '';
16 | });
17 |
18 | after( () => {
19 | scratch.parentNode.removeChild(scratch);
20 | scratch = null;
21 | });
22 |
23 | describe('defaultProps', () => {
24 | it('should apply default props on initial render', () => {
25 | class WithDefaultProps extends Component {
26 | constructor(props, context) {
27 | super(props, context);
28 | expect(props).to.be.deep.equal({
29 | children: EMPTY_CHILDREN,
30 | fieldA: 1, fieldB: 2,
31 | fieldC: 1, fieldD: 2
32 | });
33 | }
34 | render() {
35 | return
;
36 | }
37 | }
38 | WithDefaultProps.defaultProps = { fieldC: 1, fieldD: 1 };
39 | render(
, scratch);
40 | });
41 |
42 | it('should apply default props on rerender', () => {
43 | let doRender;
44 | class Outer extends Component {
45 | constructor() {
46 | super();
47 | this.state = { i:1 };
48 | }
49 | componentDidMount() {
50 | doRender = () => this.setState({ i: 2 });
51 | }
52 | render(props, { i }) {
53 | return
;
54 | }
55 | }
56 | class WithDefaultProps extends Component {
57 | constructor(props, context) {
58 | super(props, context);
59 | this.ctor(props, context);
60 | }
61 | ctor(){}
62 | componentWillReceiveProps() {}
63 | render() {
64 | return
;
65 | }
66 | }
67 | WithDefaultProps.defaultProps = { fieldC: 1, fieldD: 1 };
68 |
69 | let proto = WithDefaultProps.prototype;
70 | sinon.spy(proto, 'ctor');
71 | sinon.spy(proto, 'componentWillReceiveProps');
72 | sinon.spy(proto, 'render');
73 |
74 | render(
, scratch);
75 | doRender();
76 |
77 | const PROPS1 = {
78 | fieldA: 1, fieldB: 1,
79 | fieldC: 1, fieldD: 1
80 | };
81 |
82 | const PROPS2 = {
83 | fieldA: 1, fieldB: 2,
84 | fieldC: 1, fieldD: 2
85 | };
86 |
87 | expect(proto.ctor).to.have.been.calledWithMatch(PROPS1);
88 | expect(proto.render).to.have.been.calledWithMatch(PROPS1);
89 |
90 | rerender();
91 |
92 | // expect(proto.ctor).to.have.been.calledWith(PROPS2);
93 | expect(proto.componentWillReceiveProps).to.have.been.calledWithMatch(PROPS2);
94 | expect(proto.render).to.have.been.calledWithMatch(PROPS2);
95 | });
96 |
97 | // @TODO: migrate this to preact-compat
98 | xit('should cache default props', () => {
99 | class WithDefaultProps extends Component {
100 | constructor(props, context) {
101 | super(props, context);
102 | expect(props).to.be.deep.equal({
103 | fieldA: 1, fieldB: 2,
104 | fieldC: 1, fieldD: 2,
105 | fieldX: 10
106 | });
107 | }
108 | getDefaultProps() {
109 | return { fieldA: 1, fieldB: 1 };
110 | }
111 | render() {
112 | return
;
113 | }
114 | }
115 | WithDefaultProps.defaultProps = { fieldC: 1, fieldD: 1 };
116 | sinon.spy(WithDefaultProps.prototype, 'getDefaultProps');
117 | render((
118 |
119 |
120 |
121 |
122 |
123 | ), scratch);
124 | expect(WithDefaultProps.prototype.getDefaultProps).to.be.calledOnce;
125 | });
126 | });
127 |
128 | describe('forceUpdate', () => {
129 | it('should force a rerender', () => {
130 | let forceUpdate;
131 | class ForceUpdateComponent extends Component {
132 | componentWillUpdate() {}
133 | componentDidMount() {
134 | forceUpdate = () => this.forceUpdate();
135 | }
136 | render() {
137 | return
;
138 | }
139 | }
140 | sinon.spy(ForceUpdateComponent.prototype, 'componentWillUpdate');
141 | sinon.spy(ForceUpdateComponent.prototype, 'forceUpdate');
142 | render(
, scratch);
143 | expect(ForceUpdateComponent.prototype.componentWillUpdate).not.to.have.been.called;
144 |
145 | forceUpdate();
146 |
147 | expect(ForceUpdateComponent.prototype.componentWillUpdate).to.have.been.called;
148 | expect(ForceUpdateComponent.prototype.forceUpdate).to.have.been.called;
149 | });
150 |
151 | it('should add callback to renderCallbacks', () => {
152 | let forceUpdate;
153 | let callback = sinon.spy();
154 | class ForceUpdateComponent extends Component {
155 | componentDidMount() {
156 | forceUpdate = () => this.forceUpdate(callback);
157 | }
158 | render() {
159 | return
;
160 | }
161 | }
162 | sinon.spy(ForceUpdateComponent.prototype, 'forceUpdate');
163 | render(
, scratch);
164 |
165 | forceUpdate();
166 |
167 | expect(ForceUpdateComponent.prototype.forceUpdate).to.have.been.called;
168 | expect(ForceUpdateComponent.prototype.forceUpdate).to.have.been.calledWith(callback);
169 | expect(callback).to.have.been.called;
170 | });
171 | });
172 | });
173 |
--------------------------------------------------------------------------------
/test/browser/svg.js:
--------------------------------------------------------------------------------
1 | import { h, render } from 'zreact';
2 | /** @jsx h */
3 |
4 |
5 | // hacky normalization of attribute order across browsers.
6 | function sortAttributes(html) {
7 | return html.replace(/<([a-z0-9-]+)((?:\s[a-z0-9:_.-]+=".*?")+)((?:\s*\/)?>)/gi, (s, pre, attrs, after) => {
8 | let list = attrs.match(/\s[a-z0-9:_.-]+=".*?"/gi).sort( (a, b) => a>b ? 1 : -1 );
9 | if (~after.indexOf('/')) after = '>'+pre+'>';
10 | return '<' + pre + list.join('') + after;
11 | });
12 | }
13 |
14 |
15 | describe('svg', () => {
16 | let scratch;
17 |
18 | before( () => {
19 | scratch = document.createElement('div');
20 | (document.body || document.documentElement).appendChild(scratch);
21 | });
22 |
23 | beforeEach( () => {
24 | scratch.innerHTML = '';
25 | });
26 |
27 | after( () => {
28 | scratch.parentNode.removeChild(scratch);
29 | scratch = null;
30 | });
31 |
32 | it('should render SVG to string', () => {
33 | render((
34 |
37 | ), scratch);
38 |
39 | let html = sortAttributes(String(scratch.innerHTML).replace(' xmlns="http://www.w3.org/2000/svg"', ''));
40 | expect(html).to.equal(sortAttributes(`
41 |
44 | `.replace(/[\n\t]+/g,'')));
45 | });
46 |
47 | it('should render SVG to DOM', () => {
48 | const Demo = () => (
49 |
52 | );
53 | render(
, scratch);
54 |
55 | let html = sortAttributes(String(scratch.innerHTML).replace(' xmlns="http://www.w3.org/2000/svg"', ''));
56 | expect(html).to.equal(sortAttributes('
'));
57 | });
58 |
59 | it('should render with the correct namespace URI', () => {
60 | render(
, scratch);
61 |
62 | let namespace = scratch.querySelector('svg').namespaceURI;
63 |
64 | expect(namespace).to.equal("http://www.w3.org/2000/svg");
65 | });
66 |
67 | it('should use attributes for className', () => {
68 | const Demo = ({ c }) => (
69 |
72 | );
73 | let vdom = render(
, scratch, vdom);
74 | sinon.spy(vdom, 'removeAttribute');
75 | vdom = render(
, scratch, vdom);
76 | expect(vdom.removeAttribute).to.have.been.calledOnce.and.calledWith('class');
77 | vdom.removeAttribute.restore();
78 |
79 | vdom = render(
, scratch, vdom);
80 | vdom = render(
, scratch, vdom);
81 | sinon.spy(vdom, 'setAttribute');
82 | vdom = render(
, scratch, vdom);
83 | expect(vdom.setAttribute).to.have.been.calledOnce.and.calledWith('class', 'foo_2');
84 | vdom.setAttribute.restore();
85 | vdom = render(
, scratch, vdom);
86 | vdom = render(
, scratch, vdom);
87 | });
88 |
89 | it('should still support class attribute', () => {
90 | render((
91 |
92 | ), scratch);
93 |
94 | expect(scratch.innerHTML).to.contain(` class="foo bar"`);
95 | });
96 |
97 | it('should switch back to HTML for
', () => {
98 | render((
99 |
106 | ), scratch);
107 |
108 | expect(scratch.getElementsByTagName('a'))
109 | .to.have.property('0')
110 | .that.is.a('HTMLAnchorElement');
111 | });
112 | });
113 |
--------------------------------------------------------------------------------
/test/compat/component.js:
--------------------------------------------------------------------------------
1 | import { renderToString } from '../../server-render';
2 | import { rerender } from 'zreact';
3 | import React from 'zreact-compat';
4 | import { Children } from '../../build/zreact';
5 |
6 | const h = React.createElement;
7 |
8 | describe('components', () => {
9 | let scratch;
10 |
11 | before( () => {
12 | scratch = document.createElement('div');
13 | (document.body || document.documentElement).appendChild(scratch);
14 | });
15 |
16 | beforeEach( () => {
17 | scratch.innerHTML = '';
18 | });
19 |
20 | after( () => {
21 | scratch.parentNode.removeChild(scratch);
22 | scratch = null;
23 | });
24 |
25 | it('should be sane', () => {
26 | let props;
27 |
28 | class Demo extends React.Component {
29 | render() {
30 | let { a, b } = this.props;
31 | props = this.props;
32 | return { this.props.children }
;
33 | }
34 | }
35 |
36 | let html = renderToString(
37 | inner
38 | );
39 |
40 | // expect(props).to.exist.and.deep.equal({
41 | // a: 'b',
42 | // c: 'd',
43 | // children: 'inner'
44 | // });
45 |
46 | expect(html).to.equal('inner
');
47 | });
48 |
49 | it('should support replaceState()', done => {
50 | class Demo extends React.Component {
51 | render() {
52 | return ;
53 | }
54 | }
55 |
56 | let render = sinon.spy(Demo.prototype, 'render'),
57 | inst;
58 |
59 | React.render( inst=c } />, scratch);
60 |
61 | inst.setState({ foo:'bar', baz:'bat' });
62 | setTimeout( () => {
63 | expect(inst.state).to.eql({ foo:'bar', baz:'bat' });
64 |
65 | let callbackState;
66 | let callback = sinon.spy( () => {
67 | callbackState = inst.state;
68 | });
69 | inst.replaceState({}, callback);
70 |
71 | setTimeout( () => {
72 | expect(callback).to.have.been.calledOnce;
73 | expect(callbackState).to.eql({});
74 | expect(inst.state).to.eql({});
75 |
76 | done();
77 | }, 10);
78 | }, 10);
79 | });
80 |
81 | it('should alias props.children', () => {
82 | class Foo extends React.Component {
83 | render() {
84 | return {this.props.children}
;
85 | }
86 | }
87 |
88 | let children = ['a', b, c],
89 | foo;
90 |
91 | React.render((
92 | foo=c }>
93 | { children }
94 |
95 | ), scratch);
96 |
97 | expect(foo).to.exist;
98 | expect(foo.props).have.property('children').eql(children);
99 |
100 | });
101 |
102 | it('should single out children before componentWillReceiveProps', () => {
103 | let props;
104 |
105 | class Child extends React.Component {
106 | componentWillReceiveProps(newProps) {
107 | props = newProps;
108 | }
109 | }
110 | class Parent extends React.Component {
111 | render() {
112 | return second;
113 | }
114 | }
115 |
116 | let a
117 | React.render( a = com}/>, scratch);
118 | a.forceUpdate();
119 |
120 | expect(props).to.exist.and.deep.equal({
121 | children: 'second'
122 | });
123 | });
124 |
125 | it('should support array[object] children', () => {
126 | let children;
127 |
128 | class Foo extends React.Component {
129 | render() {
130 | children = this.props.children;
131 | return ;
132 | }
133 | }
134 |
135 | const data = [{a: ''}];
136 | React.render({ data }, scratch);
137 |
138 | expect(Children.toArray(children)).to.exist.and.deep.equal(data);
139 | });
140 |
141 | describe('getInitialState', () => {
142 | it('should be invoked for new components', () => {
143 | class Foo extends React.Component {
144 | getInitialState() {
145 | return { foo: 'bar' };
146 | }
147 | render() {
148 | return ;
149 | }
150 | }
151 |
152 | sinon.spy(Foo.prototype, 'getInitialState');
153 |
154 | let a = React.render(, scratch);
155 |
156 | expect(Foo.prototype.getInitialState).to.have.been.calledOnce;
157 | expect(a.state).to.eql({ foo: 'bar' });
158 | });
159 | });
160 |
161 | describe('defaultProps', () => {
162 | it('should support defaultProps for components', () => {
163 | let render = sinon.stub().returns();
164 |
165 | const Foo = React.createClass({
166 | defaultProps: {
167 | foo: 'default foo',
168 | bar: 'default bar'
169 | },
170 | render
171 | });
172 |
173 | React.render(, scratch);
174 | expect(render).to.have.been.calledWithMatch(Foo.defaultProps);
175 |
176 | render.resetHistory();
177 | React.render(, scratch);
178 | expect(render).to.have.been.calledWithMatch({ foo:'default foo', bar:'bar' });
179 | });
180 |
181 | it('should support defaultProps for pure components', () => {
182 | const Foo = sinon.stub().returns();
183 | Foo.defaultProps = {
184 | foo: 'default foo',
185 | bar: 'default bar'
186 | };
187 |
188 | React.render(, scratch);
189 | expect(Foo).to.have.been.calledWithMatch(Foo.defaultProps);
190 |
191 | Foo.resetHistory();
192 | React.render(, scratch);
193 | expect(Foo).to.have.been.calledWithMatch({ foo:'default foo', bar:'bar' });
194 | });
195 | });
196 |
197 | describe('propTypes', () => {
198 | function checkPropTypes(Foo, name = 'Foo') {
199 | sinon.stub(console, 'error');
200 | React.render(, scratch);
201 | expect(console.error).to.have.been.calledWithMatch(
202 | 'Warning: Failed prop type: The prop `func` is marked as required in `' + name + '`, but its value is `undefined`.'
203 | );
204 | expect(console.error).to.have.been.called;
205 |
206 | console.error.resetHistory();
207 |
208 | React.render({}} />, scratch);
209 | expect(console.error).not.to.have.been.called;
210 |
211 | React.render({}} bool="one" />, scratch);
212 | expect(console.error).to.have.been.calledWithMatch(
213 | 'Warning: Failed prop type: Invalid prop `bool` of type `string` supplied to `' + name + '`, expected `boolean`.'
214 | );
215 |
216 | console.error.restore();
217 | }
218 |
219 | it('should support propTypes for ES Class components', () => {
220 | class Foo extends React.Component {
221 | static propTypes = {
222 | func: React.PropTypes.func.isRequired,
223 | bool: React.PropTypes.bool
224 | };
225 | render() {
226 | return ;
227 | }
228 | }
229 |
230 | checkPropTypes(Foo);
231 | });
232 |
233 | it('should support propTypes for createClass components', () => {
234 | const Bar = React.createClass({
235 | propTypes: {
236 | func: React.PropTypes.func.isRequired,
237 | bool: React.PropTypes.bool
238 | },
239 | render: () =>
240 | });
241 |
242 | checkPropTypes(Bar, 'Bar');
243 | });
244 |
245 | it('should support propTypes for pure components', () => {
246 | function Baz() { return ; }
247 | Baz.propTypes = {
248 | func: React.PropTypes.func.isRequired,
249 | bool: React.PropTypes.bool
250 | };
251 | checkPropTypes(Baz, 'Baz');
252 |
253 | const Bip = () => ;
254 | Bip.displayName = 'Bip';
255 | Bip.propTypes = {
256 | func: React.PropTypes.func.isRequired,
257 | bool: React.PropTypes.bool
258 | };
259 | checkPropTypes(Bip, 'Bip');
260 | });
261 | });
262 |
263 | describe("mixins", () => {
264 | describe("getDefaultProps", () => {
265 | it('should use a mixin', () => {
266 | const Foo = React.createClass({
267 | mixins: [
268 | { getDefaultProps: () => ({ a: true }) }
269 | ],
270 | render() {
271 | return ;
272 | }
273 | });
274 |
275 | expect(Foo.defaultProps).to.eql({
276 | a: true
277 | });
278 | });
279 |
280 | it('should combine the results', () => {
281 | const Foo = React.createClass({
282 | mixins: [
283 | { getDefaultProps: () => ({ a: true }) },
284 | { getDefaultProps: () => ({ b: true }) }
285 | ],
286 | getDefaultProps() {
287 | return { c: true };
288 | },
289 | render() {
290 | return ;
291 | }
292 | });
293 |
294 | expect(Foo.defaultProps).to.eql({
295 | a: true,
296 | b: true,
297 | c: true
298 | });
299 | });
300 |
301 | // Disabled to save bytes
302 | xit('should throw an error for duplicate keys', () => {
303 | expect(() => {
304 | const Foo = React.createClass({
305 | mixins: [
306 | { getDefaultProps: () => ({ a: true }) }
307 | ],
308 | getDefaultProps() {
309 | return { a: true };
310 | },
311 | render() {
312 | return ;
313 | }
314 | });
315 | }).to.throw();
316 | });
317 | });
318 |
319 | describe("getInitialState", () => {
320 | it('should combine the results', () => {
321 | const Foo = React.createClass({
322 | mixins: [
323 | { getInitialState: () => ({ a: true }) },
324 | { getInitialState: () => ({ b: true }) }
325 | ],
326 | getInitialState() {
327 | return { c: true };
328 | },
329 | render() {
330 | return ;
331 | }
332 | });
333 |
334 | let a = React.render(, scratch);
335 |
336 | expect(a.state).to.eql({
337 | a: true,
338 | b: true,
339 | c: true
340 | });
341 | });
342 |
343 | // Disabled to save bytes
344 | xit('should throw an error for duplicate keys', () => {
345 | const Foo = React.createClass({
346 | mixins: [
347 | { getInitialState: () => ({ a: true }) }
348 | ],
349 | getInitialState() {
350 | return { a: true };
351 | },
352 | render() {
353 | return ;
354 | }
355 | });
356 |
357 | expect(() => {
358 | React.render(, scratch);
359 | }).to.throw();
360 | });
361 | });
362 | });
363 |
364 | describe('refs', () => {
365 | it('should support string refs', () => {
366 | let inst, innerInst;
367 |
368 | class Foo extends React.Component {
369 | constructor() {
370 | super();
371 | inst = this;
372 | }
373 |
374 | render() {
375 | return (
376 |
377 |
h1
378 |
379 | text
380 |
381 |
382 |
383 | );
384 | }
385 | }
386 |
387 | const Inner = React.createClass({
388 | render() {
389 | innerInst = this;
390 | return (
391 |
392 |
h2
393 |
394 | );
395 | }
396 | });
397 |
398 | React.render(, scratch);
399 | expect(inst).to.exist;
400 | expect(inst.refs).to.exist;
401 |
402 | expect(inst.refs).to.have.property('top', scratch.firstChild._vdom);
403 | expect(inst.refs).to.have.property('h1', scratch.querySelector('h1')._vdom);
404 | expect(inst.refs).to.have.property('p', scratch.querySelector('p')._vdom);
405 | expect(inst.refs).to.have.property('span', scratch.querySelector('span')._vdom);
406 |
407 | expect(inst.refs).to.have.property('inner', innerInst, 'ref to child component');
408 |
409 | expect(inst.refs).not.to.have.property('contained');
410 | expect(inst.refs).not.to.have.property('inner-h2');
411 |
412 | expect(innerInst.refs).to.have.all.keys(['contained', 'inner-h2']);
413 | expect(innerInst.refs).to.have.property('contained', scratch.querySelector('.contained')._vdom);
414 | expect(innerInst.refs).to.have.property('inner-h2', scratch.querySelector('h2')._vdom);
415 | });
416 |
417 | it('should retain support for function refs', () => {
418 | let ref1 = sinon.spy(),
419 | ref2 = sinon.spy(),
420 | componentRef = sinon.spy(),
421 | innerInst;
422 |
423 | class Foo extends React.Component {
424 | render() {
425 | return this.props.empty ? () : (
426 |
427 |
h1
428 |
429 |
430 | );
431 | }
432 | }
433 |
434 | const Inner = React.createClass({
435 | render() {
436 | innerInst = this;
437 | return ;
438 | }
439 | });
440 |
441 | React.render(, scratch);
442 |
443 | expect(ref1).to.have.have.been.calledOnce.and.calledWith(scratch.firstChild._vdom);
444 | expect(ref2).to.have.have.been.calledOnce.and.calledWith(scratch.querySelector('h1')._vdom);
445 | expect(componentRef).to.have.been.calledOnce.and.calledWith(innerInst);
446 |
447 | React.render(, scratch);
448 | // React.unmountComponentAtNode(scratch);
449 |
450 | expect(ref1, 'ref1').to.have.have.been.calledTwice.and.calledWith(null);
451 | expect(ref2, 'ref2').to.have.have.been.calledTwice.and.calledWith(null);
452 | expect(componentRef, 'componentRef').to.have.been.calledTwice.and.calledWith(null);
453 | });
454 |
455 | it('should support string refs via cloneElement()', () => {
456 | let inner, outer;
457 |
458 | const Inner = React.createClass({
459 | render() {
460 | inner = this;
461 | return (
462 |
463 | {React.cloneElement(React.Children.only(this.props.children), { id:'one' })}
464 | {React.cloneElement(React.Children.only(this.props.children), { id:'two', ref:'two' })}
465 |
466 | );
467 | }
468 | });
469 |
470 | const Outer = React.createClass({
471 | render() {
472 | outer = this;
473 | return (
474 |
475 | foo
476 |
477 | );
478 | }
479 | });
480 |
481 | React.render(, scratch);
482 |
483 | let one = scratch.firstElementChild.children[0];
484 | let two = scratch.firstElementChild.children[1];
485 | expect(outer).to.have.property('refs').eql({ one: one._vdom });
486 | expect(inner).to.have.property('refs').eql({ two: two._vdom });
487 | });
488 | });
489 |
490 | describe('PureComponent', () => {
491 | it('should be a class', () => {
492 | expect(React).to.have.property('PureComponent').that.is.a('function');
493 | });
494 |
495 | it('should only re-render when props or state change', () => {
496 | class C extends React.PureComponent {
497 | render() {
498 | return ;
499 | }
500 | }
501 | let spy = sinon.spy(C.prototype, 'render');
502 |
503 | let inst = React.render(, scratch);
504 | expect(spy).to.have.been.calledOnce;
505 | spy.resetHistory();
506 |
507 | inst = React.render(, scratch);
508 | expect(spy).not.to.have.been.called;
509 |
510 | let b = { foo: 'bar' };
511 | inst = React.render(, scratch);
512 | expect(spy).to.have.been.calledOnce;
513 | spy.resetHistory();
514 |
515 | inst = React.render(, scratch);
516 | expect(spy).not.to.have.been.called;
517 |
518 | inst.setState({ });
519 | rerender();
520 | expect(spy).not.to.have.been.called;
521 |
522 | inst.setState({ a:'a', b });
523 | rerender();
524 | expect(spy).to.have.been.calledOnce;
525 | spy.resetHistory();
526 |
527 | inst.setState({ a:'a', b });
528 | rerender();
529 | expect(spy).not.to.have.been.called;
530 | });
531 | });
532 | });
533 |
--------------------------------------------------------------------------------
/test/compat/index.js:
--------------------------------------------------------------------------------
1 | import React from 'zreact-compat';
2 | import children from '../../build/children';
3 | const {
4 | render,
5 | createClass,
6 | createElement,
7 | cloneElement,
8 | Component,
9 | PropTypes,
10 | unstable_renderSubtreeIntoContainer,
11 | __spread,
12 | REACT_ELEMENT_TYPE,
13 | Children
14 | } = React;
15 | const h = React.createElement;
16 |
17 | describe('preact-compat', () => {
18 | describe('render()', () => {
19 | it('should be exported', () => {
20 | expect(React)
21 | .to.have.property('render')
22 | .that.is.a('function')
23 | .that.equals(render);
24 | });
25 |
26 | it('should replace isomorphic content', () => {
27 | let ce = (type) => document.createElement(type);
28 | let Text = (text) => document.createTextNode(text);
29 | let root = ce('div');
30 | let initialChild = ce('div');
31 | initialChild.appendChild(Text('initial content'));
32 | root.appendChild(initialChild);
33 |
34 | render(dynamic content
, root);
35 | expect(root)
36 | .to.have.property('textContent')
37 | .that.is.a('string')
38 | .that.equals('dynamic content');
39 | });
40 |
41 | it('should remove extra elements', () => {
42 | let ce = (type) => document.createElement(type);
43 | let Text = (text) => document.createTextNode(text);
44 | let root = ce('div');
45 |
46 | let c1 = ce('div');
47 | c1.appendChild(Text('isomorphic content'));
48 | root.appendChild(c1);
49 |
50 | let c2 = ce('div');
51 | c2.appendChild(Text('extra content'));
52 | root.appendChild(c2);
53 |
54 | render(dynamic content
, root);
55 | expect(root)
56 | .to.have.property('textContent')
57 | .that.is.a('string')
58 | .that.equals('dynamic content');
59 | });
60 |
61 | it('should remove text nodes', () => {
62 | let ce = (type) => document.createElement(type);
63 | let Text = (text) => document.createTextNode(text);
64 | let root = ce('div');
65 |
66 | root.appendChild(Text('Text Content in the root'));
67 | root.appendChild(Text('More Text Content'));
68 |
69 | render(dynamic content
, root);
70 | expect(root)
71 | .to.have.property('textContent')
72 | .that.is.a('string')
73 | .that.equals('dynamic content');
74 | });
75 |
76 | it('should support defaultValue', () => {
77 | let scratch = document.createElement('div');
78 | (document.body || document.documentElement).appendChild(scratch);
79 | render(, scratch);
80 | expect(scratch.firstElementChild).to.have.property('value', 'foo');
81 | });
82 |
83 | });
84 |
85 |
86 | describe('createClass()', () => {
87 | it('should be exported', () => {
88 | expect(React)
89 | .to.have.property('createClass')
90 | .that.is.a('function')
91 | .that.equals(createClass);
92 | });
93 |
94 | it('should create a Component', () => {
95 | let specState = { something: 1 };
96 | let spec = {
97 | foo: 'bar',
98 | getInitialState() {
99 | return specState;
100 | },
101 | method: sinon.spy()
102 | };
103 | const C = createClass(spec);
104 | let inst = new C();
105 | expect(inst).to.have.property('foo', 'bar');
106 | expect(inst).to.have.property('state', specState);
107 | expect(inst).to.have.property('method').that.is.a('function');
108 | expect(inst).to.be.an.instanceof(Component);
109 | inst.method('a','b');
110 | expect(spec.method)
111 | .to.have.been.calledOnce
112 | .and.calledOn(inst)
113 | .and.calledWithExactly('a', 'b');
114 | });
115 |
116 | it('should not bind blacklisted methods', () => {
117 | let constructor = () => {};
118 | let render = () => null;
119 | const C = createClass({
120 | constructor,
121 | render
122 | });
123 | let c = new C();
124 | expect(c).to.have.property('constructor').that.equals(constructor);
125 | expect(c).to.have.property('render').not.with.property('__bound');
126 | });
127 |
128 | it('should copy statics', () => {
129 | let def = {
130 | statics: {
131 | foo: 'bar',
132 | baz() {}
133 | }
134 | };
135 | let c = createClass(def);
136 | expect(c).to.have.property('foo', def.statics.foo);
137 | expect(c).to.have.property('baz', def.statics.baz);
138 | });
139 |
140 | it('should support mixins', () => {
141 | let def = {
142 | mixins: [
143 | {
144 | foo: sinon.spy(),
145 | bar: sinon.spy()
146 | },
147 | {
148 | bar: sinon.spy(),
149 | componentWillMount: sinon.spy(),
150 | render: 'nothing here'
151 | },
152 | {
153 | componentWillMount: sinon.spy()
154 | }
155 | ],
156 | foo: sinon.spy(),
157 | componentWillMount: sinon.spy(),
158 | render: sinon.stub().returns(null)
159 | };
160 | let C = createClass(def);
161 | let inst = new C();
162 |
163 | inst.foo();
164 | expect(def.foo).to.have.been.calledOnce;
165 | expect(def.mixins[0].foo).to.have.been.calledOnce.and.calledBefore(def.foo);
166 |
167 | inst.bar();
168 | expect(def.mixins[0].bar).to.have.been.calledOnce;
169 | expect(def.mixins[1].bar).to.have.been.calledOnce.and.calledAfter(def.mixins[0].bar);
170 |
171 | let props = {},
172 | state = {};
173 | inst.componentWillMount(props, state);
174 | expect(def.mixins[1].componentWillMount)
175 | .to.have.been.calledOnce
176 | .and.calledWithExactly(props, state);
177 | expect(def.mixins[2].componentWillMount)
178 | .to.have.been.calledOnce
179 | .and.calledWithExactly(props, state)
180 | .and.calledAfter(def.mixins[1].componentWillMount);
181 |
182 | expect(inst.render(props, state)).to.equal(null);
183 | });
184 | });
185 |
186 | describe('createElement()', () => {
187 | it('should be exported', () => {
188 | expect(React)
189 | .to.have.property('createElement')
190 | .that.is.a('function')
191 | .that.equals(createElement);
192 | });
193 |
194 | it('should normalize vnodes', () => {
195 | let vnode = ;
196 | // using typeof Symbol here injects a polyfill, which ruins the test. we'll hardcode the non-symbol value for now.
197 | let $$typeof = REACT_ELEMENT_TYPE;
198 | expect(vnode).to.have.property('$$typeof', $$typeof);
199 | expect(vnode).to.have.property('type', 'div');
200 | expect(vnode).to.have.property('props').that.is.an('object');
201 | expect(vnode.props).to.have.property('children');
202 | let _children = Children.toArray(vnode.props.children)
203 | expect(_children[0]).to.have.property('$$typeof', $$typeof);
204 | expect(_children[0]).to.have.property('type', 'a');
205 | expect(_children[0]).to.have.property('props').that.is.an('object');
206 | expect(_children[0].props).to.eql({ children: 't' });
207 | });
208 |
209 | it('should normalize onChange', () => {
210 | let props = { onChange(){} };
211 |
212 | function expectToBeNormalized(vnode, desc) {
213 | console.log('-------------------------------', vnode.props);
214 | expect(vnode, desc).to.have.property('props').with.property('oninput').that.is.a('function');
215 | }
216 |
217 | function expectToBeUnmodified(vnode, desc) {
218 | expect(vnode, desc).to.have.property('props').eql({ ...props, ...(vnode.props.type ? { type:vnode.props.type } : {}) });
219 | }
220 |
221 | expectToBeUnmodified(, '