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,