import { describe, test, expect } from "bun:test"; import { parseJobCards, parseJobDetail, extractDivContent, minutesToTPR } from "../src/helpers"; // Minimal search-card markup: parseJobCards splits on the job-posting URN and // needs an id, a base-search-card__title, and a full-link. Everything else is // optional. We inject HTML entities into the title/company to exercise decoding. function searchCard(id: string, title: string, company = "Acme"): string { return `
  • ${title}

    ${company}

  • `; } describe("decodeHtmlEntities (via parseJobCards)", () => { test("decodes hexadecimal numeric entities (é)", () => { const [card] = parseJobCards(searchCard("123", "Café Manager")); expect(card.title).toBe("Café Manager"); }); test("decodes uppercase-X hexadecimal entities (&#X...;)", () => { const [card] = parseJobCards(searchCard("124", "Debüt Role")); // ü = ü expect(card.title).toBe("Debüt Role"); }); test("still decodes decimal numeric entities (é) — regression", () => { const [card] = parseJobCards(searchCard("125", "Café Lead")); expect(card.title).toBe("Café Lead"); }); test("decodes supplementary-plane code points with fromCodePoint (😀)", () => { const [card] = parseJobCards(searchCard("126", "Growth 😀")); expect(card.title).toBe("Growth 😀"); }); test("decodes hex supplementary-plane code points (😀)", () => { const [card] = parseJobCards(searchCard("127", "Growth 😀")); expect(card.title).toBe("Growth 😀"); }); test("decodes hex entities in the company subtitle too", () => { const [card] = parseJobCards(searchCard("128", "Engineer", "Nørrebro ApS")); expect(card.company).toBe("Nørrebro ApS"); }); }); describe("decodeHtmlEntities (via parseJobDetail)", () => { test("decodes hex entities inside the job title", () => { const html = `

    Señor Engineer

    `; const job = parseJobDetail(html, "999"); expect(job.title).toBe("Señor Engineer"); }); }); describe("extractDivContent", () => { test("extracts content from simple div", () => { const html = '
    Simple text
    '; expect(extractDivContent(html, "description__text")).toBe("Simple text"); }); test("extracts content with nested divs — the regression case", () => { const html = `
    Requirements:
    About Us:

    We are...

    `; expect(extractDivContent(html, "description__text")).toBe( '\n
    Requirements:
    \n \n
    About Us:
    \n

    We are...

    \n ', ); }); test("returns null when class not found", () => { expect(extractDivContent("
    no class
    ", "nonexistent")).toBeNull(); }); test("works with show-more-less-html__markup class", () => { const html = '
    LinkedIn content
    '; expect(extractDivContent(html, "show-more-less-html__markup")).toBe("LinkedIn content"); }); test("handles deeply nested divs (3 levels)", () => { const html = `
    Deep content
    `; expect(extractDivContent(html, "description__text")).toBe( '\n
    \n
    Deep content
    \n
    \n ', ); }); test("handles empty content", () => { const html = '
    '; expect(extractDivContent(html, "description__text")).toBe(""); }); test("parseJobDetail uses extractDivContent and preserves full description", () => { const html = `
    Requirements:
    About Us:

    We are hiring!

    `; const job = parseJobDetail(html, "999"); expect(job.description).toContain("Requirements:"); expect(job.description).toContain("5 years Python"); expect(job.description).toContain("About Us:"); expect(job.description).toContain("We are hiring!"); }); }); describe("minutesToTPR", () => { test("converts minutes to an f_TPR seconds window", () => { expect(minutesToTPR(30)).toBe("r1800"); expect(minutesToTPR(1)).toBe("r60"); expect(minutesToTPR(1440)).toBe("r86400"); // matches jobageToTPR(1) }); test("returns null for non-positive input", () => { expect(minutesToTPR(0)).toBeNull(); expect(minutesToTPR(-5)).toBeNull(); }); });