diff --git a/jest/setup.js b/jest/setup.js index 66d583af..e1c762c4 100644 --- a/jest/setup.js +++ b/jest/setup.js @@ -2,7 +2,7 @@ const error = console.error; console.error = (...args) => // Supress error messages regarding error boundary in tests - /Consider adding an error boundary to your tree to customize error handling behavior/m.test( + /(Consider adding an error boundary to your tree to customize error handling behavior|React will try to recreate this component tree from scratch using the error boundary you provided|Error boundaries should implement getDerivedStateFromError)/m.test( args[0] ) ? void 0 diff --git a/packages/core/src/__tests__/index.test.tsx b/packages/core/src/__tests__/index.test.tsx index 0b081414..bbe6375c 100644 --- a/packages/core/src/__tests__/index.test.tsx +++ b/packages/core/src/__tests__/index.test.tsx @@ -357,6 +357,8 @@ it("doesn't update state if action wasn't handled", () => { const onStateChange = jest.fn(); + const spy = jest.spyOn(console, 'error').mockImplementation(); + render( @@ -367,6 +369,12 @@ it("doesn't update state if action wasn't handled", () => { ); expect(onStateChange).toBeCalledTimes(0); + + expect(spy.mock.calls[0][0]).toMatch( + "The action 'INVALID' with payload 'undefined' was not handled by any navigator." + ); + + spy.mockRestore(); }); it('cleans up state when the navigator unmounts', () => { diff --git a/packages/core/src/__tests__/useOnAction.test.tsx b/packages/core/src/__tests__/useOnAction.test.tsx index 0bf165c3..9cff7fe1 100644 --- a/packages/core/src/__tests__/useOnAction.test.tsx +++ b/packages/core/src/__tests__/useOnAction.test.tsx @@ -305,8 +305,7 @@ it("action doesn't bubble if target is specified", () => { expect(onStateChange).not.toBeCalled(); }); -// eslint-disable-next-line jest/expect-expect -it("doesn't crash if no navigator handled the action", () => { +it('logs error if no navigator handled the action', () => { const TestRouter = MockRouter; const TestNavigator = (props: any) => { @@ -366,5 +365,13 @@ it("doesn't crash if no navigator handled the action", () => { ); + const spy = jest.spyOn(console, 'error').mockImplementation(); + render(element).update(element); + + expect(spy.mock.calls[0][0]).toMatch( + "The action 'UNKNOWN' with payload 'undefined' was not handled by any navigator." + ); + + spy.mockRestore(); }); diff --git a/packages/core/src/types.tsx b/packages/core/src/types.tsx index 1eb883d2..561795ad 100644 --- a/packages/core/src/types.tsx +++ b/packages/core/src/types.tsx @@ -71,6 +71,10 @@ export type NavigationAction = { * Type of the action (e.g. `NAVIGATE`) */ type: string; + /** + * Additional data for the action + */ + payload?: object; /** * Key of the route which dispatched this action. */ diff --git a/packages/core/src/useNavigationHelpers.tsx b/packages/core/src/useNavigationHelpers.tsx index d153319c..fa1eaa57 100644 --- a/packages/core/src/useNavigationHelpers.tsx +++ b/packages/core/src/useNavigationHelpers.tsx @@ -42,13 +42,22 @@ export default function useNavigationHelpers< const { performTransaction } = React.useContext(NavigationStateContext); return React.useMemo(() => { - const dispatch = (action: Action | ((state: State) => Action)) => + const dispatch = (action: Action | ((state: State) => Action)) => { performTransaction(() => { const payload = typeof action === 'function' ? action(getState()) : action; - onAction(payload); + const handled = onAction(payload); + + if (!handled && process.env.NODE_ENV !== 'production') { + console.error( + `The action '${payload.type}' with payload '${JSON.stringify( + payload.payload + )}' was not handled by any navigator.` + ); + } }); + }; const actions = { ...router.actionCreators,