// author: simon-pierre boucher import { describe, expect, it } from "vitest"; import { parse } from "./dom.js"; import { harvestStructured } from "./structured.js"; describe("harvestStructured", () => { it("harvests JSON-LD, resolving @graph", () => { const html = `

Fallback

`; const { metadata, structured } = harvestStructured(parse(html).document); expect(structured.jsonld.length).toBe(2); expect(metadata.title).toBe("The Headline"); expect(metadata.publishedTime).toBe("2026-01-01"); expect(metadata.author).toBe("Ada"); }); it("prefers JSON-LD over OpenGraph over meta over heuristics", () => { const html = ` Doc Title

H1 Title

`; const { metadata } = harvestStructured(parse(html).document); expect(metadata.title).toBe("LD Title"); expect(metadata.description).toBe("og desc"); }); it("falls back to og:title then then h1", () => { const ogOnly = harvestStructured( parse('<html><head><meta property="og:title" content="OG"><title>T

H

') .document, ); expect(ogOnly.metadata.title).toBe("OG"); const titleOnly = harvestStructured(parse("T

H

").document); expect(titleOnly.metadata.title).toBe("T"); const h1Only = harvestStructured(parse("

H

").document); expect(h1Only.metadata.title).toBe("H"); }); it("harvests OpenGraph and Twitter cards", () => { const html = ` `; const { metadata, structured } = harvestStructured(parse(html).document); expect(structured.openGraph["site_name"]).toBe("Acme"); expect(metadata.siteName).toBe("Acme"); expect(metadata.image).toBe("https://x/og.png"); expect(structured.twitter["image"]).toBe("https://x/tw.png"); }); it("ignores malformed JSON-LD without throwing", () => { const { structured } = harvestStructured( parse('').document, ); expect(structured.jsonld).toEqual([]); }); it("splits keywords and reads canonical + lang", () => { const html = ` `; const { metadata } = harvestStructured(parse(html).document); expect(metadata.keywords).toEqual(["a", "b", "c"]); expect(metadata.canonical).toBe("https://x/canon"); expect(metadata.lang).toBe("fr"); }); });