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
+124
View File
@@ -0,0 +1,124 @@
/**
* @vitest-environment jsdom
*/
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { useDialogStore } from "./store";
describe("useDialogStore", () => {
beforeEach(() => {
vi.useFakeTimers();
// Reset store state between tests
useDialogStore.setState({ open: false, activeDialog: null, onBeforeClose: null });
});
afterEach(() => {
vi.useRealTimers();
});
describe("openDialog", () => {
it("opens a dialog and sets activeDialog", () => {
useDialogStore.getState().openDialog("api-key.create", undefined);
const state = useDialogStore.getState();
expect(state.open).toBe(true);
expect(state.activeDialog?.type).toBe("api-key.create");
});
it("clears any existing onBeforeClose handler", () => {
useDialogStore.setState({ onBeforeClose: () => true });
useDialogStore.getState().openDialog("resume.create", undefined);
expect(useDialogStore.getState().onBeforeClose).toBeNull();
});
it("preserves dialog data for typed payloads", () => {
const data = { id: "r1", name: "My Resume", slug: "my-resume", tags: [] };
useDialogStore.getState().openDialog("resume.update", data);
const active = useDialogStore.getState().activeDialog;
expect(active?.type).toBe("resume.update");
if (active?.type === "resume.update") {
expect(active.data).toEqual(data);
}
});
});
describe("closeDialog", () => {
it("immediately sets open to false", () => {
useDialogStore.setState({
open: true,
activeDialog: { type: "api-key.create", data: undefined },
});
useDialogStore.getState().closeDialog();
expect(useDialogStore.getState().open).toBe(false);
});
it("clears activeDialog after 300ms (animation finished)", () => {
useDialogStore.setState({
open: true,
activeDialog: { type: "api-key.create", data: undefined },
});
useDialogStore.getState().closeDialog();
expect(useDialogStore.getState().activeDialog).not.toBeNull();
vi.advanceTimersByTime(300);
expect(useDialogStore.getState().activeDialog).toBeNull();
});
});
describe("onOpenChange", () => {
it("opens via set without invoking onBeforeClose", () => {
const onBefore = vi.fn();
useDialogStore.setState({ onBeforeClose: onBefore });
useDialogStore.getState().onOpenChange(true);
expect(useDialogStore.getState().open).toBe(true);
expect(onBefore).not.toHaveBeenCalled();
});
it("closes immediately when no onBeforeClose handler", () => {
useDialogStore.setState({ open: true, activeDialog: { type: "api-key.create", data: undefined } });
useDialogStore.getState().onOpenChange(false);
expect(useDialogStore.getState().open).toBe(false);
});
it("invokes onBeforeClose when closing", async () => {
const handler = vi.fn().mockResolvedValue(true);
useDialogStore.setState({
open: true,
activeDialog: { type: "api-key.create", data: undefined },
onBeforeClose: handler,
});
useDialogStore.getState().onOpenChange(false);
await vi.waitFor(() => expect(handler).toHaveBeenCalled());
});
it("calls cancel when onBeforeClose returns false", async () => {
const handler = vi.fn().mockResolvedValue(false);
const cancel = vi.fn();
useDialogStore.setState({
open: true,
activeDialog: { type: "api-key.create", data: undefined },
onBeforeClose: handler,
});
useDialogStore.getState().onOpenChange(false, { cancel });
await vi.waitFor(() => expect(cancel).toHaveBeenCalled());
});
});
describe("setOnBeforeClose", () => {
it("sets and clears the handler", () => {
const handler = () => true;
useDialogStore.getState().setOnBeforeClose(handler);
expect(useDialogStore.getState().onBeforeClose).toBe(handler);
useDialogStore.getState().setOnBeforeClose(null);
expect(useDialogStore.getState().onBeforeClose).toBeNull();
});
});
});
+102
View File
@@ -0,0 +1,102 @@
import { describe, expect, it } from "vitest";
import { ORPCError } from "@orpc/client";
import { getOrpcErrorMessage, getReadableErrorMessage, getResumeErrorMessage } from "./error-message";
describe("getReadableErrorMessage", () => {
it("returns the string error directly", () => {
expect(getReadableErrorMessage("explicit error", "fallback")).toBe("explicit error");
});
it("returns Error.message", () => {
expect(getReadableErrorMessage(new Error("boom"), "fallback")).toBe("boom");
});
it("returns fallback for unknown shapes", () => {
expect(getReadableErrorMessage({ random: "object" }, "fallback")).toBe("fallback");
expect(getReadableErrorMessage(null, "fallback")).toBe("fallback");
expect(getReadableErrorMessage(undefined, "fallback")).toBe("fallback");
expect(getReadableErrorMessage(42, "fallback")).toBe("fallback");
});
it("returns fallback for empty string error (falsy)", () => {
expect(getReadableErrorMessage("", "fallback")).toBe("fallback");
});
it("returns fallback for Error with empty message", () => {
expect(getReadableErrorMessage(new Error(""), "fallback")).toBe("fallback");
});
});
describe("getOrpcErrorMessage", () => {
it("delegates to getReadableErrorMessage for non-ORPCErrors", () => {
expect(getOrpcErrorMessage(new Error("boom"), { fallback: "fallback" })).toBe("boom");
expect(getOrpcErrorMessage("string error", { fallback: "fallback" })).toBe("string error");
});
it("uses byCode mapping when present", () => {
const error = new ORPCError("RESUME_LOCKED");
expect(
getOrpcErrorMessage(error, {
fallback: "fallback",
byCode: { RESUME_LOCKED: "It is locked." },
}),
).toBe("It is locked.");
});
it("returns server message when allowServerMessage and message is set", () => {
const error = new ORPCError("OTHER", { message: "Server-provided message" });
expect(
getOrpcErrorMessage(error, {
fallback: "fallback",
allowServerMessage: true,
}),
).toBe("Server-provided message");
});
it("falls back when allowServerMessage is false even if message set", () => {
const error = new ORPCError("OTHER", { message: "Server-provided message" });
expect(getOrpcErrorMessage(error, { fallback: "fallback" })).toBe("fallback");
});
it("byCode takes precedence over allowServerMessage", () => {
const error = new ORPCError("RESUME_LOCKED", { message: "Server msg" });
expect(
getOrpcErrorMessage(error, {
fallback: "fallback",
byCode: { RESUME_LOCKED: "It is locked." },
allowServerMessage: true,
}),
).toBe("It is locked.");
});
it("returns fallback when no mapping or server message", () => {
const error = new ORPCError("UNKNOWN_CODE");
expect(getOrpcErrorMessage(error, { fallback: "fallback" })).toBe("fallback");
});
});
describe("getResumeErrorMessage", () => {
it("returns mapped message for RESUME_SLUG_ALREADY_EXISTS", () => {
const error = new ORPCError("RESUME_SLUG_ALREADY_EXISTS");
expect(getResumeErrorMessage(error)).toBe("A resume with this slug already exists.");
});
it("returns mapped message for RESUME_LOCKED", () => {
const error = new ORPCError("RESUME_LOCKED");
expect(getResumeErrorMessage(error)).toBe("This resume is locked. Unlock it first to make changes.");
});
it("returns generic fallback for unknown codes", () => {
const error = new ORPCError("UNKNOWN");
expect(getResumeErrorMessage(error)).toBe("Something went wrong. Please try again.");
});
it("returns fallback for plain Error (delegates to getOrpcErrorMessage)", () => {
// Plain Error gets readable message
expect(getResumeErrorMessage(new Error("boom"))).toBe("boom");
});
it("returns fallback for unknown shape", () => {
expect(getResumeErrorMessage(null)).toBe("Something went wrong. Please try again.");
});
});
+89
View File
@@ -0,0 +1,89 @@
import { describe, expect, it } from "vitest";
import { isLocale, isRTL, resolveLocale } from "./locale";
describe("isLocale", () => {
it("returns true for known locale en-US", () => {
expect(isLocale("en-US")).toBe(true);
});
it("returns true for de-DE", () => {
expect(isLocale("de-DE")).toBe(true);
});
it("returns true for zh-CN", () => {
expect(isLocale("zh-CN")).toBe(true);
});
it("returns false for unknown locale", () => {
expect(isLocale("xx-YY")).toBe(false);
});
it("returns false for empty string", () => {
expect(isLocale("")).toBe(false);
});
it("returns false for malformed locale", () => {
expect(isLocale("not a locale")).toBe(false);
});
it("is case-sensitive", () => {
expect(isLocale("en-us")).toBe(false);
});
});
describe("resolveLocale", () => {
it("returns the locale unchanged when valid", () => {
expect(resolveLocale("fr-FR")).toBe("fr-FR");
});
it("returns en-US default for invalid locale", () => {
expect(resolveLocale("xx-YY")).toBe("en-US");
});
it("returns en-US default for empty string", () => {
expect(resolveLocale("")).toBe("en-US");
});
});
describe("isRTL", () => {
it("returns true for Arabic", () => {
expect(isRTL("ar-SA")).toBe(true);
});
it("returns true for Hebrew", () => {
expect(isRTL("he-IL")).toBe(true);
});
it("returns true for Persian/Farsi", () => {
expect(isRTL("fa-IR")).toBe(true);
});
it("returns true for Urdu", () => {
expect(isRTL("ur-PK")).toBe(true);
});
it("returns false for English", () => {
expect(isRTL("en-US")).toBe(false);
});
it("returns false for German", () => {
expect(isRTL("de-DE")).toBe(false);
});
it("returns false for Chinese", () => {
expect(isRTL("zh-CN")).toBe(false);
});
it("returns false for unknown locale", () => {
expect(isRTL("xyz-XX")).toBe(false);
});
it("matches prefix case-insensitively (lowercase prefix)", () => {
expect(isRTL("AR-SA")).toBe(true);
});
it("works with locale-only string (no region)", () => {
expect(isRTL("ar")).toBe(true);
expect(isRTL("en")).toBe(false);
});
});
+72
View File
@@ -0,0 +1,72 @@
import { describe, expect, it } from "vitest";
import { pwaHeadMetaTags, pwaManifest, pwaServiceWorkerRegistrationScript } from "./pwa";
describe("pwaManifest", () => {
it("uses the same theme color and background color (dark zinc)", () => {
expect(pwaManifest.theme_color).toBe(pwaManifest.background_color);
});
it("declares standalone display and portrait orientation", () => {
expect(pwaManifest.display).toBe("standalone");
expect(pwaManifest.orientation).toBe("portrait");
});
it("scopes to root '/'", () => {
expect(pwaManifest.scope).toBe("/");
expect(pwaManifest.start_url).toContain("/");
});
it("declares all icon sizes (64, 192, 512, maskable, favicon)", () => {
const sizes = pwaManifest.icons?.map((i) => i.sizes) ?? [];
expect(sizes).toContain("64x64");
expect(sizes).toContain("192x192");
expect(sizes).toContain("512x512");
});
it("declares at least one maskable icon", () => {
const maskable = pwaManifest.icons?.find((i) => i.purpose === "maskable");
expect(maskable).toBeDefined();
});
it("includes both wide and narrow screenshots for proper installation UI", () => {
const wide = pwaManifest.screenshots?.filter((s) => s.form_factor === "wide");
const narrow = pwaManifest.screenshots?.filter((s) => s.form_factor === "narrow");
expect(wide?.length).toBeGreaterThan(0);
expect(narrow?.length).toBeGreaterThan(0);
});
it("includes 'resume' in categories for app stores", () => {
expect(pwaManifest.categories).toContain("resume");
});
});
describe("pwaHeadMetaTags", () => {
it("includes theme-color meta tag", () => {
expect(pwaHeadMetaTags).toContainEqual(expect.objectContaining({ name: "theme-color" }));
});
it("declares apple-mobile-web-app capable", () => {
const tag = pwaHeadMetaTags.find((t) => t.name === "apple-mobile-web-app-capable");
expect(tag?.content).toBe("yes");
});
it("uses 'black-translucent' status bar style for iOS", () => {
const tag = pwaHeadMetaTags.find((t) => t.name === "apple-mobile-web-app-status-bar-style");
expect(tag?.content).toBe("black-translucent");
});
});
describe("pwaServiceWorkerRegistrationScript", () => {
it("registers the service worker only when supported", () => {
expect(pwaServiceWorkerRegistrationScript).toContain('"serviceWorker" in navigator');
});
it("registers /sw.js with root scope", () => {
expect(pwaServiceWorkerRegistrationScript).toContain("/sw.js");
expect(pwaServiceWorkerRegistrationScript).toContain('scope: "/"');
});
it("registers on window load to avoid blocking initial paint", () => {
expect(pwaServiceWorkerRegistrationScript).toContain('addEventListener("load"');
});
});
@@ -0,0 +1,50 @@
import { describe, expect, it } from "vitest";
import { makeSectionItem } from "./make-section-item";
describe("makeSectionItem", () => {
it("clones the provided item but generates a fresh id", () => {
const original = { id: "original-id", name: "Skill" };
const result = makeSectionItem({ id: "default", name: "" }, original);
expect(result.name).toBe("Skill");
expect(result.id).not.toBe("original-id");
expect(result.id.length).toBeGreaterThan(0);
});
it("uses defaultItem when no item is provided", () => {
const defaultItem = { id: "default", name: "", level: 0 };
const result = makeSectionItem(defaultItem);
expect(result.name).toBe("");
expect(result.level).toBe(0);
expect(result.id).not.toBe("default");
});
it("does not mutate the input item when duplicating", () => {
const original = { id: "original-id", value: "test" };
const before = JSON.stringify(original);
makeSectionItem({ id: "default", value: "" }, original);
expect(JSON.stringify(original)).toBe(before);
});
it("does not mutate the defaultItem", () => {
const defaultItem = { id: "default", value: "x" };
const before = JSON.stringify(defaultItem);
makeSectionItem(defaultItem);
expect(JSON.stringify(defaultItem)).toBe(before);
});
it("generates a unique id per call", () => {
const a = makeSectionItem({ id: "default", name: "" });
const b = makeSectionItem({ id: "default", name: "" });
expect(a.id).not.toBe(b.id);
});
it("preserves all other fields when duplicating", () => {
const item = { id: "x", a: 1, b: { c: 2 }, d: [3] };
const result = makeSectionItem({ id: "default", a: 0, b: { c: 0 }, d: [] }, item);
expect(result.a).toBe(1);
expect(result.b).toEqual({ c: 2 });
expect(result.d).toEqual([3]);
});
});
+246
View File
@@ -0,0 +1,246 @@
import { beforeAll, describe, expect, it } from "vitest";
import { i18n } from "@lingui/core";
import { produce } from "immer";
import { defaultResumeData } from "@reactive-resume/schema/resume/default";
import {
addItemToSection,
createCustomSectionWithItem,
createPageWithSection,
getCompatibleMoveTargets,
getSourceSectionTitle,
removeItemFromSource,
} from "./move-item";
beforeAll(() => {
// Lingui requires an active locale before t`...` macros can render.
i18n.loadAndActivate({ locale: "en", messages: {} });
});
describe("getSourceSectionTitle", () => {
it("returns custom section title for custom sections", () => {
const data = produce(defaultResumeData, (draft) => {
draft.customSections.push({
id: "ext-1",
type: "cover-letter",
title: "My Custom Title",
columns: 1,
hidden: false,
items: [],
});
});
expect(getSourceSectionTitle(data, "cover-letter", "ext-1")).toBe("My Custom Title");
});
it("returns localized default title when customSectionId is undefined", () => {
// Default section title comes from t`...` macro — we just check it's a non-empty string
expect(getSourceSectionTitle(defaultResumeData, "experience").length).toBeGreaterThan(0);
});
it("falls back to default title when custom section is not found", () => {
expect(getSourceSectionTitle(defaultResumeData, "experience", "non-existent").length).toBeGreaterThan(0);
});
});
describe("getCompatibleMoveTargets", () => {
it("excludes the source section itself", () => {
// Default has experience in main; trying to move from experience to other targets
const targets = getCompatibleMoveTargets(defaultResumeData, "experience", undefined);
expect(targets[0]?.sections.find((s) => s.sectionId === "experience")).toBeUndefined();
});
it("returns only custom sections of the same type as source", () => {
const data = produce(defaultResumeData, (draft) => {
draft.customSections.push({
id: "ext-1",
type: "cover-letter",
title: "Cover Letter",
columns: 1,
hidden: false,
items: [],
});
draft.metadata.layout.pages[0].main.push("ext-1");
});
// Source is also a cover-letter custom section, but with different id
const targets = getCompatibleMoveTargets(data, "cover-letter", "different-id");
expect(targets[0]?.sections.find((s) => s.sectionId === "ext-1")).toBeDefined();
});
it("returns empty per page when no compatible targets", () => {
const targets = getCompatibleMoveTargets(defaultResumeData, "cover-letter", undefined);
// No custom sections of cover-letter type in default
for (const page of targets) {
expect(page.sections).toHaveLength(0);
}
});
it("returns one entry per page", () => {
const targets = getCompatibleMoveTargets(defaultResumeData, "experience", undefined);
expect(targets).toHaveLength(defaultResumeData.metadata.layout.pages.length);
});
});
describe("removeItemFromSource", () => {
it("removes an item from a built-in section and returns its id", () => {
const initial = produce(defaultResumeData, (draft) => {
draft.sections.skills.items.push({
id: "skill-1",
hidden: false,
icon: "",
iconColor: "",
name: "Go",
proficiency: "",
level: 4,
keywords: [],
});
});
// Capture the id before produce revokes the draft proxy.
let removedId: string | undefined;
const result = produce(initial, (draft) => {
const removed = removeItemFromSource(draft, "skill-1", "skills");
removedId = (removed as { id: string } | null)?.id;
});
expect(result.sections.skills.items).toHaveLength(0);
expect(removedId).toBe("skill-1");
});
it("returns null when item id is not found in built-in section", () => {
let removed: unknown;
produce(defaultResumeData, (draft) => {
removed = removeItemFromSource(draft, "non-existent", "skills");
});
expect(removed).toBeNull();
});
it("removes an item from a custom section and returns its id", () => {
const initial = produce(defaultResumeData, (draft) => {
draft.customSections.push({
id: "ext-1",
type: "cover-letter",
title: "",
columns: 1,
hidden: false,
items: [{ id: "i1" } as never],
});
});
let removedId: string | undefined;
const result = produce(initial, (draft) => {
const removed = removeItemFromSource(draft, "i1", "cover-letter", "ext-1");
removedId = (removed as { id?: string } | null)?.id;
});
expect(result.customSections[0]?.items).toHaveLength(0);
expect(removedId).toBe("i1");
});
it("returns null when custom section does not exist", () => {
let removed: unknown;
produce(defaultResumeData, (draft) => {
removed = removeItemFromSource(draft, "i1", "cover-letter", "non-existent");
});
expect(removed).toBeNull();
});
});
describe("addItemToSection", () => {
it("adds to a matching built-in section", () => {
const item = {
id: "skill-x",
hidden: false,
icon: "",
iconColor: "",
name: "Rust",
proficiency: "",
level: 4,
keywords: [],
};
const result = produce(defaultResumeData, (draft) => {
addItemToSection(draft, item, "skills", "skills");
});
expect(result.sections.skills.items).toHaveLength(1);
});
it("adds to a custom section by id", () => {
const initial = produce(defaultResumeData, (draft) => {
draft.customSections.push({
id: "ext-1",
type: "cover-letter",
title: "",
columns: 1,
hidden: false,
items: [],
});
});
const result = produce(initial, (draft) => {
addItemToSection(draft, { id: "i1" } as never, "ext-1", "cover-letter");
});
expect(result.customSections[0]?.items).toHaveLength(1);
});
it("does not add when target id does not match anything", () => {
const result = produce(defaultResumeData, (draft) => {
addItemToSection(draft, { id: "x" } as never, "non-existent", "skills");
});
expect(result.sections.skills.items).toHaveLength(0);
});
});
describe("createCustomSectionWithItem", () => {
it("creates a new custom section and adds it to the target page main", () => {
let newSectionId = "";
const result = produce(defaultResumeData, (draft) => {
newSectionId = createCustomSectionWithItem(draft, { id: "i1" } as never, "cover-letter", "My Section", 0);
});
expect(result.customSections).toHaveLength(1);
expect(result.customSections[0]?.title).toBe("My Section");
expect(result.customSections[0]?.items).toHaveLength(1);
expect(result.metadata.layout.pages[0]?.main).toContain(newSectionId);
});
it("returns the generated section id", () => {
let newSectionId = "";
produce(defaultResumeData, (draft) => {
newSectionId = createCustomSectionWithItem(draft, { id: "i1" } as never, "cover-letter", "X", 0);
});
expect(newSectionId.length).toBeGreaterThan(0);
});
it("does not crash on out-of-range page index (no main column to push to)", () => {
// targetPageIndex=99 — page does not exist; section should still be created
const result = produce(defaultResumeData, (draft) => {
createCustomSectionWithItem(draft, { id: "i1" } as never, "cover-letter", "X", 99);
});
expect(result.customSections).toHaveLength(1);
});
});
describe("createPageWithSection", () => {
it("creates a new page with the new custom section in main", () => {
const initialPageCount = defaultResumeData.metadata.layout.pages.length;
const result = produce(defaultResumeData, (draft) => {
createPageWithSection(draft, { id: "i1" } as never, "cover-letter", "My New Page");
});
expect(result.metadata.layout.pages).toHaveLength(initialPageCount + 1);
const newPage = result.metadata.layout.pages.at(-1);
expect(newPage?.main).toHaveLength(1);
expect(newPage?.sidebar).toEqual([]);
expect(newPage?.fullWidth).toBe(false);
});
it("adds the custom section to customSections array", () => {
const result = produce(defaultResumeData, (draft) => {
createPageWithSection(draft, { id: "i1" } as never, "cover-letter", "My Section");
});
expect(result.customSections).toHaveLength(1);
expect(result.customSections[0]?.title).toBe("My Section");
});
});
@@ -0,0 +1,99 @@
import { describe, expect, it } from "vitest";
import { produce } from "immer";
import { defaultResumeData } from "@reactive-resume/schema/resume/default";
import { createSectionItem, updateSectionItem } from "./section-actions";
describe("createSectionItem", () => {
it("appends to a built-in section's items array", () => {
const result = produce(defaultResumeData, (draft) => {
createSectionItem(draft, "skills", { id: "1", name: "Go" });
});
expect(result.sections.skills.items).toHaveLength(1);
expect((result.sections.skills.items[0] as { name?: string })?.name).toBe("Go");
});
it("appends to a custom section by id", () => {
const initial = produce(defaultResumeData, (draft) => {
draft.customSections.push({
id: "custom-1",
type: "cover-letter",
title: "Cover Letter",
columns: 1,
hidden: false,
items: [],
});
});
const result = produce(initial, (draft) => {
createSectionItem(draft, "skills", { id: "1", text: "Custom item" }, "custom-1");
});
const customSection = result.customSections.find((s) => s.id === "custom-1");
expect(customSection?.items).toHaveLength(1);
// Built-in section is untouched
expect(result.sections.skills.items).toHaveLength(0);
});
it("does nothing when customSectionId does not match", () => {
const result = produce(defaultResumeData, (draft) => {
createSectionItem(draft, "skills", { id: "1", name: "x" }, "non-existent");
});
// Skills not touched, no custom section to modify
expect(result.sections.skills.items).toHaveLength(0);
});
});
describe("updateSectionItem", () => {
it("replaces the matching item in a built-in section", () => {
const initial = produce(defaultResumeData, (draft) => {
createSectionItem(draft, "skills", { id: "abc", name: "Go", level: 4 });
});
const result = produce(initial, (draft) => {
updateSectionItem(draft, "skills", { id: "abc", name: "Go", level: 5 });
});
const item = result.sections.skills.items[0] as { id?: string; name?: string; level?: number };
expect(item?.level).toBe(5);
});
it("does nothing when item id does not exist", () => {
const initial = produce(defaultResumeData, (draft) => {
createSectionItem(draft, "skills", { id: "abc", name: "Go" });
});
const before = JSON.stringify(initial.sections.skills);
const result = produce(initial, (draft) => {
updateSectionItem(draft, "skills", { id: "non-existent", name: "X" });
});
expect(JSON.stringify(result.sections.skills)).toBe(before);
});
it("replaces the matching item in a custom section", () => {
const initial = produce(defaultResumeData, (draft) => {
draft.customSections.push({
id: "custom-1",
type: "cover-letter",
title: "",
columns: 1,
hidden: false,
items: [{ id: "x", value: "old" } as never],
});
});
const result = produce(initial, (draft) => {
updateSectionItem(draft, "skills", { id: "x", value: "new" }, "custom-1");
});
const customSection = result.customSections.find((s) => s.id === "custom-1");
expect((customSection?.items[0] as { value?: string }).value).toBe("new");
});
it("does nothing when custom section is not found", () => {
const before = JSON.stringify(defaultResumeData);
const result = produce(defaultResumeData, (draft) => {
updateSectionItem(draft, "skills", { id: "x", value: "new" }, "non-existent");
});
expect(JSON.stringify(result)).toBe(before);
});
});
+33
View File
@@ -0,0 +1,33 @@
import { describe, expect, it } from "vitest";
import { isTheme, themeMap } from "./theme";
describe("isTheme", () => {
it("returns true for 'light'", () => {
expect(isTheme("light")).toBe(true);
});
it("returns true for 'dark'", () => {
expect(isTheme("dark")).toBe(true);
});
it("returns false for unknown theme", () => {
expect(isTheme("auto")).toBe(false);
expect(isTheme("system")).toBe(false);
});
it("returns false for empty string", () => {
expect(isTheme("")).toBe(false);
});
it("is case-sensitive", () => {
expect(isTheme("Light")).toBe(false);
expect(isTheme("DARK")).toBe(false);
});
});
describe("themeMap", () => {
it("includes a descriptor for each theme", () => {
expect(themeMap.light).toBeDefined();
expect(themeMap.dark).toBeDefined();
});
});