-
Notifications
You must be signed in to change notification settings - Fork 47.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Support editable useState hooks in DevTools (#14906)
* ReactDebugHooks identifies State and Reducer hooks as editable * Inject overrideHookState() method to DevTools to support editing in DEV builds * Added an integration test for React DevTools, react-debug-tools, and overrideHookState
- Loading branch information
Showing
5 changed files
with
567 additions
and
38 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
176 changes: 176 additions & 0 deletions
176
packages/react-debug-tools/src/__tests__/ReactDevToolsHooksIntegration-test.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,176 @@ | ||
/** | ||
* Copyright (c) Facebook, Inc. and its affiliates. | ||
* | ||
* This source code is licensed under the MIT license found in the | ||
* LICENSE file in the root directory of this source tree. | ||
* | ||
* @emails react-core | ||
* @jest-environment node | ||
*/ | ||
|
||
'use strict'; | ||
|
||
describe('React hooks DevTools integration', () => { | ||
let React; | ||
let ReactDebugTools; | ||
let ReactTestRenderer; | ||
let act; | ||
let overrideHookState; | ||
|
||
beforeEach(() => { | ||
global.__REACT_DEVTOOLS_GLOBAL_HOOK__ = { | ||
inject: injected => { | ||
overrideHookState = injected.overrideHookState; | ||
}, | ||
supportsFiber: true, | ||
onCommitFiberRoot: () => {}, | ||
onCommitFiberUnmount: () => {}, | ||
}; | ||
|
||
jest.resetModules(); | ||
|
||
React = require('react'); | ||
ReactDebugTools = require('react-debug-tools'); | ||
ReactTestRenderer = require('react-test-renderer'); | ||
|
||
act = ReactTestRenderer.act; | ||
}); | ||
|
||
it('should support editing useState hooks', () => { | ||
let setCountFn; | ||
|
||
function MyComponent() { | ||
const [count, setCount] = React.useState(0); | ||
setCountFn = setCount; | ||
return <div>count:{count}</div>; | ||
} | ||
|
||
const renderer = ReactTestRenderer.create(<MyComponent />); | ||
expect(renderer.toJSON()).toEqual({ | ||
type: 'div', | ||
props: {}, | ||
children: ['count:', '0'], | ||
}); | ||
|
||
const fiber = renderer.root.findByType(MyComponent)._currentFiber(); | ||
const tree = ReactDebugTools.inspectHooksOfFiber(fiber); | ||
const stateHook = tree[0]; | ||
expect(stateHook.isStateEditable).toBe(true); | ||
|
||
if (__DEV__) { | ||
overrideHookState(fiber, stateHook.id, [], 10); | ||
expect(renderer.toJSON()).toEqual({ | ||
type: 'div', | ||
props: {}, | ||
children: ['count:', '10'], | ||
}); | ||
|
||
act(() => setCountFn(count => count + 1)); | ||
expect(renderer.toJSON()).toEqual({ | ||
type: 'div', | ||
props: {}, | ||
children: ['count:', '11'], | ||
}); | ||
} | ||
}); | ||
|
||
it('should support editable useReducer hooks', () => { | ||
const initialData = {foo: 'abc', bar: 123}; | ||
|
||
function reducer(state, action) { | ||
switch (action.type) { | ||
case 'swap': | ||
return {foo: state.bar, bar: state.foo}; | ||
default: | ||
throw new Error(); | ||
} | ||
} | ||
|
||
let dispatchFn; | ||
function MyComponent() { | ||
const [state, dispatch] = React.useReducer(reducer, initialData); | ||
dispatchFn = dispatch; | ||
return ( | ||
<div> | ||
foo:{state.foo}, bar:{state.bar} | ||
</div> | ||
); | ||
} | ||
|
||
const renderer = ReactTestRenderer.create(<MyComponent />); | ||
expect(renderer.toJSON()).toEqual({ | ||
type: 'div', | ||
props: {}, | ||
children: ['foo:', 'abc', ', bar:', '123'], | ||
}); | ||
|
||
const fiber = renderer.root.findByType(MyComponent)._currentFiber(); | ||
const tree = ReactDebugTools.inspectHooksOfFiber(fiber); | ||
const reducerHook = tree[0]; | ||
expect(reducerHook.isStateEditable).toBe(true); | ||
|
||
if (__DEV__) { | ||
overrideHookState(fiber, reducerHook.id, ['foo'], 'def'); | ||
expect(renderer.toJSON()).toEqual({ | ||
type: 'div', | ||
props: {}, | ||
children: ['foo:', 'def', ', bar:', '123'], | ||
}); | ||
|
||
act(() => dispatchFn({type: 'swap'})); | ||
expect(renderer.toJSON()).toEqual({ | ||
type: 'div', | ||
props: {}, | ||
children: ['foo:', '123', ', bar:', 'def'], | ||
}); | ||
} | ||
}); | ||
|
||
// This test case is based on an open source bug report: | ||
// facebookincubator/redux-react-hook/issues/34#issuecomment-466693787 | ||
it('should handle interleaved stateful hooks (e.g. useState) and non-stateful hooks (e.g. useContext)', () => { | ||
const MyContext = React.createContext(1); | ||
|
||
let setStateFn; | ||
function useCustomHook() { | ||
const context = React.useContext(MyContext); | ||
const [state, setState] = React.useState({count: context}); | ||
React.useDebugValue(state.count); | ||
setStateFn = setState; | ||
return state.count; | ||
} | ||
|
||
function MyComponent() { | ||
const count = useCustomHook(); | ||
return <div>count:{count}</div>; | ||
} | ||
|
||
const renderer = ReactTestRenderer.create(<MyComponent />); | ||
expect(renderer.toJSON()).toEqual({ | ||
type: 'div', | ||
props: {}, | ||
children: ['count:', '1'], | ||
}); | ||
|
||
const fiber = renderer.root.findByType(MyComponent)._currentFiber(); | ||
const tree = ReactDebugTools.inspectHooksOfFiber(fiber); | ||
const stateHook = tree[0].subHooks[1]; | ||
expect(stateHook.isStateEditable).toBe(true); | ||
|
||
if (__DEV__) { | ||
overrideHookState(fiber, stateHook.id, ['count'], 10); | ||
expect(renderer.toJSON()).toEqual({ | ||
type: 'div', | ||
props: {}, | ||
children: ['count:', '10'], | ||
}); | ||
|
||
act(() => setStateFn(state => ({count: state.count + 1}))); | ||
expect(renderer.toJSON()).toEqual({ | ||
type: 'div', | ||
props: {}, | ||
children: ['count:', '11'], | ||
}); | ||
} | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.