import { describe, expect, test } from "bun:test"; import { parseJobPostingFromHtml } from "../src/commands/detail"; function pageWithScripts(...scripts: string[]): string { return `${scripts .map((content) => ``) .join("")}`; } describe("parseJobPostingFromHtml JSON-LD", () => { test("normalizes a JobPosting object and scalar employment type", () => { const posting = { "@context": "https://schema.org", "@type": "JobPosting", title: "Data Engineer", datePosted: "2026-07-13", validThrough: "2026-08-13", employmentType: "FULL_TIME", hiringOrganization: { name: "Acme A/S", logo: "https://cdn.example/logo.png" }, jobLocation: { address: { streetAddress: "Testvej 1", addressLocality: "Odense", addressRegion: "Syddanmark", postalCode: "5000", addressCountry: "DK", }, }, description: "

Build reliable pipelines.

", }; const parsed = parseJobPostingFromHtml( pageWithScripts(JSON.stringify(posting)), "data-engineer", "https://jobdanmark.dk/job/data-engineer", ); expect(parsed).toEqual({ slug: "data-engineer", url: "https://jobdanmark.dk/job/data-engineer", title: "Data Engineer", datePosted: "2026-07-13", validThrough: "2026-08-13", employmentType: ["FULL_TIME"], hiringOrganization: { name: "Acme A/S", logo: "https://cdn.example/logo.png" }, jobLocation: { streetAddress: "Testvej 1", addressLocality: "Odense", addressRegion: "Syddanmark", postalCode: "5000", addressCountry: "DK", }, description: "Build reliable pipelines.", applyUrl: null, }); }); test("renders an HTML description to the rendered-branch text shape", () => { // add-portal.md Step 4: a detail description is "readable text (entities // decoded, tags stripped, paragraph breaks preserved)". The rendered-HTML // fallback already emits one line per

/

  • ; the JSON-LD branch passed // the markup through verbatim, so the same command produced two shapes. const posting = { "@type": "JobPosting", title: "Udvikler", description: "

    Vi søger en udvikler til R&D.

    Second para
    line two

    ", }; const parsed = parseJobPostingFromHtml( pageWithScripts(JSON.stringify(posting)), "udvikler", "https://jobdanmark.dk/job/udvikler", ); expect(parsed.description).toBe("Vi søger en udvikler til R&D.\nSecond para\nline two\nPython & Go\nSQL"); expect(parsed.description).not.toMatch(/<[^>]+>/); }); test("leaves a plain-text description unchanged and an absent one empty", () => { const withText = { "@type": "JobPosting", title: "A", description: "Plain prose, no markup." }; const without = { "@type": "JobPosting", title: "B" }; expect(parseJobPostingFromHtml(pageWithScripts(JSON.stringify(withText)), "a", "https://jobdanmark.dk/job/a").description).toBe( "Plain prose, no markup.", ); expect(parseJobPostingFromHtml(pageWithScripts(JSON.stringify(without)), "b", "https://jobdanmark.dk/job/b").description).toBe(""); }); test("skips malformed scripts and finds a JobPosting in an array", () => { const scripts = [ "{not-json", JSON.stringify([ { "@type": "BreadcrumbList" }, { "@type": "JobPosting", title: "Analyst", employmentType: ["FULL_TIME", "PART_TIME"] }, ]), ]; const parsed = parseJobPostingFromHtml( pageWithScripts(...scripts), "analyst", "https://jobdanmark.dk/job/analyst", ); expect(parsed.title).toBe("Analyst"); expect(parsed.employmentType).toEqual(["FULL_TIME", "PART_TIME"]); expect(parsed.validThrough).toBeNull(); expect(parsed.hiringOrganization).toEqual({ name: "", logo: null }); }); test("recognizes rendered not-found and unparseable pages", () => { expect(() => parseJobPostingFromHtml( "404 | Jobdanmark", "missing", "https://jobdanmark.dk/job/missing", ), ).toThrow("NOT_FOUND"); expect(() => parseJobPostingFromHtml( "Jobdanmark", "broken", "https://jobdanmark.dk/job/broken", ), ).toThrow("Failed to parse job page HTML"); }); });