Skip to content

Commit

Permalink
[DevTools] [Context] Legacy Context (#16617)
Browse files Browse the repository at this point in the history
* Added hasLegacyContext check.

* Passed hasLegacyContext as prop to SelectedElement

* Changing context labels based on hasLegacyContext

* Fixed flow types.

* Fixed typos.

* Added tests for hasLegacyContext.

* Renamed test.

* Removed test imports.
  • Loading branch information
hristo-kanchev authored and bvaughn committed Sep 10, 2019
1 parent c317fc2 commit 4ef6387
Show file tree
Hide file tree
Showing 7 changed files with 146 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import type Store from 'react-devtools-shared/src/devtools/store';
describe('InspectedElementContext', () => {
let React;
let ReactDOM;
let PropTypes;
let TestRenderer: ReactTestRenderer;
let bridge: FrontendBridge;
let store: Store;
Expand All @@ -40,6 +41,7 @@ describe('InspectedElementContext', () => {

React = require('react');
ReactDOM = require('react-dom');
PropTypes = require('prop-types');
TestUtils = require('react-dom/test-utils');
TestRenderer = utils.requireTestRenderer();

Expand Down Expand Up @@ -114,6 +116,119 @@ describe('InspectedElementContext', () => {
done();
});

it('should have hasLegacyContext flag set to either "true" or "false" depending on which context API is used.', async done => {
const contextData = {
bool: true,
};

// Legacy Context API.
class LegacyContextProvider extends React.Component<any> {
static childContextTypes = {
bool: PropTypes.bool,
};
getChildContext() {
return contextData;
}
render() {
return this.props.children;
}
}
class LegacyContextConsumer extends React.Component<any> {
static contextTypes = {
bool: PropTypes.bool,
};
render() {
return null;
}
}

// Modern Context API
const BoolContext = React.createContext(contextData.bool);
BoolContext.displayName = 'BoolContext';

class ModernContextType extends React.Component<any> {
static contextType = BoolContext;
render() {
return null;
}
}

const ModernContext = React.createContext();
ModernContext.displayName = 'ModernContext';

const container = document.createElement('div');
await utils.actAsync(() =>
ReactDOM.render(
<React.Fragment>
<LegacyContextProvider>
<LegacyContextConsumer />
</LegacyContextProvider>
<BoolContext.Consumer>{value => null}</BoolContext.Consumer>
<ModernContextType />
<ModernContext.Provider value={contextData}>
<ModernContext.Consumer>{value => null}</ModernContext.Consumer>
</ModernContext.Provider>
</React.Fragment>,
container,
),
);

const ids = [
{
// <LegacyContextConsumer />
id: ((store.getElementIDAtIndex(1): any): number),
shouldHaveLegacyContext: true,
},
{
// <BoolContext.Consumer>
id: ((store.getElementIDAtIndex(2): any): number),
shouldHaveLegacyContext: false,
},
{
// <ModernContextType />
id: ((store.getElementIDAtIndex(3): any): number),
shouldHaveLegacyContext: false,
},
{
// <ModernContext.Consumer>
id: ((store.getElementIDAtIndex(5): any): number),
shouldHaveLegacyContext: false,
},
];

function Suspender({target, shouldHaveLegacyContext}) {
const {getInspectedElement} = React.useContext(InspectedElementContext);
const inspectedElement = getInspectedElement(target);

expect(inspectedElement.context).not.toBe(null);
expect(inspectedElement.hasLegacyContext).toBe(shouldHaveLegacyContext);

return null;
}

for (let i = 0; i < ids.length; i++) {
const {id, shouldHaveLegacyContext} = ids[i];

await utils.actAsync(
() =>
TestRenderer.create(
<Contexts
defaultSelectedElementID={id}
defaultSelectedElementIndex={0}>
<React.Suspense fallback={null}>
<Suspender
target={id}
shouldHaveLegacyContext={shouldHaveLegacyContext}
/>
</React.Suspense>
</Contexts>,
),
false,
);
}
done();
});

it('should poll for updates for the currently selected element', async done => {
const Example = () => null;

Expand Down
3 changes: 3 additions & 0 deletions packages/react-devtools-shared/src/backend/legacy/renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -753,6 +753,9 @@ export function attach(
// Can view component source location.
canViewSource: type === ElementTypeClass || type === ElementTypeFunction,

// Only legacy context exists in legacy versions.
hasLegacyContext: true,

displayName: displayName,

type: type,
Expand Down
19 changes: 17 additions & 2 deletions packages/react-devtools-shared/src/backend/renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -2107,6 +2107,8 @@ export function attach(
type,
} = fiber;

const elementType = getElementTypeForFiber(fiber);

const usesHooks =
(tag === FunctionComponent ||
tag === SimpleMemoComponent ||
Expand All @@ -2128,7 +2130,14 @@ export function attach(
) {
canViewSource = true;
if (stateNode && stateNode.context != null) {
context = stateNode.context;
// Don't show an empty context object for class components that don't use the context API.
const shouldHideContext =
elementType === ElementTypeClass &&
!(type.contextTypes || type.contextType);

if (!shouldHideContext) {
context = stateNode.context;
}
}
} else if (
typeSymbol === CONTEXT_CONSUMER_NUMBER ||
Expand Down Expand Up @@ -2166,7 +2175,10 @@ export function attach(
}
}

let hasLegacyContext = false;
if (context !== null) {
hasLegacyContext = !!type.contextTypes;

// To simplify hydration and display logic for context, wrap in a value object.
// Otherwise simple values (e.g. strings, booleans) become harder to handle.
context = {value: context};
Expand Down Expand Up @@ -2238,8 +2250,11 @@ export function attach(
// Can view component source location.
canViewSource,

// Does the component have legacy context attached to it.
hasLegacyContext,

displayName: getDisplayNameForFiber(fiber),
type: getElementTypeForFiber(fiber),
type: elementType,

// Inspectable properties.
// TODO Review sanitization approach for the below inspectable values.
Expand Down
3 changes: 3 additions & 0 deletions packages/react-devtools-shared/src/backend/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,9 @@ export type InspectedElement = {|
// Can view component source location.
canViewSource: boolean,

// Does the component have legacy context attached to it.
hasLegacyContext: boolean,

// Inspectable properties.
context: Object | null,
hooks: Object | null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ function InspectedElementContextController({children}: Props) {
canEditHooks,
canToggleSuspense,
canViewSource,
hasLegacyContext,
source,
type,
owners,
Expand All @@ -173,6 +174,7 @@ function InspectedElementContextController({children}: Props) {
canEditHooks,
canToggleSuspense,
canViewSource,
hasLegacyContext,
id,
source,
type,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,7 @@ function InspectedElementView({
canEditFunctionProps,
canEditHooks,
canToggleSuspense,
hasLegacyContext,
context,
hooks,
owners,
Expand Down Expand Up @@ -376,7 +377,7 @@ function InspectedElementView({
)}
<HooksTree canEditHooks={canEditHooks} hooks={hooks} id={id} />
<InspectedElementTree
label="context"
label={hasLegacyContext ? 'legacy context' : 'context'}
data={context}
inspectPath={inspectContextPath}
overrideValueFn={overrideContextFn}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ export type InspectedElement = {|
// Can view component source location.
canViewSource: boolean,

// Does the component have legacy context attached to it.
hasLegacyContext: boolean,

// Inspectable properties.
context: Object | null,
hooks: Object | null,
Expand All @@ -80,7 +83,7 @@ export type InspectedElement = {|
// List of owners
owners: Array<Owner> | null,

// Location of component in source coude.
// Location of component in source code.
source: Source | null,

type: ElementType,
Expand Down

0 comments on commit 4ef6387

Please sign in to comment.