import { describe, expect, it } from "vitest";
import { sanitizeHtml, stripInvisibleUnicode } from "./web-fetch-visibility.js";
describe("sanitizeHtml", () => {
it("strips display:none elements", async () => {
const html = '
Visible
Hidden
';
const result = await sanitizeHtml(html);
expect(result).toContain("Visible");
expect(result).not.toContain("Hidden");
});
it("strips visibility:hidden elements", async () => {
const html = 'Visible
Secret';
const result = await sanitizeHtml(html);
expect(result).not.toContain("Secret");
});
it("strips opacity:0 elements", async () => {
const html = 'Show
Invisible
';
const result = await sanitizeHtml(html);
expect(result).not.toContain("Invisible");
});
it("strips font-size:0 elements", async () => {
const html = 'Normal
Tiny';
const result = await sanitizeHtml(html);
expect(result).not.toContain("Tiny");
});
it("strips text-indent far-offscreen elements", async () => {
const html = 'Normal
Offscreen
';
const result = await sanitizeHtml(html);
expect(result).not.toContain("Offscreen");
});
it("strips color:transparent elements", async () => {
const html = 'Visible
Ghost
';
const result = await sanitizeHtml(html);
expect(result).not.toContain("Ghost");
});
it("strips color:rgba with zero alpha elements", async () => {
const html = 'Visible
Invisible
';
const result = await sanitizeHtml(html);
expect(result).not.toContain("Invisible");
});
it("strips color:rgba with zero decimal alpha elements", async () => {
const html = 'Visible
Invisible
';
const result = await sanitizeHtml(html);
expect(result).not.toContain("Invisible");
});
it("strips color:hsla with zero alpha elements", async () => {
const html = 'Visible
Invisible
';
const result = await sanitizeHtml(html);
expect(result).not.toContain("Invisible");
});
it("strips transform:scale(0) elements", async () => {
const html = 'Show
Scaled
';
const result = await sanitizeHtml(html);
expect(result).not.toContain("Scaled");
});
it("strips transform:translateX far-offscreen elements", async () => {
const html = 'Show
Translated
';
const result = await sanitizeHtml(html);
expect(result).not.toContain("Translated");
});
it("strips width:0 height:0 overflow:hidden elements", async () => {
const html = 'Show
Zero
';
const result = await sanitizeHtml(html);
expect(result).not.toContain("Zero");
});
it("strips left far-offscreen positioned elements", async () => {
const html = 'Show
Offscreen
';
const result = await sanitizeHtml(html);
expect(result).not.toContain("Offscreen");
});
it("strips clip-path:inset(100%) elements", async () => {
const html = 'Show
Clipped
';
const result = await sanitizeHtml(html);
expect(result).not.toContain("Clipped");
});
it("strips clip-path:inset(50%) elements", async () => {
const html = 'Show
Clipped
';
const result = await sanitizeHtml(html);
expect(result).not.toContain("Clipped");
});
it("does not strip clip-path:inset(0%) elements", async () => {
const html = 'Show
Visible
';
const result = await sanitizeHtml(html);
expect(result).toContain("Visible");
});
it("strips sr-only class elements", async () => {
const html = 'Main
Screen reader only';
const result = await sanitizeHtml(html);
expect(result).not.toContain("Screen reader only");
});
it("strips visually-hidden class elements", async () => {
const html = 'Main
Hidden visually';
const result = await sanitizeHtml(html);
expect(result).not.toContain("Hidden visually");
});
it("strips d-none class elements", async () => {
const html = 'Main
Bootstrap hidden
';
const result = await sanitizeHtml(html);
expect(result).not.toContain("Bootstrap hidden");
});
it("strips hidden class elements", async () => {
const html = 'Main
Class hidden
';
const result = await sanitizeHtml(html);
expect(result).not.toContain("Class hidden");
});
it("does not strip elements with hidden as substring of class name", async () => {
const html = 'Main
Should be visible
';
const result = await sanitizeHtml(html);
expect(result).toContain("Should be visible");
});
it("strips aria-hidden=true elements", async () => {
const html = 'Visible
Aria hidden
';
const result = await sanitizeHtml(html);
expect(result).not.toContain("Aria hidden");
});
it("strips elements with hidden attribute", async () => {
const html = "Visible
HTML hidden
";
const result = await sanitizeHtml(html);
expect(result).not.toContain("HTML hidden");
});
it("strips input type=hidden", async () => {
const html = '';
const result = await sanitizeHtml(html);
expect(result).not.toContain("csrf-token-secret");
});
it("strips HTML comments", async () => {
const html = "Visible
";
const result = await sanitizeHtml(html);
expect(result).not.toContain("inject");
expect(result).not.toContain("ignore previous instructions");
});
it("strips meta tags", async () => {
const html = 'Body
';
const result = await sanitizeHtml(html);
expect(result).not.toContain("prompt payload");
});
it("strips template tags", async () => {
const html = "Visible
Hidden template content";
const result = await sanitizeHtml(html);
expect(result).not.toContain("Hidden template content");
});
it("strips iframe tags", async () => {
const html = "Visible
";
const result = await sanitizeHtml(html);
expect(result).not.toContain("Iframe content");
});
it("preserves visible content", async () => {
const html = "Hello world
Title
Link";
const result = await sanitizeHtml(html);
expect(result).toContain("Hello world");
expect(result).toContain("Title");
});
it("handles nested hidden elements without removing visible siblings", async () => {
const html =
'Visible
HiddenAlso visible
';
const result = await sanitizeHtml(html);
expect(result).toContain("Visible");
expect(result).toContain("Also visible");
expect(result).not.toContain("Hidden");
});
it("handles malformed HTML gracefully", async () => {
const html = "Unclosed
Nested";
await expect(sanitizeHtml(html)).resolves.toBeDefined();
});
});
describe("stripInvisibleUnicode", () => {
it("strips zero-width space", () => {
const text = "Hello\u200BWorld";
expect(stripInvisibleUnicode(text)).toBe("HelloWorld");
});
it("strips zero-width non-joiner", () => {
const text = "Hello\u200CWorld";
expect(stripInvisibleUnicode(text)).toBe("HelloWorld");
});
it("strips zero-width joiner", () => {
const text = "Hello\u200DWorld";
expect(stripInvisibleUnicode(text)).toBe("HelloWorld");
});
it("strips left-to-right mark", () => {
const text = "Hello\u200EWorld";
expect(stripInvisibleUnicode(text)).toBe("HelloWorld");
});
it("strips right-to-left mark", () => {
const text = "Hello\u200FWorld";
expect(stripInvisibleUnicode(text)).toBe("HelloWorld");
});
it("strips directional overrides (LRO, RLO, PDF, etc.)", () => {
const text = "\u202AHello\u202E";
expect(stripInvisibleUnicode(text)).toBe("Hello");
});
it("strips word joiner and other formatting chars", () => {
const text = "Hello\u2060World\uFEFF";
expect(stripInvisibleUnicode(text)).toBe("HelloWorld");
});
it("preserves normal text unchanged", () => {
const text = "Hello, World! 123 \u00e9\u4e2d\u6587";
expect(stripInvisibleUnicode(text)).toBe(text);
});
it("strips multiple invisible chars in a row", () => {
const text = "A\u200B\u200C\u200D\u200E\u200FB";
expect(stripInvisibleUnicode(text)).toBe("AB");
});
it("handles empty string", () => {
expect(stripInvisibleUnicode("")).toBe("");
});
});