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
57 changes: 57 additions & 0 deletions client/src/services/__tests__/presets.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { afterEach, describe, expect, it } from "vitest";

import { deletePreset, loadPresets, savePreset } from "../presets";

const STORAGE_KEY = "phase-game-presets";

afterEach(() => {
localStorage.clear();
});

describe("loadPresets", () => {
it("returns the defaults when storage is empty", () => {
const presets = loadPresets();
expect(presets.length).toBeGreaterThan(0);
expect(presets.some((p) => p.id.startsWith("default-"))).toBe(true);
});

it("falls back to the defaults on corrupt storage instead of throwing", () => {
localStorage.setItem(STORAGE_KEY, "{oops");
expect(() => loadPresets()).not.toThrow();
expect(loadPresets()).toEqual(
expect.arrayContaining([
expect.objectContaining({ id: expect.stringMatching(/^default-/) }),
]),
);
});

it("does not let corrupt storage crash savePreset/deletePreset", () => {
localStorage.setItem(STORAGE_KEY, "not json");
expect(() =>
savePreset({
id: "custom-1",
name: "My Preset",
format: "Standard",
formatConfig: {},
deckId: null,
aiDifficulty: "Medium",
playerCount: 2,
}),
).not.toThrow();
expect(() => deletePreset("custom-1")).not.toThrow();
});

it("returns saved presets when storage holds a valid non-empty array", () => {
savePreset({
id: "custom-2",
name: "Saved",
format: "Commander",
formatConfig: {},
deckId: null,
aiDifficulty: "Hard",
playerCount: 4,
});
const presets = loadPresets();
expect(presets.some((p) => p.id === "custom-2")).toBe(true);
});
});
12 changes: 10 additions & 2 deletions client/src/services/presets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,16 @@ const DEFAULT_PRESETS: GamePreset[] = [
export function loadPresets(): GamePreset[] {
const raw = localStorage.getItem(STORAGE_KEY);
if (!raw) return DEFAULT_PRESETS;
const saved = JSON.parse(raw) as GamePreset[];
return saved.length > 0 ? saved : DEFAULT_PRESETS;
// Corrupt/partial storage must not throw — every sibling loader falls back to
// a default. Without this guard, a malformed value crashes loadPresets and,
// through it, savePreset/deletePreset (both call it first), taking down the
// game-setup preset UI.
try {
const saved = JSON.parse(raw) as GamePreset[];
return saved.length > 0 ? saved : DEFAULT_PRESETS;
} catch {
return DEFAULT_PRESETS;
}
}

export function savePreset(preset: GamePreset): void {
Expand Down
Loading