From 5c3aeb47345f81107cd4a7a721e21d4846e19606 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 17 Jul 2026 06:37:39 +0000 Subject: [PATCH 1/2] test: add tests for useCardSettings hook Co-authored-by: is0692vs <135803462+is0692vs@users.noreply.github.com> --- src/hooks/__tests__/useCardSettings.test.ts | 146 ++++++++++++++++++++ 1 file changed, 146 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..6e577c05 --- /dev/null +++ b/src/hooks/__tests__/useCardSettings.test.ts @@ -0,0 +1,146 @@ +import { renderHook, act } from "@testing-library/react"; +import { describe, it, expect, vi, beforeEach } from "vitest"; +import { useCardSettings } from "../useCardSettings"; +import * as cardSettingsLib from "@/lib/cardSettings"; +import * as cardLayoutLib from "@/lib/cardLayout"; +import { DEFAULT_CARD_LAYOUT } from "@/lib/types"; +import type { CardLayout } from "@/lib/types"; + +// Mock the external dependencies +vi.mock("@/lib/cardSettings", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + loadCardSettings: vi.fn(), + saveCardSettings: vi.fn(), + }; +}); + +vi.mock("@/lib/cardLayout", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + toggleBlockVisibility: vi.fn(), + }; +}); + +describe("useCardSettings", () => { + const mockDefaultOptions = { + showAvatar: true, + showBio: true, + showStats: true, + showLanguage: true, + showRepos: true, + showCompany: true, + showLocation: true, + showWebsite: true, + showTwitter: true, + showJoinedDate: true, + showTopics: true, + showContributionBreakdown: true, + showStreaks: true, + showInterests: true, + showActivityBreakdown: true, + }; + + const mockLoadedSettings = { + layout: { + ...DEFAULT_CARD_LAYOUT, + blocks: [ + { id: "profile", visible: true, order: 0 }, + { id: "stats", visible: false, order: 1 }, + ] + } as CardLayout, + options: { + ...mockDefaultOptions, + showAvatar: false, + }, + }; + + beforeEach(() => { + vi.clearAllMocks(); + (cardSettingsLib.loadCardSettings as any).mockReturnValue(mockLoadedSettings); + (cardLayoutLib.toggleBlockVisibility as any).mockImplementation( + (prev: any, id: any) => ({ ...prev, toggled: id }) + ); + }); + + it("should initialize with values from loadCardSettings", () => { + const { result } = renderHook(() => useCardSettings(false)); + + expect(result.current.layout).toEqual(mockLoadedSettings.layout); + expect(result.current.displayOptions).toEqual(mockLoadedSettings.options); + expect(cardSettingsLib.loadCardSettings).toHaveBeenCalledTimes(2); + }); + + it("should not hydrate or save if not mounted", () => { + renderHook(() => useCardSettings(false)); + + // It's called once during state initialization, but the effect shouldn't re-call it or save + expect(cardSettingsLib.loadCardSettings).toHaveBeenCalledTimes(2); + expect(cardSettingsLib.saveCardSettings).not.toHaveBeenCalled(); + }); + + it("should hydrate and update state when mounted changes to true", () => { + const { result, rerender } = renderHook(({ mounted }) => useCardSettings(mounted), { + initialProps: { mounted: false }, + }); + + // Reset mock count to ignore initialization + vi.clearAllMocks(); + + rerender({ mounted: true }); + + expect(cardSettingsLib.loadCardSettings).toHaveBeenCalledTimes(1); + expect(result.current.layout).toEqual(mockLoadedSettings.layout); + expect(result.current.displayOptions).toEqual(mockLoadedSettings.options); + }); + + it("should save settings when layout or options change (if hydrated)", () => { + const { result } = renderHook(() => useCardSettings(true)); + + act(() => { + result.current.toggleDisplayOption("showAvatar"); + }); + + // Once for initial hydration, once for the change + expect(cardSettingsLib.saveCardSettings).toHaveBeenCalledWith( + expect.anything(), + expect.objectContaining({ showAvatar: true }) + ); + }); + + it("toggleMainBlockVisibility should update layout using toggleBlockVisibility", () => { + const { result } = renderHook(() => useCardSettings(true)); + + act(() => { + result.current.toggleMainBlockVisibility("stats"); + }); + + expect(cardLayoutLib.toggleBlockVisibility).toHaveBeenCalledWith( + mockLoadedSettings.layout, + "stats" + ); + expect(result.current.layout).toHaveProperty("toggled", "stats"); + }); + + it("toggleDisplayOption should flip boolean values in displayOptions", () => { + const { result } = renderHook(() => useCardSettings(true)); + + expect(result.current.displayOptions.showBio).toBe(true); + + act(() => { + result.current.toggleDisplayOption("showBio"); + }); + + expect(result.current.displayOptions.showBio).toBe(false); + }); + + it("isBlockVisible should return true if block is visible, false otherwise", () => { + const { result } = renderHook(() => useCardSettings(true)); + + expect(result.current.isBlockVisible("profile")).toBe(true); + expect(result.current.isBlockVisible("stats")).toBe(false); + expect(result.current.isBlockVisible("non-existent-block" as any)).toBe(false); + }); +}); From 6a085e7b3a4e5a635bad70baf7518adeb7275860 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 17 Jul 2026 06:57:40 +0000 Subject: [PATCH 2/2] test: fix type errors and unused lint disable in useCardSettings hook Co-authored-by: is0692vs <135803462+is0692vs@users.noreply.github.com> --- src/hooks/__tests__/useCardSettings.test.ts | 13 +++++++------ src/hooks/useCardSettings.ts | 1 - 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/hooks/__tests__/useCardSettings.test.ts b/src/hooks/__tests__/useCardSettings.test.ts index 6e577c05..e43b7b33 100644 --- a/src/hooks/__tests__/useCardSettings.test.ts +++ b/src/hooks/__tests__/useCardSettings.test.ts @@ -47,8 +47,8 @@ describe("useCardSettings", () => { layout: { ...DEFAULT_CARD_LAYOUT, blocks: [ - { id: "profile", visible: true, order: 0 }, - { id: "stats", visible: false, order: 1 }, + { id: "profile", visible: true, column: "full" }, + { id: "stats", visible: false, column: "left" }, ] } as CardLayout, options: { @@ -59,9 +59,10 @@ describe("useCardSettings", () => { beforeEach(() => { vi.clearAllMocks(); - (cardSettingsLib.loadCardSettings as any).mockReturnValue(mockLoadedSettings); - (cardLayoutLib.toggleBlockVisibility as any).mockImplementation( - (prev: any, id: any) => ({ ...prev, toggled: id }) + vi.mocked(cardSettingsLib.loadCardSettings).mockReturnValue(mockLoadedSettings); + vi.mocked(cardLayoutLib.toggleBlockVisibility).mockImplementation( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (prev: any, id: any) => ({ ...prev, toggled: id }) as any ); }); @@ -141,6 +142,6 @@ describe("useCardSettings", () => { expect(result.current.isBlockVisible("profile")).toBe(true); expect(result.current.isBlockVisible("stats")).toBe(false); - expect(result.current.isBlockVisible("non-existent-block" as any)).toBe(false); + expect(result.current.isBlockVisible("non-existent-block" as import("@/lib/types").CardBlockId)).toBe(false); }); }); diff --git a/src/hooks/useCardSettings.ts b/src/hooks/useCardSettings.ts index c54dbc3e..db3e7779 100644 --- a/src/hooks/useCardSettings.ts +++ b/src/hooks/useCardSettings.ts @@ -26,7 +26,6 @@ export function useCardSettings(mounted: boolean) { // eslint-disable-next-line react-hooks/set-state-in-effect setLayout((prev) => JSON.stringify(prev) !== JSON.stringify(storedLayout) ? storedLayout : prev); - // eslint-disable-next-line react-hooks/set-state-in-effect setDisplayOptions((prev) => JSON.stringify(prev) !== JSON.stringify(storedOptions) ? storedOptions : prev); setIsHydrated(true);