feeds-fun
Version:
Frontend for the Feeds Fun — web-based news reader
150 lines (131 loc) • 6.82 kB
text/typescript
import {afterEach, describe, expect, it, vi} from "vitest";
import DOMPurify from "dompurify";
import {purifyBody, purifyTitle} from "@/logic/utils";
afterEach(() => {
vi.restoreAllMocks();
});
describe("purifyBody", () => {
it("removes styling and page behavior attributes", () => {
const raw = `
<p aria-label="Summary" class="lead" data-id="42" dir="rtl" id="entry"
lang="en" onclick="alert()" slot="main" style="color: red" title="Title">Body</p>
<a href="https://example.com" class="link" download ping="https://tracker.example"
rel="nofollow" target="_self" type="text/html">Link</a>
<img alt="Chart" height="100" src="https://example.com/chart.png"
sizes="(max-width: 600px) 100vw, 600px"
srcset="https://example.com/chart-2x.png 2x" style="width: 100px"
width="100" />
<video controls poster="https://example.com/poster.png" style="width: 100%">
<source media="(min-width: 800px)" src="https://example.com/video.mp4"
type="video/mp4" />
<track default kind="captions" label="English"
src="https://example.com/captions.vtt" srclang="en" />
</video>
<table><tr><th colspan="2" scope="col" style="color:red">Head</th></tr></table>
<ol reversed start="5" type="A"><li value="7">Item</li></ol>
<time datetime="2026-04-27">Today</time>
<svg viewBox="0 0 10 10">
<path d="M0 0h10v10H0z" fill="red" stroke="blue"></path>
</svg>
`;
const purified = purifyBody({raw, default_: "No description"});
expect(purified).toContain('aria-label="Summary"');
expect(purified).toContain('dir="rtl"');
expect(purified).toContain('href="https://example.com"');
expect(purified).toContain('lang="en"');
expect(purified).toContain('src="https://example.com/chart.png"');
expect(purified).toContain('sizes="(max-width: 600px) 100vw, 600px"');
expect(purified).toContain('srcset="https://example.com/chart-2x.png 2x"');
expect(purified).toContain('alt="Chart"');
expect(purified).toContain('title="Title"');
expect(purified).toContain("controls");
expect(purified).toContain('poster="https://example.com/poster.png"');
expect(purified).toContain('media="(min-width: 800px)"');
expect(purified).toContain('src="https://example.com/video.mp4"');
expect(purified).toContain('type="video/mp4"');
expect(purified).toContain('slot="main"');
expect(purified).toContain("default");
expect(purified).toContain('kind="captions"');
expect(purified).toContain('label="English"');
expect(purified).toContain('src="https://example.com/captions.vtt"');
expect(purified).toContain('srclang="en"');
expect(purified).toContain('colspan="2"');
expect(purified).toContain('datetime="2026-04-27"');
expect(purified).toContain("download");
expect(purified).toContain('scope="col"');
expect(purified).toContain("reversed");
expect(purified).toContain('start="5"');
expect(purified).toContain('type="A"');
expect(purified).toContain('viewBox="0 0 10 10"');
expect(purified).toContain('d="M0 0h10v10H0z"');
expect(purified).toContain('fill="red"');
expect(purified).toContain('stroke="blue"');
expect(purified).toContain('value="7"');
expect(purified).not.toContain('class="');
expect(purified).not.toContain('data-id="');
expect(purified).not.toContain('id="');
expect(purified).not.toContain('onclick="');
expect(purified).not.toContain('ping="');
expect(purified).not.toContain('style="');
});
it("sets required security attributes for links", () => {
const raw = '<a href="https://example.com" target="_self" rel="noopener" referrerpolicy="unsafe-url">Example</a>';
const purified = purifyBody({raw, default_: "No description"});
expect(purified).toContain('href="https://example.com"');
expect(purified).toContain('target="_blank"');
expect(purified).toContain('rel="noopener noreferrer nofollow"');
expect(purified).toContain('referrerpolicy="strict-origin-when-cross-origin"');
});
it("keeps sanitized allowlisted iframes", () => {
const raw = `
<iframe
src="https://www.youtube.com/embed/video-id"
width="560"
height="315"
title="Video title"
class="video"
allow="camera"
sandbox="allow-top-navigation">
</iframe>
`;
const purified = purifyBody({raw, default_: "No description"});
const parsed = new DOMParser().parseFromString(purified, "text/html");
const iframe = parsed.body.querySelector("iframe");
expect(iframe?.getAttribute("src")).toBe("https://www.youtube-nocookie.com/embed/video-id");
expect(iframe?.getAttribute("title")).toBe("Video title");
expect(iframe?.getAttribute("loading")).toBe("lazy");
expect(iframe?.getAttribute("referrerpolicy")).toBe("strict-origin-when-cross-origin");
expect(iframe?.getAttribute("sandbox")).toBe(
"allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox"
);
expect(iframe?.getAttribute("allow")).toBe("fullscreen; picture-in-picture; encrypted-media");
expect(iframe?.hasAttribute("allowfullscreen")).toBe(true);
expect(iframe?.hasAttribute("width")).toBe(false);
expect(iframe?.hasAttribute("height")).toBe(false);
expect(iframe?.hasAttribute("class")).toBe(false);
});
it("strips srcdoc from sanitized iframes", () => {
const raw = '<iframe src="https://www.youtube-nocookie.com/embed/video-id" srcdoc="<p>HTML</p>"></iframe>';
const purified = purifyBody({raw, default_: "No description"});
const parsed = new DOMParser().parseFromString(purified, "text/html");
const iframe = parsed.body.querySelector("iframe");
expect(iframe?.getAttribute("src")).toBe("https://www.youtube-nocookie.com/embed/video-id");
expect(iframe?.hasAttribute("srcdoc")).toBe(false);
});
});
describe("purifyTitle", () => {
it("returns default value for null and empty values", () => {
expect(purifyTitle({raw: null, default_: "No title"})).toBe("No title");
expect(purifyTitle({raw: " ", default_: "No title"})).toBe("No title");
});
it("sets required security attributes for links in sanitized output", () => {
vi.spyOn(DOMPurify, "sanitize").mockReturnValue(
'<a href="https://example.com" target="_self" rel="noopener" referrerpolicy="unsafe-url">Example</a>'
);
const purified = purifyTitle({raw: "Example title", default_: "No title"});
expect(purified).toContain('href="https://example.com"');
expect(purified).toContain('target="_blank"');
expect(purified).toContain('rel="noopener noreferrer nofollow"');
expect(purified).toContain('referrerpolicy="strict-origin-when-cross-origin"');
});
});