From 5f2fca323528ce0e363b08af6454007188d2afcf Mon Sep 17 00:00:00 2001 From: CodeByPeace Date: Sun, 11 Oct 2026 18:11:56 +0000 Subject: [PATCH] test(hooks): add unit tests for useClipboardCopy Adds Vitest tests for the useClipboardCopy hook with a mocked navigator.clipboard.writeText and fake timers. They cover the start state, a successful copy, the reset after the default and a custom timeoutMs, and a rejected write. --- src/hooks/tests/use-clipboard-copy.test.ts | 89 ++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 src/hooks/tests/use-clipboard-copy.test.ts diff --git a/src/hooks/tests/use-clipboard-copy.test.ts b/src/hooks/tests/use-clipboard-copy.test.ts new file mode 100644 index 0000000..70c0b09 --- /dev/null +++ b/src/hooks/tests/use-clipboard-copy.test.ts @@ -0,0 +1,89 @@ +// @vitest-environment jsdom +import { act, cleanup, renderHook } from "@testing-library/react"; +import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; +import { useClipboardCopy } from "@/hooks/use-clipboard-copy"; + +const writeText = vi.fn(); + +beforeEach(() => { + writeText.mockReset(); + writeText.mockResolvedValue(undefined); + Object.defineProperty(navigator, "clipboard", { + value: { writeText }, + configurable: true, + }); + vi.useFakeTimers(); +}); + +afterEach(() => { + cleanup(); + vi.useRealTimers(); +}); + +describe("useClipboardCopy", () => { + test("starts with copied set to false", () => { + const { result } = renderHook(() => useClipboardCopy()); + + expect(result.current.copied).toBe(false); + }); + + test("copies the text and sets copied to true", async () => { + const { result } = renderHook(() => useClipboardCopy()); + let ok: boolean | undefined; + + await act(async () => { + ok = await result.current.copy("hello"); + }); + + expect(ok).toBe(true); + expect(writeText).toHaveBeenCalledWith("hello"); + expect(result.current.copied).toBe(true); + }); + + test("resets copied after the default 2000 ms", async () => { + const { result } = renderHook(() => useClipboardCopy()); + + await act(async () => { + await result.current.copy("hello"); + }); + act(() => { + vi.advanceTimersByTime(1999); + }); + expect(result.current.copied).toBe(true); + + act(() => { + vi.advanceTimersByTime(1); + }); + expect(result.current.copied).toBe(false); + }); + + test("resets copied after a custom timeoutMs", async () => { + const { result } = renderHook(() => useClipboardCopy({ timeoutMs: 500 })); + + await act(async () => { + await result.current.copy("hello"); + }); + act(() => { + vi.advanceTimersByTime(499); + }); + expect(result.current.copied).toBe(true); + + act(() => { + vi.advanceTimersByTime(1); + }); + expect(result.current.copied).toBe(false); + }); + + test("returns false and keeps copied false when the clipboard rejects", async () => { + writeText.mockRejectedValue(new Error("denied")); + const { result } = renderHook(() => useClipboardCopy()); + let ok: boolean | undefined; + + await act(async () => { + ok = await result.current.copy("hello"); + }); + + expect(ok).toBe(false); + expect(result.current.copied).toBe(false); + }); +});