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
+154
View File
@@ -0,0 +1,154 @@
import { act, render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { describe, expect, it } from "vitest";
import { ConfirmDialogProvider, useConfirm } from "./use-confirm";
const ConfirmRunner = ({
onResult,
title,
options,
}: {
onResult: (v: boolean) => void;
title: string;
options?: Parameters<ReturnType<typeof useConfirm>>[1];
}) => {
const confirm = useConfirm();
return (
<button
type="button"
onClick={async () => {
const result = await confirm(title, options);
onResult(result);
}}
>
Trigger
</button>
);
};
describe("useConfirm", () => {
it("throws when used outside ConfirmDialogProvider", () => {
// React 19 logs the error rather than throwing — we use renderHook+ErrorBoundary alt:
// Use the hook in a component and check that React surfaces the throw.
expect(() =>
render(
<ConfirmRunner
title="x"
onResult={() => {
/* noop */
}}
/>,
),
).toThrow(/useConfirm must be used within/);
});
it("renders children inside provider", () => {
render(
<ConfirmDialogProvider>
<div data-testid="child">child</div>
</ConfirmDialogProvider>,
);
expect(screen.getByTestId("child")).toBeInTheDocument();
});
it("opens an alert dialog when confirm is called", async () => {
const results: boolean[] = [];
render(
<ConfirmDialogProvider>
<ConfirmRunner title="Delete it?" onResult={(v) => results.push(v)} />
</ConfirmDialogProvider>,
);
await userEvent.click(screen.getByText("Trigger"));
expect(await screen.findByText("Delete it?")).toBeInTheDocument();
});
it("resolves true when confirm is clicked", async () => {
const results: boolean[] = [];
render(
<ConfirmDialogProvider>
<ConfirmRunner title="Sure?" onResult={(v) => results.push(v)} />
</ConfirmDialogProvider>,
);
await userEvent.click(screen.getByText("Trigger"));
await screen.findByText("Sure?");
await userEvent.click(screen.getByText("Confirm"));
// Wait for promise to resolve
await act(async () => {
await new Promise((r) => setTimeout(r, 0));
});
expect(results).toEqual([true]);
});
it("resolves false when cancel is clicked", async () => {
const results: boolean[] = [];
render(
<ConfirmDialogProvider>
<ConfirmRunner title="Cancel?" onResult={(v) => results.push(v)} />
</ConfirmDialogProvider>,
);
await userEvent.click(screen.getByText("Trigger"));
await screen.findByText("Cancel?");
await userEvent.click(screen.getByText("Cancel"));
await act(async () => {
await new Promise((r) => setTimeout(r, 0));
});
expect(results).toEqual([false]);
});
it("renders custom confirmText and cancelText", async () => {
render(
<ConfirmDialogProvider>
<ConfirmRunner
title="x"
options={{ confirmText: "Yes!", cancelText: "Nope" }}
onResult={() => {
/* noop */
}}
/>
</ConfirmDialogProvider>,
);
await userEvent.click(screen.getByText("Trigger"));
expect(await screen.findByText("Yes!")).toBeInTheDocument();
expect(screen.getByText("Nope")).toBeInTheDocument();
});
it("renders description when provided", async () => {
render(
<ConfirmDialogProvider>
<ConfirmRunner
title="x"
options={{ description: "This will permanently delete the record." }}
onResult={() => {
/* noop */
}}
/>
</ConfirmDialogProvider>,
);
await userEvent.click(screen.getByText("Trigger"));
expect(await screen.findByText("This will permanently delete the record.")).toBeInTheDocument();
});
it("falls back to 'Confirm' / 'Cancel' default labels", async () => {
render(
<ConfirmDialogProvider>
<ConfirmRunner
title="x"
onResult={() => {
/* noop */
}}
/>
</ConfirmDialogProvider>,
);
await userEvent.click(screen.getByText("Trigger"));
expect(await screen.findByText("Confirm")).toBeInTheDocument();
expect(screen.getByText("Cancel")).toBeInTheDocument();
});
});
@@ -0,0 +1,78 @@
import { act, renderHook } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest";
import { useControlledState } from "./use-controlled-state";
describe("useControlledState", () => {
it("returns the defaultValue when uncontrolled", () => {
const { result } = renderHook(() => useControlledState({ defaultValue: 0 }));
expect(result.current[0]).toBe(0);
});
it("returns the value when controlled", () => {
const { result } = renderHook(() => useControlledState({ value: 42, defaultValue: 0 }));
expect(result.current[0]).toBe(42);
});
it("updates internal state when value prop changes (controlled)", () => {
const { result, rerender } = renderHook(({ value }: { value: number }) => useControlledState({ value }), {
initialProps: { value: 1 },
});
expect(result.current[0]).toBe(1);
rerender({ value: 2 });
expect(result.current[0]).toBe(2);
});
it("updates state via setter when uncontrolled", () => {
const { result } = renderHook(() => useControlledState<number>({ defaultValue: 0 }));
act(() => {
result.current[1](10);
});
expect(result.current[0]).toBe(10);
});
it("calls onChange when state is updated", () => {
const onChange = vi.fn();
const { result } = renderHook(() => useControlledState<string>({ defaultValue: "a", onChange }));
act(() => {
result.current[1]("b");
});
expect(onChange).toHaveBeenCalledWith("b");
});
it("forwards extra args to onChange", () => {
const onChange = vi.fn();
const { result } = renderHook(() => useControlledState<string, [number, boolean]>({ defaultValue: "a", onChange }));
act(() => {
result.current[1]("b", 42, true);
});
expect(onChange).toHaveBeenCalledWith("b", 42, true);
});
it("does not call onChange when value prop changes from outside", () => {
const onChange = vi.fn();
const { rerender } = renderHook(({ value }: { value: number }) => useControlledState<number>({ value, onChange }), {
initialProps: { value: 1 },
});
rerender({ value: 2 });
// onChange is only called via setState, not the controlled-value sync effect.
expect(onChange).not.toHaveBeenCalled();
});
it("returns the same setter reference when onChange is stable", () => {
const onChange = vi.fn();
const { result, rerender } = renderHook(
({ value }: { value: number }) => useControlledState<number>({ value, onChange }),
{ initialProps: { value: 1 } },
);
const initialSetter = result.current[1];
rerender({ value: 2 });
expect(result.current[1]).toBe(initialSetter);
});
});
+75
View File
@@ -0,0 +1,75 @@
import { act, renderHook } from "@testing-library/react";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { useIsMobile } from "./use-mobile";
type ChangeListener = (e: { matches: boolean }) => void;
describe("useIsMobile", () => {
let listeners: ChangeListener[];
let matches: boolean;
let originalMatchMedia: typeof window.matchMedia;
beforeEach(() => {
listeners = [];
matches = false;
originalMatchMedia = window.matchMedia;
window.matchMedia = vi.fn().mockImplementation((query: string) => ({
matches,
media: query,
onchange: null,
addEventListener: vi.fn((_event: string, listener: ChangeListener) => {
listeners.push(listener);
}),
removeEventListener: vi.fn((_event: string, listener: ChangeListener) => {
const idx = listeners.indexOf(listener);
if (idx >= 0) listeners.splice(idx, 1);
}),
addListener: vi.fn(),
removeListener: vi.fn(),
dispatchEvent: vi.fn(),
}));
});
afterEach(() => {
window.matchMedia = originalMatchMedia;
});
it("returns false when matchMedia.matches is false", () => {
matches = false;
const { result } = renderHook(() => useIsMobile());
expect(result.current).toBe(false);
});
it("returns true when matchMedia.matches is true", () => {
matches = true;
const { result } = renderHook(() => useIsMobile());
expect(result.current).toBe(true);
});
it("uses the documented mobile breakpoint of 768px", () => {
renderHook(() => useIsMobile());
expect(window.matchMedia).toHaveBeenCalledWith("(max-width: 767px)");
});
it("updates when the media query changes", () => {
const { result } = renderHook(() => useIsMobile());
expect(result.current).toBe(false);
act(() => {
for (const listener of listeners) listener({ matches: true });
});
expect(result.current).toBe(true);
act(() => {
for (const listener of listeners) listener({ matches: false });
});
expect(result.current).toBe(false);
});
it("removes the change listener on unmount", () => {
const { unmount } = renderHook(() => useIsMobile());
expect(listeners).toHaveLength(1);
unmount();
expect(listeners).toHaveLength(0);
});
});
+177
View File
@@ -0,0 +1,177 @@
import { act, render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { describe, expect, it } from "vitest";
import { PromptDialogProvider, usePrompt } from "./use-prompt";
const PromptRunner = ({
onResult,
title,
options,
}: {
onResult: (v: string | null) => void;
title: string;
options?: Parameters<ReturnType<typeof usePrompt>>[1];
}) => {
const prompt = usePrompt();
return (
<button
type="button"
onClick={async () => {
const result = await prompt(title, options);
onResult(result);
}}
>
Trigger
</button>
);
};
describe("usePrompt", () => {
it("throws when used outside PromptDialogProvider", () => {
expect(() =>
render(
<PromptRunner
title="x"
onResult={() => {
/* noop */
}}
/>,
),
).toThrow(/usePrompt must be used within/);
});
it("renders children inside provider", () => {
render(
<PromptDialogProvider>
<div data-testid="child">child</div>
</PromptDialogProvider>,
);
expect(screen.getByTestId("child")).toBeInTheDocument();
});
it("opens an alert dialog when prompt is called", async () => {
render(
<PromptDialogProvider>
<PromptRunner
title="Enter name"
onResult={() => {
/* noop */
}}
/>
</PromptDialogProvider>,
);
await userEvent.click(screen.getByText("Trigger"));
expect(await screen.findByText("Enter name")).toBeInTheDocument();
});
it("resolves with input value when confirm clicked", async () => {
const results: (string | null)[] = [];
render(
<PromptDialogProvider>
<PromptRunner title="Name?" onResult={(v) => results.push(v)} />
</PromptDialogProvider>,
);
await userEvent.click(screen.getByText("Trigger"));
await screen.findByText("Name?");
const input = screen.getByRole("textbox");
await userEvent.type(input, "Alice");
await userEvent.click(screen.getByText("Confirm"));
await act(async () => {
await new Promise((r) => setTimeout(r, 0));
});
expect(results).toEqual(["Alice"]);
});
it("resolves null when cancel is clicked", async () => {
const results: (string | null)[] = [];
render(
<PromptDialogProvider>
<PromptRunner title="x" onResult={(v) => results.push(v)} />
</PromptDialogProvider>,
);
await userEvent.click(screen.getByText("Trigger"));
await screen.findByText("x");
await userEvent.click(screen.getByText("Cancel"));
await act(async () => {
await new Promise((r) => setTimeout(r, 0));
});
expect(results).toEqual([null]);
});
it("uses defaultValue as initial input value", async () => {
render(
<PromptDialogProvider>
<PromptRunner
title="x"
options={{ defaultValue: "default-name" }}
onResult={() => {
/* noop */
}}
/>
</PromptDialogProvider>,
);
await userEvent.click(screen.getByText("Trigger"));
const input = await screen.findByRole("textbox");
expect(input).toHaveValue("default-name");
});
it("submits on Enter key", async () => {
const results: (string | null)[] = [];
render(
<PromptDialogProvider>
<PromptRunner title="x" onResult={(v) => results.push(v)} />
</PromptDialogProvider>,
);
await userEvent.click(screen.getByText("Trigger"));
const input = await screen.findByRole("textbox");
await userEvent.type(input, "via-enter{Enter}");
await act(async () => {
await new Promise((r) => setTimeout(r, 0));
});
expect(results).toEqual(["via-enter"]);
});
it("renders custom confirmText and cancelText", async () => {
render(
<PromptDialogProvider>
<PromptRunner
title="x"
options={{ confirmText: "Save", cancelText: "Discard" }}
onResult={() => {
/* noop */
}}
/>
</PromptDialogProvider>,
);
await userEvent.click(screen.getByText("Trigger"));
expect(await screen.findByText("Save")).toBeInTheDocument();
expect(screen.getByText("Discard")).toBeInTheDocument();
});
it("renders description when provided", async () => {
render(
<PromptDialogProvider>
<PromptRunner
title="x"
options={{ description: "Explain why" }}
onResult={() => {
/* noop */
}}
/>
</PromptDialogProvider>,
);
await userEvent.click(screen.getByText("Trigger"));
expect(await screen.findByText("Explain why")).toBeInTheDocument();
});
});