mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2026-08-25 07:42:20 +10:00
feat(export): separate resume/cover-letter downloads, redesign dialog, add Markdown export (#3217)
* feat(export): separate resume/cover-letter downloads, redesign dialog, add Markdown Let people export the resume and cover letter as distinct documents, and add a Markdown format alongside PDF / DOCX / JSON (handy for AI agents). - Server/API: scope PDF generation and download URLs to a resume/cover-letter target. - Export domain: getResumeExportData + resumeHasCoverLetter in @reactive-resume/resume. - Redesign the download dialog: one global "What to export" scope toggle (Tabs) plus flattened per-format rows, reusing existing UI components and design language. - Add Markdown export (@reactive-resume/resume/markdown) with a small tiptap-HTML converter. - Fix blank section headings in DOCX and Markdown by injecting the locale-aware section-title resolver (titles are stored empty and resolved at render time). - Locale catalogs updated for the new strings. * test(e2e): open the download dialog before exporting JSON The JSON export moved into the redesigned download dialog, so the spec now opens the dialog from the Export sidebar section before clicking "Download JSON".
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { sampleResumeData } from "@reactive-resume/schema/resume/sample";
|
||||
import { getResumeExportData } from "./export-sections";
|
||||
import { buildMarkdown, htmlToMarkdown } from "./markdown";
|
||||
|
||||
describe("htmlToMarkdown", () => {
|
||||
it("converts the constrained tiptap tag set", () => {
|
||||
const html = "<p>Led <strong>teams</strong> of <em>engineers</em>.</p><ul><li>Shipped X</li><li>Owned Y</li></ul>";
|
||||
expect(htmlToMarkdown(html)).toBe("Led **teams** of _engineers_.\n\n- Shipped X\n- Owned Y");
|
||||
});
|
||||
|
||||
it("converts anchors to Markdown links and decodes entities", () => {
|
||||
expect(htmlToMarkdown('<p>See <a href="https://x.com">Tom & Jerry</a></p>')).toBe(
|
||||
"See [Tom & Jerry](https://x.com)",
|
||||
);
|
||||
});
|
||||
|
||||
it("returns an empty string for empty input", () => {
|
||||
expect(htmlToMarkdown("")).toBe("");
|
||||
});
|
||||
});
|
||||
|
||||
describe("buildMarkdown", () => {
|
||||
// Section titles are stored empty and resolved by the caller; mimic that with a simple resolver.
|
||||
const resolveTitle = (sectionId: string) =>
|
||||
({ summary: "Summary", experience: "Experience", education: "Education" })[sectionId];
|
||||
const md = buildMarkdown(getResumeExportData(sampleResumeData, "resume"), resolveTitle);
|
||||
|
||||
it("emits the name as an H1 and headline as emphasis", () => {
|
||||
expect(md.startsWith(`# ${sampleResumeData.basics.name}`)).toBe(true);
|
||||
expect(md).toContain(`_${sampleResumeData.basics.headline}_`);
|
||||
});
|
||||
|
||||
it("renders resolved section headings as H2", () => {
|
||||
expect(md).toContain("## Experience");
|
||||
});
|
||||
|
||||
it("falls back to the stored title when no resolver is given", () => {
|
||||
const bare = buildMarkdown(getResumeExportData(sampleResumeData, "resume"));
|
||||
expect(bare).not.toContain("## Experience");
|
||||
});
|
||||
|
||||
it("ends with a single trailing newline and never contains raw HTML tags", () => {
|
||||
expect(md.endsWith("\n")).toBe(true);
|
||||
expect(md).not.toMatch(/<[a-z][^>]*>/i);
|
||||
});
|
||||
|
||||
it("excludes the cover letter from the resume scope and includes it in the cover-letter scope", () => {
|
||||
const cover = buildMarkdown(getResumeExportData(sampleResumeData, "cover-letter"));
|
||||
expect(cover.length).toBeGreaterThan(0);
|
||||
expect(cover).not.toBe(md);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user