fix(portals): depth-track div extraction so nested job descriptions aren't truncated (#204)

The jobindex and linkedin detail parsers matched description containers with a non-greedy regex that stops at the first inner </div>, so any posting whose description contains nested divs was silently truncated (jobindex dropped later sections; linkedin dropped everything after the first block). Replaces the regex with a depth-tracked extractDivContent scanner that walks div open/close markers to the matching close. Verified: truncation bug reproduced against real markup fixtures, depth arithmetic correct (no off-by-one/infinite-loop), 28 tests pass network-free, no regression on non-nested divs. Malformed-HTML over-grabs rather than truncates - the safer failure, cleaned by downstream stripTags/decode.

By @oscarbol09.
This commit is contained in:
Oscar Madera
2026-07-21 08:11:17 +02:00
committed by GitHub
parent 808be3daad
commit d3eea27b90
5 changed files with 179 additions and 11 deletions
@@ -1,5 +1,5 @@
import { describe, test, expect } from "bun:test";
import { parseJobCards, parseJobDetail } from "../src/helpers";
import { parseJobCards, parseJobDetail, extractDivContent } 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
@@ -53,3 +53,61 @@ describe("decodeHtmlEntities (via parseJobDetail)", () => {
expect(job.title).toBe("Señor Engineer");
});
});
describe("extractDivContent", () => {
test("extracts content from simple div", () => {
const html = '<div class="description__text">Simple text</div>';
expect(extractDivContent(html, "description__text")).toBe("Simple text");
});
test("extracts content with nested divs — the regression case", () => {
const html = `<div class="description__text">
<div>Requirements:</div>
<ul><li>Skill A</li></ul>
<div>About Us:</div>
<p>We are...</p>
</div>`;
expect(extractDivContent(html, "description__text")).toBe(
'\n <div>Requirements:</div>\n <ul><li>Skill A</li></ul>\n <div>About Us:</div>\n <p>We are...</p>\n ',
);
});
test("returns null when class not found", () => {
expect(extractDivContent("<div>no class</div>", "nonexistent")).toBeNull();
});
test("works with show-more-less-html__markup class", () => {
const html = '<div class="show-more-less-html__markup">LinkedIn content</div>';
expect(extractDivContent(html, "show-more-less-html__markup")).toBe("LinkedIn content");
});
test("handles deeply nested divs (3 levels)", () => {
const html = `<div class="description__text">
<div>
<div>Deep content</div>
</div>
</div>`;
expect(extractDivContent(html, "description__text")).toBe(
'\n <div>\n <div>Deep content</div>\n </div>\n ',
);
});
test("handles empty content", () => {
const html = '<div class="description__text"></div>';
expect(extractDivContent(html, "description__text")).toBe("");
});
test("parseJobDetail uses extractDivContent and preserves full description", () => {
const html = `<div class="description__text">
<div>Requirements:</div>
<ul><li>5 years Python</li></ul>
<div>About Us:</div>
<p>We are hiring!</p>
</div>`;
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!");
});
});