refactor(import): add v4section/v4url aliases, inline clamp wrappers, replace classes with fns

Finding 5: Introduce V4Url + V4Section<T> type aliases in reactive-resume-v4-json.tsx; collapse
310-line V4ResumeData type (13x 5-field section header, 11x {label,href}) into typed aliases.
Inferred shape is structurally identical.

Finding 6: Remove 9 single-caller clamp/pxToPt wrappers; replace compile-time constants
(rotation=0, sidebarWidth=35, shadowWidth=0, gapX=4, gapY=6) with literals; inline dynamic
calls (clamp(x, 32, 512) etc.) at the two body/heading typography call sites.

Finding 7: Convert three stateless single-method importer classes to plain functions.
Extract shared rethrowAsImportError() to error.ts, replacing triplicated ZodError catch blocks.
Update web import dialog + all in-package test files to use function API.

Claude-Session: https://claude.ai/code/session_012Bnvt1MghwHj4qQRxuQUGa
This commit is contained in:
Amruth Pillai
2026-07-04 21:02:17 +02:00
parent 493ef12a9a
commit afd734dd61
9 changed files with 834 additions and 1026 deletions
+13 -15
View File
@@ -1,22 +1,20 @@
// biome-ignore-all lint/style/noNonNullAssertion: These tests assert imported section lengths before inspecting the first item.
import { describe, expect, it } from "vitest";
import { JSONResumeImporter } from "./json-resume";
import { parseJSONResume } from "./json-resume";
const importer = new JSONResumeImporter();
describe("JSONResumeImporter.parse", () => {
describe("parseJSONResume", () => {
it("throws when input is not valid JSON", () => {
expect(() => importer.parse("not json")).toThrow();
expect(() => parseJSONResume("not json")).toThrow();
});
it("throws a serialized validation error for an obviously invalid shape", () => {
// Email field is validated; a non-email string should fail the loose schema.
const invalid = JSON.stringify({ basics: { email: "not-an-email" } });
expect(() => importer.parse(invalid)).toThrow();
expect(() => parseJSONResume(invalid)).toThrow();
});
it("imports an empty JSON Resume into a baseline ResumeData", () => {
const result = importer.parse("{}");
const result = parseJSONResume("{}");
// Defaults preserve a name field even when unset by input.
expect(typeof result.basics.name).toBe("string");
});
@@ -34,7 +32,7 @@ describe("JSONResumeImporter.parse", () => {
},
});
const result = importer.parse(json);
const result = parseJSONResume(json);
expect(result.basics.name).toBe("Jane Doe");
expect(result.basics.headline).toBe("Engineer");
expect(result.basics.email).toBe("jane@example.com");
@@ -62,7 +60,7 @@ describe("JSONResumeImporter.parse", () => {
],
});
const result = importer.parse(json);
const result = parseJSONResume(json);
expect(result.sections.experience.items).toHaveLength(1);
const item = result.sections.experience.items[0]!;
@@ -89,7 +87,7 @@ describe("JSONResumeImporter.parse", () => {
],
});
const result = importer.parse(json);
const result = parseJSONResume(json);
expect(result.sections.education.items).toHaveLength(1);
const edu = result.sections.education.items[0]!;
@@ -103,7 +101,7 @@ describe("JSONResumeImporter.parse", () => {
skills: [{ name: "TypeScript", level: "Master", keywords: ["node", "react"] }],
});
const result = importer.parse(json);
const result = parseJSONResume(json);
const skill = result.sections.skills.items[0]!;
expect(skill.name).toBe("TypeScript");
@@ -121,7 +119,7 @@ describe("JSONResumeImporter.parse", () => {
},
});
const result = importer.parse(json);
const result = parseJSONResume(json);
expect(result.sections.profiles.items).toHaveLength(1);
const profile = result.sections.profiles.items[0]!;
@@ -134,13 +132,13 @@ describe("JSONResumeImporter.parse", () => {
basics: { image: "https://example.com/pic.jpg" },
});
const result = importer.parse(json);
const result = parseJSONResume(json);
expect(result.picture.url).toBe("https://example.com/pic.jpg");
expect(result.picture.hidden).toBe(false);
});
it("leaves the summary content empty when basics.summary is absent", () => {
const result = importer.parse(JSON.stringify({ basics: { name: "Jane" } }));
const result = parseJSONResume(JSON.stringify({ basics: { name: "Jane" } }));
expect(result.summary.content).toBe("");
});
@@ -159,7 +157,7 @@ describe("JSONResumeImporter.parse", () => {
],
});
const result = importer.parse(json);
const result = parseJSONResume(json);
expect(result.sections.projects.items).toHaveLength(1);
const project = result.sections.projects.items[0]!;