Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
81155 views
1
/**
2
* Copyright 2013-2014, Facebook, Inc.
3
* All rights reserved.
4
*
5
* This source code is licensed under the BSD-style license found in the
6
* LICENSE file in the root directory of this source tree. An additional grant
7
* of patent rights can be found in the PATENTS file in the same directory.
8
*
9
* @emails react-core
10
*/
11
12
"use strict";
13
14
var ChildUpdates;
15
var MorphingComponent;
16
var React;
17
var ReactComponent;
18
var ReactCurrentOwner;
19
var ReactDoNotBindDeprecated;
20
var ReactMount;
21
var ReactPropTypes;
22
var ReactServerRendering;
23
var ReactTestUtils;
24
var TogglingComponent;
25
26
var cx;
27
var reactComponentExpect;
28
var mocks;
29
var warn;
30
31
32
describe('ReactCompositeComponent', function() {
33
34
beforeEach(function() {
35
cx = require('cx');
36
mocks = require('mocks');
37
38
reactComponentExpect = require('reactComponentExpect');
39
React = require('React');
40
ReactComponent = require('ReactComponent');
41
ReactCurrentOwner = require('ReactCurrentOwner');
42
ReactDoNotBindDeprecated = require('ReactDoNotBindDeprecated');
43
ReactPropTypes = require('ReactPropTypes');
44
ReactTestUtils = require('ReactTestUtils');
45
ReactMount = require('ReactMount');
46
ReactServerRendering = require('ReactServerRendering');
47
48
MorphingComponent = React.createClass({
49
getInitialState: function() {
50
return {activated: false};
51
},
52
53
_toggleActivatedState: function() {
54
this.setState({activated: !this.state.activated});
55
},
56
57
render: function() {
58
var toggleActivatedState = this._toggleActivatedState;
59
return !this.state.activated ?
60
<a ref="x" onClick={toggleActivatedState} /> :
61
<b ref="x" onClick={toggleActivatedState} />;
62
}
63
});
64
65
/**
66
* We'll use this to ensure that an old version is not cached when it is
67
* reallocated again.
68
*/
69
ChildUpdates = React.createClass({
70
getAnchorID: function() {
71
return this.refs.anch._rootNodeID;
72
},
73
render: function() {
74
var className = cx({'anchorClass': this.props.anchorClassOn});
75
return this.props.renderAnchor ?
76
<a ref="anch" className={className}></a> :
77
<b></b>;
78
}
79
});
80
81
TogglingComponent = React.createClass({
82
getInitialState: function() {
83
return {component: this.props.firstComponent};
84
},
85
componentDidMount: function() {
86
console.log(this.getDOMNode());
87
this.setState({component: this.props.secondComponent});
88
},
89
componentDidUpdate: function() {
90
console.log(this.getDOMNode());
91
},
92
render: function() {
93
var Component = this.state.component;
94
return Component ? <Component /> : null;
95
}
96
});
97
98
warn = console.warn;
99
console.warn = mocks.getMockFunction();
100
});
101
102
afterEach(function() {
103
console.warn = warn;
104
});
105
106
it('should give context for PropType errors in nested components.', () => {
107
// In this test, we're making sure that if a proptype error is found in a
108
// component, we give a small hint as to which parent instantiated that
109
// component as per warnings about key usage in ReactElementValidator.
110
spyOn(console, 'warn');
111
var MyComp = React.createClass({
112
propTypes: {
113
color: ReactPropTypes.string
114
},
115
render: function() {
116
return <div>My color is {this.color}</div>;
117
}
118
});
119
var ParentComp = React.createClass({
120
render: function() {
121
return <MyComp color={123} />;
122
}
123
});
124
ReactTestUtils.renderIntoDocument(<ParentComp />);
125
expect(console.warn.calls[0].args[0]).toBe(
126
'Warning: Invalid prop `color` of type `number` supplied to `MyComp`, ' +
127
'expected `string`. Check the render method of `ParentComp`.'
128
);
129
});
130
131
it('should support rendering to different child types over time', function() {
132
var instance = <MorphingComponent />;
133
instance = ReactTestUtils.renderIntoDocument(instance);
134
135
reactComponentExpect(instance)
136
.expectRenderedChild()
137
.toBeDOMComponentWithTag('a');
138
139
instance._toggleActivatedState();
140
reactComponentExpect(instance)
141
.expectRenderedChild()
142
.toBeDOMComponentWithTag('b');
143
144
instance._toggleActivatedState();
145
reactComponentExpect(instance)
146
.expectRenderedChild()
147
.toBeDOMComponentWithTag('a');
148
});
149
150
it('should render null and false as a noscript tag under the hood', () => {
151
var Component1 = React.createClass({
152
render: function() {
153
return null;
154
}
155
});
156
var Component2 = React.createClass({
157
render: function() {
158
return false;
159
}
160
});
161
162
var instance1 = ReactTestUtils.renderIntoDocument(<Component1 />);
163
var instance2 = ReactTestUtils.renderIntoDocument(<Component2 />);
164
reactComponentExpect(instance1)
165
.expectRenderedChild()
166
.toBeDOMComponentWithTag('noscript');
167
reactComponentExpect(instance2)
168
.expectRenderedChild()
169
.toBeDOMComponentWithTag('noscript');
170
});
171
172
it('should still throw when rendering to undefined', () => {
173
var Component = React.createClass({
174
render: function() {}
175
});
176
expect(function() {
177
ReactTestUtils.renderIntoDocument(<Component />);
178
}).toThrow(
179
'Invariant Violation: Component.render(): A valid ReactComponent must ' +
180
'be returned. You may have returned undefined, an array or some other ' +
181
'invalid object.'
182
);
183
});
184
185
it('should be able to switch between rendering null and a normal tag', () => {
186
spyOn(console, 'log');
187
188
var instance1 =
189
<TogglingComponent
190
firstComponent={null}
191
secondComponent={'div'}
192
/>;
193
var instance2 =
194
<TogglingComponent
195
firstComponent={'div'}
196
secondComponent={null}
197
/>;
198
199
expect(function() {
200
ReactTestUtils.renderIntoDocument(instance1);
201
ReactTestUtils.renderIntoDocument(instance2);
202
}).not.toThrow();
203
204
expect(console.log.argsForCall.length).toBe(4);
205
expect(console.log.argsForCall[0][0]).toBe(null);
206
expect(console.log.argsForCall[1][0].tagName).toBe('DIV');
207
expect(console.log.argsForCall[2][0].tagName).toBe('DIV');
208
expect(console.log.argsForCall[3][0]).toBe(null);
209
});
210
211
it('should distinguish between a script placeholder and an actual script tag',
212
() => {
213
spyOn(console, 'log');
214
215
var instance1 =
216
<TogglingComponent
217
firstComponent={null}
218
secondComponent={'script'}
219
/>;
220
var instance2 =
221
<TogglingComponent
222
firstComponent={'script'}
223
secondComponent={null}
224
/>;
225
226
expect(function() {
227
ReactTestUtils.renderIntoDocument(instance1);
228
}).not.toThrow();
229
expect(function() {
230
ReactTestUtils.renderIntoDocument(instance2);
231
}).not.toThrow();
232
233
expect(console.log.argsForCall.length).toBe(4);
234
expect(console.log.argsForCall[0][0]).toBe(null);
235
expect(console.log.argsForCall[1][0].tagName).toBe('SCRIPT');
236
expect(console.log.argsForCall[2][0].tagName).toBe('SCRIPT');
237
expect(console.log.argsForCall[3][0]).toBe(null);
238
}
239
);
240
241
it('should have getDOMNode return null when multiple layers of composite ' +
242
'components render to the same null placeholder', () => {
243
spyOn(console, 'log');
244
245
var GrandChild = React.createClass({
246
render: function() {
247
return null;
248
}
249
});
250
251
var Child = React.createClass({
252
render: function() {
253
return <GrandChild />;
254
}
255
});
256
257
var instance1 =
258
<TogglingComponent
259
firstComponent={'div'}
260
secondComponent={Child}
261
/>;
262
var instance2 =
263
<TogglingComponent
264
firstComponent={Child}
265
secondComponent={'div'}
266
/>;
267
268
expect(function() {
269
ReactTestUtils.renderIntoDocument(instance1);
270
}).not.toThrow();
271
expect(function() {
272
ReactTestUtils.renderIntoDocument(instance2);
273
}).not.toThrow();
274
275
expect(console.log.argsForCall.length).toBe(4);
276
expect(console.log.argsForCall[0][0].tagName).toBe('DIV');
277
expect(console.log.argsForCall[1][0]).toBe(null);
278
expect(console.log.argsForCall[2][0]).toBe(null);
279
expect(console.log.argsForCall[3][0].tagName).toBe('DIV');
280
}
281
);
282
283
it('should not thrash a server rendered layout with client side one', () => {
284
var Child = React.createClass({
285
render: function() {
286
return null;
287
}
288
});
289
var Parent = React.createClass({
290
render: function() {
291
return <div><Child /></div>;
292
}
293
});
294
295
var markup = ReactServerRendering.renderToString(<Parent />);
296
var container = document.createElement('div');
297
container.innerHTML = markup;
298
299
spyOn(console, 'warn');
300
React.render(<Parent />, container);
301
expect(console.warn).not.toHaveBeenCalled();
302
});
303
304
it('should react to state changes from callbacks', function() {
305
var instance = <MorphingComponent />;
306
instance = ReactTestUtils.renderIntoDocument(instance);
307
308
var renderedChild = reactComponentExpect(instance)
309
.expectRenderedChild()
310
.instance();
311
312
ReactTestUtils.Simulate.click(renderedChild);
313
reactComponentExpect(instance)
314
.expectRenderedChild()
315
.toBeDOMComponentWithTag('b');
316
});
317
318
it('should rewire refs when rendering to different child types', function() {
319
var instance = <MorphingComponent />;
320
instance = ReactTestUtils.renderIntoDocument(instance);
321
322
reactComponentExpect(instance.refs.x).toBeDOMComponentWithTag('a');
323
instance._toggleActivatedState();
324
reactComponentExpect(instance.refs.x).toBeDOMComponentWithTag('b');
325
instance._toggleActivatedState();
326
reactComponentExpect(instance.refs.x).toBeDOMComponentWithTag('a');
327
});
328
329
it('should not cache old DOM nodes when switching constructors', function() {
330
var instance = <ChildUpdates renderAnchor={true} anchorClassOn={false}/>;
331
instance = ReactTestUtils.renderIntoDocument(instance);
332
instance.setProps({anchorClassOn: true}); // Warm any cache
333
instance.setProps({renderAnchor: false}); // Clear out the anchor
334
// rerender
335
instance.setProps({renderAnchor: true, anchorClassOn: false});
336
var anchorID = instance.getAnchorID();
337
var actualDOMAnchorNode = ReactMount.getNode(anchorID);
338
expect(actualDOMAnchorNode.className).toBe('');
339
});
340
341
it('should auto bind methods and values correctly', function() {
342
spyOn(console, 'warn');
343
344
var ComponentClass = React.createClass({
345
getInitialState: function() {
346
return {valueToReturn: 'hi'};
347
},
348
methodToBeExplicitlyBound: function() {
349
return this;
350
},
351
methodAutoBound: function() {
352
return this;
353
},
354
methodExplicitlyNotBound: ReactDoNotBindDeprecated.doNotBind(function() {
355
return this;
356
}),
357
render: function() {
358
return <div></div>;
359
}
360
});
361
var instance = <ComponentClass />;
362
363
// Next, prove that once mounted, the scope is bound correctly to the actual
364
// component.
365
var mountedInstance = ReactTestUtils.renderIntoDocument(instance);
366
367
expect(function() {
368
mountedInstance.methodToBeExplicitlyBound.bind(instance)();
369
}).not.toThrow();
370
expect(function() {
371
mountedInstance.methodAutoBound();
372
}).not.toThrow();
373
expect(function() {
374
mountedInstance.methodExplicitlyNotBound();
375
}).not.toThrow();
376
377
expect(console.warn.argsForCall.length).toBe(1);
378
var explicitlyBound = mountedInstance.methodToBeExplicitlyBound.bind(
379
mountedInstance
380
);
381
expect(console.warn.argsForCall.length).toBe(2);
382
var autoBound = mountedInstance.methodAutoBound;
383
var explicitlyNotBound = mountedInstance.methodExplicitlyNotBound;
384
385
var context = {};
386
expect(explicitlyBound.call(context)).toBe(mountedInstance);
387
expect(autoBound.call(context)).toBe(mountedInstance);
388
expect(explicitlyNotBound.call(context)).toBe(context);
389
390
expect(explicitlyBound.call(mountedInstance)).toBe(mountedInstance);
391
expect(autoBound.call(mountedInstance)).toBe(mountedInstance);
392
// This one is the weird one
393
expect(explicitlyNotBound.call(mountedInstance)).toBe(mountedInstance);
394
395
});
396
397
it('should not pass this to getDefaultProps', function() {
398
var Component = React.createClass({
399
getDefaultProps: function() {
400
expect(this.render).not.toBeDefined();
401
return {};
402
},
403
render: function() {
404
return <div />;
405
}
406
});
407
ReactTestUtils.renderIntoDocument(<Component />);
408
});
409
410
it('should use default values for undefined props', function() {
411
var Component = React.createClass({
412
getDefaultProps: function() {
413
return {prop: 'testKey'};
414
},
415
render: function() {
416
return <span />;
417
}
418
});
419
420
var instance1 = <Component />;
421
instance1 = ReactTestUtils.renderIntoDocument(instance1);
422
reactComponentExpect(instance1).scalarPropsEqual({prop: 'testKey'});
423
424
var instance2 = <Component prop={undefined} />;
425
instance2 = ReactTestUtils.renderIntoDocument(instance2);
426
reactComponentExpect(instance2).scalarPropsEqual({prop: 'testKey'});
427
428
var instance3 = <Component prop={null} />;
429
instance3 = ReactTestUtils.renderIntoDocument(instance3);
430
reactComponentExpect(instance3).scalarPropsEqual({prop: null});
431
});
432
433
it('should not mutate passed-in props object', function() {
434
var Component = React.createClass({
435
getDefaultProps: function() {
436
return {prop: 'testKey'};
437
},
438
render: function() {
439
return <span />;
440
}
441
});
442
443
var inputProps = {};
444
var instance1 = <Component {...inputProps} />;
445
instance1 = ReactTestUtils.renderIntoDocument(instance1);
446
expect(instance1.props.prop).toBe('testKey');
447
448
// We don't mutate the input, just in case the caller wants to do something
449
// with it after using it to instantiate a component
450
expect(inputProps.prop).not.toBeDefined();
451
});
452
453
it('should use default prop value when removing a prop', function() {
454
var Component = React.createClass({
455
getDefaultProps: function() {
456
return {fruit: 'persimmon'};
457
},
458
render: function() {
459
return <span />;
460
}
461
});
462
463
var container = document.createElement('div');
464
var instance = React.render(
465
<Component fruit="mango" />,
466
container
467
);
468
expect(instance.props.fruit).toBe('mango');
469
470
React.render(<Component />, container);
471
expect(instance.props.fruit).toBe('persimmon');
472
});
473
474
it('should normalize props with default values', function() {
475
var Component = React.createClass({
476
propTypes: {prop: ReactPropTypes.string.isRequired},
477
getDefaultProps: function() {
478
return {prop: 'testKey'};
479
},
480
getInitialState: function() {
481
return {prop: this.props.prop + 'State'};
482
},
483
render: function() {
484
return <span>{this.props.prop}</span>;
485
}
486
});
487
488
var instance = ReactTestUtils.renderIntoDocument(<Component />);
489
reactComponentExpect(instance).scalarPropsEqual({prop: 'testKey'});
490
reactComponentExpect(instance).scalarStateEqual({prop: 'testKeyState'});
491
492
ReactTestUtils.renderIntoDocument(<Component prop={null} />);
493
494
expect(console.warn.mock.calls.length).toBe(1);
495
expect(console.warn.mock.calls[0][0]).toBe(
496
'Warning: Required prop `prop` was not specified in `Component`.'
497
);
498
});
499
500
it('should check default prop values', function() {
501
var Component = React.createClass({
502
propTypes: {prop: ReactPropTypes.string.isRequired},
503
getDefaultProps: function() {
504
return {prop: null};
505
},
506
render: function() {
507
return <span>{this.props.prop}</span>;
508
}
509
});
510
511
ReactTestUtils.renderIntoDocument(<Component />);
512
513
expect(console.warn.mock.calls.length).toBe(1);
514
expect(console.warn.mock.calls[0][0]).toBe(
515
'Warning: Required prop `prop` was not specified in `Component`.'
516
);
517
});
518
519
it('should check declared prop types', function() {
520
var Component = React.createClass({
521
propTypes: {
522
prop: ReactPropTypes.string.isRequired
523
},
524
render: function() {
525
return <span>{this.props.prop}</span>;
526
}
527
});
528
529
ReactTestUtils.renderIntoDocument(<Component />);
530
ReactTestUtils.renderIntoDocument(<Component prop={42} />);
531
532
expect(console.warn.mock.calls.length).toBe(2);
533
expect(console.warn.mock.calls[0][0]).toBe(
534
'Warning: Required prop `prop` was not specified in `Component`.'
535
);
536
537
expect(console.warn.mock.calls[1][0]).toBe(
538
'Warning: Invalid prop `prop` of type `number` supplied to ' +
539
'`Component`, expected `string`.'
540
);
541
542
ReactTestUtils.renderIntoDocument(<Component prop="string" />);
543
544
// Should not error for strings
545
expect(console.warn.mock.calls.length).toBe(2);
546
});
547
548
it('should throw on invalid prop types', function() {
549
expect(function() {
550
React.createClass({
551
displayName: 'Component',
552
propTypes: {
553
prop: null
554
},
555
render: function() {
556
return <span>{this.props.prop}</span>;
557
}
558
});
559
}).toThrow(
560
'Invariant Violation: Component: prop type `prop` is invalid; ' +
561
'it must be a function, usually from React.PropTypes.'
562
);
563
});
564
565
it('should throw on invalid context types', function() {
566
expect(function() {
567
React.createClass({
568
displayName: 'Component',
569
contextTypes: {
570
prop: null
571
},
572
render: function() {
573
return <span>{this.props.prop}</span>;
574
}
575
});
576
}).toThrow(
577
'Invariant Violation: Component: context type `prop` is invalid; ' +
578
'it must be a function, usually from React.PropTypes.'
579
);
580
});
581
582
it('should throw on invalid child context types', function() {
583
expect(function() {
584
React.createClass({
585
displayName: 'Component',
586
childContextTypes: {
587
prop: null
588
},
589
render: function() {
590
return <span>{this.props.prop}</span>;
591
}
592
});
593
}).toThrow(
594
'Invariant Violation: Component: child context type `prop` is invalid; ' +
595
'it must be a function, usually from React.PropTypes.'
596
);
597
});
598
599
it('should not allow `forceUpdate` on unmounted components', function() {
600
var container = document.createElement('div');
601
document.documentElement.appendChild(container);
602
603
var Component = React.createClass({
604
render: function() {
605
return <div />;
606
}
607
});
608
609
var instance = <Component />;
610
expect(instance.forceUpdate).not.toBeDefined();
611
612
instance = React.render(instance, container);
613
expect(function() {
614
instance.forceUpdate();
615
}).not.toThrow();
616
617
React.unmountComponentAtNode(container);
618
expect(function() {
619
instance.forceUpdate();
620
}).toThrow(
621
'Invariant Violation: forceUpdate(...): Can only force an update on ' +
622
'mounted or mounting components.'
623
);
624
});
625
626
it('should cleanup even if render() fatals', function() {
627
var BadComponent = React.createClass({
628
render: function() {
629
throw new Error();
630
}
631
});
632
var instance = <BadComponent />;
633
634
expect(ReactCurrentOwner.current).toBe(null);
635
636
expect(function() {
637
instance = ReactTestUtils.renderIntoDocument(instance);
638
}).toThrow();
639
640
expect(ReactCurrentOwner.current).toBe(null);
641
});
642
643
it('should support mixins with getInitialState()', function() {
644
var Mixin = {
645
getInitialState: function() {
646
return {mixin: true};
647
}
648
};
649
var Component = React.createClass({
650
mixins: [Mixin],
651
getInitialState: function() {
652
return {component: true};
653
},
654
render: function() {
655
return <span />;
656
}
657
});
658
var instance = <Component />;
659
instance = ReactTestUtils.renderIntoDocument(instance);
660
expect(instance.state.component).toBe(true);
661
expect(instance.state.mixin).toBe(true);
662
});
663
664
it('should throw with conflicting getInitialState() methods', function() {
665
var Mixin = {
666
getInitialState: function() {
667
return {x: true};
668
}
669
};
670
var Component = React.createClass({
671
mixins: [Mixin],
672
getInitialState: function() {
673
return {x: true};
674
},
675
render: function() {
676
return <span />;
677
}
678
});
679
var instance = <Component />;
680
expect(function() {
681
instance = ReactTestUtils.renderIntoDocument(instance);
682
}).toThrow(
683
'Invariant Violation: mergeObjectsWithNoDuplicateKeys(): ' +
684
'Tried to merge two objects with the same key: `x`. This conflict ' +
685
'may be due to a mixin; in particular, this may be caused by two ' +
686
'getInitialState() or getDefaultProps() methods returning objects ' +
687
'with clashing keys.'
688
);
689
});
690
691
it('should work with object getInitialState() return values', function() {
692
var Component = React.createClass({
693
getInitialState: function() {
694
return {
695
occupation: 'clown'
696
};
697
},
698
render: function() {
699
return <span />;
700
}
701
});
702
var instance = <Component />;
703
instance = ReactTestUtils.renderIntoDocument(instance);
704
expect(instance.state.occupation).toEqual('clown');
705
});
706
707
it('should throw with non-object getInitialState() return values', function() {
708
[['an array'], 'a string', 1234].forEach(function(state) {
709
var Component = React.createClass({
710
getInitialState: function() {
711
return state;
712
},
713
render: function() {
714
return <span />;
715
}
716
});
717
var instance = <Component />;
718
expect(function() {
719
instance = ReactTestUtils.renderIntoDocument(instance);
720
}).toThrow(
721
'Invariant Violation: Component.getInitialState(): ' +
722
'must return an object or null'
723
);
724
});
725
});
726
727
it('should work with a null getInitialState() return value', function() {
728
var Component = React.createClass({
729
getInitialState: function() {
730
return null;
731
},
732
render: function() {
733
return <span />;
734
}
735
});
736
expect(
737
() => ReactTestUtils.renderIntoDocument(<Component />)
738
).not.toThrow();
739
});
740
741
it('should work with a null getInitialState return value and a mixin', () => {
742
var Component;
743
var instance;
744
745
var Mixin = {
746
getInitialState: function() {
747
return {foo: 'bar'};
748
}
749
};
750
Component = React.createClass({
751
mixins: [Mixin],
752
getInitialState: function() {
753
return null;
754
},
755
render: function() {
756
return <span />;
757
}
758
});
759
expect(
760
() => ReactTestUtils.renderIntoDocument(<Component />)
761
).not.toThrow();
762
763
instance = <Component />;
764
instance = ReactTestUtils.renderIntoDocument(instance);
765
expect(instance.state).toEqual({foo: 'bar'});
766
767
// Also the other way round should work
768
var Mixin2 = {
769
getInitialState: function() {
770
return null;
771
}
772
};
773
Component = React.createClass({
774
mixins: [Mixin2],
775
getInitialState: function() {
776
return {foo: 'bar'};
777
},
778
render: function() {
779
return <span />;
780
}
781
});
782
expect(
783
() => ReactTestUtils.renderIntoDocument(<Component />)
784
).not.toThrow();
785
786
instance = <Component />;
787
instance = ReactTestUtils.renderIntoDocument(instance);
788
expect(instance.state).toEqual({foo: 'bar'});
789
790
// Multiple mixins should be fine too
791
Component = React.createClass({
792
mixins: [Mixin, Mixin2],
793
getInitialState: function() {
794
return {x: true};
795
},
796
render: function() {
797
return <span />;
798
}
799
});
800
expect(
801
() => ReactTestUtils.renderIntoDocument(<Component />)
802
).not.toThrow();
803
804
instance = <Component />;
805
instance = ReactTestUtils.renderIntoDocument(instance);
806
expect(instance.state).toEqual({foo: 'bar', x: true});
807
});
808
809
it('should work with object getInitialState() return values', function() {
810
var Component = React.createClass({
811
getInitialState: function() {
812
return {
813
occupation: 'clown'
814
};
815
},
816
render: function() {
817
return <span />;
818
}
819
});
820
var instance = <Component />;
821
instance = ReactTestUtils.renderIntoDocument(instance);
822
expect(instance.state.occupation).toEqual('clown');
823
});
824
825
it('should throw with non-object getInitialState() return values', function() {
826
[['an array'], 'a string', 1234].forEach(function(state) {
827
var Component = React.createClass({
828
getInitialState: function() {
829
return state;
830
},
831
render: function() {
832
return <span />;
833
}
834
});
835
var instance = <Component />;
836
expect(function() {
837
instance = ReactTestUtils.renderIntoDocument(instance);
838
}).toThrow(
839
'Invariant Violation: Component.getInitialState(): ' +
840
'must return an object or null'
841
);
842
});
843
});
844
845
it('should call componentWillUnmount before unmounting', function() {
846
var container = document.createElement('div');
847
var innerUnmounted = false;
848
849
spyOn(ReactMount, 'purgeID').andCallThrough();
850
851
var Component = React.createClass({
852
render: function() {
853
return <div>
854
<Inner />
855
</div>;
856
}
857
});
858
var Inner = React.createClass({
859
componentWillUnmount: function() {
860
// It's important that ReactMount.purgeID be called after any component
861
// lifecycle methods, because a componentWillMount implementation is
862
// likely call this.getDOMNode(), which will repopulate the node cache
863
// after it's been cleared, causing a memory leak.
864
expect(ReactMount.purgeID.callCount).toBe(0);
865
innerUnmounted = true;
866
},
867
render: function() {
868
return <div />;
869
}
870
});
871
872
React.render(<Component />, container);
873
React.unmountComponentAtNode(container);
874
expect(innerUnmounted).toBe(true);
875
876
// <Component />, <Inner />, and both <div /> elements each call
877
// unmountIDFromEnvironment which calls purgeID, for a total of 4.
878
expect(ReactMount.purgeID.callCount).toBe(4);
879
});
880
881
it('should warn but detect valid CompositeComponent classes', function() {
882
var warn = console.warn;
883
console.warn = mocks.getMockFunction();
884
885
var Component = React.createClass({
886
render: function() {
887
return <div/>;
888
}
889
});
890
891
expect(React.isValidClass(Component)).toBe(true);
892
893
expect(console.warn.mock.calls.length).toBe(1);
894
expect(console.warn.mock.calls[0][0]).toContain(
895
'isValidClass is deprecated and will be removed in a future release'
896
);
897
});
898
899
it('should warn but detect invalid CompositeComponent classes', function() {
900
var warn = console.warn;
901
console.warn = mocks.getMockFunction();
902
903
var FnComponent = function() {
904
return false;
905
};
906
907
var NullComponent = null;
908
909
var TrickFnComponent = function() {
910
return true;
911
};
912
TrickFnComponent.componentConstructor = true;
913
914
expect(React.isValidClass(FnComponent)).toBe(false);
915
expect(React.isValidClass(NullComponent)).toBe(false);
916
expect(React.isValidClass(TrickFnComponent)).toBe(false);
917
918
expect(console.warn.mock.calls.length).toBe(3);
919
console.warn.mock.calls.forEach(function(call) {
920
expect(call[0]).toContain(
921
'isValidClass is deprecated and will be removed in a future release'
922
);
923
});
924
});
925
926
it('should warn when shouldComponentUpdate() returns undefined', function() {
927
var warn = console.warn;
928
console.warn = mocks.getMockFunction();
929
930
try {
931
var Component = React.createClass({
932
getInitialState: function () {
933
return {bogus: false};
934
},
935
936
shouldComponentUpdate: function() {
937
return undefined;
938
},
939
940
render: function() {
941
return <div />;
942
}
943
});
944
945
var instance = ReactTestUtils.renderIntoDocument(<Component />);
946
instance.setState({bogus: true});
947
948
expect(console.warn.mock.calls.length).toBe(1);
949
expect(console.warn.mock.calls[0][0]).toBe(
950
'Component.shouldComponentUpdate(): Returned undefined instead of a ' +
951
'boolean value. Make sure to return true or false.'
952
);
953
} finally {
954
console.warn = warn;
955
}
956
});
957
958
it('should warn when mispelling shouldComponentUpdate', function() {
959
var warn = console.warn;
960
console.warn = mocks.getMockFunction();
961
962
try {
963
React.createClass({
964
componentShouldUpdate: function() {
965
return false;
966
},
967
render: function() {
968
return <div />;
969
}
970
});
971
expect(console.warn.mock.calls.length).toBe(1);
972
expect(console.warn.mock.calls[0][0]).toBe(
973
'A component has a method called componentShouldUpdate(). Did you ' +
974
'mean shouldComponentUpdate()? The name is phrased as a question ' +
975
'because the function is expected to return a value.'
976
);
977
978
var NamedComponent = React.createClass({
979
componentShouldUpdate: function() {
980
return false;
981
},
982
render: function() {
983
return <div />;
984
}
985
});
986
expect(console.warn.mock.calls.length).toBe(2);
987
expect(console.warn.mock.calls[1][0]).toBe(
988
'NamedComponent has a method called componentShouldUpdate(). Did you ' +
989
'mean shouldComponentUpdate()? The name is phrased as a question ' +
990
'because the function is expected to return a value.'
991
);
992
993
<NamedComponent />; // Shut up lint
994
} finally {
995
console.warn = warn;
996
}
997
});
998
999
xit('should warn when using deprecated non-static spec keys', function() {
1000
var warn = console.warn;
1001
console.warn = mocks.getMockFunction();
1002
try {
1003
React.createClass({
1004
mixins: [{}],
1005
propTypes: {
1006
foo: ReactPropTypes.string
1007
},
1008
contextTypes: {
1009
foo: ReactPropTypes.string
1010
},
1011
childContextTypes: {
1012
foo: ReactPropTypes.string
1013
},
1014
render: function() {
1015
return <div />;
1016
}
1017
});
1018
expect(console.warn.mock.calls.length).toBe(4);
1019
expect(console.warn.mock.calls[0][0]).toBe(
1020
'createClass(...): `mixins` is now a static property and should ' +
1021
'be defined inside "statics".'
1022
);
1023
expect(console.warn.mock.calls[1][0]).toBe(
1024
'createClass(...): `propTypes` is now a static property and should ' +
1025
'be defined inside "statics".'
1026
);
1027
expect(console.warn.mock.calls[2][0]).toBe(
1028
'createClass(...): `contextTypes` is now a static property and ' +
1029
'should be defined inside "statics".'
1030
);
1031
expect(console.warn.mock.calls[3][0]).toBe(
1032
'createClass(...): `childContextTypes` is now a static property and ' +
1033
'should be defined inside "statics".'
1034
);
1035
} finally {
1036
console.warn = warn;
1037
}
1038
});
1039
1040
it('should pass context', function() {
1041
var childInstance = null;
1042
var grandchildInstance = null;
1043
1044
var Parent = React.createClass({
1045
childContextTypes: {
1046
foo: ReactPropTypes.string,
1047
depth: ReactPropTypes.number
1048
},
1049
1050
getChildContext: function() {
1051
return {
1052
foo: 'bar',
1053
depth: 0
1054
};
1055
},
1056
1057
render: function() {
1058
return <Child />;
1059
}
1060
});
1061
1062
var Child = React.createClass({
1063
contextTypes: {
1064
foo: ReactPropTypes.string,
1065
depth: ReactPropTypes.number
1066
},
1067
1068
childContextTypes: {
1069
depth: ReactPropTypes.number
1070
},
1071
1072
getChildContext: function() {
1073
return {
1074
depth: this.context.depth + 1
1075
};
1076
},
1077
1078
render: function() {
1079
childInstance = this;
1080
return <Grandchild />;
1081
}
1082
});
1083
1084
var Grandchild = React.createClass({
1085
contextTypes: {
1086
foo: ReactPropTypes.string,
1087
depth: ReactPropTypes.number
1088
},
1089
1090
render: function() {
1091
grandchildInstance = this;
1092
return <div />;
1093
}
1094
});
1095
1096
ReactTestUtils.renderIntoDocument(<Parent />);
1097
reactComponentExpect(childInstance).scalarContextEqual({foo: 'bar', depth: 0});
1098
reactComponentExpect(grandchildInstance).scalarContextEqual({foo: 'bar', depth: 1});
1099
});
1100
1101
it('should check context types', function() {
1102
var Component = React.createClass({
1103
contextTypes: {
1104
foo: ReactPropTypes.string.isRequired
1105
},
1106
1107
render: function() {
1108
return <div />;
1109
}
1110
});
1111
1112
ReactTestUtils.renderIntoDocument(<Component />);
1113
1114
expect(console.warn.mock.calls.length).toBe(1);
1115
expect(console.warn.mock.calls[0][0]).toBe(
1116
'Warning: Required context `foo` was not specified in `Component`.'
1117
);
1118
1119
React.withContext({foo: 'bar'}, function() {
1120
ReactTestUtils.renderIntoDocument(<Component />);
1121
});
1122
1123
// Previous call should not error
1124
expect(console.warn.mock.calls.length).toBe(1);
1125
1126
React.withContext({foo: 123}, function() {
1127
ReactTestUtils.renderIntoDocument(<Component />);
1128
});
1129
1130
expect(console.warn.mock.calls.length).toBe(2);
1131
expect(console.warn.mock.calls[1][0]).toBe(
1132
'Warning: Invalid context `foo` of type `number` supplied ' +
1133
'to `Component`, expected `string`.'
1134
);
1135
});
1136
1137
it('should check child context types', function() {
1138
var Component = React.createClass({
1139
childContextTypes: {
1140
foo: ReactPropTypes.string.isRequired,
1141
bar: ReactPropTypes.number
1142
},
1143
1144
getChildContext: function() {
1145
return this.props.testContext;
1146
},
1147
1148
render: function() {
1149
return <div />;
1150
}
1151
});
1152
1153
ReactTestUtils.renderIntoDocument(<Component testContext={{bar: 123}} />);
1154
1155
expect(console.warn.mock.calls.length).toBe(1);
1156
expect(console.warn.mock.calls[0][0]).toBe(
1157
'Warning: Required child context `foo` was not specified in `Component`.'
1158
);
1159
1160
ReactTestUtils.renderIntoDocument(<Component testContext={{foo: 123}} />);
1161
1162
expect(console.warn.mock.calls.length).toBe(2);
1163
expect(console.warn.mock.calls[1][0]).toBe(
1164
'Warning: Invalid child context `foo` of type `number` ' +
1165
'supplied to `Component`, expected `string`.'
1166
);
1167
1168
ReactTestUtils.renderIntoDocument(
1169
<Component testContext={{foo: 'foo', bar: 123}} />
1170
);
1171
1172
ReactTestUtils.renderIntoDocument(
1173
<Component testContext={{foo: 'foo'}} />
1174
);
1175
1176
// Previous calls should not log errors
1177
expect(console.warn.mock.calls.length).toBe(2);
1178
});
1179
1180
it('should filter out context not in contextTypes', function() {
1181
var Component = React.createClass({
1182
contextTypes: {
1183
foo: ReactPropTypes.string
1184
},
1185
1186
render: function() {
1187
return <div />;
1188
}
1189
});
1190
1191
var instance = React.withContext({foo: 'abc', bar: 123}, function() {
1192
return <Component />;
1193
});
1194
instance = ReactTestUtils.renderIntoDocument(instance);
1195
reactComponentExpect(instance).scalarContextEqual({foo: 'abc'});
1196
});
1197
1198
it('should filter context properly in callbacks', function() {
1199
var actualComponentWillReceiveProps;
1200
var actualShouldComponentUpdate;
1201
var actualComponentWillUpdate;
1202
var actualComponentDidUpdate;
1203
1204
var Parent = React.createClass({
1205
childContextTypes: {
1206
foo: ReactPropTypes.string.isRequired,
1207
bar: ReactPropTypes.string.isRequired
1208
},
1209
1210
getChildContext: function() {
1211
return {
1212
foo: this.props.foo,
1213
bar: "bar"
1214
};
1215
},
1216
1217
render: function() {
1218
return <Component />;
1219
}
1220
});
1221
1222
var Component = React.createClass({
1223
contextTypes: {
1224
foo: ReactPropTypes.string
1225
},
1226
1227
componentWillReceiveProps: function(nextProps, nextContext) {
1228
actualComponentWillReceiveProps = nextContext;
1229
return true;
1230
},
1231
1232
shouldComponentUpdate: function(nextProps, nextState, nextContext) {
1233
actualShouldComponentUpdate = nextContext;
1234
return true;
1235
},
1236
1237
componentWillUpdate: function(nextProps, nextState, nextContext) {
1238
actualComponentWillUpdate = nextContext;
1239
},
1240
1241
componentDidUpdate: function(prevProps, prevState, prevContext) {
1242
actualComponentDidUpdate = prevContext;
1243
},
1244
1245
render: function() {
1246
return <div />;
1247
}
1248
});
1249
1250
var instance = <Parent foo="abc" />;
1251
instance = ReactTestUtils.renderIntoDocument(instance);
1252
instance.replaceProps({foo: "def"});
1253
expect(actualComponentWillReceiveProps).toEqual({foo: 'def'});
1254
expect(actualShouldComponentUpdate).toEqual({foo: 'def'});
1255
expect(actualComponentWillUpdate).toEqual({foo: 'def'});
1256
expect(actualComponentDidUpdate).toEqual({foo: 'abc'});
1257
});
1258
1259
it('should support statics', function() {
1260
var Component = React.createClass({
1261
statics: {
1262
abc: 'def',
1263
def: 0,
1264
ghi: null,
1265
jkl: 'mno',
1266
pqr: function() {
1267
return this;
1268
}
1269
},
1270
1271
render: function() {
1272
return <span />;
1273
}
1274
});
1275
var instance = <Component />;
1276
instance = ReactTestUtils.renderIntoDocument(instance);
1277
expect(instance.constructor.abc).toBe('def');
1278
expect(Component.abc).toBe('def');
1279
expect(instance.constructor.def).toBe(0);
1280
expect(Component.def).toBe(0);
1281
expect(instance.constructor.ghi).toBe(null);
1282
expect(Component.ghi).toBe(null);
1283
expect(instance.constructor.jkl).toBe('mno');
1284
expect(Component.jkl).toBe('mno');
1285
expect(instance.constructor.pqr()).toBe(Component.type);
1286
expect(Component.pqr()).toBe(Component.type);
1287
});
1288
1289
it('should throw if a reserved property is in statics', function() {
1290
expect(function() {
1291
React.createClass({
1292
statics: {
1293
getDefaultProps: function() {
1294
return {
1295
foo: 0
1296
};
1297
}
1298
},
1299
1300
render: function() {
1301
return <span />;
1302
}
1303
});
1304
}).toThrow(
1305
'Invariant Violation: ReactCompositeComponent: You are attempting to ' +
1306
'define a reserved property, `getDefaultProps`, that shouldn\'t be on ' +
1307
'the "statics" key. Define it as an instance property instead; it ' +
1308
'will still be accessible on the constructor.'
1309
);
1310
});
1311
1312
it('should support statics in mixins', function() {
1313
var Mixin = {
1314
statics: {
1315
foo: 'bar'
1316
}
1317
};
1318
var Component = React.createClass({
1319
mixins: [Mixin],
1320
1321
statics: {
1322
abc: 'def'
1323
},
1324
1325
render: function() {
1326
return <span />;
1327
}
1328
});
1329
var instance = <Component />;
1330
instance = ReactTestUtils.renderIntoDocument(instance);
1331
expect(instance.constructor.foo).toBe('bar');
1332
expect(Component.foo).toBe('bar');
1333
expect(instance.constructor.abc).toBe('def');
1334
expect(Component.abc).toBe('def');
1335
});
1336
1337
it("should throw if mixins override each others' statics", function() {
1338
expect(function() {
1339
var Mixin = {
1340
statics: {
1341
abc: 'foo'
1342
}
1343
};
1344
React.createClass({
1345
mixins: [Mixin],
1346
1347
statics: {
1348
abc: 'bar'
1349
},
1350
1351
render: function() {
1352
return <span />;
1353
}
1354
});
1355
}).toThrow(
1356
'Invariant Violation: ReactCompositeComponent: You are attempting to ' +
1357
'define `abc` on your component more than once. This conflict may be ' +
1358
'due to a mixin.'
1359
);
1360
});
1361
1362
it("should throw if mixins override functions in statics", function() {
1363
expect(function() {
1364
var Mixin = {
1365
statics: {
1366
abc: function() { console.log('foo'); }
1367
}
1368
};
1369
React.createClass({
1370
mixins: [Mixin],
1371
1372
statics: {
1373
abc: function() { console.log('bar'); }
1374
},
1375
1376
render: function() {
1377
return <span />;
1378
}
1379
});
1380
}).toThrow(
1381
'Invariant Violation: ReactCompositeComponent: You are attempting to ' +
1382
'define `abc` on your component more than once. This conflict may be ' +
1383
'due to a mixin.'
1384
);
1385
});
1386
1387
it("should throw if the mixin is a React component", function() {
1388
expect(function() {
1389
React.createClass({
1390
mixins: [<div />],
1391
1392
render: function() {
1393
return <span />;
1394
}
1395
});
1396
}).toThrow(
1397
'Invariant Violation: ReactCompositeComponent: You\'re attempting to ' +
1398
'use a component as a mixin. Instead, just use a regular object.'
1399
);
1400
});
1401
1402
it("should throw if the mixin is a React component class", function() {
1403
expect(function() {
1404
var Component = React.createClass({
1405
render: function() {
1406
return <span />;
1407
}
1408
});
1409
1410
React.createClass({
1411
mixins: [Component],
1412
1413
render: function() {
1414
return <span />;
1415
}
1416
});
1417
}).toThrow(
1418
'Invariant Violation: ReactCompositeComponent: You\'re attempting to ' +
1419
'use a component class as a mixin. Instead, just use a regular object.'
1420
);
1421
});
1422
1423
it('should have bound the mixin methods to the component', function() {
1424
var mixin = {
1425
mixinFunc: function() {return this;}
1426
};
1427
1428
var Component = React.createClass({
1429
mixins: [mixin],
1430
componentDidMount: function() {
1431
expect(this.mixinFunc()).toBe(this);
1432
},
1433
render: function() {
1434
return <span />;
1435
}
1436
});
1437
var instance = <Component />;
1438
instance = ReactTestUtils.renderIntoDocument(instance);
1439
});
1440
1441
it('should include the mixin keys in even if their values are falsy',
1442
function() {
1443
var mixin = {
1444
keyWithNullValue: null,
1445
randomCounter: 0
1446
};
1447
1448
var Component = React.createClass({
1449
mixins: [mixin],
1450
componentDidMount: function() {
1451
expect(this.randomCounter).toBe(0);
1452
expect(this.keyWithNullValue).toBeNull();
1453
},
1454
render: function() {
1455
return <span />;
1456
}
1457
});
1458
var instance = <Component />;
1459
instance = ReactTestUtils.renderIntoDocument(instance);
1460
});
1461
1462
it('should disallow nested render calls', function() {
1463
spyOn(console, 'warn');
1464
var Inner = React.createClass({
1465
render: function() {
1466
return <div />;
1467
}
1468
});
1469
var Outer = React.createClass({
1470
render: function() {
1471
ReactTestUtils.renderIntoDocument(<Inner />);
1472
return <div />;
1473
}
1474
});
1475
1476
ReactTestUtils.renderIntoDocument(<Outer />);
1477
expect(console.warn.argsForCall.length).toBe(1);
1478
expect(console.warn.argsForCall[0][0]).toBe(
1479
'Warning: _renderNewRootComponent(): Render methods should ' +
1480
'be a pure function of props and state; triggering nested component ' +
1481
'updates from render is not allowed. If necessary, trigger nested ' +
1482
'updates in componentDidUpdate.'
1483
);
1484
});
1485
1486
it('gives a helpful error when passing null or undefined', function() {
1487
spyOn(console, 'warn');
1488
React.createElement(undefined);
1489
React.createElement(null);
1490
expect(console.warn.calls.length).toBe(2);
1491
expect(console.warn.calls[0].args[0]).toBe(
1492
'Warning: React.createElement: type should not be null or undefined. ' +
1493
'It should be a string (for DOM elements) or a ReactClass (for ' +
1494
'composite components).'
1495
);
1496
expect(console.warn.calls[1].args[0]).toBe(
1497
'Warning: React.createElement: type should not be null or undefined. ' +
1498
'It should be a string (for DOM elements) or a ReactClass (for ' +
1499
'composite components).'
1500
);
1501
React.createElement('div');
1502
expect(console.warn.calls.length).toBe(2);
1503
1504
expect(() => React.createElement(undefined)).not.toThrow()
1505
});
1506
1507
});
1508
1509