From 2dc2a34f8860b9bb5e7157a1f8fbbb7c9eca1170 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 6 Jun 2026 03:16:38 +0000 Subject: [PATCH 1/2] test: add missing tests for useCardSettings hook Co-authored-by: is0692vs <135803462+is0692vs@users.noreply.github.com> --- src/hooks/__tests__/useCardSettings.test.ts | 156 ++++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 src/hooks/__tests__/useCardSettings.test.ts diff --git a/src/hooks/__tests__/useCardSettings.test.ts b/src/hooks/__tests__/useCardSettings.test.ts new file mode 100644 index 00000000..963322e6 --- /dev/null +++ b/src/hooks/__tests__/useCardSettings.test.ts @@ -0,0 +1,156 @@ +// @vitest-environment jsdom +import { describe, it, expect, vi, beforeEach } from "vitest"; +import { renderHook, act } from "@testing-library/react"; +import { useCardSettings } from "../useCardSettings"; +import * as cardSettingsLib from "@/lib/cardSettings"; +import * as cardLayoutLib from "@/lib/cardLayout"; + +vi.mock("@/lib/cardSettings", () => ({ + loadCardSettings: vi.fn(), + saveCardSettings: vi.fn(), +})); + +vi.mock("@/lib/cardLayout", () => ({ + toggleBlockVisibility: vi.fn(), +})); + +describe("useCardSettings", () => { + const mockDefaultLayout = { + blocks: [ + { id: "profile", visible: true, column: "full" }, + { id: "stats", visible: false, column: "left" }, + ], + }; + + const mockDefaultOptions = { + showAvatar: true, + showBio: false, + }; + + beforeEach(() => { + vi.clearAllMocks(); + vi.mocked(cardSettingsLib.loadCardSettings).mockReturnValue({ + // @ts-expect-error - mock data + layout: mockDefaultLayout, + // @ts-expect-error - mock data + options: mockDefaultOptions, + }); + }); + + it("should initialize with values from loadCardSettings synchronously", () => { + const { result } = renderHook(() => useCardSettings(false)); + + // loadCardSettings is called twice during useState lazy initialization (once for layout, once for options) + expect(cardSettingsLib.loadCardSettings).toHaveBeenCalledTimes(2); + expect(result.current.layout).toEqual(mockDefaultLayout); + expect(result.current.displayOptions).toEqual(mockDefaultOptions); + expect(cardSettingsLib.saveCardSettings).not.toHaveBeenCalled(); + }); + + it("should not hydrate if mounted is false", () => { + const { result } = renderHook(() => useCardSettings(false)); + + expect(cardSettingsLib.loadCardSettings).toHaveBeenCalledTimes(2); // Only for init + // Hydration useEffect should return early + expect(result.current.layout).toEqual(mockDefaultLayout); + expect(result.current.displayOptions).toEqual(mockDefaultOptions); + expect(cardSettingsLib.saveCardSettings).not.toHaveBeenCalled(); + }); + + it("should hydrate and update state when mounted becomes true", () => { + const { result, rerender } = renderHook( + ({ mounted }) => useCardSettings(mounted), + { initialProps: { mounted: false } } + ); + + // Now mount + rerender({ mounted: true }); + + // Expect loadCardSettings to be called again during the effect (2 from init + 1 from effect) + expect(cardSettingsLib.loadCardSettings).toHaveBeenCalledTimes(3); + expect(result.current.layout).toEqual(mockDefaultLayout); + expect(result.current.displayOptions).toEqual(mockDefaultOptions); + }); + + it("should not call saveCardSettings before hydration is complete", () => { + const { rerender } = renderHook( + ({ mounted }) => useCardSettings(mounted), + { initialProps: { mounted: false } } + ); + + expect(cardSettingsLib.saveCardSettings).not.toHaveBeenCalled(); + + // Trigger hydration + rerender({ mounted: true }); + + // Once hydration is complete, isHydrated becomes true, and the second useEffect will call saveCardSettings + expect(cardSettingsLib.saveCardSettings).toHaveBeenCalledTimes(1); + expect(cardSettingsLib.saveCardSettings).toHaveBeenCalledWith(mockDefaultLayout, mockDefaultOptions); + }); + + it("should call saveCardSettings when state changes after hydration", () => { + const { result, rerender } = renderHook( + ({ mounted }) => useCardSettings(mounted), + { initialProps: { mounted: true } } + ); + + // Clear mock to isolate the save call from the subsequent manual state change + vi.mocked(cardSettingsLib.saveCardSettings).mockClear(); + + // Trigger state change + act(() => { + // @ts-expect-error - mock data + result.current.setDisplayOptions({ showAvatar: false, showBio: false }); + }); + + expect(cardSettingsLib.saveCardSettings).toHaveBeenCalledTimes(1); + expect(cardSettingsLib.saveCardSettings).toHaveBeenCalledWith( + mockDefaultLayout, + { showAvatar: false, showBio: false } + ); + }); + + it("toggleMainBlockVisibility should call layout toggle function", () => { + const { result } = renderHook(() => useCardSettings(true)); + + const mockToggledLayout = { blocks: [] }; + vi.mocked(cardLayoutLib.toggleBlockVisibility).mockReturnValue(mockToggledLayout as any); + + act(() => { + // @ts-expect-error - mock data + result.current.toggleMainBlockVisibility("profile"); + }); + + expect(cardLayoutLib.toggleBlockVisibility).toHaveBeenCalledWith(mockDefaultLayout, "profile"); + expect(result.current.layout).toEqual(mockToggledLayout); + }); + + it("toggleDisplayOption should toggle boolean values", () => { + const { result } = renderHook(() => useCardSettings(true)); + + act(() => { + // @ts-expect-error - mock data + result.current.toggleDisplayOption("showAvatar"); + }); + + expect(result.current.displayOptions.showAvatar).toBe(false); + + act(() => { + // @ts-expect-error - mock data + result.current.toggleDisplayOption("showBio"); + }); + + expect(result.current.displayOptions.showBio).toBe(true); + }); + + it("isBlockVisible should return true for visible blocks and false for hidden/unknown", () => { + const { result } = renderHook(() => useCardSettings(true)); + + // @ts-expect-error - mock data + expect(result.current.isBlockVisible("profile")).toBe(true); + // @ts-expect-error - mock data + expect(result.current.isBlockVisible("stats")).toBe(false); + // @ts-expect-error - mock data + expect(result.current.isBlockVisible("unknown")).toBe(false); + }); +}); From 383ba537ed82c36c2dbb12d4ad3591b2acd731ab Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 6 Jun 2026 03:22:16 +0000 Subject: [PATCH 2/2] test: fix typing issues in useCardSettings test Co-authored-by: is0692vs <135803462+is0692vs@users.noreply.github.com> --- src/hooks/__tests__/useCardSettings.test.ts | 37 ++++++++------------- 1 file changed, 14 insertions(+), 23 deletions(-) diff --git a/src/hooks/__tests__/useCardSettings.test.ts b/src/hooks/__tests__/useCardSettings.test.ts index 963322e6..97468ece 100644 --- a/src/hooks/__tests__/useCardSettings.test.ts +++ b/src/hooks/__tests__/useCardSettings.test.ts @@ -15,10 +15,10 @@ vi.mock("@/lib/cardLayout", () => ({ })); describe("useCardSettings", () => { - const mockDefaultLayout = { + const mockDefaultLayout: import("@/lib/types").CardLayout = { blocks: [ - { id: "profile", visible: true, column: "full" }, - { id: "stats", visible: false, column: "left" }, + { id: "profile" as import("@/lib/types").CardBlockId, visible: true, column: "full" }, + { id: "stats" as import("@/lib/types").CardBlockId, visible: false, column: "left" }, ], }; @@ -30,10 +30,8 @@ describe("useCardSettings", () => { beforeEach(() => { vi.clearAllMocks(); vi.mocked(cardSettingsLib.loadCardSettings).mockReturnValue({ - // @ts-expect-error - mock data - layout: mockDefaultLayout, - // @ts-expect-error - mock data - options: mockDefaultOptions, + layout: mockDefaultLayout, + options: mockDefaultOptions, }); }); @@ -89,7 +87,7 @@ describe("useCardSettings", () => { }); it("should call saveCardSettings when state changes after hydration", () => { - const { result, rerender } = renderHook( + const { result } = renderHook( ({ mounted }) => useCardSettings(mounted), { initialProps: { mounted: true } } ); @@ -99,8 +97,7 @@ describe("useCardSettings", () => { // Trigger state change act(() => { - // @ts-expect-error - mock data - result.current.setDisplayOptions({ showAvatar: false, showBio: false }); + result.current.setDisplayOptions({ showAvatar: false, showBio: false }); }); expect(cardSettingsLib.saveCardSettings).toHaveBeenCalledTimes(1); @@ -114,11 +111,10 @@ describe("useCardSettings", () => { const { result } = renderHook(() => useCardSettings(true)); const mockToggledLayout = { blocks: [] }; - vi.mocked(cardLayoutLib.toggleBlockVisibility).mockReturnValue(mockToggledLayout as any); + vi.mocked(cardLayoutLib.toggleBlockVisibility).mockReturnValue(mockToggledLayout as unknown as import("@/lib/types").CardLayout); act(() => { - // @ts-expect-error - mock data - result.current.toggleMainBlockVisibility("profile"); + result.current.toggleMainBlockVisibility("profile"); }); expect(cardLayoutLib.toggleBlockVisibility).toHaveBeenCalledWith(mockDefaultLayout, "profile"); @@ -129,15 +125,13 @@ describe("useCardSettings", () => { const { result } = renderHook(() => useCardSettings(true)); act(() => { - // @ts-expect-error - mock data - result.current.toggleDisplayOption("showAvatar"); + result.current.toggleDisplayOption("showAvatar"); }); expect(result.current.displayOptions.showAvatar).toBe(false); act(() => { - // @ts-expect-error - mock data - result.current.toggleDisplayOption("showBio"); + result.current.toggleDisplayOption("showBio"); }); expect(result.current.displayOptions.showBio).toBe(true); @@ -146,11 +140,8 @@ describe("useCardSettings", () => { it("isBlockVisible should return true for visible blocks and false for hidden/unknown", () => { const { result } = renderHook(() => useCardSettings(true)); - // @ts-expect-error - mock data - expect(result.current.isBlockVisible("profile")).toBe(true); - // @ts-expect-error - mock data - expect(result.current.isBlockVisible("stats")).toBe(false); - // @ts-expect-error - mock data - expect(result.current.isBlockVisible("unknown")).toBe(false); + expect(result.current.isBlockVisible("profile")).toBe(true); + expect(result.current.isBlockVisible("stats")).toBe(false); + expect(result.current.isBlockVisible("unknown" as import("@/lib/types").CardBlockId)).toBe(false); }); });