// The three widgets a user reported as "not working": the category panel's // expand, the tile bar and the view list. History: context/test-evidence/clients.md. import { test, expect } from "@playwright/test"; /// Open the demo document and put the keyboard where the page listens. async function openApp(page) { await page.goto("/app/index.html"); await expect(page.locator("#grid table")).toBeVisible({ timeout: 20_000 }); await page.locator("#grid").focus(); } test.describe("the category panel", () => { // Expanding is resolved client-side and has always worked by KEYBOARD; the // panel is nevertheless the one of the three that has DOM, so this pins it. test("Enter on a category header reveals its items", async ({ page }) => { await openApp(page); const rows = page.locator("#categories li"); await page.keyboard.press("Shift+C"); await expect(page.locator("#mode")).toHaveText("CATEGORIES"); // web/demo.improv: _Index, _Dim, _Measure, Product, Quarter. Product is // the first with items a user would recognise. The glyphs are // `indicator()`'s, which is the page's ONE spelling and the TUI's too. for (let i = 0; i < 3; i++) await page.keyboard.press("j"); await expect(rows.filter({ hasText: "Product" })).toHaveText(/▶ Product \(4\)/); await page.keyboard.press("Enter"); await expect(rows.filter({ hasText: "Product" })).toHaveText(/▼ Product \(4\)/); await expect(page.locator("#categories li.item")).toHaveText([ "Espresso", "Latte", "Cold Brew", "Pastry", ]); await page.keyboard.press("Enter"); await expect(page.locator("#categories li.item")).toHaveCount(0); }); }); test.describe("the tile bar", () => { // Nothing was DRAWN: TileBarCache was folded, `T` entered the mode and the // axis keys reached the host, with no tile bar on screen to aim them at. test("the tiles are drawn, and tile-select moves a visible cursor", async ({ page, }) => { await openApp(page); const tiles = page.locator("#tiles .tile"); await expect(tiles).toHaveCount(5); // Axis labels are `Axis::short_label`: three columns, so Page is "Pag", // and None is "·" — an answer rather than a truncation (gotcha 26). await expect(tiles.filter({ hasText: "Product" })).toHaveText("Product Row"); await expect(tiles.filter({ hasText: "Quarter" })).toHaveText("Quarter Pag"); await expect(tiles.filter({ hasText: "_Index" })).toHaveText("_Index ·"); // Out of tile select nothing is highlighted, as the TUI's widget decides. await expect(page.locator("#tiles .tile.selected")).toHaveCount(0); await page.keyboard.press("Shift+T"); await expect(page.locator("#mode")).toHaveText("TILES"); await expect(tiles.nth(0)).toHaveClass(/selected/); await page.keyboard.press("ArrowRight"); await expect(tiles.nth(1)).toHaveClass(/selected/); await expect(page.locator("#tiles .tile.selected")).toHaveCount(1); }); // The axis change was reaching the host all along; what it could not do was // show the result. Setting Product to Column must redraw the tile AND the grid. test("setting a tile's axis is visible in the bar it was aimed at", async ({ page, }) => { await openApp(page); const product = page.locator("#tiles .tile").filter({ hasText: "Product" }); await expect(product).toHaveText("Product Row"); await page.keyboard.press("Shift+T"); for (let i = 0; i < 3; i++) await page.keyboard.press("ArrowRight"); await expect(product).toHaveClass(/selected/); // Product's four items are the grid's ROW headers to begin with. const rowHeaders = page.locator("#grid tbody th"); await expect(rowHeaders).toHaveText(["Espresso", "Latte", "Cold Brew", "Pastry"]); await page.keyboard.press("c"); await expect(product).toHaveText("Product Col", { timeout: 10_000 }); // The tile label is not the point on its own: the grid must have moved // too, or the bar would be reporting an axis the document does not have. // `.first()`: Product now shares the column axis with _Measure, so the // label appears once per header level. That it appears at all is the point. await expect( page.locator("#grid thead th").filter({ hasText: "Espresso" }).first(), ).toBeVisible(); await expect(rowHeaders).not.toHaveText(["Espresso", "Latte", "Cold Brew", "Pastry"]); }); }); test.describe("the view panel", () => { // Nothing was DRAWN: ViewListCache held both views and `V` entered ViewPanel // mode, so Enter switched views off a cursor the user could not see. test("V lists the views, marks the active one, and Enter switches", async ({ page, }) => { await openApp(page); await expect(page.locator("#views")).toBeHidden(); await expect(page.locator("#view-name")).toHaveText("Plan"); await page.keyboard.press("Shift+V"); await expect(page.locator("#mode")).toHaveText("VIEWS"); await expect(page.locator("#views")).toBeVisible(); // web/demo.improv carries two views; the axis line is the shared // `view_axis_summary`, so its absence would be a second derivation. await expect(page.locator("#views li")).toHaveCount(2); await expect(page.locator("#views li").nth(0)).toContainText("Default"); await expect(page.locator("#views li").nth(0).locator(".axes")).toHaveText( "R:Product C:Quarter", ); await expect(page.locator("#views li.active")).toContainText("Plan"); await expect(page.locator("#views li.selected")).toContainText("Default"); await page.keyboard.press("Enter"); await expect(page.locator("#view-name")).toHaveText("Default", { timeout: 10_000 }); // Switching returns to Normal but leaves the panel OPEN, exactly as // `src/draw.rs` keeps drawing it off `view_panel_open` alone. The active // marker is what must move, and it is the fact the list exists to carry. await expect(page.locator("#mode")).toHaveText("NORMAL"); await expect(page.locator("#views")).toBeVisible(); await expect(page.locator("#views li.active")).toContainText("Default"); }); }); test.describe("the formula panel", () => { // The third of `Panel::ALL`, equally undrawn and fixed by the same mechanism. test("F lists the document's formulas", async ({ page }) => { await openApp(page); await expect(page.locator("#formulas")).toBeHidden(); await page.keyboard.press("Shift+F"); await expect(page.locator("#mode")).toHaveText("FORMULAS"); await expect(page.locator("#formulas li")).toHaveText(["Profit = Revenue - Cost"]); await expect(page.locator("#formulas li.selected")).toHaveCount(1); }); });