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
@@ -1,29 +1,27 @@
import { describe, expect, it } from "vitest";
import { defaultResumeData } from "@reactive-resume/schema/resume/default";
import { ReactiveResumeJSONImporter } from "./reactive-resume-json";
import { parseReactiveResumeJSON } from "./reactive-resume-json";
const importer = new ReactiveResumeJSONImporter();
describe("ReactiveResumeJSONImporter", () => {
describe("parseReactiveResumeJSON", () => {
it("round-trips the default resume data", () => {
const result = importer.parse(JSON.stringify(defaultResumeData));
const result = parseReactiveResumeJSON(JSON.stringify(defaultResumeData));
expect(result.basics.name).toBe(defaultResumeData.basics.name);
});
it("throws a JSON-serialised validation error for an invalid object", () => {
// Missing required top-level fields.
expect(() => importer.parse(JSON.stringify({ foo: "bar" }))).toThrow();
expect(() => parseReactiveResumeJSON(JSON.stringify({ foo: "bar" }))).toThrow();
});
it("throws when the input is not valid JSON", () => {
expect(() => importer.parse("not-json")).toThrow();
expect(() => parseReactiveResumeJSON("not-json")).toThrow();
});
it("creates a default layout page when the imported data has no pages", () => {
const data = structuredClone(defaultResumeData);
data.metadata.layout.pages = [];
const result = importer.parse(JSON.stringify(data));
const result = parseReactiveResumeJSON(JSON.stringify(data));
expect(result.metadata.layout.pages.length).toBe(1);
expect(result.metadata.layout.pages[0]?.main.length).toBeGreaterThan(0);
});
@@ -38,7 +36,7 @@ describe("ReactiveResumeJSONImporter", () => {
},
];
const result = importer.parse(JSON.stringify(data));
const result = parseReactiveResumeJSON(JSON.stringify(data));
const firstPage = result.metadata.layout.pages[0];
expect(firstPage).toBeDefined();
const allIds = new Set([...(firstPage?.main ?? []), ...(firstPage?.sidebar ?? [])]);
@@ -53,7 +51,7 @@ describe("ReactiveResumeJSONImporter", () => {
});
it("does not modify the layout when every built-in section is already placed", () => {
const result = importer.parse(JSON.stringify(defaultResumeData));
const result = parseReactiveResumeJSON(JSON.stringify(defaultResumeData));
expect(result.metadata.layout.pages.length).toBe(defaultResumeData.metadata.layout.pages.length);
});
});