Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 38 additions & 7 deletions src/lib/__tests__/cardSettings.test.ts
Original file line number Diff line number Diff line change
@@ -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<typeof vi.fn>;
let getItemMock: ReturnType<typeof vi.fn>;
let setItemMock: ReturnType<typeof vi.fn>;

beforeEach(() => {
originalWindow = globalThis.window;
getItemMock = vi.fn();
getItemMock = vi.fn();
setItemMock = vi.fn();

vi.stubGlobal("window", {
Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -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);

Expand All @@ -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<CardDisplayOptions> = { showTwitter: false, showLocation: false };

getItemMock.mockImplementation((key: string) => {
Expand All @@ -123,6 +123,37 @@ describe("cardSettings", () => {
});
});


it("normalizes semantically invalid layout structure that is valid JSON", () => {
Comment thread
is0692vs marked this conversation as resolved.
// 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);
Expand Down
3 changes: 2 additions & 1 deletion src/lib/cardSettings.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -47,7 +48,7 @@ export function loadCardSettings(): { layout: CardLayout; options: CardDisplayOp
const parsedOptions = safeParse<CardDisplayOptions>(window.localStorage.getItem(OPTIONS_KEY));

return {
layout: parsedLayout ?? DEFAULT_CARD_LAYOUT,
layout: parsedLayout ? normalizeCardLayout(parsedLayout) : DEFAULT_CARD_LAYOUT,
options: { ...DEFAULT_DISPLAY_OPTIONS, ...(parsedOptions ?? {}) },
};
}
Expand Down
Loading