import { describe, expect, it } from "vitest"; import { cleanTextForMeta, extractFirstUserPrompt, renderableThumbnailText, } from "./sharePreviewText"; import type { Message } from "$lib/types/Message"; // Non-ASCII inputs are built from codepoints to keep this file ASCII-only const cp = (...codepoints: number[]) => String.fromCodePoint(...codepoints); const CJK_POEM = cp(0x5e2e, 0x6211, 0x5199, 0x4e00, 0x9996, 0x8bd7); // "help me write a poem" const ARABIC = cp(0x0627, 0x0643, 0x062a, 0x0628, 0x0644, 0x064a, 0x0642, 0x0635, 0x0629); const PARTY_POPPER = cp(0x1f389); const RLO = cp(0x202e); // right-to-left override const PDF_CHAR = cp(0x202c); // pop directional formatting const ZWSP = cp(0x200b); const ELLIPSIS = cp(0x2026); describe("cleanTextForMeta", () => { it("collapses whitespace and newlines", () => { expect(cleanTextForMeta("a\n\nb\t c\r\nd", 100)).toBe("a b c d"); }); it("strips control characters and bidi overrides", () => { expect(cleanTextForMeta(`hello ${RLO}evil${PDF_CHAR} world${cp(0)}!`, 100)).toBe( "hello evil world !" ); }); it("strips zero-width characters", () => { expect(cleanTextForMeta(`zero${ZWSP}${ZWSP}width`, 100)).toBe("zero width"); }); it("keeps non-Latin scripts (for og: metadata)", () => { expect(cleanTextForMeta(CJK_POEM, 100)).toBe(CJK_POEM); }); it("truncates on a word boundary with an ellipsis", () => { const out = cleanTextForMeta("word ".repeat(60), 50); expect(out.length).toBeLessThanOrEqual(51); expect(out.endsWith(ELLIPSIS)).toBe(true); expect(out).not.toContain("wor" + ELLIPSIS); }); it("applies NFC normalization", () => { // "e" + combining acute accent becomes precomposed "é" expect(cleanTextForMeta("caf" + cp(0x65, 0x0301), 100)).toBe("caf" + cp(0xe9)); }); it("returns empty string for whitespace-only input", () => { expect(cleanTextForMeta(" \n\t ", 100)).toBe(""); }); }); describe("renderableThumbnailText", () => { it("keeps plain English prompts", () => { expect(renderableThumbnailText("How do I write a Python script?", 240)).toBe( "How do I write a Python script?" ); }); it("keeps HTML-looking text as literal text", () => { const input = ' & "quotes" bold'; expect(renderableThumbnailText(input, 240)).toBe(input); }); it("strips emoji", () => { expect(renderableThumbnailText(`Plan a party ${PARTY_POPPER} for my daughter`, 240)).toBe( "Plan a party for my daughter" ); }); it("returns empty string for text in non-renderable scripts", () => { expect(renderableThumbnailText(CJK_POEM, 240)).toBe(""); expect(renderableThumbnailText(ARABIC, 240)).toBe(""); }); it("replaces non-renderable runs in mixed text with one ellipsis", () => { expect(renderableThumbnailText(`Translate ${CJK_POEM} to English for me please`, 240)).toBe( `Translate ${ELLIPSIS} to English for me please` ); }); it("keeps Latin-extended and Cyrillic scripts", () => { const vietnamese = `Vi${cp(0x1ebf)}t cho t${cp(0xf4)}i about spring please`; expect(renderableThumbnailText(vietnamese, 240)).toBe(vietnamese); const russian = cp(0x041d, 0x0430, 0x043f, 0x0438, 0x0448, 0x0438) + " please my friend"; expect(renderableThumbnailText(russian, 240)).toBe(russian); }); it("returns empty string when too little content remains", () => { expect(renderableThumbnailText("", 240)).toBe(""); expect(renderableThumbnailText(" ", 240)).toBe(""); expect(renderableThumbnailText("ok!", 240)).toBe(""); }); it("caps length", () => { const out = renderableThumbnailText("x".repeat(1000), 240); expect(out.length).toBeLessThanOrEqual(241); }); }); describe("extractFirstUserPrompt", () => { const msg = ( id: string, from: Message["from"], content: string, children: string[] = [] ): Pick => ({ id, from, content, children }); it("walks past the system root to the first user message", () => { const messages = [ msg("sys", "system", "preprompt", ["u1"]), msg("u1", "user", "first prompt", ["a1"]), msg("a1", "assistant", "answer"), ]; expect(extractFirstUserPrompt(messages, "sys")).toBe("first prompt"); }); it("follows the first child branch", () => { const messages = [ msg("sys", "system", "", ["u1", "u2"]), msg("u1", "user", "original prompt"), msg("u2", "user", "edited prompt"), ]; expect(extractFirstUserPrompt(messages, "sys")).toBe("original prompt"); }); it("falls back to array order without a root id", () => { const messages = [msg("a1", "assistant", "hi"), msg("u1", "user", "the prompt")]; expect(extractFirstUserPrompt(messages)).toBe("the prompt"); }); it("skips user messages with empty content", () => { const messages = [ msg("sys", "system", "", ["u1"]), msg("u1", "user", " ", ["u2"]), msg("u2", "user", "real prompt"), ]; expect(extractFirstUserPrompt(messages, "sys")).toBe("real prompt"); }); it("terminates on cyclic trees", () => { const messages = [msg("a", "assistant", "x", ["b"]), msg("b", "assistant", "y", ["a"])]; expect(extractFirstUserPrompt(messages, "a")).toBe(""); }); it("returns empty string for empty conversations", () => { expect(extractFirstUserPrompt([])).toBe(""); }); });