import { describe, expect, it } from "vitest"; import { load } from "cheerio"; import { applyTransform, cutTeasers, embeddedJson, evalRule, extractAddress, extractGeo, jsonLd, jsonPath, mainText, pdfText, publishedDate, walkJson } from "./extract.js"; const HTML = `Page

DC1

Under Construction`; describe("jsonLd / embeddedJson / jsonPath / walkJson", () => { it("flattens @graph and filters by @type case-insensitively", () => { expect(jsonLd(HTML)).toHaveLength(1); expect(jsonLd(HTML, "place")[0]?.name).toBe("DC1"); expect(jsonLd(HTML, "Organization")).toEqual([]); }); it("finds __NEXT_DATA__ and window.__INITIAL_STATE__ blobs", () => { const ids = embeddedJson(HTML).map((b) => b.id); expect(ids).toContain("__NEXT_DATA__"); expect(ids).toContain("__INITIAL_STATE__"); }); it("jsonPath supports $.a.b[0].c and plain a.b", () => { const data = embeddedJson(HTML).find((b) => b.id === "__NEXT_DATA__")!.data; expect(jsonPath(data, "$.props.pageProps.facility.code")).toBe("IAD1"); expect(jsonPath(data, "props.pageProps.facility.items[1].n")).toBe(2); expect(jsonPath(data, "$.props.missing.x")).toBeUndefined(); expect(jsonPath({ a: { b: [{ c: "deep" }] } }, "a.b[0].c")).toBe("deep"); }); it("walkJson collects matching nodes depth-first", () => { const hits = walkJson({ x: [{ code: "A" }, { y: { code: "B" } }], code: "C" }, (o) => typeof o.code === "string"); expect(hits.map((h) => h.code)).toEqual(["C", "A", "B"]); }); }); describe("applyTransform", () => { it("normalizes MW / area / money / dates / status / type / country / numbers / text", () => { expect(applyTransform("0.779MW", "mw")).toBe(0.779); expect(applyTransform("1.2 GW", "mw")).toBe(1200); expect(applyTransform("10,000 sqm", "sqm")).toBe(10000); expect(applyTransform("$2 billion", "money")).toEqual({ amount: 2_000_000_000, currency: "USD" }); expect(applyTransform("Q2 2027", "date")).toBe("2027-Q2"); expect(applyTransform("Under Construction", "status")).toBe("under_construction"); expect(applyTransform("Germany", "country")).toBe("DE"); expect(applyTransform("US", "country")).toBe("US"); expect(applyTransform("1,234 racks", "int")).toBe(1234); expect(applyTransform("PUE 1.25", "float")).toBe(1.25); expect(applyTransform(" a b ", "trim")).toBe("a b"); expect(applyTransform(" X ", "lower")).toBe("x"); expect(applyTransform("yes", "bool")).toBe(true); expect(applyTransform("no", "bool")).toBe(false); expect(applyTransform(null, "mw")).toBeNull(); expect(applyTransform(" raw ", undefined)).toBe("raw"); }); }); describe("evalRule", () => { const $ = load(HTML); it("selector, attr, all, regex+group, meta, json-ld, embedded json, default", () => { expect(evalRule($, HTML, "h1")).toEqual({ value: "DC1", method: "selector:h1" }); expect(evalRule($, HTML, { selector: ".specs li", all: true }).value).toEqual(["Power: 12.5 MW", "Area: 10,000 sqm"]); expect(evalRule($, HTML, { selector: ".specs li", all: true, regex: "Power: (.+)", transform: "mw" }).value).toEqual([12.5]); expect(evalRule($, HTML, { selector: 'meta[name="description"]', attr: "content" }).value).toBe("Desc"); expect(evalRule($, HTML, { meta: "og:title" }).value).toBe("OG Title"); expect(evalRule($, HTML, { jsonld: "Place", jsonPath: "address.addressLocality" })).toEqual({ value: "Ashburn", method: "json-ld:Place.address.addressLocality" }); expect(evalRule($, HTML, { jsonPath: "$.props.pageProps.facility.mw", transform: "mw" }).value).toBe(12.5); expect(evalRule($, HTML, { selector: ".nope", default: "fallback" }).value).toBe("fallback"); expect(evalRule($, HTML, { regex: "Area: ([\\d,]+) sqm", transform: "int" }).value).toBe(10000); expect(evalRule($, HTML, { selector: ".status", transform: "status" }).value).toBe("under_construction"); }); it("scopes to a container", () => { const scope = $(".specs"); expect(evalRule($, HTML, { selector: "li" }, scope).value).toBe("Power: 12.5 MW"); }); }); describe("geo / address / date", () => { it("extracts JSON-LD geo and address, meta geo.position, data attributes and Google Maps links", () => { expect(extractGeo(HTML)).toEqual({ lat: 39.04, lng: -77.49, method: "json-ld:geo" }); expect(extractAddress(HTML)).toEqual({ street: "1 Main St", city: "Ashburn", region: "VA", postal: "20147", country: "US" }); expect(extractGeo('')).toMatchObject({ lat: 48.85, lng: 2.35 }); expect(extractGeo('
')).toMatchObject({ lat: 1.5, lng: 2.5, method: "attr:data-lat" }); expect(extractGeo('m')).toMatchObject({ lat: 51.5, lng: -0.12 }); expect(extractGeo("

nothing

")).toBeNull(); }); it("reads the publication date from meta tags", () => { expect(publishedDate(HTML)).toBe("2026-09-01"); expect(publishedDate("")).toBeNull(); }); }); describe("mainText", () => { const story = `

${"The developer secured planning permission for a 12MW data center in Bucharest. ".repeat(6)}

`; it("keeps the largest
, drops related / recommended / trending / sidebar / aside / footer blocks and cuts trailing teasers", () => { const html = `
`; const t = mainText(html); expect(t).toContain("S+B Gruppe to build data center in Bucharest"); expect(t).toContain("planning permission"); for (const leak of ["Microsoft", "Ignis", "More in", "Google", "Equinix", "Vantage", "Digital Realty", "AWS", "Amazon"]) expect(t, leak).not.toContain(leak); }); it("cutTeasers only cuts after the story has started and only on a heading line", () => { expect(cutTeasers("Related\nshort lead")).toBe("Related\nshort lead"); const body = "x".repeat(400); expect(cutTeasers(`${body}\n Tags \n Bucharest\n Comments`)).toBe(body); expect(cutTeasers(`${body}\nMore in the article follows here with a long sentence about the project itself.`)).toContain("More in the article"); }); }); describe("pdfText limits", () => { it("rejects oversized PDFs before parsing", async () => { await expect(pdfText(Buffer.alloc(16 * 1024 * 1024), 60, { maxBytes: 15 * 1024 * 1024 })).rejects.toThrow(/too large/); }); it("throws on garbage input instead of hanging", async () => { await expect(pdfText(Buffer.from("%PDF-1.4 definitely not a pdf"), 60, { timeoutMs: 5_000 })).rejects.toThrow(); }); });