From 8ad544eb29c2d3be11bb4e98a58cabd16c9d4fe7 Mon Sep 17 00:00:00 2001 From: Arya Emami Date: Wed, 4 Oct 2023 16:31:42 -0500 Subject: [PATCH] Fix runtime implementation of the new version of `createSelectorCreator` --- src/types.ts | 16 +- test/reselect.bench.ts | 112 ++-- test/reselect.spec.ts | 755 ++++++++++++++++++++++++- typescript_test/argsMemoizeTypeTest.ts | 292 ++++++++++ typescript_test/test.ts | 48 +- typescript_test/tsconfig.json | 4 +- 6 files changed, 1122 insertions(+), 105 deletions(-) create mode 100644 typescript_test/argsMemoizeTypeTest.ts diff --git a/src/types.ts b/src/types.ts index 7cfaeeba2..5b4765ebf 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,4 +1,3 @@ -import { CreateSelectorOptions } from 'reselect' import type { MergeParameters } from './versionedTypes' export type { MergeParameters } from './versionedTypes' @@ -120,11 +119,26 @@ export type UnknownMemoizer = ( ...options: any[] ) => Func +/** + * Omit any index signatures from the given object type, leaving only explicitly defined properties. + * Source: https://stackoverflow.com/questions/51465182/how-to-remove-index-signature-using-mapped-types/68261113#68261113 + * This is mainly used to remove explicit `any`s from the return type of some memoizers. e.g: `microMemoize` + */ +export type OmitIndexSignature = { + [KeyType in keyof ObjectType as {} extends Record + ? never + : KeyType]: ObjectType[KeyType] +} + /** Extracts memoize options from the parameters of a memoizer function. */ export type MemoizeOptsFromParams = | DropFirst>[0] | DropFirst> +/** Extract the extra properties that are attached to the return value of a memoizer. e.g.: clearCache */ +export type ExtractMemoizerFields = + OmitIndexSignature> + /** Extract the return type from all functions as a tuple */ export type ExtractReturnType = { [index in keyof T]: T[index] extends T[number] ? ReturnType : never diff --git a/test/reselect.bench.ts b/test/reselect.bench.ts index 2edefbb37..06f979001 100644 --- a/test/reselect.bench.ts +++ b/test/reselect.bench.ts @@ -1,56 +1,56 @@ -import { createSelector } from '@reduxjs/toolkit' -import { bench } from 'vitest' -import { autotrackMemoize } from '../src/autotrackMemoize/autotrackMemoize' -import { weakMapMemoize } from '../src/weakMapMemoize' - -describe('bench', () => { - interface State { - todos: { - id: number - completed: boolean - }[] - } - const state: State = { - todos: [ - { id: 0, completed: false }, - { id: 1, completed: false } - ] - } - bench( - 'selectorDefault', - () => { - const selectorDefault = createSelector( - (state: State) => state.todos, - todos => todos.map(t => t.id) - ) - selectorDefault(state) - }, - { iterations: 500 } - ) - - bench( - 'selectorAutotrack', - () => { - const selectorAutotrack = createSelector( - (state: State) => state.todos, - todos => todos.map(t => t.id), - { memoize: autotrackMemoize } - ) - selectorAutotrack(state) - }, - { iterations: 500 } - ) - - bench( - 'selectorWeakMap', - () => { - const selectorWeakMap = createSelector( - (state: State) => state.todos, - todos => todos.map(t => t.id), - { memoize: weakMapMemoize } - ) - selectorWeakMap(state) - }, - { iterations: 500 } - ) -}) +import { createSelector } from '@reduxjs/toolkit' +import { bench } from 'vitest' +import { autotrackMemoize } from '../src/autotrackMemoize/autotrackMemoize' +import { weakMapMemoize } from '../src/weakMapMemoize' + +describe('bench', () => { + interface State { + todos: { + id: number + completed: boolean + }[] + } + const state: State = { + todos: [ + { id: 0, completed: false }, + { id: 1, completed: false } + ] + } + bench( + 'selectorDefault', + () => { + const selectorDefault = createSelector( + (state: State) => state.todos, + todos => todos.map(t => t.id) + ) + selectorDefault(state) + }, + { iterations: 500 } + ) + + bench( + 'selectorAutotrack', + () => { + const selectorAutotrack = createSelector( + (state: State) => state.todos, + todos => todos.map(t => t.id), + { memoize: autotrackMemoize } + ) + selectorAutotrack(state) + }, + { iterations: 500 } + ) + + bench( + 'selectorWeakMap', + () => { + const selectorWeakMap = createSelector( + (state: State) => state.todos, + todos => todos.map(t => t.id), + { memoize: weakMapMemoize } + ) + selectorWeakMap(state) + }, + { iterations: 500 } + ) +}) diff --git a/test/reselect.spec.ts b/test/reselect.spec.ts index d8d5e303a..c47f489ad 100644 --- a/test/reselect.spec.ts +++ b/test/reselect.spec.ts @@ -403,9 +403,336 @@ describe('Customizing selectors', () => { ] } const selectorDefault = createSelector( + (state: State) => state.todos, + todos => todos.map(t => t.id), + { + argsMemoize: defaultMemoize, + argsMemoizeOptions: { + equalityCheck: (a, b) => a === b, + resultEqualityCheck: (a, b) => a === b + } + } + ) + // const selectorDefaultParametric = createSelector( + // (state: State, id: number) => id, + // (state: State) => state.todos, + // (id, todos) => todos.filter(t => t.id === id), + // { + // argsMemoize: defaultMemoize, + // inputStabilityCheck: 'never', + // memoize: defaultMemoize, + // memoizeOptions: { + // equalityCheck: (a, b) => { + // console.log( + // 'memoize equalityCheck run', + // { prev: a }, + // '\n', + // { curr: b }, + // a === b + // ) + // return a === b + // }, + // resultEqualityCheck: (a, b) => { + // console.log( + // 'memoize resultEqualityCheck run', + // { prev: a }, + // '\n', + // { curr: b }, + // a === b + // ) + // return a === b + // } + // }, + // argsMemoizeOptions: { + // equalityCheck: (a, b) => { + // console.log( + // 'argsMemoize equalityCheck run', + // { prev: a }, + // '\n', + // { curr: b }, + // a === b + // ) + // return a === b + // }, + // resultEqualityCheck: (a, b) => { + // console.log( + // 'argsMemoize resultEqualityCheck run', + // { prev: a }, + // '\n', + // { curr: b }, + // a === b + // ) + // return a === b + // } + // } + // } + // ) + // selectorDefaultParametric(state, 0) + // selectorDefaultParametric(state, 1) + // selectorDefaultParametric(state, 1) + // selectorDefaultParametric( + // { + // todos: [ + // { id: 0, completed: false }, + // { id: 1, completed: false } + // ] + // }, + // 1 + // ) + // selectorDefaultParametric( + // { + // todos: [ + // { id: 0, completed: false }, + // { id: 1, completed: false } + // ] + // }, + // 0 + // ) + // selectorDefaultParametric( + // { + // todos: [ + // { id: 0, completed: false }, + // { id: 1, completed: false } + // ] + // }, + // 0 + // ) + const createSelectorDefaultObj = createSelectorCreator({ + memoize: defaultMemoize + }) + const createSelectorDefaultFunc = createSelectorCreator(defaultMemoize) + const createSelectorMicroObj = createSelectorCreator({ + memoize: microMemoize + }) + const createSelectorMicroFunc = createSelectorCreator(microMemoize) + const createSelectorMicroObjWithArgsMemoize = createSelectorCreator({ + memoize: microMemoize, + argsMemoize: microMemoize, + memoizeOptions: { isEqual: (a, b) => a === b }, + argsMemoizeOptions: { isEqual: (a, b) => a === b } + }) + const createSelectorLodashFunc = createSelectorCreator(lodashMemoize) + const selectorLodashFunc = createSelectorLodashFunc( + (state: State) => state.todos, + todos => todos.map(t => t.id) + ) + const selectorDefaultObj = createSelectorDefaultObj( + (state: State) => state.todos, + todos => todos.map(t => t.id) + ) + + // @ts-expect-error + expect(selectorDefaultObj.fn).toBeUndefined() + // @ts-expect-error + expect(selectorDefaultObj.cache).toBeUndefined() + // @ts-expect-error + expect(selectorDefaultObj.isMemoized).toBeUndefined() + // @ts-expect-error + expect(selectorDefaultObj.options).toBeUndefined() + expect(selectorDefaultObj.lastResult).toBeDefined() + expect(selectorDefaultObj.recomputations).toBeDefined() + expect(selectorDefaultObj.dependencies).toBeDefined() + expect(selectorDefaultObj.resetRecomputations).toBeDefined() + expect(selectorDefaultObj.resultFunc).toBeDefined() + expect(selectorDefaultObj.clearCache).toBeDefined() + expect(selectorDefaultObj.memoizedResultFunc).toBeDefined() + // @ts-expect-error + expect(selectorDefaultObj.memoizedResultFunc.cache).toBeUndefined() + expect(selectorDefaultObj.memoizedResultFunc.clearCache).toBeDefined() + const selectorDefaultFunc = createSelectorDefaultFunc( + (state: State) => state.todos, + todos => todos.map(t => t.id) + ) + // @ts-expect-error + expect(selectorDefaultFunc.fn).toBeUndefined() + // @ts-expect-error + expect(selectorDefaultFunc.cache).toBeUndefined() + // @ts-expect-error + expect(selectorDefaultFunc.isMemoized).toBeUndefined() + // @ts-expect-error + expect(selectorDefaultFunc.options).toBeUndefined() + expect(selectorDefaultFunc.lastResult).toBeDefined() + expect(selectorDefaultFunc.recomputations).toBeDefined() + expect(selectorDefaultFunc.dependencies).toBeDefined() + expect(selectorDefaultFunc.resetRecomputations).toBeDefined() + expect(selectorDefaultFunc.resultFunc).toBeDefined() + expect(selectorDefaultFunc.clearCache).toBeDefined() + expect(selectorDefaultFunc.memoizedResultFunc).toBeDefined() + // @ts-expect-error + expect(selectorDefaultFunc.memoizedResultFunc.cache).toBeUndefined() + expect(selectorDefaultFunc.memoizedResultFunc.clearCache).toBeDefined() + const selectorMicroFunc = createSelectorMicroFunc( + (state: State) => state.todos, + todos => todos.map(t => t.id) + ) + const selectorMicroObj = createSelectorMicroObj( (state: State) => state.todos, todos => todos.map(t => t.id) ) + selectorMicroObj(state) + // @ts-expect-error + expect(selectorMicroObj.fn).toBeUndefined() + // @ts-expect-error + expect(selectorMicroObj.cache).toBeUndefined() + // @ts-expect-error + expect(selectorMicroObj.isMemoized).toBeUndefined() + // @ts-expect-error + expect(selectorMicroObj.options).toBeUndefined() + expect(selectorMicroObj.lastResult).toBeDefined() + expect(selectorMicroObj.recomputations).toBeDefined() + expect(selectorMicroObj.dependencies).toBeDefined() + expect(selectorMicroObj.resetRecomputations).toBeDefined() + expect(selectorMicroObj.resultFunc).toBeDefined() + expect(selectorMicroObj.clearCache).toBeDefined() + expect(selectorMicroObj.memoizedResultFunc).toBeDefined() + expect(selectorMicroObj.memoizedResultFunc.cache).toBeDefined() + // @ts-expect-error + expect(selectorMicroObj.memoizedResultFunc.clearCache).toBeUndefined() + // @ts-expect-error + expect(selectorMicroFunc.fn).toBeUndefined() + // @ts-expect-error + expect(selectorMicroFunc.cache).toBeUndefined() + // @ts-expect-error + expect(selectorMicroFunc.isMemoized).toBeUndefined() + // @ts-expect-error + expect(selectorMicroFunc.options).toBeUndefined() + expect(selectorMicroFunc.lastResult).toBeDefined() + expect(selectorMicroFunc.recomputations).toBeDefined() + expect(selectorMicroFunc.dependencies).toBeDefined() + expect(selectorMicroFunc.resetRecomputations).toBeDefined() + expect(selectorMicroFunc.resultFunc).toBeDefined() + expect(selectorMicroFunc.clearCache).toBeDefined() + expect(selectorMicroFunc.memoizedResultFunc).toBeDefined() + expect(selectorMicroFunc.memoizedResultFunc.cache).toBeDefined() + // @ts-expect-error + expect(selectorMicroFunc.memoizedResultFunc.clearCache).toBeUndefined() + // `memoizeOptions` should match params of `microMemoize` + const selectorMicroObj1 = createSelectorMicroObj( + (state: State) => state.todos, + todos => todos.map(t => t.id), + { memoizeOptions: { isEqual: (a, b) => a === b } } + ) + // @ts-expect-error + expect(selectorMicroObj1.fn).toBeUndefined() + // @ts-expect-error + expect(selectorMicroObj1.cache).toBeUndefined() + // @ts-expect-error + expect(selectorMicroObj1.isMemoized).toBeUndefined() + // @ts-expect-error + expect(selectorMicroObj1.options).toBeUndefined() + expect(selectorMicroObj1.lastResult).toBeDefined() + expect(selectorMicroObj1.recomputations).toBeDefined() + expect(selectorMicroObj1.dependencies).toBeDefined() + expect(selectorMicroObj1.resetRecomputations).toBeDefined() + expect(selectorMicroObj1.resultFunc).toBeDefined() + // Because `argsMemoize` is `defaultMemoize`. + expect(selectorMicroObj1.clearCache).toBeDefined() + expect(selectorMicroObj1.memoizedResultFunc).toBeDefined() + // This is undefined because `memoize` is set to `microMemoize`. + // @ts-expect-error + expect(selectorMicroObj1.memoizedResultFunc.clearCache).toBeUndefined() + expect(selectorMicroObj1.memoizedResultFunc.cache).toBeDefined() + expect(selectorMicroObj1.memoizedResultFunc.fn).toBeDefined() + expect(selectorMicroObj1.memoizedResultFunc.isMemoized).toBeDefined() + expect(selectorMicroObj1.memoizedResultFunc.options).toBeDefined() + // memoizeOptions should match params of defaultMemoize + const selectorMicroObj2 = createSelectorMicroObj( + (state: State) => state.todos, + todos => todos.map(t => t.id), + { memoize: defaultMemoize } + ) + const selectorMicroObj3 = createSelectorMicroObj( + (state: State) => state.todos, + todos => todos.map(t => t.id), + { + memoize: defaultMemoize, + memoizeOptions: { equalityCheck: (a: any, b: any) => a === b }, + argsMemoize: microMemoize, + argsMemoizeOptions: { isEqual: (a, b) => a === b } + } + ) + expect(selectorMicroObj3(state)).toBeDefined() + // @ts-expect-error + expect(selectorMicroObj2.fn).toBeUndefined() + // @ts-expect-error + expect(selectorMicroObj2.cache).toBeUndefined() + // @ts-expect-error + expect(selectorMicroObj2.isMemoized).toBeUndefined() + // @ts-expect-error + expect(selectorMicroObj2.options).toBeUndefined() + expect(selectorMicroObj2.lastResult).toBeDefined() + expect(selectorMicroObj2.recomputations).toBeDefined() + expect(selectorMicroObj2.dependencies).toBeDefined() + expect(selectorMicroObj2.resetRecomputations).toBeDefined() + expect(selectorMicroObj2.resultFunc).toBeDefined() + // Because argsMemoize is defaultMemoize + expect(selectorMicroObj2.clearCache).toBeDefined() + expect(selectorMicroObj2.memoizedResultFunc).toBeDefined() + // @ts-expect-error + expect(selectorMicroObj2.memoizedResultFunc.cache).toBeUndefined() + // Because memoize is + expect(selectorMicroObj2.memoizedResultFunc.clearCache).toBeDefined() + selectorMicroObj2(state) + expect(selectorMicroObj3.fn).toBeDefined() + expect(selectorMicroObj3.cache).toBeDefined() + expect(selectorMicroObj3.isMemoized).toBeDefined() + expect(selectorMicroObj3.options).toBeDefined() + expect(selectorMicroObj3.lastResult).toBeDefined() + expect(selectorMicroObj3.recomputations).toBeDefined() + expect(selectorMicroObj3.dependencies).toBeDefined() + expect(selectorMicroObj3.resetRecomputations).toBeDefined() + expect(selectorMicroObj3.resultFunc).toBeDefined() + // @ts-expect-error + expect(selectorMicroObj3.clearCache).toBeUndefined() + expect(selectorMicroObj3.memoizedResultFunc).toBeDefined() + // @ts-expect-error + expect(selectorMicroObj3.memoizedResultFunc.cache).toBeUndefined() + expect(selectorMicroObj3.memoizedResultFunc.clearCache).toBeDefined() + const selectorLodashObjWithArgsMemoize = + createSelectorMicroObjWithArgsMemoize( + (state: State) => state.todos, + todos => todos.map(t => t.id) + ) + + expect(selectorLodashObjWithArgsMemoize.fn).toBeDefined() + expect(selectorLodashObjWithArgsMemoize.cache).toBeDefined() + expect(selectorLodashObjWithArgsMemoize.isMemoized).toBeDefined() + expect(selectorLodashObjWithArgsMemoize.options).toBeDefined() + expect(selectorLodashObjWithArgsMemoize.lastResult).toBeDefined() + expect(selectorLodashObjWithArgsMemoize.recomputations).toBeDefined() + expect(selectorLodashObjWithArgsMemoize.dependencies).toBeDefined() + expect(selectorLodashObjWithArgsMemoize.resetRecomputations).toBeDefined() + expect(selectorLodashObjWithArgsMemoize.resultFunc).toBeDefined() + expect(selectorLodashObjWithArgsMemoize.memoizedResultFunc).toBeDefined() + expect( + selectorLodashObjWithArgsMemoize.memoizedResultFunc.cache + ).toBeDefined() + expect(selectorLodashObjWithArgsMemoize.memoizedResultFunc.fn).toBeDefined() + expect( + selectorLodashObjWithArgsMemoize.memoizedResultFunc.isMemoized + ).toBeDefined() + expect( + selectorLodashObjWithArgsMemoize.memoizedResultFunc.options + ).toBeDefined() + + expect(selectorMicroObj.lastResult).toBeDefined() + expect(selectorMicroObj.recomputations).toBeDefined() + expect(selectorMicroObj.dependencies).toBeDefined() + expect(selectorMicroObj.resetRecomputations).toBeDefined() + expect(selectorMicroObj.resultFunc).toBeDefined() + expect(selectorMicroObj.memoizedResultFunc.cache).toBeDefined() + // @ts-expect-error + expect(selectorMicroObj.cache).toBeUndefined() + expect(selectorMicroObj.clearCache).toBeDefined() + + // @ts-expect-error + expect(selectorMicroObj.cache).toBeUndefined() + // @ts-expect-error + expect(selectorLodashObjWithArgsMemoize.clearCache).toBeUndefined() + expect(selectorLodashObjWithArgsMemoize.cache).toBeDefined() + expect(selectorLodashFunc.clearCache).toBeDefined() + // @ts-expect-error + expect(selectorLodashFunc.cache).toBeUndefined() const selectorAutotrack = createSelector( (state: State) => state.todos, todos => todos.map(t => t.id), @@ -465,7 +792,7 @@ describe('Customizing selectors', () => { expect(defaultSelectorLastResult1).toStrictEqual(defaultSelectorLastResult2) }) - test('args memoize', () => { + test('passing argsMemoize directly to createSelector', () => { interface State { todos: { id: number @@ -478,43 +805,30 @@ describe('Customizing selectors', () => { { id: 1, completed: false } ] } - const selectorDefault = createSelector( - (state: State) => state.todos, - todos => todos.map(t => t.id), - { argsMemoizeOptions: { maxSize: 2 }, argsMemoize: defaultMemoize } - ) - const selectorAutotrack = createSelector( - (state: State) => state.todos, - todos => todos.map(t => t.id), - {} - ) - const createSelectorFunc = createSelectorCreator(microMemoize) - const createSelectorObj = createSelectorCreator({ - memoize: defaultMemoize, - argsMemoize: defaultMemoize, - memoizeOptions: { maxSize: 2 }, - argsMemoizeOptions: { maxSize: 2 } + const otherCreateSelector = createSelectorCreator({ + memoize: microMemoize, + argsMemoize: microMemoize }) - const selectorFunc = createSelectorFunc( + // Overriding back to default + const selectorDefault = otherCreateSelector( (state: State) => state.todos, todos => todos.map(t => t.id), { memoize: defaultMemoize, - // memoizeOptions: (a, b) => a === b, - memoizeOptions: { maxSize: 2 }, argsMemoize: defaultMemoize, - argsMemoizeOptions: { maxSize: 2 } + argsMemoizeOptions: { + equalityCheck: (a, b) => a === b, + resultEqualityCheck: (a, b) => a === b + }, + memoizeOptions: { + equalityCheck: (a, b) => a === b, + resultEqualityCheck: (a, b) => a === b + } } ) - const selectorObj = createSelectorObj( + const selectorAutotrack = createSelector( (state: State) => state.todos, - todos => todos.map(t => t.id), - { - memoize: defaultMemoize, - memoizeOptions: { maxSize: 2 }, - argsMemoize: defaultMemoize, - argsMemoizeOptions: { maxSize: 2 } - } + todos => todos.map(t => t.id) ) expect(selectorDefault({ ...state })).toStrictEqual([0, 1]) expect(selectorAutotrack({ ...state })).toStrictEqual([0, 1]) @@ -526,6 +840,8 @@ describe('Customizing selectors', () => { { id: 1, completed: false } ] }) + selectorDefault(state) + selectorDefault(state) const defaultSelectorLastResult1 = selectorDefault.lastResult() selectorDefault({ todos: [ @@ -548,10 +864,389 @@ describe('Customizing selectors', () => { ] }) const autotrackSelectorLastResult2 = selectorAutotrack.lastResult() - expect(selectorDefault.recomputations()).toBe(3) + expect(selectorDefault.recomputations()).toBe(4) expect(selectorAutotrack.recomputations()).toBe(3) expect(autotrackSelectorLastResult1).not.toBe(autotrackSelectorLastResult2) expect(defaultSelectorLastResult1).not.toBe(defaultSelectorLastResult2) expect(defaultSelectorLastResult1).toStrictEqual(defaultSelectorLastResult2) }) + + test('passing argsMemoize to createSelectorCreator', () => { + interface State { + todos: { + id: number + completed: boolean + }[] + } + const state: State = { + todos: [ + { id: 0, completed: false }, + { id: 1, completed: false } + ] + } + const createSelectorMicroMemoize = createSelectorCreator({ + memoize: microMemoize, + memoizeOptions: { isEqual: (a, b) => a === b }, + argsMemoize: microMemoize, + argsMemoizeOptions: { isEqual: (a, b) => a === b } + }) + const selectorMicroMemoize = createSelectorMicroMemoize( + (state: State) => state.todos, + todos => todos.map(t => t.id) + ) + expect(selectorMicroMemoize(state)).toBeDefined() + // Checking existence of fields related to `argsMemoize` + expect(selectorMicroMemoize.cache).toBeDefined() + expect(selectorMicroMemoize.fn).toBeDefined() + expect(selectorMicroMemoize.isMemoized).toBeDefined() + expect(selectorMicroMemoize.options).toBeDefined() + // @ts-expect-error + expect(selectorMicroMemoize.clearCache).toBeUndefined() + // Checking existence of fields related to `memoize` + expect(selectorMicroMemoize.memoizedResultFunc.cache).toBeDefined() + expect(selectorMicroMemoize.memoizedResultFunc.fn).toBeDefined() + expect(selectorMicroMemoize.memoizedResultFunc.isMemoized).toBeDefined() + expect(selectorMicroMemoize.memoizedResultFunc.options).toBeDefined() + // @ts-expect-error + expect(selectorMicroMemoize.memoizedResultFunc.clearCache).toBeUndefined() + expect(selectorMicroMemoize.memoizedResultFunc).toBeDefined() + // Checking existence of fields related to the actual memoized selector + expect(selectorMicroMemoize.dependencies).toBeDefined() + expect(selectorMicroMemoize.lastResult()).toBeDefined() + expect( + selectorMicroMemoize.memoizedResultFunc([{ id: 0, completed: true }]) + ).toBeDefined() + expect(selectorMicroMemoize.recomputations()).toBeDefined() + expect(selectorMicroMemoize.resetRecomputations()).toBeDefined() + expect(selectorMicroMemoize.resultFunc).toBeDefined() + expect( + selectorMicroMemoize.resultFunc([{ id: 0, completed: true }]) + ).toBeDefined() + + // Checking to see if types dynamically change if memoize or argsMemoize or overridden inside `createSelector` + const selectorMicroMemoizeOverridden = createSelectorMicroMemoize( + (state: State) => state.todos, + todos => todos.map(t => t.id), + { + memoize: defaultMemoize, + argsMemoize: defaultMemoize, + memoizeOptions: { equalityCheck: (a, b) => a === b, maxSize: 2 }, + argsMemoizeOptions: { equalityCheck: (a, b) => a === b, maxSize: 3 } + } + ) + expect(selectorMicroMemoizeOverridden(state)).toBeDefined() + // Checking existence of fields related to `argsMemoize` + expect(selectorMicroMemoizeOverridden.clearCache).toBeDefined() + // @ts-expect-error + expect(selectorMicroMemoizeOverridden.cache).toBeUndefined() + // @ts-expect-error + expect(selectorMicroMemoizeOverridden.fn).toBeUndefined() + // @ts-expect-error + expect(selectorMicroMemoizeOverridden.isMemoized).toBeUndefined() + // @ts-expect-error + expect(selectorMicroMemoizeOverridden.options).toBeUndefined() + // Checking existence of fields related to `memoize` + expect( + selectorMicroMemoizeOverridden.memoizedResultFunc.clearCache + ).toBeDefined() + expect( + // @ts-expect-error + selectorMicroMemoizeOverridden.memoizedResultFunc.cache + ).toBeUndefined() + // @ts-expect-error + expect(selectorMicroMemoizeOverridden.memoizedResultFunc.fn).toBeUndefined() + expect( + // @ts-expect-error + selectorMicroMemoizeOverridden.memoizedResultFunc.isMemoized + ).toBeUndefined() + expect( + // @ts-expect-error + selectorMicroMemoizeOverridden.memoizedResultFunc.options + ).toBeUndefined() + // Checking existence of fields related to the actual memoized selector + expect(selectorMicroMemoizeOverridden.dependencies).toBeDefined() + expect(selectorMicroMemoizeOverridden.lastResult()).toBeDefined() + expect( + selectorMicroMemoizeOverridden.memoizedResultFunc([ + { id: 0, completed: true } + ]) + ).toBeDefined() + expect(selectorMicroMemoizeOverridden.memoizedResultFunc).toBeDefined() + expect(selectorMicroMemoizeOverridden.recomputations()).toBeDefined() + expect(selectorMicroMemoizeOverridden.resetRecomputations()).toBeDefined() + expect(selectorMicroMemoizeOverridden.resultFunc).toBeDefined() + expect( + selectorMicroMemoizeOverridden.resultFunc([{ id: 0, completed: true }]) + ).toBeDefined() + + const selectorMicroMemoizeOverrideArgsMemoizeOnly = + createSelectorMicroMemoize( + (state: State) => state.todos, + todos => todos.map(t => t.id), + { + argsMemoize: defaultMemoize, + argsMemoizeOptions: { resultEqualityCheck: (a, b) => a === b } + } + ) + expect(selectorMicroMemoizeOverrideArgsMemoizeOnly(state)).toBeDefined() + // Checking existence of fields related to `argsMemoize` + expect(selectorMicroMemoizeOverrideArgsMemoizeOnly.clearCache).toBeDefined() + // @ts-expect-error + expect(selectorMicroMemoizeOverrideArgsMemoizeOnly.cache).toBeUndefined() + // @ts-expect-error + expect(selectorMicroMemoizeOverrideArgsMemoizeOnly.fn).toBeUndefined() + expect( + // @ts-expect-error + selectorMicroMemoizeOverrideArgsMemoizeOnly.isMemoized + ).toBeUndefined() + // @ts-expect-error + expect(selectorMicroMemoizeOverrideArgsMemoizeOnly.options).toBeUndefined() + expect( + // Checking existence of fields related to `memoize` + // @ts-expect-error Note that since we did not override `memoize` in the options object, + // memoizedResultFunc.clearCache becomes an invalid field access, and we get `cache`, `fn`, `isMemoized` and `options` instead. + selectorMicroMemoizeOverrideArgsMemoizeOnly.memoizedResultFunc.clearCache + ).toBeUndefined() + expect( + selectorMicroMemoizeOverrideArgsMemoizeOnly.memoizedResultFunc.cache + ).toBeDefined() + expect( + selectorMicroMemoizeOverrideArgsMemoizeOnly.memoizedResultFunc.fn + ).toBeDefined() + expect( + selectorMicroMemoizeOverrideArgsMemoizeOnly.memoizedResultFunc.isMemoized + ).toBeDefined() + expect( + selectorMicroMemoizeOverrideArgsMemoizeOnly.memoizedResultFunc.options + ).toBeDefined() + // Checking existence of fields related to the actual memoized selector + expect( + selectorMicroMemoizeOverrideArgsMemoizeOnly.dependencies + ).toBeDefined() + expect( + selectorMicroMemoizeOverrideArgsMemoizeOnly.lastResult() + ).toBeDefined() + expect( + selectorMicroMemoizeOverrideArgsMemoizeOnly.memoizedResultFunc([ + { id: 0, completed: true } + ]) + ).toBeDefined() + expect( + selectorMicroMemoizeOverrideArgsMemoizeOnly.memoizedResultFunc + ).toBeDefined() + expect( + selectorMicroMemoizeOverrideArgsMemoizeOnly.recomputations() + ).toBeDefined() + expect( + selectorMicroMemoizeOverrideArgsMemoizeOnly.resetRecomputations() + ).toBeDefined() + expect(selectorMicroMemoizeOverrideArgsMemoizeOnly.resultFunc).toBeDefined() + expect( + selectorMicroMemoizeOverrideArgsMemoizeOnly.resultFunc([ + { id: 0, completed: true } + ]) + ).toBeDefined() + + const selectorMicroMemoizeOverrideMemoizeOnly = createSelectorMicroMemoize( + (state: State) => state.todos, + todos => todos.map(t => t.id), + { + memoize: defaultMemoize, + memoizeOptions: { resultEqualityCheck: (a, b) => a === b } + } + ) + expect(selectorMicroMemoizeOverrideMemoizeOnly(state)).toBeDefined() + // Checking existence of fields related to `argsMemoize` + // @ts-expect-error Note that since we did not override `argsMemoize` in the options object, + // selector.clearCache becomes an invalid field access, and we get `cache`, `fn`, `isMemoized` and `options` instead. + expect(selectorMicroMemoizeOverrideMemoizeOnly.clearCache).toBeUndefined() + expect(selectorMicroMemoizeOverrideMemoizeOnly.cache).toBeDefined() + expect(selectorMicroMemoizeOverrideMemoizeOnly.fn).toBeDefined() + expect(selectorMicroMemoizeOverrideMemoizeOnly.isMemoized).toBeDefined() + expect(selectorMicroMemoizeOverrideMemoizeOnly.options).toBeDefined() + // Checking existence of fields related to `memoize` + expect( + // @ts-expect-error + selectorMicroMemoizeOverrideMemoizeOnly.memoizedResultFunc.cache + ).toBeUndefined() + expect( + // @ts-expect-error + selectorMicroMemoizeOverrideMemoizeOnly.memoizedResultFunc.fn + ).toBeUndefined() + expect( + // @ts-expect-error + selectorMicroMemoizeOverrideMemoizeOnly.memoizedResultFunc.isMemoized + ).toBeUndefined() + expect( + // @ts-expect-error + selectorMicroMemoizeOverrideMemoizeOnly.memoizedResultFunc.options + ).toBeUndefined() + expect( + selectorMicroMemoizeOverrideMemoizeOnly.memoizedResultFunc.clearCache + ).toBeDefined() + // Checking existence of fields related to the actual memoized selector + expect(selectorMicroMemoizeOverrideMemoizeOnly.dependencies).toBeDefined() + expect(selectorMicroMemoizeOverrideMemoizeOnly.lastResult()).toBeDefined() + expect( + selectorMicroMemoizeOverrideMemoizeOnly.memoizedResultFunc([ + { id: 0, completed: true } + ]) + ).toBeDefined() + expect( + selectorMicroMemoizeOverrideMemoizeOnly.memoizedResultFunc + ).toBeDefined() + expect( + selectorMicroMemoizeOverrideMemoizeOnly.recomputations() + ).toBeDefined() + expect( + selectorMicroMemoizeOverrideMemoizeOnly.resetRecomputations() + ).toBeDefined() + expect(selectorMicroMemoizeOverrideMemoizeOnly.resultFunc).toBeDefined() + expect( + selectorMicroMemoizeOverrideMemoizeOnly.resultFunc([ + { id: 0, completed: true } + ]) + ).toBeDefined() + // If we don't pass in `argsMemoize`, the type for `argsMemoizeOptions` falls back to the options parameter of `defaultMemoize`. + const createSelectorMicroMemoizeArgsMemoizeOptionsFallbackToDefault = + createSelectorCreator({ + memoize: microMemoize, + memoizeOptions: { isPromise: false }, + argsMemoizeOptions: { resultEqualityCheck: (a, b) => a === b } + }) + const selectorMicroMemoizeArgsMemoizeOptionsFallbackToDefault = + createSelectorMicroMemoizeArgsMemoizeOptionsFallbackToDefault( + (state: State) => state.todos, + todos => todos.map(t => t.id) + ) + expect( + selectorMicroMemoizeArgsMemoizeOptionsFallbackToDefault(state) + ).toBeDefined() + expect( + selectorMicroMemoizeArgsMemoizeOptionsFallbackToDefault.clearCache + ).toBeDefined() + expect( + // @ts-expect-error + selectorMicroMemoizeArgsMemoizeOptionsFallbackToDefault.cache + ).toBeUndefined() + expect( + // @ts-expect-error + selectorMicroMemoizeArgsMemoizeOptionsFallbackToDefault.fn + ).toBeUndefined() + expect( + // @ts-expect-error + selectorMicroMemoizeArgsMemoizeOptionsFallbackToDefault.isMemoized + ).toBeUndefined() + expect( + // @ts-expect-error + selectorMicroMemoizeArgsMemoizeOptionsFallbackToDefault.options + ).toBeUndefined() + // Checking existence of fields related to `memoize` + expect( + selectorMicroMemoizeArgsMemoizeOptionsFallbackToDefault.memoizedResultFunc + .cache + ).toBeDefined() + expect( + selectorMicroMemoizeArgsMemoizeOptionsFallbackToDefault.memoizedResultFunc + .fn + ).toBeDefined() + expect( + selectorMicroMemoizeArgsMemoizeOptionsFallbackToDefault.memoizedResultFunc + .isMemoized + ).toBeDefined() + expect( + selectorMicroMemoizeArgsMemoizeOptionsFallbackToDefault.memoizedResultFunc + .options + ).toBeDefined() + expect( + selectorMicroMemoizeArgsMemoizeOptionsFallbackToDefault.dependencies + ).toBeDefined() + expect( + selectorMicroMemoizeArgsMemoizeOptionsFallbackToDefault.lastResult() + ).toBeDefined() + expect( + selectorMicroMemoizeArgsMemoizeOptionsFallbackToDefault.memoizedResultFunc( + [{ id: 0, completed: true }] + ) + ).toBeDefined() + expect( + selectorMicroMemoizeArgsMemoizeOptionsFallbackToDefault.memoizedResultFunc + ).toBeDefined() + expect( + selectorMicroMemoizeArgsMemoizeOptionsFallbackToDefault.recomputations() + ).toBeDefined() + expect( + selectorMicroMemoizeArgsMemoizeOptionsFallbackToDefault.resetRecomputations() + ).toBeDefined() + expect( + selectorMicroMemoizeArgsMemoizeOptionsFallbackToDefault.resultFunc + ).toBeDefined() + expect( + selectorMicroMemoizeArgsMemoizeOptionsFallbackToDefault.resultFunc([ + { id: 0, completed: true } + ]) + ).toBeDefined() + }) + + test('passing argsMemoize directly to createSelector', () => { + interface State { + todos: { + id: number + completed: boolean + }[] + } + const state: State = { + todos: [ + { id: 0, completed: false }, + { id: 1, completed: false } + ] + } + // original options untouched. + const selectorOriginal = createSelector( + (state: State) => state.todos, + todos => todos.map(t => t.id) + ) + selectorOriginal(state) + // Call with new reference to force the selector to re-run + selectorOriginal({ + todos: [ + { id: 0, completed: false }, + { id: 1, completed: false } + ] + }) + selectorOriginal({ + todos: [ + { id: 0, completed: false }, + { id: 1, completed: false } + ] + }) + // Override `argsMemoize` with `autotrackMemoize` + const selectorOverrideArgsMemoize = createSelector( + (state: State) => state.todos, + todos => todos.map(t => t.id), + { + memoize: defaultMemoize, + memoizeOptions: { equalityCheck: (a, b) => a === b }, + // WARNING!! This is just for testing purposes, do not use `autotrackMemoize` to memoize the arguments, + // it can return false positives, since it's not tracking a nested field. + argsMemoize: autotrackMemoize + } + ) + selectorOverrideArgsMemoize(state) + // Call with new reference to force the selector to re-run + selectorOverrideArgsMemoize({ + todos: [ + { id: 0, completed: false }, + { id: 1, completed: false } + ] + }) + selectorOverrideArgsMemoize({ + todos: [ + { id: 0, completed: false }, + { id: 1, completed: false } + ] + }) + expect(selectorOverrideArgsMemoize.recomputations()).toBe(1) + expect(selectorOriginal.recomputations()).toBe(3) + }) }) diff --git a/typescript_test/argsMemoizeTypeTest.ts b/typescript_test/argsMemoizeTypeTest.ts new file mode 100644 index 000000000..42652357c --- /dev/null +++ b/typescript_test/argsMemoizeTypeTest.ts @@ -0,0 +1,292 @@ +import microMemoize from 'micro-memoize' +import { createSelectorCreator, defaultMemoize } from '../src/index' + +function testArgsMemoizeInCreateSelectorCreatorDoneCorrectly() { + interface State { + todos: { + id: number + completed: boolean + }[] + } + const state: State = { + todos: [ + { id: 0, completed: false }, + { id: 1, completed: false } + ] + } + + const createSelectorMicroMemoize = createSelectorCreator({ + memoize: microMemoize, + memoizeOptions: { isEqual: (a, b) => a === b }, + argsMemoize: microMemoize, + argsMemoizeOptions: { isEqual: (a, b) => a === b } + }) + const selectorMicroMemoize = createSelectorMicroMemoize( + (state: State) => state.todos, + todos => todos.map(t => t.id) + ) + selectorMicroMemoize(state) + // @ts-expect-error + selectorMicroMemoize() + // Checking existence of fields related to `argsMemoize` + selectorMicroMemoize.cache + selectorMicroMemoize.fn() + selectorMicroMemoize.isMemoized + selectorMicroMemoize.options + // @ts-expect-error + selectorMicroMemoize.clearCache() + // Checking existence of fields related to `memoize` + selectorMicroMemoize.memoizedResultFunc.cache + selectorMicroMemoize.memoizedResultFunc.fn() + selectorMicroMemoize.memoizedResultFunc.isMemoized + selectorMicroMemoize.memoizedResultFunc.options + // @ts-expect-error + selectorMicroMemoize.memoizedResultFunc.clearCache() + // Checking existence of fields related to the actual memoized selector + selectorMicroMemoize.dependencies + selectorMicroMemoize.lastResult() + selectorMicroMemoize.memoizedResultFunc([{ id: 0, completed: true }]) + // @ts-expect-error + selectorMicroMemoize.memoizedResultFunc() + selectorMicroMemoize.recomputations() + selectorMicroMemoize.resetRecomputations() + // @ts-expect-error + selectorMicroMemoize.resultFunc() + selectorMicroMemoize.resultFunc([{ id: 0, completed: true }]) + + // Checking to see if types dynamically change if memoize or argsMemoize or overridden inside `createSelector` + const selectorMicroMemoizeOverridden = createSelectorMicroMemoize( + (state: State) => state.todos, + todos => todos.map(t => t.id), + { + memoize: defaultMemoize, + argsMemoize: defaultMemoize, + memoizeOptions: { equalityCheck: (a, b) => a === b, maxSize: 2 }, + argsMemoizeOptions: { equalityCheck: (a, b) => a === b, maxSize: 3 } + } + ) + selectorMicroMemoizeOverridden(state) + // @ts-expect-error + selectorMicroMemoizeOverridden() + // Checking existence of fields related to `argsMemoize` + selectorMicroMemoizeOverridden.clearCache() + // @ts-expect-error + selectorMicroMemoizeOverridden.cache + // @ts-expect-error + selectorMicroMemoizeOverridden.fn() + // @ts-expect-error + selectorMicroMemoizeOverridden.isMemoized + // @ts-expect-error + selectorMicroMemoizeOverridden.options + // Checking existence of fields related to `memoize` + selectorMicroMemoizeOverridden.memoizedResultFunc.clearCache() + // @ts-expect-error + selectorMicroMemoizeOverridden.memoizedResultFunc.cache + // @ts-expect-error + selectorMicroMemoizeOverridden.memoizedResultFunc.fn() + // @ts-expect-error + selectorMicroMemoizeOverridden.memoizedResultFunc.isMemoized + // @ts-expect-error + selectorMicroMemoizeOverridden.memoizedResultFunc.options + // Checking existence of fields related to the actual memoized selector + selectorMicroMemoizeOverridden.dependencies + selectorMicroMemoizeOverridden.lastResult() + selectorMicroMemoizeOverridden.memoizedResultFunc([ + { id: 0, completed: true } + ]) + // @ts-expect-error + selectorMicroMemoizeOverridden.memoizedResultFunc() + selectorMicroMemoizeOverridden.recomputations() + selectorMicroMemoizeOverridden.resetRecomputations() + // @ts-expect-error + selectorMicroMemoizeOverridden.resultFunc() + selectorMicroMemoizeOverridden.resultFunc([{ id: 0, completed: true }]) + + const selectorMicroMemoizeOverrideArgsMemoizeOnly = + createSelectorMicroMemoize( + (state: State) => state.todos, + todos => todos.map(t => t.id), + { + argsMemoize: defaultMemoize, + argsMemoizeOptions: { resultEqualityCheck: (a, b) => a === b } + } + ) + selectorMicroMemoizeOverrideArgsMemoizeOnly(state) + // @ts-expect-error + selectorMicroMemoizeOverrideArgsMemoizeOnly() + // Checking existence of fields related to `argsMemoize` + selectorMicroMemoizeOverrideArgsMemoizeOnly.clearCache() + // @ts-expect-error + selectorMicroMemoizeOverrideArgsMemoizeOnly.cache + // @ts-expect-error + selectorMicroMemoizeOverrideArgsMemoizeOnly.fn() + // @ts-expect-error + selectorMicroMemoizeOverrideArgsMemoizeOnly.isMemoized + // @ts-expect-error + selectorMicroMemoizeOverrideArgsMemoizeOnly.options + // Checking existence of fields related to `memoize` + // @ts-expect-error Note that since we did not override `memoize` in the options object, + // memoizedResultFunc.clearCache becomes an invalid field access, and we get `cache`, `fn`, `isMemoized` and `options` instead. + selectorMicroMemoizeOverrideArgsMemoizeOnly.memoizedResultFunc.clearCache() + selectorMicroMemoizeOverrideArgsMemoizeOnly.memoizedResultFunc.cache + selectorMicroMemoizeOverrideArgsMemoizeOnly.memoizedResultFunc.fn() + selectorMicroMemoizeOverrideArgsMemoizeOnly.memoizedResultFunc.isMemoized + selectorMicroMemoizeOverrideArgsMemoizeOnly.memoizedResultFunc.options + // Checking existence of fields related to the actual memoized selector + selectorMicroMemoizeOverrideArgsMemoizeOnly.dependencies + selectorMicroMemoizeOverrideArgsMemoizeOnly.lastResult() + selectorMicroMemoizeOverrideArgsMemoizeOnly.memoizedResultFunc([ + { id: 0, completed: true } + ]) + // @ts-expect-error + selectorMicroMemoizeOverrideArgsMemoizeOnly.memoizedResultFunc() + selectorMicroMemoizeOverrideArgsMemoizeOnly.recomputations() + selectorMicroMemoizeOverrideArgsMemoizeOnly.resetRecomputations() + // @ts-expect-error + selectorMicroMemoizeOverrideArgsMemoizeOnly.resultFunc() + selectorMicroMemoizeOverrideArgsMemoizeOnly.resultFunc([ + { id: 0, completed: true } + ]) + + const selectorMicroMemoizeOverrideMemoizeOnly = createSelectorMicroMemoize( + (state: State) => state.todos, + todos => todos.map(t => t.id), + { + memoize: defaultMemoize, + memoizeOptions: { resultEqualityCheck: (a, b) => a === b } + } + ) + selectorMicroMemoizeOverrideMemoizeOnly(state) + // @ts-expect-error + selectorMicroMemoizeOverrideMemoizeOnly() + // Checking existence of fields related to `argsMemoize` + // @ts-expect-error Note that since we did not override `argsMemoize` in the options object, + // selector.clearCache becomes an invalid field access, and we get `cache`, `fn`, `isMemoized` and `options` instead. + selectorMicroMemoizeOverrideMemoizeOnly.clearCache() + selectorMicroMemoizeOverrideMemoizeOnly.cache + selectorMicroMemoizeOverrideMemoizeOnly.fn + selectorMicroMemoizeOverrideMemoizeOnly.isMemoized + selectorMicroMemoizeOverrideMemoizeOnly.options + // Checking existence of fields related to `memoize` + // @ts-expect-error + selectorMicroMemoizeOverrideMemoizeOnly.memoizedResultFunc.cache + // @ts-expect-error + selectorMicroMemoizeOverrideMemoizeOnly.memoizedResultFunc.fn() + // @ts-expect-error + selectorMicroMemoizeOverrideMemoizeOnly.memoizedResultFunc.isMemoized + // @ts-expect-error + selectorMicroMemoizeOverrideMemoizeOnly.memoizedResultFunc.options + selectorMicroMemoizeOverrideMemoizeOnly.memoizedResultFunc.clearCache() + // Checking existence of fields related to the actual memoized selector + selectorMicroMemoizeOverrideMemoizeOnly.dependencies + selectorMicroMemoizeOverrideMemoizeOnly.lastResult() + selectorMicroMemoizeOverrideMemoizeOnly.memoizedResultFunc([ + { id: 0, completed: true } + ]) + // @ts-expect-error + selectorMicroMemoizeOverrideMemoizeOnly.memoizedResultFunc() + selectorMicroMemoizeOverrideMemoizeOnly.recomputations() + selectorMicroMemoizeOverrideMemoizeOnly.resetRecomputations() + // @ts-expect-error + selectorMicroMemoizeOverrideMemoizeOnly.resultFunc() + selectorMicroMemoizeOverrideMemoizeOnly.resultFunc([ + { id: 0, completed: true } + ]) + // If we don't pass in `argsMemoize`, the type for `argsMemoizeOptions` falls back to the options parameter of `defaultMemoize`. + const createSelectorMicroMemoizeArgsMemoizeOptionsFallbackToDefault = + createSelectorCreator({ + memoize: microMemoize, + memoizeOptions: { isPromise: false }, + argsMemoizeOptions: { resultEqualityCheck: (a, b) => a === b } + }) + const selectorMicroMemoizeArgsMemoizeOptionsFallbackToDefault = + createSelectorMicroMemoizeArgsMemoizeOptionsFallbackToDefault( + (state: State) => state.todos, + todos => todos.map(t => t.id) + ) + selectorMicroMemoizeArgsMemoizeOptionsFallbackToDefault(state) + // @ts-expect-error + selectorMicroMemoizeArgsMemoizeOptionsFallbackToDefault() + selectorMicroMemoizeArgsMemoizeOptionsFallbackToDefault.clearCache() + // @ts-expect-error + selectorMicroMemoizeArgsMemoizeOptionsFallbackToDefault.cache + // @ts-expect-error + selectorMicroMemoizeArgsMemoizeOptionsFallbackToDefault.fn + // @ts-expect-error + selectorMicroMemoizeArgsMemoizeOptionsFallbackToDefault.isMemoized + // @ts-expect-error + selectorMicroMemoizeArgsMemoizeOptionsFallbackToDefault.options + // Checking existence of fields related to `memoize` + selectorMicroMemoizeArgsMemoizeOptionsFallbackToDefault.memoizedResultFunc + .cache + selectorMicroMemoizeArgsMemoizeOptionsFallbackToDefault.memoizedResultFunc.fn() + selectorMicroMemoizeArgsMemoizeOptionsFallbackToDefault.memoizedResultFunc + .isMemoized + selectorMicroMemoizeArgsMemoizeOptionsFallbackToDefault.memoizedResultFunc + .options + // @ts-expect-error + selectorMicroMemoizeArgsMemoizeOptionsFallbackToDefault.memoizedResultFunc.clearCache() + // Checking existence of fields related to the actual memoized selector + selectorMicroMemoizeArgsMemoizeOptionsFallbackToDefault.dependencies + selectorMicroMemoizeArgsMemoizeOptionsFallbackToDefault.lastResult() + selectorMicroMemoizeArgsMemoizeOptionsFallbackToDefault.memoizedResultFunc([ + { id: 0, completed: true } + ]) + // @ts-expect-error + selectorMicroMemoizeArgsMemoizeOptionsFallbackToDefault.memoizedResultFunc() + selectorMicroMemoizeArgsMemoizeOptionsFallbackToDefault.recomputations() + selectorMicroMemoizeArgsMemoizeOptionsFallbackToDefault.resetRecomputations() + // @ts-expect-error + selectorMicroMemoizeArgsMemoizeOptionsFallbackToDefault.resultFunc() + selectorMicroMemoizeArgsMemoizeOptionsFallbackToDefault.resultFunc([ + { id: 0, completed: true } + ]) +} + +function testArgsMemoizeInCreateSelectorCreatorDoneWrong() { + interface State { + todos: { + id: number + completed: boolean + }[] + } + const state: State = { + todos: [ + { id: 0, completed: false }, + { id: 1, completed: false } + ] + } + + const createSelectorMicroMemoize = createSelectorCreator({ + memoize: microMemoize, + memoizeOptions: { isPromise: false }, + argsMemoize: microMemoize, + argsMemoizeOptions: { isEqual: (a, b) => a === b } + }) + + const createSelectorMicroMemoizeArgsMemoizeOptionsFallbackToDefaultError = + // @ts-expect-error If we don't pass in `argsMemoize`, the type for `argsMemoizeOptions` falls back to the options parameter of `defaultMemoize`. + createSelectorCreator({ + memoize: microMemoize, + memoizeOptions: { isEqual: (a, b) => a === b }, + // @ts-expect-error + argsMemoizeOptions: { isEqual: (a, b) => a === b } + }) + + // @ts-expect-error Since `argsMemoize` is set to `defaultMemoize`, `argsMemoizeOptions` must match the options object parameter of `defaultMemoize` + const selectorMicroMemoizePartiallyOverridden = createSelectorMicroMemoize( + (state: State) => state.todos, + // @ts-expect-error + todos => todos.map(t => t.id), + { + memoize: defaultMemoize, + argsMemoize: defaultMemoize, + memoizeOptions: { + // @ts-expect-error + equalityCheck: (a, b) => a === b, + maxSize: 2 + }, + argsMemoizeOptions: { isPromise: false } // This field causes a type error since it does not match the options param of `defaultMemoize`. + } + ) +} diff --git a/typescript_test/test.ts b/typescript_test/test.ts index 81980dd91..37acb8c2c 100644 --- a/typescript_test/test.ts +++ b/typescript_test/test.ts @@ -950,7 +950,7 @@ function multiArgMemoize any>( (state: { foo: string }) => state.foo, foo => foo + '!' ) - // @ts-expect-error - not using defaultMemoize, so clearCache shouldn't exist + // error is not applicable anymore select.clearCache() const createMultiMemoizeArgSelector2 = createSelectorCreator( @@ -1708,6 +1708,7 @@ function testMemoizeMethodInCreateSelector() { // @ts-expect-error When memoize is autotrackMemoize, type of memoizeOptions needs to be the same as options args in autotrackMemoize. const selectorAutotrackArgsAsArrayWithMemoizeOptions = createSelector( [(state: State) => state.todos], + // @ts-expect-error todos => todos.map(t => t.id), { memoize: autotrackMemoize, memoizeOptions: { maxSize: 2 } } ) @@ -1731,6 +1732,7 @@ function testMemoizeMethodInCreateSelector() { // @ts-expect-error When memoize is weakMapMemoize, type of memoizeOptions needs to be the same as options args in weakMapMemoize. const selectorWeakMapArgsAsArrayWithMemoizeOptions = createSelector( [(state: State) => state.todos], + // @ts-expect-error todos => todos.map(t => t.id), { memoize: weakMapMemoize, memoizeOptions: { maxSize: 2 } } ) @@ -1781,7 +1783,6 @@ function testMemoizeMethodInCreateSelector() { ) } - function testArgsMemoizeMethodInCreateSelector() { interface State { todos: { @@ -1822,6 +1823,7 @@ function testArgsMemoizeMethodInCreateSelector() { // @ts-expect-error When argsMemoize is autotrackMemoize, type of argsMemoizeOptions needs to be the same as options args in autotrackMemoize. const selectorAutotrackArgsAsArrayWithMemoizeOptions = createSelector( [(state: State) => state.todos], + // @ts-expect-error todos => todos.map(t => t.id), { argsMemoize: autotrackMemoize, argsMemoizeOptions: { maxSize: 2 } } ) @@ -1845,6 +1847,7 @@ function testArgsMemoizeMethodInCreateSelector() { // @ts-expect-error When argsMemoize is weakMapMemoize, type of argsMemoizeOptions needs to be the same as options args in weakMapMemoize. const selectorWeakMapArgsAsArrayWithMemoizeOptions = createSelector( [(state: State) => state.todos], + // @ts-expect-error todos => todos.map(t => t.id), { argsMemoize: weakMapMemoize, argsMemoizeOptions: { maxSize: 2 } } ) @@ -1918,9 +1921,34 @@ function testArgsMemoizeMethodInCreateSelector() { memoize: microMemoize, memoizeOptions: { maxSize: 2 }, argsMemoize: microMemoize, - // memoizeOptions: { maxSize: 2 }, argsMemoizeOptions: { maxSize: 2 } }) + + const selectorObj2 = createSelectorObj2( + (state: State) => state.todos, + todos => todos.map(t => t.id) + ) + selectorObj2.dependencies + selectorObj2.lastResult + selectorObj2.memoizedResultFunc + selectorObj2.recomputations + selectorObj2.resetRecomputations + selectorObj2.resultFunc + + const selectorObj3 = createSelectorObj2( + (state: State) => state.todos, + todos => todos.map(t => t.id), + { memoize: defaultMemoize } + ) + + selectorObj3.dependencies + selectorObj3.lastResult + selectorObj3.memoizedResultFunc + selectorObj3.recomputations + selectorObj3.resetRecomputations + selectorObj3.resultFunc + // selectorObj3.clearCache() + const selectorFunc = createSelectorFunc( (state: State) => state.todos, todos => todos.map(t => t.id), @@ -1932,17 +1960,5 @@ function testArgsMemoizeMethodInCreateSelector() { argsMemoizeOptions: { maxSize: 2 } } ) - const selectorObj = createSelectorObj( - (state: State) => state.todos, - todos => todos.map(t => t.id), - { - // memoizeOptions: { maxSize: 2 }, - // argsMemoize: defaultMemoize, - // argsMemoizeOptions: { maxSize: 2 } - } - ) - const element = createSelectorCreator({ - memoize: defaultMemoize, - memoizeOptions: { maxSize: 2 } - }) + selectorFunc.clearCache() } diff --git a/typescript_test/tsconfig.json b/typescript_test/tsconfig.json index 268d28232..159f0caa2 100644 --- a/typescript_test/tsconfig.json +++ b/typescript_test/tsconfig.json @@ -2,6 +2,7 @@ "compilerOptions": { "module": "commonjs", "strict": true, + "esModuleInterop": true, "target": "ES2015", "declaration": true, "noEmit": true, @@ -10,6 +11,5 @@ "reselect": ["../src/index"] // @remap-prod-remove-line } }, - "include": ["test.ts"], - + "include": ["test.ts", "argsMemoizeTypeTest.ts"] }