From 0af458cb57c518339dccf89c0c8bab120cdfcb70 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 17 Apr 2026 06:59:52 +0000 Subject: [PATCH 1/4] =?UTF-8?q?=F0=9F=A7=AA=20fix(cardSettings):=20apply?= =?UTF-8?q?=20layout=20normalization=20to=20localStorage=20data=20and=20im?= =?UTF-8?q?prove=20test=20coverage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: is0692vs <135803462+is0692vs@users.noreply.github.com> --- src/lib/__tests__/cardSettings.test.ts | 43 +++++++++++++++++++++----- src/lib/cardSettings.ts | 3 +- 2 files changed, 38 insertions(+), 8 deletions(-) diff --git a/src/lib/__tests__/cardSettings.test.ts b/src/lib/__tests__/cardSettings.test.ts index 2dd83a4..8978a7c 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) => { @@ -121,6 +121,35 @@ describe("cardSettings", () => { expect(getItemMock).toHaveBeenCalledWith("card-layout"); expect(getItemMock).toHaveBeenCalledWith("card-display-options"); }); + 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", () => { 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 ?? {}) }, }; } From 931cc57fc18b5b8c21c47196526c2f025fe6603d Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 17 Apr 2026 08:39:23 +0000 Subject: [PATCH 2/4] =?UTF-8?q?=F0=9F=A7=AA=20fix(cardSettings):=20apply?= =?UTF-8?q?=20layout=20normalization=20to=20localStorage=20data=20and=20im?= =?UTF-8?q?prove=20test=20coverage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: is0692vs <135803462+is0692vs@users.noreply.github.com> --- src/lib/__tests__/cardSettings.test.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/lib/__tests__/cardSettings.test.ts b/src/lib/__tests__/cardSettings.test.ts index 8978a7c..a50ff89 100644 --- a/src/lib/__tests__/cardSettings.test.ts +++ b/src/lib/__tests__/cardSettings.test.ts @@ -1,15 +1,14 @@ 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 getItemMock: ReturnType; + let getItemMock: ReturnType; let setItemMock: ReturnType; beforeEach(() => { - getItemMock = vi.fn(); + getItemMock = vi.fn(); setItemMock = vi.fn(); vi.stubGlobal("window", { @@ -121,6 +120,9 @@ describe("cardSettings", () => { expect(getItemMock).toHaveBeenCalledWith("card-layout"); expect(getItemMock).toHaveBeenCalledWith("card-display-options"); }); + }); + + it("normalizes semantically invalid layout structure that is valid JSON", () => { // Valid JSON, but semantically invalid layout (missing blocks, wrong column type) const invalidLayout = { @@ -150,7 +152,6 @@ describe("cardSettings", () => { const profileBlock = settings.layout.blocks.find(b => b.id === "profile"); expect(profileBlock).toBeDefined(); }); - }); describe("saveCardSettings", () => { it("does nothing when window is undefined", () => { From 538ef3b3007ced9a4ca2e1b11570235d4f1d4a19 Mon Sep 17 00:00:00 2001 From: is0692vs Date: Fri, 17 Apr 2026 17:43:12 +0900 Subject: [PATCH 3/4] fix(tests): use CardBlockId in invalid block assertion Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/lib/__tests__/cardSettings.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/__tests__/cardSettings.test.ts b/src/lib/__tests__/cardSettings.test.ts index a50ff89..0477df8 100644 --- a/src/lib/__tests__/cardSettings.test.ts +++ b/src/lib/__tests__/cardSettings.test.ts @@ -1,6 +1,6 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { getDefaultCardSettings, loadCardSettings, saveCardSettings } from "../cardSettings"; -import { DEFAULT_CARD_LAYOUT, CardLayout, CardDisplayOptions } from "../types"; +import { DEFAULT_CARD_LAYOUT, CardBlockId, CardLayout, CardDisplayOptions } from "../types"; import { normalizeCardLayout } from "../cardLayout"; describe("cardSettings", () => { @@ -145,7 +145,7 @@ describe("cardSettings", () => { 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); + const invalidBlock = settings.layout.blocks.find(b => b.id === "invalidBlockType" as unknown as CardBlockId); expect(invalidBlock).toBeUndefined(); // Missing blocks should be added From 666a6dec354a7f43d3a8e0ccce7b05d2aa82ba7e Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 17 Apr 2026 08:47:43 +0000 Subject: [PATCH 4/4] =?UTF-8?q?=F0=9F=A7=AA=20fix(cardSettings):=20apply?= =?UTF-8?q?=20layout=20normalization=20to=20localStorage=20data=20and=20im?= =?UTF-8?q?prove=20test=20coverage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: is0692vs <135803462+is0692vs@users.noreply.github.com> --- src/lib/__tests__/cardSettings.test.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/lib/__tests__/cardSettings.test.ts b/src/lib/__tests__/cardSettings.test.ts index 0477df8..e39e978 100644 --- a/src/lib/__tests__/cardSettings.test.ts +++ b/src/lib/__tests__/cardSettings.test.ts @@ -1,6 +1,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { getDefaultCardSettings, loadCardSettings, saveCardSettings } from "../cardSettings"; -import { DEFAULT_CARD_LAYOUT, CardBlockId, CardLayout, CardDisplayOptions } from "../types"; +import { DEFAULT_CARD_LAYOUT, CardLayout, CardDisplayOptions } from "../types"; +import { CardBlockType } from "../cardOptions"; import { normalizeCardLayout } from "../cardLayout"; describe("cardSettings", () => { @@ -145,7 +146,7 @@ describe("cardSettings", () => { 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 CardBlockId); + const invalidBlock = settings.layout.blocks.find(b => b.id === "invalidBlockType" as unknown as CardBlockType); expect(invalidBlock).toBeUndefined(); // Missing blocks should be added