This commit is contained in:
Amruth Pillai
2026-04-02 00:14:54 +02:00
parent d9a24448e8
commit 4fd43657dc
175 changed files with 11886 additions and 1840 deletions
+122
View File
@@ -0,0 +1,122 @@
import { act, render, renderHook, screen } from "@testing-library/react";
import React from "react";
import { afterEach, describe, expect, it } from "vite-plus/test";
import { ConfirmDialogProvider, useConfirm } from "./use-confirm";
afterEach(() => {
document.body.innerHTML = "";
});
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
function wrapper({ children }: { children: React.ReactNode }) {
return <ConfirmDialogProvider>{children}</ConfirmDialogProvider>;
}
/** A test component that triggers confirm and stores the result */
function ConfirmTester() {
const confirm = useConfirm();
const [result, setResult] = React.useState<boolean | null>(null);
return (
<div>
<button
onClick={async () => {
const confirmed = await confirm("Delete this?", {
description: "This action cannot be undone.",
confirmText: "Delete",
cancelText: "Keep",
});
setResult(confirmed);
}}
>
Trigger
</button>
{result !== null && <span data-testid="result">{String(result)}</span>}
</div>
);
}
// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------
describe("useConfirm", () => {
it("throws when used outside provider", () => {
expect(() => {
renderHook(() => useConfirm());
}).toThrow("useConfirm must be used within a <ConfirmDialogProvider />");
});
it("returns a function when used inside provider", () => {
const { result } = renderHook(() => useConfirm(), { wrapper });
expect(typeof result.current).toBe("function");
});
it("confirm() returns a promise", () => {
const { result } = renderHook(() => useConfirm(), { wrapper });
let promise: Promise<boolean> | undefined;
act(() => {
promise = result.current("Are you sure?");
});
expect(promise).toBeInstanceOf(Promise);
});
it("opens dialog with title and description when confirm is called", async () => {
render(
<ConfirmDialogProvider>
<ConfirmTester />
</ConfirmDialogProvider>,
);
await act(async () => {
screen.getByText("Trigger").click();
});
expect(screen.getByText("Delete this?")).toBeDefined();
expect(screen.getByText("This action cannot be undone.")).toBeDefined();
expect(screen.getByText("Delete")).toBeDefined();
expect(screen.getByText("Keep")).toBeDefined();
});
it("resolves true when confirm button is clicked", async () => {
render(
<ConfirmDialogProvider>
<ConfirmTester />
</ConfirmDialogProvider>,
);
await act(async () => {
screen.getByText("Trigger").click();
});
await act(async () => {
screen.getByText("Delete").click();
});
expect(screen.getByTestId("result").textContent).toBe("true");
});
it("resolves false when cancel button is clicked", async () => {
render(
<ConfirmDialogProvider>
<ConfirmTester />
</ConfirmDialogProvider>,
);
await act(async () => {
screen.getByText("Trigger").click();
});
await act(async () => {
screen.getByText("Keep").click();
});
expect(screen.getByTestId("result").textContent).toBe("false");
});
});
+109
View File
@@ -0,0 +1,109 @@
import { act, renderHook } from "@testing-library/react";
import { describe, expect, it, vi } from "vite-plus/test";
import { useControlledState } from "./use-controlled-state";
describe("useControlledState", () => {
describe("uncontrolled mode (no value prop)", () => {
it("uses defaultValue as initial state", () => {
const { result } = renderHook(() => useControlledState({ defaultValue: "hello" }));
expect(result.current[0]).toBe("hello");
});
it("updates internal state when setter is called", () => {
const { result } = renderHook(() => useControlledState({ defaultValue: 0 }));
act(() => {
result.current[1](42);
});
expect(result.current[0]).toBe(42);
});
it("calls onChange when setter is called", () => {
const onChange = vi.fn();
const { result } = renderHook(() => useControlledState({ defaultValue: "a", onChange }));
act(() => {
result.current[1]("b");
});
expect(onChange).toHaveBeenCalledWith("b");
});
it("passes extra args to onChange", () => {
const onChange = vi.fn();
const { result } = renderHook(() => useControlledState<string, [number]>({ defaultValue: "a", onChange }));
act(() => {
result.current[1]("b", 99);
});
expect(onChange).toHaveBeenCalledWith("b", 99);
});
});
describe("controlled mode (value prop provided)", () => {
it("uses value prop as initial state", () => {
const { result } = renderHook(() => useControlledState({ value: "controlled" }));
expect(result.current[0]).toBe("controlled");
});
it("syncs internal state when value prop changes", () => {
let value = "first";
const { result, rerender } = renderHook(() => useControlledState({ value }));
expect(result.current[0]).toBe("first");
value = "second";
rerender();
expect(result.current[0]).toBe("second");
});
it("still calls onChange when setter is called in controlled mode", () => {
const onChange = vi.fn();
const { result } = renderHook(() => useControlledState({ value: "x", onChange }));
act(() => {
result.current[1]("y");
});
expect(onChange).toHaveBeenCalledWith("y");
});
});
describe("edge cases", () => {
it("handles undefined defaultValue", () => {
const { result } = renderHook(() => useControlledState({}));
expect(result.current[0]).toBeUndefined();
});
it("handles boolean values", () => {
const { result } = renderHook(() => useControlledState({ defaultValue: false }));
expect(result.current[0]).toBe(false);
act(() => {
result.current[1](true);
});
expect(result.current[0]).toBe(true);
});
it("handles object values", () => {
const obj = { key: "value" };
const { result } = renderHook(() => useControlledState({ defaultValue: obj }));
expect(result.current[0]).toBe(obj);
});
it("works without onChange callback", () => {
const { result } = renderHook(() => useControlledState({ defaultValue: 5 }));
act(() => {
result.current[1](10);
});
expect(result.current[0]).toBe(10);
});
});
});
+104
View File
@@ -0,0 +1,104 @@
import { act, renderHook } from "@testing-library/react";
import { afterEach, beforeEach, describe, expect, it, vi } from "vite-plus/test";
import { useIsMobile } from "./use-mobile";
// ---------------------------------------------------------------------------
// Mock window.matchMedia
// ---------------------------------------------------------------------------
type MockMatchMedia = {
matches: boolean;
listeners: Array<(e: { matches: boolean }) => void>;
trigger: (matches: boolean) => void;
};
let mockMql: MockMatchMedia;
beforeEach(() => {
mockMql = {
matches: false,
listeners: [],
trigger(matches: boolean) {
this.matches = matches;
for (const listener of this.listeners) {
listener({ matches });
}
},
};
vi.stubGlobal(
"matchMedia",
vi.fn(() => ({
get matches() {
return mockMql.matches;
},
addEventListener: (_event: string, cb: (e: { matches: boolean }) => void) => {
mockMql.listeners.push(cb);
},
removeEventListener: (_event: string, cb: (e: { matches: boolean }) => void) => {
mockMql.listeners = mockMql.listeners.filter((l) => l !== cb);
},
})),
);
});
afterEach(() => {
vi.unstubAllGlobals();
});
// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------
describe("useIsMobile", () => {
it("returns false on desktop-sized screens", () => {
mockMql.matches = false;
const { result } = renderHook(() => useIsMobile());
expect(result.current).toBe(false);
});
it("returns true on mobile-sized screens", () => {
mockMql.matches = true;
const { result } = renderHook(() => useIsMobile());
expect(result.current).toBe(true);
});
it("updates when screen size changes to mobile", () => {
mockMql.matches = false;
const { result } = renderHook(() => useIsMobile());
expect(result.current).toBe(false);
act(() => {
mockMql.trigger(true);
});
expect(result.current).toBe(true);
});
it("updates when screen size changes to desktop", () => {
mockMql.matches = true;
const { result } = renderHook(() => useIsMobile());
expect(result.current).toBe(true);
act(() => {
mockMql.trigger(false);
});
expect(result.current).toBe(false);
});
it("cleans up event listener on unmount", () => {
mockMql.matches = false;
const { unmount } = renderHook(() => useIsMobile());
expect(mockMql.listeners).toHaveLength(1);
unmount();
expect(mockMql.listeners).toHaveLength(0);
});
it("uses 768px breakpoint (max-width: 767px)", () => {
renderHook(() => useIsMobile());
expect(window.matchMedia).toHaveBeenCalledWith("(max-width: 767px)");
});
});
+159
View File
@@ -0,0 +1,159 @@
import { act, fireEvent, render, renderHook, screen } from "@testing-library/react";
import React from "react";
import { afterEach, describe, expect, it, vi } from "vite-plus/test";
vi.mock("@lingui/core/macro", () => ({
t: (strings: TemplateStringsArray) => strings[0],
}));
import { PromptDialogProvider, usePrompt } from "./use-prompt";
afterEach(() => {
document.body.innerHTML = "";
});
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
function wrapper({ children }: { children: React.ReactNode }) {
return <PromptDialogProvider>{children}</PromptDialogProvider>;
}
function PromptTester() {
const prompt = usePrompt();
const [result, setResult] = React.useState<string | null | undefined>(undefined);
return (
<div>
<button
onClick={async () => {
const value = await prompt("Enter name", {
description: "Provide your full name",
defaultValue: "John",
confirmText: "Submit",
cancelText: "Skip",
});
setResult(value);
}}
>
Open Prompt
</button>
{result !== undefined && <span data-testid="result">{result === null ? "null" : result}</span>}
</div>
);
}
// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------
describe("usePrompt", () => {
it("throws when used outside provider", () => {
expect(() => {
renderHook(() => usePrompt());
}).toThrow("usePrompt must be used within a <PromptDialogProvider />");
});
it("returns a function when used inside provider", () => {
const { result } = renderHook(() => usePrompt(), { wrapper });
expect(typeof result.current).toBe("function");
});
it("prompt() returns a promise", () => {
const { result } = renderHook(() => usePrompt(), { wrapper });
let promise: Promise<string | null> | undefined;
act(() => {
promise = result.current("Enter value");
});
expect(promise).toBeInstanceOf(Promise);
});
it("opens dialog with title, description, and default value", async () => {
render(
<PromptDialogProvider>
<PromptTester />
</PromptDialogProvider>,
);
await act(async () => {
screen.getByText("Open Prompt").click();
});
expect(screen.getByText("Enter name")).toBeDefined();
expect(screen.getByText("Provide your full name")).toBeDefined();
expect(screen.getByText("Submit")).toBeDefined();
expect(screen.getByText("Skip")).toBeDefined();
// Check default value in input
const input = screen.getByDisplayValue("John");
expect(input).toBeDefined();
});
it("resolves with input value when confirm is clicked", async () => {
render(
<PromptDialogProvider>
<PromptTester />
</PromptDialogProvider>,
);
await act(async () => {
screen.getByText("Open Prompt").click();
});
// Change the input value
const input = screen.getByDisplayValue("John");
await act(async () => {
fireEvent.change(input, { target: { value: "Jane Doe" } });
});
await act(async () => {
screen.getByText("Submit").click();
});
expect(screen.getByTestId("result").textContent).toBe("Jane Doe");
});
it("resolves with null when cancel is clicked", async () => {
render(
<PromptDialogProvider>
<PromptTester />
</PromptDialogProvider>,
);
await act(async () => {
screen.getByText("Open Prompt").click();
});
await act(async () => {
screen.getByText("Skip").click();
});
expect(screen.getByTestId("result").textContent).toBe("null");
});
it("resolves with value when Enter key is pressed", async () => {
render(
<PromptDialogProvider>
<PromptTester />
</PromptDialogProvider>,
);
await act(async () => {
screen.getByText("Open Prompt").click();
});
const input = screen.getByDisplayValue("John");
await act(async () => {
fireEvent.change(input, { target: { value: "Enter User" } });
});
await act(async () => {
fireEvent.keyDown(input, { key: "Enter" });
});
expect(screen.getByTestId("result").textContent).toBe("Enter User");
});
});