diff options
| author | Adam Malczewski <[email protected]> | 2026-06-10 16:29:01 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-06-10 16:29:01 +0900 |
| commit | 871957b930203c019e631c4606cfdf8266d222fa (patch) | |
| tree | 50c522018c3ce4127ffa76f4b3b6c7843e90db43 /src/features/views/logic/panels.test.ts | |
| parent | 7b345f132763fa6405ae858b74e46229629c19d9 (diff) | |
| download | dispatch-web-871957b930203c019e631c4606cfdf8266d222fa.tar.gz dispatch-web-871957b930203c019e631c4606cfdf8266d222fa.zip | |
feat(views,surface-host): Extensions sidebar view — auto-expanded surfaces + tables
views (new feature):
- pure panel-stack reducer + thin generic ViewSidebar (dropdown picker + add/remove),
switches on view KIND, never a surface id
Extensions view (composition root):
- folds frontend modules + backend surfaces into one "Extensions" view
- frontend module list AGGREGATED from each feature's public `manifest` export
(can't drift); no per-module version (FE features are internal to dispatch-web)
- surfaces are AUTO-SUBSCRIBED on catalog + rendered expanded (no catalog buttons)
surface-host:
- consecutive `stat` fields coalesce into one aligned label/value table (StatTable)
- generic custom-field renderer: dispatch on rendererId === "table" → SurfaceTable
(pure parseTablePayload), so a backend `custom`/table field renders generically
- shared presentational components/Table.svelte (used by both, neither feature
depends on the other)
store:
- auto-subscribe every catalog entry, unsubscribe vanished ones, re-subscribe all
on reconnect; expose all received specs via `surfaces` (drops single-selection)
backend-handoff: CR-1 — emit Loaded Extensions as a custom/table field; notes
what's already covered FE-side (renderer shipped, stat-table fallback works).
Diffstat (limited to 'src/features/views/logic/panels.test.ts')
| -rw-r--r-- | src/features/views/logic/panels.test.ts | 55 |
1 files changed, 55 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); + }); +}); |
