/* Copyright 2024 New Vector Ltd. Copyright 2023 The Matrix.org Foundation C.I.C. SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only Please see LICENSE files in the repository root for full details. */ import { logger } from "matrix-js-sdk/src/logger"; import { LruCache } from "../../../src/utils/LruCache"; describe("LruCache", () => { it("when creating a cache with negative capacity it should raise an error", () => { expect(() => { new LruCache(-23); }).toThrow("Cache capacity must be at least 1"); }); it("when creating a cache with 0 capacity it should raise an error", () => { expect(() => { new LruCache(0); }).toThrow("Cache capacity must be at least 1"); }); describe("when there is a cache with a capacity of 3", () => { let cache: LruCache; beforeEach(() => { cache = new LruCache(3); }); it("has() should return false", () => { expect(cache.has("a")).toBe(false); }); it("get() should return undefined", () => { expect(cache.get("a")).toBeUndefined(); }); it("values() should return an empty iterator", () => { 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"); cache.set("b", "b value"); }); it("has() should return false for an item not in the cache", () => { expect(cache.has("c")).toBe(false); }); it("get() should return undefined for an item not in the cahce", () => { expect(cache.get("c")).toBeUndefined(); }); it("values() should return the items in the cache", () => { expect(Array.from(cache.values())).toEqual(["a value", "b value"]); }); it("clear() should clear the cache", () => { cache.clear(); expect(cache.has("a")).toBe(false); expect(cache.has("b")).toBe(false); expect(Array.from(cache.values())).toEqual([]); }); it("when an error occurs while setting an item the cache should be cleard", () => { jest.spyOn(logger, "warn"); const err = new Error("Something weng wrong :("); // @ts-ignore cache.safeSet = () => { throw err; }; cache.set("c", "c value"); expect(Array.from(cache.values())).toEqual([]); expect(logger.warn).toHaveBeenCalledWith("LruCache error", err); }); describe("and adding another item", () => { beforeEach(() => { 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"]); // 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("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("deleting the last item should work", () => { cache.delete("c"); expect(Array.from(cache.values())).toEqual(["a value", "b value"]); // add an item after delete should work work cache.set("d", "d value"); expect(Array.from(cache.values())).toEqual(["a value", "b value", "d value"]); }); 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"); 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("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"]); }); 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(); }); }); 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"]); }); }); }); }); describe("when the cache contains some items where one of them is a replacement", () => { beforeEach(() => { cache.set("a", "a value"); cache.set("b", "b value"); cache.set("c", "c value"); cache.set("a", "a value 2"); cache.set("d", "d value"); }); it("should contain the last recently set items", () => { expect(cache.has("a")).toBe(true); expect(cache.get("a")).toEqual("a value 2"); 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 2", "c value", "d value"]); }); }); }); });