import { describe, expect, it } from "vitest"; import { httpUrl, parseFeed } from "./utils"; const rss = ` Example https://example.com/ <![CDATA[Tom & Jerry]]> https://example.com/one Tue, 22 Sep 2026 10:00:00 GMT Enclosure image https://example.com/two 2026-09-21T10:00:00Z Audio only javascript:alert(1) abc-123 No title, skipped `; const atom = ` Example Fish & chips 2026-09-22T12:00:00Z YouTube style 2026-09-20T12:00:00Z not a date `; describe("widgets/feed/utils", () => { it("parses rss 2.0 items", () => { expect(parseFeed(rss)).toEqual([ { title: "Tom & Jerry", link: "https://example.com/one", date: "2026-09-22T10:00:00.000Z", image: "https://img.example.com/one.jpg", }, { title: "Enclosure image", link: "https://example.com/two", date: "2026-09-21T10:00:00.000Z", image: "https://example.com/two.png", }, { title: "Audio only", link: null, date: null, image: null }, ]); }); it("parses atom entries", () => { expect(parseFeed(atom)).toEqual([ { title: "Fish & chips", link: "https://example.com/one", date: "2026-09-22T12:00:00.000Z", image: "https://example.com/one.jpg", }, { title: "YouTube style", link: "https://example.com/two", date: "2026-09-20T12:00:00.000Z", image: "https://example.com/two.jpg", }, ]); }); it("falls back to the first usable image in item html", () => { const xml = ` https://example.com/ Content image Hi ]]> Description image <img src="https://example.com/desc.png"> unclosed <b> Media wins ]]> No usable image ]]> `; expect(parseFeed(xml).map((item) => item.image)).toEqual([ "https://example.com/big.jpg?w=925&h=925", "https://example.com/desc.png", "https://example.com/thumb.jpg", null, ]); }); it("finds images in atom html content and summary", () => { const xml = ` Content<img src="https://example.com/c.jpg"> Summary<img src="https://example.com/s.jpg"> `; expect(parseFeed(xml).map((item) => item.image)).toEqual([ "https://example.com/c.jpg", "https://example.com/s.jpg", ]); }); it("resolves relative urls against the feed's alternate link, never its self link", () => { const entry = `Relative`; const selfOnly = ` ${entry}`; const withSite = ` ${entry}`; expect(parseFeed(selfOnly)[0].link).toBeNull(); expect(parseFeed(withSite)[0].link).toBe("https://example.com/posts/1"); }); it("handles single-item and empty feeds", () => { const single = "Only"; expect(parseFeed(single)).toEqual([{ title: "Only", link: null, date: null, image: null }]); expect(parseFeed("")).toEqual([]); }); it("rejects unsupported documents", () => { expect(() => parseFeed("nope")).toThrow("Unsupported feed format"); expect(() => parseFeed("not xml <")).toThrow(); }); it.each([ ["https://a.com/x", undefined, "https://a.com/x"], ["/x", "https://a.com/feed", "https://a.com/x"], ["data:image/png;base64,AAAA", undefined, null], ["not a url", undefined, null], ["", undefined, null], [" \n ", "https://a.com/feed", null], [" /x ", "https://a.com/feed", "https://a.com/x"], ])("httpUrl(%j, %j) is %j", (value, base, expected) => { expect(httpUrl(value, base)).toBe(expected); }); });