summaryrefslogtreecommitdiffhomepage
path: root/src/features/surface-host/logic/table.test.ts
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-06-10 16:29:01 +0900
committerAdam Malczewski <[email protected]>2026-06-10 16:29:01 +0900
commit871957b930203c019e631c4606cfdf8266d222fa (patch)
tree50c522018c3ce4127ffa76f4b3b6c7843e90db43 /src/features/surface-host/logic/table.test.ts
parent7b345f132763fa6405ae858b74e46229629c19d9 (diff)
downloaddispatch-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/surface-host/logic/table.test.ts')
-rw-r--r--src/features/surface-host/logic/table.test.ts47
1 files changed, 47 insertions, 0 deletions
diff --git a/src/features/surface-host/logic/table.test.ts b/src/features/surface-host/logic/table.test.ts
new file mode 100644
index 0000000..e55b3f7
--- /dev/null
+++ b/src/features/surface-host/logic/table.test.ts
@@ -0,0 +1,47 @@
+import { describe, expect, it } from "vitest";
+import { parseTablePayload } from "./table";
+
+describe("parseTablePayload", () => {
+ it("parses a well-formed table payload", () => {
+ const data = parseTablePayload({
+ columns: ["Name", "Version"],
+ rows: [
+ ["alpha", "1.0"],
+ ["beta", "2.3"],
+ ],
+ });
+ expect(data).toEqual({
+ columns: ["Name", "Version"],
+ rows: [
+ ["alpha", "1.0"],
+ ["beta", "2.3"],
+ ],
+ });
+ });
+
+ it("coerces numeric and boolean cells to strings", () => {
+ const data = parseTablePayload({
+ columns: ["k", "n", "b"],
+ rows: [["x", 42, true]],
+ });
+ expect(data?.rows[0]).toEqual(["x", "42", "true"]);
+ });
+
+ it("accepts an empty rows array", () => {
+ expect(parseTablePayload({ columns: ["A"], rows: [] })).toEqual({ columns: ["A"], rows: [] });
+ });
+
+ it.each([
+ ["null", null],
+ ["a number", 7],
+ ["a string", "nope"],
+ ["missing columns", { rows: [] }],
+ ["missing rows", { columns: ["A"] }],
+ ["non-string column", { columns: [1], rows: [] }],
+ ["row that is not an array", { columns: ["A"], rows: ["x"] }],
+ ["cell of unsupported type", { columns: ["A"], rows: [[{ nested: true }]] }],
+ ["non-finite numeric cell", { columns: ["A"], rows: [[Number.NaN]] }],
+ ])("returns null for invalid payload: %s", (_label, payload) => {
+ expect(parseTablePayload(payload)).toBeNull();
+ });
+});