import { describe, expect, test } from "bun:test"; import { HINT_STORAGE_KEY, PROMPT_TOKEN_BUDGET, fitHintToBudget, loadHint, normalizeHint, saveHint, } from "../src/lib/hint"; /** One "token" per UTF-16 unit: deterministic, monotone, easy to reason about. */ const charEncode = (text: string) => [...text].map((c) => c.charCodeAt(0)); const charDecode = (ids: number[]) => String.fromCharCode(...ids); function mapStorage(initial: Record = {}) { const map = new Map(Object.entries(initial)); return { getItem: (key: string) => map.get(key) ?? null, setItem: (key: string, value: string) => void map.set(key, value), map, }; } const throwingStorage = { getItem(): string | null { throw new Error("denied"); }, setItem(): void { throw new Error("denied"); }, }; describe("normalizeHint", () => { test("one term per line becomes a comma-separated list", () => { expect(normalizeHint(" Maxwell\n\n Voices of the Void \n")).toBe( "Maxwell, Voices of the Void", ); }); test("a comma-separated list is unchanged", () => { expect(normalizeHint("Maxwell, Voices of the Void")).toBe("Maxwell, Voices of the Void"); }); test("control characters are removed and whitespace-only means no hint", () => { expect(normalizeHint("Max\u0000well\u0007")).toBe("Maxwell"); expect(normalizeHint(" \n\t \r\n")).toBe(""); }); test("Windows line endings and tabs are handled", () => { expect(normalizeHint("Århus\r\nÆrø\tFestuge")).toBe("Århus, Ærø Festuge"); }); }); describe("hint storage", () => { test("round-trips through storage", () => { const storage = mapStorage(); expect(loadHint(storage)).toBe(""); saveHint("Maxwell\nVoid", storage); expect(storage.map.get(HINT_STORAGE_KEY)).toBe("Maxwell\nVoid"); expect(loadHint(storage)).toBe("Maxwell\nVoid"); }); test("unavailable storage never throws", () => { expect(loadHint(throwingStorage)).toBe(""); expect(() => saveHint("x", throwingStorage)).not.toThrow(); expect(loadHint(null)).toBe(""); }); }); describe("fitHintToBudget (D-WS2a truncation rule)", () => { test("empty hint means no prompt", () => { expect(fitHintToBudget(" \n", charEncode)).toEqual([]); }); test("a hint within budget is encoded as a space-led sentence ending in a full stop", () => { // Measured (D-WS2a): an unterminated list prompt suppresses punctuation // and triggers repetition loops on smaller models; a full stop does not. expect(fitHintToBudget("Maxwell\nVoices of the Void", charEncode)).toEqual( charEncode(" Maxwell, Voices of the Void."), ); }); test("a hint already ending in . ? or ! gets no extra full stop", () => { expect(charDecode(fitHintToBudget("Is it Maxwell?", charEncode))).toBe(" Is it Maxwell?"); expect(charDecode(fitHintToBudget("Dr. Bao.", charEncode))).toBe(" Dr. Bao."); }); test("over budget: keeps whole leading words, drops a dangling comma, ends in a full stop", () => { expect(charDecode(fitHintToBudget("alpha, beta gamma", charEncode, 10))).toBe(" alpha."); expect(charDecode(fitHintToBudget("alpha, beta gamma", charEncode, 13))).toBe(" alpha, beta."); }); test("a single word longer than the budget is cut at the budget", () => { const ids = fitHintToBudget("x".repeat(50), charEncode, 10); expect(ids).toEqual(charEncode(" xxxxxxxxx")); expect(ids.length).toBe(10); }); test("a very long hint fits the 223-token budget and is a word prefix of the hint", () => { const words = Array.from({ length: 5000 }, (_, i) => `term${i}`); const hint = words.join(", "); const ids = fitHintToBudget(hint, charEncode); expect(PROMPT_TOKEN_BUDGET).toBe(223); expect(ids.length).toBeLessThanOrEqual(223); expect(ids.length).toBeGreaterThan(200); const kept = charDecode(ids); // Ends on a whole term plus the full stop: the hint continues with a separator there. expect(kept.endsWith(".")).toBe(true); const body = kept.slice(0, -1); expect(` ${hint}`.startsWith(body)).toBe(true); expect([",", " "]).toContain(` ${hint}`.charAt(body.length)); }); test("is deterministic", () => { const hint = "Maxwell, ".repeat(200); expect(fitHintToBudget(hint, charEncode)).toEqual(fitHintToBudget(hint, charEncode)); }); });