// What a node in a delegated surface MEANS, and what the page bar reads as. // Both are decisions, so neither needs a browser — context/code/clients.md. import { test } from "node:test"; import assert from "node:assert/strict"; import { cellAction, cellAttrs, gridPlan, indexAction, menuKey, pageEntries, pageItemAction, } from "../shell.js"; const cell = (text, extra = {}) => ({ text, numeric: true, error: false, selected: false, editable: true, ...extra, }); /// The shape the row index has to survive: a group header between data rows, /// which is exactly what walking the table back up would get wrong. function grouped() { return { row_levels: 1, col_levels: [["Revenue", "Cost"]], col_groups: [], totals: [], editing: null, rows: [ { kind: "group", label: "Hot", collapsed: false }, { kind: "data", labels: ["Espresso"], cells: [cell("1"), cell("2")] }, { kind: "group", label: "Cold", collapsed: true }, { kind: "data", labels: ["Cold Brew"], cells: [cell("3", { editable: false }), cell("4")], }, ], }; } test("a cell's attributes carry the plan's answers back to the handler", () => { const plan = gridPlan(grouped()); const last = plan.body.at(-1).cells[1]; const action = cellAction(cellAttrs(last), last.editable); assert.deepEqual(action, { row: 1, col: 1, editable: true }); }); // improvise-b8ot: the DATA row index is what a click sends, and `gridPlan` // counts it because a group header is not a data row. A delegated handler // that inferred the row from the node's position in the table would say 3. test("the row a delegated click means is the data index, not the table row", () => { const plan = gridPlan(grouped()); const rows = plan.body .filter((r) => r.kind === "data") .map((r) => cellAction(cellAttrs(r.cells[0]), r.cells[0].editable).row); assert.deepEqual(rows, [0, 1]); }); test("editable is the class, so an unsettable cell opens no editor", () => { const plan = gridPlan(grouped()); const aggregate = plan.body.at(-1).cells[0]; assert.equal(aggregate.classes.editable, false); assert.equal(cellAction(cellAttrs(aggregate), false).editable, false); }); // Every header, the corner and the totals strip reach the delegated handler // too. None of them is a cell, and `Number("")` is 0 rather than nothing. test("a node carrying no coordinates means nothing at all", () => { assert.equal(cellAction({}, false), null); assert.equal(cellAction({ row: "0" }, false), null); assert.equal(cellAction({ row: "", col: "" }, false), null); assert.equal(cellAction({ row: "x", col: "0" }, false), null); assert.equal(cellAction({ row: "1.5", col: "0" }, false), null); }); test("an index surface reads its row back, and nothing else", () => { assert.equal(indexAction({ index: "0" }), 0); assert.equal(indexAction({ index: "12" }), 12); assert.equal(indexAction({}), null); assert.equal(indexAction({ index: "" }), null); assert.equal(indexAction({ index: "-1" }), null); }); // improvise-p7lh: a grid filtered to Q1 said nothing at all, and `[`/`]` // moved it in silence. The bar is what says which quarter this is. test("the page bar names the category and the item it is fixed to", () => { const [entry] = pageEntries({ page_coords: [ { cat: "Quarter", selected: "Q2", cycle: { at: 1, count: 4, items: ["Q1", "Q2", "Q3", "Q4"] }, }, ], }); assert.equal(entry.text, "Quarter = Q2"); assert.equal(entry.position, "2/4", "one-based, as a user counts"); assert.equal(entry.navigable, true); }); // Gotcha 27: Page is navigable and Filter is not. A Filter axis offers no // position, because there is no key that would move it. test("a filter axis is shown, and is not offered as something to cycle", () => { const [entry] = pageEntries({ page_coords: [{ cat: "Region", selected: "North", cycle: null }], }); assert.equal(entry.text, "Region = North"); assert.equal(entry.position, ""); assert.equal(entry.navigable, false); }); test("no fixed categories is an empty bar, not a blank one", () => { assert.deepEqual(pageEntries({ page_coords: [] }), []); }); // improvise-rw6l: the arrow and the menu follow `cycle`, which is the Page / // Filter split decided in Rust — never "is there an entry on the bar". test("a page axis offers its items, and a filter axis offers none", () => { const [page, filter] = pageEntries({ page_coords: [ { cat: "Quarter", selected: "Q3", cycle: { at: 2, count: 3, items: ["Q1", "Q2", "Q3"] }, }, { cat: "Region", selected: "North", cycle: null }, ], }); assert.deepEqual(page.items, [ { item: 0, label: "Q1", selected: false }, { item: 1, label: "Q2", selected: false }, { item: 2, label: "Q3", selected: true }, ]); assert.equal(page.index, 0, "the axis names itself by its place on the bar"); assert.deepEqual( filter.items, [], "a Filter axis constrains without being navigable: nothing to pick", ); assert.equal(filter.index, 1); }); // One meaning across two surfaces (improvise-b8ot): a chip carries an axis, a // menu row carries an axis AND an item. Nothing infers from structure. test("a chip opens the menu and a menu row picks, one attribute apart", () => { assert.deepEqual(pageItemAction({ axis: "1" }), { kind: "open", axis: 1 }); assert.deepEqual(pageItemAction({ axis: "1", item: "0" }), { kind: "pick", axis: 1, item: 0, }); assert.equal(pageItemAction({}), null, "the gaps between the chips are not a chip"); assert.equal(pageItemAction({ axis: "" }), null); assert.equal(pageItemAction({ axis: "-1" }), null); assert.deepEqual( pageItemAction({ axis: "0", item: "" }), { kind: "open", axis: 0 }, "an empty item attribute is NO item, where Number('') would say 0", ); }); // The menu owns the keyboard while it is open, so every key is answered and // nothing falls through to the grid it is anchored to. test("the menu answers its own keys and swallows the rest", () => { const at = (active) => ({ count: 3, active }); assert.deepEqual(menuKey("ArrowDown", at(0)), { kind: "move", item: 1 }); assert.deepEqual(menuKey("j", at(2)), { kind: "move", item: 0 }, "and it wraps"); assert.deepEqual(menuKey("ArrowUp", at(0)), { kind: "move", item: 2 }, "both ways"); assert.deepEqual(menuKey("k", at(1)), { kind: "move", item: 0 }); assert.deepEqual(menuKey("Home", at(2)), { kind: "move", item: 0 }); assert.deepEqual(menuKey("End", at(0)), { kind: "move", item: 2 }); assert.deepEqual(menuKey("Enter", at(1)), { kind: "pick", item: 1 }); assert.deepEqual(menuKey(" ", at(1)), { kind: "pick", item: 1 }); assert.deepEqual(menuKey("Escape", at(1)), { kind: "close" }); assert.deepEqual(menuKey("Tab", at(1)), { kind: "close" }); assert.deepEqual( menuKey("h", at(1)), { kind: "ignore" }, "a grid key is SWALLOWED, not passed on — the grid is behind the menu", ); assert.deepEqual( menuKey("ArrowDown", { count: 0, active: 0 }), { kind: "close" }, "a menu with nothing in it cannot be moved around", ); });