|
1 | 1 | /** |
2 | | - * Frame-level checks for the command palette's three-column rows: the category |
3 | | - * prefix, the right-aligned chord, and how they degrade at narrow widths. |
| 2 | + * Frame-level checks for the command palette's rows: the label, the |
| 3 | + * right-aligned chord, and how the chord degrades at narrow widths. |
4 | 4 | */ |
5 | 5 | import { describe, expect, test } from "bun:test" |
6 | 6 |
|
@@ -39,33 +39,31 @@ function rowFor(rows: readonly string[], label: string): string | undefined { |
39 | 39 | } |
40 | 40 |
|
41 | 41 | describe("command palette rows", () => { |
42 | | - test("titles the box with a broken rule and shows the filter prompt", async () => { |
| 42 | + test("shows the filter prompt with no title rule above it", async () => { |
43 | 43 | const rows = await paletteFrame(100) |
44 | | - expect(rows.some((r) => r.startsWith("─ command palette ─"))).toBe(true) |
| 44 | + expect(rows.some((r) => r.startsWith("─ command palette ─"))).toBe(false) |
45 | 45 | expect(rows.some((r) => r.trim() === ">")).toBe(true) |
46 | 46 | }) |
47 | 47 |
|
48 | | - test("paints the category prefix and right-aligned chord at 100 columns", async () => { |
| 48 | + test("has no leading selection marker or kind column", async () => { |
49 | 49 | const rows = await paletteFrame(100) |
50 | 50 | const help = rowFor(rows, "Show keymap help") |
51 | 51 | expect(help).toBeDefined() |
52 | | - expect(help).toMatch(/^\s+[> ] view\s+Show keymap help\s+\?$/) |
| 52 | + expect(help).toMatch(/^\s*Show keymap help\s+\?$/) |
| 53 | + expect(help).not.toContain(">") |
| 54 | + expect(help).not.toContain("view") |
| 55 | + }) |
53 | 56 |
|
| 57 | + test("keeps the right-aligned chord at 100 columns", async () => { |
| 58 | + const rows = await paletteFrame(100) |
54 | 59 | const copy = rowFor(rows, "Copy active message / tool") |
55 | 60 | expect(copy?.endsWith("Alt+C")).toBe(true) |
56 | 61 | }) |
57 | 62 |
|
58 | | - test("keeps both side columns at 60 columns", async () => { |
59 | | - const rows = await paletteFrame(60) |
60 | | - const help = rowFor(rows, "Show keymap help") |
61 | | - expect(help).toContain("view") |
62 | | - expect(help?.endsWith("?")).toBe(true) |
63 | | - }) |
64 | | - |
65 | | - test("drops the chord first at 48 columns, keeping the category", async () => { |
66 | | - const rows = await paletteFrame(48) |
| 63 | + test("drops the chord at a narrow width, and the label always survives", async () => { |
| 64 | + const rows = await paletteFrame(36) |
67 | 65 | const help = rowFor(rows, "Show keymap help") |
68 | | - expect(help).toContain("view") |
| 66 | + expect(help).toBeDefined() |
69 | 67 | expect(help?.endsWith("?")).toBe(false) |
70 | 68 |
|
71 | 69 | const copy = rowFor(rows, "Copy active") |
@@ -193,3 +191,68 @@ describe("palette filters as you type", () => { |
193 | 191 | ) |
194 | 192 | }) |
195 | 193 | }) |
| 194 | + |
| 195 | +describe("command palette width", () => { |
| 196 | + // Both boxes are children of the same padded root; a width computed a |
| 197 | + // second way for the floating palette drifts from the prompt box's "100%". |
| 198 | + test("shares the prompt box's left/right edges while floating over landing", async () => { |
| 199 | + const rows = await withTestRenderer( |
| 200 | + async (h) => { |
| 201 | + const shell = createAppShell(h.renderer, { |
| 202 | + terminal: { columns: 80, rows: 24 }, |
| 203 | + wireKeys: false, |
| 204 | + run: "idle", |
| 205 | + }) |
| 206 | + openPalette(shell) |
| 207 | + await h.renderOnce() |
| 208 | + return h.captureCharFrame().split("\n") |
| 209 | + }, |
| 210 | + { width: 80, height: 24 }, |
| 211 | + ) |
| 212 | + const overlayTop = rows.find((r) => r.includes("┌")) |
| 213 | + const promptTop = rows.find((r) => r.includes("╭")) |
| 214 | + expect(overlayTop).toBeDefined() |
| 215 | + expect(promptTop).toBeDefined() |
| 216 | + expect(overlayTop?.indexOf("┌")).toBe(promptTop?.indexOf("╭")) |
| 217 | + expect(overlayTop?.lastIndexOf("┐")).toBe(promptTop?.lastIndexOf("╮")) |
| 218 | + }) |
| 219 | +}) |
| 220 | + |
| 221 | +describe("command palette selection colour", () => { |
| 222 | + test("marks the active row by text colour, not a filled background", async () => { |
| 223 | + await withTestRenderer( |
| 224 | + async (h) => { |
| 225 | + const shell = createAppShell(h.renderer, { |
| 226 | + terminal: { columns: 100, rows: 32 }, |
| 227 | + wireKeys: false, |
| 228 | + run: "idle", |
| 229 | + }) |
| 230 | + openPalette(shell) |
| 231 | + await h.renderOnce() |
| 232 | + const frame = h.captureSpans() |
| 233 | + const activeLine = frame.lines.find((line) => |
| 234 | + line.spans.some((s) => s.text.includes("Open permissions")), |
| 235 | + ) |
| 236 | + const groundLine = frame.lines.find((line) => |
| 237 | + line.spans.some((s) => s.text.includes("Ask operator question")), |
| 238 | + ) |
| 239 | + expect(activeLine).toBeDefined() |
| 240 | + expect(groundLine).toBeDefined() |
| 241 | + const activeBg = activeLine!.spans[0]!.bg |
| 242 | + const groundBg = groundLine!.spans[0]!.bg |
| 243 | + // Same background either way — selection reads through text colour |
| 244 | + // (fg), not a filled band behind the row. |
| 245 | + expect(activeBg).toEqual(groundBg) |
| 246 | + const activeFg = activeLine!.spans.find((s) => |
| 247 | + s.text.includes("Open permissions"), |
| 248 | + )!.fg |
| 249 | + const groundFg = groundLine!.spans.find((s) => |
| 250 | + s.text.includes("Ask operator question"), |
| 251 | + )!.fg |
| 252 | + expect(activeFg).not.toEqual(groundFg) |
| 253 | + }, |
| 254 | + { width: 100, height: 32 }, |
| 255 | + ) |
| 256 | + }) |
| 257 | +}) |
| 258 | + |
0 commit comments