mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
58e8304483
It will still result in a null access error, so there's no change in semantics. We will print a user-friendly error message in DEV.
339 lines
8.3 KiB
JavaScript
339 lines
8.3 KiB
JavaScript
/**
|
|
* 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';
|
|
|
|
let React;
|
|
let ReactDebugTools;
|
|
|
|
describe('ReactHooksInspection', () => {
|
|
beforeEach(() => {
|
|
jest.resetModules();
|
|
React = require('react');
|
|
ReactDebugTools = require('react-debug-tools');
|
|
});
|
|
|
|
it('should inspect a simple useState hook', () => {
|
|
function Foo(props) {
|
|
const [state] = React.useState('hello world');
|
|
return <div>{state}</div>;
|
|
}
|
|
const tree = ReactDebugTools.inspectHooks(Foo, {});
|
|
expect(tree).toEqual([
|
|
{
|
|
isStateEditable: true,
|
|
id: 0,
|
|
name: 'State',
|
|
value: 'hello world',
|
|
subHooks: [],
|
|
},
|
|
]);
|
|
});
|
|
|
|
it('should inspect a simple custom hook', () => {
|
|
function useCustom(value) {
|
|
const [state] = React.useState(value);
|
|
React.useDebugValue('custom hook label');
|
|
return state;
|
|
}
|
|
function Foo(props) {
|
|
const value = useCustom('hello world');
|
|
return <div>{value}</div>;
|
|
}
|
|
const tree = ReactDebugTools.inspectHooks(Foo, {});
|
|
expect(tree).toEqual([
|
|
{
|
|
isStateEditable: false,
|
|
id: null,
|
|
name: 'Custom',
|
|
value: __DEV__ ? 'custom hook label' : undefined,
|
|
subHooks: [
|
|
{
|
|
isStateEditable: true,
|
|
id: 0,
|
|
name: 'State',
|
|
value: 'hello world',
|
|
subHooks: [],
|
|
},
|
|
],
|
|
},
|
|
]);
|
|
});
|
|
|
|
it('should inspect a tree of multiple hooks', () => {
|
|
function effect() {}
|
|
function useCustom(value) {
|
|
const [state] = React.useState(value);
|
|
React.useEffect(effect);
|
|
return state;
|
|
}
|
|
function Foo(props) {
|
|
const value1 = useCustom('hello');
|
|
const value2 = useCustom('world');
|
|
return (
|
|
<div>
|
|
{value1} {value2}
|
|
</div>
|
|
);
|
|
}
|
|
const tree = ReactDebugTools.inspectHooks(Foo, {});
|
|
expect(tree).toEqual([
|
|
{
|
|
isStateEditable: false,
|
|
id: null,
|
|
name: 'Custom',
|
|
value: undefined,
|
|
subHooks: [
|
|
{
|
|
isStateEditable: true,
|
|
id: 0,
|
|
name: 'State',
|
|
subHooks: [],
|
|
value: 'hello',
|
|
},
|
|
{
|
|
isStateEditable: false,
|
|
id: 1,
|
|
name: 'Effect',
|
|
subHooks: [],
|
|
value: effect,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
isStateEditable: false,
|
|
id: null,
|
|
name: 'Custom',
|
|
value: undefined,
|
|
subHooks: [
|
|
{
|
|
isStateEditable: true,
|
|
id: 2,
|
|
name: 'State',
|
|
value: 'world',
|
|
subHooks: [],
|
|
},
|
|
{
|
|
isStateEditable: false,
|
|
id: 3,
|
|
name: 'Effect',
|
|
value: effect,
|
|
subHooks: [],
|
|
},
|
|
],
|
|
},
|
|
]);
|
|
});
|
|
|
|
it('should inspect a tree of multiple levels of hooks', () => {
|
|
function effect() {}
|
|
function useCustom(value) {
|
|
const [state] = React.useReducer((s, a) => s, value);
|
|
React.useEffect(effect);
|
|
return state;
|
|
}
|
|
function useBar(value) {
|
|
const result = useCustom(value);
|
|
React.useLayoutEffect(effect);
|
|
return result;
|
|
}
|
|
function useBaz(value) {
|
|
React.useLayoutEffect(effect);
|
|
const result = useCustom(value);
|
|
return result;
|
|
}
|
|
function Foo(props) {
|
|
const value1 = useBar('hello');
|
|
const value2 = useBaz('world');
|
|
return (
|
|
<div>
|
|
{value1} {value2}
|
|
</div>
|
|
);
|
|
}
|
|
const tree = ReactDebugTools.inspectHooks(Foo, {});
|
|
expect(tree).toEqual([
|
|
{
|
|
isStateEditable: false,
|
|
id: null,
|
|
name: 'Bar',
|
|
value: undefined,
|
|
subHooks: [
|
|
{
|
|
isStateEditable: false,
|
|
id: null,
|
|
name: 'Custom',
|
|
value: undefined,
|
|
subHooks: [
|
|
{
|
|
isStateEditable: true,
|
|
id: 0,
|
|
name: 'Reducer',
|
|
value: 'hello',
|
|
subHooks: [],
|
|
},
|
|
{
|
|
isStateEditable: false,
|
|
id: 1,
|
|
name: 'Effect',
|
|
value: effect,
|
|
subHooks: [],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
isStateEditable: false,
|
|
id: 2,
|
|
name: 'LayoutEffect',
|
|
value: effect,
|
|
subHooks: [],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
isStateEditable: false,
|
|
id: null,
|
|
name: 'Baz',
|
|
value: undefined,
|
|
subHooks: [
|
|
{
|
|
isStateEditable: false,
|
|
id: 3,
|
|
name: 'LayoutEffect',
|
|
value: effect,
|
|
subHooks: [],
|
|
},
|
|
{
|
|
isStateEditable: false,
|
|
id: null,
|
|
name: 'Custom',
|
|
subHooks: [
|
|
{
|
|
isStateEditable: true,
|
|
id: 4,
|
|
name: 'Reducer',
|
|
subHooks: [],
|
|
value: 'world',
|
|
},
|
|
{
|
|
isStateEditable: false,
|
|
id: 5,
|
|
name: 'Effect',
|
|
subHooks: [],
|
|
value: effect,
|
|
},
|
|
],
|
|
value: undefined,
|
|
},
|
|
],
|
|
},
|
|
]);
|
|
});
|
|
|
|
it('should inspect the default value using the useContext hook', () => {
|
|
const MyContext = React.createContext('default');
|
|
function Foo(props) {
|
|
const value = React.useContext(MyContext);
|
|
return <div>{value}</div>;
|
|
}
|
|
const tree = ReactDebugTools.inspectHooks(Foo, {});
|
|
expect(tree).toEqual([
|
|
{
|
|
isStateEditable: false,
|
|
id: null,
|
|
name: 'Context',
|
|
value: 'default',
|
|
subHooks: [],
|
|
},
|
|
]);
|
|
});
|
|
|
|
it('should support an injected dispatcher', () => {
|
|
function Foo(props) {
|
|
const [state] = React.useState('hello world');
|
|
return <div>{state}</div>;
|
|
}
|
|
|
|
const initial = {};
|
|
let current = initial;
|
|
let getterCalls = 0;
|
|
const setterCalls = [];
|
|
const FakeDispatcherRef = {
|
|
get current() {
|
|
getterCalls++;
|
|
return current;
|
|
},
|
|
set current(value) {
|
|
setterCalls.push(value);
|
|
current = value;
|
|
},
|
|
};
|
|
|
|
expect(() => {
|
|
expect(() => {
|
|
ReactDebugTools.inspectHooks(Foo, {}, FakeDispatcherRef);
|
|
}).toThrow("Cannot read property 'useState' of null");
|
|
}).toErrorDev(
|
|
'Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for' +
|
|
' one of the following reasons:\n' +
|
|
'1. You might have mismatching versions of React and the renderer (such as React DOM)\n' +
|
|
'2. You might be breaking the Rules of Hooks\n' +
|
|
'3. You might have more than one copy of React in the same app\n' +
|
|
'See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.',
|
|
{withoutStack: true},
|
|
);
|
|
|
|
expect(getterCalls).toBe(1);
|
|
expect(setterCalls).toHaveLength(2);
|
|
expect(setterCalls[0]).not.toBe(initial);
|
|
expect(setterCalls[1]).toBe(initial);
|
|
});
|
|
|
|
describe('useDebugValue', () => {
|
|
it('should be ignored when called outside of a custom hook', () => {
|
|
function Foo(props) {
|
|
React.useDebugValue('this is invalid');
|
|
return null;
|
|
}
|
|
const tree = ReactDebugTools.inspectHooks(Foo, {});
|
|
expect(tree).toHaveLength(0);
|
|
});
|
|
|
|
it('should support an optional formatter function param', () => {
|
|
function useCustom() {
|
|
React.useDebugValue({bar: 123}, object => `bar:${object.bar}`);
|
|
React.useState(0);
|
|
}
|
|
function Foo(props) {
|
|
useCustom();
|
|
return null;
|
|
}
|
|
const tree = ReactDebugTools.inspectHooks(Foo, {});
|
|
expect(tree).toEqual([
|
|
{
|
|
isStateEditable: false,
|
|
id: null,
|
|
name: 'Custom',
|
|
value: __DEV__ ? 'bar:123' : undefined,
|
|
subHooks: [
|
|
{
|
|
isStateEditable: true,
|
|
id: 0,
|
|
name: 'State',
|
|
subHooks: [],
|
|
value: 0,
|
|
},
|
|
],
|
|
},
|
|
]);
|
|
});
|
|
});
|
|
});
|