@samitouri / QOS-React-2 / commits / b74f3f507a

Added basic tests for InspectedElementContext

Brian Vaughn committed May 10, 2019 at 09:13 UTC b74f3f507a0243a124a041f3d5ebc246a98911f4
7 files changed +265 -6
package.json
+1
@@ -24,6 +24,7 @@
24 "<rootDir>/src/__tests__/setupTests"
25 ],
26 "snapshotSerializers": [
27 + "<rootDir>/src/__tests__/inspectedElementSerializer",
28 "<rootDir>/src/__tests__/storeSerializer"
29 ],
30 "testMatch": [
src/__tests__/__snapshots__/inspectedElementContext-test.js.snap new
+61
@@ -0,0 +1,61 @@
1 +// Jest Snapshot v1, https://goo.gl/fbAQLP
2 +
3 +exports[`InspectedElementContext should inspect the currently selected element: 1: mount 1`] = `
4 +[root]
5 + <Example>
6 +`;
7 +
8 +exports[`InspectedElementContext should inspect the currently selected element: 2: Inspected element 2 1`] = `
9 +{
10 + "id": 2,
11 + "owners": null,
12 + "context": null,
13 + "hooks": [
14 + {
15 + "id": 0,
16 + "isStateEditable": true,
17 + "name": "State",
18 + "value": 1,
19 + "subHooks": []
20 + }
21 + ],
22 + "props": {
23 + "foo": 1,
24 + "bar": "abc"
25 + },
26 + "state": null
27 +}
28 +`;
29 +
30 +exports[`InspectedElementContext should poll for updates for the currently selected element: 1: mount 1`] = `
31 +[root]
32 + <Example>
33 +`;
34 +
35 +exports[`InspectedElementContext should poll for updates for the currently selected element: 2: initial render 1`] = `
36 +{
37 + "id": 2,
38 + "owners": null,
39 + "context": null,
40 + "hooks": null,
41 + "props": {
42 + "foo": 1,
43 + "bar": "abc"
44 + },
45 + "state": null
46 +}
47 +`;
48 +
49 +exports[`InspectedElementContext should poll for updates for the currently selected element: 2: updated state 1`] = `
50 +{
51 + "id": 2,
52 + "owners": null,
53 + "context": null,
54 + "hooks": null,
55 + "props": {
56 + "foo": 2,
57 + "bar": "def"
58 + },
59 + "state": null
60 +}
61 +`;
src/__tests__/inspectedElementContext-test.js new
+162
@@ -0,0 +1,162 @@
1 +// @flow
2 +
3 +import typeof ReactTestRenderer from 'react-test-renderer';
4 +import type { Element } from 'src/devtools/views/Components/types';
5 +import type Bridge from 'src/bridge';
6 +import type Store from 'src/devtools/store';
7 +
8 +describe('InspectedElementContext', () => {
9 + let React;
10 + let ReactDOM;
11 + let TestRenderer: ReactTestRenderer;
12 + let bridge: Bridge;
13 + let store: Store;
14 + let utils;
15 +
16 + let BridgeContext;
17 + let InspectedElementContext;
18 + let InspectedElementContextController;
19 + let StoreContext;
20 + let TreeContextController;
21 +
22 + beforeEach(() => {
23 + utils = require('./utils');
24 + utils.beforeEachProfiling();
25 +
26 + bridge = global.bridge;
27 + store = global.store;
28 + store.collapseNodesByDefault = false;
29 +
30 + React = require('react');
31 + ReactDOM = require('react-dom');
32 + TestRenderer = utils.requireTestRenderer();
33 +
34 + BridgeContext = require('src/devtools/views/context').BridgeContext;
35 + InspectedElementContext = require('src/devtools/views/Components/InspectedElementContext')
36 + .InspectedElementContext;
37 + InspectedElementContextController = require('src/devtools/views/Components/InspectedElementContext')
38 + .InspectedElementContextController;
39 + StoreContext = require('src/devtools/views/context').StoreContext;
40 + TreeContextController = require('src/devtools/views/Components/TreeContext')
41 + .TreeContextController;
42 + });
43 +
44 + const Contexts = ({
45 + children,
46 + defaultSelectedElementID = null,
47 + defaultSelectedElementIndex = null,
48 + }) => (
49 + <BridgeContext.Provider value={bridge}>
50 + <StoreContext.Provider value={store}>
51 + <TreeContextController
52 + defaultSelectedElementID={defaultSelectedElementID}
53 + defaultSelectedElementIndex={defaultSelectedElementIndex}
54 + >
55 + <InspectedElementContextController>
56 + {children}
57 + </InspectedElementContextController>
58 + </TreeContextController>
59 + </StoreContext.Provider>
60 + </BridgeContext.Provider>
61 + );
62 +
63 + it('should inspect the currently selected element', async done => {
64 + const Example = () => {
65 + const [count] = React.useState(1);
66 + return count;
67 + };
68 +
69 + const container = document.createElement('div');
70 + utils.act(() => ReactDOM.render(<Example foo={1} bar="abc" />, container));
71 + expect(store).toMatchSnapshot('1: mount');
72 +
73 + const example = ((store.getElementAtIndex(0): any): Element);
74 +
75 + let didFinish = false;
76 +
77 + function Suspender({ target }) {
78 + const { read } = React.useContext(InspectedElementContext);
79 + const inspectedElement = read(target.id);
80 + expect(inspectedElement).toMatchSnapshot(
81 + `2: Inspected element ${target.id}`
82 + );
83 + didFinish = true;
84 + return null;
85 + }
86 +
87 + await utils.actAsync(
88 + () =>
89 + TestRenderer.create(
90 + <Contexts
91 + defaultSelectedElementID={example.id}
92 + defaultSelectedElementIndex={0}
93 + >
94 + <React.Suspense fallback={null}>
95 + <Suspender target={example} />
96 + </React.Suspense>
97 + </Contexts>
98 + ),
99 + 3
100 + );
101 + expect(didFinish).toBe(true);
102 +
103 + done();
104 + });
105 +
106 + it('should poll for updates for the currently selected element', async done => {
107 + const Example = () => null;
108 +
109 + const container = document.createElement('div');
110 + utils.act(() => ReactDOM.render(<Example foo={1} bar="abc" />, container));
111 + expect(store).toMatchSnapshot('1: mount');
112 +
113 + const example = ((store.getElementAtIndex(0): any): Element);
114 +
115 + let inspectedElement = null;
116 +
117 + function Suspender({ target }) {
118 + const { read } = React.useContext(InspectedElementContext);
119 + inspectedElement = read(target.id);
120 + return null;
121 + }
122 +
123 + await utils.actAsync(
124 + () =>
125 + TestRenderer.create(
126 + <Contexts
127 + defaultSelectedElementID={example.id}
128 + defaultSelectedElementIndex={0}
129 + >
130 + <React.Suspense fallback={null}>
131 + <Suspender target={example} />
132 + </React.Suspense>
133 + </Contexts>
134 + ),
135 + 3
136 + );
137 + expect(inspectedElement).toMatchSnapshot('2: initial render');
138 +
139 + await utils.actAsync(() =>
140 + ReactDOM.render(<Example foo={2} bar="def" />, container)
141 + );
142 +
143 + inspectedElement = null;
144 + await utils.actAsync(
145 + () =>
146 + TestRenderer.create(
147 + <Contexts
148 + defaultSelectedElementID={example.id}
149 + defaultSelectedElementIndex={0}
150 + >
151 + <React.Suspense fallback={null}>
152 + <Suspender target={example} />
153 + </React.Suspense>
154 + </Contexts>
155 + ),
156 + 1
157 + );
158 + expect(inspectedElement).toMatchSnapshot('2: updated state');
159 +
160 + done();
161 + });
162 +});
src/__tests__/inspectedElementSerializer.js new
+27
@@ -0,0 +1,27 @@
1 +// test() is part of Jest's serializer API
2 +export function test(maybeInspectedElement) {
3 + return (
4 + maybeInspectedElement !== null &&
5 + typeof maybeInspectedElement === 'object' &&
6 + maybeInspectedElement.hasOwnProperty('canEditFunctionProps') &&
7 + maybeInspectedElement.hasOwnProperty('canEditHooks') &&
8 + maybeInspectedElement.hasOwnProperty('canToggleSuspense') &&
9 + maybeInspectedElement.hasOwnProperty('canViewSource')
10 + );
11 +}
12 +
13 +// print() is part of Jest's serializer API
14 +export function print(inspectedElement, serialize, indent) {
15 + return JSON.stringify(
16 + {
17 + id: inspectedElement.id,
18 + owners: inspectedElement.owners,
19 + context: inspectedElement.context,
20 + hooks: inspectedElement.hooks,
21 + props: inspectedElement.props,
22 + state: inspectedElement.state,
23 + },
24 + null,
25 + 2
26 + );
27 +}
src/__tests__/utils.js
+2 -2
@@ -26,7 +26,7 @@ export async function actAsync(
26 callback();
27
28 // Resolve pending suspense promises
29 - jest.runAllTimers();
29 + jest.runOnlyPendingTimers();
30 });
31
32 // Run cascading microtasks and flush scheduled React work.
@@ -35,7 +35,7 @@ export async function actAsync(
35 while (--numTimesToFlush >= 0) {
36 // $FlowFixMe Flow doens't know about "await act()" yet
37 await TestUtils.act(async () => {
38 - jest.runAllTimers();
38 + jest.runOnlyPendingTimers();
39 Scheduler.flushAll();
40 });
41 }
src/backend/agent.js
-1
@@ -358,7 +358,6 @@ export default class Agent extends EventEmitter {
358 console.warn(`Invalid renderer id "${rendererID}" for element "${id}"`);
359 } else {
360 renderer.selectElement(id);
361 - this._bridge.send('selectElement');
361
362 // When user selects an element, stop trying to restore the selection,
363 // and instead remember the current selection for the next reload.
src/devtools/views/Components/TreeContext.js
+12 -3
@@ -594,10 +594,17 @@ type Props = {|
594
595 // Used for automated testing
596 defaultOwnerID?: ?number,
597 + defaultSelectedElementID?: ?number,
598 + defaultSelectedElementIndex?: ?number,
599 |};
600
601 // TODO Remove TreeContextController wrapper element once global ConsearchText.write API exists.
600 -function TreeContextController({ children, defaultOwnerID }: Props) {
602 +function TreeContextController({
603 + children,
604 + defaultOwnerID,
605 + defaultSelectedElementID,
606 + defaultSelectedElementIndex,
607 +}: Props) {
608 const bridge = useContext(BridgeContext);
609 const store = useContext(StoreContext);
610
@@ -652,8 +659,10 @@ function TreeContextController({ children, defaultOwnerID }: Props) {
659 const [state, dispatch] = useReducer(reducer, {
660 // Tree
661 numElements: store.numElements,
655 - selectedElementIndex: null,
656 - selectedElementID: null,
662 + selectedElementID:
663 + defaultSelectedElementID == null ? null : defaultSelectedElementID,
664 + selectedElementIndex:
665 + defaultSelectedElementIndex == null ? null : defaultSelectedElementIndex,
666
667 // Search
668 searchIndex: null,