u
This commit is contained in:
95
.ui-smoke.mjs
Normal file
95
.ui-smoke.mjs
Normal file
@@ -0,0 +1,95 @@
|
||||
import { createElement as h } from 'react';
|
||||
import { renderToString } from 'ink';
|
||||
import assert from 'node:assert/strict';
|
||||
import chalk from 'chalk';
|
||||
import { createStore } from './src/ui/store.js';
|
||||
import { App } from './src/ui/app.js';
|
||||
|
||||
// --- fixture: exercises header, history, command bar, token footer ---
|
||||
function buildMainStore() {
|
||||
const store = createStore();
|
||||
const RESET = '\x1b[0m', BG = '\x1b[100m', BGCLOSE = '\x1b[49m';
|
||||
const paint = (text, width = 40) => text.split('\n').map((l) => {
|
||||
const vis = l.replace(/\x1b\[[0-9;]*m/g, '').length;
|
||||
const pad = l + ' '.repeat(Math.max(0, width - vis));
|
||||
return BG + pad.replace(/\x1b\[0m/g, RESET + BG) + BGCLOSE;
|
||||
}).join('\n');
|
||||
|
||||
store.setMeta({ model: 'smoke-model', workspace: '/tmp/ws', reasoning: 'low' });
|
||||
store.log(chalk.cyan.bold(' [banner line]'));
|
||||
store.log('\n ' + chalk.green.bold('Assistant') + '\n ' + '-'.repeat(20) + '\n' + paint(' ' + chalk.bold('hello') + ' world'));
|
||||
store.addUsage({ input: 1234, output: 567, cached: 100, cost: 0.01234 });
|
||||
store.prompt({ type: 'main', message: 'You', prefill: '/rea' });
|
||||
return store;
|
||||
}
|
||||
|
||||
// --- 1. main pane layout ---
|
||||
const store = buildMainStore();
|
||||
const out = renderToString(h(App, { store }), { columns: 80 });
|
||||
console.log('----- JSON -----');
|
||||
console.log(JSON.stringify(out));
|
||||
console.log('----- VISIBLE -----');
|
||||
console.log(out);
|
||||
|
||||
// Strip ANSI for structural assertions.
|
||||
const strip = (s) => s.replace(/\x1b\[[0-9;]*m/g, '');
|
||||
const vis = strip(out);
|
||||
assert.ok(vis.includes('FIDO'), 'header chrome renders');
|
||||
assert.ok(vis.includes('smoke-model'), 'header shows model');
|
||||
assert.ok(vis.includes('[banner line]'), 'history renders');
|
||||
assert.ok(vis.includes('hello world'), 'history renders painted block');
|
||||
assert.ok(vis.includes('/reasoning'), 'command bar shows slash autocomplete');
|
||||
assert.ok(vis.includes('in=1,234'), 'footer shows token totals');
|
||||
assert.ok(vis.includes('cost=$0.01234'), 'footer shows cost');
|
||||
assert.ok(vis.includes('PgUp/PgDn'), 'footer shows scroll hints');
|
||||
store.resolve(null); // settle the prompt promise so the process can exit
|
||||
|
||||
// --- 2. scrolled history hides the live tail, shows the scroll badge ---
|
||||
const scrolled = buildMainStore();
|
||||
for (let i = 1; i <= 30; i++) scrolled.log(` filler line ${i}`);
|
||||
scrolled.scrollBy(10);
|
||||
const outScrolled = strip(renderToString(h(App, { store: scrolled }), { columns: 80 }));
|
||||
assert.ok(outScrolled.includes('(PgUp/PgDn'), 'header shows scroll badge when scrolled');
|
||||
assert.ok(!outScrolled.includes('filler line 30'), 'scrolled view hides the live tail');
|
||||
assert.ok(outScrolled.includes('filler line 20'), 'scrolled view shows older lines');
|
||||
scrolled.resolve(null);
|
||||
|
||||
// Wheel decoding is intentionally exercised through the same store movement
|
||||
// semantics as the terminal listener: up increases the offset, down decreases.
|
||||
const wheel = buildMainStore();
|
||||
for (let i = 1; i <= 30; i++) wheel.log(` wheel line ${i}`);
|
||||
wheel.scrollBy(3);
|
||||
assert.equal(wheel.getState().scroll, 3, 'wheel-up delta moves toward older history');
|
||||
wheel.scrollBy(-3);
|
||||
assert.equal(wheel.getState().scroll, 0, 'wheel-down delta returns to the live tail');
|
||||
wheel.resolve(null);
|
||||
|
||||
// --- 3. tiny-terminal fallback (rows < 12): plain column, no clipping ---
|
||||
// NOTE: renderToString only accepts `columns`, so drive the fallback through
|
||||
// App's `sizeOverride` test seam instead of render options.
|
||||
const tiny = buildMainStore();
|
||||
const outTiny = strip(renderToString(h(App, { store: tiny, sizeOverride: { columns: 80, rows: 8 } }), { columns: 80 }));
|
||||
assert.ok(outTiny.includes('[banner line]'), 'tiny fallback still renders history');
|
||||
assert.ok(outTiny.includes('in=1,234'), 'tiny fallback still shows totals');
|
||||
assert.ok(!outTiny.includes('PgUp/PgDn history'), 'tiny fallback drops fullscreen key hints');
|
||||
tiny.resolve(null);
|
||||
|
||||
// --- 4. live reasoning streams into the scrollable history pane ---
|
||||
const reasoningStore = createStore();
|
||||
reasoningStore.setBusy(true);
|
||||
reasoningStore.setStatus('thinking…');
|
||||
reasoningStore.setReasoningDraft('Analyzing user question\nChecking relevant files');
|
||||
|
||||
const outLiveReasoning = strip(renderToString(h(App, { store: reasoningStore, sizeOverride: { columns: 80, rows: 24 } }), { columns: 80 }));
|
||||
assert.ok(outLiveReasoning.includes('🧠 Reasoning (thinking…)'), 'history pane streams live reasoning with thinking header');
|
||||
assert.ok(outLiveReasoning.includes('Analyzing user question'), 'history pane shows line 1 of reasoning');
|
||||
assert.ok(outLiveReasoning.includes('Checking relevant files'), 'history pane shows line 2 of reasoning');
|
||||
|
||||
// Finalizing promotes to permanent entry without "(thinking…)"
|
||||
reasoningStore.finalizeReasoning();
|
||||
const outFinalReasoning = strip(renderToString(h(App, { store: reasoningStore, sizeOverride: { columns: 80, rows: 24 } }), { columns: 80 }));
|
||||
assert.ok(outFinalReasoning.includes('🧠 Reasoning'), 'finalized reasoning has header');
|
||||
assert.ok(!outFinalReasoning.includes('🧠 Reasoning (thinking…)'), 'finalized reasoning drops thinking indicator from header');
|
||||
assert.ok(outFinalReasoning.includes('Analyzing user question'), 'finalized reasoning preserves content');
|
||||
|
||||
console.log('\nSMOKE-OK: header/history/command-bar/footer + scroll + tiny fallback + streaming reasoning');
|
||||
Reference in New Issue
Block a user