feat(state): nav panel store and expanded prefs
This commit is contained in:
@@ -15,7 +15,12 @@ describe('prefs', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('returns defaults when localStorage empty', () => {
|
it('returns defaults when localStorage empty', () => {
|
||||||
expect(getPrefs()).toEqual({ storyOpenMode: 'auto', actionDetailMode: 'inline' });
|
expect(getPrefs()).toEqual({
|
||||||
|
storyOpenMode: 'auto',
|
||||||
|
actionDetailMode: 'inline',
|
||||||
|
collapsedActionGroups: {},
|
||||||
|
showEventLog: true,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('round-trips updated prefs', () => {
|
it('round-trips updated prefs', () => {
|
||||||
@@ -23,3 +28,11 @@ describe('prefs', () => {
|
|||||||
expect(getPrefs().storyOpenMode).toBe('manual');
|
expect(getPrefs().storyOpenMode).toBe('manual');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('expanded prefs', () => {
|
||||||
|
it('defaults collapsedActionGroups and showEventLog', () => {
|
||||||
|
const prefs = getPrefs();
|
||||||
|
expect(prefs.collapsedActionGroups).toEqual({});
|
||||||
|
expect(prefs.showEventLog).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
|
import { useGameStore } from '../store';
|
||||||
|
|
||||||
|
describe('game store nav and prefs', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
const store: Record<string, string> = {};
|
||||||
|
vi.stubGlobal('localStorage', {
|
||||||
|
getItem(key: string) {
|
||||||
|
return store[key] ?? null;
|
||||||
|
},
|
||||||
|
setItem(key: string, value: string) {
|
||||||
|
store[key] = value;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('has default activePanel and selectedStoryNodeId', () => {
|
||||||
|
const state = useGameStore.getState();
|
||||||
|
expect(state.activePanel).toBe('play');
|
||||||
|
expect(state.selectedStoryNodeId).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('updates activePanel via setActivePanel', () => {
|
||||||
|
const state = useGameStore.getState();
|
||||||
|
state.setActivePanel('story');
|
||||||
|
expect(useGameStore.getState().activePanel).toBe('story');
|
||||||
|
|
||||||
|
// Reset back
|
||||||
|
useGameStore.getState().setActivePanel('play');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('updates selectedStoryNodeId via setSelectedStoryNodeId', () => {
|
||||||
|
const state = useGameStore.getState();
|
||||||
|
state.setSelectedStoryNodeId('node-1');
|
||||||
|
expect(useGameStore.getState().selectedStoryNodeId).toBe('node-1');
|
||||||
|
|
||||||
|
state.setSelectedStoryNodeId(null);
|
||||||
|
expect(useGameStore.getState().selectedStoryNodeId).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('toggles collapsed action groups in prefs', () => {
|
||||||
|
const state = useGameStore.getState();
|
||||||
|
expect(state.prefs.collapsedActionGroups['skills:gather']).toBeUndefined();
|
||||||
|
|
||||||
|
state.toggleActionGroupCollapsed('skills:gather');
|
||||||
|
expect(useGameStore.getState().prefs.collapsedActionGroups['skills:gather']).toBe(true);
|
||||||
|
|
||||||
|
useGameStore.getState().toggleActionGroupCollapsed('skills:gather');
|
||||||
|
expect(useGameStore.getState().prefs.collapsedActionGroups['skills:gather']).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
+5
-1
@@ -1,4 +1,4 @@
|
|||||||
const PREFS_KEY = 'idlegame:prefs:v1';
|
const PREFS_KEY = 'idlegame:prefs:v2';
|
||||||
|
|
||||||
export type StoryOpenMode = 'auto' | 'choices-only' | 'manual';
|
export type StoryOpenMode = 'auto' | 'choices-only' | 'manual';
|
||||||
export type ActionDetailMode = 'inline' | 'hover' | 'info-button';
|
export type ActionDetailMode = 'inline' | 'hover' | 'info-button';
|
||||||
@@ -6,11 +6,15 @@ export type ActionDetailMode = 'inline' | 'hover' | 'info-button';
|
|||||||
export interface GamePrefs {
|
export interface GamePrefs {
|
||||||
storyOpenMode: StoryOpenMode;
|
storyOpenMode: StoryOpenMode;
|
||||||
actionDetailMode: ActionDetailMode;
|
actionDetailMode: ActionDetailMode;
|
||||||
|
collapsedActionGroups: Record<string, boolean>;
|
||||||
|
showEventLog: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULTS: GamePrefs = {
|
const DEFAULTS: GamePrefs = {
|
||||||
storyOpenMode: 'auto',
|
storyOpenMode: 'auto',
|
||||||
actionDetailMode: 'inline',
|
actionDetailMode: 'inline',
|
||||||
|
collapsedActionGroups: {},
|
||||||
|
showEventLog: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getPrefs(): GamePrefs {
|
export function getPrefs(): GamePrefs {
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ export interface StoryLogEntry {
|
|||||||
choiceLabel?: string;
|
choiceLabel?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ActivePanel = 'play' | 'story' | 'settings' | 'about';
|
||||||
|
|
||||||
export interface GameStoreState extends GameView {
|
export interface GameStoreState extends GameView {
|
||||||
log: string[];
|
log: string[];
|
||||||
storyPanelOpen: boolean;
|
storyPanelOpen: boolean;
|
||||||
@@ -23,6 +25,8 @@ export interface GameStoreState extends GameView {
|
|||||||
storyLog: StoryLogEntry[];
|
storyLog: StoryLogEntry[];
|
||||||
prefs: GamePrefs;
|
prefs: GamePrefs;
|
||||||
settingsOpen: boolean;
|
settingsOpen: boolean;
|
||||||
|
activePanel: ActivePanel;
|
||||||
|
selectedStoryNodeId: string | null;
|
||||||
setView: (view: GameView) => void;
|
setView: (view: GameView) => void;
|
||||||
appendLog: (line: string) => void;
|
appendLog: (line: string) => void;
|
||||||
appendStoryLog: (entry: StoryLogEntry) => void;
|
appendStoryLog: (entry: StoryLogEntry) => void;
|
||||||
@@ -30,6 +34,9 @@ export interface GameStoreState extends GameView {
|
|||||||
setStoryHasUnread: (unread: boolean) => void;
|
setStoryHasUnread: (unread: boolean) => void;
|
||||||
setPrefs: (partial: Partial<GamePrefs>) => void;
|
setPrefs: (partial: Partial<GamePrefs>) => void;
|
||||||
setSettingsOpen: (open: boolean) => void;
|
setSettingsOpen: (open: boolean) => void;
|
||||||
|
setActivePanel: (panel: ActivePanel) => void;
|
||||||
|
setSelectedStoryNodeId: (id: string | null) => void;
|
||||||
|
toggleActionGroupCollapsed: (groupKey: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useGameStore = create<GameStoreState>((set) => ({
|
export const useGameStore = create<GameStoreState>((set) => ({
|
||||||
@@ -48,6 +55,8 @@ export const useGameStore = create<GameStoreState>((set) => ({
|
|||||||
storyLog: [],
|
storyLog: [],
|
||||||
prefs: getPrefs(),
|
prefs: getPrefs(),
|
||||||
settingsOpen: false,
|
settingsOpen: false,
|
||||||
|
activePanel: 'play',
|
||||||
|
selectedStoryNodeId: null,
|
||||||
setView: (view) => set((state) => ({ ...state, ...view })),
|
setView: (view) => set((state) => ({ ...state, ...view })),
|
||||||
appendLog: (line) => set((state) => ({ log: [...state.log, line].slice(-MAX_LOG_LINES) })),
|
appendLog: (line) => set((state) => ({ log: [...state.log, line].slice(-MAX_LOG_LINES) })),
|
||||||
appendStoryLog: (entry) => set((state) => ({ storyLog: [...state.storyLog, entry] })),
|
appendStoryLog: (entry) => set((state) => ({ storyLog: [...state.storyLog, entry] })),
|
||||||
@@ -58,4 +67,15 @@ export const useGameStore = create<GameStoreState>((set) => ({
|
|||||||
set({ prefs });
|
set({ prefs });
|
||||||
},
|
},
|
||||||
setSettingsOpen: (open) => set({ settingsOpen: open }),
|
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 };
|
||||||
|
}),
|
||||||
}));
|
}));
|
||||||
|
|||||||
Reference in New Issue
Block a user