diff options
Diffstat (limited to 'src/features/views/logic')
| -rw-r--r-- | src/features/views/logic/panels.test.ts | 55 | ||||
| -rw-r--r-- | src/features/views/logic/panels.ts | 49 |
2 files changed, 104 insertions, 0 deletions
diff --git a/src/features/views/logic/panels.test.ts b/src/features/views/logic/panels.test.ts new file mode 100644 index 0000000..edd7d9e --- /dev/null +++ b/src/features/views/logic/panels.test.ts @@ -0,0 +1,55 @@ +import { describe, expect, it } from "vitest"; +import { addPanel, initialPanels, removePanel, selectKind } from "./panels"; + +describe("view panels reducer", () => { + it("seeds one empty panel by default", () => { + const s = initialPanels(); + expect(s.panels).toHaveLength(1); + expect(s.panels[0]?.kind).toBeNull(); + }); + + it("seeds a panel per provided kind, in order, with unique ids", () => { + const s = initialPanels(["surfaces", null]); + expect(s.panels.map((p) => p.kind)).toEqual(["surfaces", null]); + expect(new Set(s.panels.map((p) => p.id)).size).toBe(2); + }); + + it("addPanel appends an empty panel with a fresh id", () => { + const seed = initialPanels(["surfaces"]); + const s = addPanel(seed); + expect(s.panels).toHaveLength(2); + expect(s.panels[1]?.kind).toBeNull(); + expect(s.panels[1]?.id).not.toBe(s.panels[0]?.id); + }); + + it("addPanel can seed a kind", () => { + const s = addPanel(initialPanels([null]), "surfaces"); + expect(s.panels[1]?.kind).toBe("surfaces"); + }); + + it("removePanel drops the matching id only", () => { + const seed = initialPanels(["surfaces", null]); + const firstId = seed.panels[0]?.id ?? -1; + const s = removePanel(seed, firstId); + expect(s.panels).toHaveLength(1); + expect(s.panels[0]?.kind).toBeNull(); + }); + + it("selectKind updates only the targeted panel", () => { + const seed = initialPanels([null, null]); + const targetId = seed.panels[1]?.id ?? -1; + const s = selectKind(seed, targetId, "surfaces"); + expect(s.panels[0]?.kind).toBeNull(); + expect(s.panels[1]?.kind).toBe("surfaces"); + }); + + it("is pure — never mutates the input state", () => { + const seed = initialPanels(["surfaces"]); + const snapshot = JSON.stringify(seed); + const id = seed.panels[0]?.id ?? -1; + addPanel(seed); + removePanel(seed, id); + selectKind(seed, id, null); + expect(JSON.stringify(seed)).toBe(snapshot); + }); +}); diff --git a/src/features/views/logic/panels.ts b/src/features/views/logic/panels.ts new file mode 100644 index 0000000..38c28fb --- /dev/null +++ b/src/features/views/logic/panels.ts @@ -0,0 +1,49 @@ +/** + * Pure reducer for the view sidebar's panel stack. + * + * A "view" is the RESERVED Dispatch sidebar affordance (see GLOSSARY): the user + * stacks panels, each showing one view KIND chosen from a dropdown, and adds + * more with a `+` button. This module is the pure model — zero DOM, zero Svelte. + * The component (`ViewSidebar.svelte`) is a thin runes wrapper over it. + * + * `id` is a per-session stable key for `{#each}` only; it is never persisted. + */ + +export interface ViewPanel { + readonly id: number; + /** Selected view-kind id, or `null` while the panel still reads "Select a view". */ + readonly kind: string | null; +} + +export interface PanelsState { + readonly panels: readonly ViewPanel[]; + readonly nextId: number; +} + +/** + * Seed state. Each entry becomes one panel in order; pass `["surfaces"]` to open + * a single preset panel, or `[null]` for one empty "Select a view" panel. + */ +export function initialPanels(kinds: readonly (string | null)[] = [null]): PanelsState { + let nextId = 0; + const panels = kinds.map((kind) => ({ id: nextId++, kind })); + return { panels, nextId }; +} + +export function addPanel(state: PanelsState, kind: string | null = null): PanelsState { + return { + panels: [...state.panels, { id: state.nextId, kind }], + nextId: state.nextId + 1, + }; +} + +export function removePanel(state: PanelsState, id: number): PanelsState { + return { ...state, panels: state.panels.filter((p) => p.id !== id) }; +} + +export function selectKind(state: PanelsState, id: number, kind: string | null): PanelsState { + return { + ...state, + panels: state.panels.map((p) => (p.id === id ? { ...p, kind } : p)), + }; +} |
