2023-10-03 11:14:36 +08:00
|
|
|
import moize from '../src';
|
|
|
|
|
|
|
|
type Type = {
|
|
|
|
number: number;
|
|
|
|
};
|
|
|
|
|
|
|
|
const method = (one: number, two: Type) => one + two.number;
|
|
|
|
const promiseMethodResolves = (one: number, two: Type) =>
|
|
|
|
new Promise((resolve) => setTimeout(() => resolve(one + two.number), 1000));
|
|
|
|
const promiseMethodRejects =
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
|
|
(one: number, two: Type) =>
|
|
|
|
new Promise((resolve, reject) =>
|
|
|
|
setTimeout(() => reject(new Error('boom')), 1000)
|
|
|
|
);
|
|
|
|
|
|
|
|
describe('moize.updateCacheForKey', () => {
|
|
|
|
describe('success', () => {
|
|
|
|
it('will refresh the cache', () => {
|
|
|
|
const moized = moize.maxSize(2)(method, {
|
|
|
|
updateCacheForKey(args) {
|
|
|
|
return args[1].number % 2 === 0;
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
const mutated = { number: 5 };
|
|
|
|
|
|
|
|
const result = moized(6, mutated);
|
|
|
|
|
|
|
|
expect(result).toBe(11);
|
|
|
|
|
|
|
|
mutated.number = 11;
|
|
|
|
|
|
|
|
const mutatedResult = moized(6, mutated);
|
|
|
|
|
|
|
|
// Result was not recalculated because `updateCacheForKey` returned `false` and the values are
|
|
|
|
// seen as unchanged.
|
|
|
|
expect(mutatedResult).toBe(result);
|
|
|
|
|
|
|
|
mutated.number = 10;
|
|
|
|
|
|
|
|
const refreshedResult = moized(6, mutated);
|
|
|
|
|
|
|
|
// Result was recalculated because `updateCacheForKey` returned `true`.
|
|
|
|
expect(refreshedResult).not.toBe(result);
|
|
|
|
expect(refreshedResult).toBe(16);
|
|
|
|
|
|
|
|
const { keys, values } = moized.cacheSnapshot;
|
|
|
|
|
|
|
|
expect(keys).toEqual([[6, mutated]]);
|
|
|
|
expect(values).toEqual([16]);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('will refresh the cache based on external values', async () => {
|
|
|
|
const mockMethod = jest.fn(method);
|
|
|
|
|
|
|
|
let lastUpdate = Date.now();
|
|
|
|
|
|
|
|
const moized = moize.maxSize(2)(mockMethod, {
|
|
|
|
updateCacheForKey() {
|
|
|
|
const now = Date.now();
|
|
|
|
const last = lastUpdate;
|
|
|
|
|
|
|
|
lastUpdate = now;
|
|
|
|
|
|
|
|
return last + 1000 < now;
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
const mutated = { number: 5 };
|
|
|
|
|
|
|
|
moized(6, mutated);
|
|
|
|
moized(6, mutated);
|
|
|
|
moized(6, mutated);
|
|
|
|
|
|
|
|
expect(mockMethod).toHaveBeenCalledTimes(1);
|
|
|
|
|
|
|
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
|
|
|
|
|
|
moized(6, mutated);
|
|
|
|
|
|
|
|
expect(mockMethod).toHaveBeenCalledTimes(2);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('will refresh the cache when used with promises', async () => {
|
|
|
|
const moized = moize.maxSize(2)(promiseMethodResolves, {
|
|
|
|
isPromise: true,
|
|
|
|
updateCacheForKey(args) {
|
|
|
|
return args[1].number % 2 === 0;
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
const mutated = { number: 5 };
|
|
|
|
|
|
|
|
const result = await moized(6, mutated);
|
|
|
|
|
|
|
|
expect(result).toBe(11);
|
|
|
|
|
|
|
|
mutated.number = 11;
|
|
|
|
|
|
|
|
const mutatedResult = await moized(6, mutated);
|
|
|
|
|
|
|
|
// Result was not recalculated because `updateCacheForKey` returned `false` and the values are
|
|
|
|
// seen as unchanged.
|
|
|
|
expect(mutatedResult).toBe(result);
|
|
|
|
|
|
|
|
mutated.number = 10;
|
|
|
|
|
|
|
|
const refreshedResult = await moized(6, mutated);
|
|
|
|
|
|
|
|
// Result was recalculated because `updateCacheForKey` returned `true`.
|
|
|
|
expect(refreshedResult).not.toBe(result);
|
|
|
|
expect(refreshedResult).toBe(16);
|
|
|
|
|
|
|
|
const { keys, values } = moized.cacheSnapshot;
|
|
|
|
|
|
|
|
expect(keys).toEqual([[6, mutated]]);
|
|
|
|
expect(values).toEqual([Promise.resolve(16)]);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('will refresh the cache when used with custom key transformers', () => {
|
|
|
|
type ConditionalIncrement = {
|
|
|
|
force?: boolean;
|
|
|
|
};
|
|
|
|
|
|
|
|
let count = 0;
|
|
|
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
|
|
const increment = (_?: ConditionalIncrement) => ++count;
|
|
|
|
|
|
|
|
const moized = moize.maxSize(2)(increment, {
|
|
|
|
isSerialized: true,
|
|
|
|
updateCacheForKey: (args: [ConditionalIncrement]) =>
|
|
|
|
args[0] && args[0].force === true,
|
|
|
|
serializer: () => ['always same'],
|
|
|
|
});
|
|
|
|
|
|
|
|
expect(moized()).toBe(1);
|
|
|
|
expect(moized()).toBe(1);
|
|
|
|
expect(moized({ force: true })).toBe(2);
|
|
|
|
expect(moized()).toBe(2);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('will refresh the cache with shorthand', () => {
|
|
|
|
const moized = moize.updateCacheForKey(
|
|
|
|
(args) => args[1].number % 2 === 0
|
|
|
|
)(method);
|
|
|
|
|
|
|
|
const mutated = { number: 5 };
|
|
|
|
|
|
|
|
const result = moized(6, mutated);
|
|
|
|
|
|
|
|
expect(result).toBe(11);
|
|
|
|
|
|
|
|
mutated.number = 11;
|
|
|
|
|
|
|
|
const mutatedResult = moized(6, mutated);
|
|
|
|
|
|
|
|
// Result was not recalculated because `updateCacheForKey` returned `false` and the values are
|
|
|
|
// seen as unchanged.
|
|
|
|
expect(mutatedResult).toBe(result);
|
|
|
|
|
|
|
|
mutated.number = 10;
|
|
|
|
|
|
|
|
const refreshedResult = moized(6, mutated);
|
|
|
|
|
|
|
|
// Result was recalculated because `updateCacheForKey` returned `true`.
|
|
|
|
expect(refreshedResult).not.toBe(result);
|
|
|
|
expect(refreshedResult).toBe(16);
|
|
|
|
|
|
|
|
const { keys, values } = moized.cacheSnapshot;
|
|
|
|
|
|
|
|
expect(keys).toEqual([[6, mutated]]);
|
|
|
|
expect(values).toEqual([16]);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('will refresh the cache with composed shorthand', () => {
|
|
|
|
const moizer = moize.compose(
|
|
|
|
moize.maxSize(2),
|
|
|
|
moize.updateCacheForKey((args) => args[1].number % 2 === 0)
|
|
|
|
);
|
|
|
|
const moized = moizer(method);
|
|
|
|
|
|
|
|
const mutated = { number: 5 };
|
|
|
|
|
|
|
|
const result = moized(6, mutated);
|
|
|
|
|
|
|
|
expect(result).toBe(11);
|
|
|
|
|
|
|
|
mutated.number = 11;
|
|
|
|
|
|
|
|
const mutatedResult = moized(6, mutated);
|
|
|
|
|
|
|
|
// Result was not recalculated because `updateCacheForKey` returned `false` and the values are
|
|
|
|
// seen as unchanged.
|
|
|
|
expect(mutatedResult).toBe(result);
|
|
|
|
|
|
|
|
mutated.number = 10;
|
|
|
|
|
|
|
|
const refreshedResult = moized(6, mutated);
|
|
|
|
|
|
|
|
// Result was recalculated because `updateCacheForKey` returned `true`.
|
|
|
|
expect(refreshedResult).not.toBe(result);
|
|
|
|
expect(refreshedResult).toBe(16);
|
|
|
|
|
|
|
|
const { keys, values } = moized.cacheSnapshot;
|
|
|
|
|
|
|
|
expect(keys).toEqual([[6, mutated]]);
|
|
|
|
expect(values).toEqual([16]);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('fail', () => {
|
|
|
|
it('surfaces the error if the function fails', () => {
|
|
|
|
const moized = moize.maxSize(2)(
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
|
|
(_1: number, _2: Type) => {
|
|
|
|
throw new Error('boom');
|
|
|
|
},
|
|
|
|
{
|
|
|
|
updateCacheForKey(args) {
|
|
|
|
return args[1].number % 2 === 0;
|
|
|
|
},
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
const mutated = { number: 5 };
|
|
|
|
|
|
|
|
expect(() => moized(6, mutated)).toThrow(new Error('boom'));
|
|
|
|
});
|
|
|
|
|
|
|
|
it('surfaces the error if the promise rejects', async () => {
|
|
|
|
const moized = moize.maxSize(2)(promiseMethodRejects, {
|
|
|
|
isPromise: true,
|
|
|
|
updateCacheForKey(args) {
|
|
|
|
return args[1].number % 2 === 0;
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
const mutated = { number: 5 };
|
|
|
|
|
|
|
|
await expect(moized(6, mutated)).rejects.toEqual(new Error('boom'));
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should have nothing in cache if promise is rejected and key was never present', async () => {
|
|
|
|
const moized = moize.maxSize(2)(promiseMethodRejects, {
|
|
|
|
isPromise: true,
|
|
|
|
updateCacheForKey(args) {
|
|
|
|
return args[1].number % 2 === 0;
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
const mutated = { number: 5 };
|
|
|
|
|
|
|
|
await expect(moized(6, mutated)).rejects.toEqual(new Error('boom'));
|
|
|
|
|
|
|
|
expect(moized.keys()).toEqual([]);
|
|
|
|
expect(moized.values()).toEqual([]);
|
|
|
|
});
|
|
|
|
|
|
|
|
// For some reason, this is causing `jest` to crash instead of handle the rejection
|
|
|
|
it.skip('should have nothing in cache if promise is rejected and key was present', async () => {
|
|
|
|
const moized = moize.maxSize(2)(promiseMethodRejects, {
|
|
|
|
isPromise: true,
|
|
|
|
updateCacheForKey(args) {
|
|
|
|
return args[1].number % 2 === 0;
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
const mutated = { number: 5 };
|
|
|
|
|
|
|
|
moized.set([6, mutated], Promise.resolve(11));
|
|
|
|
|
|
|
|
expect(moized.get([6, mutated])).toEqual(Promise.resolve(11));
|
|
|
|
|
|
|
|
mutated.number = 10;
|
|
|
|
|
|
|
|
await expect(moized(6, mutated)).rejects.toEqual(new Error('boom'));
|
|
|
|
|
|
|
|
expect(moized.keys()).toEqual([]);
|
|
|
|
expect(moized.values()).toEqual([]);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('infrastructure', () => {
|
|
|
|
it('should have all the static properties of a standard moized method', () => {
|
|
|
|
const moized = moize.maxSize(2)(promiseMethodResolves, {
|
|
|
|
updateCacheForKey(args) {
|
|
|
|
return args[1].number % 2 === 0;
|
|
|
|
},
|
|
|
|
});
|
|
|
|
const standardMoized = moize.maxSize(2)(promiseMethodResolves);
|
|
|
|
|
|
|
|
expect(Object.getOwnPropertyNames(moized)).toEqual(
|
|
|
|
Object.getOwnPropertyNames(standardMoized)
|
|
|
|
);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('edge cases', () => {
|
|
|
|
it('should retain the original function name', () => {
|
|
|
|
function myNamedFunction() {}
|
|
|
|
|
|
|
|
const memoized = moize(myNamedFunction, {
|
|
|
|
updateCacheForKey: () => false,
|
|
|
|
});
|
|
|
|
|
|
|
|
expect(memoized.name).toBe('moized(myNamedFunction)');
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|