Added basic tests for InspectedElementContext

This commit is contained in:
Brian Vaughn
2019-05-10 09:13:54 -07:00
parent 61ab40c15c
commit b74f3f507a
7 changed files with 265 additions and 6 deletions
+1
View File
@@ -24,6 +24,7 @@
"<rootDir>/src/__tests__/setupTests"
],
"snapshotSerializers": [
"<rootDir>/src/__tests__/inspectedElementSerializer",
"<rootDir>/src/__tests__/storeSerializer"
],
"testMatch": [
@@ -0,0 +1,61 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`InspectedElementContext should inspect the currently selected element: 1: mount 1`] = `
[root]
<Example>
`;
exports[`InspectedElementContext should inspect the currently selected element: 2: Inspected element 2 1`] = `
{
"id": 2,
"owners": null,
"context": null,
"hooks": [
{
"id": 0,
"isStateEditable": true,
"name": "State",
"value": 1,
"subHooks": []
}
],
"props": {
"foo": 1,
"bar": "abc"
},
"state": null
}
`;
exports[`InspectedElementContext should poll for updates for the currently selected element: 1: mount 1`] = `
[root]
<Example>
`;
exports[`InspectedElementContext should poll for updates for the currently selected element: 2: initial render 1`] = `
{
"id": 2,
"owners": null,
"context": null,
"hooks": null,
"props": {
"foo": 1,
"bar": "abc"
},
"state": null
}
`;
exports[`InspectedElementContext should poll for updates for the currently selected element: 2: updated state 1`] = `
{
"id": 2,
"owners": null,
"context": null,
"hooks": null,
"props": {
"foo": 2,
"bar": "def"
},
"state": null
}
`;
@@ -0,0 +1,162 @@
// @flow
import typeof ReactTestRenderer from 'react-test-renderer';
import type { Element } from 'src/devtools/views/Components/types';
import type Bridge from 'src/bridge';
import type Store from 'src/devtools/store';
describe('InspectedElementContext', () => {
let React;
let ReactDOM;
let TestRenderer: ReactTestRenderer;
let bridge: Bridge;
let store: Store;
let utils;
let BridgeContext;
let InspectedElementContext;
let InspectedElementContextController;
let StoreContext;
let TreeContextController;
beforeEach(() => {
utils = require('./utils');
utils.beforeEachProfiling();
bridge = global.bridge;
store = global.store;
store.collapseNodesByDefault = false;
React = require('react');
ReactDOM = require('react-dom');
TestRenderer = utils.requireTestRenderer();
BridgeContext = require('src/devtools/views/context').BridgeContext;
InspectedElementContext = require('src/devtools/views/Components/InspectedElementContext')
.InspectedElementContext;
InspectedElementContextController = require('src/devtools/views/Components/InspectedElementContext')
.InspectedElementContextController;
StoreContext = require('src/devtools/views/context').StoreContext;
TreeContextController = require('src/devtools/views/Components/TreeContext')
.TreeContextController;
});
const Contexts = ({
children,
defaultSelectedElementID = null,
defaultSelectedElementIndex = null,
}) => (
<BridgeContext.Provider value={bridge}>
<StoreContext.Provider value={store}>
<TreeContextController
defaultSelectedElementID={defaultSelectedElementID}
defaultSelectedElementIndex={defaultSelectedElementIndex}
>
<InspectedElementContextController>
{children}
</InspectedElementContextController>
</TreeContextController>
</StoreContext.Provider>
</BridgeContext.Provider>
);
it('should inspect the currently selected element', async done => {
const Example = () => {
const [count] = React.useState(1);
return count;
};
const container = document.createElement('div');
utils.act(() => ReactDOM.render(<Example foo={1} bar="abc" />, container));
expect(store).toMatchSnapshot('1: mount');
const example = ((store.getElementAtIndex(0): any): Element);
let didFinish = false;
function Suspender({ target }) {
const { read } = React.useContext(InspectedElementContext);
const inspectedElement = read(target.id);
expect(inspectedElement).toMatchSnapshot(
`2: Inspected element ${target.id}`
);
didFinish = true;
return null;
}
await utils.actAsync(
() =>
TestRenderer.create(
<Contexts
defaultSelectedElementID={example.id}
defaultSelectedElementIndex={0}
>
<React.Suspense fallback={null}>
<Suspender target={example} />
</React.Suspense>
</Contexts>
),
3
);
expect(didFinish).toBe(true);
done();
});
it('should poll for updates for the currently selected element', async done => {
const Example = () => null;
const container = document.createElement('div');
utils.act(() => ReactDOM.render(<Example foo={1} bar="abc" />, container));
expect(store).toMatchSnapshot('1: mount');
const example = ((store.getElementAtIndex(0): any): Element);
let inspectedElement = null;
function Suspender({ target }) {
const { read } = React.useContext(InspectedElementContext);
inspectedElement = read(target.id);
return null;
}
await utils.actAsync(
() =>
TestRenderer.create(
<Contexts
defaultSelectedElementID={example.id}
defaultSelectedElementIndex={0}
>
<React.Suspense fallback={null}>
<Suspender target={example} />
</React.Suspense>
</Contexts>
),
3
);
expect(inspectedElement).toMatchSnapshot('2: initial render');
await utils.actAsync(() =>
ReactDOM.render(<Example foo={2} bar="def" />, container)
);
inspectedElement = null;
await utils.actAsync(
() =>
TestRenderer.create(
<Contexts
defaultSelectedElementID={example.id}
defaultSelectedElementIndex={0}
>
<React.Suspense fallback={null}>
<Suspender target={example} />
</React.Suspense>
</Contexts>
),
1
);
expect(inspectedElement).toMatchSnapshot('2: updated state');
done();
});
});
@@ -0,0 +1,27 @@
// test() is part of Jest's serializer API
export function test(maybeInspectedElement) {
return (
maybeInspectedElement !== null &&
typeof maybeInspectedElement === 'object' &&
maybeInspectedElement.hasOwnProperty('canEditFunctionProps') &&
maybeInspectedElement.hasOwnProperty('canEditHooks') &&
maybeInspectedElement.hasOwnProperty('canToggleSuspense') &&
maybeInspectedElement.hasOwnProperty('canViewSource')
);
}
// print() is part of Jest's serializer API
export function print(inspectedElement, serialize, indent) {
return JSON.stringify(
{
id: inspectedElement.id,
owners: inspectedElement.owners,
context: inspectedElement.context,
hooks: inspectedElement.hooks,
props: inspectedElement.props,
state: inspectedElement.state,
},
null,
2
);
}
+2 -2
View File
@@ -26,7 +26,7 @@ export async function actAsync(
callback();
// Resolve pending suspense promises
jest.runAllTimers();
jest.runOnlyPendingTimers();
});
// Run cascading microtasks and flush scheduled React work.
@@ -35,7 +35,7 @@ export async function actAsync(
while (--numTimesToFlush >= 0) {
// $FlowFixMe Flow doens't know about "await act()" yet
await TestUtils.act(async () => {
jest.runAllTimers();
jest.runOnlyPendingTimers();
Scheduler.flushAll();
});
}
-1
View File
@@ -358,7 +358,6 @@ export default class Agent extends EventEmitter {
console.warn(`Invalid renderer id "${rendererID}" for element "${id}"`);
} else {
renderer.selectElement(id);
this._bridge.send('selectElement');
// When user selects an element, stop trying to restore the selection,
// and instead remember the current selection for the next reload.
+12 -3
View File
@@ -594,10 +594,17 @@ type Props = {|
// Used for automated testing
defaultOwnerID?: ?number,
defaultSelectedElementID?: ?number,
defaultSelectedElementIndex?: ?number,
|};
// TODO Remove TreeContextController wrapper element once global ConsearchText.write API exists.
function TreeContextController({ children, defaultOwnerID }: Props) {
function TreeContextController({
children,
defaultOwnerID,
defaultSelectedElementID,
defaultSelectedElementIndex,
}: Props) {
const bridge = useContext(BridgeContext);
const store = useContext(StoreContext);
@@ -652,8 +659,10 @@ function TreeContextController({ children, defaultOwnerID }: Props) {
const [state, dispatch] = useReducer(reducer, {
// Tree
numElements: store.numElements,
selectedElementIndex: null,
selectedElementID: null,
selectedElementID:
defaultSelectedElementID == null ? null : defaultSelectedElementID,
selectedElementIndex:
defaultSelectedElementIndex == null ? null : defaultSelectedElementIndex,
// Search
searchIndex: null,