From 6dbc58b75e775ac3d776d1dfe0f68abf77923700 Mon Sep 17 00:00:00 2001 From: Konstantin Lukas Date: Sun, 8 Sep 2024 14:49:35 +0200 Subject: [PATCH] add tests for useDarkMode --- src/hooks/useDarkMode.ts | 2 +- tests/useDarkMode.test.ts | 128 +++++++++++++++++++++++++++++++ tests/usePreferredScheme.test.ts | 2 +- 3 files changed, 130 insertions(+), 2 deletions(-) create mode 100644 tests/useDarkMode.test.ts diff --git a/src/hooks/useDarkMode.ts b/src/hooks/useDarkMode.ts index d62d0a1..5aab400 100644 --- a/src/hooks/useDarkMode.ts +++ b/src/hooks/useDarkMode.ts @@ -70,7 +70,7 @@ function useDarkMode({ return { setTheme: (selectedTheme: "light" | "dark") => setTheme(selectedTheme), - toggleTheme: () => { setTheme(prevState => prevState === "light" ? "dark" : "light"); }, + toggleTheme: () => setTheme(prevState => prevState === "light" ? "dark" : "light"), theme, }; } diff --git a/tests/useDarkMode.test.ts b/tests/useDarkMode.test.ts new file mode 100644 index 0000000..bef58b7 --- /dev/null +++ b/tests/useDarkMode.test.ts @@ -0,0 +1,128 @@ +import { act, renderHook } from "@testing-library/react"; +import { useDarkMode } from "../src"; + + +describe("useDarkMode", () => { + let triggerChange: (e: { matches: boolean }) => void; + const defineMock = (defTheme: boolean) => { + Object.defineProperty(window, "matchMedia", { + writable: true, + value: jest.fn().mockImplementation(() => ({ + matches: defTheme, + addEventListener: (_: string, callback: (e: { matches: boolean }) => void) => { + triggerChange = callback; + }, + removeEventListener: jest.fn(), + })), + }); + }; + + afterEach(() => { + localStorage.clear(); + jest.clearAllMocks(); + }); + + test("should use the current preferred scheme (light) by default", async () => { + defineMock(false); + const { result } = await act(async () => { + return renderHook(() => useDarkMode()); + }); + expect(result.current.theme).toBe("light"); + }); + + test("should use the current preferred scheme (dark) by default", async () => { + defineMock(true); + const { result } = await act(async () => { + return renderHook(() => useDarkMode()); + }); + expect(result.current.theme).toBe("dark"); + }); + + test("should ignore the preferred theme if a value exists in local storage", async () => { + localStorage.setItem("anzol-dark-mode-hook", "light"); + defineMock(true); + const { result } = await act(async () => { + return renderHook(() => useDarkMode()); + }); + expect(result.current.theme).toBe("light"); + }); + + test("should ignore the preferred theme if a value exists in local storage unless configured otherwise", async () => { + localStorage.setItem("anzol-dark-mode-hook", "light"); + defineMock(true); + const { result } = await act(async () => { + return renderHook(() => useDarkMode({ persistStateInLocalStorage: false })); + }); + expect(result.current.theme).toBe("dark"); + }); + + test("should ignore the value stored in local storage and used a specified default if configured to do so", async () => { + localStorage.setItem("anzol-dark-mode-hook", "light"); + defineMock(false); + const { result } = await act(async () => { + return renderHook(() => useDarkMode({ persistStateInLocalStorage: false, defaultTheme: "dark" })); + }); + expect(result.current.theme).toBe("dark"); + }); + + test("should ignore the selected default theme if a value exists in local storage", async () => { + localStorage.setItem("anzol-dark-mode-hook", "light"); + defineMock(true); + const { result } = await act(async () => { + return renderHook(() => useDarkMode({ defaultTheme: "dark" })); + }); + expect(result.current.theme).toBe("light"); + }); + + test("should update its state when the preferred scheme changes", async () => { + defineMock(false); + const { result } = await act(async () => { + return renderHook(() => useDarkMode()); + }); + expect(result.current.theme).toBe("light"); + act(() => triggerChange({ matches: true })); + expect(result.current.theme).toBe("dark"); + act(() => triggerChange({ matches: false })); + expect(result.current.theme).toBe("light"); + }); + + test("should not update its state when the preferred scheme changes if configured to do so", async () => { + defineMock(false); + const { result } = await act(async () => { + return renderHook(() => useDarkMode({ updateOnPreferredSchemeChange: false })); + }); + expect(result.current.theme).toBe("light"); + act(() => triggerChange({ matches: true })); + expect(result.current.theme).toBe("light"); + act(() => triggerChange({ matches: false })); + expect(result.current.theme).toBe("light"); + }); + + test("should allow setting the theme", async () => { + defineMock(false); + const { result } = await act(async () => { + return renderHook(() => useDarkMode({ updateOnPreferredSchemeChange: false })); + }); + expect(result.current.theme).toBe("light"); + for (let i = 0; i < 2; i++) { + act(() => result.current.setTheme("dark")); + expect(result.current.theme).toBe("dark"); + } + for (let i = 0; i < 2; i++) { + act(() => result.current.setTheme("light")); + expect(result.current.theme).toBe("light"); + } + }); + + test("should allow toggling the theme", async () => { + defineMock(false); + const { result } = await act(async () => { + return renderHook(() => useDarkMode({ updateOnPreferredSchemeChange: false })); + }); + expect(result.current.theme).toBe("light"); + for (let i = 0; i < 5; i++) { + act(() => result.current.toggleTheme()); + expect(result.current.theme).toBe(i % 2 === 0 ? "dark" : "light"); + } + }); +}); \ No newline at end of file diff --git a/tests/usePreferredScheme.test.ts b/tests/usePreferredScheme.test.ts index 6e5fa62..6e0d7db 100644 --- a/tests/usePreferredScheme.test.ts +++ b/tests/usePreferredScheme.test.ts @@ -33,7 +33,7 @@ describe("usePreferredScheme", () => { expect(result.current).toBe("light"); }); - test("should change use the current preferred scheme by default", async () => { + test("should use the current preferred scheme by default", async () => { defineMock(true); const { result } = await act(async () => { return renderHook(() => usePreferredScheme());