From e10125d0eb790f54d110330cf38956fd92fb3696 Mon Sep 17 00:00:00 2001 From: Mengdi Chen Date: Fri, 25 Mar 2022 14:36:39 -0400 Subject: [PATCH 01/12] [ReactDebugTools] wrap uncaught error from rendering user's component --- .../react-debug-tools/src/ReactDebugHooks.js | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/packages/react-debug-tools/src/ReactDebugHooks.js b/packages/react-debug-tools/src/ReactDebugHooks.js index 49bf65e3136fa..f8f30ff21dfc8 100644 --- a/packages/react-debug-tools/src/ReactDebugHooks.js +++ b/packages/react-debug-tools/src/ReactDebugHooks.js @@ -667,6 +667,28 @@ function processDebugValues( } } +function handleRenderFunctionError(error: any): void { + // original error might be any type. + const isError = error instanceof Error; + if (isError && error.name === 'UnsupportedFeatureError') { + throw error; + } + // If the error is not caused by an unsupported feature, it means + // that the error is caused by user's code in renderFunction. + // In this case, we should wrap the original error inside a custom error + // so that devtools can show a clear message for it. + const messgae: string = + isError && error.message + ? error.message + : 'Error rendering inspected component' + // $FlowFixMe: Flow doesn't know about 2nd argument of Error constructor + const wrapperError = new Error(messgae, {cause: error}); + // Note: This error name needs to stay in sync with react-devtools-shared + // TODO: refactor this if we ever combine the devtools and debug tools packages + wrapperError.name = 'RenderFunctionError'; + throw wrapperError; +} + export function inspectHooks( renderFunction: Props => React$Node, props: Props, @@ -686,6 +708,8 @@ export function inspectHooks( try { ancestorStackError = new Error(); renderFunction(props); + } catch (error) { + handleRenderFunctionError(error); } finally { readHookLog = hookLog; hookLog = []; @@ -730,6 +754,8 @@ function inspectHooksOfForwardRef( try { ancestorStackError = new Error(); renderFunction(props, ref); + } catch (error) { + handleRenderFunctionError(error); } finally { readHookLog = hookLog; hookLog = []; From 82bcb13efefee306ba50b653ce579540c34485f4 Mon Sep 17 00:00:00 2001 From: Mengdi Chen Date: Wed, 30 Mar 2022 11:46:12 -0400 Subject: [PATCH 02/12] fix lint --- packages/react-debug-tools/src/ReactDebugHooks.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-debug-tools/src/ReactDebugHooks.js b/packages/react-debug-tools/src/ReactDebugHooks.js index f8f30ff21dfc8..7b74082e8c221 100644 --- a/packages/react-debug-tools/src/ReactDebugHooks.js +++ b/packages/react-debug-tools/src/ReactDebugHooks.js @@ -680,7 +680,7 @@ function handleRenderFunctionError(error: any): void { const messgae: string = isError && error.message ? error.message - : 'Error rendering inspected component' + : 'Error rendering inspected component'; // $FlowFixMe: Flow doesn't know about 2nd argument of Error constructor const wrapperError = new Error(messgae, {cause: error}); // Note: This error name needs to stay in sync with react-devtools-shared From 35faa3ab94a4a977dad0a440786e33dc830affb8 Mon Sep 17 00:00:00 2001 From: Mengdi Chen Date: Wed, 30 Mar 2022 12:47:52 -0400 Subject: [PATCH 03/12] make error names more package specific --- packages/react-debug-tools/src/ReactDebugHooks.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/react-debug-tools/src/ReactDebugHooks.js b/packages/react-debug-tools/src/ReactDebugHooks.js index 7b74082e8c221..7d20780ed199f 100644 --- a/packages/react-debug-tools/src/ReactDebugHooks.js +++ b/packages/react-debug-tools/src/ReactDebugHooks.js @@ -366,7 +366,7 @@ const DispatcherProxyHandler = { const error = new Error('Missing method in Dispatcher: ' + prop); // Note: This error name needs to stay in sync with react-devtools-shared // TODO: refactor this if we ever combine the devtools and debug tools packages - error.name = 'UnsupportedFeatureError'; + error.name = 'ReactDebugToolsUnsupportedFeatureError'; throw error; }, }; @@ -670,7 +670,7 @@ function processDebugValues( function handleRenderFunctionError(error: any): void { // original error might be any type. const isError = error instanceof Error; - if (isError && error.name === 'UnsupportedFeatureError') { + if (isError && error.name === 'ReactDebugToolsUnsupportedFeatureError') { throw error; } // If the error is not caused by an unsupported feature, it means @@ -685,7 +685,7 @@ function handleRenderFunctionError(error: any): void { const wrapperError = new Error(messgae, {cause: error}); // Note: This error name needs to stay in sync with react-devtools-shared // TODO: refactor this if we ever combine the devtools and debug tools packages - wrapperError.name = 'RenderFunctionError'; + wrapperError.name = 'ReactDebugToolsRenderFunctionError'; throw wrapperError; } From 589deb6b0799623dd1a8d3f0f73c013d00a6e704 Mon Sep 17 00:00:00 2001 From: Mengdi Chen Date: Wed, 30 Mar 2022 14:14:43 -0400 Subject: [PATCH 04/12] update per review comments --- .../react-debug-tools/src/ReactDebugHooks.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/react-debug-tools/src/ReactDebugHooks.js b/packages/react-debug-tools/src/ReactDebugHooks.js index 7d20780ed199f..43114fcc43999 100644 --- a/packages/react-debug-tools/src/ReactDebugHooks.js +++ b/packages/react-debug-tools/src/ReactDebugHooks.js @@ -366,7 +366,7 @@ const DispatcherProxyHandler = { const error = new Error('Missing method in Dispatcher: ' + prop); // Note: This error name needs to stay in sync with react-devtools-shared // TODO: refactor this if we ever combine the devtools and debug tools packages - error.name = 'ReactDebugToolsUnsupportedFeatureError'; + error.name = 'ReactDebugToolsUnsupportedHookError'; throw error; }, }; @@ -669,20 +669,20 @@ function processDebugValues( function handleRenderFunctionError(error: any): void { // original error might be any type. - const isError = error instanceof Error; - if (isError && error.name === 'ReactDebugToolsUnsupportedFeatureError') { + if ( + error instanceof Error && + error.name === 'ReactDebugToolsUnsupportedHookError' + ) { throw error; } // If the error is not caused by an unsupported feature, it means // that the error is caused by user's code in renderFunction. // In this case, we should wrap the original error inside a custom error - // so that devtools can show a clear message for it. - const messgae: string = - isError && error.message - ? error.message - : 'Error rendering inspected component'; + // so that devtools can give a clear message about it. // $FlowFixMe: Flow doesn't know about 2nd argument of Error constructor - const wrapperError = new Error(messgae, {cause: error}); + const wrapperError = new Error('Error rendering inspected component', { + cause: error, + }); // Note: This error name needs to stay in sync with react-devtools-shared // TODO: refactor this if we ever combine the devtools and debug tools packages wrapperError.name = 'ReactDebugToolsRenderFunctionError'; From 56f0584d623641498d212599c49956bc8a4f1e24 Mon Sep 17 00:00:00 2001 From: Mengdi Chen Date: Wed, 30 Mar 2022 15:17:51 -0400 Subject: [PATCH 05/12] fix tests --- .../src/__tests__/ReactHooksInspection-test.js | 14 +++++++++++++- .../src/__tests__/inspectedElement-test.js | 2 +- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/packages/react-debug-tools/src/__tests__/ReactHooksInspection-test.js b/packages/react-debug-tools/src/__tests__/ReactHooksInspection-test.js index 50eadbedcbe46..2bda3e47f854a 100644 --- a/packages/react-debug-tools/src/__tests__/ReactHooksInspection-test.js +++ b/packages/react-debug-tools/src/__tests__/ReactHooksInspection-test.js @@ -261,6 +261,10 @@ describe('ReactHooksInspection', () => { return
{state}
; } + const OriginalError = global.Error; + const MockError = jest.fn(); + global.Error = MockError; + const initial = {}; let current = initial; let getterCalls = 0; @@ -279,7 +283,13 @@ describe('ReactHooksInspection', () => { expect(() => { expect(() => { ReactDebugTools.inspectHooks(Foo, {}, FakeDispatcherRef); - }).toThrow("Cannot read property 'useState' of null"); + }).toThrow(MockError); + expect(MockError.mock.calls.length).toBe(1); + // first argument is the error message + expect(MockError.mock.calls[0][0]).toBe('Error rendering inspected component'); + // The second arg of the first call to the function was 'second arg' + expect(MockError.mock.calls[0][1]).toBeInstanceOf(OriginalError); + expect(MockError.mock.calls[0][1].message).toBe("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' + @@ -294,6 +304,8 @@ describe('ReactHooksInspection', () => { expect(setterCalls).toHaveLength(2); expect(setterCalls[0]).not.toBe(initial); expect(setterCalls[1]).toBe(initial); + + global.Error = OriginalError; }); describe('useDebugValue', () => { diff --git a/packages/react-devtools-shared/src/__tests__/inspectedElement-test.js b/packages/react-devtools-shared/src/__tests__/inspectedElement-test.js index 919fbb059f76b..81c0a3a758212 100644 --- a/packages/react-devtools-shared/src/__tests__/inspectedElement-test.js +++ b/packages/react-devtools-shared/src/__tests__/inspectedElement-test.js @@ -2145,7 +2145,7 @@ describe('InspectedElement', () => { expect(value).toBe(null); const error = errorBoundaryInstance.state.error; - expect(error.message).toBe('Expected'); + expect(error.message).toBe('Error rendering inspected component'); expect(error.stack).toContain('inspectHooksOfFiber'); }); From e1808652c50ab48be1e6c478429513b87c37bdda Mon Sep 17 00:00:00 2001 From: Mengdi Chen Date: Wed, 30 Mar 2022 15:37:31 -0400 Subject: [PATCH 06/12] fix lint --- .../src/__tests__/ReactHooksInspection-test.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/react-debug-tools/src/__tests__/ReactHooksInspection-test.js b/packages/react-debug-tools/src/__tests__/ReactHooksInspection-test.js index 2bda3e47f854a..5fb65b045e5e9 100644 --- a/packages/react-debug-tools/src/__tests__/ReactHooksInspection-test.js +++ b/packages/react-debug-tools/src/__tests__/ReactHooksInspection-test.js @@ -286,10 +286,14 @@ describe('ReactHooksInspection', () => { }).toThrow(MockError); expect(MockError.mock.calls.length).toBe(1); // first argument is the error message - expect(MockError.mock.calls[0][0]).toBe('Error rendering inspected component'); + expect(MockError.mock.calls[0][0]).toBe( + 'Error rendering inspected component', + ); // The second arg of the first call to the function was 'second arg' expect(MockError.mock.calls[0][1]).toBeInstanceOf(OriginalError); - expect(MockError.mock.calls[0][1].message).toBe("Cannot read property 'useState' of null"); + expect(MockError.mock.calls[0][1].message).toBe( + "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' + From 2a3e9d4f43ec2c64c6176a545cff2f717bf81669 Mon Sep 17 00:00:00 2001 From: Mengdi Chen Date: Thu, 31 Mar 2022 14:16:20 -0400 Subject: [PATCH 07/12] fix tests --- .../__tests__/ReactHooksInspection-test.js | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/packages/react-debug-tools/src/__tests__/ReactHooksInspection-test.js b/packages/react-debug-tools/src/__tests__/ReactHooksInspection-test.js index 5fb65b045e5e9..64855dbcb16e2 100644 --- a/packages/react-debug-tools/src/__tests__/ReactHooksInspection-test.js +++ b/packages/react-debug-tools/src/__tests__/ReactHooksInspection-test.js @@ -261,10 +261,6 @@ describe('ReactHooksInspection', () => { return
{state}
; } - const OriginalError = global.Error; - const MockError = jest.fn(); - global.Error = MockError; - const initial = {}; let current = initial; let getterCalls = 0; @@ -281,18 +277,28 @@ describe('ReactHooksInspection', () => { }; expect(() => { + // mock the Error constructor to check the internal of the error instance + const OriginalError = global.Error; + const MockError = jest.fn(); + global.Error = MockError; + expect(() => { ReactDebugTools.inspectHooks(Foo, {}, FakeDispatcherRef); }).toThrow(MockError); - expect(MockError.mock.calls.length).toBe(1); + // clean up + global.Error = OriginalError; + + // expect the thrown Error is the last one to call the MockError + const lastCall = MockError.mock.calls[MockError.mock.calls.length - 1]; // first argument is the error message - expect(MockError.mock.calls[0][0]).toBe( + expect(lastCall[0]).toBe( 'Error rendering inspected component', ); - // The second arg of the first call to the function was 'second arg' - expect(MockError.mock.calls[0][1]).toBeInstanceOf(OriginalError); - expect(MockError.mock.calls[0][1].message).toBe( - "Cannot read property 'useState' of null", + // The second arg is the options object with the cause, which is the + // original error + expect(lastCall[1].cause).toBeInstanceOf(OriginalError); + expect(lastCall[1].cause.message).toBe( + "Cannot read properties of null (reading 'useState')", ); }).toErrorDev( 'Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for' + @@ -309,7 +315,6 @@ describe('ReactHooksInspection', () => { expect(setterCalls[0]).not.toBe(initial); expect(setterCalls[1]).toBe(initial); - global.Error = OriginalError; }); describe('useDebugValue', () => { From e4039a427410412f8b879de13b7dc6f388b75ed0 Mon Sep 17 00:00:00 2001 From: Mengdi Chen Date: Thu, 31 Mar 2022 14:18:28 -0400 Subject: [PATCH 08/12] fix lint --- .../src/__tests__/ReactHooksInspection-test.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/react-debug-tools/src/__tests__/ReactHooksInspection-test.js b/packages/react-debug-tools/src/__tests__/ReactHooksInspection-test.js index 64855dbcb16e2..2f4688a86dfd3 100644 --- a/packages/react-debug-tools/src/__tests__/ReactHooksInspection-test.js +++ b/packages/react-debug-tools/src/__tests__/ReactHooksInspection-test.js @@ -291,9 +291,7 @@ describe('ReactHooksInspection', () => { // expect the thrown Error is the last one to call the MockError const lastCall = MockError.mock.calls[MockError.mock.calls.length - 1]; // first argument is the error message - expect(lastCall[0]).toBe( - 'Error rendering inspected component', - ); + expect(lastCall[0]).toBe('Error rendering inspected component'); // The second arg is the options object with the cause, which is the // original error expect(lastCall[1].cause).toBeInstanceOf(OriginalError); @@ -314,7 +312,6 @@ describe('ReactHooksInspection', () => { expect(setterCalls).toHaveLength(2); expect(setterCalls[0]).not.toBe(initial); expect(setterCalls[1]).toBe(initial); - }); describe('useDebugValue', () => { From 4939cb25fd3523ee6b2aed6102663996e5d5bd81 Mon Sep 17 00:00:00 2001 From: Mengdi Chen Date: Thu, 31 Mar 2022 14:23:31 -0400 Subject: [PATCH 09/12] fix error name & nits --- packages/react-debug-tools/src/ReactDebugHooks.js | 2 +- .../src/__tests__/ReactHooksInspection-test.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-debug-tools/src/ReactDebugHooks.js b/packages/react-debug-tools/src/ReactDebugHooks.js index 43114fcc43999..40a8253501dc0 100644 --- a/packages/react-debug-tools/src/ReactDebugHooks.js +++ b/packages/react-debug-tools/src/ReactDebugHooks.js @@ -685,7 +685,7 @@ function handleRenderFunctionError(error: any): void { }); // Note: This error name needs to stay in sync with react-devtools-shared // TODO: refactor this if we ever combine the devtools and debug tools packages - wrapperError.name = 'ReactDebugToolsRenderFunctionError'; + wrapperError.name = 'ReactDebugToolsRenderError'; throw wrapperError; } diff --git a/packages/react-debug-tools/src/__tests__/ReactHooksInspection-test.js b/packages/react-debug-tools/src/__tests__/ReactHooksInspection-test.js index 2f4688a86dfd3..fb66b0bb8086d 100644 --- a/packages/react-debug-tools/src/__tests__/ReactHooksInspection-test.js +++ b/packages/react-debug-tools/src/__tests__/ReactHooksInspection-test.js @@ -296,7 +296,7 @@ describe('ReactHooksInspection', () => { // original error expect(lastCall[1].cause).toBeInstanceOf(OriginalError); expect(lastCall[1].cause.message).toBe( - "Cannot read properties of null (reading 'useState')", + "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' + From ce34cfe081479bfdf04c338efd847865ce1e2242 Mon Sep 17 00:00:00 2001 From: Mengdi Chen Date: Fri, 1 Apr 2022 12:00:18 -0400 Subject: [PATCH 10/12] try catch instead of mocking error --- .../__tests__/ReactHooksInspection-test.js | 30 +++++++------------ .../ReactHooksInspectionIntegration-test.js | 27 ++++++++++------- 2 files changed, 28 insertions(+), 29 deletions(-) diff --git a/packages/react-debug-tools/src/__tests__/ReactHooksInspection-test.js b/packages/react-debug-tools/src/__tests__/ReactHooksInspection-test.js index fb66b0bb8086d..ecd7a3f20730d 100644 --- a/packages/react-debug-tools/src/__tests__/ReactHooksInspection-test.js +++ b/packages/react-debug-tools/src/__tests__/ReactHooksInspection-test.js @@ -278,26 +278,18 @@ describe('ReactHooksInspection', () => { expect(() => { // mock the Error constructor to check the internal of the error instance - const OriginalError = global.Error; - const MockError = jest.fn(); - global.Error = MockError; - - expect(() => { + try { ReactDebugTools.inspectHooks(Foo, {}, FakeDispatcherRef); - }).toThrow(MockError); - // clean up - global.Error = OriginalError; - - // expect the thrown Error is the last one to call the MockError - const lastCall = MockError.mock.calls[MockError.mock.calls.length - 1]; - // first argument is the error message - expect(lastCall[0]).toBe('Error rendering inspected component'); - // The second arg is the options object with the cause, which is the - // original error - expect(lastCall[1].cause).toBeInstanceOf(OriginalError); - expect(lastCall[1].cause.message).toBe( - "Cannot read property 'useState' of null", - ); + } catch (error) { + // first argument is the error message + expect(error.message).toBe('Error rendering inspected component'); + // The second arg is the options object with the cause, which is the + // original error + expect(error.cause).toBeInstanceOf(Error); + expect(error.cause.message).toBe( + "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' + diff --git a/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js b/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js index f5003e96bdf6c..be2d737847171 100644 --- a/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js +++ b/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js @@ -920,17 +920,24 @@ describe('ReactHooksInspectionIntegration', () => { const renderer = ReactTestRenderer.create(); const childFiber = renderer.root._currentFiber(); - expect(() => { - ReactDebugTools.inspectHooksOfFiber(childFiber, FakeDispatcherRef); - }).toThrow( - '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.', - ); + try { + ReactDebugTools.inspectHooksOfFiber(childFiber, FakeDispatcherRef); + } catch (error) { + // first argument is the error message + expect(error.message).toBe('Error rendering inspected component'); + // The second arg is the options object with the cause, which is the + // original error + expect(error.cause).toBeInstanceOf(Error); + expect(error.cause.message).toBe( + '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.', + ); + } expect(getterCalls).toBe(1); expect(setterCalls).toHaveLength(2); expect(setterCalls[0]).not.toBe(initial); From 19cb6e5c4949acb8a10aa44a7abebeda8759cf7b Mon Sep 17 00:00:00 2001 From: Mengdi Chen Date: Fri, 1 Apr 2022 12:54:49 -0400 Subject: [PATCH 11/12] fix test for older node.js version --- packages/react-debug-tools/src/ReactDebugHooks.js | 2 ++ .../src/__tests__/ReactHooksInspection-test.js | 4 +--- .../src/__tests__/ReactHooksInspectionIntegration-test.js | 3 --- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/packages/react-debug-tools/src/ReactDebugHooks.js b/packages/react-debug-tools/src/ReactDebugHooks.js index 40a8253501dc0..3657ed2db059a 100644 --- a/packages/react-debug-tools/src/ReactDebugHooks.js +++ b/packages/react-debug-tools/src/ReactDebugHooks.js @@ -686,6 +686,8 @@ function handleRenderFunctionError(error: any): void { // Note: This error name needs to stay in sync with react-devtools-shared // TODO: refactor this if we ever combine the devtools and debug tools packages wrapperError.name = 'ReactDebugToolsRenderError'; + // this stage-4 proposal is not supported by all environments yet. + wrapperError.cause = error; throw wrapperError; } diff --git a/packages/react-debug-tools/src/__tests__/ReactHooksInspection-test.js b/packages/react-debug-tools/src/__tests__/ReactHooksInspection-test.js index ecd7a3f20730d..7a116f3f42c64 100644 --- a/packages/react-debug-tools/src/__tests__/ReactHooksInspection-test.js +++ b/packages/react-debug-tools/src/__tests__/ReactHooksInspection-test.js @@ -281,10 +281,8 @@ describe('ReactHooksInspection', () => { try { ReactDebugTools.inspectHooks(Foo, {}, FakeDispatcherRef); } catch (error) { - // first argument is the error message expect(error.message).toBe('Error rendering inspected component'); - // The second arg is the options object with the cause, which is the - // original error + // error.cause is the original error expect(error.cause).toBeInstanceOf(Error); expect(error.cause.message).toBe( "Cannot read property 'useState' of null", diff --git a/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js b/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js index be2d737847171..3edfa32e39185 100644 --- a/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js +++ b/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js @@ -924,10 +924,7 @@ describe('ReactHooksInspectionIntegration', () => { try { ReactDebugTools.inspectHooksOfFiber(childFiber, FakeDispatcherRef); } catch (error) { - // first argument is the error message expect(error.message).toBe('Error rendering inspected component'); - // The second arg is the options object with the cause, which is the - // original error expect(error.cause).toBeInstanceOf(Error); expect(error.cause.message).toBe( 'Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for' + From 79d0f594466f7209d669b28560dc95d2a01a6aec Mon Sep 17 00:00:00 2001 From: Mengdi Chen Date: Fri, 1 Apr 2022 13:30:23 -0400 Subject: [PATCH 12/12] avoid false positive from try-catch in tests --- .../src/__tests__/ReactHooksInspection-test.js | 4 ++++ .../src/__tests__/ReactHooksInspectionIntegration-test.js | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/packages/react-debug-tools/src/__tests__/ReactHooksInspection-test.js b/packages/react-debug-tools/src/__tests__/ReactHooksInspection-test.js index 7a116f3f42c64..43624ce37affa 100644 --- a/packages/react-debug-tools/src/__tests__/ReactHooksInspection-test.js +++ b/packages/react-debug-tools/src/__tests__/ReactHooksInspection-test.js @@ -276,6 +276,7 @@ describe('ReactHooksInspection', () => { }, }; + let didCatch = false; expect(() => { // mock the Error constructor to check the internal of the error instance try { @@ -288,6 +289,7 @@ describe('ReactHooksInspection', () => { "Cannot read property 'useState' of null", ); } + didCatch = true; }).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' + @@ -297,6 +299,8 @@ describe('ReactHooksInspection', () => { 'See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.', {withoutStack: true}, ); + // avoid false positive if no error was thrown at all + expect(didCatch).toBe(true); expect(getterCalls).toBe(1); expect(setterCalls).toHaveLength(2); diff --git a/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js b/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js index 3edfa32e39185..e57f084975d63 100644 --- a/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js +++ b/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js @@ -921,6 +921,8 @@ describe('ReactHooksInspectionIntegration', () => { const renderer = ReactTestRenderer.create(); const childFiber = renderer.root._currentFiber(); + let didCatch = false; + try { ReactDebugTools.inspectHooksOfFiber(childFiber, FakeDispatcherRef); } catch (error) { @@ -934,7 +936,11 @@ describe('ReactHooksInspectionIntegration', () => { '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.', ); + didCatch = true; } + // avoid false positive if no error was thrown at all + expect(didCatch).toBe(true); + expect(getterCalls).toBe(1); expect(setterCalls).toHaveLength(2); expect(setterCalls[0]).not.toBe(initial);