diff --git a/src/lib/__tests__/cardSettings.test.ts b/src/lib/__tests__/cardSettings.test.ts index 2dd83a4..e39e978 100644 --- a/src/lib/__tests__/cardSettings.test.ts +++ b/src/lib/__tests__/cardSettings.test.ts @@ -1,15 +1,15 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { getDefaultCardSettings, loadCardSettings, saveCardSettings } from "../cardSettings"; import { DEFAULT_CARD_LAYOUT, CardLayout, CardDisplayOptions } from "../types"; +import { CardBlockType } from "../cardOptions"; +import { normalizeCardLayout } from "../cardLayout"; describe("cardSettings", () => { - let originalWindow: typeof window | undefined; - let getItemMock: ReturnType; + let getItemMock: ReturnType; let setItemMock: ReturnType; beforeEach(() => { - originalWindow = globalThis.window; - getItemMock = vi.fn(); + getItemMock = vi.fn(); setItemMock = vi.fn(); vi.stubGlobal("window", { @@ -55,7 +55,7 @@ describe("cardSettings", () => { }); it("safely handles partially invalid JSON across multiple localStorage keys (safeParse fallback)", () => { - const mockLayout: CardLayout = { blocks: [{ id: "bio", visible: true, column: "full" }] }; + const mockLayout: CardLayout = normalizeCardLayout({ blocks: [{ id: "bio", visible: true, column: "full" }] }); // Return valid JSON for layout, but completely invalid JSON for options getItemMock.mockImplementation((key: string) => { @@ -92,7 +92,7 @@ describe("cardSettings", () => { }); it("safely handles valid JSON for layout but null for options", () => { - const mockLayout: CardLayout = { blocks: [{ id: "bio", visible: true, column: "left" }] }; + const mockLayout: CardLayout = normalizeCardLayout({ blocks: [{ id: "bio", visible: true, column: "left" }] }); getItemMock.mockReturnValueOnce(JSON.stringify(mockLayout)); getItemMock.mockReturnValueOnce(null); @@ -103,7 +103,7 @@ describe("cardSettings", () => { }); it("returns parsed settings from localStorage when window is defined", () => { - const mockLayout: CardLayout = { blocks: [{ id: "bio", visible: true, column: "left" }] }; + const mockLayout: CardLayout = normalizeCardLayout({ blocks: [{ id: "bio", visible: true, column: "left" }] }); const mockOptions: Partial = { showTwitter: false, showLocation: false }; getItemMock.mockImplementation((key: string) => { @@ -123,6 +123,37 @@ describe("cardSettings", () => { }); }); + + it("normalizes semantically invalid layout structure that is valid JSON", () => { + // Valid JSON, but semantically invalid layout (missing blocks, wrong column type) + const invalidLayout = { + blocks: [ + { id: "invalidBlockType", visible: true, column: "left" }, + { id: "bio", visible: true, column: "wrongColumn" } + ] + }; + + getItemMock.mockImplementation((key: string) => { + if (key === "card-layout") return JSON.stringify(invalidLayout); + return null; + }); + + const settings = loadCardSettings(); + + // The bio block should be fixed to 'left' (or full depending on normalization, but valid) + const bioBlock = settings.layout.blocks.find(b => b.id === "bio"); + expect(bioBlock).toBeDefined(); + expect(["left", "right", "full"]).toContain(bioBlock?.column); + + // The invalid block should be removed + const invalidBlock = settings.layout.blocks.find(b => b.id === "invalidBlockType" as unknown as CardBlockType); + expect(invalidBlock).toBeUndefined(); + + // Missing blocks should be added + const profileBlock = settings.layout.blocks.find(b => b.id === "profile"); + expect(profileBlock).toBeDefined(); + }); + describe("saveCardSettings", () => { it("does nothing when window is undefined", () => { vi.stubGlobal("window", undefined); diff --git a/src/lib/cardSettings.ts b/src/lib/cardSettings.ts index b21fdb4..87252ec 100644 --- a/src/lib/cardSettings.ts +++ b/src/lib/cardSettings.ts @@ -1,6 +1,7 @@ import type { CardDisplayOptions, CardLayout } from "@/lib/types"; export type { CardDisplayOptions, CardLayout }; import { DEFAULT_CARD_LAYOUT } from "@/lib/types"; +import { normalizeCardLayout } from "@/lib/cardLayout"; const LAYOUT_KEY = "card-layout"; const OPTIONS_KEY = "card-display-options"; @@ -47,7 +48,7 @@ export function loadCardSettings(): { layout: CardLayout; options: CardDisplayOp const parsedOptions = safeParse(window.localStorage.getItem(OPTIONS_KEY)); return { - layout: parsedLayout ?? DEFAULT_CARD_LAYOUT, + layout: parsedLayout ? normalizeCardLayout(parsedLayout) : DEFAULT_CARD_LAYOUT, options: { ...DEFAULT_DISPLAY_OPTIONS, ...(parsedOptions ?? {}) }, }; }