@samitouri / QOS-React-2 / commits / 4175f05934

Temporarily feature flag numeric fallback for symbols (#24401)

Ricky committed Apr 19, 2022 at 17:34 UTC 4175f05934d3954827c00b7633c1ce8e4e87d227
15 files changed +328 -27
packages/react/src/__tests__/ReactElement-test.js
+112
@@ -9,16 +9,26 @@
9
10 'use strict';
11
12 +import {enableSymbolFallbackForWWW} from 'shared/ReactFeatureFlags';
13 +
14 let React;
15 let ReactDOM;
16 let ReactTestUtils;
17
18 describe('ReactElement', () => {
19 let ComponentClass;
20 + let originalSymbol;
21
22 beforeEach(() => {
23 jest.resetModules();
24
25 + if (enableSymbolFallbackForWWW) {
26 + // Delete the native Symbol if we have one to ensure we test the
27 + // unpolyfilled environment.
28 + originalSymbol = global.Symbol;
29 + global.Symbol = undefined;
30 + }
31 +
32 React = require('react');
33 ReactDOM = require('react-dom');
34 ReactTestUtils = require('react-dom/test-utils');
@@ -31,6 +41,17 @@ describe('ReactElement', () => {
41 };
42 });
43
44 + afterEach(() => {
45 + if (enableSymbolFallbackForWWW) {
46 + global.Symbol = originalSymbol;
47 + }
48 + });
49 +
50 + // @gate enableSymbolFallbackForWWW
51 + it('uses the fallback value when in an environment without Symbol', () => {
52 + expect((<div />).$$typeof).toBe(0xeac7);
53 + });
54 +
55 it('returns a complete element according to spec', () => {
56 const element = React.createElement(ComponentClass);
57 expect(element.type).toBe(ComponentClass);
@@ -280,6 +301,42 @@ describe('ReactElement', () => {
301 expect(element.type.someStaticMethod()).toBe('someReturnValue');
302 });
303
304 + // NOTE: We're explicitly not using JSX here. This is intended to test
305 + // classic JS without JSX.
306 + // @gate enableSymbolFallbackForWWW
307 + it('identifies valid elements', () => {
308 + class Component extends React.Component {
309 + render() {
310 + return React.createElement('div');
311 + }
312 + }
313 +
314 + expect(React.isValidElement(React.createElement('div'))).toEqual(true);
315 + expect(React.isValidElement(React.createElement(Component))).toEqual(true);
316 +
317 + expect(React.isValidElement(null)).toEqual(false);
318 + expect(React.isValidElement(true)).toEqual(false);
319 + expect(React.isValidElement({})).toEqual(false);
320 + expect(React.isValidElement('string')).toEqual(false);
321 + if (!__EXPERIMENTAL__) {
322 + let factory;
323 + expect(() => {
324 + factory = React.createFactory('div');
325 + }).toWarnDev(
326 + 'Warning: React.createFactory() is deprecated and will be removed in a ' +
327 + 'future major release. Consider using JSX or use React.createElement() ' +
328 + 'directly instead.',
329 + {withoutStack: true},
330 + );
331 + expect(React.isValidElement(factory)).toEqual(false);
332 + }
333 + expect(React.isValidElement(Component)).toEqual(false);
334 + expect(React.isValidElement({type: 'div', props: {}})).toEqual(false);
335 +
336 + const jsonElement = JSON.stringify(React.createElement('div'));
337 + expect(React.isValidElement(JSON.parse(jsonElement))).toBe(true);
338 + });
339 +
340 // NOTE: We're explicitly not using JSX here. This is intended to test
341 // classic JS without JSX.
342 it('is indistinguishable from a plain object', () => {
@@ -397,6 +454,7 @@ describe('ReactElement', () => {
454
455 // NOTE: We're explicitly not using JSX here. This is intended to test
456 // classic JS without JSX.
457 + // @gate !enableSymbolFallbackForWWW
458 it('identifies elements, but not JSON, if Symbols are supported', () => {
459 class Component extends React.Component {
460 render() {
@@ -429,4 +487,58 @@ describe('ReactElement', () => {
487 const jsonElement = JSON.stringify(React.createElement('div'));
488 expect(React.isValidElement(JSON.parse(jsonElement))).toBe(false);
489 });
490 +
491 + // NOTE: We're explicitly not using JSX here. This is intended to test
492 + // classic JS without JSX.
493 + it('identifies elements, but not JSON, if Symbols are supported (with polyfill)', () => {
494 + // Rudimentary polyfill
495 + // Once all jest engines support Symbols natively we can swap this to test
496 + // WITH native Symbols by default.
497 + const REACT_ELEMENT_TYPE = function() {}; // fake Symbol
498 + const OTHER_SYMBOL = function() {}; // another fake Symbol
499 + global.Symbol = function(name) {
500 + return OTHER_SYMBOL;
501 + };
502 + global.Symbol.for = function(key) {
503 + if (key === 'react.element') {
504 + return REACT_ELEMENT_TYPE;
505 + }
506 + return OTHER_SYMBOL;
507 + };
508 +
509 + jest.resetModules();
510 +
511 + React = require('react');
512 +
513 + class Component extends React.Component {
514 + render() {
515 + return React.createElement('div');
516 + }
517 + }
518 +
519 + expect(React.isValidElement(React.createElement('div'))).toEqual(true);
520 + expect(React.isValidElement(React.createElement(Component))).toEqual(true);
521 +
522 + expect(React.isValidElement(null)).toEqual(false);
523 + expect(React.isValidElement(true)).toEqual(false);
524 + expect(React.isValidElement({})).toEqual(false);
525 + expect(React.isValidElement('string')).toEqual(false);
526 + if (!__EXPERIMENTAL__) {
527 + let factory;
528 + expect(() => {
529 + factory = React.createFactory('div');
530 + }).toWarnDev(
531 + 'Warning: React.createFactory() is deprecated and will be removed in a ' +
532 + 'future major release. Consider using JSX or use React.createElement() ' +
533 + 'directly instead.',
534 + {withoutStack: true},
535 + );
536 + expect(React.isValidElement(factory)).toEqual(false);
537 + }
538 + expect(React.isValidElement(Component)).toEqual(false);
539 + expect(React.isValidElement({type: 'div', props: {}})).toEqual(false);
540 +
541 + const jsonElement = JSON.stringify(React.createElement('div'));
542 + expect(React.isValidElement(JSON.parse(jsonElement))).toBe(false);
543 + });
544 });
packages/react/src/__tests__/ReactElementJSX-test.js
+113
@@ -9,6 +9,8 @@
9
10 'use strict';
11
12 +import {enableSymbolFallbackForWWW} from 'shared/ReactFeatureFlags';
13 +
14 let React;
15 let ReactDOM;
16 let ReactTestUtils;
@@ -20,9 +22,18 @@ let JSXDEVRuntime;
22 // A lot of these tests are pulled from ReactElement-test because
23 // this api is meant to be backwards compatible.
24 describe('ReactElement.jsx', () => {
25 + let originalSymbol;
26 +
27 beforeEach(() => {
28 jest.resetModules();
29
30 + if (enableSymbolFallbackForWWW) {
31 + // Delete the native Symbol if we have one to ensure we test the
32 + // unpolyfilled environment.
33 + originalSymbol = global.Symbol;
34 + global.Symbol = undefined;
35 + }
36 +
37 React = require('react');
38 JSXRuntime = require('react/jsx-runtime');
39 JSXDEVRuntime = require('react/jsx-dev-runtime');
@@ -30,6 +41,12 @@ describe('ReactElement.jsx', () => {
41 ReactTestUtils = require('react-dom/test-utils');
42 });
43
44 + afterEach(() => {
45 + if (enableSymbolFallbackForWWW) {
46 + global.Symbol = originalSymbol;
47 + }
48 + });
49 +
50 it('allows static methods to be called using the type property', () => {
51 class StaticMethodComponentClass extends React.Component {
52 render() {
@@ -42,6 +59,48 @@ describe('ReactElement.jsx', () => {
59 expect(element.type.someStaticMethod()).toBe('someReturnValue');
60 });
61
62 + // @gate enableSymbolFallbackForWWW
63 + it('identifies valid elements', () => {
64 + class Component extends React.Component {
65 + render() {
66 + return JSXRuntime.jsx('div', {});
67 + }
68 + }
69 +
70 + expect(React.isValidElement(JSXRuntime.jsx('div', {}))).toEqual(true);
71 + expect(React.isValidElement(JSXRuntime.jsx(Component, {}))).toEqual(true);
72 + expect(
73 + React.isValidElement(JSXRuntime.jsx(JSXRuntime.Fragment, {})),
74 + ).toEqual(true);
75 + if (__DEV__) {
76 + expect(React.isValidElement(JSXDEVRuntime.jsxDEV('div', {}))).toEqual(
77 + true,
78 + );
79 + }
80 +
81 + expect(React.isValidElement(null)).toEqual(false);
82 + expect(React.isValidElement(true)).toEqual(false);
83 + expect(React.isValidElement({})).toEqual(false);
84 + expect(React.isValidElement('string')).toEqual(false);
85 + if (!__EXPERIMENTAL__) {
86 + let factory;
87 + expect(() => {
88 + factory = React.createFactory('div');
89 + }).toWarnDev(
90 + 'Warning: React.createFactory() is deprecated and will be removed in a ' +
91 + 'future major release. Consider using JSX or use React.createElement() ' +
92 + 'directly instead.',
93 + {withoutStack: true},
94 + );
95 + expect(React.isValidElement(factory)).toEqual(false);
96 + }
97 + expect(React.isValidElement(Component)).toEqual(false);
98 + expect(React.isValidElement({type: 'div', props: {}})).toEqual(false);
99 +
100 + const jsonElement = JSON.stringify(JSXRuntime.jsx('div', {}));
101 + expect(React.isValidElement(JSON.parse(jsonElement))).toBe(true);
102 + });
103 +
104 it('is indistinguishable from a plain object', () => {
105 const element = JSXRuntime.jsx('div', {className: 'foo'});
106 const object = {};
@@ -235,6 +294,7 @@ describe('ReactElement.jsx', () => {
294 );
295 });
296
297 + // @gate !enableSymbolFallbackForWWW
298 it('identifies elements, but not JSON, if Symbols are supported', () => {
299 class Component extends React.Component {
300 render() {
@@ -276,6 +336,59 @@ describe('ReactElement.jsx', () => {
336 expect(React.isValidElement(JSON.parse(jsonElement))).toBe(false);
337 });
338
339 + it('identifies elements, but not JSON, if Symbols are polyfilled', () => {
340 + // Rudimentary polyfill
341 + // Once all jest engines support Symbols natively we can swap this to test
342 + // WITH native Symbols by default.
343 + const REACT_ELEMENT_TYPE = function() {}; // fake Symbol
344 + const OTHER_SYMBOL = function() {}; // another fake Symbol
345 + global.Symbol = function(name) {
346 + return OTHER_SYMBOL;
347 + };
348 + global.Symbol.for = function(key) {
349 + if (key === 'react.element') {
350 + return REACT_ELEMENT_TYPE;
351 + }
352 + return OTHER_SYMBOL;
353 + };
354 +
355 + jest.resetModules();
356 +
357 + React = require('react');
358 + JSXRuntime = require('react/jsx-runtime');
359 +
360 + class Component extends React.Component {
361 + render() {
362 + return JSXRuntime.jsx('div');
363 + }
364 + }
365 +
366 + expect(React.isValidElement(JSXRuntime.jsx('div', {}))).toEqual(true);
367 + expect(React.isValidElement(JSXRuntime.jsx(Component, {}))).toEqual(true);
368 +
369 + expect(React.isValidElement(null)).toEqual(false);
370 + expect(React.isValidElement(true)).toEqual(false);
371 + expect(React.isValidElement({})).toEqual(false);
372 + expect(React.isValidElement('string')).toEqual(false);
373 + if (!__EXPERIMENTAL__) {
374 + let factory;
375 + expect(() => {
376 + factory = React.createFactory('div');
377 + }).toWarnDev(
378 + 'Warning: React.createFactory() is deprecated and will be removed in a ' +
379 + 'future major release. Consider using JSX or use React.createElement() ' +
380 + 'directly instead.',
381 + {withoutStack: true},
382 + );
383 + expect(React.isValidElement(factory)).toEqual(false);
384 + }
385 + expect(React.isValidElement(Component)).toEqual(false);
386 + expect(React.isValidElement({type: 'div', props: {}})).toEqual(false);
387 +
388 + const jsonElement = JSON.stringify(JSXRuntime.jsx('div', {}));
389 + expect(React.isValidElement(JSON.parse(jsonElement))).toBe(false);
390 + });
391 +
392 it('should warn when unkeyed children are passed to jsx', () => {
393 const container = document.createElement('div');
394
packages/shared/ReactFeatureFlags.js
+3
@@ -28,6 +28,9 @@ export const enablePersistentOffscreenHostContainer = false;
28 // like migrating internal callers or performance testing.
29 // -----------------------------------------------------------------------------
30
31 +// This is blocked on adding a symbol polyfill to www.
32 +export const enableSymbolFallbackForWWW = false;
33 +
34 // This rolled out to 10% public in www, so we should be able to land, but some
35 // internal tests need to be updated. The open source behavior is correct.
36 export const skipUnmountedBoundaries = true;
packages/shared/ReactSymbols.js
+64 -25
@@ -11,33 +11,72 @@
11 // When adding new symbols to this file,
12 // Please consider also adding to 'react-devtools-shared/src/backend/ReactSymbols'
13
14 +import {enableSymbolFallbackForWWW} from './ReactFeatureFlags';
15 +
16 +const usePolyfill =
17 + enableSymbolFallbackForWWW && (typeof Symbol !== 'function' || !Symbol.for);
18 +
19 // The Symbol used to tag the ReactElement-like types.
15 -export const REACT_ELEMENT_TYPE = Symbol.for('react.element');
16 -export const REACT_PORTAL_TYPE = Symbol.for('react.portal');
17 -export const REACT_FRAGMENT_TYPE = Symbol.for('react.fragment');
18 -export const REACT_STRICT_MODE_TYPE = Symbol.for('react.strict_mode');
19 -export const REACT_PROFILER_TYPE = Symbol.for('react.profiler');
20 -export const REACT_PROVIDER_TYPE = Symbol.for('react.provider');
21 -export const REACT_CONTEXT_TYPE = Symbol.for('react.context');
22 -export const REACT_SERVER_CONTEXT_TYPE = Symbol.for('react.server_context');
23 -export const REACT_FORWARD_REF_TYPE = Symbol.for('react.forward_ref');
24 -export const REACT_SUSPENSE_TYPE = Symbol.for('react.suspense');
25 -export const REACT_SUSPENSE_LIST_TYPE = Symbol.for('react.suspense_list');
26 -export const REACT_MEMO_TYPE = Symbol.for('react.memo');
27 -export const REACT_LAZY_TYPE = Symbol.for('react.lazy');
28 -export const REACT_SCOPE_TYPE = Symbol.for('react.scope');
29 -export const REACT_DEBUG_TRACING_MODE_TYPE = Symbol.for(
30 - 'react.debug_trace_mode',
31 -);
32 -export const REACT_OFFSCREEN_TYPE = Symbol.for('react.offscreen');
33 -export const REACT_LEGACY_HIDDEN_TYPE = Symbol.for('react.legacy_hidden');
34 -export const REACT_CACHE_TYPE = Symbol.for('react.cache');
35 -export const REACT_TRACING_MARKER_TYPE = Symbol.for('react.tracing_marker');
36 -export const REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED = Symbol.for(
37 - 'react.default_value',
38 -);
20 +export const REACT_ELEMENT_TYPE = usePolyfill
21 + ? 0xeac7
22 + : Symbol.for('react.element');
23 +export const REACT_PORTAL_TYPE = usePolyfill
24 + ? 0xeaca
25 + : Symbol.for('react.portal');
26 +export const REACT_FRAGMENT_TYPE = usePolyfill
27 + ? 0xeacb
28 + : Symbol.for('react.fragment');
29 +export const REACT_STRICT_MODE_TYPE = usePolyfill
30 + ? 0xeacc
31 + : Symbol.for('react.strict_mode');
32 +export const REACT_PROFILER_TYPE = usePolyfill
33 + ? 0xead2
34 + : Symbol.for('react.profiler');
35 +export const REACT_PROVIDER_TYPE = usePolyfill
36 + ? 0xeacd
37 + : Symbol.for('react.provider');
38 +export const REACT_CONTEXT_TYPE = usePolyfill
39 + ? 0xeace
40 + : Symbol.for('react.context');
41 +export const REACT_SERVER_CONTEXT_TYPE = usePolyfill
42 + ? 0xeae6
43 + : Symbol.for('react.server_context');
44 +export const REACT_FORWARD_REF_TYPE = usePolyfill
45 + ? 0xead0
46 + : Symbol.for('react.forward_ref');
47 +export const REACT_SUSPENSE_TYPE = usePolyfill
48 + ? 0xead1
49 + : Symbol.for('react.suspense');
50 +export const REACT_SUSPENSE_LIST_TYPE = usePolyfill
51 + ? 0xead8
52 + : Symbol.for('react.suspense_list');
53 +export const REACT_MEMO_TYPE = usePolyfill ? 0xead3 : Symbol.for('react.memo');
54 +export const REACT_LAZY_TYPE = usePolyfill ? 0xead4 : Symbol.for('react.lazy');
55 +export const REACT_SCOPE_TYPE = usePolyfill
56 + ? 0xead7
57 + : Symbol.for('react.scope');
58 +export const REACT_DEBUG_TRACING_MODE_TYPE = usePolyfill
59 + ? 0xeae1
60 + : Symbol.for('react.debug_trace_mode');
61 +export const REACT_OFFSCREEN_TYPE = usePolyfill
62 + ? 0xeae2
63 + : Symbol.for('react.offscreen');
64 +export const REACT_LEGACY_HIDDEN_TYPE = usePolyfill
65 + ? 0xeae3
66 + : Symbol.for('react.legacy_hidden');
67 +export const REACT_CACHE_TYPE = usePolyfill
68 + ? 0xeae4
69 + : Symbol.for('react.cache');
70 +export const REACT_TRACING_MARKER_TYPE = usePolyfill
71 + ? 0xeae5
72 + : Symbol.for('react.tracing_marker');
73 +export const REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED = usePolyfill
74 + ? 0xeae7
75 + : Symbol.for('react.default_value');
76 +const MAYBE_ITERATOR_SYMBOL = usePolyfill
77 + ? typeof Symbol === 'function' && Symbol.iterator
78 + : Symbol.iterator;
79
40 -const MAYBE_ITERATOR_SYMBOL = Symbol.iterator;
80 const FAUX_ITERATOR_SYMBOL = '@@iterator';
81
82 export function getIteratorFn(maybeIterable: ?any): ?() => ?Iterator<*> {
packages/shared/__tests__/ReactSymbols-test.internal.js
+16
@@ -26,4 +26,20 @@ describe('ReactSymbols', () => {
26 it('Symbol values should be unique', () => {
27 expectToBeUnique(Object.entries(require('shared/ReactSymbols')));
28 });
29 +
30 + // @gate enableSymbolFallbackForWWW
31 + it('numeric values should be unique', () => {
32 + const originalSymbolFor = global.Symbol.for;
33 + global.Symbol.for = null;
34 + try {
35 + const entries = Object.entries(require('shared/ReactSymbols')).filter(
36 + // REACT_ASYNC_MODE_TYPE and REACT_CONCURRENT_MODE_TYPE have the same numeric value
37 + // for legacy backwards compatibility
38 + ([key]) => key !== 'REACT_ASYNC_MODE_TYPE',
39 + );
40 + expectToBeUnique(entries);
41 + } finally {
42 + global.Symbol.for = originalSymbolFor;
43 + }
44 + });
45 });
packages/shared/forks/ReactFeatureFlags.native-fb.js
+1
@@ -83,6 +83,7 @@ export const enableServerContext = false;
83 export const enableUseMutableSource = true;
84
85 export const enableTransitionTracing = false;
86 +export const enableSymbolFallbackForWWW = false;
87
88 // Flow magic to verify the exports of this file match the original version.
89 // eslint-disable-next-line no-unused-vars
packages/shared/forks/ReactFeatureFlags.native-oss.js
+1
@@ -74,6 +74,7 @@ export const enableServerContext = false;
74 export const enableUseMutableSource = false;
75
76 export const enableTransitionTracing = false;
77 +export const enableSymbolFallbackForWWW = false;
78
79 // Flow magic to verify the exports of this file match the original version.
80 // eslint-disable-next-line no-unused-vars
packages/shared/forks/ReactFeatureFlags.test-renderer.js
+1
@@ -74,6 +74,7 @@ export const enableServerContext = false;
74 export const enableUseMutableSource = false;
75
76 export const enableTransitionTracing = false;
77 +export const enableSymbolFallbackForWWW = false;
78
79 // Flow magic to verify the exports of this file match the original version.
80 // eslint-disable-next-line no-unused-vars
packages/shared/forks/ReactFeatureFlags.test-renderer.native.js
+1
@@ -72,6 +72,7 @@ export const enableServerContext = false;
72 export const enableUseMutableSource = false;
73
74 export const enableTransitionTracing = false;
75 +export const enableSymbolFallbackForWWW = false;
76
77 // Flow magic to verify the exports of this file match the original version.
78 // eslint-disable-next-line no-unused-vars
packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
+1
@@ -76,6 +76,7 @@ export const enableServerContext = false;
76 export const enableUseMutableSource = true;
77
78 export const enableTransitionTracing = false;
79 +export const enableSymbolFallbackForWWW = false;
80
81 // Flow magic to verify the exports of this file match the original version.
82 // eslint-disable-next-line no-unused-vars
packages/shared/forks/ReactFeatureFlags.testing.js
+1
@@ -74,6 +74,7 @@ export const enableServerContext = false;
74 export const enableUseMutableSource = false;
75
76 export const enableTransitionTracing = false;
77 +export const enableSymbolFallbackForWWW = false;
78
79 // Flow magic to verify the exports of this file match the original version.
80 // eslint-disable-next-line no-unused-vars
packages/shared/forks/ReactFeatureFlags.testing.www.js
+1
@@ -75,6 +75,7 @@ export const enableServerContext = false;
75 export const enableUseMutableSource = true;
76
77 export const enableTransitionTracing = false;
78 +export const enableSymbolFallbackForWWW = false;
79
80 // Flow magic to verify the exports of this file match the original version.
81 // eslint-disable-next-line no-unused-vars
packages/shared/forks/ReactFeatureFlags.www-dynamic.js
+1 -1
@@ -29,7 +29,7 @@ export const enableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay = __
29 export const enableClientRenderFallbackOnHydrationMismatch = __VARIANT__;
30 export const enableClientRenderFallbackOnTextMismatch = __VARIANT__;
31 export const enableTransitionTracing = __VARIANT__;
32 -
32 +export const enableSymbolFallbackForWWW = __VARIANT__;
33 // Enable this flag to help with concurrent mode debugging.
34 // It logs information to the console about React scheduling, rendering, and commit phases.
35 //
packages/shared/forks/ReactFeatureFlags.www.js
+1
@@ -115,6 +115,7 @@ export const enableCustomElementPropertySupport = __EXPERIMENTAL__;
115
116 export const enableTransitionTracing = false;
117
118 +export const enableSymbolFallbackForWWW = true;
119 // Flow magic to verify the exports of this file match the original version.
120 // eslint-disable-next-line no-unused-vars
121 type Check<_X, Y: _X, X: Y = _X> = null;
packages/shared/isValidElementType.js
+11 -1
@@ -31,9 +31,19 @@ import {
31 enableTransitionTracing,
32 enableDebugTracing,
33 enableLegacyHidden,
34 + enableSymbolFallbackForWWW,
35 } from './ReactFeatureFlags';
36
36 -const REACT_MODULE_REFERENCE: Symbol = Symbol.for('react.module.reference');
37 +let REACT_MODULE_REFERENCE;
38 +if (enableSymbolFallbackForWWW) {
39 + if (typeof Symbol === 'function') {
40 + REACT_MODULE_REFERENCE = Symbol.for('react.module.reference');
41 + } else {
42 + REACT_MODULE_REFERENCE = 0;
43 + }
44 +} else {
45 + REACT_MODULE_REFERENCE = Symbol.for('react.module.reference');
46 +}
47
48 export default function isValidElementType(type: mixed) {
49 if (typeof type === 'string' || typeof type === 'function') {