From 1d41dd0d8d78f23f37fd17e5c10db9722ed95ce0 Mon Sep 17 00:00:00 2001 From: Andrew Clark Date: Sat, 11 Mar 2023 01:03:46 -0500 Subject: [PATCH] Add disableLegacyContext test gates where needed The www builds include disableLegacyContext as a dynamic flag, so we should be running the tests in that mode, too. Previously we were overriding the flag during the test run. This strategy usually doesn't work because the flags get compiled out in the final build, but we happen to not test www in build mode, only source. To get of this hacky override, I added a test gate to every test that uses legacy context. When we eventually remove legacy context from the codebase, this should make it slightly easier to find which tests are affected. And removes one more hack from our hack-ridden test config. Given that sometimes www has features enabled that aren't on in other builds, we might want to consider testing its build artifacts in CI, rather than just source. That would have forced this cleanup to happen sooner. Currently we only test the public builds in CI. --- .../__tests__/ReactCompositeComponent-test.js | 8 + .../src/__tests__/ReactDOMFiber-test.js | 3 + .../src/__tests__/ReactDOMFizzServer-test.js | 1 + ...tDOMServerIntegrationLegacyContext-test.js | 7 + .../ReactErrorBoundaries-test.internal.js | 73 +++++---- .../__tests__/ReactFunctionComponent-test.js | 3 + ...eactLegacyErrorBoundaries-test.internal.js | 1 + .../__tests__/ReactServerRendering-test.js | 1 + .../renderSubtreeIntoContainer-test.js | 5 + .../ReactNativeEvents-test.internal.js | 1 + .../src/__tests__/ReactIncremental-test.js | 94 ++++++----- ...tIncrementalErrorHandling-test.internal.js | 6 + .../src/__tests__/ReactNewContext-test.js | 1 + .../ReactCoffeeScriptClass-test.coffee | 141 ++++++++-------- .../__tests__/ReactContextValidator-test.js | 7 + .../react/src/__tests__/ReactES6Class-test.js | 154 +++++++++--------- .../ReactJSXElementValidator-test.js | 1 + .../src/__tests__/ReactStrictMode-test.js | 1 + .../__tests__/ReactTypeScriptClass-test.ts | 61 ++++--- .../createReactClassIntegration-test.js | 1 + scripts/jest/setupTests.www.js | 1 - 21 files changed, 326 insertions(+), 245 deletions(-) diff --git a/packages/react-dom/src/__tests__/ReactCompositeComponent-test.js b/packages/react-dom/src/__tests__/ReactCompositeComponent-test.js index 0595f868b14fd..bf91fb08dda10 100644 --- a/packages/react-dom/src/__tests__/ReactCompositeComponent-test.js +++ b/packages/react-dom/src/__tests__/ReactCompositeComponent-test.js @@ -587,6 +587,7 @@ describe('ReactCompositeComponent', () => { ); }); + // @gate !disableLegacyContext it('should pass context to children when not owner', () => { class Parent extends React.Component { render() { @@ -652,6 +653,7 @@ describe('ReactCompositeComponent', () => { expect(childRenders).toBe(1); }); + // @gate !disableLegacyContext it('should pass context when re-rendered for static child', () => { let parentInstance = null; let childInstance = null; @@ -712,6 +714,7 @@ describe('ReactCompositeComponent', () => { expect(childInstance.context).toEqual({foo: 'bar', flag: true}); }); + // @gate !disableLegacyContext it('should pass context when re-rendered for static child within a composite component', () => { class Parent extends React.Component { static childContextTypes = { @@ -768,6 +771,7 @@ describe('ReactCompositeComponent', () => { expect(wrapper.childRef.current.context).toEqual({flag: false}); }); + // @gate !disableLegacyContext it('should pass context transitively', () => { let childInstance = null; let grandchildInstance = null; @@ -829,6 +833,7 @@ describe('ReactCompositeComponent', () => { expect(grandchildInstance.context).toEqual({foo: 'bar', depth: 1}); }); + // @gate !disableLegacyContext it('should pass context when re-rendered', () => { let parentInstance = null; let childInstance = null; @@ -883,6 +888,7 @@ describe('ReactCompositeComponent', () => { expect(childInstance.context).toEqual({foo: 'bar', depth: 0}); }); + // @gate !disableLegacyContext it('unmasked context propagates through updates', () => { class Leaf extends React.Component { static contextTypes = { @@ -946,6 +952,7 @@ describe('ReactCompositeComponent', () => { expect(div.children[0].id).toBe('aliens'); }); + // @gate !disableLegacyContext it('should trigger componentWillReceiveProps for context changes', () => { let contextChanges = 0; let propChanges = 0; @@ -1219,6 +1226,7 @@ describe('ReactCompositeComponent', () => { expect(a).toBe(b); }); + // @gate !disableLegacyContext || !__DEV__ it('context should be passed down from the parent', () => { class Parent extends React.Component { static childContextTypes = { diff --git a/packages/react-dom/src/__tests__/ReactDOMFiber-test.js b/packages/react-dom/src/__tests__/ReactDOMFiber-test.js index 865337e8b5470..06773b68f1f55 100644 --- a/packages/react-dom/src/__tests__/ReactDOMFiber-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMFiber-test.js @@ -718,6 +718,7 @@ describe('ReactDOMFiber', () => { ); }); + // @gate !disableLegacyContext it('should pass portal context when rendering subtree elsewhere', () => { const portalContainer = document.createElement('div'); @@ -752,6 +753,7 @@ describe('ReactDOMFiber', () => { expect(portalContainer.innerHTML).toBe('
bar
'); }); + // @gate !disableLegacyContext it('should update portal context if it changes due to setState', () => { const portalContainer = document.createElement('div'); @@ -796,6 +798,7 @@ describe('ReactDOMFiber', () => { expect(container.innerHTML).toBe(''); }); + // @gate !disableLegacyContext it('should update portal context if it changes due to re-render', () => { const portalContainer = document.createElement('div'); diff --git a/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js b/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js index d4c5a0bf8f298..e745835c1601a 100644 --- a/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js @@ -1624,6 +1624,7 @@ describe('ReactDOMFizzServer', () => { } }); + // @gate !disableLegacyContext it('should can suspend in a class component with legacy context', async () => { class TestProvider extends React.Component { static childContextTypes = { diff --git a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationLegacyContext-test.js b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationLegacyContext-test.js index 5681f0806b434..1481fcd326bb0 100644 --- a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationLegacyContext-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationLegacyContext-test.js @@ -44,6 +44,13 @@ describe('ReactDOMServerIntegration', () => { }); describe('legacy context', function () { + // The `itRenders` test abstraction doesn't work with @gate so we have + // to do this instead. + if (gate(flags => flags.disableLegacyContext)) { + test('empty test to stop Jest from being a complainy complainer', () => {}); + return; + } + let PurpleContext, RedContext; beforeEach(() => { class Parent extends React.Component { diff --git a/packages/react-dom/src/__tests__/ReactErrorBoundaries-test.internal.js b/packages/react-dom/src/__tests__/ReactErrorBoundaries-test.internal.js index 04cb8bbc1e492..1431205b28f36 100644 --- a/packages/react-dom/src/__tests__/ReactErrorBoundaries-test.internal.js +++ b/packages/react-dom/src/__tests__/ReactErrorBoundaries-test.internal.js @@ -775,6 +775,7 @@ describe('ReactErrorBoundaries', () => { assertLog(['ErrorBoundary componentWillUnmount']); }); + // @gate !disableLegacyContext || !__DEV__ it('renders an error state if context provider throws in componentWillMount', () => { class BrokenComponentWillMountWithContext extends React.Component { static childContextTypes = {foo: PropTypes.number}; @@ -799,45 +800,45 @@ describe('ReactErrorBoundaries', () => { expect(container.firstChild.textContent).toBe('Caught an error: Hello.'); }); - if (!require('shared/ReactFeatureFlags').disableModulePatternComponents) { - it('renders an error state if module-style context provider throws in componentWillMount', () => { - function BrokenComponentWillMountWithContext() { - return { - getChildContext() { - return {foo: 42}; - }, - render() { - return
{this.props.children}
; - }, - UNSAFE_componentWillMount() { - throw new Error('Hello'); - }, - }; - } - BrokenComponentWillMountWithContext.childContextTypes = { - foo: PropTypes.number, + // @gate !disableModulePatternComponents + // @gate !disableLegacyContext + it('renders an error state if module-style context provider throws in componentWillMount', () => { + function BrokenComponentWillMountWithContext() { + return { + getChildContext() { + return {foo: 42}; + }, + render() { + return
{this.props.children}
; + }, + UNSAFE_componentWillMount() { + throw new Error('Hello'); + }, }; + } + BrokenComponentWillMountWithContext.childContextTypes = { + foo: PropTypes.number, + }; - const container = document.createElement('div'); - expect(() => - ReactDOM.render( - - - , - container, - ), - ).toErrorDev( - 'Warning: The component appears to be a function component that ' + - 'returns a class instance. ' + - 'Change BrokenComponentWillMountWithContext to a class that extends React.Component instead. ' + - "If you can't use a class try assigning the prototype on the function as a workaround. " + - '`BrokenComponentWillMountWithContext.prototype = React.Component.prototype`. ' + - "Don't use an arrow function since it cannot be called with `new` by React.", - ); + const container = document.createElement('div'); + expect(() => + ReactDOM.render( + + + , + container, + ), + ).toErrorDev( + 'Warning: The component appears to be a function component that ' + + 'returns a class instance. ' + + 'Change BrokenComponentWillMountWithContext to a class that extends React.Component instead. ' + + "If you can't use a class try assigning the prototype on the function as a workaround. " + + '`BrokenComponentWillMountWithContext.prototype = React.Component.prototype`. ' + + "Don't use an arrow function since it cannot be called with `new` by React.", + ); - expect(container.firstChild.textContent).toBe('Caught an error: Hello.'); - }); - } + expect(container.firstChild.textContent).toBe('Caught an error: Hello.'); + }); it('mounts the error message if mounting fails', () => { function renderError(error) { diff --git a/packages/react-dom/src/__tests__/ReactFunctionComponent-test.js b/packages/react-dom/src/__tests__/ReactFunctionComponent-test.js index f3bdb85ab7185..b30293f548b50 100644 --- a/packages/react-dom/src/__tests__/ReactFunctionComponent-test.js +++ b/packages/react-dom/src/__tests__/ReactFunctionComponent-test.js @@ -59,6 +59,7 @@ describe('ReactFunctionComponent', () => { expect(container.textContent).toBe(''); }); + // @gate !disableLegacyContext it('should pass context thru stateless component', () => { class Child extends React.Component { static contextTypes = { @@ -305,6 +306,7 @@ describe('ReactFunctionComponent', () => { // This guards against a regression caused by clearing the current debug fiber. // https://github.com/facebook/react/issues/10831 + // @gate !disableLegacyContext || !__DEV__ it('should warn when giving a function ref with context', () => { function Child() { return null; @@ -375,6 +377,7 @@ describe('ReactFunctionComponent', () => { ]); }); + // @gate !disableLegacyContext it('should receive context', () => { class Parent extends React.Component { static childContextTypes = { diff --git a/packages/react-dom/src/__tests__/ReactLegacyErrorBoundaries-test.internal.js b/packages/react-dom/src/__tests__/ReactLegacyErrorBoundaries-test.internal.js index f833ed39ed948..fb5c827b3ae27 100644 --- a/packages/react-dom/src/__tests__/ReactLegacyErrorBoundaries-test.internal.js +++ b/packages/react-dom/src/__tests__/ReactLegacyErrorBoundaries-test.internal.js @@ -800,6 +800,7 @@ describe('ReactLegacyErrorBoundaries', () => { expect(log).toEqual(['ErrorBoundary componentWillUnmount']); }); + // @gate !disableLegacyContext || !__DEV__ it('renders an error state if context provider throws in componentWillMount', () => { class BrokenComponentWillMountWithContext extends React.Component { static childContextTypes = {foo: PropTypes.number}; diff --git a/packages/react-dom/src/__tests__/ReactServerRendering-test.js b/packages/react-dom/src/__tests__/ReactServerRendering-test.js index 40c8248ee14c6..02f02187cb351 100644 --- a/packages/react-dom/src/__tests__/ReactServerRendering-test.js +++ b/packages/react-dom/src/__tests__/ReactServerRendering-test.js @@ -340,6 +340,7 @@ describe('ReactDOMServer', () => { expect(markup).toContain('hello, world'); }); + // @gate !disableLegacyContext it('renders with context when using custom constructor', () => { class Component extends React.Component { constructor() { diff --git a/packages/react-dom/src/__tests__/renderSubtreeIntoContainer-test.js b/packages/react-dom/src/__tests__/renderSubtreeIntoContainer-test.js index 288a72818e6bb..49993b556ea22 100644 --- a/packages/react-dom/src/__tests__/renderSubtreeIntoContainer-test.js +++ b/packages/react-dom/src/__tests__/renderSubtreeIntoContainer-test.js @@ -17,6 +17,7 @@ const renderSubtreeIntoContainer = require('react-dom').unstable_renderSubtreeIntoContainer; describe('renderSubtreeIntoContainer', () => { + // @gate !disableLegacyContext it('should pass context when rendering subtree elsewhere', () => { const portal = document.createElement('div'); @@ -99,6 +100,7 @@ describe('renderSubtreeIntoContainer', () => { } }); + // @gate !disableLegacyContext it('should update context if it changes due to setState', () => { const container = document.createElement('div'); document.body.appendChild(container); @@ -159,6 +161,7 @@ describe('renderSubtreeIntoContainer', () => { expect(portal.firstChild.innerHTML).toBe('changed-changed'); }); + // @gate !disableLegacyContext it('should update context if it changes due to re-render', () => { const container = document.createElement('div'); document.body.appendChild(container); @@ -238,6 +241,7 @@ describe('renderSubtreeIntoContainer', () => { expect(portal.firstChild.innerHTML).toBe('hello'); }); + // @gate !disableLegacyContext it('should get context through non-context-provider parent', () => { const container = document.createElement('div'); document.body.appendChild(container); @@ -281,6 +285,7 @@ describe('renderSubtreeIntoContainer', () => { expect(portal.textContent).toBe('foo'); }); + // @gate !disableLegacyContext it('should get context through middle non-context-provider layer', () => { const container = document.createElement('div'); document.body.appendChild(container); diff --git a/packages/react-native-renderer/src/__tests__/ReactNativeEvents-test.internal.js b/packages/react-native-renderer/src/__tests__/ReactNativeEvents-test.internal.js index c044be590a8f6..56a1d6fb7c1a9 100644 --- a/packages/react-native-renderer/src/__tests__/ReactNativeEvents-test.internal.js +++ b/packages/react-native-renderer/src/__tests__/ReactNativeEvents-test.internal.js @@ -197,6 +197,7 @@ it('handles events', () => { ]); }); +// @gate !disableLegacyContext || !__DEV__ it('handles events on text nodes', () => { expect(RCTEventEmitter.register).toHaveBeenCalledTimes(1); const EventEmitter = RCTEventEmitter.register.mock.calls[0][0]; diff --git a/packages/react-reconciler/src/__tests__/ReactIncremental-test.js b/packages/react-reconciler/src/__tests__/ReactIncremental-test.js index 885955273c967..fbfaba83ff187 100644 --- a/packages/react-reconciler/src/__tests__/ReactIncremental-test.js +++ b/packages/react-reconciler/src/__tests__/ReactIncremental-test.js @@ -1683,6 +1683,7 @@ describe('ReactIncremental', () => { expect(instance.state.n).toEqual(3); }); + // @gate !disableLegacyContext it('merges and masks context', async () => { class Intl extends React.Component { static childContextTypes = { @@ -1830,7 +1831,11 @@ describe('ReactIncremental', () => { ]); }); + // @gate !disableLegacyContext it('does not leak own context into context provider', async () => { + if (gate(flags => flags.disableLegacyContext)) { + throw new Error('This test infinite loops when context is disabled.'); + } class Recurse extends React.Component { static contextTypes = { n: PropTypes.number, @@ -1859,49 +1864,50 @@ describe('ReactIncremental', () => { ]); }); - if (!require('shared/ReactFeatureFlags').disableModulePatternComponents) { - it('does not leak own context into context provider (factory components)', async () => { - function Recurse(props, context) { - return { - getChildContext() { - return {n: (context.n || 3) - 1}; - }, - render() { - Scheduler.log('Recurse ' + JSON.stringify(context)); - if (context.n === 0) { - return null; - } - return ; - }, - }; - } - Recurse.contextTypes = { - n: PropTypes.number, - }; - Recurse.childContextTypes = { - n: PropTypes.number, + // @gate !disableModulePatternComponents + // @gate !disableLegacyContext + it('does not leak own context into context provider (factory components)', async () => { + function Recurse(props, context) { + return { + getChildContext() { + return {n: (context.n || 3) - 1}; + }, + render() { + Scheduler.log('Recurse ' + JSON.stringify(context)); + if (context.n === 0) { + return null; + } + return ; + }, }; + } + Recurse.contextTypes = { + n: PropTypes.number, + }; + Recurse.childContextTypes = { + n: PropTypes.number, + }; - ReactNoop.render(); - await expect( - async () => - await waitForAll([ - 'Recurse {}', - 'Recurse {"n":2}', - 'Recurse {"n":1}', - 'Recurse {"n":0}', - ]), - ).toErrorDev([ - 'Warning: The component appears to be a function component that returns a class instance. ' + - 'Change Recurse to a class that extends React.Component instead. ' + - "If you can't use a class try assigning the prototype on the function as a workaround. " + - '`Recurse.prototype = React.Component.prototype`. ' + - "Don't use an arrow function since it cannot be called with `new` by React.", - ]); - }); - } + ReactNoop.render(); + await expect( + async () => + await waitForAll([ + 'Recurse {}', + 'Recurse {"n":2}', + 'Recurse {"n":1}', + 'Recurse {"n":0}', + ]), + ).toErrorDev([ + 'Warning: The component appears to be a function component that returns a class instance. ' + + 'Change Recurse to a class that extends React.Component instead. ' + + "If you can't use a class try assigning the prototype on the function as a workaround. " + + '`Recurse.prototype = React.Component.prototype`. ' + + "Don't use an arrow function since it cannot be called with `new` by React.", + ]); + }); // @gate www + // @gate !disableLegacyContext it('provides context when reusing work', async () => { class Intl extends React.Component { static childContextTypes = { @@ -1955,6 +1961,7 @@ describe('ReactIncremental', () => { ]); }); + // @gate !disableLegacyContext it('reads context when setState is below the provider', async () => { let statefulInst; @@ -2041,6 +2048,7 @@ describe('ReactIncremental', () => { assertLog([]); }); + // @gate !disableLegacyContext it('reads context when setState is above the provider', async () => { let statefulInst; @@ -2135,6 +2143,7 @@ describe('ReactIncremental', () => { ]); }); + // @gate !disableLegacyContext || !__DEV__ it('maintains the correct context when providers bail out due to low priority', async () => { class Root extends React.Component { render() { @@ -2178,6 +2187,7 @@ describe('ReactIncremental', () => { await waitForAll([]); }); + // @gate !disableLegacyContext || !__DEV__ it('maintains the correct context when unwinding due to an error in render', async () => { class Root extends React.Component { componentDidCatch(error) { @@ -2229,6 +2239,7 @@ describe('ReactIncremental', () => { ); }); + // @gate !disableLegacyContext || !__DEV__ it('should not recreate masked context unless inputs have changed', async () => { let scuCounter = 0; @@ -2354,6 +2365,7 @@ describe('ReactIncremental', () => { expect(cduNextProps).toEqual([{children: 'B'}]); }); + // @gate !disableLegacyContext it('updates descendants with new context values', async () => { let instance; @@ -2403,6 +2415,7 @@ describe('ReactIncremental', () => { await waitForAll(['count:1']); }); + // @gate !disableLegacyContext it('updates descendants with multiple context-providing ancestors with new context values', async () => { let instance; @@ -2458,6 +2471,7 @@ describe('ReactIncremental', () => { await waitForAll(['count:1']); }); + // @gate !disableLegacyContext it('should not update descendants with new context values if shouldComponentUpdate returns false', async () => { let instance; @@ -2522,6 +2536,7 @@ describe('ReactIncremental', () => { await waitForAll([]); }); + // @gate !disableLegacyContext it('should update descendants with new context values if setState() is called in the middle of the tree', async () => { let middleInstance; let topInstance; @@ -2667,6 +2682,7 @@ describe('ReactIncremental', () => { }); // We sometimes use Maps with Fibers as keys. + // @gate !disableLegacyContext || !__DEV__ it('does not break with a bad Map polyfill', async () => { const realMapSet = Map.prototype.set; diff --git a/packages/react-reconciler/src/__tests__/ReactIncrementalErrorHandling-test.internal.js b/packages/react-reconciler/src/__tests__/ReactIncrementalErrorHandling-test.internal.js index e97476b71b580..2b274484bc625 100644 --- a/packages/react-reconciler/src/__tests__/ReactIncrementalErrorHandling-test.internal.js +++ b/packages/react-reconciler/src/__tests__/ReactIncrementalErrorHandling-test.internal.js @@ -1159,6 +1159,12 @@ describe('ReactIncrementalErrorHandling', () => { expect(ReactNoop.getChildrenAsJSX('f')).toEqual(null); }); + // NOTE: When legacy context is removed, it's probably fine to just delete + // this test. There's plenty of test coverage of stack unwinding in general + // because it's used for new context, suspense, and many other features. + // It has to be tested independently for each feature anyway. So although it + // doesn't look like it, this test is specific to legacy context. + // @gate !disableLegacyContext it('unwinds the context stack correctly on error', async () => { class Provider extends React.Component { static childContextTypes = {message: PropTypes.string}; diff --git a/packages/react-reconciler/src/__tests__/ReactNewContext-test.js b/packages/react-reconciler/src/__tests__/ReactNewContext-test.js index 6fc22f00e6d6f..cedf698148ee1 100644 --- a/packages/react-reconciler/src/__tests__/ReactNewContext-test.js +++ b/packages/react-reconciler/src/__tests__/ReactNewContext-test.js @@ -984,6 +984,7 @@ describe('ReactNewContext', () => { expect(ReactNoop).toMatchRenderedOutput(); }); + // @gate !disableLegacyContext it('provider does not bail out if legacy context changed above', async () => { const Context = React.createContext(0); diff --git a/packages/react/src/__tests__/ReactCoffeeScriptClass-test.coffee b/packages/react/src/__tests__/ReactCoffeeScriptClass-test.coffee index 928ddb8959a5d..78a7ecafd9799 100644 --- a/packages/react/src/__tests__/ReactCoffeeScriptClass-test.coffee +++ b/packages/react/src/__tests__/ReactCoffeeScriptClass-test.coffee @@ -11,6 +11,8 @@ ReactDOM = null ReactDOMClient = null act = null +featureFlags = require 'shared/ReactFeatureFlags' + describe 'ReactCoffeeScriptClass', -> container = null root = null @@ -216,36 +218,37 @@ describe 'ReactCoffeeScriptClass', -> test React.createElement(Foo, update: false), 'DIV', 'initial' test React.createElement(Foo, update: true), 'DIV', 'updated' - it 'renders based on context in the constructor', -> - class Foo extends React.Component - @contextTypes: - tag: PropTypes.string - className: PropTypes.string + if !featureFlags.disableLegacyContext + it 'renders based on context in the constructor', -> + class Foo extends React.Component + @contextTypes: + tag: PropTypes.string + className: PropTypes.string - constructor: (props, context) -> - super props, context - @state = - tag: context.tag - className: @context.className + constructor: (props, context) -> + super props, context + @state = + tag: context.tag + className: @context.className - render: -> - Tag = @state.tag - React.createElement Tag, - className: @state.className + render: -> + Tag = @state.tag + React.createElement Tag, + className: @state.className - class Outer extends React.Component - @childContextTypes: - tag: PropTypes.string - className: PropTypes.string + class Outer extends React.Component + @childContextTypes: + tag: PropTypes.string + className: PropTypes.string - getChildContext: -> - tag: 'span' - className: 'foo' + getChildContext: -> + tag: 'span' + className: 'foo' - render: -> - React.createElement Foo + render: -> + React.createElement Foo - test React.createElement(Outer), 'SPAN', 'foo' + test React.createElement(Outer), 'SPAN', 'foo' it 'renders only once when setting state in componentWillMount', -> renderCount = 0 @@ -395,40 +398,41 @@ describe 'ReactCoffeeScriptClass', -> root.unmount() expect(lifeCycles).toEqual ['will-unmount'] - it 'warns when classic properties are defined on the instance, - but does not invoke them.', -> - getInitialStateWasCalled = false - getDefaultPropsWasCalled = false - class Foo extends React.Component - constructor: -> - @contextTypes = {} - @contextType = {} - @propTypes = {} + if !featureFlags.disableLegacyContext + it 'warns when classic properties are defined on the instance, + but does not invoke them.', -> + getInitialStateWasCalled = false + getDefaultPropsWasCalled = false + class Foo extends React.Component + constructor: -> + @contextTypes = {} + @contextType = {} + @propTypes = {} - getInitialState: -> - getInitialStateWasCalled = true - {} + getInitialState: -> + getInitialStateWasCalled = true + {} - getDefaultProps: -> - getDefaultPropsWasCalled = true - {} + getDefaultProps: -> + getDefaultPropsWasCalled = true + {} - render: -> - React.createElement('span', - className: 'foo' - ) + render: -> + React.createElement('span', + className: 'foo' + ) - expect(-> - test React.createElement(Foo), 'SPAN', 'foo' - ).toErrorDev([ - 'getInitialState was defined on Foo, a plain JavaScript class.', - 'getDefaultProps was defined on Foo, a plain JavaScript class.', - 'propTypes was defined as an instance property on Foo.', - 'contextTypes was defined as an instance property on Foo.', - 'contextType was defined as an instance property on Foo.', - ]) - expect(getInitialStateWasCalled).toBe false - expect(getDefaultPropsWasCalled).toBe false + expect(-> + test React.createElement(Foo), 'SPAN', 'foo' + ).toErrorDev([ + 'getInitialState was defined on Foo, a plain JavaScript class.', + 'getDefaultProps was defined on Foo, a plain JavaScript class.', + 'propTypes was defined as an instance property on Foo.', + 'contextTypes was defined as an instance property on Foo.', + 'contextType was defined as an instance property on Foo.', + ]) + expect(getInitialStateWasCalled).toBe false + expect(getDefaultPropsWasCalled).toBe false it 'does not warn about getInitialState() on class components if state is also defined.', -> @@ -515,22 +519,23 @@ describe 'ReactCoffeeScriptClass', -> {withoutStack: true} ) - it 'supports this.context passed via getChildContext', -> - class Bar extends React.Component - @contextTypes: - bar: PropTypes.string - render: -> - React.createElement('div', className: @context.bar) + if !featureFlags.disableLegacyContext + it 'supports this.context passed via getChildContext', -> + class Bar extends React.Component + @contextTypes: + bar: PropTypes.string + render: -> + React.createElement('div', className: @context.bar) - class Foo extends React.Component - @childContextTypes: - bar: PropTypes.string - getChildContext: -> - bar: 'bar-through-context' - render: -> - React.createElement Bar + class Foo extends React.Component + @childContextTypes: + bar: PropTypes.string + getChildContext: -> + bar: 'bar-through-context' + render: -> + React.createElement Bar - test React.createElement(Foo), 'DIV', 'bar-through-context' + test React.createElement(Foo), 'DIV', 'bar-through-context' it 'supports string refs', -> class Foo extends React.Component diff --git a/packages/react/src/__tests__/ReactContextValidator-test.js b/packages/react/src/__tests__/ReactContextValidator-test.js index e4895d5c15bd4..9bccbbc51f3d2 100644 --- a/packages/react/src/__tests__/ReactContextValidator-test.js +++ b/packages/react/src/__tests__/ReactContextValidator-test.js @@ -35,6 +35,7 @@ describe('ReactContextValidator', () => { // TODO: This behavior creates a runtime dependency on propTypes. We should // ensure that this is not required for ES6 classes with Flow. + // @gate !disableLegacyContext it('should filter out context not in contextTypes', () => { class Component extends React.Component { render() { @@ -70,6 +71,7 @@ describe('ReactContextValidator', () => { expect(instance.childRef.current.context).toEqual({foo: 'abc'}); }); + // @gate !disableLegacyContext it('should pass next context to lifecycles', () => { let componentDidMountContext; let componentDidUpdateContext; @@ -148,6 +150,7 @@ describe('ReactContextValidator', () => { expect(componentDidUpdateContext).toEqual({foo: 'def'}); }); + // @gate !disableLegacyContext || !__DEV__ it('should check context types', () => { class Component extends React.Component { render() { @@ -213,6 +216,7 @@ describe('ReactContextValidator', () => { ); }); + // @gate !disableLegacyContext || !__DEV__ it('should check child context types', () => { class Component extends React.Component { getChildContext() { @@ -278,6 +282,7 @@ describe('ReactContextValidator', () => { // TODO (bvaughn) Remove this test and the associated behavior in the future. // It has only been added in Fiber to match the (unintentional) behavior in Stack. + // @gate !disableLegacyContext || !__DEV__ it('should warn (but not error) if getChildContext method is missing', () => { class ComponentA extends React.Component { static childContextTypes = { @@ -314,6 +319,7 @@ describe('ReactContextValidator', () => { // TODO (bvaughn) Remove this test and the associated behavior in the future. // It has only been added in Fiber to match the (unintentional) behavior in Stack. + // @gate !disableLegacyContext it('should pass parent context if getChildContext method is missing', () => { class ParentContextProvider extends React.Component { static childContextTypes = { @@ -474,6 +480,7 @@ describe('ReactContextValidator', () => { expect(renderedContext).toBe(secondContext); }); + // @gate !disableLegacyContext || !__DEV__ it('should warn if both contextType and contextTypes are defined', () => { const Context = React.createContext(); diff --git a/packages/react/src/__tests__/ReactES6Class-test.js b/packages/react/src/__tests__/ReactES6Class-test.js index 70944b398dcbd..72a632f4dd42a 100644 --- a/packages/react/src/__tests__/ReactES6Class-test.js +++ b/packages/react/src/__tests__/ReactES6Class-test.js @@ -246,36 +246,38 @@ describe('ReactES6Class', () => { test(, 'DIV', 'updated'); }); - it('renders based on context in the constructor', () => { - class Foo extends React.Component { - constructor(props, context) { - super(props, context); - this.state = {tag: context.tag, className: this.context.className}; - } - render() { - const Tag = this.state.tag; - return ; + if (!require('shared/ReactFeatureFlags').disableLegacyContext) { + it('renders based on context in the constructor', () => { + class Foo extends React.Component { + constructor(props, context) { + super(props, context); + this.state = {tag: context.tag, className: this.context.className}; + } + render() { + const Tag = this.state.tag; + return ; + } } - } - Foo.contextTypes = { - tag: PropTypes.string, - className: PropTypes.string, - }; + Foo.contextTypes = { + tag: PropTypes.string, + className: PropTypes.string, + }; - class Outer extends React.Component { - getChildContext() { - return {tag: 'span', className: 'foo'}; - } - render() { - return ; + class Outer extends React.Component { + getChildContext() { + return {tag: 'span', className: 'foo'}; + } + render() { + return ; + } } - } - Outer.childContextTypes = { - tag: PropTypes.string, - className: PropTypes.string, - }; - test(, 'SPAN', 'foo'); - }); + Outer.childContextTypes = { + tag: PropTypes.string, + className: PropTypes.string, + }; + test(, 'SPAN', 'foo'); + }); + } it('renders only once when setting state in componentWillMount', () => { let renderCount = 0; @@ -439,39 +441,41 @@ describe('ReactES6Class', () => { expect(lifeCycles).toEqual(['will-unmount']); }); - it('warns when classic properties are defined on the instance, but does not invoke them.', () => { - let getDefaultPropsWasCalled = false; - let getInitialStateWasCalled = false; - class Foo extends React.Component { - constructor() { - super(); - this.contextTypes = {}; - this.contextType = {}; - this.propTypes = {}; - } - getInitialState() { - getInitialStateWasCalled = true; - return {}; - } - getDefaultProps() { - getDefaultPropsWasCalled = true; - return {}; - } - render() { - return ; + if (!require('shared/ReactFeatureFlags').disableLegacyContext) { + it('warns when classic properties are defined on the instance, but does not invoke them.', () => { + let getDefaultPropsWasCalled = false; + let getInitialStateWasCalled = false; + class Foo extends React.Component { + constructor() { + super(); + this.contextTypes = {}; + this.contextType = {}; + this.propTypes = {}; + } + getInitialState() { + getInitialStateWasCalled = true; + return {}; + } + getDefaultProps() { + getDefaultPropsWasCalled = true; + return {}; + } + render() { + return ; + } } - } - expect(() => test(, 'SPAN', 'foo')).toErrorDev([ - 'getInitialState was defined on Foo, a plain JavaScript class.', - 'getDefaultProps was defined on Foo, a plain JavaScript class.', - 'propTypes was defined as an instance property on Foo.', - 'contextType was defined as an instance property on Foo.', - 'contextTypes was defined as an instance property on Foo.', - ]); - expect(getInitialStateWasCalled).toBe(false); - expect(getDefaultPropsWasCalled).toBe(false); - }); + expect(() => test(, 'SPAN', 'foo')).toErrorDev([ + 'getInitialState was defined on Foo, a plain JavaScript class.', + 'getDefaultProps was defined on Foo, a plain JavaScript class.', + 'propTypes was defined as an instance property on Foo.', + 'contextType was defined as an instance property on Foo.', + 'contextTypes was defined as an instance property on Foo.', + ]); + expect(getInitialStateWasCalled).toBe(false); + expect(getDefaultPropsWasCalled).toBe(false); + }); + } it('does not warn about getInitialState() on class components if state is also defined.', () => { class Foo extends React.Component { @@ -553,24 +557,26 @@ describe('ReactES6Class', () => { ); }); - it('supports this.context passed via getChildContext', () => { - class Bar extends React.Component { - render() { - return
; - } - } - Bar.contextTypes = {bar: PropTypes.string}; - class Foo extends React.Component { - getChildContext() { - return {bar: 'bar-through-context'}; + if (!require('shared/ReactFeatureFlags').disableLegacyContext) { + it('supports this.context passed via getChildContext', () => { + class Bar extends React.Component { + render() { + return
; + } } - render() { - return ; + Bar.contextTypes = {bar: PropTypes.string}; + class Foo extends React.Component { + getChildContext() { + return {bar: 'bar-through-context'}; + } + render() { + return ; + } } - } - Foo.childContextTypes = {bar: PropTypes.string}; - test(, 'DIV', 'bar-through-context'); - }); + Foo.childContextTypes = {bar: PropTypes.string}; + test(, 'DIV', 'bar-through-context'); + }); + } it('supports string refs', () => { class Foo extends React.Component { diff --git a/packages/react/src/__tests__/ReactJSXElementValidator-test.js b/packages/react/src/__tests__/ReactJSXElementValidator-test.js index d2611c14a136a..71753ed0ace20 100644 --- a/packages/react/src/__tests__/ReactJSXElementValidator-test.js +++ b/packages/react/src/__tests__/ReactJSXElementValidator-test.js @@ -302,6 +302,7 @@ describe('ReactJSXElementValidator', () => { ); }); + // @gate !disableLegacyContext || !__DEV__ it('should warn on invalid context types', () => { class NullContextTypeComponent extends React.Component { render() { diff --git a/packages/react/src/__tests__/ReactStrictMode-test.js b/packages/react/src/__tests__/ReactStrictMode-test.js index 800607dd956ed..c0d392a61f2da 100644 --- a/packages/react/src/__tests__/ReactStrictMode-test.js +++ b/packages/react/src/__tests__/ReactStrictMode-test.js @@ -985,6 +985,7 @@ describe('context legacy', () => { jest.restoreAllMocks(); }); + // @gate !disableLegacyContext || !__DEV__ it('should warn if the legacy context API have been used in strict mode', () => { class LegacyContextProvider extends React.Component { getChildContext() { diff --git a/packages/react/src/__tests__/ReactTypeScriptClass-test.ts b/packages/react/src/__tests__/ReactTypeScriptClass-test.ts index efaac47524efb..f356fd08b609c 100644 --- a/packages/react/src/__tests__/ReactTypeScriptClass-test.ts +++ b/packages/react/src/__tests__/ReactTypeScriptClass-test.ts @@ -16,6 +16,7 @@ import ReactDOM = require('react-dom'); import ReactDOMClient = require('react-dom/client'); import ReactDOMTestUtils = require('react-dom/test-utils'); import PropTypes = require('prop-types'); +import ReactFeatureFlags = require('shared/ReactFeatureFlags'); // Before Each @@ -511,9 +512,11 @@ describe('ReactTypeScriptClass', function() { test(React.createElement(Foo, {update: true}), 'DIV', 'updated'); }); - it('renders based on context in the constructor', function() { - test(React.createElement(ProvideChildContextTypes), 'SPAN', 'foo'); - }); + if (!ReactFeatureFlags.disableLegacyContext) { + it('renders based on context in the constructor', function() { + test(React.createElement(ProvideChildContextTypes), 'SPAN', 'foo'); + }); + } it('renders only once when setting state in componentWillMount', function() { renderCount = 0; @@ -592,27 +595,29 @@ describe('ReactTypeScriptClass', function() { expect(lifeCycles).toEqual(['will-unmount']); }); - it( - 'warns when classic properties are defined on the instance, ' + - 'but does not invoke them.', - function() { - getInitialStateWasCalled = false; - getDefaultPropsWasCalled = false; - expect(() => - test(React.createElement(ClassicProperties), 'SPAN', 'foo') - ).toErrorDev([ - 'getInitialState was defined on ClassicProperties, ' + - 'a plain JavaScript class.', - 'getDefaultProps was defined on ClassicProperties, ' + - 'a plain JavaScript class.', - 'propTypes was defined as an instance property on ClassicProperties.', - 'contextTypes was defined as an instance property on ClassicProperties.', - 'contextType was defined as an instance property on ClassicProperties.', - ]); - expect(getInitialStateWasCalled).toBe(false); - expect(getDefaultPropsWasCalled).toBe(false); - } - ); + if (!ReactFeatureFlags.disableLegacyContext) { + it( + 'warns when classic properties are defined on the instance, ' + + 'but does not invoke them.', + function() { + getInitialStateWasCalled = false; + getDefaultPropsWasCalled = false; + expect(() => + test(React.createElement(ClassicProperties), 'SPAN', 'foo') + ).toErrorDev([ + 'getInitialState was defined on ClassicProperties, ' + + 'a plain JavaScript class.', + 'getDefaultProps was defined on ClassicProperties, ' + + 'a plain JavaScript class.', + 'propTypes was defined as an instance property on ClassicProperties.', + 'contextTypes was defined as an instance property on ClassicProperties.', + 'contextType was defined as an instance property on ClassicProperties.', + ]); + expect(getInitialStateWasCalled).toBe(false); + expect(getDefaultPropsWasCalled).toBe(false); + } + ); + } it( 'does not warn about getInitialState() on class components ' + @@ -680,9 +685,11 @@ describe('ReactTypeScriptClass', function() { ); }); - it('supports this.context passed via getChildContext', function() { - test(React.createElement(ProvideContext), 'DIV', 'bar-through-context'); - }); + if (!ReactFeatureFlags.disableLegacyContext) { + it('supports this.context passed via getChildContext', function() { + test(React.createElement(ProvideContext), 'DIV', 'bar-through-context'); + }); + } it('supports string refs', function() { const ref = React.createRef(); diff --git a/packages/react/src/__tests__/createReactClassIntegration-test.js b/packages/react/src/__tests__/createReactClassIntegration-test.js index 5a5abafbb1dd5..b907fafea64f9 100644 --- a/packages/react/src/__tests__/createReactClassIntegration-test.js +++ b/packages/react/src/__tests__/createReactClassIntegration-test.js @@ -291,6 +291,7 @@ describe('create-react-class-integration', () => { expect(instance.state.occupation).toEqual('clown'); }); + // @gate !disableLegacyContext it('renders based on context getInitialState', () => { const Foo = createReactClass({ contextTypes: { diff --git a/scripts/jest/setupTests.www.js b/scripts/jest/setupTests.www.js index c0058cb6e0849..a9ce345e04591 100644 --- a/scripts/jest/setupTests.www.js +++ b/scripts/jest/setupTests.www.js @@ -13,7 +13,6 @@ jest.mock('shared/ReactFeatureFlags', () => { // TODO: Many tests were written before we started running them against the // www configuration. Update those tests so that they work against the www // configuration, too. Then remove these overrides. - wwwFlags.disableLegacyContext = defaultFlags.disableLegacyContext; wwwFlags.disableJavaScriptURLs = defaultFlags.disableJavaScriptURLs; return wwwFlags;