diff --git a/package.json b/package.json index b89f9faae..b396f5729 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "prepack": "yarn build", "bench": "vitest --run bench --mode production", "test": "node --expose-gc ./node_modules/vitest/dist/cli-wrapper.js run", + "test:watch": "node --expose-gc ./node_modules/vitest/dist/cli-wrapper.js watch", "test:cov": "vitest run --coverage", "type-check": "vitest --run typecheck", "type-check:trace": "vitest --run typecheck && tsc --noEmit -p typescript_test/tsconfig.json --generateTrace trace && npx @typescript/analyze-trace trace && rimraf trace", diff --git a/src/devModeChecks/identityFunctionCheck.ts b/src/devModeChecks/identityFunctionCheck.ts index dc4e3d450..6d8cfcdfa 100644 --- a/src/devModeChecks/identityFunctionCheck.ts +++ b/src/devModeChecks/identityFunctionCheck.ts @@ -19,11 +19,19 @@ export const runIdentityFunctionCheck = (resultFunc: AnyFunction) => { // Do nothing } if (isInputSameAsOutput) { + let stack: string | undefined = undefined + try { + throw new Error() + } catch (e) { + // eslint-disable-next-line @typescript-eslint/no-extra-semi, no-extra-semi + ;({ stack } = e as Error) + } console.warn( 'The result function returned its own inputs without modification. e.g' + '\n`createSelector([state => state.todos], todos => todos)`' + '\nThis could lead to inefficient memoization and unnecessary re-renders.' + - '\nEnsure transformation logic is in the result function, and extraction logic is in the input selectors.' + '\nEnsure transformation logic is in the result function, and extraction logic is in the input selectors.', + { stack } ) } } diff --git a/src/devModeChecks/inputStabilityCheck.ts b/src/devModeChecks/inputStabilityCheck.ts index 4f62d418e..c2283e64f 100644 --- a/src/devModeChecks/inputStabilityCheck.ts +++ b/src/devModeChecks/inputStabilityCheck.ts @@ -31,7 +31,13 @@ export const runInputStabilityCheck = ( createAnEmptyObject.apply(null, inputSelectorResults) === createAnEmptyObject.apply(null, inputSelectorResultsCopy) if (!areInputSelectorResultsEqual) { - // do we want to log more information about the selector? + let stack: string | undefined = undefined + try { + throw new Error() + } catch (e) { + // eslint-disable-next-line @typescript-eslint/no-extra-semi, no-extra-semi + ;({ stack } = e as Error) + } console.warn( 'An input selector returned a different result when passed same arguments.' + '\nThis means your output selector will likely run more frequently than intended.' + @@ -40,7 +46,8 @@ export const runInputStabilityCheck = ( { arguments: inputSelectorArgs, firstInputs: inputSelectorResults, - secondInputs: inputSelectorResultsCopy + secondInputs: inputSelectorResultsCopy, + stack } ) } diff --git a/test/identityFunctionCheck.test.ts b/test/identityFunctionCheck.test.ts index d2f60967d..eee26f61f 100644 --- a/test/identityFunctionCheck.test.ts +++ b/test/identityFunctionCheck.test.ts @@ -2,10 +2,10 @@ import { createSelector, setGlobalDevModeChecks } from 'reselect' import type { LocalTestContext, RootState } from './testUtils' import { localTest } from './testUtils' -describe('identityFunctionCheck', () => { +describe('identityFunctionCheck', () => { const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}) const identityFunction = vi.fn((state: T) => state) - const badSelector = createSelector( + let badSelector = createSelector( [(state: RootState) => state], identityFunction ) @@ -13,8 +13,10 @@ describe('identityFunctionCheck', () => { afterEach(() => { consoleSpy.mockClear() identityFunction.mockClear() - badSelector.clearCache() - badSelector.memoizedResultFunc.clearCache() + badSelector = createSelector( + [(state: RootState) => state], + identityFunction + ) }) afterAll(() => { consoleSpy.mockRestore() @@ -39,6 +41,21 @@ describe('identityFunctionCheck', () => { } ) + localTest('includes stack with warning', ({ state }) => { + expect(badSelector(state)).toBe(state) + + expect(identityFunction).toHaveBeenCalledTimes(2) + + expect(consoleSpy).toHaveBeenCalledWith( + expect.stringContaining( + 'The result function returned its own inputs without modification' + ), + { + stack: expect.any(String) + } + ) + }) + localTest('disables check if global setting is set to never', ({ state }) => { setGlobalDevModeChecks({ identityFunctionCheck: 'never' }) diff --git a/test/inputStabilityCheck.spec.ts b/test/inputStabilityCheck.spec.ts index f419f4387..bf29222f3 100644 --- a/test/inputStabilityCheck.spec.ts +++ b/test/inputStabilityCheck.spec.ts @@ -45,7 +45,8 @@ describe('inputStabilityCheck', () => { ]), secondInputs: expect.arrayContaining([ expect.objectContaining({ a: 1, b: 2 }) - ]) + ]), + stack: expect.any(String) }) ) })