From 55253e84703545e815f57c33d51ede2803871f92 Mon Sep 17 00:00:00 2001 From: Lenz Weber Date: Sat, 4 Apr 2020 19:55:31 +0200 Subject: [PATCH] allow configuration of createSerializableStateInvariantMiddleware ignoredActionPaths, update default value, add tests (#457) --- etc/redux-toolkit.api.md | 1 + src/createAsyncThunk.test.ts | 13 ++++ ...rializableStateInvariantMiddleware.test.ts | 78 +++++++++++++------ src/serializableStateInvariantMiddleware.ts | 11 ++- 4 files changed, 78 insertions(+), 25 deletions(-) diff --git a/etc/redux-toolkit.api.md b/etc/redux-toolkit.api.md index ada4a0084..dd9dd679f 100644 --- a/etc/redux-toolkit.api.md +++ b/etc/redux-toolkit.api.md @@ -271,6 +271,7 @@ export { Selector } // @public export interface SerializableStateInvariantMiddlewareOptions { getEntries?: (value: any) => [string, any][]; + ignoredActionPaths?: string[]; ignoredActions?: string[]; ignoredPaths?: string[]; isSerializable?: (value: any) => boolean; diff --git a/src/createAsyncThunk.test.ts b/src/createAsyncThunk.test.ts index c1a5c150a..dd50f78d8 100644 --- a/src/createAsyncThunk.test.ts +++ b/src/createAsyncThunk.test.ts @@ -463,3 +463,16 @@ describe('createAsyncThunk with abortController', () => { }) }) }) + +test('non-serializable arguments are ignored by serializableStateInvariantMiddleware', async () => { + const restore = mockConsole(createConsole()) + const nonSerializableValue = new Map() + const asyncThunk = createAsyncThunk('test', (arg: Map) => {}) + + configureStore({ + reducer: () => 0 + }).dispatch(asyncThunk(nonSerializableValue)) + + expect(getLog().log).toMatchInlineSnapshot(`""`) + restore() +}) diff --git a/src/serializableStateInvariantMiddleware.test.ts b/src/serializableStateInvariantMiddleware.test.ts index c435b6592..a512ed507 100644 --- a/src/serializableStateInvariantMiddleware.test.ts +++ b/src/serializableStateInvariantMiddleware.test.ts @@ -330,6 +330,61 @@ describe('serializableStateInvariantMiddleware', () => { expect(numTimesCalled).toBeGreaterThan(0) }) + describe('ignored action paths', () => { + function reducer() { + return 0 + } + const nonSerializableValue = new Map() + + it('default value: meta.arg', () => { + configureStore({ + reducer, + middleware: [createSerializableStateInvariantMiddleware()] + }).dispatch({ type: 'test', meta: { arg: nonSerializableValue } }) + + expect(getLog().log).toMatchInlineSnapshot(`""`) + }) + + it('default value can be overridden', () => { + configureStore({ + reducer, + middleware: [ + createSerializableStateInvariantMiddleware({ + ignoredActionPaths: [] + }) + ] + }).dispatch({ type: 'test', meta: { arg: nonSerializableValue } }) + + expect(getLog().log).toMatchInlineSnapshot(` + "A non-serializable value was detected in an action, in the path: \`meta.arg\`. Value: Map {} + Take a look at the logic that dispatched this action: Object { + \\"meta\\": Object { + \\"arg\\": Map {}, + }, + \\"type\\": \\"test\\", + } + (See https://redux.js.org/faq/actions#why-should-type-be-a-string-or-at-least-serializable-why-should-my-action-types-be-constants)" + `) + }) + + it('can specify (multiple) different values', () => { + configureStore({ + reducer, + middleware: [ + createSerializableStateInvariantMiddleware({ + ignoredActionPaths: ['payload', 'meta.arg'] + }) + ] + }).dispatch({ + type: 'test', + payload: { arg: nonSerializableValue }, + meta: { arg: nonSerializableValue } + }) + + expect(getLog().log).toMatchInlineSnapshot(`""`) + }) + }) + it('should not check serializability for ignored slice names', () => { const ACTION_TYPE = 'TEST_ACTION' @@ -386,29 +441,6 @@ describe('serializableStateInvariantMiddleware', () => { `) }) - it('should not check serializability for meta.args by default', () => { - const badValue = new Map() - - const reducer: Reducer = (state = 42, action) => { - return state - } - - const serializableStateInvariantMiddleware = createSerializableStateInvariantMiddleware() - - const store = configureStore({ - reducer: { - testSlice: reducer - }, - middleware: [serializableStateInvariantMiddleware] - }) - - store.dispatch({ type: 'testAction', meta: { args: { badValue } } }) - - const { log } = getLog() - expect(log).toBe('') - const q = 42 - }) - it('Should print a warning if execution takes too long', () => { const reducer: Reducer = (state = 42, action) => { return state diff --git a/src/serializableStateInvariantMiddleware.ts b/src/serializableStateInvariantMiddleware.ts index a49554255..a69aaad23 100644 --- a/src/serializableStateInvariantMiddleware.ts +++ b/src/serializableStateInvariantMiddleware.ts @@ -36,7 +36,7 @@ export function findNonSerializableValue( path: ReadonlyArray = [], isSerializable: (value: unknown) => boolean = isPlain, getEntries?: (value: unknown) => [string, any][], - ignoredPaths: string[] = ['meta.args'] + ignoredPaths: string[] = [] ): NonSerializableValue | false { let foundNestedSerializable: NonSerializableValue | false @@ -111,6 +111,11 @@ export interface SerializableStateInvariantMiddlewareOptions { */ ignoredActions?: string[] + /** + * An array of dot-separated path strings to ignore when checking for serializability, Defaults to ['meta.arg'] + */ + ignoredActionPaths?: string[] + /** * An array of dot-separated path strings to ignore when checking for serializability, Defaults to [] */ @@ -140,6 +145,7 @@ export function createSerializableStateInvariantMiddleware( isSerializable = isPlain, getEntries, ignoredActions = [], + ignoredActionPaths = ['meta.arg'], ignoredPaths = [], warnAfter = 32 } = options @@ -158,7 +164,8 @@ export function createSerializableStateInvariantMiddleware( action, [], isSerializable, - getEntries + getEntries, + ignoredActionPaths ) if (foundActionNonSerializableValue) {