-
Notifications
You must be signed in to change notification settings - Fork 47.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add a feature flag to disable legacy context (#16269)
* Add a feature flag to disable legacy context * Address review - invariant -> warning - Make this.context and context argument actually undefined * Increase test coverage for lifecycles * Also disable it on the server is flag is on * Make this.context {} when disabled, but function context is undefined * Move checks inside
- Loading branch information
Showing
14 changed files
with
810 additions
and
278 deletions.
There are no files selected for viewing
168 changes: 168 additions & 0 deletions
168
...s/react-dom/src/__tests__/ReactDOMServerIntegrationLegacyContextDisabled-test.internal.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
/** | ||
* Copyright (c) Facebook, Inc. and its affiliates. | ||
* | ||
* This source code is licensed under the MIT license found in the | ||
* LICENSE file in the root directory of this source tree. | ||
* | ||
* @emails react-core | ||
*/ | ||
|
||
'use strict'; | ||
|
||
const ReactDOMServerIntegrationUtils = require('./utils/ReactDOMServerIntegrationTestUtils'); | ||
|
||
let React; | ||
let ReactDOM; | ||
let ReactFeatureFlags; | ||
let ReactDOMServer; | ||
let ReactTestUtils; | ||
|
||
function initModules() { | ||
// Reset warning cache. | ||
jest.resetModuleRegistry(); | ||
React = require('react'); | ||
ReactDOM = require('react-dom'); | ||
ReactDOMServer = require('react-dom/server'); | ||
ReactTestUtils = require('react-dom/test-utils'); | ||
|
||
ReactFeatureFlags = require('shared/ReactFeatureFlags'); | ||
ReactFeatureFlags.disableLegacyContext = true; | ||
|
||
// Make them available to the helpers. | ||
return { | ||
ReactDOM, | ||
ReactDOMServer, | ||
ReactTestUtils, | ||
}; | ||
} | ||
|
||
const {resetModules, itRenders} = ReactDOMServerIntegrationUtils(initModules); | ||
|
||
function formatValue(val) { | ||
if (val === null) { | ||
return 'null'; | ||
} | ||
if (val === undefined) { | ||
return 'undefined'; | ||
} | ||
if (typeof val === 'string') { | ||
return val; | ||
} | ||
return JSON.stringify(val); | ||
} | ||
|
||
describe('ReactDOMServerIntegrationLegacyContextDisabled', () => { | ||
beforeEach(() => { | ||
resetModules(); | ||
}); | ||
|
||
itRenders('undefined legacy context with warning', async render => { | ||
class LegacyProvider extends React.Component { | ||
static childContextTypes = { | ||
foo() {}, | ||
}; | ||
getChildContext() { | ||
return {foo: 10}; | ||
} | ||
render() { | ||
return this.props.children; | ||
} | ||
} | ||
|
||
let lifecycleContextLog = []; | ||
class LegacyClsConsumer extends React.Component { | ||
static contextTypes = { | ||
foo() {}, | ||
}; | ||
shouldComponentUpdate(nextProps, nextState, nextContext) { | ||
lifecycleContextLog.push(nextContext); | ||
return true; | ||
} | ||
UNSAFE_componentWillReceiveProps(nextProps, nextContext) { | ||
lifecycleContextLog.push(nextContext); | ||
} | ||
UNSAFE_componentWillUpdate(nextProps, nextState, nextContext) { | ||
lifecycleContextLog.push(nextContext); | ||
} | ||
render() { | ||
return formatValue(this.context); | ||
} | ||
} | ||
|
||
function LegacyFnConsumer(props, context) { | ||
return formatValue(context); | ||
} | ||
LegacyFnConsumer.contextTypes = {foo() {}}; | ||
|
||
function RegularFn(props, context) { | ||
return formatValue(context); | ||
} | ||
|
||
const e = await render( | ||
<LegacyProvider> | ||
<span> | ||
<LegacyClsConsumer /> | ||
<LegacyFnConsumer /> | ||
<RegularFn /> | ||
</span> | ||
</LegacyProvider>, | ||
3, | ||
); | ||
expect(e.textContent).toBe('{}undefinedundefined'); | ||
expect(lifecycleContextLog).toEqual([]); | ||
}); | ||
|
||
itRenders('modern context', async render => { | ||
let Ctx = React.createContext(); | ||
|
||
class Provider extends React.Component { | ||
render() { | ||
return ( | ||
<Ctx.Provider value={this.props.value}> | ||
{this.props.children} | ||
</Ctx.Provider> | ||
); | ||
} | ||
} | ||
|
||
class RenderPropConsumer extends React.Component { | ||
render() { | ||
return <Ctx.Consumer>{value => formatValue(value)}</Ctx.Consumer>; | ||
} | ||
} | ||
|
||
let lifecycleContextLog = []; | ||
class ContextTypeConsumer extends React.Component { | ||
static contextType = Ctx; | ||
shouldComponentUpdate(nextProps, nextState, nextContext) { | ||
lifecycleContextLog.push(nextContext); | ||
return true; | ||
} | ||
UNSAFE_componentWillReceiveProps(nextProps, nextContext) { | ||
lifecycleContextLog.push(nextContext); | ||
} | ||
UNSAFE_componentWillUpdate(nextProps, nextState, nextContext) { | ||
lifecycleContextLog.push(nextContext); | ||
} | ||
render() { | ||
return formatValue(this.context); | ||
} | ||
} | ||
|
||
function FnConsumer() { | ||
return formatValue(React.useContext(Ctx)); | ||
} | ||
|
||
const e = await render( | ||
<Provider value="a"> | ||
<span> | ||
<RenderPropConsumer /> | ||
<ContextTypeConsumer /> | ||
<FnConsumer /> | ||
</span> | ||
</Provider>, | ||
); | ||
expect(e.textContent).toBe('aaa'); | ||
expect(lifecycleContextLog).toEqual([]); | ||
}); | ||
}); |
207 changes: 207 additions & 0 deletions
207
packages/react-dom/src/__tests__/ReactLegacyContextDisabled-test.internal.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,207 @@ | ||
/** | ||
* Copyright (c) Facebook, Inc. and its affiliates. | ||
* | ||
* This source code is licensed under the MIT license found in the | ||
* LICENSE file in the root directory of this source tree. | ||
* | ||
* @emails react-core | ||
*/ | ||
|
||
'use strict'; | ||
|
||
let React; | ||
let ReactDOM; | ||
let ReactFeatureFlags; | ||
|
||
describe('ReactLegacyContextDisabled', () => { | ||
beforeEach(() => { | ||
jest.resetModules(); | ||
|
||
React = require('react'); | ||
ReactDOM = require('react-dom'); | ||
ReactFeatureFlags = require('shared/ReactFeatureFlags'); | ||
ReactFeatureFlags.disableLegacyContext = true; | ||
}); | ||
|
||
function formatValue(val) { | ||
if (val === null) { | ||
return 'null'; | ||
} | ||
if (val === undefined) { | ||
return 'undefined'; | ||
} | ||
if (typeof val === 'string') { | ||
return val; | ||
} | ||
return JSON.stringify(val); | ||
} | ||
|
||
it('warns for legacy context', () => { | ||
class LegacyProvider extends React.Component { | ||
static childContextTypes = { | ||
foo() {}, | ||
}; | ||
getChildContext() { | ||
return {foo: 10}; | ||
} | ||
render() { | ||
return this.props.children; | ||
} | ||
} | ||
|
||
let lifecycleContextLog = []; | ||
class LegacyClsConsumer extends React.Component { | ||
static contextTypes = { | ||
foo() {}, | ||
}; | ||
shouldComponentUpdate(nextProps, nextState, nextContext) { | ||
lifecycleContextLog.push(nextContext); | ||
return true; | ||
} | ||
UNSAFE_componentWillReceiveProps(nextProps, nextContext) { | ||
lifecycleContextLog.push(nextContext); | ||
} | ||
UNSAFE_componentWillUpdate(nextProps, nextState, nextContext) { | ||
lifecycleContextLog.push(nextContext); | ||
} | ||
render() { | ||
return formatValue(this.context); | ||
} | ||
} | ||
|
||
function LegacyFnConsumer(props, context) { | ||
return formatValue(context); | ||
} | ||
LegacyFnConsumer.contextTypes = {foo() {}}; | ||
|
||
function RegularFn(props, context) { | ||
return formatValue(context); | ||
} | ||
|
||
const container = document.createElement('div'); | ||
expect(() => { | ||
ReactDOM.render( | ||
<LegacyProvider> | ||
<span> | ||
<LegacyClsConsumer /> | ||
<LegacyFnConsumer /> | ||
<RegularFn /> | ||
</span> | ||
</LegacyProvider>, | ||
container, | ||
); | ||
}).toWarnDev( | ||
[ | ||
'LegacyProvider uses the legacy childContextTypes API which is no longer supported. ' + | ||
'Use React.createContext() instead.', | ||
'LegacyClsConsumer uses the legacy contextTypes API which is no longer supported. ' + | ||
'Use React.createContext() with static contextType instead.', | ||
'LegacyFnConsumer uses the legacy contextTypes API which is no longer supported. ' + | ||
'Use React.createContext() with React.useContext() instead.', | ||
], | ||
{withoutStack: true}, | ||
); | ||
expect(container.textContent).toBe('{}undefinedundefined'); | ||
expect(lifecycleContextLog).toEqual([]); | ||
|
||
// Test update path. | ||
ReactDOM.render( | ||
<LegacyProvider> | ||
<span> | ||
<LegacyClsConsumer /> | ||
<LegacyFnConsumer /> | ||
<RegularFn /> | ||
</span> | ||
</LegacyProvider>, | ||
container, | ||
); | ||
expect(container.textContent).toBe('{}undefinedundefined'); | ||
expect(lifecycleContextLog).toEqual([{}, {}, {}]); | ||
ReactDOM.unmountComponentAtNode(container); | ||
}); | ||
|
||
it('renders a tree with modern context', () => { | ||
let Ctx = React.createContext(); | ||
|
||
class Provider extends React.Component { | ||
render() { | ||
return ( | ||
<Ctx.Provider value={this.props.value}> | ||
{this.props.children} | ||
</Ctx.Provider> | ||
); | ||
} | ||
} | ||
|
||
class RenderPropConsumer extends React.Component { | ||
render() { | ||
return <Ctx.Consumer>{value => formatValue(value)}</Ctx.Consumer>; | ||
} | ||
} | ||
|
||
let lifecycleContextLog = []; | ||
class ContextTypeConsumer extends React.Component { | ||
static contextType = Ctx; | ||
shouldComponentUpdate(nextProps, nextState, nextContext) { | ||
lifecycleContextLog.push(nextContext); | ||
return true; | ||
} | ||
UNSAFE_componentWillReceiveProps(nextProps, nextContext) { | ||
lifecycleContextLog.push(nextContext); | ||
} | ||
UNSAFE_componentWillUpdate(nextProps, nextState, nextContext) { | ||
lifecycleContextLog.push(nextContext); | ||
} | ||
render() { | ||
return formatValue(this.context); | ||
} | ||
} | ||
|
||
function FnConsumer() { | ||
return formatValue(React.useContext(Ctx)); | ||
} | ||
|
||
const container = document.createElement('div'); | ||
ReactDOM.render( | ||
<Provider value="a"> | ||
<span> | ||
<RenderPropConsumer /> | ||
<ContextTypeConsumer /> | ||
<FnConsumer /> | ||
</span> | ||
</Provider>, | ||
container, | ||
); | ||
expect(container.textContent).toBe('aaa'); | ||
expect(lifecycleContextLog).toEqual([]); | ||
|
||
// Test update path | ||
ReactDOM.render( | ||
<Provider value="a"> | ||
<span> | ||
<RenderPropConsumer /> | ||
<ContextTypeConsumer /> | ||
<FnConsumer /> | ||
</span> | ||
</Provider>, | ||
container, | ||
); | ||
expect(container.textContent).toBe('aaa'); | ||
expect(lifecycleContextLog).toEqual(['a', 'a', 'a']); | ||
lifecycleContextLog.length = 0; | ||
|
||
ReactDOM.render( | ||
<Provider value="b"> | ||
<span> | ||
<RenderPropConsumer /> | ||
<ContextTypeConsumer /> | ||
<FnConsumer /> | ||
</span> | ||
</Provider>, | ||
container, | ||
); | ||
expect(container.textContent).toBe('bbb'); | ||
expect(lifecycleContextLog).toEqual(['b', 'b']); // sCU skipped due to changed context value. | ||
ReactDOM.unmountComponentAtNode(container); | ||
}); | ||
}); |
Oops, something went wrong.