Temporarily feature flag numeric fallback for symbols

This commit is contained in:
Rick Hanlon
2022-04-13 11:02:17 -04:00
parent de516ca5a6
commit 59b6d0a80e
15 changed files with 328 additions and 27 deletions
@@ -9,16 +9,26 @@
'use strict';
import {enableSymbolFallbackForWWW} from 'shared/ReactFeatureFlags';
let React;
let ReactDOM;
let ReactTestUtils;
describe('ReactElement', () => {
let ComponentClass;
let originalSymbol;
beforeEach(() => {
jest.resetModules();
if (enableSymbolFallbackForWWW) {
// Delete the native Symbol if we have one to ensure we test the
// unpolyfilled environment.
originalSymbol = global.Symbol;
global.Symbol = undefined;
}
React = require('react');
ReactDOM = require('react-dom');
ReactTestUtils = require('react-dom/test-utils');
@@ -31,6 +41,17 @@ describe('ReactElement', () => {
};
});
afterEach(() => {
if (enableSymbolFallbackForWWW) {
global.Symbol = originalSymbol;
}
});
// @gate enableSymbolFallbackForWWW
it('uses the fallback value when in an environment without Symbol', () => {
expect((<div />).$$typeof).toBe(0xeac7);
});
it('returns a complete element according to spec', () => {
const element = React.createElement(ComponentClass);
expect(element.type).toBe(ComponentClass);
@@ -280,6 +301,42 @@ describe('ReactElement', () => {
expect(element.type.someStaticMethod()).toBe('someReturnValue');
});
// NOTE: We're explicitly not using JSX here. This is intended to test
// classic JS without JSX.
// @gate enableSymbolFallbackForWWW
it('identifies valid elements', () => {
class Component extends React.Component {
render() {
return React.createElement('div');
}
}
expect(React.isValidElement(React.createElement('div'))).toEqual(true);
expect(React.isValidElement(React.createElement(Component))).toEqual(true);
expect(React.isValidElement(null)).toEqual(false);
expect(React.isValidElement(true)).toEqual(false);
expect(React.isValidElement({})).toEqual(false);
expect(React.isValidElement('string')).toEqual(false);
if (!__EXPERIMENTAL__) {
let factory;
expect(() => {
factory = React.createFactory('div');
}).toWarnDev(
'Warning: React.createFactory() is deprecated and will be removed in a ' +
'future major release. Consider using JSX or use React.createElement() ' +
'directly instead.',
{withoutStack: true},
);
expect(React.isValidElement(factory)).toEqual(false);
}
expect(React.isValidElement(Component)).toEqual(false);
expect(React.isValidElement({type: 'div', props: {}})).toEqual(false);
const jsonElement = JSON.stringify(React.createElement('div'));
expect(React.isValidElement(JSON.parse(jsonElement))).toBe(true);
});
// NOTE: We're explicitly not using JSX here. This is intended to test
// classic JS without JSX.
it('is indistinguishable from a plain object', () => {
@@ -397,6 +454,7 @@ describe('ReactElement', () => {
// NOTE: We're explicitly not using JSX here. This is intended to test
// classic JS without JSX.
// @gate !enableSymbolFallbackForWWW
it('identifies elements, but not JSON, if Symbols are supported', () => {
class Component extends React.Component {
render() {
@@ -429,4 +487,58 @@ describe('ReactElement', () => {
const jsonElement = JSON.stringify(React.createElement('div'));
expect(React.isValidElement(JSON.parse(jsonElement))).toBe(false);
});
// NOTE: We're explicitly not using JSX here. This is intended to test
// classic JS without JSX.
it('identifies elements, but not JSON, if Symbols are supported (with polyfill)', () => {
// Rudimentary polyfill
// Once all jest engines support Symbols natively we can swap this to test
// WITH native Symbols by default.
const REACT_ELEMENT_TYPE = function() {}; // fake Symbol
const OTHER_SYMBOL = function() {}; // another fake Symbol
global.Symbol = function(name) {
return OTHER_SYMBOL;
};
global.Symbol.for = function(key) {
if (key === 'react.element') {
return REACT_ELEMENT_TYPE;
}
return OTHER_SYMBOL;
};
jest.resetModules();
React = require('react');
class Component extends React.Component {
render() {
return React.createElement('div');
}
}
expect(React.isValidElement(React.createElement('div'))).toEqual(true);
expect(React.isValidElement(React.createElement(Component))).toEqual(true);
expect(React.isValidElement(null)).toEqual(false);
expect(React.isValidElement(true)).toEqual(false);
expect(React.isValidElement({})).toEqual(false);
expect(React.isValidElement('string')).toEqual(false);
if (!__EXPERIMENTAL__) {
let factory;
expect(() => {
factory = React.createFactory('div');
}).toWarnDev(
'Warning: React.createFactory() is deprecated and will be removed in a ' +
'future major release. Consider using JSX or use React.createElement() ' +
'directly instead.',
{withoutStack: true},
);
expect(React.isValidElement(factory)).toEqual(false);
}
expect(React.isValidElement(Component)).toEqual(false);
expect(React.isValidElement({type: 'div', props: {}})).toEqual(false);
const jsonElement = JSON.stringify(React.createElement('div'));
expect(React.isValidElement(JSON.parse(jsonElement))).toBe(false);
});
});
@@ -9,6 +9,8 @@
'use strict';
import {enableSymbolFallbackForWWW} from 'shared/ReactFeatureFlags';
let React;
let ReactDOM;
let ReactTestUtils;
@@ -20,9 +22,18 @@ let JSXDEVRuntime;
// A lot of these tests are pulled from ReactElement-test because
// this api is meant to be backwards compatible.
describe('ReactElement.jsx', () => {
let originalSymbol;
beforeEach(() => {
jest.resetModules();
if (enableSymbolFallbackForWWW) {
// Delete the native Symbol if we have one to ensure we test the
// unpolyfilled environment.
originalSymbol = global.Symbol;
global.Symbol = undefined;
}
React = require('react');
JSXRuntime = require('react/jsx-runtime');
JSXDEVRuntime = require('react/jsx-dev-runtime');
@@ -30,6 +41,12 @@ describe('ReactElement.jsx', () => {
ReactTestUtils = require('react-dom/test-utils');
});
afterEach(() => {
if (enableSymbolFallbackForWWW) {
global.Symbol = originalSymbol;
}
});
it('allows static methods to be called using the type property', () => {
class StaticMethodComponentClass extends React.Component {
render() {
@@ -42,6 +59,48 @@ describe('ReactElement.jsx', () => {
expect(element.type.someStaticMethod()).toBe('someReturnValue');
});
// @gate enableSymbolFallbackForWWW
it('identifies valid elements', () => {
class Component extends React.Component {
render() {
return JSXRuntime.jsx('div', {});
}
}
expect(React.isValidElement(JSXRuntime.jsx('div', {}))).toEqual(true);
expect(React.isValidElement(JSXRuntime.jsx(Component, {}))).toEqual(true);
expect(
React.isValidElement(JSXRuntime.jsx(JSXRuntime.Fragment, {})),
).toEqual(true);
if (__DEV__) {
expect(React.isValidElement(JSXDEVRuntime.jsxDEV('div', {}))).toEqual(
true,
);
}
expect(React.isValidElement(null)).toEqual(false);
expect(React.isValidElement(true)).toEqual(false);
expect(React.isValidElement({})).toEqual(false);
expect(React.isValidElement('string')).toEqual(false);
if (!__EXPERIMENTAL__) {
let factory;
expect(() => {
factory = React.createFactory('div');
}).toWarnDev(
'Warning: React.createFactory() is deprecated and will be removed in a ' +
'future major release. Consider using JSX or use React.createElement() ' +
'directly instead.',
{withoutStack: true},
);
expect(React.isValidElement(factory)).toEqual(false);
}
expect(React.isValidElement(Component)).toEqual(false);
expect(React.isValidElement({type: 'div', props: {}})).toEqual(false);
const jsonElement = JSON.stringify(JSXRuntime.jsx('div', {}));
expect(React.isValidElement(JSON.parse(jsonElement))).toBe(true);
});
it('is indistinguishable from a plain object', () => {
const element = JSXRuntime.jsx('div', {className: 'foo'});
const object = {};
@@ -235,6 +294,7 @@ describe('ReactElement.jsx', () => {
);
});
// @gate !enableSymbolFallbackForWWW
it('identifies elements, but not JSON, if Symbols are supported', () => {
class Component extends React.Component {
render() {
@@ -276,6 +336,59 @@ describe('ReactElement.jsx', () => {
expect(React.isValidElement(JSON.parse(jsonElement))).toBe(false);
});
it('identifies elements, but not JSON, if Symbols are polyfilled', () => {
// Rudimentary polyfill
// Once all jest engines support Symbols natively we can swap this to test
// WITH native Symbols by default.
const REACT_ELEMENT_TYPE = function() {}; // fake Symbol
const OTHER_SYMBOL = function() {}; // another fake Symbol
global.Symbol = function(name) {
return OTHER_SYMBOL;
};
global.Symbol.for = function(key) {
if (key === 'react.element') {
return REACT_ELEMENT_TYPE;
}
return OTHER_SYMBOL;
};
jest.resetModules();
React = require('react');
JSXRuntime = require('react/jsx-runtime');
class Component extends React.Component {
render() {
return JSXRuntime.jsx('div');
}
}
expect(React.isValidElement(JSXRuntime.jsx('div', {}))).toEqual(true);
expect(React.isValidElement(JSXRuntime.jsx(Component, {}))).toEqual(true);
expect(React.isValidElement(null)).toEqual(false);
expect(React.isValidElement(true)).toEqual(false);
expect(React.isValidElement({})).toEqual(false);
expect(React.isValidElement('string')).toEqual(false);
if (!__EXPERIMENTAL__) {
let factory;
expect(() => {
factory = React.createFactory('div');
}).toWarnDev(
'Warning: React.createFactory() is deprecated and will be removed in a ' +
'future major release. Consider using JSX or use React.createElement() ' +
'directly instead.',
{withoutStack: true},
);
expect(React.isValidElement(factory)).toEqual(false);
}
expect(React.isValidElement(Component)).toEqual(false);
expect(React.isValidElement({type: 'div', props: {}})).toEqual(false);
const jsonElement = JSON.stringify(JSXRuntime.jsx('div', {}));
expect(React.isValidElement(JSON.parse(jsonElement))).toBe(false);
});
it('should warn when unkeyed children are passed to jsx', () => {
const container = document.createElement('div');
+3
View File
@@ -28,6 +28,9 @@ export const enablePersistentOffscreenHostContainer = false;
// like migrating internal callers or performance testing.
// -----------------------------------------------------------------------------
// This is blocked on adding a symbol polyfill to www.
export const enableSymbolFallbackForWWW = false;
// This rolled out to 10% public in www, so we should be able to land, but some
// internal tests need to be updated. The open source behavior is correct.
export const skipUnmountedBoundaries = true;
+64 -25
View File
@@ -11,33 +11,72 @@
// When adding new symbols to this file,
// Please consider also adding to 'react-devtools-shared/src/backend/ReactSymbols'
import {enableSymbolFallbackForWWW} from './ReactFeatureFlags';
const usePolyfill =
enableSymbolFallbackForWWW && (typeof Symbol !== 'function' || !Symbol.for);
// The Symbol used to tag the ReactElement-like types.
export const REACT_ELEMENT_TYPE = Symbol.for('react.element');
export const REACT_PORTAL_TYPE = Symbol.for('react.portal');
export const REACT_FRAGMENT_TYPE = Symbol.for('react.fragment');
export const REACT_STRICT_MODE_TYPE = Symbol.for('react.strict_mode');
export const REACT_PROFILER_TYPE = Symbol.for('react.profiler');
export const REACT_PROVIDER_TYPE = Symbol.for('react.provider');
export const REACT_CONTEXT_TYPE = Symbol.for('react.context');
export const REACT_SERVER_CONTEXT_TYPE = Symbol.for('react.server_context');
export const REACT_FORWARD_REF_TYPE = Symbol.for('react.forward_ref');
export const REACT_SUSPENSE_TYPE = Symbol.for('react.suspense');
export const REACT_SUSPENSE_LIST_TYPE = Symbol.for('react.suspense_list');
export const REACT_MEMO_TYPE = Symbol.for('react.memo');
export const REACT_LAZY_TYPE = Symbol.for('react.lazy');
export const REACT_SCOPE_TYPE = Symbol.for('react.scope');
export const REACT_DEBUG_TRACING_MODE_TYPE = Symbol.for(
'react.debug_trace_mode',
);
export const REACT_OFFSCREEN_TYPE = Symbol.for('react.offscreen');
export const REACT_LEGACY_HIDDEN_TYPE = Symbol.for('react.legacy_hidden');
export const REACT_CACHE_TYPE = Symbol.for('react.cache');
export const REACT_TRACING_MARKER_TYPE = Symbol.for('react.tracing_marker');
export const REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED = Symbol.for(
'react.default_value',
);
export const REACT_ELEMENT_TYPE = usePolyfill
? 0xeac7
: Symbol.for('react.element');
export const REACT_PORTAL_TYPE = usePolyfill
? 0xeaca
: Symbol.for('react.portal');
export const REACT_FRAGMENT_TYPE = usePolyfill
? 0xeacb
: Symbol.for('react.fragment');
export const REACT_STRICT_MODE_TYPE = usePolyfill
? 0xeacc
: Symbol.for('react.strict_mode');
export const REACT_PROFILER_TYPE = usePolyfill
? 0xead2
: Symbol.for('react.profiler');
export const REACT_PROVIDER_TYPE = usePolyfill
? 0xeacd
: Symbol.for('react.provider');
export const REACT_CONTEXT_TYPE = usePolyfill
? 0xeace
: Symbol.for('react.context');
export const REACT_SERVER_CONTEXT_TYPE = usePolyfill
? 0xeae6
: Symbol.for('react.server_context');
export const REACT_FORWARD_REF_TYPE = usePolyfill
? 0xead0
: Symbol.for('react.forward_ref');
export const REACT_SUSPENSE_TYPE = usePolyfill
? 0xead1
: Symbol.for('react.suspense');
export const REACT_SUSPENSE_LIST_TYPE = usePolyfill
? 0xead8
: Symbol.for('react.suspense_list');
export const REACT_MEMO_TYPE = usePolyfill ? 0xead3 : Symbol.for('react.memo');
export const REACT_LAZY_TYPE = usePolyfill ? 0xead4 : Symbol.for('react.lazy');
export const REACT_SCOPE_TYPE = usePolyfill
? 0xead7
: Symbol.for('react.scope');
export const REACT_DEBUG_TRACING_MODE_TYPE = usePolyfill
? 0xeae1
: Symbol.for('react.debug_trace_mode');
export const REACT_OFFSCREEN_TYPE = usePolyfill
? 0xeae2
: Symbol.for('react.offscreen');
export const REACT_LEGACY_HIDDEN_TYPE = usePolyfill
? 0xeae3
: Symbol.for('react.legacy_hidden');
export const REACT_CACHE_TYPE = usePolyfill
? 0xeae4
: Symbol.for('react.cache');
export const REACT_TRACING_MARKER_TYPE = usePolyfill
? 0xeae5
: Symbol.for('react.tracing_marker');
export const REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED = usePolyfill
? 0xeae7
: Symbol.for('react.default_value');
const MAYBE_ITERATOR_SYMBOL = usePolyfill
? typeof Symbol === 'function' && Symbol.iterator
: Symbol.iterator;
const MAYBE_ITERATOR_SYMBOL = Symbol.iterator;
const FAUX_ITERATOR_SYMBOL = '@@iterator';
export function getIteratorFn(maybeIterable: ?any): ?() => ?Iterator<*> {
@@ -26,4 +26,20 @@ describe('ReactSymbols', () => {
it('Symbol values should be unique', () => {
expectToBeUnique(Object.entries(require('shared/ReactSymbols')));
});
// @gate enableSymbolFallbackForWWW
it('numeric values should be unique', () => {
const originalSymbolFor = global.Symbol.for;
global.Symbol.for = null;
try {
const entries = Object.entries(require('shared/ReactSymbols')).filter(
// REACT_ASYNC_MODE_TYPE and REACT_CONCURRENT_MODE_TYPE have the same numeric value
// for legacy backwards compatibility
([key]) => key !== 'REACT_ASYNC_MODE_TYPE',
);
expectToBeUnique(entries);
} finally {
global.Symbol.for = originalSymbolFor;
}
});
});
@@ -83,6 +83,7 @@ export const enableServerContext = false;
export const enableUseMutableSource = true;
export const enableTransitionTracing = false;
export const enableSymbolFallbackForWWW = false;
// Flow magic to verify the exports of this file match the original version.
// eslint-disable-next-line no-unused-vars
@@ -74,6 +74,7 @@ export const enableServerContext = false;
export const enableUseMutableSource = false;
export const enableTransitionTracing = false;
export const enableSymbolFallbackForWWW = false;
// Flow magic to verify the exports of this file match the original version.
// eslint-disable-next-line no-unused-vars
@@ -74,6 +74,7 @@ export const enableServerContext = false;
export const enableUseMutableSource = false;
export const enableTransitionTracing = false;
export const enableSymbolFallbackForWWW = false;
// Flow magic to verify the exports of this file match the original version.
// eslint-disable-next-line no-unused-vars
@@ -72,6 +72,7 @@ export const enableServerContext = false;
export const enableUseMutableSource = false;
export const enableTransitionTracing = false;
export const enableSymbolFallbackForWWW = false;
// Flow magic to verify the exports of this file match the original version.
// eslint-disable-next-line no-unused-vars
@@ -76,6 +76,7 @@ export const enableServerContext = false;
export const enableUseMutableSource = true;
export const enableTransitionTracing = false;
export const enableSymbolFallbackForWWW = false;
// Flow magic to verify the exports of this file match the original version.
// eslint-disable-next-line no-unused-vars
@@ -74,6 +74,7 @@ export const enableServerContext = false;
export const enableUseMutableSource = false;
export const enableTransitionTracing = false;
export const enableSymbolFallbackForWWW = false;
// Flow magic to verify the exports of this file match the original version.
// eslint-disable-next-line no-unused-vars
@@ -75,6 +75,7 @@ export const enableServerContext = false;
export const enableUseMutableSource = true;
export const enableTransitionTracing = false;
export const enableSymbolFallbackForWWW = false;
// Flow magic to verify the exports of this file match the original version.
// eslint-disable-next-line no-unused-vars
@@ -29,7 +29,7 @@ export const enableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay = __
export const enableClientRenderFallbackOnHydrationMismatch = __VARIANT__;
export const enableClientRenderFallbackOnTextMismatch = __VARIANT__;
export const enableTransitionTracing = __VARIANT__;
export const enableSymbolFallbackForWWW = __VARIANT__;
// Enable this flag to help with concurrent mode debugging.
// It logs information to the console about React scheduling, rendering, and commit phases.
//
@@ -115,6 +115,7 @@ export const enableCustomElementPropertySupport = __EXPERIMENTAL__;
export const enableTransitionTracing = false;
export const enableSymbolFallbackForWWW = true;
// Flow magic to verify the exports of this file match the original version.
// eslint-disable-next-line no-unused-vars
type Check<_X, Y: _X, X: Y = _X> = null;
+11 -1
View File
@@ -31,9 +31,19 @@ import {
enableTransitionTracing,
enableDebugTracing,
enableLegacyHidden,
enableSymbolFallbackForWWW,
} from './ReactFeatureFlags';
const REACT_MODULE_REFERENCE: Symbol = Symbol.for('react.module.reference');
let REACT_MODULE_REFERENCE;
if (enableSymbolFallbackForWWW) {
if (typeof Symbol === 'function') {
REACT_MODULE_REFERENCE = Symbol.for('react.module.reference');
} else {
REACT_MODULE_REFERENCE = 0;
}
} else {
REACT_MODULE_REFERENCE = Symbol.for('react.module.reference');
}
export default function isValidElementType(type: mixed) {
if (typeof type === 'string' || typeof type === 'function') {