mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2026-10-03 10:13:47 +10:00
fix(import): escape plain-text fields from json resume
Summaries, highlights, courses, and award, publication and reference texts went into the HTML as written, so text such as "C<T>" was lost. They now go through the same escapeHtml helper as the LinkedIn and plain-text importers.
This commit is contained in:
@@ -31,9 +31,8 @@ describe("toHtmlDescription", () => {
|
||||
expect(toHtmlDescription("", ["a"])).toBe("<ul><li>a</li></ul>");
|
||||
});
|
||||
|
||||
it("does not escape HTML in inputs (caller's responsibility)", () => {
|
||||
// Document existing behavior: caller must sanitize before passing
|
||||
expect(toHtmlDescription("<script>")).toBe("<p><script></p>");
|
||||
it("escapes HTML in inputs", () => {
|
||||
expect(toHtmlDescription("<script>", ["C<T>"])).toBe("<p><script></p><ul><li>C<T></li></ul>");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -56,7 +55,7 @@ describe("arrayToHtmlList", () => {
|
||||
expect(result.indexOf("a")).toBeLessThan(result.indexOf("b"));
|
||||
});
|
||||
|
||||
it("does not escape HTML in items", () => {
|
||||
expect(arrayToHtmlList(["<b>bold</b>"])).toBe("<ul><li><b>bold</b></li></ul>");
|
||||
it("escapes HTML in items", () => {
|
||||
expect(arrayToHtmlList(["<b>bold</b>"])).toBe("<ul><li><b>bold</b></li></ul>");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import { escapeHtml } from "@reactive-resume/utils/string";
|
||||
/**
|
||||
* Converts a summary string and optional highlights array into an HTML description.
|
||||
* Converts a plain-text summary and optional highlights array into an escaped HTML description.
|
||||
* Summary becomes a <p> tag, highlights become a <ul> list.
|
||||
*/
|
||||
export function toHtmlDescription(summary?: string, highlights?: string[]): string {
|
||||
const parts: string[] = [];
|
||||
|
||||
if (summary) {
|
||||
parts.push(`<p>${summary}</p>`);
|
||||
parts.push(`<p>${escapeHtml(summary)}</p>`);
|
||||
}
|
||||
|
||||
if (highlights && highlights.length > 0) {
|
||||
parts.push("<ul>");
|
||||
|
||||
for (const highlight of highlights) {
|
||||
parts.push(`<li>${highlight}</li>`);
|
||||
parts.push(`<li>${escapeHtml(highlight)}</li>`);
|
||||
}
|
||||
|
||||
parts.push("</ul>");
|
||||
@@ -24,11 +24,11 @@ export function toHtmlDescription(summary?: string, highlights?: string[]): stri
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an array of strings into an HTML unordered list.
|
||||
* Converts an array of plain-text strings into an escaped HTML unordered list.
|
||||
*/
|
||||
export function arrayToHtmlList(items: string[]): string {
|
||||
if (items.length === 0) return "";
|
||||
return `<ul>${items.map((item) => `<li>${item}</li>`).join("")}</ul>`;
|
||||
return `<ul>${items.map((item) => `<li>${escapeHtml(item)}</li>`).join("")}</ul>`;
|
||||
}
|
||||
|
||||
export const BULLET_PATTERN = /^\s*[-–—•*◦‣·]\s+/;
|
||||
|
||||
@@ -196,4 +196,20 @@ describe("parseJSONResume", () => {
|
||||
expect(next.sections.experience.items).toHaveLength(0);
|
||||
expect(defaultResumeData.sections.experience.items).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("escapes plain-text fields so markup-like text survives as text", () => {
|
||||
const result = parseJSONResume(
|
||||
JSON.stringify({
|
||||
basics: { summary: "Generics like C<T> & more" },
|
||||
work: [{ name: "Acme", summary: "Used List<T>", highlights: ["Wrote <b> tags"] }],
|
||||
awards: [{ title: "Prize", summary: "Best <T>" }],
|
||||
}),
|
||||
);
|
||||
|
||||
expect(result.summary.content).toBe("<p>Generics like C<T> & more</p>");
|
||||
expect(result.sections.experience.items[0]!.description).toBe(
|
||||
"<p>Used List<T></p><ul><li>Wrote <b> tags</li></ul>",
|
||||
);
|
||||
expect(result.sections.awards.items[0]!.description).toBe("<p>Best <T></p>");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -203,7 +203,7 @@ function convertJSONResume(jsonResume: JSONResume): ResumeData {
|
||||
if (jsonResume.basics?.summary) {
|
||||
result.summary = {
|
||||
...defaultResumeData.summary,
|
||||
content: `<p>${jsonResume.basics.summary}</p>`,
|
||||
content: toHtmlDescription(jsonResume.basics.summary),
|
||||
hidden: false,
|
||||
};
|
||||
}
|
||||
@@ -335,7 +335,7 @@ function convertJSONResume(jsonResume: JSONResume): ResumeData {
|
||||
date: "",
|
||||
dates: toSingleDates(award.date),
|
||||
website: createItemWebsite(),
|
||||
description: award.summary ? `<p>${award.summary}</p>` : "",
|
||||
description: toHtmlDescription(award.summary),
|
||||
})),
|
||||
};
|
||||
}
|
||||
@@ -373,7 +373,7 @@ function convertJSONResume(jsonResume: JSONResume): ResumeData {
|
||||
date: "",
|
||||
dates: toSingleDates(pub.releaseDate),
|
||||
website: createItemWebsite(pub.url),
|
||||
description: pub.summary ? `<p>${pub.summary}</p>` : "",
|
||||
description: toHtmlDescription(pub.summary),
|
||||
})),
|
||||
};
|
||||
}
|
||||
@@ -410,7 +410,7 @@ function convertJSONResume(jsonResume: JSONResume): ResumeData {
|
||||
position: "",
|
||||
website: createItemWebsite(),
|
||||
phone: "",
|
||||
description: ref.reference ? `<p>${ref.reference}</p>` : "",
|
||||
description: toHtmlDescription(ref.reference),
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user