/*** Copyright (c) Meta Platforms, Inc. and 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;
let scheduleUpdate;
let setSuspenseHandler;
let waitForAll;
global.IS_REACT_ACT_ENVIRONMENT = true;
beforeEach(() => {
global.__REACT_DEVTOOLS_GLOBAL_HOOK__ = {inject: injected => {
overrideHookState = injected.overrideHookState;
scheduleUpdate = injected.scheduleUpdate;
setSuspenseHandler = injected.setSuspenseHandler;
},supportsFiber: true,
onCommitFiberRoot: () => {},
onCommitFiberUnmount: () => {},
};jest.resetModules();
React = require('react');
ReactDebugTools = require('react-debug-tools');
ReactTestRenderer = require('react-test-renderer');
const InternalTestUtils = require('internal-test-utils');
waitForAll = InternalTestUtils.waitForAll;
act = ReactTestRenderer.act;
});it('should support editing useState hooks', async () => {
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__) {await act(() => overrideHookState(fiber, stateHook.id, [], 10));
expect(renderer.toJSON()).toEqual({
type: 'div',props: {},children: ['count:', '10'],
});await act(() => setCountFn(count => count + 1));
expect(renderer.toJSON()).toEqual({
type: 'div',props: {},children: ['count:', '11'],
});}});it('should support editable useReducer hooks', async () => {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__) {await act(() => overrideHookState(fiber, reducerHook.id, ['foo'], 'def'));
expect(renderer.toJSON()).toEqual({
type: 'div',props: {},children: ['foo:', 'def', ', bar:', '123'],
});await 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:
// https://github.com/facebookincubator/redux-react-hook/issues/34#issuecomment-466693787
it('should handle interleaved stateful hooks (e.g. useState) and non-stateful hooks (e.g. useContext)', async () => {
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__) {await act(() => overrideHookState(fiber, stateHook.id, ['count'], 10));
expect(renderer.toJSON()).toEqual({
type: 'div',props: {},children: ['count:', '10'],
});await act(() => setStateFn(state => ({count: state.count + 1})));
expect(renderer.toJSON()).toEqual({
type: 'div',props: {},children: ['count:', '11'],
});}});it('should support overriding suspense in legacy mode', async () => {if (__DEV__) {// Lock the first rendersetSuspenseHandler(() => true);}function MyComponent() {return 'Done';}const renderer = ReactTestRenderer.create(
<div><React.Suspense fallback={'Loading'}>
<MyComponent /></React.Suspense>
</div>,);const fiber = renderer.root._currentFiber().child;
if (__DEV__) {// First render was lockedexpect(renderer.toJSON().children).toEqual(['Loading']);
await act(() => scheduleUpdate(fiber)); // Re-renderexpect(renderer.toJSON().children).toEqual(['Loading']);
// Release the locksetSuspenseHandler(() => false);await act(() => scheduleUpdate(fiber)); // Re-renderexpect(renderer.toJSON().children).toEqual(['Done']);
await act(() => scheduleUpdate(fiber)); // Re-renderexpect(renderer.toJSON().children).toEqual(['Done']);
// Lock againsetSuspenseHandler(() => true);await act(() => scheduleUpdate(fiber)); // Re-renderexpect(renderer.toJSON().children).toEqual(['Loading']);
// Release the lock againsetSuspenseHandler(() => false);await act(() => scheduleUpdate(fiber)); // Re-renderexpect(renderer.toJSON().children).toEqual(['Done']);
// Ensure it checks specific fibers.
setSuspenseHandler(f => f === fiber || f === fiber.alternate);
await act(() => scheduleUpdate(fiber)); // Re-renderexpect(renderer.toJSON().children).toEqual(['Loading']);
setSuspenseHandler(f => f !== fiber && f !== fiber.alternate);
await act(() => scheduleUpdate(fiber)); // Re-renderexpect(renderer.toJSON().children).toEqual(['Done']);
} else {expect(renderer.toJSON().children).toEqual(['Done']);
}});// @gate __DEV__
it('should support overriding suspense in concurrent mode', async () => {
if (__DEV__) {
// Lock the first render
setSuspenseHandler(() => true);
}function MyComponent() {
return 'Done';
}const renderer = await act(() =>
ReactTestRenderer.create(
<div>
<React.Suspense fallback={'Loading'}>
<MyComponent />
</React.Suspense>
</div>,
{unstable_isConcurrent: true},
),);await waitForAll([]);
// Ensure we timeout any suspense time.
jest.advanceTimersByTime(1000);
const fiber = renderer.root._currentFiber().child;
if (__DEV__) {
// First render was locked
expect(renderer.toJSON().children).toEqual(['Loading']);
await act(() => scheduleUpdate(fiber)); // Re-render
expect(renderer.toJSON().children).toEqual(['Loading']);
// Release the lock
setSuspenseHandler(() => false);
await act(() => scheduleUpdate(fiber)); // Re-render
expect(renderer.toJSON().children).toEqual(['Done']);
await act(() => scheduleUpdate(fiber)); // Re-render
expect(renderer.toJSON().children).toEqual(['Done']);
// Lock again
setSuspenseHandler(() => true);
await act(() => scheduleUpdate(fiber)); // Re-render
expect(renderer.toJSON().children).toEqual(['Loading']);
// Release the lock again
setSuspenseHandler(() => false);
await act(() => scheduleUpdate(fiber)); // Re-render
expect(renderer.toJSON().children).toEqual(['Done']);
// Ensure it checks specific fibers.
setSuspenseHandler(f => f === fiber || f === fiber.alternate);
await act(() => scheduleUpdate(fiber)); // Re-render
expect(renderer.toJSON().children).toEqual(['Loading']);
setSuspenseHandler(f => f !== fiber && f !== fiber.alternate);
await act(() => scheduleUpdate(fiber)); // Re-render
expect(renderer.toJSON().children).toEqual(['Done']);
} else {
expect(renderer.toJSON().children).toEqual(['Done']);
}});});