test: add unit and component tests across the monorepo

Adds ~1000 tests to bring the previously-untested packages and apps
under coverage:

- packages/utils — string, color, date, file, level, locale, sanitize,
  field, html, network-icons, rate-limit, url, url-security, monorepo,
  resume/patch, resume/docx/link-utils, style helpers (~97% on the
  testable utility files)
- packages/ui — 28 component test files plus the use-controlled-state,
  use-mobile, use-confirm, use-prompt hooks (95% statements)
- packages/pdf — shared template helpers (filtering, columns, picture,
  metrics, section-links, page-size, rich-text-html, section-title)
- packages/schema — resumeDataSchema, page, templates, default
- packages/fonts — expanded coverage on font helpers
- packages/ai — resume sanitize and extraction template
- packages/api — resume-access-policy
- apps/web — error-message, locale, theme, pwa, dialogs/store, and
  the resume/move-item / section-actions / make-section-item helpers

Adds jsdom polyfills (ResizeObserver, IntersectionObserver,
scrollIntoView, matchMedia) and an explicit React Testing Library
cleanup hook to vitest.setup.ts so portal- and overlay-based components
work without per-test setup.
This commit is contained in:
Amruth Pillai
2026-05-10 20:00:07 +02:00
parent 33103536ae
commit 6a01207b6b
83 changed files with 8099 additions and 6 deletions
+42
View File
@@ -0,0 +1,42 @@
import { describe, expect, it } from "vitest";
import { pageDimensionsAsMillimeters, pageDimensionsAsPixels } from "./page";
describe("pageDimensionsAsPixels", () => {
it("defines a4 dimensions matching ISO 216 at ~96 DPI", () => {
expect(pageDimensionsAsPixels.a4).toEqual({ width: 794, height: 1123 });
});
it("defines US letter dimensions", () => {
expect(pageDimensionsAsPixels.letter).toEqual({ width: 816, height: 1056 });
});
it("defines free-form dimensions same as a4", () => {
expect(pageDimensionsAsPixels["free-form"]).toEqual({ width: 794, height: 1123 });
});
it("uses portrait orientation (height > width) for all formats", () => {
expect(pageDimensionsAsPixels.a4.height).toBeGreaterThan(pageDimensionsAsPixels.a4.width);
expect(pageDimensionsAsPixels.letter.height).toBeGreaterThan(pageDimensionsAsPixels.letter.width);
});
});
describe("pageDimensionsAsMillimeters", () => {
it("defines a4 dimensions in millimeters per ISO 216", () => {
expect(pageDimensionsAsMillimeters.a4).toEqual({ width: "210mm", height: "297mm" });
});
it("defines letter dimensions in millimeters", () => {
expect(pageDimensionsAsMillimeters.letter).toEqual({ width: "216mm", height: "279mm" });
});
it("appends 'mm' unit suffix to all values", () => {
for (const [, dims] of Object.entries(pageDimensionsAsMillimeters)) {
expect(dims.width).toMatch(/mm$/);
expect(dims.height).toMatch(/mm$/);
}
});
it("provides all three formats (a4, letter, free-form)", () => {
expect(Object.keys(pageDimensionsAsMillimeters).sort()).toEqual(["a4", "free-form", "letter"]);
});
});
+272
View File
@@ -0,0 +1,272 @@
import { describe, expect, it } from "vitest";
import {
basicsSchema,
customFieldSchema,
experienceItemSchema,
layoutSchema,
pageSchema,
pictureSchema,
resumeDataSchema,
skillItemSchema,
websiteSchema,
} from "./data";
import { defaultResumeData } from "./default";
describe("resumeDataSchema", () => {
it("validates the default resume", () => {
expect(resumeDataSchema.safeParse(defaultResumeData).success).toBe(true);
});
it("rejects empty object", () => {
expect(resumeDataSchema.safeParse({}).success).toBe(false);
});
it("rejects missing top-level keys", () => {
const partial = { ...defaultResumeData, basics: undefined };
expect(resumeDataSchema.safeParse(partial).success).toBe(false);
});
});
describe("websiteSchema", () => {
it("requires url and label fields", () => {
expect(websiteSchema.safeParse({ url: "https://example.com", label: "Example" }).success).toBe(true);
});
it("rejects missing url", () => {
expect(websiteSchema.safeParse({ label: "Example" }).success).toBe(false);
});
it("rejects missing label", () => {
expect(websiteSchema.safeParse({ url: "https://example.com" }).success).toBe(false);
});
it("allows empty strings (caller decides display)", () => {
expect(websiteSchema.safeParse({ url: "", label: "" }).success).toBe(true);
});
});
describe("pictureSchema", () => {
it("accepts the default picture config", () => {
expect(pictureSchema.safeParse(defaultResumeData.picture).success).toBe(true);
});
it("rejects size below 32", () => {
const invalid = { ...defaultResumeData.picture, size: 16 };
expect(pictureSchema.safeParse(invalid).success).toBe(false);
});
it("rejects size above 512", () => {
const invalid = { ...defaultResumeData.picture, size: 1024 };
expect(pictureSchema.safeParse(invalid).success).toBe(false);
});
it("rejects rotation below 0", () => {
const invalid = { ...defaultResumeData.picture, rotation: -1 };
expect(pictureSchema.safeParse(invalid).success).toBe(false);
});
it("rejects rotation above 360", () => {
const invalid = { ...defaultResumeData.picture, rotation: 361 };
expect(pictureSchema.safeParse(invalid).success).toBe(false);
});
});
describe("basicsSchema", () => {
it("validates default basics", () => {
expect(basicsSchema.safeParse(defaultResumeData.basics).success).toBe(true);
});
it("requires email to be a string but does not enforce email shape (catches at app level)", () => {
const result = basicsSchema.safeParse({
...defaultResumeData.basics,
email: "not-an-email",
});
expect(result.success).toBe(true);
});
it("supports customFields array", () => {
const result = basicsSchema.safeParse({
...defaultResumeData.basics,
customFields: [{ id: "1", icon: "phone", text: "555-0000", link: "" }],
});
expect(result.success).toBe(true);
});
});
describe("customFieldSchema", () => {
it("requires id, icon, and text", () => {
const result = customFieldSchema.safeParse({
id: "1",
icon: "phone",
text: "555-0000",
link: "",
});
expect(result.success).toBe(true);
});
it("rejects missing id", () => {
expect(customFieldSchema.safeParse({ icon: "phone", text: "555-0000", link: "" }).success).toBe(false);
});
it("falls back to empty link via .catch when missing", () => {
const result = customFieldSchema.safeParse({ id: "1", icon: "phone", text: "x" });
expect(result.success).toBe(true);
if (result.success) expect(result.data.link).toBe("");
});
});
describe("experienceItemSchema", () => {
it("requires company name (min 1)", () => {
const result = experienceItemSchema.safeParse({
id: "abcdef0123456789",
hidden: false,
company: "",
position: "Engineer",
location: "",
period: "",
website: { url: "", label: "", inlineLink: false },
description: "",
roles: [],
});
expect(result.success).toBe(false);
});
it("validates a complete experience item", () => {
const result = experienceItemSchema.safeParse({
id: "abcdef0123456789",
hidden: false,
company: "Acme",
position: "Engineer",
location: "NYC",
period: "2020 - 2023",
website: { url: "https://acme.com", label: "Acme", inlineLink: false },
description: "<p>Did things</p>",
roles: [],
});
expect(result.success).toBe(true);
});
it("defaults roles to [] via .catch when missing", () => {
const result = experienceItemSchema.safeParse({
id: "x",
hidden: false,
company: "Acme",
position: "",
location: "",
period: "",
website: { url: "", label: "", inlineLink: false },
description: "",
});
expect(result.success).toBe(true);
if (result.success) expect(result.data.roles).toEqual([]);
});
});
describe("skillItemSchema", () => {
it("requires name (min 1)", () => {
const invalid = {
id: "x",
hidden: false,
icon: "",
iconColor: "",
name: "",
proficiency: "",
level: 4,
keywords: [],
};
expect(skillItemSchema.safeParse(invalid).success).toBe(false);
});
it("clamps invalid level via .catch(0)", () => {
const item = {
id: "x",
hidden: false,
icon: "",
iconColor: "",
name: "TS",
proficiency: "",
level: 99,
keywords: [],
};
const result = skillItemSchema.safeParse(item);
expect(result.success).toBe(true);
if (result.success) expect(result.data.level).toBe(0);
});
it("allows level between 0 and 5", () => {
for (const level of [0, 1, 2, 3, 4, 5]) {
const item = {
id: "x",
hidden: false,
icon: "",
iconColor: "",
name: "X",
proficiency: "",
level,
keywords: [],
};
expect(skillItemSchema.safeParse(item).success).toBe(true);
}
});
it("falls back to empty array when keywords is invalid via .catch", () => {
const item = {
id: "x",
hidden: false,
icon: "",
iconColor: "",
name: "X",
proficiency: "",
level: 3,
keywords: "not-an-array",
};
const result = skillItemSchema.safeParse(item);
expect(result.success).toBe(true);
if (result.success) expect(result.data.keywords).toEqual([]);
});
});
describe("layoutSchema", () => {
it("validates default layout", () => {
expect(layoutSchema.safeParse(defaultResumeData.metadata.layout).success).toBe(true);
});
it("clamps sidebarWidth out-of-range to 35", () => {
const result = layoutSchema.safeParse({
...defaultResumeData.metadata.layout,
sidebarWidth: 999,
});
expect(result.success).toBe(true);
if (result.success) expect(result.data.sidebarWidth).toBe(35);
});
it("clamps sidebarWidth below 10 to 35", () => {
const result = layoutSchema.safeParse({
...defaultResumeData.metadata.layout,
sidebarWidth: 5,
});
expect(result.success).toBe(true);
if (result.success) expect(result.data.sidebarWidth).toBe(35);
});
});
describe("pageSchema", () => {
it("rejects negative gap or margin values", () => {
const invalid = { ...defaultResumeData.metadata.page, gapX: -1 };
expect(pageSchema.safeParse(invalid).success).toBe(false);
});
it("falls back to 'a4' for unknown format via .catch", () => {
const invalid = { ...defaultResumeData.metadata.page, format: "huge" };
const result = pageSchema.safeParse(invalid);
expect(result.success).toBe(true);
if (result.success) expect(result.data.format).toBe("a4");
});
it("accepts each known format", () => {
for (const format of ["a4", "letter", "free-form"] as const) {
const valid = { ...defaultResumeData.metadata.page, format };
expect(pageSchema.safeParse(valid).success).toBe(true);
}
});
});
@@ -0,0 +1,54 @@
import { describe, expect, it } from "vitest";
import { resumeDataSchema } from "./data";
import { defaultResumeData } from "./default";
describe("defaultResumeData", () => {
it("conforms to resumeDataSchema", () => {
const result = resumeDataSchema.safeParse(defaultResumeData);
expect(result.success).toBe(true);
});
it("uses the 'onyx' template", () => {
expect(defaultResumeData.metadata.template).toBe("onyx");
});
it("uses en-US locale", () => {
expect(defaultResumeData.metadata.page.locale).toBe("en-US");
});
it("uses A4 format by default", () => {
expect(defaultResumeData.metadata.page.format).toBe("a4");
});
it("starts with no resume content", () => {
expect(defaultResumeData.basics.name).toBe("");
expect(defaultResumeData.summary.content).toBe("");
for (const section of Object.values(defaultResumeData.sections)) {
expect(section.items).toEqual([]);
}
});
it("has exactly one default page layout", () => {
expect(defaultResumeData.metadata.layout.pages).toHaveLength(1);
});
it("default page has standard main and sidebar columns", () => {
const page = defaultResumeData.metadata.layout.pages[0];
expect(page).toBeDefined();
expect(page?.main).toContain("experience");
expect(page?.main).toContain("education");
expect(page?.sidebar).toContain("skills");
});
it("uses serif font family for body", () => {
expect(defaultResumeData.metadata.typography.body.fontFamily).toBe("IBM Plex Serif");
});
it("default sidebar width is 35%", () => {
expect(defaultResumeData.metadata.layout.sidebarWidth).toBe(35);
});
it("custom sections are empty", () => {
expect(defaultResumeData.customSections).toEqual([]);
});
});
+51
View File
@@ -0,0 +1,51 @@
import { describe, expect, it } from "vitest";
import { templateSchema } from "./templates";
describe("templateSchema", () => {
it("accepts known template names", () => {
const validTemplates = [
"azurill",
"bronzor",
"chikorita",
"ditgar",
"ditto",
"gengar",
"glalie",
"kakuna",
"lapras",
"leafish",
"meowth",
"onyx",
"pikachu",
"rhyhorn",
];
for (const t of validTemplates) {
expect(templateSchema.safeParse(t).success).toBe(true);
}
});
it("rejects unknown template names", () => {
expect(templateSchema.safeParse("unknown").success).toBe(false);
expect(templateSchema.safeParse("").success).toBe(false);
expect(templateSchema.safeParse("ONYX").success).toBe(false); // case-sensitive
});
it("rejects non-string values", () => {
expect(templateSchema.safeParse(null).success).toBe(false);
expect(templateSchema.safeParse(undefined).success).toBe(false);
expect(templateSchema.safeParse(42).success).toBe(false);
});
it("returns the exact value for a valid template", () => {
const result = templateSchema.safeParse("onyx");
expect(result.success).toBe(true);
if (result.success) {
expect(result.data).toBe("onyx");
}
});
it("includes 14 templates", () => {
const validTemplates = templateSchema.options;
expect(validTemplates).toHaveLength(14);
});
});