@samitouri / QOS-React / commits / 0e15ff5669

Put React.Fragment under a feature flag (#11421)

* Put React.Fragment under a feature flag * Don't export undefined Fragment

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