diff --git a/src/utils/LruCache.ts b/src/utils/LruCache.ts index 39f84e3fa6b2..466454b15a4c 100644 --- a/src/utils/LruCache.ts +++ b/src/utils/LruCache.ts @@ -93,7 +93,7 @@ export class LruCache { return; } - const newItem = { + const newItem: CacheItem = { key, value, next: null, @@ -106,21 +106,14 @@ export class LruCache { newItem.next = this.head; } - this.head = newItem; - - if (!this.tail) { - // This is the first item added to the list. Also set it as tail. - this.tail = newItem; - } + this.setHeadTail(newItem); // Store item in lookup map. this.map.set(key, newItem); if (this.map.size > this.capacity) { // Map size exceeded cache capcity. Drop tail item. - this.map.delete(this.tail.key); - this.tail = this.tail.prev; - this.tail.next = null; + this.delete(this.tail.key); } } @@ -159,15 +152,33 @@ export class LruCache { if (item === this.head) return item; this.removeItemFromList(item); - // …and put it to the front. - this.head.prev = item; + + // Put item to the front. + + if (this.head) { + this.head.prev = item; + } + item.prev = null; item.next = this.head; - this.head = item; + + this.setHeadTail(item); return item; } + private setHeadTail(item: CacheItem): void { + if (item.prev === null) { + // Item has no previous item → head + this.head = item; + } + + if (item.next === null) { + // Item has no next item → tail + this.tail = item; + } + } + private removeItemFromList(item: CacheItem): void { if (item === this.head) { this.head = item.next ?? null; diff --git a/test/utils/LruCache-test.ts b/test/utils/LruCache-test.ts index 5f0dff8c5ca1..7c56d0289dfd 100644 --- a/test/utils/LruCache-test.ts +++ b/test/utils/LruCache-test.ts @@ -48,6 +48,10 @@ describe("LruCache", () => { expect(Array.from(cache.values())).toEqual([]); }); + it("delete() should not raise an error", () => { + cache.delete("a"); + }); + describe("when the cache contains 2 items", () => { beforeEach(() => { cache.set("a", "a value"); @@ -65,94 +69,120 @@ describe("LruCache", () => { it("values() should return the items in the cache", () => { expect(Array.from(cache.values())).toEqual(["a value", "b value"]); }); - }); - - describe("when the cache contains 3 items", () => { - beforeEach(() => { - cache.set("a", "a value"); - cache.set("b", "b value"); - cache.set("c", "c value"); - }); - it("deleting an unkonwn item should not raise an error", () => { - cache.delete("unknown"); - }); - - it("deleting the first item should work", () => { - cache.delete("a"); - expect(Array.from(cache.values())).toEqual(["b value", "c value"]); - }); - - it("deleting the item in the middle should work", () => { - cache.delete("b"); - expect(Array.from(cache.values())).toEqual(["a value", "c value"]); - }); + describe("and adding another item", () => { + beforeEach(() => { + cache.set("c", "c value"); + }); - it("deleting the last item should work", () => { - cache.delete("c"); - expect(Array.from(cache.values())).toEqual(["a value", "b value"]); - }); + it("deleting an unkonwn item should not raise an error", () => { + cache.delete("unknown"); + }); - it("deleting and adding some items should work", () => { - cache.set("d", "d value"); - cache.get("b"); - cache.delete("b"); - cache.set("e", "e value"); - expect(Array.from(cache.values())).toEqual(["c value", "d value", "e value"]); - }); + it("deleting the first item should work", () => { + cache.delete("a"); + expect(Array.from(cache.values())).toEqual(["b value", "c value"]); - describe("and accesing the first added item and adding another item", () => { - beforeEach(() => { - cache.get("a"); + // add an item after delete should work work cache.set("d", "d value"); + expect(Array.from(cache.values())).toEqual(["b value", "c value", "d value"]); }); - it("should contain the last recently accessed items", () => { - expect(cache.has("a")).toBe(true); - expect(cache.get("a")).toEqual("a value"); - expect(cache.has("c")).toBe(true); - expect(cache.get("c")).toEqual("c value"); - expect(cache.has("d")).toBe(true); - expect(cache.get("d")).toEqual("d value"); + it("deleting the item in the middle should work", () => { + cache.delete("b"); + expect(Array.from(cache.values())).toEqual(["a value", "c value"]); + + // add an item after delete should work work + cache.set("d", "d value"); expect(Array.from(cache.values())).toEqual(["a value", "c value", "d value"]); }); - it("should not contain the least recently accessed items", () => { - expect(cache.has("b")).toBe(false); - expect(cache.get("b")).toBeUndefined(); - }); - }); + it("deleting the last item should work", () => { + cache.delete("c"); + expect(Array.from(cache.values())).toEqual(["a value", "b value"]); - describe("and adding 2 additional items", () => { - beforeEach(() => { + // add an item after delete should work work cache.set("d", "d value"); - cache.set("e", "e value"); + expect(Array.from(cache.values())).toEqual(["a value", "b value", "d value"]); }); - it("has() should return false for expired items", () => { - expect(cache.has("a")).toBe(false); - expect(cache.has("b")).toBe(false); - }); + it("deleting all items should work", () => { + cache.delete("a"); + cache.delete("b"); + cache.delete("c"); + // should not raise an error + cache.delete("a"); + cache.delete("b"); + cache.delete("c"); - it("has() should return true for items in the caceh", () => { - expect(cache.has("c")).toBe(true); - expect(cache.has("d")).toBe(true); - expect(cache.has("e")).toBe(true); + expect(Array.from(cache.values())).toEqual([]); + + // add an item after delete should work work + cache.set("d", "d value"); + expect(Array.from(cache.values())).toEqual(["d value"]); }); - it("get() should return undefined for expired items", () => { - expect(cache.get("a")).toBeUndefined(); - expect(cache.get("b")).toBeUndefined(); + it("deleting and adding some items should work", () => { + cache.set("d", "d value"); + cache.get("b"); + cache.delete("b"); + cache.set("e", "e value"); + expect(Array.from(cache.values())).toEqual(["c value", "d value", "e value"]); }); - it("get() should return the items in the cache", () => { - expect(cache.get("c")).toBe("c value"); - expect(cache.get("d")).toBe("d value"); - expect(cache.get("e")).toBe("e value"); + describe("and accesing the first added item and adding another item", () => { + beforeEach(() => { + cache.get("a"); + cache.set("d", "d value"); + }); + + it("should contain the last recently accessed items", () => { + expect(cache.has("a")).toBe(true); + expect(cache.get("a")).toEqual("a value"); + expect(cache.has("c")).toBe(true); + expect(cache.get("c")).toEqual("c value"); + expect(cache.has("d")).toBe(true); + expect(cache.get("d")).toEqual("d value"); + expect(Array.from(cache.values())).toEqual(["a value", "c value", "d value"]); + }); + + it("should not contain the least recently accessed items", () => { + expect(cache.has("b")).toBe(false); + expect(cache.get("b")).toBeUndefined(); + }); }); - it("values() should return the items in the cache", () => { - expect(Array.from(cache.values())).toEqual(["c value", "d value", "e value"]); + describe("and adding 2 additional items", () => { + beforeEach(() => { + cache.set("d", "d value"); + cache.set("e", "e value"); + }); + + it("has() should return false for expired items", () => { + expect(cache.has("a")).toBe(false); + expect(cache.has("b")).toBe(false); + }); + + it("has() should return true for items in the caceh", () => { + expect(cache.has("c")).toBe(true); + expect(cache.has("d")).toBe(true); + expect(cache.has("e")).toBe(true); + }); + + it("get() should return undefined for expired items", () => { + expect(cache.get("a")).toBeUndefined(); + expect(cache.get("b")).toBeUndefined(); + }); + + it("get() should return the items in the cache", () => { + expect(cache.get("c")).toBe("c value"); + expect(cache.get("d")).toBe("d value"); + expect(cache.get("e")).toBe("e value"); + }); + + it("values() should return the items in the cache", () => { + expect(Array.from(cache.values())).toEqual(["c value", "d value", "e value"]); + }); }); }); });