Files
Reactive-Resume/packages/ui/src/hooks/use-controlled-state.test.tsx
T
Amruth Pillai 6a01207b6b 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.
2026-05-10 20:00:07 +02:00

79 lines
2.5 KiB
TypeScript

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);
});
});