@samitouri / QOS-React-2 / commits / 4ef6387d6e

[DevTools] [Context] Legacy Context (#16617)

* 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.

Hristo Kanchev committed Sep 10, 2019 at 22:30 UTC 4ef6387d6e79e87c3ebc11a53847fd26ad5e71d9
7 files changed +146 -4
packages/react-devtools-shared/src/__tests__/inspectedElementContext-test.js
+115
@@ -15,6 +15,7 @@ import type Store from 'react-devtools-shared/src/devtools/store';
15 describe('InspectedElementContext', () => {
16 let React;
17 let ReactDOM;
18 + let PropTypes;
19 let TestRenderer: ReactTestRenderer;
20 let bridge: FrontendBridge;
21 let store: Store;
@@ -40,6 +41,7 @@ describe('InspectedElementContext', () => {
41
42 React = require('react');
43 ReactDOM = require('react-dom');
44 + PropTypes = require('prop-types');
45 TestUtils = require('react-dom/test-utils');
46 TestRenderer = utils.requireTestRenderer();
47
@@ -114,6 +116,119 @@ describe('InspectedElementContext', () => {
116 done();
117 });
118
119 + it('should have hasLegacyContext flag set to either "true" or "false" depending on which context API is used.', async done => {
120 + const contextData = {
121 + bool: true,
122 + };
123 +
124 + // Legacy Context API.
125 + class LegacyContextProvider extends React.Component<any> {
126 + static childContextTypes = {
127 + bool: PropTypes.bool,
128 + };
129 + getChildContext() {
130 + return contextData;
131 + }
132 + render() {
133 + return this.props.children;
134 + }
135 + }
136 + class LegacyContextConsumer extends React.Component<any> {
137 + static contextTypes = {
138 + bool: PropTypes.bool,
139 + };
140 + render() {
141 + return null;
142 + }
143 + }
144 +
145 + // Modern Context API
146 + const BoolContext = React.createContext(contextData.bool);
147 + BoolContext.displayName = 'BoolContext';
148 +
149 + class ModernContextType extends React.Component<any> {
150 + static contextType = BoolContext;
151 + render() {
152 + return null;
153 + }
154 + }
155 +
156 + const ModernContext = React.createContext();
157 + ModernContext.displayName = 'ModernContext';
158 +
159 + const container = document.createElement('div');
160 + await utils.actAsync(() =>
161 + ReactDOM.render(
162 + <React.Fragment>
163 + <LegacyContextProvider>
164 + <LegacyContextConsumer />
165 + </LegacyContextProvider>
166 + <BoolContext.Consumer>{value => null}</BoolContext.Consumer>
167 + <ModernContextType />
168 + <ModernContext.Provider value={contextData}>
169 + <ModernContext.Consumer>{value => null}</ModernContext.Consumer>
170 + </ModernContext.Provider>
171 + </React.Fragment>,
172 + container,
173 + ),
174 + );
175 +
176 + const ids = [
177 + {
178 + // <LegacyContextConsumer />
179 + id: ((store.getElementIDAtIndex(1): any): number),
180 + shouldHaveLegacyContext: true,
181 + },
182 + {
183 + // <BoolContext.Consumer>
184 + id: ((store.getElementIDAtIndex(2): any): number),
185 + shouldHaveLegacyContext: false,
186 + },
187 + {
188 + // <ModernContextType />
189 + id: ((store.getElementIDAtIndex(3): any): number),
190 + shouldHaveLegacyContext: false,
191 + },
192 + {
193 + // <ModernContext.Consumer>
194 + id: ((store.getElementIDAtIndex(5): any): number),
195 + shouldHaveLegacyContext: false,
196 + },
197 + ];
198 +
199 + function Suspender({target, shouldHaveLegacyContext}) {
200 + const {getInspectedElement} = React.useContext(InspectedElementContext);
201 + const inspectedElement = getInspectedElement(target);
202 +
203 + expect(inspectedElement.context).not.toBe(null);
204 + expect(inspectedElement.hasLegacyContext).toBe(shouldHaveLegacyContext);
205 +
206 + return null;
207 + }
208 +
209 + for (let i = 0; i < ids.length; i++) {
210 + const {id, shouldHaveLegacyContext} = ids[i];
211 +
212 + await utils.actAsync(
213 + () =>
214 + TestRenderer.create(
215 + <Contexts
216 + defaultSelectedElementID={id}
217 + defaultSelectedElementIndex={0}>
218 + <React.Suspense fallback={null}>
219 + <Suspender
220 + target={id}
221 + shouldHaveLegacyContext={shouldHaveLegacyContext}
222 + />
223 + </React.Suspense>
224 + </Contexts>,
225 + ),
226 + false,
227 + );
228 + }
229 + done();
230 + });
231 +
232 it('should poll for updates for the currently selected element', async done => {
233 const Example = () => null;
234
packages/react-devtools-shared/src/backend/legacy/renderer.js
+3
@@ -753,6 +753,9 @@ export function attach(
753 // Can view component source location.
754 canViewSource: type === ElementTypeClass || type === ElementTypeFunction,
755
756 + // Only legacy context exists in legacy versions.
757 + hasLegacyContext: true,
758 +
759 displayName: displayName,
760
761 type: type,
packages/react-devtools-shared/src/backend/renderer.js
+17 -2
@@ -2107,6 +2107,8 @@ export function attach(
2107 type,
2108 } = fiber;
2109
2110 + const elementType = getElementTypeForFiber(fiber);
2111 +
2112 const usesHooks =
2113 (tag === FunctionComponent ||
2114 tag === SimpleMemoComponent ||
@@ -2128,7 +2130,14 @@ export function attach(
2130 ) {
2131 canViewSource = true;
2132 if (stateNode && stateNode.context != null) {
2131 - context = stateNode.context;
2133 + // Don't show an empty context object for class components that don't use the context API.
2134 + const shouldHideContext =
2135 + elementType === ElementTypeClass &&
2136 + !(type.contextTypes || type.contextType);
2137 +
2138 + if (!shouldHideContext) {
2139 + context = stateNode.context;
2140 + }
2141 }
2142 } else if (
2143 typeSymbol === CONTEXT_CONSUMER_NUMBER ||
@@ -2166,7 +2175,10 @@ export function attach(
2175 }
2176 }
2177
2178 + let hasLegacyContext = false;
2179 if (context !== null) {
2180 + hasLegacyContext = !!type.contextTypes;
2181 +
2182 // To simplify hydration and display logic for context, wrap in a value object.
2183 // Otherwise simple values (e.g. strings, booleans) become harder to handle.
2184 context = {value: context};
@@ -2238,8 +2250,11 @@ export function attach(
2250 // Can view component source location.
2251 canViewSource,
2252
2253 + // Does the component have legacy context attached to it.
2254 + hasLegacyContext,
2255 +
2256 displayName: getDisplayNameForFiber(fiber),
2242 - type: getElementTypeForFiber(fiber),
2257 + type: elementType,
2258
2259 // Inspectable properties.
2260 // TODO Review sanitization approach for the below inspectable values.
packages/react-devtools-shared/src/backend/types.js
+3
@@ -163,6 +163,9 @@ export type InspectedElement = {|
163 // Can view component source location.
164 canViewSource: boolean,
165
166 + // Does the component have legacy context attached to it.
167 + hasLegacyContext: boolean,
168 +
169 // Inspectable properties.
170 context: Object | null,
171 hooks: Object | null,
packages/react-devtools-shared/src/devtools/views/Components/InspectedElementContext.js
+2
@@ -159,6 +159,7 @@ function InspectedElementContextController({children}: Props) {
159 canEditHooks,
160 canToggleSuspense,
161 canViewSource,
162 + hasLegacyContext,
163 source,
164 type,
165 owners,
@@ -173,6 +174,7 @@ function InspectedElementContextController({children}: Props) {
174 canEditHooks,
175 canToggleSuspense,
176 canViewSource,
177 + hasLegacyContext,
178 id,
179 source,
180 type,
packages/react-devtools-shared/src/devtools/views/Components/SelectedElement.js
+2 -1
@@ -267,6 +267,7 @@ function InspectedElementView({
267 canEditFunctionProps,
268 canEditHooks,
269 canToggleSuspense,
270 + hasLegacyContext,
271 context,
272 hooks,
273 owners,
@@ -376,7 +377,7 @@ function InspectedElementView({
377 )}
378 <HooksTree canEditHooks={canEditHooks} hooks={hooks} id={id} />
379 <InspectedElementTree
379 - label="context"
380 + label={hasLegacyContext ? 'legacy context' : 'context'}
381 data={context}
382 inspectPath={inspectContextPath}
383 overrideValueFn={overrideContextFn}
packages/react-devtools-shared/src/devtools/views/Components/types.js
+4 -1
@@ -71,6 +71,9 @@ export type InspectedElement = {|
71 // Can view component source location.
72 canViewSource: boolean,
73
74 + // Does the component have legacy context attached to it.
75 + hasLegacyContext: boolean,
76 +
77 // Inspectable properties.
78 context: Object | null,
79 hooks: Object | null,
@@ -80,7 +83,7 @@ export type InspectedElement = {|
83 // List of owners
84 owners: Array<Owner> | null,
85
83 - // Location of component in source coude.
86 + // Location of component in source code.
87 source: Source | null,
88
89 type: ElementType,