Make ReactComponentTreeDevtool work with React Native
Dan Abramov committed
Apr 27, 2016 at 22:12 UTC
b7f6a642f61c3071b77288309548cdedab431b3b
6 files changed
+1754
-2
src/isomorphic/devtools/__tests__/ReactComponentTreeDevtool-test.js
+4
-2
@@ -93,6 +93,8 @@ describe('ReactComponentTreeDevtool', () => {
93
return getTree(rootInstance._debugID, options).children[0];
94
}
95
96
+ // Mount once, render updates, then unmount.
97
+ // Ensure the tree is correct on every step.
98
pairs.forEach(([element, expectedTree]) => {
99
currentElement = element;
100
ReactDOM.render(<Wrapper />, node);
@@ -101,17 +103,17 @@ describe('ReactComponentTreeDevtool', () => {
103
expect(getActualTree()).toEqual(expectedTree);
104
});
105
ReactDOM.unmountComponentAtNode(node);
104
-
106
ReactComponentTreeDevtool.purgeUnmountedComponents();
107
expect(getActualTree()).toBe(undefined);
108
expect(getRootDisplayNames()).toEqual([]);
109
expect(getRegisteredDisplayNames()).toEqual([]);
110
111
+ // Server render every pair.
112
+ // Ensure the tree is correct on every step.
113
pairs.forEach(([element, expectedTree]) => {
114
currentElement = element;
115
ReactDOMServer.renderToString(<Wrapper />);
116
expect(getActualTree()).toEqual(expectedTree);
114
-
117
ReactComponentTreeDevtool.purgeUnmountedComponents();
118
expect(getActualTree()).toBe(undefined);
119
expect(getRootDisplayNames()).toEqual([]);
src/isomorphic/devtools/__tests__/ReactComponentTreeDevtool-test.native.js
new
+1705
@@ -0,0 +1,1705 @@
1
+/**
2
+ * Copyright 2016-present, 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
+describe('ReactComponentTreeDevtool', () => {
15
+ var React;
16
+ var ReactNative;
17
+ var ReactInstanceMap;
18
+ var ReactComponentTreeDevtool;
19
+ var createReactNativeComponentClass;
20
+ var View;
21
+ var Image;
22
+ var Text;
23
+
24
+ beforeEach(() => {
25
+ jest.resetModuleRegistry();
26
+
27
+ React = require('React');
28
+ ReactNative = require('ReactNative');
29
+ ReactInstanceMap = require('ReactInstanceMap');
30
+ ReactComponentTreeDevtool = require('ReactComponentTreeDevtool');
31
+ View = require('View');
32
+ createReactNativeComponentClass = require('createReactNativeComponentClass');
33
+ Image = createReactNativeComponentClass({
34
+ validAttributes: {},
35
+ uiViewClassName: 'Image',
36
+ });
37
+ var RCText = createReactNativeComponentClass({
38
+ validAttributes: {},
39
+ uiViewClassName: 'RCText',
40
+ });
41
+ Text = React.createClass({
42
+ childContextTypes: {
43
+ isInAParentText: React.PropTypes.bool,
44
+ },
45
+ getChildContext() {
46
+ return {isInAParentText: true};
47
+ },
48
+ render() {
49
+ return <RCText {...this.props} />;
50
+ },
51
+ });
52
+ });
53
+
54
+ function getRootDisplayNames() {
55
+ return ReactComponentTreeDevtool.getRootIDs()
56
+ .map(ReactComponentTreeDevtool.getDisplayName);
57
+ }
58
+
59
+ function getRegisteredDisplayNames() {
60
+ return ReactComponentTreeDevtool.getRegisteredIDs()
61
+ .map(ReactComponentTreeDevtool.getDisplayName);
62
+ }
63
+
64
+ function getTree(rootID, options = {}) {
65
+ var {
66
+ includeOwnerDisplayName = false,
67
+ includeParentDisplayName = false,
68
+ expectedParentID = null,
69
+ } = options;
70
+
71
+ var result = {
72
+ displayName: ReactComponentTreeDevtool.getDisplayName(rootID),
73
+ };
74
+
75
+ var ownerID = ReactComponentTreeDevtool.getOwnerID(rootID);
76
+ var parentID = ReactComponentTreeDevtool.getParentID(rootID);
77
+ expect(parentID).toBe(expectedParentID);
78
+
79
+ if (includeParentDisplayName && parentID) {
80
+ result.parentDisplayName = ReactComponentTreeDevtool.getDisplayName(parentID);
81
+ }
82
+ if (includeOwnerDisplayName && ownerID) {
83
+ result.ownerDisplayName = ReactComponentTreeDevtool.getDisplayName(ownerID);
84
+ }
85
+
86
+ var childIDs = ReactComponentTreeDevtool.getChildIDs(rootID);
87
+ var text = ReactComponentTreeDevtool.getText(rootID);
88
+ if (text != null) {
89
+ result.text = text;
90
+ } else {
91
+ result.children = childIDs.map(childID =>
92
+ getTree(childID, {...options, expectedParentID: rootID })
93
+ );
94
+ }
95
+
96
+ return result;
97
+ }
98
+
99
+ function assertTreeMatches(pairs, options) {
100
+ if (!Array.isArray(pairs[0])) {
101
+ pairs = [pairs];
102
+ }
103
+
104
+ var currentElement;
105
+ var rootInstance;
106
+
107
+ class Wrapper extends React.Component {
108
+ render() {
109
+ rootInstance = ReactInstanceMap.get(this);
110
+ return currentElement;
111
+ }
112
+ }
113
+
114
+ function getActualTree() {
115
+ return getTree(rootInstance._debugID, options).children[0];
116
+ }
117
+
118
+ // Mount once, render updates, then unmount.
119
+ // Ensure the tree is correct on every step.
120
+ pairs.forEach(([element, expectedTree]) => {
121
+ currentElement = element;
122
+ ReactNative.render(<Wrapper />, 1);
123
+ expect(getActualTree()).toEqual(expectedTree);
124
+ ReactComponentTreeDevtool.purgeUnmountedComponents();
125
+ expect(getActualTree()).toEqual(expectedTree);
126
+ });
127
+ ReactNative.unmountComponentAtNode(1);
128
+ ReactComponentTreeDevtool.purgeUnmountedComponents();
129
+ expect(getActualTree()).toBe(undefined);
130
+ expect(getRootDisplayNames()).toEqual([]);
131
+ expect(getRegisteredDisplayNames()).toEqual([]);
132
+
133
+ // Mount and unmount for every pair.
134
+ // Ensure the tree is correct on every step.
135
+ pairs.forEach(([element, expectedTree]) => {
136
+ currentElement = element;
137
+ ReactNative.render(<Wrapper />, 1);
138
+ ReactNative.unmountComponentAtNode(1);
139
+ expect(getActualTree()).toEqual(expectedTree);
140
+ ReactComponentTreeDevtool.purgeUnmountedComponents();
141
+ expect(getActualTree()).toBe(undefined);
142
+ expect(getRootDisplayNames()).toEqual([]);
143
+ expect(getRegisteredDisplayNames()).toEqual([]);
144
+ });
145
+ }
146
+
147
+ describe('mount', () => {
148
+ it('uses displayName or Unknown for classic components', () => {
149
+ var Foo = React.createClass({
150
+ render() {
151
+ return null;
152
+ },
153
+ });
154
+ Foo.displayName = 'Bar';
155
+ var Baz = React.createClass({
156
+ render() {
157
+ return null;
158
+ },
159
+ });
160
+ var Qux = React.createClass({
161
+ render() {
162
+ return null;
163
+ },
164
+ });
165
+ delete Qux.displayName;
166
+
167
+ var element = <View><Foo /><Baz /><Qux /></View>;
168
+ var tree = {
169
+ displayName: 'View',
170
+ children: [{
171
+ displayName: 'Bar',
172
+ children: [],
173
+ }, {
174
+ displayName: 'Baz',
175
+ children: [],
176
+ }, {
177
+ displayName: 'Unknown',
178
+ children: [],
179
+ }],
180
+ };
181
+ assertTreeMatches([element, tree]);
182
+ });
183
+
184
+ it('uses displayName, name, or ReactComponent for modern components', () => {
185
+ class Foo extends React.Component {
186
+ render() {
187
+ return null;
188
+ }
189
+ }
190
+ Foo.displayName = 'Bar';
191
+ class Baz extends React.Component {
192
+ render() {
193
+ return null;
194
+ }
195
+ }
196
+ class Qux extends React.Component {
197
+ render() {
198
+ return null;
199
+ }
200
+ }
201
+ delete Qux.name;
202
+
203
+ var element = <View><Foo /><Baz /><Qux /></View>;
204
+ var tree = {
205
+ displayName: 'View',
206
+ children: [{
207
+ displayName: 'Bar',
208
+ children: [],
209
+ }, {
210
+ displayName: 'Baz',
211
+ children: [],
212
+ }, {
213
+ // Note: Ideally fallback name should be consistent (e.g. "Unknown")
214
+ displayName: 'ReactComponent',
215
+ children: [],
216
+ }],
217
+ };
218
+ assertTreeMatches([element, tree]);
219
+ });
220
+
221
+ it('uses displayName, name, or Object for factory components', () => {
222
+ function Foo() {
223
+ return {
224
+ render() {
225
+ return null;
226
+ },
227
+ };
228
+ }
229
+ Foo.displayName = 'Bar';
230
+ function Baz() {
231
+ return {
232
+ render() {
233
+ return null;
234
+ },
235
+ };
236
+ }
237
+ function Qux() {
238
+ return {
239
+ render() {
240
+ return null;
241
+ },
242
+ };
243
+ }
244
+ delete Qux.name;
245
+
246
+ var element = <View><Foo /><Baz /><Qux /></View>;
247
+ var tree = {
248
+ displayName: 'View',
249
+ children: [{
250
+ displayName: 'Bar',
251
+ children: [],
252
+ }, {
253
+ displayName: 'Baz',
254
+ children: [],
255
+ }, {
256
+ displayName: 'Unknown',
257
+ children: [],
258
+ }],
259
+ };
260
+ assertTreeMatches([element, tree]);
261
+ });
262
+
263
+ it('uses displayName, name, or StatelessComponent for functional components', () => {
264
+ function Foo() {
265
+ return null;
266
+ }
267
+ Foo.displayName = 'Bar';
268
+ function Baz() {
269
+ return null;
270
+ }
271
+ function Qux() {
272
+ return null;
273
+ }
274
+ delete Qux.name;
275
+
276
+ var element = <View><Foo /><Baz /><Qux /></View>;
277
+ var tree = {
278
+ displayName: 'View',
279
+ children: [{
280
+ displayName: 'Bar',
281
+ children: [],
282
+ }, {
283
+ displayName: 'Baz',
284
+ children: [],
285
+ }, {
286
+ displayName: 'Unknown',
287
+ children: [],
288
+ }],
289
+ };
290
+ assertTreeMatches([element, tree]);
291
+ });
292
+
293
+ it('reports a native tree correctly', () => {
294
+ var element = (
295
+ <View>
296
+ <View>
297
+ <Text>
298
+ Hi!
299
+ </Text>
300
+ </View>
301
+ <Image />
302
+ </View>
303
+ );
304
+ var tree = {
305
+ displayName: 'View',
306
+ children: [{
307
+ displayName: 'View',
308
+ children: [{
309
+ displayName: 'Text',
310
+ children: [{
311
+ displayName: 'RCText',
312
+ children: [{
313
+ displayName: '#text',
314
+ text: 'Hi!',
315
+ }],
316
+ }],
317
+ }],
318
+ }, {
319
+ displayName: 'Image',
320
+ children: [],
321
+ }],
322
+ };
323
+ assertTreeMatches([element, tree]);
324
+ });
325
+
326
+ it('reports a simple tree with composites correctly', () => {
327
+ class Foo extends React.Component {
328
+ render() {
329
+ return <Image />;
330
+ }
331
+ }
332
+
333
+ var element = <Foo />;
334
+ var tree = {
335
+ displayName: 'Foo',
336
+ children: [{
337
+ displayName: 'Image',
338
+ children: [],
339
+ }],
340
+ };
341
+ assertTreeMatches([element, tree]);
342
+ });
343
+
344
+ it('reports a tree with composites correctly', () => {
345
+ var Qux = React.createClass({
346
+ render() {
347
+ return null;
348
+ },
349
+ });
350
+ function Foo() {
351
+ return {
352
+ render() {
353
+ return <Qux />;
354
+ },
355
+ };
356
+ }
357
+ function Bar({children}) {
358
+ return <View>{children}</View>;
359
+ }
360
+ class Baz extends React.Component {
361
+ render() {
362
+ return (
363
+ <View>
364
+ <Foo />
365
+ <Bar>
366
+ <Text>Hi,</Text>
367
+ </Bar>
368
+ <Image />
369
+ </View>
370
+ );
371
+ }
372
+ }
373
+
374
+ var element = <Baz />;
375
+ var tree = {
376
+ displayName: 'Baz',
377
+ children: [{
378
+ displayName: 'View',
379
+ children: [{
380
+ displayName: 'Foo',
381
+ children: [{
382
+ displayName: 'Qux',
383
+ children: [],
384
+ }],
385
+ }, {
386
+ displayName: 'Bar',
387
+ children: [{
388
+ displayName: 'View',
389
+ children: [{
390
+ displayName: 'Text',
391
+ children: [{
392
+ displayName: 'RCText',
393
+ children: [{
394
+ displayName: '#text',
395
+ text: 'Hi,',
396
+ }],
397
+ }],
398
+ }],
399
+ }],
400
+ }, {
401
+ displayName: 'Image',
402
+ children: [],
403
+ }],
404
+ }],
405
+ };
406
+ assertTreeMatches([element, tree]);
407
+ });
408
+
409
+ it('ignores null children', () => {
410
+ class Foo extends React.Component {
411
+ render() {
412
+ return null;
413
+ }
414
+ }
415
+ var element = <Foo />;
416
+ var tree = {
417
+ displayName: 'Foo',
418
+ children: [],
419
+ };
420
+ assertTreeMatches([element, tree]);
421
+ });
422
+
423
+ it('ignores false children', () => {
424
+ class Foo extends React.Component {
425
+ render() {
426
+ return false;
427
+ }
428
+ }
429
+ var element = <Foo />;
430
+ var tree = {
431
+ displayName: 'Foo',
432
+ children: [],
433
+ };
434
+ assertTreeMatches([element, tree]);
435
+ });
436
+
437
+ it('reports text nodes as children', () => {
438
+ var element = <Text>{'1'}{2}</Text>;
439
+ var tree = {
440
+ displayName: 'Text',
441
+ children: [{
442
+ displayName: 'RCText',
443
+ children: [{
444
+ displayName: '#text',
445
+ text: '1',
446
+ }, {
447
+ displayName: '#text',
448
+ text: '2',
449
+ }],
450
+ }],
451
+ };
452
+ assertTreeMatches([element, tree]);
453
+ });
454
+
455
+ it('reports a single text node as a child', () => {
456
+ var element = <Text>{'1'}</Text>;
457
+ var tree = {
458
+ displayName: 'Text',
459
+ children: [{
460
+ displayName: 'RCText',
461
+ children: [{
462
+ displayName: '#text',
463
+ text: '1',
464
+ }],
465
+ }],
466
+ };
467
+ assertTreeMatches([element, tree]);
468
+ });
469
+
470
+ it('reports a single number node as a child', () => {
471
+ var element = <Text>{42}</Text>;
472
+ var tree = {
473
+ displayName: 'Text',
474
+ children: [{
475
+ displayName: 'RCText',
476
+ children: [{
477
+ displayName: '#text',
478
+ text: '42',
479
+ }],
480
+ }],
481
+ };
482
+ assertTreeMatches([element, tree]);
483
+ });
484
+
485
+ it('reports a zero as a child', () => {
486
+ var element = <Text>{0}</Text>;
487
+ var tree = {
488
+ displayName: 'Text',
489
+ children: [{
490
+ displayName: 'RCText',
491
+ children: [{
492
+ displayName: '#text',
493
+ text: '0',
494
+ }],
495
+ }],
496
+ };
497
+ assertTreeMatches([element, tree]);
498
+ });
499
+
500
+ it('skips empty nodes for multiple children', () => {
501
+ function Foo() {
502
+ return <Image />;
503
+ }
504
+ var element = (
505
+ <View>
506
+ {false}
507
+ <Foo />
508
+ {null}
509
+ <Foo />
510
+ </View>
511
+ );
512
+ var tree = {
513
+ displayName: 'View',
514
+ children: [{
515
+ displayName: 'Foo',
516
+ children: [{
517
+ displayName: 'Image',
518
+ children: [],
519
+ }],
520
+ }, {
521
+ displayName: 'Foo',
522
+ children: [{
523
+ displayName: 'Image',
524
+ children: [],
525
+ }],
526
+ }],
527
+ };
528
+ assertTreeMatches([element, tree]);
529
+ });
530
+ });
531
+
532
+ describe('update', () => {
533
+ describe('native component', () => {
534
+ it('updates text of a single text child', () => {
535
+ var elementBefore = <Text>Hi.</Text>;
536
+ var treeBefore = {
537
+ displayName: 'Text',
538
+ children: [{
539
+ displayName: 'RCText',
540
+ children: [{
541
+ displayName: '#text',
542
+ text: 'Hi.',
543
+ }],
544
+ }],
545
+ };
546
+
547
+ var elementAfter = <Text>Bye.</Text>;
548
+ var treeAfter = {
549
+ displayName: 'Text',
550
+ children: [{
551
+ displayName: 'RCText',
552
+ children: [{
553
+ displayName: '#text',
554
+ text: 'Bye.',
555
+ }],
556
+ }],
557
+ };
558
+
559
+ assertTreeMatches([
560
+ [elementBefore, treeBefore],
561
+ [elementAfter, treeAfter],
562
+ ]);
563
+ });
564
+
565
+ it('updates from no children to a single text child', () => {
566
+ var elementBefore = <Text />;
567
+ var treeBefore = {
568
+ displayName: 'Text',
569
+ children: [{
570
+ displayName: 'RCText',
571
+ children: [],
572
+ }],
573
+ };
574
+
575
+ var elementAfter = <Text>Hi.</Text>;
576
+ var treeAfter = {
577
+ displayName: 'Text',
578
+ children: [{
579
+ displayName: 'RCText',
580
+ children: [{
581
+ displayName: '#text',
582
+ text: 'Hi.',
583
+ }],
584
+ }],
585
+ };
586
+
587
+ assertTreeMatches([
588
+ [elementBefore, treeBefore],
589
+ [elementAfter, treeAfter],
590
+ ]);
591
+ });
592
+
593
+ it('updates from a single text child to no children', () => {
594
+ var elementBefore = <Text>Hi.</Text>;
595
+ var treeBefore = {
596
+ displayName: 'Text',
597
+ children: [{
598
+ displayName: 'RCText',
599
+ children: [{
600
+ displayName: '#text',
601
+ text: 'Hi.',
602
+ }],
603
+ }],
604
+ };
605
+
606
+ var elementAfter = <Text />;
607
+ var treeAfter = {
608
+ displayName: 'Text',
609
+ children: [{
610
+ displayName: 'RCText',
611
+ children: [],
612
+ }],
613
+ };
614
+
615
+ assertTreeMatches([
616
+ [elementBefore, treeBefore],
617
+ [elementAfter, treeAfter],
618
+ ]);
619
+ });
620
+
621
+ it('updates from no children to multiple text children', () => {
622
+ var elementBefore = <Text />;
623
+ var treeBefore = {
624
+ displayName: 'Text',
625
+ children: [{
626
+ displayName: 'RCText',
627
+ children: [],
628
+ }],
629
+ };
630
+
631
+ var elementAfter = <Text>{'Hi.'}{'Bye.'}</Text>;
632
+ var treeAfter = {
633
+ displayName: 'Text',
634
+ children: [{
635
+ displayName: 'RCText',
636
+ children: [{
637
+ displayName: '#text',
638
+ text: 'Hi.',
639
+ }, {
640
+ displayName: '#text',
641
+ text: 'Bye.',
642
+ }],
643
+ }],
644
+ };
645
+
646
+ assertTreeMatches([
647
+ [elementBefore, treeBefore],
648
+ [elementAfter, treeAfter],
649
+ ]);
650
+ });
651
+
652
+ it('updates from multiple text children to no children', () => {
653
+ var elementBefore = <Text>{'Hi.'}{'Bye.'}</Text>;
654
+ var treeBefore = {
655
+ displayName: 'Text',
656
+ children: [{
657
+ displayName: 'RCText',
658
+ children: [{
659
+ displayName: '#text',
660
+ text: 'Hi.',
661
+ }, {
662
+ displayName: '#text',
663
+ text: 'Bye.',
664
+ }],
665
+ }],
666
+ };
667
+
668
+ var elementAfter = <Text />;
669
+ var treeAfter = {
670
+ displayName: 'Text',
671
+ children: [{
672
+ displayName: 'RCText',
673
+ children: [],
674
+ }],
675
+ };
676
+ assertTreeMatches([
677
+ [elementBefore, treeBefore],
678
+ [elementAfter, treeAfter],
679
+ ]);
680
+ });
681
+
682
+ it('updates from one text child to multiple text children', () => {
683
+ var elementBefore = <Text>Hi.</Text>;
684
+ var treeBefore = {
685
+ displayName: 'Text',
686
+ children: [{
687
+ displayName: 'RCText',
688
+ children: [{
689
+ displayName: '#text',
690
+ text: 'Hi.',
691
+ }],
692
+ }],
693
+ };
694
+
695
+ var elementAfter = <Text>{'Hi.'}{'Bye.'}</Text>;
696
+ var treeAfter = {
697
+ displayName: 'Text',
698
+ children: [{
699
+ displayName: 'RCText',
700
+ children: [{
701
+ displayName: '#text',
702
+ text: 'Hi.',
703
+ }, {
704
+ displayName: '#text',
705
+ text: 'Bye.',
706
+ }],
707
+ }],
708
+ };
709
+
710
+ assertTreeMatches([
711
+ [elementBefore, treeBefore],
712
+ [elementAfter, treeAfter],
713
+ ]);
714
+ });
715
+
716
+ it('updates from multiple text children to one text child', () => {
717
+ var elementBefore = <Text>{'Hi.'}{'Bye.'}</Text>;
718
+ var treeBefore = {
719
+ displayName: 'Text',
720
+ children: [{
721
+ displayName: 'RCText',
722
+ children: [{
723
+ displayName: '#text',
724
+ text: 'Hi.',
725
+ }, {
726
+ displayName: '#text',
727
+ text: 'Bye.',
728
+ }],
729
+ }],
730
+ };
731
+
732
+ var elementAfter = <Text>Hi.</Text>;
733
+ var treeAfter = {
734
+ displayName: 'Text',
735
+ children: [{
736
+ displayName: 'RCText',
737
+ children: [{
738
+ displayName: '#text',
739
+ text: 'Hi.',
740
+ }],
741
+ }],
742
+ };
743
+ assertTreeMatches([
744
+ [elementBefore, treeBefore],
745
+ [elementAfter, treeAfter],
746
+ ]);
747
+ });
748
+
749
+ it('updates text nodes when reordering', () => {
750
+ var elementBefore = <Text>{'Hi.'}{'Bye.'}</Text>;
751
+ var treeBefore = {
752
+ displayName: 'Text',
753
+ children: [{
754
+ displayName: 'RCText',
755
+ children: [{
756
+ displayName: '#text',
757
+ text: 'Hi.',
758
+ }, {
759
+ displayName: '#text',
760
+ text: 'Bye.',
761
+ }],
762
+ }],
763
+ };
764
+
765
+ var elementAfter = <Text>{'Bye.'}{'Hi.'}</Text>;
766
+ var treeAfter = {
767
+ displayName: 'Text',
768
+ children: [{
769
+ displayName: 'RCText',
770
+ children: [{
771
+ displayName: '#text',
772
+ text: 'Bye.',
773
+ }, {
774
+ displayName: '#text',
775
+ text: 'Hi.',
776
+ }],
777
+ }],
778
+ };
779
+ assertTreeMatches([
780
+ [elementBefore, treeBefore],
781
+ [elementAfter, treeAfter],
782
+ ]);
783
+ });
784
+
785
+ it('updates native nodes when reordering with keys', () => {
786
+ var elementBefore = (
787
+ <View>
788
+ <Text key="a">Hi.</Text>
789
+ <Text key="b">Bye.</Text>
790
+ </View>
791
+ );
792
+ var treeBefore = {
793
+ displayName: 'View',
794
+ children: [{
795
+ displayName: 'Text',
796
+ children: [{
797
+ displayName: 'RCText',
798
+ children: [{
799
+ displayName: '#text',
800
+ text: 'Hi.',
801
+ }],
802
+ }],
803
+ }, {
804
+ displayName: 'Text',
805
+ children: [{
806
+ displayName: 'RCText',
807
+ children: [{
808
+ displayName: '#text',
809
+ text: 'Bye.',
810
+ }],
811
+ }],
812
+ }],
813
+ };
814
+
815
+ var elementAfter = (
816
+ <View>
817
+ <Text key="b">Bye.</Text>
818
+ <Text key="a">Hi.</Text>
819
+ </View>
820
+ );
821
+ var treeAfter = {
822
+ displayName: 'View',
823
+ children: [{
824
+ displayName: 'Text',
825
+ children: [{
826
+ displayName: 'RCText',
827
+ children: [{
828
+ displayName: '#text',
829
+ text: 'Bye.',
830
+ }],
831
+ }],
832
+ }, {
833
+ displayName: 'Text',
834
+ children: [{
835
+ displayName: 'RCText',
836
+ children: [{
837
+ displayName: '#text',
838
+ text: 'Hi.',
839
+ }],
840
+ }],
841
+ }],
842
+ };
843
+
844
+ assertTreeMatches([
845
+ [elementBefore, treeBefore],
846
+ [elementAfter, treeAfter],
847
+ ]);
848
+ });
849
+
850
+ it('updates native nodes when reordering with keys', () => {
851
+ var elementBefore = (
852
+ <View>
853
+ <Text>Hi.</Text>
854
+ <Text>Bye.</Text>
855
+ </View>
856
+ );
857
+ var treeBefore = {
858
+ displayName: 'View',
859
+ children: [{
860
+ displayName: 'Text',
861
+ children: [{
862
+ displayName: 'RCText',
863
+ children: [{
864
+ displayName: '#text',
865
+ text: 'Hi.',
866
+ }],
867
+ }],
868
+ }, {
869
+ displayName: 'Text',
870
+ children: [{
871
+ displayName: 'RCText',
872
+ children: [{
873
+ displayName: '#text',
874
+ text: 'Bye.',
875
+ }],
876
+ }],
877
+ }],
878
+ };
879
+
880
+ var elementAfter = (
881
+ <View>
882
+ <Text>Bye.</Text>
883
+ <Text>Hi.</Text>
884
+ </View>
885
+ );
886
+ var treeAfter = {
887
+ displayName: 'View',
888
+ children: [{
889
+ displayName: 'Text',
890
+ children: [{
891
+ displayName: 'RCText',
892
+ children: [{
893
+ displayName: '#text',
894
+ text: 'Bye.',
895
+ }],
896
+ }],
897
+ }, {
898
+ displayName: 'Text',
899
+ children: [{
900
+ displayName: 'RCText',
901
+ children: [{
902
+ displayName: '#text',
903
+ text: 'Hi.',
904
+ }],
905
+ }],
906
+ }],
907
+ };
908
+
909
+ assertTreeMatches([
910
+ [elementBefore, treeBefore],
911
+ [elementAfter, treeAfter],
912
+ ]);
913
+ });
914
+
915
+ it('updates a single composite child of a different type', () => {
916
+ function Foo() {
917
+ return null;
918
+ }
919
+
920
+ function Bar() {
921
+ return null;
922
+ }
923
+
924
+ var elementBefore = <View><Foo /></View>;
925
+ var treeBefore = {
926
+ displayName: 'View',
927
+ children: [{
928
+ displayName: 'Foo',
929
+ children: [],
930
+ }],
931
+ };
932
+
933
+ var elementAfter = <View><Bar /></View>;
934
+ var treeAfter = {
935
+ displayName: 'View',
936
+ children: [{
937
+ displayName: 'Bar',
938
+ children: [],
939
+ }],
940
+ };
941
+
942
+ assertTreeMatches([
943
+ [elementBefore, treeBefore],
944
+ [elementAfter, treeAfter],
945
+ ]);
946
+ });
947
+
948
+ it('updates a single composite child of the same type', () => {
949
+ function Foo({ children }) {
950
+ return children;
951
+ }
952
+
953
+ var elementBefore = <View><Foo><View /></Foo></View>;
954
+ var treeBefore = {
955
+ displayName: 'View',
956
+ children: [{
957
+ displayName: 'Foo',
958
+ children: [{
959
+ displayName: 'View',
960
+ children: [],
961
+ }],
962
+ }],
963
+ };
964
+
965
+ var elementAfter = <View><Foo><Image /></Foo></View>;
966
+ var treeAfter = {
967
+ displayName: 'View',
968
+ children: [{
969
+ displayName: 'Foo',
970
+ children: [{
971
+ displayName: 'Image',
972
+ children: [],
973
+ }],
974
+ }],
975
+ };
976
+
977
+ assertTreeMatches([
978
+ [elementBefore, treeBefore],
979
+ [elementAfter, treeAfter],
980
+ ]);
981
+ });
982
+
983
+ it('updates from no children to a single composite child', () => {
984
+ function Foo() {
985
+ return null;
986
+ }
987
+
988
+ var elementBefore = <View />;
989
+ var treeBefore = {
990
+ displayName: 'View',
991
+ children: [],
992
+ };
993
+
994
+ var elementAfter = <View><Foo /></View>;
995
+ var treeAfter = {
996
+ displayName: 'View',
997
+ children: [{
998
+ displayName: 'Foo',
999
+ children: [],
1000
+ }],
1001
+ };
1002
+
1003
+ assertTreeMatches([
1004
+ [elementBefore, treeBefore],
1005
+ [elementAfter, treeAfter],
1006
+ ]);
1007
+ });
1008
+
1009
+ it('updates from a single composite child to no children', () => {
1010
+ function Foo() {
1011
+ return null;
1012
+ }
1013
+
1014
+ var elementBefore = <View><Foo /></View>;
1015
+ var treeBefore = {
1016
+ displayName: 'View',
1017
+ children: [{
1018
+ displayName: 'Foo',
1019
+ children: [],
1020
+ }],
1021
+ };
1022
+
1023
+ var elementAfter = <View />;
1024
+ var treeAfter = {
1025
+ displayName: 'View',
1026
+ children: [],
1027
+ };
1028
+
1029
+ assertTreeMatches([
1030
+ [elementBefore, treeBefore],
1031
+ [elementAfter, treeAfter],
1032
+ ]);
1033
+ });
1034
+
1035
+ it('updates mixed children', () => {
1036
+ function Foo() {
1037
+ return <View />;
1038
+ }
1039
+ var element1 = (
1040
+ <View>
1041
+ <Text>hi</Text>
1042
+ {false}
1043
+ <Text>{42}</Text>
1044
+ {null}
1045
+ <Foo />
1046
+ </View>
1047
+ );
1048
+ var tree1 = {
1049
+ displayName: 'View',
1050
+ children: [{
1051
+ displayName: 'Text',
1052
+ children: [{
1053
+ displayName: 'RCText',
1054
+ children: [{
1055
+ displayName: '#text',
1056
+ text: 'hi',
1057
+ }],
1058
+ }],
1059
+ }, {
1060
+ displayName: 'Text',
1061
+ children: [{
1062
+ displayName: 'RCText',
1063
+ children: [{
1064
+ displayName: '#text',
1065
+ text: '42',
1066
+ }],
1067
+ }],
1068
+ }, {
1069
+ displayName: 'Foo',
1070
+ children: [{
1071
+ displayName: 'View',
1072
+ children: [],
1073
+ }],
1074
+ }],
1075
+ };
1076
+
1077
+ var element2 = (
1078
+ <View>
1079
+ <Foo />
1080
+ {false}
1081
+ <Text>hi</Text>
1082
+ {null}
1083
+ </View>
1084
+ );
1085
+ var tree2 = {
1086
+ displayName: 'View',
1087
+ children: [{
1088
+ displayName: 'Foo',
1089
+ children: [{
1090
+ displayName: 'View',
1091
+ children: [],
1092
+ }],
1093
+ }, {
1094
+ displayName: 'Text',
1095
+ children: [{
1096
+ displayName: 'RCText',
1097
+ children: [{
1098
+ displayName: '#text',
1099
+ text: 'hi',
1100
+ }],
1101
+ }],
1102
+ }],
1103
+ };
1104
+
1105
+ var element3 = (
1106
+ <View>
1107
+ <Foo />
1108
+ </View>
1109
+ );
1110
+ var tree3 = {
1111
+ displayName: 'View',
1112
+ children: [{
1113
+ displayName: 'Foo',
1114
+ children: [{
1115
+ displayName: 'View',
1116
+ children: [],
1117
+ }],
1118
+ }],
1119
+ };
1120
+
1121
+ assertTreeMatches([
1122
+ [element1, tree1],
1123
+ [element2, tree2],
1124
+ [element3, tree3],
1125
+ ]);
1126
+ });
1127
+ });
1128
+
1129
+ describe('functional component', () => {
1130
+ it('updates with a native child', () => {
1131
+ function Foo({ children }) {
1132
+ return children;
1133
+ }
1134
+
1135
+ var elementBefore = <Foo><View /></Foo>;
1136
+ var treeBefore = {
1137
+ displayName: 'Foo',
1138
+ children: [{
1139
+ displayName: 'View',
1140
+ children: [],
1141
+ }],
1142
+ };
1143
+
1144
+ var elementAfter = <Foo><Image /></Foo>;
1145
+ var treeAfter = {
1146
+ displayName: 'Foo',
1147
+ children: [{
1148
+ displayName: 'Image',
1149
+ children: [],
1150
+ }],
1151
+ };
1152
+
1153
+ assertTreeMatches([
1154
+ [elementBefore, treeBefore],
1155
+ [elementAfter, treeAfter],
1156
+ ]);
1157
+ });
1158
+
1159
+ it('updates from null to a native child', () => {
1160
+ function Foo({ children }) {
1161
+ return children;
1162
+ }
1163
+
1164
+ var elementBefore = <Foo>{null}</Foo>;
1165
+ var treeBefore = {
1166
+ displayName: 'Foo',
1167
+ children: [],
1168
+ };
1169
+
1170
+ var elementAfter = <Foo><View /></Foo>;
1171
+ var treeAfter = {
1172
+ displayName: 'Foo',
1173
+ children: [{
1174
+ displayName: 'View',
1175
+ children: [],
1176
+ }],
1177
+ };
1178
+
1179
+ assertTreeMatches([
1180
+ [elementBefore, treeBefore],
1181
+ [elementAfter, treeAfter],
1182
+ ]);
1183
+ });
1184
+
1185
+ it('updates from a native child to null', () => {
1186
+ function Foo({ children }) {
1187
+ return children;
1188
+ }
1189
+
1190
+ var elementBefore = <Foo><View /></Foo>;
1191
+ var treeBefore = {
1192
+ displayName: 'Foo',
1193
+ children: [{
1194
+ displayName: 'View',
1195
+ children: [],
1196
+ }],
1197
+ };
1198
+
1199
+ var elementAfter = <Foo>{null}</Foo>;
1200
+ var treeAfter = {
1201
+ displayName: 'Foo',
1202
+ children: [],
1203
+ };
1204
+
1205
+ assertTreeMatches([
1206
+ [elementBefore, treeBefore],
1207
+ [elementAfter, treeAfter],
1208
+ ]);
1209
+ });
1210
+
1211
+ it('updates from a native child to a composite child', () => {
1212
+ function Bar() {
1213
+ return null;
1214
+ }
1215
+
1216
+ function Foo({ children }) {
1217
+ return children;
1218
+ }
1219
+
1220
+ var elementBefore = <Foo><View /></Foo>;
1221
+ var treeBefore = {
1222
+ displayName: 'Foo',
1223
+ children: [{
1224
+ displayName: 'View',
1225
+ children: [],
1226
+ }],
1227
+ };
1228
+
1229
+ var elementAfter = <Foo><Bar /></Foo>;
1230
+ var treeAfter = {
1231
+ displayName: 'Foo',
1232
+ children: [{
1233
+ displayName: 'Bar',
1234
+ children: [],
1235
+ }],
1236
+ };
1237
+
1238
+ assertTreeMatches([
1239
+ [elementBefore, treeBefore],
1240
+ [elementAfter, treeAfter],
1241
+ ]);
1242
+ });
1243
+
1244
+ it('updates from a composite child to a native child', () => {
1245
+ function Bar() {
1246
+ return null;
1247
+ }
1248
+
1249
+ function Foo({ children }) {
1250
+ return children;
1251
+ }
1252
+
1253
+ var elementBefore = <Foo><Bar /></Foo>;
1254
+ var treeBefore = {
1255
+ displayName: 'Foo',
1256
+ children: [{
1257
+ displayName: 'Bar',
1258
+ children: [],
1259
+ }],
1260
+ };
1261
+
1262
+ var elementAfter = <Foo><View /></Foo>;
1263
+ var treeAfter = {
1264
+ displayName: 'Foo',
1265
+ children: [{
1266
+ displayName: 'View',
1267
+ children: [],
1268
+ }],
1269
+ };
1270
+
1271
+ assertTreeMatches([
1272
+ [elementBefore, treeBefore],
1273
+ [elementAfter, treeAfter],
1274
+ ]);
1275
+ });
1276
+
1277
+ it('updates from null to a composite child', () => {
1278
+ function Bar() {
1279
+ return null;
1280
+ }
1281
+
1282
+ function Foo({ children }) {
1283
+ return children;
1284
+ }
1285
+
1286
+ var elementBefore = <Foo>{null}</Foo>;
1287
+ var treeBefore = {
1288
+ displayName: 'Foo',
1289
+ children: [],
1290
+ };
1291
+
1292
+ var elementAfter = <Foo><Bar /></Foo>;
1293
+ var treeAfter = {
1294
+ displayName: 'Foo',
1295
+ children: [{
1296
+ displayName: 'Bar',
1297
+ children: [],
1298
+ }],
1299
+ };
1300
+
1301
+ assertTreeMatches([
1302
+ [elementBefore, treeBefore],
1303
+ [elementAfter, treeAfter],
1304
+ ]);
1305
+ });
1306
+
1307
+ it('updates from a composite child to null', () => {
1308
+ function Bar() {
1309
+ return null;
1310
+ }
1311
+
1312
+ function Foo({ children }) {
1313
+ return children;
1314
+ }
1315
+
1316
+ var elementBefore = <Foo><Bar /></Foo>;
1317
+ var treeBefore = {
1318
+ displayName: 'Foo',
1319
+ children: [{
1320
+ displayName: 'Bar',
1321
+ children: [],
1322
+ }],
1323
+ };
1324
+
1325
+ var elementAfter = <Foo>{null}</Foo>;
1326
+ var treeAfter = {
1327
+ displayName: 'Foo',
1328
+ children: [],
1329
+ };
1330
+
1331
+ assertTreeMatches([
1332
+ [elementBefore, treeBefore],
1333
+ [elementAfter, treeAfter],
1334
+ ]);
1335
+ });
1336
+ });
1337
+
1338
+ describe('class component', () => {
1339
+ it('updates with a native child', () => {
1340
+ var Foo = React.createClass({
1341
+ render() {
1342
+ return this.props.children;
1343
+ },
1344
+ });
1345
+
1346
+ var elementBefore = <Foo><View /></Foo>;
1347
+ var treeBefore = {
1348
+ displayName: 'Foo',
1349
+ children: [{
1350
+ displayName: 'View',
1351
+ children: [],
1352
+ }],
1353
+ };
1354
+
1355
+ var elementAfter = <Foo><Image /></Foo>;
1356
+ var treeAfter = {
1357
+ displayName: 'Foo',
1358
+ children: [{
1359
+ displayName: 'Image',
1360
+ children: [],
1361
+ }],
1362
+ };
1363
+
1364
+ assertTreeMatches([
1365
+ [elementBefore, treeBefore],
1366
+ [elementAfter, treeAfter],
1367
+ ]);
1368
+ });
1369
+
1370
+ it('updates from null to a native child', () => {
1371
+ var Foo = React.createClass({
1372
+ render() {
1373
+ return this.props.children;
1374
+ },
1375
+ });
1376
+
1377
+ var elementBefore = <Foo>{null}</Foo>;
1378
+ var treeBefore = {
1379
+ displayName: 'Foo',
1380
+ children: [],
1381
+ };
1382
+
1383
+ var elementAfter = <Foo><View /></Foo>;
1384
+ var treeAfter = {
1385
+ displayName: 'Foo',
1386
+ children: [{
1387
+ displayName: 'View',
1388
+ children: [],
1389
+ }],
1390
+ };
1391
+
1392
+ assertTreeMatches([
1393
+ [elementBefore, treeBefore],
1394
+ [elementAfter, treeAfter],
1395
+ ]);
1396
+ });
1397
+
1398
+ it('updates from a native child to null', () => {
1399
+ var Foo = React.createClass({
1400
+ render() {
1401
+ return this.props.children;
1402
+ },
1403
+ });
1404
+
1405
+ var elementBefore = <Foo><View /></Foo>;
1406
+ var treeBefore = {
1407
+ displayName: 'Foo',
1408
+ children: [{
1409
+ displayName: 'View',
1410
+ children: [],
1411
+ }],
1412
+ };
1413
+
1414
+ var elementAfter = <Foo>{null}</Foo>;
1415
+ var treeAfter = {
1416
+ displayName: 'Foo',
1417
+ children: [],
1418
+ };
1419
+
1420
+ assertTreeMatches([
1421
+ [elementBefore, treeBefore],
1422
+ [elementAfter, treeAfter],
1423
+ ]);
1424
+ });
1425
+
1426
+ it('updates from a native child to a composite child', () => {
1427
+ var Bar = React.createClass({
1428
+ render() {
1429
+ return null;
1430
+ },
1431
+ });
1432
+
1433
+ var Foo = React.createClass({
1434
+ render() {
1435
+ return this.props.children;
1436
+ },
1437
+ });
1438
+
1439
+ var elementBefore = <Foo><View /></Foo>;
1440
+ var treeBefore = {
1441
+ displayName: 'Foo',
1442
+ children: [{
1443
+ displayName: 'View',
1444
+ children: [],
1445
+ }],
1446
+ };
1447
+
1448
+ var elementAfter = <Foo><Bar /></Foo>;
1449
+ var treeAfter = {
1450
+ displayName: 'Foo',
1451
+ children: [{
1452
+ displayName: 'Bar',
1453
+ children: [],
1454
+ }],
1455
+ };
1456
+
1457
+ assertTreeMatches([
1458
+ [elementBefore, treeBefore],
1459
+ [elementAfter, treeAfter],
1460
+ ]);
1461
+ });
1462
+
1463
+ it('updates from a composite child to a native child', () => {
1464
+ var Bar = React.createClass({
1465
+ render() {
1466
+ return null;
1467
+ },
1468
+ });
1469
+
1470
+ var Foo = React.createClass({
1471
+ render() {
1472
+ return this.props.children;
1473
+ },
1474
+ });
1475
+
1476
+ var elementBefore = <Foo><Bar /></Foo>;
1477
+ var treeBefore = {
1478
+ displayName: 'Foo',
1479
+ children: [{
1480
+ displayName: 'Bar',
1481
+ children: [],
1482
+ }],
1483
+ };
1484
+
1485
+ var elementAfter = <Foo><View /></Foo>;
1486
+ var treeAfter = {
1487
+ displayName: 'Foo',
1488
+ children: [{
1489
+ displayName: 'View',
1490
+ children: [],
1491
+ }],
1492
+ };
1493
+
1494
+ assertTreeMatches([
1495
+ [elementBefore, treeBefore],
1496
+ [elementAfter, treeAfter],
1497
+ ]);
1498
+ });
1499
+
1500
+ it('updates from null to a composite child', () => {
1501
+ var Bar = React.createClass({
1502
+ render() {
1503
+ return null;
1504
+ },
1505
+ });
1506
+
1507
+ var Foo = React.createClass({
1508
+ render() {
1509
+ return this.props.children;
1510
+ },
1511
+ });
1512
+
1513
+ var elementBefore = <Foo>{null}</Foo>;
1514
+ var treeBefore = {
1515
+ displayName: 'Foo',
1516
+ children: [],
1517
+ };
1518
+
1519
+ var elementAfter = <Foo><Bar /></Foo>;
1520
+ var treeAfter = {
1521
+ displayName: 'Foo',
1522
+ children: [{
1523
+ displayName: 'Bar',
1524
+ children: [],
1525
+ }],
1526
+ };
1527
+
1528
+ assertTreeMatches([
1529
+ [elementBefore, treeBefore],
1530
+ [elementAfter, treeAfter],
1531
+ ]);
1532
+ });
1533
+
1534
+ it('updates from a composite child to null', () => {
1535
+ var Bar = React.createClass({
1536
+ render() {
1537
+ return null;
1538
+ },
1539
+ });
1540
+
1541
+ var Foo = React.createClass({
1542
+ render() {
1543
+ return this.props.children;
1544
+ },
1545
+ });
1546
+
1547
+ var elementBefore = <Foo><Bar /></Foo>;
1548
+ var treeBefore = {
1549
+ displayName: 'Foo',
1550
+ children: [{
1551
+ displayName: 'Bar',
1552
+ children: [],
1553
+ }],
1554
+ };
1555
+
1556
+ var elementAfter = <Foo>{null}</Foo>;
1557
+ var treeAfter = {
1558
+ displayName: 'Foo',
1559
+ children: [],
1560
+ };
1561
+
1562
+ assertTreeMatches([
1563
+ [elementBefore, treeBefore],
1564
+ [elementAfter, treeAfter],
1565
+ ]);
1566
+ });
1567
+ });
1568
+ });
1569
+
1570
+ it('tracks owner correctly', () => {
1571
+ class Foo extends React.Component {
1572
+ render() {
1573
+ return <Bar><Text>Hi.</Text></Bar>;
1574
+ }
1575
+ }
1576
+ function Bar({children}) {
1577
+ return <View>{children}<Text>Mom</Text></View>;
1578
+ }
1579
+
1580
+ // Note that owner is not calculated for text nodes
1581
+ // because they are not created from real elements.
1582
+ var element = <View><Foo /></View>;
1583
+ var tree = {
1584
+ displayName: 'View',
1585
+ children: [{
1586
+ displayName: 'Foo',
1587
+ children: [{
1588
+ displayName: 'Bar',
1589
+ ownerDisplayName: 'Foo',
1590
+ children: [{
1591
+ displayName: 'View',
1592
+ ownerDisplayName: 'Bar',
1593
+ children: [{
1594
+ displayName: 'Text',
1595
+ ownerDisplayName: 'Foo',
1596
+ children: [{
1597
+ displayName: 'RCText',
1598
+ ownerDisplayName: 'Text',
1599
+ children: [{
1600
+ displayName: '#text',
1601
+ text: 'Hi.',
1602
+ }],
1603
+ }],
1604
+ }, {
1605
+ displayName: 'Text',
1606
+ ownerDisplayName: 'Bar',
1607
+ children: [{
1608
+ displayName: 'RCText',
1609
+ ownerDisplayName: 'Text',
1610
+ children: [{
1611
+ displayName: '#text',
1612
+ text: 'Mom',
1613
+ }],
1614
+ }],
1615
+ }],
1616
+ }],
1617
+ }],
1618
+ }],
1619
+ };
1620
+ assertTreeMatches([element, tree], {includeOwnerDisplayName: true});
1621
+ });
1622
+
1623
+ it('preserves unmounted components until purge', () => {
1624
+ var renderBar = true;
1625
+ var fooInstance;
1626
+ var barInstance;
1627
+
1628
+ class Foo extends React.Component {
1629
+ render() {
1630
+ fooInstance = ReactInstanceMap.get(this);
1631
+ return renderBar ? <Bar /> : null;
1632
+ }
1633
+ }
1634
+
1635
+ class Bar extends React.Component {
1636
+ render() {
1637
+ barInstance = ReactInstanceMap.get(this);
1638
+ return null;
1639
+ }
1640
+ }
1641
+
1642
+ ReactNative.render(<Foo />, 1);
1643
+ expect(
1644
+ getTree(barInstance._debugID, {
1645
+ includeParentDisplayName: true,
1646
+ expectedParentID: fooInstance._debugID,
1647
+ })
1648
+ ).toEqual({
1649
+ displayName: 'Bar',
1650
+ parentDisplayName: 'Foo',
1651
+ children: [],
1652
+ });
1653
+
1654
+ renderBar = false;
1655
+ ReactNative.render(<Foo />, 1);
1656
+ expect(
1657
+ getTree(barInstance._debugID, {
1658
+ includeParentDisplayName: true,
1659
+ expectedParentID: fooInstance._debugID,
1660
+ })
1661
+ ).toEqual({
1662
+ displayName: 'Bar',
1663
+ parentDisplayName: 'Foo',
1664
+ children: [],
1665
+ });
1666
+
1667
+ ReactNative.unmountComponentAtNode(1);
1668
+ expect(
1669
+ getTree(barInstance._debugID, {
1670
+ includeParentDisplayName: true,
1671
+ expectedParentID: fooInstance._debugID,
1672
+ })
1673
+ ).toEqual({
1674
+ displayName: 'Bar',
1675
+ parentDisplayName: 'Foo',
1676
+ children: [],
1677
+ });
1678
+
1679
+ ReactComponentTreeDevtool.purgeUnmountedComponents();
1680
+ expect(
1681
+ getTree(barInstance._debugID, {includeParentDisplayName: true})
1682
+ ).toEqual({
1683
+ displayName: 'Unknown',
1684
+ children: [],
1685
+ });
1686
+ });
1687
+
1688
+ it('does not report top-level wrapper as a root', () => {
1689
+ ReactNative.render(<View><Image /></View>, 1);
1690
+ expect(getRootDisplayNames()).toEqual(['View']);
1691
+
1692
+ ReactNative.render(<View><Text /></View>, 1);
1693
+ expect(getRootDisplayNames()).toEqual(['View']);
1694
+
1695
+ ReactNative.unmountComponentAtNode(1);
1696
+ expect(getRootDisplayNames()).toEqual([]);
1697
+
1698
+ ReactComponentTreeDevtool.purgeUnmountedComponents();
1699
+ expect(getRootDisplayNames()).toEqual([]);
1700
+
1701
+ // This currently contains TopLevelWrapper until purge
1702
+ // so we only check it at the very end.
1703
+ expect(getRegisteredDisplayNames()).toEqual([]);
1704
+ });
1705
+});
src/renderers/native/ReactNative/ReactNativeMount.js
+13
@@ -12,6 +12,7 @@
12
'use strict';
13
14
var ReactElement = require('ReactElement');
15
+var ReactInstrumentation = require('ReactInstrumentation');
16
var ReactNativeContainerInfo = require('ReactNativeContainerInfo');
17
var ReactNativeTagHandles = require('ReactNativeTagHandles');
18
var ReactPerf = require('ReactPerf');
@@ -138,6 +139,12 @@ var ReactNativeMount = {
139
var instance = instantiateReactComponent(nextWrappedElement);
140
ReactNativeMount._instancesByContainerID[containerTag] = instance;
141
142
+ if (__DEV__) {
143
+ // Mute future events from the top level wrapper.
144
+ // It is an implementation detail that devtools should not know about.
145
+ instance._debugID = 0;
146
+ }
147
+
148
// The initial render is synchronous but any updates that happen during
149
// rendering, in componentWillMount or componentDidMount, will be batched
150
// according to the current batching strategy.
@@ -147,6 +154,12 @@ var ReactNativeMount = {
154
instance,
155
containerTag
156
);
157
+ if (__DEV__) {
158
+ // The instance here is TopLevelWrapper so we report mount for its child.
159
+ ReactInstrumentation.debugTool.onMountRootComponent(
160
+ instance._renderedComponent._debugID
161
+ );
162
+ }
163
var component = instance.getPublicInstance();
164
if (callback) {
165
callback.call(component);
src/renderers/native/ReactNative/ReactNativeTextComponent.js
+11
@@ -11,6 +11,7 @@
11
12
'use strict';
13
14
+var ReactInstrumentation = require('ReactInstrumentation');
15
var ReactNativeComponentTree = require('ReactNativeComponentTree');
16
var ReactNativeTagHandles = require('ReactNativeTagHandles');
17
var UIManager = require('UIManager');
@@ -28,6 +29,10 @@ var ReactNativeTextComponent = function(text) {
29
Object.assign(ReactNativeTextComponent.prototype, {
30
31
mountComponent: function(transaction, nativeParent, nativeContainerInfo, context) {
32
+ if (__DEV__) {
33
+ ReactInstrumentation.debugTool.onSetText(this._debugID, this._stringText);
34
+ }
35
+
36
// TODO: nativeParent should have this context already. Stop abusing context.
37
invariant(
38
context.isInAParentText,
@@ -65,6 +70,12 @@ Object.assign(ReactNativeTextComponent.prototype, {
70
'RCTRawText',
71
{text: this._stringText}
72
);
73
+ if (__DEV__) {
74
+ ReactInstrumentation.debugTool.onSetText(
75
+ this._debugID,
76
+ nextStringText
77
+ );
78
+ }
79
}
80
}
81
},
src/renderers/native/ReactNative/__mocks__/UIManager.js
+2
@@ -16,6 +16,8 @@ var RCTUIManager = {
16
setChildren: jest.fn(),
17
manageChildren: jest.fn(),
18
updateView: jest.fn(),
19
+ removeSubviewsFromContainerWithID: jest.fn(),
20
+ replaceExistingNonRootView: jest.fn(),
21
};
22
23
module.exports = RCTUIManager;
src/renderers/native/ReactNative/__mocks__/View.js
new
+19
@@ -0,0 +1,19 @@
1
+/**
2
+ * Copyright 2013-2015, 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
+
10
+'use strict';
11
+
12
+var createReactNativeComponentClass = require('createReactNativeComponentClass');
13
+
14
+var View = createReactNativeComponentClass({
15
+ validAttributes: {},
16
+ uiViewClassName: 'View',
17
+});
18
+
19
+module.exports = View;