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)");
});
});