From 09e2d06b877992ff7dab2ecdb2084a4463661b16 Mon Sep 17 00:00:00 2001 From: ginnoir Date: Thu, 11 Jun 2026 21:12:20 -0500 Subject: [PATCH] refactor(state): fix code quality findings for nav panel store and prefs --- src/state/__tests__/prefs.test.ts | 42 ++++++++++++++++++++++++++++++- src/state/__tests__/store.test.ts | 12 ++++++++- src/state/prefs.ts | 15 ++++++++++- src/state/store.ts | 18 ++++++------- 4 files changed, 75 insertions(+), 12 deletions(-) diff --git a/src/state/__tests__/prefs.test.ts b/src/state/__tests__/prefs.test.ts index 8d5727a..fd26684 100644 --- a/src/state/__tests__/prefs.test.ts +++ b/src/state/__tests__/prefs.test.ts @@ -1,4 +1,4 @@ -import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { getPrefs, setPrefs } from '../prefs'; describe('prefs', () => { @@ -14,6 +14,10 @@ describe('prefs', () => { }); }); + afterEach(() => { + vi.unstubAllGlobals(); + }); + it('returns defaults when localStorage empty', () => { expect(getPrefs()).toEqual({ storyOpenMode: 'auto', @@ -27,6 +31,42 @@ describe('prefs', () => { setPrefs({ storyOpenMode: 'manual', actionDetailMode: 'hover' }); expect(getPrefs().storyOpenMode).toBe('manual'); }); + + it('migrates v1 prefs to v2 if v2 does not exist', () => { + const v1Prefs = { + storyOpenMode: 'manual', + actionDetailMode: 'hover', + collapsedActionGroups: { 'some-group': true }, + }; + localStorage.setItem('idlegame:prefs:v1', JSON.stringify(v1Prefs)); + + const migratedPrefs = getPrefs(); + + expect(migratedPrefs).toEqual({ + storyOpenMode: 'manual', + actionDetailMode: 'hover', + collapsedActionGroups: { 'some-group': true }, + showEventLog: true, + }); + + const rawV2 = localStorage.getItem('idlegame:prefs:v2'); + expect(rawV2).not.toBeNull(); + expect(JSON.parse(rawV2 ?? 'null')).toEqual(migratedPrefs); + }); + + it('returns defaults and does not migrate if v1 prefs is invalid JSON', () => { + localStorage.setItem('idlegame:prefs:v1', '{invalid-json}'); + + const prefs = getPrefs(); + expect(prefs).toEqual({ + storyOpenMode: 'auto', + actionDetailMode: 'inline', + collapsedActionGroups: {}, + showEventLog: true, + }); + + expect(localStorage.getItem('idlegame:prefs:v2')).toBeNull(); + }); }); describe('expanded prefs', () => { diff --git a/src/state/__tests__/store.test.ts b/src/state/__tests__/store.test.ts index 026e521..9a52eca 100644 --- a/src/state/__tests__/store.test.ts +++ b/src/state/__tests__/store.test.ts @@ -1,4 +1,5 @@ -import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { getPrefs } from '../prefs'; import { useGameStore } from '../store'; describe('game store nav and prefs', () => { @@ -12,6 +13,15 @@ describe('game store nav and prefs', () => { store[key] = value; }, }); + useGameStore.setState({ + activePanel: 'play', + selectedStoryNodeId: null, + prefs: getPrefs(), + }); + }); + + afterEach(() => { + vi.unstubAllGlobals(); }); it('has default activePanel and selectedStoryNodeId', () => { diff --git a/src/state/prefs.ts b/src/state/prefs.ts index feca74e..a46fd63 100644 --- a/src/state/prefs.ts +++ b/src/state/prefs.ts @@ -21,7 +21,20 @@ export function getPrefs(): GamePrefs { if (typeof localStorage === 'undefined') return { ...DEFAULTS }; try { const raw = localStorage.getItem(PREFS_KEY); - if (!raw) return { ...DEFAULTS }; + if (!raw) { + const rawV1 = localStorage.getItem('idlegame:prefs:v1'); + if (rawV1) { + try { + const parsedV1 = JSON.parse(rawV1); + const migrated = { ...DEFAULTS, ...parsedV1 }; + localStorage.setItem(PREFS_KEY, JSON.stringify(migrated)); + return migrated; + } catch { + return { ...DEFAULTS }; + } + } + return { ...DEFAULTS }; + } return { ...DEFAULTS, ...JSON.parse(raw) }; } catch { return { ...DEFAULTS }; diff --git a/src/state/store.ts b/src/state/store.ts index 5e161b9..96c155a 100644 --- a/src/state/store.ts +++ b/src/state/store.ts @@ -69,13 +69,13 @@ export const useGameStore = create((set) => ({ setSettingsOpen: (open) => set({ settingsOpen: open }), setActivePanel: (panel) => set({ activePanel: panel }), setSelectedStoryNodeId: (id) => set({ selectedStoryNodeId: id }), - toggleActionGroupCollapsed: (groupKey) => - set((state) => { - const nextCollapsed = { - ...state.prefs.collapsedActionGroups, - [groupKey]: !state.prefs.collapsedActionGroups[groupKey], - }; - const prefs = persistPrefs({ collapsedActionGroups: nextCollapsed }); - return { prefs }; - }), + toggleActionGroupCollapsed: (groupKey) => { + const currentPrefs = useGameStore.getState().prefs; + const nextCollapsed = { + ...currentPrefs.collapsedActionGroups, + [groupKey]: !currentPrefs.collapsedActionGroups[groupKey], + }; + const prefs = persistPrefs({ collapsedActionGroups: nextCollapsed }); + set({ prefs }); + }, }));