1
+/**
2
+ * Copyright (c) 2013-present, Facebook, Inc.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @emails react-core
8
+ */
9
+'use strict';
10
+
11
+let React;
12
+let ReactNoop;
13
+
14
+describe('ReactFragment', () => {
15
+ beforeEach(function() {
16
+ jest.resetModules();
17
+ React = require('react');
18
+ ReactNoop = require('react-noop-renderer');
19
+ });
20
+
21
+ function span(prop) {
22
+ return {type: 'span', children: [], prop};
23
+ }
24
+
25
+ function text(val) {
26
+ return {text: val};
27
+ }
28
+
29
+ function div(...children) {
30
+ children = children.map(c => (typeof c === 'string' ? {text: c} : c));
31
+ return {type: 'div', children, prop: undefined};
32
+ }
33
+
34
+ it('should render a single child via noop renderer', () => {
35
+ const element = (
36
+ <React.Fragment>
37
+ <span>foo</span>
38
+ </React.Fragment>
39
+ );
40
+
41
+ ReactNoop.render(element);
42
+ ReactNoop.flush();
43
+
44
+ expect(ReactNoop.getChildren()).toEqual([span()]);
45
+ });
46
+
47
+ it('should render zero children via noop renderer', () => {
48
+ const element = <React.Fragment />;
49
+
50
+ ReactNoop.render(element);
51
+ ReactNoop.flush();
52
+
53
+ expect(ReactNoop.getChildren()).toEqual([]);
54
+ });
55
+
56
+ it('should render multiple children via noop renderer', () => {
57
+ const element = (
58
+ <React.Fragment>
59
+ hello <span>world</span>
60
+ </React.Fragment>
61
+ );
62
+
63
+ ReactNoop.render(element);
64
+ ReactNoop.flush();
65
+
66
+ expect(ReactNoop.getChildren()).toEqual([text('hello '), span()]);
67
+ });
68
+
69
+ it('should render an iterable via noop renderer', () => {
70
+ const element = (
71
+ <React.Fragment>
72
+ {new Set([<span key="a">hi</span>, <span key="b">bye</span>])}
73
+ </React.Fragment>
74
+ );
75
+
76
+ ReactNoop.render(element);
77
+ ReactNoop.flush();
78
+
79
+ expect(ReactNoop.getChildren()).toEqual([span(), span()]);
80
+ });
81
+
82
+ it('should preserve state of children with 1 level nesting', function() {
83
+ var ops = [];
84
+
85
+ class Stateful extends React.Component {
86
+ componentDidUpdate() {
87
+ ops.push('Update Stateful');
88
+ }
89
+
90
+ render() {
91
+ return <div>Hello</div>;
92
+ }
93
+ }
94
+
95
+ function Foo({condition}) {
96
+ return condition
97
+ ? <Stateful key="a" />
98
+ : <React.Fragment>
99
+ <Stateful key="a" />
100
+ <div key="b">World</div>
101
+ </React.Fragment>;
102
+ }
103
+
104
+ ReactNoop.render(<Foo condition={true} />);
105
+ ReactNoop.flush();
106
+
107
+ ReactNoop.render(<Foo condition={false} />);
108
+ ReactNoop.flush();
109
+
110
+ expect(ops).toEqual(['Update Stateful']);
111
+ expect(ReactNoop.getChildren()).toEqual([div(), div()]);
112
+
113
+ ReactNoop.render(<Foo condition={true} />);
114
+ ReactNoop.flush();
115
+
116
+ expect(ops).toEqual(['Update Stateful', 'Update Stateful']);
117
+ expect(ReactNoop.getChildren()).toEqual([div()]);
118
+ });
119
+
120
+ it('should preserve state between top-level fragments', function() {
121
+ var ops = [];
122
+
123
+ class Stateful extends React.Component {
124
+ componentDidUpdate() {
125
+ ops.push('Update Stateful');
126
+ }
127
+
128
+ render() {
129
+ return <div>Hello</div>;
130
+ }
131
+ }
132
+
133
+ function Foo({condition}) {
134
+ return condition
135
+ ? <React.Fragment>
136
+ <Stateful />
137
+ </React.Fragment>
138
+ : <React.Fragment>
139
+ <Stateful />
140
+ </React.Fragment>;
141
+ }
142
+
143
+ ReactNoop.render(<Foo condition={true} />);
144
+ ReactNoop.flush();
145
+
146
+ ReactNoop.render(<Foo condition={false} />);
147
+ ReactNoop.flush();
148
+
149
+ expect(ops).toEqual(['Update Stateful']);
150
+ expect(ReactNoop.getChildren()).toEqual([div()]);
151
+
152
+ ReactNoop.render(<Foo condition={true} />);
153
+ ReactNoop.flush();
154
+
155
+ expect(ops).toEqual(['Update Stateful', 'Update Stateful']);
156
+ expect(ReactNoop.getChildren()).toEqual([div()]);
157
+ });
158
+
159
+ it('should preserve state of children nested at same level', function() {
160
+ var ops = [];
161
+
162
+ class Stateful extends React.Component {
163
+ componentDidUpdate() {
164
+ ops.push('Update Stateful');
165
+ }
166
+
167
+ render() {
168
+ return <div>Hello</div>;
169
+ }
170
+ }
171
+
172
+ function Foo({condition}) {
173
+ return condition
174
+ ? <React.Fragment>
175
+ <React.Fragment>
176
+ <React.Fragment>
177
+ <Stateful key="a" />
178
+ </React.Fragment>
179
+ </React.Fragment>
180
+ </React.Fragment>
181
+ : <React.Fragment>
182
+ <React.Fragment>
183
+ <React.Fragment>
184
+ <div />
185
+ <Stateful key="a" />
186
+ </React.Fragment>
187
+ </React.Fragment>
188
+ </React.Fragment>;
189
+ }
190
+
191
+ ReactNoop.render(<Foo condition={true} />);
192
+ ReactNoop.flush();
193
+
194
+ ReactNoop.render(<Foo condition={false} />);
195
+ ReactNoop.flush();
196
+
197
+ expect(ops).toEqual(['Update Stateful']);
198
+ expect(ReactNoop.getChildren()).toEqual([div(), div()]);
199
+
200
+ ReactNoop.render(<Foo condition={true} />);
201
+ ReactNoop.flush();
202
+
203
+ expect(ops).toEqual(['Update Stateful', 'Update Stateful']);
204
+ expect(ReactNoop.getChildren()).toEqual([div()]);
205
+ });
206
+
207
+ it('should not preserve state in non-top-level fragment nesting', function() {
208
+ var ops = [];
209
+
210
+ class Stateful extends React.Component {
211
+ componentDidUpdate() {
212
+ ops.push('Update Stateful');
213
+ }
214
+
215
+ render() {
216
+ return <div>Hello</div>;
217
+ }
218
+ }
219
+
220
+ function Foo({condition}) {
221
+ return condition
222
+ ? <React.Fragment>
223
+ <React.Fragment><Stateful key="a" /></React.Fragment>
224
+ </React.Fragment>
225
+ : <React.Fragment><Stateful key="a" /></React.Fragment>;
226
+ }
227
+
228
+ ReactNoop.render(<Foo condition={true} />);
229
+ ReactNoop.flush();
230
+
231
+ ReactNoop.render(<Foo condition={false} />);
232
+ ReactNoop.flush();
233
+
234
+ expect(ops).toEqual([]);
235
+ expect(ReactNoop.getChildren()).toEqual([div()]);
236
+
237
+ ReactNoop.render(<Foo condition={true} />);
238
+ ReactNoop.flush();
239
+
240
+ expect(ops).toEqual([]);
241
+ expect(ReactNoop.getChildren()).toEqual([div()]);
242
+ });
243
+
244
+ it('should not preserve state of children if nested 2 levels without siblings', function() {
245
+ var ops = [];
246
+
247
+ class Stateful extends React.Component {
248
+ componentDidUpdate() {
249
+ ops.push('Update Stateful');
250
+ }
251
+
252
+ render() {
253
+ return <div>Hello</div>;
254
+ }
255
+ }
256
+
257
+ function Foo({condition}) {
258
+ return condition
259
+ ? <Stateful key="a" />
260
+ : <React.Fragment>
261
+ <React.Fragment>
262
+ <Stateful key="a" />
263
+ </React.Fragment>
264
+ </React.Fragment>;
265
+ }
266
+
267
+ ReactNoop.render(<Foo condition={true} />);
268
+ ReactNoop.flush();
269
+
270
+ ReactNoop.render(<Foo condition={false} />);
271
+ ReactNoop.flush();
272
+
273
+ expect(ops).toEqual([]);
274
+ expect(ReactNoop.getChildren()).toEqual([div()]);
275
+
276
+ ReactNoop.render(<Foo condition={true} />);
277
+ ReactNoop.flush();
278
+
279
+ expect(ops).toEqual([]);
280
+ expect(ReactNoop.getChildren()).toEqual([div()]);
281
+ });
282
+
283
+ it('should not preserve state of children if nested 2 levels with siblings', function() {
284
+ var ops = [];
285
+
286
+ class Stateful extends React.Component {
287
+ componentDidUpdate() {
288
+ ops.push('Update Stateful');
289
+ }
290
+
291
+ render() {
292
+ return <div>Hello</div>;
293
+ }
294
+ }
295
+
296
+ function Foo({condition}) {
297
+ return condition
298
+ ? <Stateful key="a" />
299
+ : <React.Fragment>
300
+ <React.Fragment>
301
+ <Stateful key="a" />
302
+ </React.Fragment>
303
+ <div />
304
+ </React.Fragment>;
305
+ }
306
+
307
+ ReactNoop.render(<Foo condition={true} />);
308
+ ReactNoop.flush();
309
+
310
+ ReactNoop.render(<Foo condition={false} />);
311
+ ReactNoop.flush();
312
+
313
+ expect(ops).toEqual([]);
314
+ expect(ReactNoop.getChildren()).toEqual([div(), div()]);
315
+
316
+ ReactNoop.render(<Foo condition={true} />);
317
+ ReactNoop.flush();
318
+
319
+ expect(ops).toEqual([]);
320
+ expect(ReactNoop.getChildren()).toEqual([div()]);
321
+ });
322
+
323
+ it('should preserve state between array nested in fragment and fragment', function() {
324
+ var ops = [];
325
+
326
+ class Stateful extends React.Component {
327
+ componentDidUpdate() {
328
+ ops.push('Update Stateful');
329
+ }
330
+
331
+ render() {
332
+ return <div>Hello</div>;
333
+ }
334
+ }
335
+
336
+ function Foo({condition}) {
337
+ return condition
338
+ ? <React.Fragment>
339
+ <Stateful key="a" />
340
+ </React.Fragment>
341
+ : <React.Fragment>
342
+ {[<Stateful key="a" />]}
343
+ </React.Fragment>;
344
+ }
345
+
346
+ ReactNoop.render(<Foo condition={true} />);
347
+ ReactNoop.flush();
348
+
349
+ ReactNoop.render(<Foo condition={false} />);
350
+ ReactNoop.flush();
351
+
352
+ expect(ops).toEqual(['Update Stateful']);
353
+ expect(ReactNoop.getChildren()).toEqual([div()]);
354
+
355
+ ReactNoop.render(<Foo condition={true} />);
356
+ ReactNoop.flush();
357
+
358
+ expect(ops).toEqual(['Update Stateful', 'Update Stateful']);
359
+ expect(ReactNoop.getChildren()).toEqual([div()]);
360
+ });
361
+
362
+ it('should preserve state between top level fragment and array', function() {
363
+ var ops = [];
364
+
365
+ class Stateful extends React.Component {
366
+ componentDidUpdate() {
367
+ ops.push('Update Stateful');
368
+ }
369
+
370
+ render() {
371
+ return <div>Hello</div>;
372
+ }
373
+ }
374
+
375
+ function Foo({condition}) {
376
+ return condition
377
+ ? [<Stateful key="a" />]
378
+ : <React.Fragment>
379
+ <Stateful key="a" />
380
+ </React.Fragment>;
381
+ }
382
+
383
+ ReactNoop.render(<Foo condition={true} />);
384
+ ReactNoop.flush();
385
+
386
+ ReactNoop.render(<Foo condition={false} />);
387
+ ReactNoop.flush();
388
+
389
+ expect(ops).toEqual(['Update Stateful']);
390
+ expect(ReactNoop.getChildren()).toEqual([div()]);
391
+
392
+ ReactNoop.render(<Foo condition={true} />);
393
+ ReactNoop.flush();
394
+
395
+ expect(ops).toEqual(['Update Stateful', 'Update Stateful']);
396
+ expect(ReactNoop.getChildren()).toEqual([div()]);
397
+ });
398
+
399
+ it('should not preserve state between array nested in fragment and double nested fragment', function() {
400
+ var ops = [];
401
+
402
+ class Stateful extends React.Component {
403
+ componentDidUpdate() {
404
+ ops.push('Update Stateful');
405
+ }
406
+
407
+ render() {
408
+ return <div>Hello</div>;
409
+ }
410
+ }
411
+
412
+ function Foo({condition}) {
413
+ return condition
414
+ ? <React.Fragment>{[<Stateful key="a" />]}</React.Fragment>
415
+ : <React.Fragment>
416
+ <React.Fragment><Stateful key="a" /></React.Fragment>
417
+ </React.Fragment>;
418
+ }
419
+
420
+ ReactNoop.render(<Foo condition={true} />);
421
+ ReactNoop.flush();
422
+
423
+ ReactNoop.render(<Foo condition={false} />);
424
+ ReactNoop.flush();
425
+
426
+ expect(ops).toEqual([]);
427
+ expect(ReactNoop.getChildren()).toEqual([div()]);
428
+
429
+ ReactNoop.render(<Foo condition={true} />);
430
+ ReactNoop.flush();
431
+
432
+ expect(ops).toEqual([]);
433
+ expect(ReactNoop.getChildren()).toEqual([div()]);
434
+ });
435
+
436
+ it('should not preserve state between array nested in fragment and double nested array', function() {
437
+ var ops = [];
438
+
439
+ class Stateful extends React.Component {
440
+ componentDidUpdate() {
441
+ ops.push('Update Stateful');
442
+ }
443
+
444
+ render() {
445
+ return <div>Hello</div>;
446
+ }
447
+ }
448
+
449
+ function Foo({condition}) {
450
+ return condition
451
+ ? <React.Fragment>{[<Stateful key="a" />]}</React.Fragment>
452
+ : [[<Stateful key="a" />]];
453
+ }
454
+
455
+ ReactNoop.render(<Foo condition={true} />);
456
+ ReactNoop.flush();
457
+
458
+ ReactNoop.render(<Foo condition={false} />);
459
+ ReactNoop.flush();
460
+
461
+ expect(ops).toEqual([]);
462
+ expect(ReactNoop.getChildren()).toEqual([div()]);
463
+
464
+ ReactNoop.render(<Foo condition={true} />);
465
+ ReactNoop.flush();
466
+
467
+ expect(ops).toEqual([]);
468
+ expect(ReactNoop.getChildren()).toEqual([div()]);
469
+ });
470
+
471
+ it('should preserve state between double nested fragment and double nested array', function() {
472
+ var ops = [];
473
+
474
+ class Stateful extends React.Component {
475
+ componentDidUpdate() {
476
+ ops.push('Update Stateful');
477
+ }
478
+
479
+ render() {
480
+ return <div>Hello</div>;
481
+ }
482
+ }
483
+
484
+ function Foo({condition}) {
485
+ return condition
486
+ ? <React.Fragment>
487
+ <React.Fragment><Stateful key="a" /></React.Fragment>
488
+ </React.Fragment>
489
+ : [[<Stateful key="a" />]];
490
+ }
491
+
492
+ ReactNoop.render(<Foo condition={true} />);
493
+ ReactNoop.flush();
494
+
495
+ ReactNoop.render(<Foo condition={false} />);
496
+ ReactNoop.flush();
497
+
498
+ expect(ops).toEqual(['Update Stateful']);
499
+ expect(ReactNoop.getChildren()).toEqual([div()]);
500
+
501
+ ReactNoop.render(<Foo condition={true} />);
502
+ ReactNoop.flush();
503
+
504
+ expect(ops).toEqual(['Update Stateful', 'Update Stateful']);
505
+ expect(ReactNoop.getChildren()).toEqual([div()]);
506
+ });
507
+
508
+ it('should not preserve state of children when the keys are different', function() {
509
+ var ops = [];
510
+
511
+ class Stateful extends React.Component {
512
+ componentDidUpdate() {
513
+ ops.push('Update Stateful');
514
+ }
515
+
516
+ render() {
517
+ return <div>Hello</div>;
518
+ }
519
+ }
520
+
521
+ function Foo({condition}) {
522
+ return condition
523
+ ? <React.Fragment key="a">
524
+ <Stateful />
525
+ </React.Fragment>
526
+ : <React.Fragment key="b">
527
+ <Stateful />
528
+ <span>World</span>
529
+ </React.Fragment>;
530
+ }
531
+
532
+ ReactNoop.render(<Foo condition={true} />);
533
+ ReactNoop.flush();
534
+
535
+ ReactNoop.render(<Foo condition={false} />);
536
+ ReactNoop.flush();
537
+
538
+ expect(ops).toEqual([]);
539
+ expect(ReactNoop.getChildren()).toEqual([div(), span()]);
540
+
541
+ ReactNoop.render(<Foo condition={true} />);
542
+ ReactNoop.flush();
543
+
544
+ expect(ops).toEqual([]);
545
+ expect(ReactNoop.getChildren()).toEqual([div()]);
546
+ });
547
+
548
+ it('should not preserve state between unkeyed and keyed fragment', function() {
549
+ var ops = [];
550
+
551
+ class Stateful extends React.Component {
552
+ componentDidUpdate() {
553
+ ops.push('Update Stateful');
554
+ }
555
+
556
+ render() {
557
+ return <div>Hello</div>;
558
+ }
559
+ }
560
+
561
+ function Foo({condition}) {
562
+ return condition
563
+ ? <React.Fragment key="a">
564
+ <Stateful />
565
+ </React.Fragment>
566
+ : <React.Fragment>
567
+ <Stateful />
568
+ </React.Fragment>;
569
+ }
570
+
571
+ ReactNoop.render(<Foo condition={true} />);
572
+ ReactNoop.flush();
573
+
574
+ ReactNoop.render(<Foo condition={false} />);
575
+ ReactNoop.flush();
576
+
577
+ expect(ops).toEqual([]);
578
+ expect(ReactNoop.getChildren()).toEqual([div()]);
579
+
580
+ ReactNoop.render(<Foo condition={true} />);
581
+ ReactNoop.flush();
582
+
583
+ expect(ops).toEqual([]);
584
+ expect(ReactNoop.getChildren()).toEqual([div()]);
585
+ });
586
+
587
+ it('should preserve state with reordering in multiple levels', function() {
588
+ var ops = [];
589
+
590
+ class Stateful extends React.Component {
591
+ componentDidUpdate() {
592
+ ops.push('Update Stateful');
593
+ }
594
+
595
+ render() {
596
+ return <div>Hello</div>;
597
+ }
598
+ }
599
+
600
+ function Foo({condition}) {
601
+ return condition
602
+ ? <div>
603
+ <React.Fragment key="c">
604
+ <span>foo</span>
605
+ <div key="b">
606
+ <Stateful key="a" />
607
+ </div>
608
+ </React.Fragment>
609
+ <span>boop</span>
610
+ </div>
611
+ : <div>
612
+ <span>beep</span>
613
+ <React.Fragment key="c">
614
+ <div key="b">
615
+ <Stateful key="a" />
616
+ </div>
617
+ <span>bar</span>
618
+ </React.Fragment>
619
+ </div>;
620
+ }
621
+
622
+ ReactNoop.render(<Foo condition={true} />);
623
+ ReactNoop.flush();
624
+
625
+ ReactNoop.render(<Foo condition={false} />);
626
+ ReactNoop.flush();
627
+
628
+ expect(ops).toEqual(['Update Stateful']);
629
+ expect(ReactNoop.getChildren()).toEqual([div(span(), div(div()), span())]);
630
+
631
+ ReactNoop.render(<Foo condition={true} />);
632
+ ReactNoop.flush();
633
+
634
+ expect(ops).toEqual(['Update Stateful', 'Update Stateful']);
635
+ expect(ReactNoop.getChildren()).toEqual([div(span(), div(div()), span())]);
636
+ });
637
+
638
+ it('should not preserve state when switching to a keyed fragment to an array', function() {
639
+ spyOn(console, 'error');
640
+ var ops = [];
641
+
642
+ class Stateful extends React.Component {
643
+ componentDidUpdate() {
644
+ ops.push('Update Stateful');
645
+ }
646
+
647
+ render() {
648
+ return <div>Hello</div>;
649
+ }
650
+ }
651
+
652
+ function Foo({condition}) {
653
+ return condition
654
+ ? <div>
655
+ {<React.Fragment key="foo"><Stateful /></React.Fragment>}<span />
656
+ </div>
657
+ : <div>{[<Stateful />]}<span /></div>;
658
+ }
659
+
660
+ ReactNoop.render(<Foo condition={true} />);
661
+ ReactNoop.flush();
662
+
663
+ ReactNoop.render(<Foo condition={false} />);
664
+ ReactNoop.flush();
665
+
666
+ expect(ops).toEqual([]);
667
+ expect(ReactNoop.getChildren()).toEqual([div(div(), span())]);
668
+
669
+ ReactNoop.render(<Foo condition={true} />);
670
+ ReactNoop.flush();
671
+
672
+ expect(ops).toEqual([]);
673
+ expect(ReactNoop.getChildren()).toEqual([div(div(), span())]);
674
+ expectDev(console.error.calls.count()).toBe(1);
675
+ expectDev(console.error.calls.argsFor(0)[0]).toContain(
676
+ 'Each child in an array or iterator should have a unique "key" prop.',
677
+ );
678
+ });
679
+
680
+ it('should preserve state when it does not change positions', function() {
681
+ spyOn(console, 'error');
682
+ var ops = [];
683
+
684
+ class Stateful extends React.Component {
685
+ componentDidUpdate() {
686
+ ops.push('Update Stateful');
687
+ }
688
+
689
+ render() {
690
+ return <div>Hello</div>;
691
+ }
692
+ }
693
+
694
+ function Foo({condition}) {
695
+ return condition
696
+ ? [<span />, <React.Fragment><Stateful /></React.Fragment>]
697
+ : [<span />, <React.Fragment><Stateful /></React.Fragment>];
698
+ }
699
+
700
+ ReactNoop.render(<Foo condition={true} />);
701
+ ReactNoop.flush();
702
+
703
+ ReactNoop.render(<Foo condition={false} />);
704
+ ReactNoop.flush();
705
+
706
+ expect(ops).toEqual(['Update Stateful']);
707
+ expect(ReactNoop.getChildren()).toEqual([span(), div()]);
708
+
709
+ ReactNoop.render(<Foo condition={true} />);
710
+ ReactNoop.flush();
711
+
712
+ expect(ops).toEqual(['Update Stateful', 'Update Stateful']);
713
+ expect(ReactNoop.getChildren()).toEqual([span(), div()]);
714
+ expectDev(console.error.calls.count()).toBe(3);
715
+ for (let errorIndex = 0; errorIndex < 3; ++errorIndex) {
716
+ expectDev(console.error.calls.argsFor(errorIndex)[0]).toContain(
717
+ 'Each child in an array or iterator should have a unique "key" prop.',
718
+ );
719
+ }
720
+ });
721
+});