From 8ea067dcde917b5cb04c472df9ed37d1e2f8ac83 Mon Sep 17 00:00:00 2001 From: Erik Kotsiuba Date: Sat, 25 Apr 2026 21:40:09 +0200 Subject: [PATCH] test: tighten render assertions --- src/ui/render-frame.ts | 1 - tests/package.test.ts | 32 ----------------- tests/render-frame.test.ts | 69 ++++++++++++++++--------------------- tests/render-submit.test.ts | 65 +++++++++++++++------------------- 4 files changed, 57 insertions(+), 110 deletions(-) delete mode 100644 tests/package.test.ts diff --git a/src/ui/render-frame.ts b/src/ui/render-frame.ts index 9cab6a4..c406fdb 100644 --- a/src/ui/render-frame.ts +++ b/src/ui/render-frame.ts @@ -212,7 +212,6 @@ function wrapDelimitedFooterHints(footer: string, width: number): string[] { lines.push(...wrappedChunk); return lines; } - lines.push(current); lines.push(...wrappedChunk); return lines; }, []); diff --git a/tests/package.test.ts b/tests/package.test.ts deleted file mode 100644 index f10c6f0..0000000 --- a/tests/package.test.ts +++ /dev/null @@ -1,32 +0,0 @@ -import assert from "node:assert/strict"; -import { readdirSync, readFileSync } from "node:fs"; -import path from "node:path"; -import test from "node:test"; - -const skillsDir = path.resolve("skills"); -const SKILL_NAME_RE = /^name:\s*(.+)$/m; - -function readDeclaredSkillName(skillPath: string): string | undefined { - const content = readFileSync(skillPath, "utf8"); - const match = content.match(SKILL_NAME_RE); - return match?.[1]?.trim(); -} - -test("each bundled skill name matches its folder name", () => { - const skillFolders = readdirSync(skillsDir, { withFileTypes: true }).filter( - (entry) => entry.isDirectory() - ); - - assert.ok(skillFolders.length > 0); - - for (const folder of skillFolders) { - const skillPath = path.join(skillsDir, folder.name, "SKILL.md"); - const declaredName = readDeclaredSkillName(skillPath); - - assert.equal( - declaredName, - folder.name, - `${skillPath} declares name "${declaredName}" but folder is "${folder.name}"` - ); - } -}); diff --git a/tests/render-frame.test.ts b/tests/render-frame.test.ts index 9698976..c41b374 100644 --- a/tests/render-frame.test.ts +++ b/tests/render-frame.test.ts @@ -3,20 +3,6 @@ import test from "node:test"; import { createInitialState } from "../src/state/create.ts"; import { renderAskScreen } from "../src/ui/render.ts"; -function mockTheme() { - return { - fg(color: string, text: string) { - return `<${color}>${text}`; - }, - bg(color: string, text: string) { - return `{${color}}${text}{/${color}}`; - }, - bold(text: string) { - return text; - }, - } as never; -} - function mockEditor() { return { getText() { @@ -42,7 +28,7 @@ function plainTheme() { } as never; } -test("tab strip always shows dimmed framing arrows on wide screens", () => { +test("wide header keeps all tabs and framing arrows on the tab row", () => { const state = createInitialState({ title: "Demo", questions: [ @@ -63,13 +49,12 @@ test("tab strip always shows dimmed framing arrows on wide screens", () => { const lines = renderAskScreen({ state, - theme: mockTheme(), + theme: plainTheme(), width: 120, editor: mockEditor(), }); - assert(lines.some((line) => line.includes("← "))); - assert(lines.some((line) => line.includes(" ✔ Submit "))); + assert.equal(lines[3], " ← ☐ One ☐ Two ✔ Submit →"); }); test("narrow tab strip keeps active middle tab visible", () => { @@ -117,10 +102,7 @@ test("narrow tab strip keeps active middle tab visible", () => { editor: mockEditor(), }); - const tabLine = lines.find((line) => line.includes("← ")); - assert.ok(tabLine); - assert(tabLine.includes("Three")); - assert(!tabLine.includes("One")); + assert.equal(lines[3], " ← ☐ Three ☐ Four →"); }); test("narrow tab strip keeps submit tab visible when active", () => { @@ -157,10 +139,7 @@ test("narrow tab strip keeps submit tab visible when active", () => { editor: mockEditor(), }); - const tabLine = lines.find((line) => line.includes("Submit")); - assert.ok(tabLine); - assert(tabLine.includes("Submit")); - assert(tabLine.includes("←") || tabLine.includes("→")); + assert.equal(lines[3], " ← ✔ Submit →"); }); test("tab strip avoids truncation at narrow boundary widths", () => { @@ -201,23 +180,26 @@ test("tab strip avoids truncation at narrow boundary widths", () => { }); state.activeTabIndex = 2; - for (const width of [28, 29, 30, 31, 32]) { + const expectedByWidth = new Map([ + [28, " ← ☐ Three ☐ Four →"], + [29, " ← ☐ Three ☐ Four →"], + [30, " ← ☐ Three ☐ Four →"], + [31, " ← ☐ Two ☐ Three ☐ Four →"], + [32, " ← ☐ Two ☐ Three ☐ Four →"], + ]); + + for (const [width, expected] of expectedByWidth) { const lines = renderAskScreen({ state, theme: plainTheme(), width, editor: mockEditor(), }); - const tabLine = lines.find((line) => line.includes("← ")); - assert.ok(tabLine); - assert(tabLine.includes("Three")); - assert(!tabLine.includes("...")); - assert(!tabLine.includes("Fou...")); - assert(!tabLine.includes("Fi...")); + assert.equal(lines[3], expected); } }); -test("footer hints wrap on narrow screens instead of truncating", () => { +test("footer hints wrap into exact lines on narrow screens", () => { const state = createInitialState({ questions: [ { @@ -237,12 +219,15 @@ test("footer hints wrap on narrow screens instead of truncating", () => { editor: mockEditor(), }); - assert(lines.some((line) => line.includes("Space toggle"))); - assert(lines.some((line) => line.includes("Enter continue"))); - assert(lines.some((line) => line.includes("N/Shift+N note"))); + assert.deepEqual(lines.slice(-5, -1), [ + "Space toggle", + "Enter continue", + "N/Shift+N note", + "Esc dismiss", + ]); }); -test("footer keeps earlier hint chunks when later chunks wrap", () => { +test("footer keeps earlier hint chunk on the first wrapped line", () => { const state = createInitialState({ questions: [ { @@ -261,6 +246,10 @@ test("footer keeps earlier hint chunks when later chunks wrap", () => { editor: mockEditor(), }); - assert(lines.some((line) => line.includes("⇆ tab · ↑↓ select"))); - assert(lines.some((line) => line.includes("Enter confirm"))); + assert.equal(lines.at(-5), " ⇆ tab · ↑↓ select"); + assert.deepEqual(lines.slice(-4, -1), [ + "Enter confirm", + "N/Shift+N note", + "Esc dismiss", + ]); }); diff --git a/tests/render-submit.test.ts b/tests/render-submit.test.ts index eb6a4f5..4f4a94e 100644 --- a/tests/render-submit.test.ts +++ b/tests/render-submit.test.ts @@ -24,7 +24,7 @@ function plainTheme() { } as never; } -test("submit screen uses shorter action labels without extra prompt text", () => { +test("submit screen renders the compact action labels without extra prompt copy", () => { const state = createInitialState({ questions: [ { @@ -44,14 +44,14 @@ test("submit screen uses shorter action labels without extra prompt text", () => UI_DIMENSIONS.submitWideMinWidth + 16 ); - assert(!lines.some((line) => line.includes("Submit answers?"))); - assert(lines.some((line) => line.includes("1. Submit"))); - assert(lines.some((line) => line.includes("2. Elaborate"))); - assert(lines.some((line) => line.includes("3. Cancel"))); - assert(!lines.some((line) => line.includes("1. Submit answers"))); + assert.equal(lines[0], "❯ 1. Submit Review answers"); + assert.equal(lines[1], " 2. Elaborate "); + assert.equal(lines[2], " 3. Cancel Single"); + assert(!lines.join("\n").includes("Submit answers?")); + assert(!lines.join("\n").includes("Submit answers")); }); -test("submit screen uses side-by-side layout on wide screens", () => { +test("submit screen keeps review and actions grouped side by side on wide screens", () => { const state = createInitialState({ questions: [ { @@ -71,14 +71,12 @@ test("submit screen uses side-by-side layout on wide screens", () => { UI_DIMENSIONS.submitWideMinWidth + 16 ); - assert( - lines.some( - (line) => line.includes("1. Submit") && line.includes("Review answers") - ) - ); - assert( - lines.some((line) => line.includes("3. Cancel") && line.includes("Color")) - ); + assert.deepEqual(lines, [ + "❯ 1. Submit Review answers", + " 2. Elaborate ", + " 3. Cancel Color", + " → unanswered", + ]); }); test("submit screen stacks review above actions on narrow screens", () => { @@ -101,19 +99,16 @@ test("submit screen stacks review above actions on narrow screens", () => { UI_DIMENSIONS.submitWideMinWidth - 14 ); - const reviewIndex = lines.findIndex((line) => - line.includes("Review answers") - ); - const actionIndex = lines.findIndex((line) => line.includes("1. Submit")); - - assert.notEqual(reviewIndex, -1); - assert.notEqual(actionIndex, -1); - assert(reviewIndex < actionIndex); - assert( - !lines.some( - (line) => line.includes("1. Submit") && line.includes("Review answers") - ) - ); + assert.deepEqual(lines, [ + " Review answers", + "", + " Color", + " → unanswered", + "", + "❯ 1. Submit", + " 2. Elaborate", + " 3. Cancel", + ]); }); test("submit screen shows notes only for answered questions in submit mode", () => { @@ -280,7 +275,7 @@ test("submit screen renders multi-select option notes under their related answer assert(secondAnswerIndex < secondNoteIndex); }); -test("submit action column fits all action labels at the wide breakpoint", () => { +test("submit action column keeps all three actions on consecutive rows at the wide breakpoint", () => { const state = createInitialState({ questions: [ { @@ -300,12 +295,8 @@ test("submit action column fits all action labels at the wide breakpoint", () => UI_DIMENSIONS.submitWideMinWidth ); - assert( - lines.some( - (line) => line.includes("1. Submit") && line.includes("Review answers") - ) - ); - assert(lines.some((line) => line.includes("2. Elaborate"))); - assert(lines.some((line) => line.includes("3. Cancel"))); - assert(!lines.some((line) => line.includes("Elaborat\n"))); + assert.equal(lines[0], "❯ 1. Submit Review answers"); + assert.equal(lines[1], " 2. Elaborate "); + assert.equal(lines[2], " 3. Cancel Color"); + assert.equal(lines[3], " → unanswered"); });