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 React;
15
var ReactTestUtils;
16
var ReactUpdates;
17
18
describe('ReactUpdates', function() {
19
beforeEach(function() {
20
React = require('React');
21
ReactTestUtils = require('ReactTestUtils');
22
ReactUpdates = require('ReactUpdates');
23
});
24
25
it('should batch state when updating state twice', function() {
26
var updateCount = 0;
27
var Component = React.createClass({
28
getInitialState: function() {
29
return {x: 0};
30
},
31
componentDidUpdate: function() {
32
updateCount++;
33
},
34
render: function() {
35
return <div>{this.state.x}</div>;
36
}
37
});
38
39
var instance = ReactTestUtils.renderIntoDocument(<Component />);
40
expect(instance.state.x).toBe(0);
41
42
ReactUpdates.batchedUpdates(function() {
43
instance.setState({x: 1});
44
instance.setState({x: 2});
45
expect(instance.state.x).toBe(0);
46
expect(updateCount).toBe(0);
47
});
48
49
expect(instance.state.x).toBe(2);
50
expect(updateCount).toBe(1);
51
});
52
53
it('should batch state when updating two different state keys', function() {
54
var updateCount = 0;
55
var Component = React.createClass({
56
getInitialState: function() {
57
return {x: 0, y: 0};
58
},
59
componentDidUpdate: function() {
60
updateCount++;
61
},
62
render: function() {
63
return <div>({this.state.x}, {this.state.y})</div>;
64
}
65
});
66
67
var instance = ReactTestUtils.renderIntoDocument(<Component />);
68
expect(instance.state.x).toBe(0);
69
expect(instance.state.y).toBe(0);
70
71
ReactUpdates.batchedUpdates(function() {
72
instance.setState({x: 1});
73
instance.setState({y: 2});
74
expect(instance.state.x).toBe(0);
75
expect(instance.state.y).toBe(0);
76
expect(updateCount).toBe(0);
77
});
78
79
expect(instance.state.x).toBe(1);
80
expect(instance.state.y).toBe(2);
81
expect(updateCount).toBe(1);
82
});
83
84
it('should batch state and props together', function() {
85
var updateCount = 0;
86
var Component = React.createClass({
87
getInitialState: function() {
88
return {y: 0};
89
},
90
componentDidUpdate: function() {
91
updateCount++;
92
},
93
render: function() {
94
return <div>({this.props.x}, {this.state.y})</div>;
95
}
96
});
97
98
var instance = ReactTestUtils.renderIntoDocument(<Component x={0} />);
99
expect(instance.props.x).toBe(0);
100
expect(instance.state.y).toBe(0);
101
102
ReactUpdates.batchedUpdates(function() {
103
instance.setProps({x: 1});
104
instance.setState({y: 2});
105
expect(instance.props.x).toBe(0);
106
expect(instance.state.y).toBe(0);
107
expect(updateCount).toBe(0);
108
});
109
110
expect(instance.props.x).toBe(1);
111
expect(instance.state.y).toBe(2);
112
expect(updateCount).toBe(1);
113
});
114
115
it('should batch parent/child state updates together', function() {
116
var parentUpdateCount = 0;
117
var Parent = React.createClass({
118
getInitialState: function() {
119
return {x: 0};
120
},
121
componentDidUpdate: function() {
122
parentUpdateCount++;
123
},
124
render: function() {
125
return <div><Child ref="child" x={this.state.x} /></div>;
126
}
127
});
128
var childUpdateCount = 0;
129
var Child = React.createClass({
130
getInitialState: function() {
131
return {y: 0};
132
},
133
componentDidUpdate: function() {
134
childUpdateCount++;
135
},
136
render: function() {
137
return <div>{this.props.x + this.state.y}</div>;
138
}
139
});
140
141
var instance = ReactTestUtils.renderIntoDocument(<Parent />);
142
var child = instance.refs.child;
143
expect(instance.state.x).toBe(0);
144
expect(child.state.y).toBe(0);
145
146
ReactUpdates.batchedUpdates(function() {
147
instance.setState({x: 1});
148
child.setState({y: 2});
149
expect(instance.state.x).toBe(0);
150
expect(child.state.y).toBe(0);
151
expect(parentUpdateCount).toBe(0);
152
expect(childUpdateCount).toBe(0);
153
});
154
155
expect(instance.state.x).toBe(1);
156
expect(child.state.y).toBe(2);
157
expect(parentUpdateCount).toBe(1);
158
expect(childUpdateCount).toBe(1);
159
});
160
161
it('should batch child/parent state updates together', function() {
162
var parentUpdateCount = 0;
163
var Parent = React.createClass({
164
getInitialState: function() {
165
return {x: 0};
166
},
167
componentDidUpdate: function() {
168
parentUpdateCount++;
169
},
170
render: function() {
171
return <div><Child ref="child" x={this.state.x} /></div>;
172
}
173
});
174
var childUpdateCount = 0;
175
var Child = React.createClass({
176
getInitialState: function() {
177
return {y: 0};
178
},
179
componentDidUpdate: function() {
180
childUpdateCount++;
181
},
182
render: function() {
183
return <div>{this.props.x + this.state.y}</div>;
184
}
185
});
186
187
var instance = ReactTestUtils.renderIntoDocument(<Parent />);
188
var child = instance.refs.child;
189
expect(instance.state.x).toBe(0);
190
expect(child.state.y).toBe(0);
191
192
ReactUpdates.batchedUpdates(function() {
193
child.setState({y: 2});
194
instance.setState({x: 1});
195
expect(instance.state.x).toBe(0);
196
expect(child.state.y).toBe(0);
197
expect(parentUpdateCount).toBe(0);
198
expect(childUpdateCount).toBe(0);
199
});
200
201
expect(instance.state.x).toBe(1);
202
expect(child.state.y).toBe(2);
203
expect(parentUpdateCount).toBe(1);
204
205
// Batching reduces the number of updates here to 1.
206
expect(childUpdateCount).toBe(1);
207
});
208
209
it('should support chained state updates', function() {
210
var updateCount = 0;
211
var Component = React.createClass({
212
getInitialState: function() {
213
return {x: 0};
214
},
215
componentDidUpdate: function() {
216
updateCount++;
217
},
218
render: function() {
219
return <div>{this.state.x}</div>;
220
}
221
});
222
223
var instance = ReactTestUtils.renderIntoDocument(<Component />);
224
expect(instance.state.x).toBe(0);
225
226
var innerCallbackRun = false;
227
ReactUpdates.batchedUpdates(function() {
228
instance.setState({x: 1}, function() {
229
instance.setState({x: 2}, function() {
230
expect(this).toBe(instance);
231
innerCallbackRun = true;
232
expect(instance.state.x).toBe(2);
233
expect(updateCount).toBe(2);
234
});
235
expect(instance.state.x).toBe(1);
236
expect(updateCount).toBe(1);
237
});
238
expect(instance.state.x).toBe(0);
239
expect(updateCount).toBe(0);
240
});
241
242
expect(innerCallbackRun).toBeTruthy();
243
expect(instance.state.x).toBe(2);
244
expect(updateCount).toBe(2);
245
});
246
247
it('should batch forceUpdate together', function() {
248
var shouldUpdateCount = 0;
249
var updateCount = 0;
250
var Component = React.createClass({
251
getInitialState: function() {
252
return {x: 0};
253
},
254
shouldComponentUpdate: function() {
255
shouldUpdateCount++;
256
},
257
componentDidUpdate: function() {
258
updateCount++;
259
},
260
render: function() {
261
return <div>{this.state.x}</div>;
262
}
263
});
264
265
var instance = ReactTestUtils.renderIntoDocument(<Component />);
266
expect(instance.state.x).toBe(0);
267
268
var callbacksRun = 0;
269
ReactUpdates.batchedUpdates(function() {
270
instance.setState({x: 1}, function() {
271
callbacksRun++;
272
});
273
instance.forceUpdate(function() {
274
callbacksRun++;
275
});
276
expect(instance.state.x).toBe(0);
277
expect(updateCount).toBe(0);
278
});
279
280
expect(callbacksRun).toBe(2);
281
// shouldComponentUpdate shouldn't be called since we're forcing
282
expect(shouldUpdateCount).toBe(0);
283
expect(instance.state.x).toBe(1);
284
expect(updateCount).toBe(1);
285
});
286
287
it('should update children even if parent blocks updates', function() {
288
var parentRenderCount = 0;
289
var childRenderCount = 0;
290
291
var Parent = React.createClass({
292
shouldComponentUpdate: function() {
293
return false;
294
},
295
296
render: function() {
297
parentRenderCount++;
298
return <Child ref="child" />;
299
}
300
});
301
302
var Child = React.createClass({
303
render: function() {
304
childRenderCount++;
305
return <div />;
306
}
307
});
308
309
expect(parentRenderCount).toBe(0);
310
expect(childRenderCount).toBe(0);
311
312
var instance = <Parent />;
313
instance = ReactTestUtils.renderIntoDocument(instance);
314
315
expect(parentRenderCount).toBe(1);
316
expect(childRenderCount).toBe(1);
317
318
ReactUpdates.batchedUpdates(function() {
319
instance.setState({x: 1});
320
});
321
322
expect(parentRenderCount).toBe(1);
323
expect(childRenderCount).toBe(1);
324
325
ReactUpdates.batchedUpdates(function() {
326
instance.refs.child.setState({x: 1});
327
});
328
329
expect(parentRenderCount).toBe(1);
330
expect(childRenderCount).toBe(2);
331
});
332
333
it('should not reconcile children passed via props', function() {
334
var numMiddleRenders = 0;
335
var numBottomRenders = 0;
336
337
var Top = React.createClass({
338
render: function() {
339
return <Middle><Bottom /></Middle>;
340
}
341
});
342
343
var Middle = React.createClass({
344
componentDidMount: function() {
345
this.forceUpdate();
346
},
347
348
render: function() {
349
numMiddleRenders++;
350
return <div>{this.props.children}</div>;
351
}
352
});
353
354
var Bottom = React.createClass({
355
render: function() {
356
numBottomRenders++;
357
return <span />;
358
}
359
});
360
361
ReactTestUtils.renderIntoDocument(<Top />);
362
expect(numMiddleRenders).toBe(2);
363
expect(numBottomRenders).toBe(1);
364
});
365
366
it('should flow updates correctly', function() {
367
var willUpdates = [];
368
var didUpdates = [];
369
370
var UpdateLoggingMixin = {
371
componentWillUpdate: function() {
372
willUpdates.push(this.constructor.displayName);
373
},
374
componentDidUpdate: function() {
375
didUpdates.push(this.constructor.displayName);
376
}
377
};
378
379
var Box = React.createClass({
380
mixins: [UpdateLoggingMixin],
381
382
render: function() {
383
return <div ref="boxDiv">{this.props.children}</div>;
384
}
385
});
386
387
var Child = React.createClass({
388
mixins: [UpdateLoggingMixin],
389
390
render: function() {
391
return <span ref="span">child</span>;
392
}
393
});
394
395
var Switcher = React.createClass({
396
mixins: [UpdateLoggingMixin],
397
398
getInitialState: function() {
399
return {tabKey: 'hello'};
400
},
401
402
render: function() {
403
var child = this.props.children;
404
405
return (
406
<Box ref="box">
407
<div
408
ref="switcherDiv"
409
style={{
410
display: this.state.tabKey === child.key ? '' : 'none'
411
}}>
412
{child}
413
</div>
414
</Box>
415
);
416
}
417
});
418
419
var App = React.createClass({
420
mixins: [UpdateLoggingMixin],
421
422
render: function() {
423
return (
424
<Switcher ref="switcher">
425
<Child key="hello" ref="child" />
426
</Switcher>
427
);
428
}
429
});
430
431
var root = <App />;
432
root = ReactTestUtils.renderIntoDocument(root);
433
434
function expectUpdates(desiredWillUpdates, desiredDidUpdates) {
435
expect(willUpdates).toEqual(desiredWillUpdates);
436
expect(didUpdates).toEqual(desiredDidUpdates);
437
willUpdates.length = 0;
438
didUpdates.length = 0;
439
}
440
441
function triggerUpdate(c) {
442
c.setState({x: 1});
443
}
444
445
function testUpdates(components, desiredWillUpdates, desiredDidUpdates) {
446
var i;
447
448
ReactUpdates.batchedUpdates(function() {
449
for (i = 0; i < components.length; i++) {
450
triggerUpdate(components[i]);
451
}
452
});
453
454
expectUpdates(desiredWillUpdates, desiredDidUpdates);
455
456
// Try them in reverse order
457
458
ReactUpdates.batchedUpdates(function() {
459
for (i = components.length - 1; i >= 0; i--) {
460
triggerUpdate(components[i]);
461
}
462
});
463
464
expectUpdates(desiredWillUpdates, desiredDidUpdates);
465
}
466
467
testUpdates(
468
[root.refs.switcher.refs.box, root.refs.switcher],
469
// Owner-child relationships have inverse will and did
470
['Switcher', 'Box'],
471
['Box', 'Switcher']
472
);
473
474
testUpdates(
475
[root.refs.child, root.refs.switcher.refs.box],
476
// Not owner-child so reconcile independently
477
['Box', 'Child'],
478
['Box', 'Child']
479
);
480
481
testUpdates(
482
[root.refs.child, root.refs.switcher],
483
// Switcher owns Box and Child, Box does not own Child
484
['Switcher', 'Box', 'Child'],
485
['Box', 'Switcher', 'Child']
486
);
487
});
488
489
it('should share reconcile transaction across different roots', function() {
490
var ReconcileTransaction = ReactUpdates.ReactReconcileTransaction;
491
spyOn(ReconcileTransaction, 'getPooled').andCallThrough();
492
493
var Component = React.createClass({
494
render: function() {
495
return <div>{this.props.text}</div>;
496
}
497
});
498
499
var containerA = document.createElement('div');
500
var containerB = document.createElement('div');
501
502
// Initial renders aren't batched together yet...
503
ReactUpdates.batchedUpdates(function() {
504
React.render(<Component text="A1" />, containerA);
505
React.render(<Component text="B1" />, containerB);
506
});
507
expect(ReconcileTransaction.getPooled.calls.length).toBe(2);
508
509
// ...but updates are! Here only one more transaction is used, which means
510
// we only have to initialize and close the wrappers once.
511
ReactUpdates.batchedUpdates(function() {
512
React.render(<Component text="A2" />, containerA);
513
React.render(<Component text="B2" />, containerB);
514
});
515
expect(ReconcileTransaction.getPooled.calls.length).toBe(3);
516
});
517
518
it('should queue mount-ready handlers across different roots', function() {
519
// We'll define two components A and B, then update both of them. When A's
520
// componentDidUpdate handlers is called, B's DOM should already have been
521
// updated.
522
523
var a;
524
var b;
525
526
var aUpdated = false;
527
528
var A = React.createClass({
529
getInitialState: function() {
530
return {x: 0};
531
},
532
componentDidUpdate: function() {
533
expect(b.getDOMNode().textContent).toBe("B1");
534
aUpdated = true;
535
},
536
render: function() {
537
return <div>A{this.state.x}</div>;
538
}
539
});
540
541
var B = React.createClass({
542
getInitialState: function() {
543
return {x: 0};
544
},
545
render: function() {
546
return <div>B{this.state.x}</div>;
547
}
548
});
549
550
a = ReactTestUtils.renderIntoDocument(<A />);
551
b = ReactTestUtils.renderIntoDocument(<B />);
552
553
ReactUpdates.batchedUpdates(function() {
554
a.setState({x: 1});
555
b.setState({x: 1});
556
});
557
558
expect(aUpdated).toBe(true);
559
});
560
561
it('should flush updates in the correct order', function() {
562
var updates = [];
563
var Outer = React.createClass({
564
getInitialState: function() {
565
return {x: 0};
566
},
567
render: function() {
568
updates.push('Outer-render-' + this.state.x);
569
return <div><Inner x={this.state.x} ref="inner" /></div>;
570
},
571
componentDidUpdate: function() {
572
var x = this.state.x;
573
updates.push('Outer-didUpdate-' + x);
574
updates.push('Inner-setState-' + x);
575
this.refs.inner.setState({x: x}, function() {
576
updates.push('Inner-callback-' + x);
577
});
578
}
579
});
580
var Inner = React.createClass({
581
getInitialState: function() {
582
return {x: 0};
583
},
584
render: function() {
585
updates.push('Inner-render-' + this.props.x + '-' + this.state.x);
586
return <div />;
587
},
588
componentDidUpdate: function() {
589
updates.push('Inner-didUpdate-' + this.props.x + '-' + this.state.x);
590
}
591
});
592
593
var instance = ReactTestUtils.renderIntoDocument(<Outer />);
594
595
updates.push('Outer-setState-1');
596
instance.setState({x: 1}, function() {
597
updates.push('Outer-callback-1');
598
updates.push('Outer-setState-2');
599
instance.setState({x: 2}, function() {
600
updates.push('Outer-callback-2');
601
});
602
});
603
604
expect(updates).toEqual([
605
'Outer-render-0',
606
'Inner-render-0-0',
607
608
'Outer-setState-1',
609
'Outer-render-1',
610
'Inner-render-1-0',
611
'Inner-didUpdate-1-0',
612
'Outer-didUpdate-1',
613
'Inner-setState-1',
614
'Inner-render-1-1',
615
'Inner-didUpdate-1-1',
616
'Inner-callback-1',
617
'Outer-callback-1',
618
619
'Outer-setState-2',
620
'Outer-render-2',
621
'Inner-render-2-1',
622
'Inner-didUpdate-2-1',
623
'Outer-didUpdate-2',
624
'Inner-setState-2',
625
'Inner-render-2-2',
626
'Inner-didUpdate-2-2',
627
'Inner-callback-2',
628
'Outer-callback-2'
629
]);
630
});
631
632
it('should queue nested updates', function() {
633
// See https://github.com/facebook/react/issues/1147
634
635
var X = React.createClass({
636
getInitialState: function() {
637
return {s: 0};
638
},
639
render: function() {
640
if (this.state.s === 0) {
641
return <div>
642
<span>0</span>
643
</div>;
644
} else {
645
return <div>1</div>;
646
}
647
},
648
go: function() {
649
this.setState({s: 1});
650
this.setState({s: 0});
651
this.setState({s: 1});
652
}
653
});
654
655
var Y = React.createClass({
656
render: function() {
657
return <div>
658
<Z />
659
</div>;
660
}
661
});
662
663
var Z = React.createClass({
664
render: function() { return <div />; },
665
componentWillUpdate: function() {
666
x.go();
667
}
668
});
669
670
var x;
671
var y;
672
673
x = ReactTestUtils.renderIntoDocument(<X />);
674
y = ReactTestUtils.renderIntoDocument(<Y />);
675
expect(x.getDOMNode().textContent).toBe('0');
676
677
y.forceUpdate();
678
expect(x.getDOMNode().textContent).toBe('1');
679
});
680
681
it('should queue updates from during mount', function() {
682
// See https://github.com/facebook/react/issues/1353
683
var a;
684
685
var A = React.createClass({
686
getInitialState: function() {
687
return {x: 0};
688
},
689
componentWillMount: function() {
690
a = this;
691
},
692
render: function() {
693
return <div>A{this.state.x}</div>;
694
}
695
});
696
697
var B = React.createClass({
698
componentWillMount: function() {
699
a.setState({x: 1});
700
},
701
render: function() {
702
return <div />;
703
}
704
});
705
706
ReactUpdates.batchedUpdates(function() {
707
ReactTestUtils.renderIntoDocument(
708
<div>
709
<A />
710
<B />
711
</div>
712
);
713
});
714
715
expect(a.state.x).toBe(1);
716
expect(a.getDOMNode().textContent).toBe('A1');
717
});
718
719
it('calls componentWillReceiveProps setState callback properly', function() {
720
var callbackCount = 0;
721
var A = React.createClass({
722
getInitialState: function() {
723
return {x: this.props.x};
724
},
725
componentWillReceiveProps: function(nextProps) {
726
var newX = nextProps.x;
727
this.setState({x: newX}, function() {
728
// State should have updated by the time this callback gets called
729
expect(this.state.x).toBe(newX);
730
callbackCount++;
731
});
732
},
733
render: function() {
734
return <div>{this.state.x}</div>;
735
}
736
});
737
738
var container = document.createElement('div');
739
React.render(<A x={1} />, container);
740
React.render(<A x={2} />, container);
741
expect(callbackCount).toBe(1);
742
});
743
744
it('calls asap callbacks properly', function() {
745
var callbackCount = 0;
746
var A = React.createClass({
747
render: function() {
748
return <div />;
749
},
750
componentDidUpdate: function() {
751
var component = this;
752
ReactUpdates.asap(function() {
753
expect(this).toBe(component);
754
callbackCount++;
755
ReactUpdates.asap(function() {
756
callbackCount++;
757
});
758
expect(callbackCount).toBe(1);
759
}, this);
760
expect(callbackCount).toBe(0);
761
}
762
});
763
764
var container = document.createElement('div');
765
var component = React.render(<A />, container);
766
component.forceUpdate();
767
expect(callbackCount).toBe(2);
768
});
769
770
it('calls asap callbacks with queued updates', function() {
771
var log = [];
772
var A = React.createClass({
773
getInitialState: () => ({updates: 0}),
774
render: function() {
775
log.push('render-' + this.state.updates);
776
return <div />;
777
},
778
componentDidUpdate: function() {
779
if (this.state.updates === 1) {
780
ReactUpdates.asap(function() {
781
this.setState({updates: 2}, function() {
782
ReactUpdates.asap(function() {
783
log.push('asap-1.2');
784
});
785
log.push('setState-cb');
786
});
787
log.push('asap-1.1');
788
}, this);
789
} else if (this.state.updates === 2) {
790
ReactUpdates.asap(function() {
791
log.push('asap-2');
792
});
793
}
794
log.push('didUpdate-' + this.state.updates);
795
}
796
});
797
798
var container = document.createElement('div');
799
var component = React.render(<A />, container);
800
component.setState({updates: 1});
801
expect(log).toEqual([
802
'render-0',
803
// We do the first update...
804
'render-1',
805
'didUpdate-1',
806
// ...which calls asap and enqueues a second update...
807
'asap-1.1',
808
// ...which runs and enqueues the asap-2 log in its didUpdate...
809
'render-2',
810
'didUpdate-2',
811
// ...and runs the setState callback, which enqueues the log for
812
// asap-1.2.
813
'setState-cb',
814
'asap-2',
815
'asap-1.2'
816
]);
817
});
818
});
819
820