Files
Reactive-Resume/packages/ui/src/hooks/use-mobile.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

76 lines
2.1 KiB
TypeScript

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