logancyang_obsidian-copilot/src/utils/sliceCache.test.ts
2026-05-23 01:06:16 -07:00

97 lines
3 KiB
TypeScript

import { frozenOr, sliceMemo, sliceMemoByKey } from "./sliceCache";
describe("sliceMemo", () => {
it("returns the same value reference when source reference is unchanged", () => {
const compute = jest.fn((src: { rows: number[] }) => src.rows.map((n) => n * 2));
const memo = sliceMemo(compute);
const source = { rows: [1, 2, 3] };
const a = memo(source);
const b = memo(source);
expect(a).toBe(b);
expect(compute).toHaveBeenCalledTimes(1);
});
it("recomputes when the source reference changes, even if contents are equal", () => {
const compute = jest.fn((src: { rows: number[] }) => [...src.rows]);
const memo = sliceMemo(compute);
const a = memo({ rows: [1, 2, 3] });
const b = memo({ rows: [1, 2, 3] });
expect(a).not.toBe(b);
expect(compute).toHaveBeenCalledTimes(2);
});
});
describe("sliceMemoByKey", () => {
it("caches independently per key and reuses on source-reference match", () => {
const compute = jest.fn((src: Record<string, number>, key: string) => src[key] ?? 0);
const memo = sliceMemoByKey(compute);
const source = { a: 1, b: 2 };
const a1 = memo(source, "a");
const b1 = memo(source, "b");
const a2 = memo(source, "a");
expect(a1).toBe(a2);
expect(a1).toBe(1);
expect(b1).toBe(2);
expect(compute).toHaveBeenCalledTimes(2);
});
it("recomputes a key when source reference changes", () => {
const compute = jest.fn((src: { rows: number[] }, key: number) => src.rows[key]);
const memo = sliceMemoByKey(compute);
const v1 = memo({ rows: [10, 20] }, 0);
const v2 = memo({ rows: [10, 20] }, 0);
expect(v1).toBe(10);
expect(v2).toBe(10);
expect(compute).toHaveBeenCalledTimes(2);
});
it("invalidates all keys when the source reference flips", () => {
const compute = jest.fn((src: Record<string, string[]>, key: string) => src[key] ?? []);
const memo = sliceMemoByKey(compute);
const src1 = { x: ["a"], y: ["b"] };
memo(src1, "x");
memo(src1, "y");
expect(compute).toHaveBeenCalledTimes(2);
const src2 = { x: ["a"], y: ["b"] };
memo(src2, "x");
memo(src2, "y");
expect(compute).toHaveBeenCalledTimes(4);
});
});
describe("frozenOr", () => {
it("returns the provided empty constant when input is empty", () => {
const EMPTY: readonly number[] = Object.freeze([]);
expect(frozenOr([], EMPTY)).toBe(EMPTY);
});
it("returns a frozen copy (not the same reference) when input is non-empty", () => {
const EMPTY: readonly number[] = Object.freeze([]);
const input = [1, 2, 3];
const result = frozenOr(input, EMPTY);
expect(result).not.toBe(input);
expect(result).toEqual([1, 2, 3]);
expect(Object.isFrozen(result)).toBe(true);
});
it("does not mutate or freeze the input array", () => {
const EMPTY: readonly number[] = Object.freeze([]);
const input = [1, 2, 3];
frozenOr(input, EMPTY);
expect(Object.isFrozen(input)).toBe(false);
input.push(4);
expect(input).toEqual([1, 2, 3, 4]);
});
});