import { describe, expect, it } from "vitest"; import { buildMarkerSignature, byteLength, compactHtml, markerOf, signatureTooLong, SIGNATURE_LIMIT } from "../signatureHtml"; describe("signature compaction", () => { it("strips office cruft and non-essential styles but keeps colors and links", () => { const src = `

John Coffey

linuxexpert.org
`; const out = compactHtml(src); expect(out).not.toContain("mso-"); expect(out).not.toContain("class="); expect(out).not.toContain("John Coffey"); expect(out).toContain('href="https://linuxexpert.org"'); expect(out).toContain('width="100"'); expect(out.length).toBeLessThan(src.length / 2); }); it("builds marker signatures within the limit", () => { const big = `
${"x".repeat(1000)}
`; const m = buildMarkerSignature("blob123", big); expect(m.htmlSignature.length).toBeLessThanOrEqual(SIGNATURE_LIMIT); expect(m.textSignature.length).toBeLessThanOrEqual(SIGNATURE_LIMIT); expect(markerOf(m.htmlSignature)).toEqual({ blobId: "blob123", type: "text/html" }); expect(markerOf("
plain
")).toBeNull(); }); }); /** * Stalwart's cap is `value.len() < 2048` on a Rust string — 2047 bytes of * UTF-8. Measuring with JavaScript's `.length` counts UTF-16 units instead, * which agrees only for ASCII: an accent is one unit and two bytes, CJK three, * an emoji two units and four. Every check has to weigh the encoded form or a * signature we judged to fit comes back rejected. */ describe("signature size is measured in bytes", () => { const sigOf = (html: string) => buildMarkerSignature("blob123", html); it("counts multi-byte characters at their encoded size", () => { expect(byteLength("hello")).toBe(5); expect(byteLength("Grüße")).toBe(7); // two 2-byte characters expect(byteLength("日本語")).toBe(9); // three 3-byte characters expect(byteLength("🎉")).toBe(4); // one 4-byte character, two UTF-16 units }); it("spots a signature that fits in characters but not in bytes", () => { // Comfortably under the limit counted as characters, well over it as bytes. const cjk = "日".repeat(1200); expect(cjk.length).toBeLessThan(SIGNATURE_LIMIT); expect(signatureTooLong(cjk, cjk)).toBe(true); }); it("keeps a marker signature within the byte limit for non-ASCII text", () => { for (const filler of ["ü", "日", "🎉", "x"]) { const m = sigOf(`
${filler.repeat(3000)}
`); expect(byteLength(m.htmlSignature), `html for ${filler}`).toBeLessThanOrEqual(SIGNATURE_LIMIT); expect(byteLength(m.textSignature), `text for ${filler}`).toBeLessThanOrEqual(SIGNATURE_LIMIT); expect(markerOf(m.htmlSignature)).toEqual({ blobId: "blob123", type: "text/html" }); } }); it("never truncates through a surrogate pair", () => { const m = sigOf(`
${"🎉".repeat(3000)}
`); // A split pair leaves a lone surrogate, which encodes as U+FFFD. expect(m.htmlSignature).not.toContain("�"); expect(m.textSignature).not.toContain("�"); expect(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])/.test(m.textSignature)).toBe(false); }); it("never truncates through an HTML entity", () => { // Escaping turns each of these into a 5-character entity; cutting the // rendered string could leave "&am" behind. const m = sigOf(`
${"a & b ".repeat(400)}
`); expect(m.htmlSignature).not.toMatch(/&[a-z]*$/i); expect(m.htmlSignature.replace(/&(amp|lt|gt|quot|#39);/g, "")).not.toContain("&"); }); it("leaves a signature that already fits completely alone", () => { const m = sigOf("
Grüße, John
"); expect(m.textSignature).toBe("Grüße, John"); expect(m.htmlSignature).not.toContain("…"); }); });