UNPKG

feeds-fun

Version:

Frontend for the Feeds Fun — web-based news reader

150 lines (131 loc) 6.82 kB
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"'); }); });