import { describe, expect, it } from "vitest"; import { atomHtmlToText, atomTextToHtml } from "./atom-html.js"; describe("atom ↔ HTML bridge (T-148 web)", () => { it("escapes untrusted text and renders atoms as spans", () => { const html = atomTextToHtml('a & @status(done) b'); expect(html).toContain("a <img src=x onerror=alert(1)> & "); // no executable markup from the input survives expect(html).not.toContain("done'); }); it("escapes label values (XSS vector) in the span text", () => { // note: label values may not contain parens (token grammar limit) const html = atomTextToHtml("@label()") expect(html).toContain( ' { const original = "Due @date(2026-10-11), @status(in-progress), @label(ops team)"; const html = atomTextToHtml(original); expect(atomHtmlToText(html)).toBe(original); }); it("returns null for atom-less HTML (caller keeps original content)", () => { expect(atomHtmlToText("

plain bold

")).toBeNull(); expect(atomHtmlToText("")).toBeNull(); }); it("strips formatting to plain text; atom-less HTML is left untouched", () => { const html = '

one done

'; expect(atomHtmlToText(html)).toBe("one @status(done)"); // plain formatting HTML has no atoms → null (caller keeps original content) expect(atomHtmlToText("

two bold

")).toBeNull(); }); it("keeps an unknown status value as plain text (no forged token)", () => { const html = 'maybe'; expect(atomHtmlToText(html)).toBe("maybe"); }); it("unescapes entity-escaped values in the token", () => { const html = 'a"b'; expect(atomHtmlToText(html)).toBe("@label(a\"b)"); }); });