UNPKG

@swarmy/lru-cache

Version:

LRU cache with event registry to handle cache changes

971 lines (812 loc) 30 kB
/* eslint-disable no-magic-number, max-lines */ import { getCache, cacheTransaction, registerCacheChangedHandler, clearAllCaches, } from "../LruCache"; const VALUE_TYPE_1 = "valueType1"; const VALUE_TYPE_2 = "valueType2"; const VALUE_TYPE_3 = "valueType3"; const DEFAULT_CACHE_SIZE = 500; beforeAll(() => { getCache(VALUE_TYPE_1).dispatchLruRemoves(true); getCache(VALUE_TYPE_1).dispatchClearRemoves(true); getCache(VALUE_TYPE_2).dispatchLruRemoves(true); getCache(VALUE_TYPE_2).dispatchClearRemoves(true); }); afterAll(() => { getCache(VALUE_TYPE_1).dispatchLruRemoves(false); getCache(VALUE_TYPE_1).dispatchClearRemoves(false); getCache(VALUE_TYPE_2).dispatchLruRemoves(false); getCache(VALUE_TYPE_2).dispatchClearRemoves(false); getCache(VALUE_TYPE_1).clear(); getCache(VALUE_TYPE_2).clear(); }); describe("LruCache", () => { it("should provide a 'set' method for a key/value pair and a 'get' method to retrieve by key", () => { const cache1 = getCache(VALUE_TYPE_1); cache1.set({ key: "key1", value: "value1 of type 1", }); expect(cache1.get("key1")).toEqual("value1 of type 1"); cache1.clear(); }); it("should allow to pass an array of alternate keys to 'set' method. 'get' and 'has' must also work with the alternate key", () => { const cache1 = getCache(VALUE_TYPE_1); cache1.set({ key: "key1", value: "value1 of type 1", alternateKeys: ["myAltKey1", "myAltKey2"], }); expect(cache1.get("myAltKey1")).toEqual("value1 of type 1"); expect(cache1.get("myAltKey2")).toEqual("value1 of type 1"); expect(typeof cache1.get("myAltKey3")).toEqual("undefined"); expect(cache1.has("key1")).toBeTruthy(); expect(cache1.has("myAltKey2")).toBeTruthy(); expect(cache1.has("myAltKey3")).toBeFalsy(); cache1.clear(); }); it("should provide a 'setAsync' method that also supports alternate keys", async () => { const cache1 = getCache(VALUE_TYPE_1); let setPromise = cache1.setAsync({ key: "key1", value: "value1 of type 1", alternateKeys: ["myAltKey1"], }); expect(typeof cache1.get("key1")).toEqual("undefined"); await setPromise; expect(cache1.get("key1")).toEqual("value1 of type 1"); setPromise = cache1.setAsync({ key: "key1", value: "value1 of type 1 modified", alternateKeys: ["myAltKey2"], // will extend the existing alternate key }); expect(cache1.get("myAltKey1")).toEqual("value1 of type 1"); expect(typeof cache1.get("myAltKey2")).toEqual("undefined"); await setPromise; expect(cache1.get("myAltKey1")).toEqual("value1 of type 1 modified"); expect(cache1.get("myAltKey2")).toEqual("value1 of type 1 modified"); cache1.clear(); }); it("should provide a 'setAll' method that supports alternateKeys and allow for alternateKeys to be a string instead of an array", () => { const cache1 = getCache(VALUE_TYPE_1); cache1.setAll([ { key: "key1", value: "value1 of type 1", alternateKeys: ["myAltKey1"], }, { key: "key2", value: "value2 of type 1", alternateKeys: "myAltKey2", }, ]); expect(cache1.get("key1")).toEqual("value1 of type 1"); expect(cache1.get("myAltKey1")).toEqual("value1 of type 1"); expect(cache1.get("myAltKey2")).toEqual("value2 of type 1"); cache1.clear(); }); it("should throw when no array is passed to 'setAll'", () => { const cache1 = getCache(VALUE_TYPE_1); cache1.set({ key: "key1", value: "value1 of type 1", alternateKeys: "myAltKey1", }); expect(() => { cache1.setAll({ key: "key2", value: "value2 of type 1", alternateKeys: "myAltKey1", }) }).toThrow("LruCache::setAll: keyValueAlternateKeysArray must be an array"); cache1.clear(); }); it("should provide a 'setAllAsync' method", async () => { const cache1 = getCache(VALUE_TYPE_1); let setAllPromise = cache1.setAllAsync([ { key: "key1", value: "value1 of type 1", alternateKeys: ["myAltKey1"], }, { key: "key2", value: "value2 of type 1", alternateKeys: "myAltKey2", }, ]); expect(typeof cache1.get("key1")).toEqual("undefined"); await setAllPromise; expect(cache1.get("key1")).toEqual("value1 of type 1"); cache1.clear(); }); it("should throw when trying to use an alternate key that is already used for another key", () => { const cache1 = getCache(VALUE_TYPE_1); cache1.set({ key: "key1", value: "value1 of type 1", alternateKeys: "myAltKey1", }); expect(() => { cache1.set({ key: "key2", value: "value2 of type 1", alternateKeys: "myAltKey1", }) }).toThrow("LruCache::setAll: alternate key 'myAltKey1' is given for key 'key2' and value type '" + VALUE_TYPE_1 + "' but is already used for key 'key1'"); cache1.clear(); }); it("should provide a 'setMaxSize' and 'getMaxSize' method and should use max size infinite, if null or 0 is passed to 'setMaxSize'", () => { const cache1 = getCache(VALUE_TYPE_1); const newSize = 37; expect(cache1.getMaxSize()).toEqual(DEFAULT_CACHE_SIZE); cache1.setMaxSize(newSize); expect(cache1.getMaxSize()).toEqual(newSize); cache1.setMaxSize(0); expect(cache1.getMaxSize()).toEqual(null); cache1.setMaxSize(newSize); cache1.setMaxSize(null); expect(cache1.getMaxSize()).toEqual(null); cache1.setMaxSize(DEFAULT_CACHE_SIZE); cache1.clear(); }); it("should provide a 'getSize' method", () => { const cache1 = getCache(VALUE_TYPE_1); expect(cache1.getSize()).toEqual(0); cache1.setAll([ { key: "key1", value: "value1 of type 1", alternateKeys: ["myAltKey1"], }, { key: "key2", value: "value2 of type 1", alternateKeys: "myAltKey2", }, ]); expect(cache1.getSize()).toEqual(2); cache1.set({ key: "key1", value: "value1 of type 1 modified", }); expect(cache1.getSize()).toEqual(2); cache1.clear(); expect(cache1.getSize()).toEqual(0); }); it("should provide a 'delete' method that works with key, but not with alternate key", () => { const cache1 = getCache(VALUE_TYPE_1); cache1.setAll([ { key: "key1", value: "value1 of type 1", alternateKeys: "myAltKey1", }, { key: "key2", value: "value2 of type 1", alternateKeys: "myAltKey2", }, ]); const deleteResult1 = cache1.delete("key1"); const deleteResult2 = cache1.delete("key1"); expect(cache1.getSize()).toEqual(1); const deleteResult3 = cache1.delete("myAltKey2"); const deleteResult4 = cache1.delete("key2"); expect(deleteResult1).toBeTruthy(); expect(deleteResult2).toBeFalsy(); expect(deleteResult3).toBeFalsy(); expect(deleteResult4).toBeTruthy(); expect(cache1.getSize()).toEqual(0); cache1.clear(); }); it("should provide a 'getValueType' method", () => { const cache1 = getCache(VALUE_TYPE_1); expect(cache1.getValueType()).toEqual(VALUE_TYPE_1); cache1.clear(); }); it("should provide a 'getEntries' method", () => { const cache1 = getCache(VALUE_TYPE_1); cache1.setAll([ { key: "key1", value: "value1 of type 1", alternateKeys: "myAltKey1", }, { key: "key2", value: "value2 of type 1", alternateKeys: "myAltKey2", }, ]); const entries = cache1.getEntries(); expect(entries[1].alternateKeys.has("myAltKey2")).toBeTruthy(); cache1.clear(); }); it("should provide a 'forEach' method that iterates from oldest to newest entry", () => { const cache1 = getCache(VALUE_TYPE_1); cache1.setAll([ { key: "key1", value: "value1 of type 1", alternateKeys: "myAltKey1", }, { key: "key2", value: "value2 of type 1", alternateKeys: "myAltKey2", }, ]); let entries = []; cache1.forEach(entry => { entries.push(entry); }); expect(entries[1].alternateKeys.has("myAltKey2")).toBeTruthy(); cache1.get("key1"); entries = []; cache1.forEach(entry => { entries.push(entry); }); expect(entries[1].alternateKeys.has("myAltKey1")).toBeTruthy(); cache1.clear(); }); it("should provide a method to define an entry getter that is used in case of a cache miss", () => { const type3GetterSync = key => ({ key, value: key + "_value", alternateKeys: [key + "_alternate"], }); const cache = getCache(VALUE_TYPE_3); cache.setEntryGetter(type3GetterSync); cache.set({ key: "key1", value: "value1", }); expect(cache.get("key1")).toEqual("value1"); expect(cache.get("key2")).toEqual("key2_value"); expect(cache.get("key2_alternate")).toEqual("key2_value"); cache.setEntryGetter(null); cache.clear(); }); it("should provide a method to define an async entry getter that is used in case of a cache miss", async () => { let nCalls = 0; const type3GetterAsync = key => new Promise(resolve => { nCalls += 1; setTimeout(() => { resolve({ key, value: key + "_value", alternateKeys: [key + "_alternate"], }); }, 200) }); const cache = getCache(VALUE_TYPE_3); cache.setEntryGetter(type3GetterAsync); expect(typeof cache.get("key1") === "object").toBeTruthy(); expect(typeof cache.get("key1").then === "function").toBeTruthy(); const val = await cache.get("key1"); expect(val).toEqual("key1_value"); expect(cache.get("key1")).toEqual("key1_value"); // We must also ensure that the getter was called only once: expect(nCalls).toEqual(1); cache.setEntryGetter(null); cache.clear(); }); it("should handle the case where a getter returns undefined", async () => { let nCalls = 0; const type3GetterAsync = () => new Promise(resolve => { nCalls += 1; setTimeout(() => { resolve(undefined); // eslint-disable-line no-undefined }, 200) }); const cache = getCache(VALUE_TYPE_3); cache.setEntryGetter(type3GetterAsync); expect(typeof cache.get("key1") === "object").toBeTruthy(); expect(typeof cache.get("key1").then === "function").toBeTruthy(); let val = await cache.get("key1"); expect(typeof val).toEqual("undefined"); expect(typeof cache.get("key1")).toEqual("object"); val = await cache.get("key1"); expect(typeof val).toEqual("undefined"); // We must also ensure that the getter was called only once: expect(nCalls).toEqual(2); cache.setEntryGetter(null); cache.clear(); }); it("should take the value from the entry getter and not from the cache, if specified in get", () => { const type3GetterSync = key => ({ key, value: key + "_value", alternateKeys: [key + "_alternate"], }); const cache = getCache(VALUE_TYPE_3); cache.setEntryGetter(type3GetterSync); cache.set({ key: "key1", value: "value1", }); expect(cache.get("key1")).toEqual("value1"); expect(cache.get("key1", true)).toEqual("key1_value"); expect(cache.get("key1")).toEqual("key1_value"); cache.setEntryGetter(null); cache.clear(); }); it("should throw, if value should be taken from entry getter and no entry getter is defined", () => { const cache = getCache(VALUE_TYPE_3); cache.set({ key: "key1", value: "value1", }); expect(cache.get("key1")).toEqual("value1"); expect(() => { cache.get("key1", true); }).toThrow("called get with notFromCache, but no entry getter was set"); cache.clear(); }); it("should be possible to override a set entry getter by passing a custom one to the get method", () => { const type3GetterSync = key => ({ key, value: key + "_value", alternateKeys: [key + "_alternate"], }); const customType3GetterSync = key => ({ key, value: key + "_value_custom", alternateKeys: [key + "_alternate"], }); const cache = getCache(VALUE_TYPE_3); cache.setEntryGetter(type3GetterSync); cache.set({ key: "key1", value: "value1", }); expect(cache.get("key1")).toEqual("value1"); expect(cache.get("key2")).toEqual("key2_value"); expect(cache.get("key2_alternate")).toEqual("key2_value"); expect(cache.get("key2", false, customType3GetterSync)).toEqual("key2_value"); expect(cache.get("key2_alternate", false, customType3GetterSync)).toEqual("key2_value"); expect(cache.get("key2", true, customType3GetterSync)).toEqual("key2_value_custom"); expect(cache.get("key2")).toEqual("key2_value_custom"); expect(cache.get("key2_alternate")).toEqual("key2_value_custom"); cache.setEntryGetter(null); cache.clear(); }); }); describe("getCache", () => { it("should return the same LruCache instance for the same value type (LruCache must be a per-valueType-singleton)", () => { const cache1 = getCache(VALUE_TYPE_1); const cache2 = getCache(VALUE_TYPE_2); const cache3 = getCache(VALUE_TYPE_1); cache1.set({ key: "key1", value: "value1 of type 1", }); cache2.set({ key: "key1", value: "value1 of type 2", }); expect(cache3.get("key1")).toEqual("value1 of type 1"); cache1.clear(); expect(typeof cache3.get("key1")).toEqual("undefined"); cache2.clear(); }); }); describe("clearAllCaches", () => { it("should clear all caches", () => { const cache1 = getCache(VALUE_TYPE_1); const cache2 = getCache(VALUE_TYPE_2); cache1.setAll([ { key: "key1", value: "value1 of type 1", alternateKeys: ["myAltKey1"], }, { key: "key2", value: "value2 of type 1", alternateKeys: "myAltKey2", }, ]); cache2.setAll([ { key: "key1", value: "value1 of type 2", alternateKeys: ["myAltKey1"], }, { key: "key2", value: "value2 of type 2", alternateKeys: "myAltKey2", }, ]); expect(cache1.get("key1")).toEqual("value1 of type 1"); expect(cache2.get("key1")).toEqual("value1 of type 2"); clearAllCaches(); expect(typeof cache1.get("key1")).toEqual("undefined"); expect(typeof cache2.get("key1")).toEqual("undefined"); }); }); describe("registerCacheChangedHandler", () => { it("should register a listener to cache change events and return a handlerHandle to activate/deactivate or unregister the listener", () => { const cache1 = getCache(VALUE_TYPE_1); let changeCounter = 0; const handlerHandle = registerCacheChangedHandler(() => { changeCounter += 1; }); expect(handlerHandle.isActive).toBeTruthy(); expect(handlerHandle.isRegistered()).toBeTruthy(); cache1.set({ key: "key1", value: "value1 of type 1", }); expect(changeCounter).toEqual(1); handlerHandle.deactivate(); expect(handlerHandle.isActive).toBeFalsy(); cache1.set({ key: "key1", value: "value1 of type 1 mod1", }); expect(changeCounter).toEqual(1); handlerHandle.activate(); expect(handlerHandle.isActive).toBeTruthy(); cache1.set({ key: "key1", value: "value1 of type 1 mod2", }); expect(changeCounter).toEqual(2); handlerHandle.unregister(); expect(handlerHandle.isRegistered()).toBeFalsy(); cache1.set({ key: "key1", value: "value1 of type 1 mod3", }); expect(changeCounter).toEqual(2); cache1.clear(); }); it("should call a listener only once in case of setAll", () => { const cache1 = getCache(VALUE_TYPE_1); let changeCounter = 0; const handlerHandle = registerCacheChangedHandler(() => { changeCounter += 1; }); cache1.setAll([ { key: "key1", value: "value1 of type 1", alternateKeys: "myAltKey1", }, { key: "key2", value: "value2 of type 1", alternateKeys: "myAltKey2", }, ]); expect(changeCounter).toEqual(1); handlerHandle.unregister(); cache1.clear(); }); it("should create correct change events for set", () => { const cache1 = getCache(VALUE_TYPE_1); let changeObject = null; const handlerHandle = registerCacheChangedHandler(changes => { changeObject = changes; }); cache1.setAll([ { key: "key1", value: "value1 of type 1", alternateKeys: "myAltKey1", }, { key: "key2", value: "value2 of type 1", alternateKeys: "myAltKey2", }, ]); expect(changeObject.valueTypes.has(VALUE_TYPE_1)).toBeTruthy(); expect(changeObject[VALUE_TYPE_1].deleteRemoves.length).toEqual(0); expect(changeObject[VALUE_TYPE_1].clearRemoves.length).toEqual(0); expect(changeObject[VALUE_TYPE_1].lruRemoves.length).toEqual(0); expect(changeObject[VALUE_TYPE_1].inserts.length).toEqual(2); expect(changeObject[VALUE_TYPE_1].inserts[0].key).toEqual("key1"); expect(changeObject[VALUE_TYPE_1].inserts[0].value).toEqual("value1 of type 1"); expect(changeObject[VALUE_TYPE_1].inserts[0].alternateKeys.has("myAltKey1")).toBeTruthy(); handlerHandle.unregister(); cache1.clear(); }); it("should throw an error in case of throwing change handlers, but still keep the cache consistent", () => { const cache1 = getCache(VALUE_TYPE_1); const handlerHandle = registerCacheChangedHandler(() => { throw new Error("this is a test"); }); expect(() => { cache1.setAll([ { key: "key1", value: "value1 of type 1", alternateKeys: "myAltKey1", }, { key: "key2", value: "value2 of type 1", alternateKeys: "myAltKey2", }, ]) }).toThrow("handleTransactionChangeObject: 1 of 1 handlers threw an error: this is a test, "); expect(cache1.get("key1")).toEqual("value1 of type 1"); expect(cache1.get("key2")).toEqual("value2 of type 1"); expect(cache1.get("myAltKey1")).toEqual("value1 of type 1"); handlerHandle.unregister(); cache1.clear(); }); it("should create correct change events for delete", () => { const cache1 = getCache(VALUE_TYPE_1); let changeObject = null; const handlerHandle = registerCacheChangedHandler(changes => { changeObject = changes; }); cache1.setAll([ { key: "key1", value: "value1 of type 1", alternateKeys: "myAltKey1", }, { key: "key2", value: "value2 of type 1", alternateKeys: "myAltKey2", }, ]); cache1.delete("key1"); expect(changeObject.valueTypes.has(VALUE_TYPE_1)).toBeTruthy(); expect(changeObject[VALUE_TYPE_1].deleteRemoves.length).toEqual(1); expect(changeObject[VALUE_TYPE_1].clearRemoves.length).toEqual(0); expect(changeObject[VALUE_TYPE_1].lruRemoves.length).toEqual(0); expect(changeObject[VALUE_TYPE_1].inserts.length).toEqual(0); expect(changeObject[VALUE_TYPE_1].deleteRemoves[0].key).toEqual("key1"); handlerHandle.unregister(); cache1.clear(); }); it("should create correct change events for clear", () => { const cache1 = getCache(VALUE_TYPE_1); let changeObject = null; const handlerHandle = registerCacheChangedHandler(changes => { changeObject = changes; }); cache1.setAll([ { key: "key1", value: "value1 of type 1", alternateKeys: "myAltKey1", }, { key: "key2", value: "value2 of type 1", alternateKeys: "myAltKey2", }, ]); cache1.clear(); expect(changeObject.valueTypes.has(VALUE_TYPE_1)).toBeTruthy(); expect(changeObject[VALUE_TYPE_1].clearRemoves.length).toEqual(2); expect(changeObject[VALUE_TYPE_1].deleteRemoves.length).toEqual(0); expect(changeObject[VALUE_TYPE_1].lruRemoves.length).toEqual(0); expect(changeObject[VALUE_TYPE_1].inserts.length).toEqual(0); expect(changeObject[VALUE_TYPE_1].clearRemoves[0].key).toEqual("key1"); expect(changeObject[VALUE_TYPE_1].clearRemoves[0].value).toEqual("value1 of type 1"); expect(changeObject[VALUE_TYPE_1].clearRemoves[0].alternateKeys.has("myAltKey1")).toBeTruthy(); handlerHandle.unregister(); cache1.clear(); }); it("should create correct change events for LRU removes", () => { const cache1 = getCache(VALUE_TYPE_1); cache1.setMaxSize(1); let changeObject = null; const handlerHandle = registerCacheChangedHandler(changes => { changeObject = changes; }); cache1.setAll([ { key: "key1", value: "value1 of type 1", alternateKeys: "myAltKey1", }, { key: "key2", value: "value2 of type 1", alternateKeys: "myAltKey2", }, ]); expect(changeObject.valueTypes.has(VALUE_TYPE_1)).toBeTruthy(); expect(changeObject[VALUE_TYPE_1].clearRemoves.length).toEqual(0); expect(changeObject[VALUE_TYPE_1].deleteRemoves.length).toEqual(0); expect(changeObject[VALUE_TYPE_1].lruRemoves.length).toEqual(1); expect(changeObject[VALUE_TYPE_1].inserts.length).toEqual(2); expect(changeObject[VALUE_TYPE_1].lruRemoves[0].key).toEqual("key1"); expect(changeObject[VALUE_TYPE_1].lruRemoves[0].value).toEqual("value1 of type 1"); expect(changeObject[VALUE_TYPE_1].lruRemoves[0].alternateKeys.has("myAltKey1")).toBeTruthy(); expect(changeObject[VALUE_TYPE_1].inserts[0].key).toEqual("key1"); expect(changeObject[VALUE_TYPE_1].inserts[0].value).toEqual("value1 of type 1"); expect(changeObject[VALUE_TYPE_1].inserts[0].alternateKeys.has("myAltKey1")).toBeTruthy(); expect(changeObject[VALUE_TYPE_1].inserts[1].key).toEqual("key2"); expect(changeObject[VALUE_TYPE_1].inserts[1].value).toEqual("value2 of type 1"); expect(changeObject[VALUE_TYPE_1].inserts[1].alternateKeys.has("myAltKey2")).toBeTruthy(); cache1.setMaxSize(DEFAULT_CACHE_SIZE); handlerHandle.unregister(); cache1.clear(); }); it("should create correct change events if setMaxSize leads to LRU removes", () => { const cache1 = getCache(VALUE_TYPE_1); let changeObject = null; const handlerHandle = registerCacheChangedHandler(changes => { changeObject = changes; }); cache1.setAll([ { key: "key1", value: "value1 of type 1", alternateKeys: "myAltKey1", }, { key: "key2", value: "value2 of type 1", alternateKeys: "myAltKey2", }, ]); cache1.setMaxSize(1); expect(changeObject.valueTypes.has(VALUE_TYPE_1)).toBeTruthy(); expect(changeObject[VALUE_TYPE_1].clearRemoves.length).toEqual(0); expect(changeObject[VALUE_TYPE_1].deleteRemoves.length).toEqual(0); expect(changeObject[VALUE_TYPE_1].lruRemoves.length).toEqual(1); expect(changeObject[VALUE_TYPE_1].inserts.length).toEqual(0); expect(changeObject[VALUE_TYPE_1].lruRemoves[0].key).toEqual("key1"); expect(changeObject[VALUE_TYPE_1].lruRemoves[0].value).toEqual("value1 of type 1"); expect(changeObject[VALUE_TYPE_1].lruRemoves[0].alternateKeys.has("myAltKey1")).toBeTruthy(); cache1.setMaxSize(DEFAULT_CACHE_SIZE); handlerHandle.unregister(); cache1.clear(); }); it("should only listen to events of the given value types", () => { const cache1 = getCache(VALUE_TYPE_1); const cache2 = getCache(VALUE_TYPE_2); let changeCounter = 0; const handlerHandle = registerCacheChangedHandler(() => { changeCounter += 1; }, [VALUE_TYPE_2]); cache1.set({ key: "key1", value: "value1 of type 1", }); expect(changeCounter).toEqual(0); cache2.set({ key: "key1", value: "value1 of type 2", }); expect(changeCounter).toEqual(1); handlerHandle.unregister(); cache1.clear(); cache2.clear(); }); }); describe("cacheTransaction", () => { it("should batch change events inside a cache transaction into a single change event being dispatched", () => { const cache1 = getCache(VALUE_TYPE_1); const cache2 = getCache(VALUE_TYPE_2); cache1.setMaxSize(2); let changeObject = null; let handlerHandle = registerCacheChangedHandler(changes => { changeObject = changes; }); cacheTransaction(() => { cache1.setAll([ { key: "key1", // order 0 value: "value1 of type 1", alternateKeys: "myAltKey1", }, { key: "key2", // order 1 value: "value2 of type 1", alternateKeys: "myAltKey2", }, ]); // 2 inserts expect(changeObject).toEqual(null); cache1.set({ key: "key2", // order 2 value: "value2 of type 1 modified", }); // 3 inserts (key2 in two versions) expect(changeObject).toEqual(null); cache1.set({ key: "key3", // order 3 => key1 lruRemove order 4 value: "value3 of type 1 modified", }); // 4 inserts and 1 lruRemove expect(changeObject).toEqual(null); cache2.set({ key: "key1", // order 5 value: "value1 of type 2", }); // VT1: 4 inserts and 1 lruRemove / VT2: 1 insert expect(changeObject).toEqual(null); cache1.delete("key2"); // order 6 // VT1: 4 inserts and 1 lruRemove and 1 deleteRemove / VT2: 1 insert expect(changeObject).toEqual(null); cache2.clear(); // order 7 // VT1: 4 inserts and 1 lruRemove and 1 deleteRemove / VT2: 1 insert and 1 clearRemove expect(changeObject).toEqual(null); }); expect(changeObject.valueTypes.has(VALUE_TYPE_1)).toBeTruthy(); expect(changeObject.valueTypes.has(VALUE_TYPE_2)).toBeTruthy(); expect(changeObject[VALUE_TYPE_1].clearRemoves.length).toEqual(0); expect(changeObject[VALUE_TYPE_2].clearRemoves.length).toEqual(1); expect(changeObject[VALUE_TYPE_1].deleteRemoves.length).toEqual(1); expect(changeObject[VALUE_TYPE_2].deleteRemoves.length).toEqual(0); expect(changeObject[VALUE_TYPE_1].lruRemoves.length).toEqual(1); expect(changeObject[VALUE_TYPE_2].lruRemoves.length).toEqual(0); expect(changeObject[VALUE_TYPE_1].inserts.length).toEqual(4); expect(changeObject[VALUE_TYPE_2].inserts.length).toEqual(1); expect(changeObject[VALUE_TYPE_1].inserts[0].order).toEqual(0); expect(changeObject[VALUE_TYPE_1].inserts[1].order).toEqual(1); expect(changeObject[VALUE_TYPE_1].inserts[2].order).toEqual(2); expect(changeObject[VALUE_TYPE_1].inserts[3].order).toEqual(3); expect(changeObject[VALUE_TYPE_1].lruRemoves[0].order).toEqual(4); expect(changeObject[VALUE_TYPE_2].inserts[0].order).toEqual(5); expect(changeObject[VALUE_TYPE_1].deleteRemoves[0].order).toEqual(6); expect(changeObject[VALUE_TYPE_2].clearRemoves[0].order).toEqual(7); expect(changeObject[VALUE_TYPE_1].inserts[1].value).toEqual("value2 of type 1"); expect(changeObject[VALUE_TYPE_1].inserts[2].value).toEqual("value2 of type 1 modified"); changeObject = null; handlerHandle.unregister(); handlerHandle = registerCacheChangedHandler(changes => { changeObject = changes; }, [VALUE_TYPE_2]); cache1.set({ key: "key2", // order 2 value: "value2 of type 1 modified again", }); expect(changeObject).toEqual(null); // handler listens only to VT2 changes involved cache2.set({ key: "key2", // order 2 value: "value2 of type 2 modified again", }); expect(changeObject[VALUE_TYPE_2].inserts.length).toEqual(1); handlerHandle.unregister(); cache1.clear(); cache2.clear(); }); it("should handle a promise instead of a callback", async () => { const cache1 = getCache(VALUE_TYPE_1); cache1.setMaxSize(2); let changeObject = null; const handlerHandle = registerCacheChangedHandler(changes => { changeObject = changes; }, [VALUE_TYPE_1]); const promise = new Promise(resolve => { setTimeout(() => { cache1.setAll([ { key: "key1", // order 0 value: "value1 of type 1", alternateKeys: "myAltKey1", }, { key: "key2", // order 1 value: "value2 of type 1", alternateKeys: "myAltKey2", }, ]); expect(changeObject).toEqual(null); resolve(); }, 0) }); cacheTransaction(promise); expect(changeObject).toEqual(null); await promise; expect(changeObject.valueTypes.has(VALUE_TYPE_1)).toBeTruthy(); expect(changeObject[VALUE_TYPE_1].inserts.length).toEqual(2); handlerHandle.unregister(); cache1.clear(); }); });