import { test } from 'node:test'; import assert from 'node:assert/strict'; import { createTestRenderer } from '@opentui/core/testing'; import { OpenTuiShell } from '../src/opentui-shell.ts'; import { HarnessInventoryController } from '../src/harness-inventory.ts'; import type { ProviderClient, ProviderRequest, ProviderSnapshot } from '../src/provider-protocol.ts'; import { ProviderDialog } from '../src/provider-dialog.ts'; function fixture() { const requests: ProviderRequest[] = []; const secrets: string[] = []; const snapshot: ProviderSnapshot = { revision: 1, rows: [{ id: 'p', name: 'Fixture', type: 'openai', endpoint: 'https://api.openai.com/v1', state: 'SAVED', detail: 'Access untested' }], phase: 'idle', message: 'Select a provider.', secretLength: 0 }; const listeners = new Set<(s: ProviderSnapshot) => void>(); const client: ProviderClient = { snapshot: () => structuredClone(snapshot), subscribe(fn) { listeners.add(fn); fn(structuredClone(snapshot)); return () => { listeners.delete(fn); }; }, async request(request) { requests.push(request); return true; }, secret(action, value) { if (value) secrets.push(value); snapshot.secretLength = action === 'clear' ? 0 : action === 'backspace' ? Math.max(0, snapshot.secretLength - 1) : snapshot.secretLength + (value?.length ?? 0); for (const fn of listeners) fn(structuredClone(snapshot)); }, cancel() {}, dispose() {}, }; const inventory = new HarnessInventoryController({ read: () => ({ version: 1, revision: 0, agents: [], providers: [], runtimes: [] }), save: candidate => candidate }, async () => []); return { client, inventory, requests, secrets, snapshot, emit() { for (const fn of listeners) fn(structuredClone(snapshot)); } }; } for (const [width, height] of [[120, 40], [60, 20]]) test(`Providers signed-out keyboard actions and modal at ${width}x${height}`, async () => { const ui = await createTestRenderer({ width, height, exitOnCtrlC: false }), f = fixture(); const shell = new OpenTuiShell(ui.renderer, f.inventory, undefined, f.client); try { for (let i = 0; i < 3; i++) ui.mockInput.pressArrow('right'); ui.mockInput.pressEnter(); await ui.renderOnce(); assert.doesNotMatch(ui.captureCharFrame(), /Add provider/); ui.mockInput.pressTab(); await ui.renderOnce(); assert.equal(shell.state.focus, 'detail'); assert.match(ui.captureCharFrame(), /Refresh models/); ui.mockInput.pressArrow('down'); ui.mockInput.pressEnter(); await ui.renderOnce(); assert.deepEqual(f.requests.at(-1), { action: 'models', provider: 'p', revision: 1 }); ui.mockInput.pressArrow('up'); ui.mockInput.pressEnter(); await ui.renderOnce(); assert.match(ui.captureCharFrame(), /EDIT PROVIDER/); assert.match(ui.captureCharFrame(), /••••••••••••/); assert.doesNotMatch(ui.captureCharFrame(), /blank keeps saved|Leave blank/); ui.mockInput.pressTab(); await ui.renderOnce(); ui.mockInput.typeText('never-render-this-key'); await ui.renderOnce(); assert.doesNotMatch(ui.captureCharFrame(), /never-render-this-key/); assert.ok(f.secrets.length > 0); ui.mockInput.pressEnter(); ui.mockInput.pressEnter(); await ui.renderOnce(); await new Promise(resolve => setTimeout(resolve, 10)); assert.equal(f.requests.at(-1)?.action, 'save'); assert.doesNotMatch(JSON.stringify(f.requests), /never-render-this-key/); } finally { shell.close(); } }); test('form input preserves q as text and cancellation clears secret without save', async () => { const ui = await createTestRenderer({ width: 60, height: 20, exitOnCtrlC: false }); const calls: string[] = []; const dialog = new ProviderDialog(ui.renderer, 'ADD PROVIDER', [{ label: 'Name', value: '' }, { label: 'API key', secret: true }], action => calls.push(action)); try { dialog.key({ name: 'q', sequence: 'q', ctrl: false, shift: false, meta: false }); assert.equal(dialog.fields[0]?.value, 'q'); dialog.cancel(); assert.equal(await dialog.done, undefined); assert.deepEqual(calls, ['clear']); } finally { ui.renderer.destroy(); } }); test('minimum-size results and recovery text remain reachable; only the model count is shown', async () => { const ui = await createTestRenderer({ width: 60, height: 20, exitOnCtrlC: false }), f = fixture(); const shell = new OpenTuiShell(ui.renderer, f.inventory, undefined, f.client); try { for (let i = 0; i < 3; i++) ui.mockInput.pressArrow('right'); ui.mockInput.pressEnter(); ui.mockInput.pressTab(); f.snapshot.resultTarget = 'p'; f.snapshot.phase = 'error'; f.snapshot.message = 'Denied. Check provider access. Reload before retrying.'; f.emit(); await ui.renderOnce(); assert.match(ui.captureCharFrame(), /Denied/); assert.match(ui.captureCharFrame(), /retrying/); ui.mockInput.pressKey('\x1b[6~'); await ui.renderOnce(); assert.match(ui.captureCharFrame(), /Type/); f.snapshot.phase = 'idle'; f.snapshot.message = 'Models loaded.'; f.snapshot.rows[0]!.state = 'Connected'; f.snapshot.rows[0]!.modelCount = 2; f.emit(); await ui.renderOnce(); assert.match(ui.captureCharFrame(), /Models loaded/); for (let i = 0; i < 12; i++) ui.mockInput.pressKey('\x1b[6~'); await ui.renderOnce(); assert.match(ui.captureCharFrame(), /Connected · 2 models/); assert.doesNotMatch(ui.captureCharFrame(), /gpt-visible|MODELS/); f.snapshot.message = 'Stopped waiting. Changes may already be saved. Reload before retrying.'; f.emit(); await ui.renderOnce(); assert.match(ui.captureCharFrame(), /Stopped waiting/); } finally { shell.close(); } }); test('success and failure reports belong only to their selected provider', async () => { const ui = await createTestRenderer({ width: 60, height: 20, exitOnCtrlC: false }), f = fixture(); f.snapshot.rows.push({ id: 'other', name: 'Other account', type: 'anthropic', endpoint: 'https://api.anthropic.com', state: 'SAVED', detail: 'Untested' }); f.snapshot.resultTarget = 'p'; f.snapshot.message = 'OpenAI result only'; const shell = new OpenTuiShell(ui.renderer, f.inventory, undefined, f.client); try { for (let i = 0; i < 3; i++) ui.mockInput.pressArrow('right'); ui.mockInput.pressEnter(); ui.mockInput.pressArrow('down'); await ui.renderOnce(); assert.doesNotMatch(ui.captureCharFrame(), /OpenAI result only/); ui.mockInput.pressArrow('up'); await ui.renderOnce(); assert.match(ui.captureCharFrame(), /OpenAI result only/); f.snapshot.phase = 'error'; f.snapshot.message = 'OpenAI denied only'; f.emit(); ui.mockInput.pressArrow('down'); await ui.renderOnce(); assert.doesNotMatch(ui.captureCharFrame(), /OpenAI denied only|FAILED/); ui.mockInput.pressArrow('up'); await ui.renderOnce(); assert.match(ui.captureCharFrame(), /OpenAI denied only/); } finally { shell.close(); } }); for (const [width, height] of [[120, 40], [60, 20]]) test(`fresh Providers always exposes supported types and states at ${width}x${height}`, async () => { const ui = await createTestRenderer({ width, height, exitOnCtrlC: false }), f = fixture(); f.snapshot.rows = []; const shell = new OpenTuiShell(ui.renderer, f.inventory, undefined, f.client); try { for (let i = 0; i < 3; i++) ui.mockInput.pressArrow('right'); ui.mockInput.pressEnter(); await ui.renderOnce(); const frame = ui.captureCharFrame(); assert.match(frame, /MODEL ACCOUNTS/); assert.match(frame, /OpenAI/); assert.match(frame, /Anthropic/); assert.match(frame, /OpenRouter/); assert.match(frame, /NOT SET/); assert.doesNotMatch(frame, /ENV MISSING/); ui.mockInput.pressTab(); ui.mockInput.pressEnter(); await ui.renderOnce(); assert.match(ui.captureCharFrame(), /API key/); assert.doesNotMatch(ui.captureCharFrame(), /Type ←/); } finally { shell.close(); } }); for (const [width, height] of [[120, 40], [60, 20]]) for (const saved of [false, true]) test(`Databricks workspace is editable with saved/default/empty precedence at ${width}x${height}, saved=${saved}`, async () => { const ui = await createTestRenderer({ width, height, exitOnCtrlC: false }), f = fixture(); f.snapshot.rows = saved ? [{ id: 'dbr', name: 'Databricks', type: 'databricks_v2', endpoint: 'https://saved.example', state: 'Not checked', detail: 'Saved' }] : []; f.snapshot.databricksHost = saved ? 'https://environment.example' : undefined; const shell = new OpenTuiShell(ui.renderer, f.inventory, undefined, f.client); try { for (let i = 0; i < 3; i++) ui.mockInput.pressArrow('right'); ui.mockInput.pressEnter(); for (let i = 0; i < 4; i++) ui.mockInput.pressArrow('down'); ui.mockInput.pressTab(); ui.mockInput.pressEnter(); await ui.renderOnce(); assert.match(ui.captureCharFrame(), /Workspace \(DATABRICKS_HOST\)/); assert.doesNotMatch(ui.captureCharFrame(), /REQUIRED|← →|environment.example/); if (saved) assert.match(ui.captureCharFrame(), /https:\/\/saved.example/); ui.mockInput.pressTab(); ui.mockInput.pressKey('\x15'); ui.mockInput.typeText('https://edited.example'); await ui.renderOnce(); assert.match(ui.captureCharFrame(), /https:\/\/edited.example/); ui.mockInput.pressEnter(); ui.mockInput.pressEnter(); await ui.renderOnce(); assert.equal(f.requests.at(-1)?.action, 'save'); assert.equal(f.requests.at(-1)?.values?.endpoint, 'https://edited.example'); if (!saved) { f.snapshot.databricksHost = 'https://environment.example'; f.emit(); ui.mockInput.pressEnter(); await ui.renderOnce(); assert.match(ui.captureCharFrame(), /https:\/\/environment.example/); } } finally { shell.close(); } }); for (const [width, height] of [[120, 40], [60, 20]]) test(`dialog visual hierarchy and pointer focus at ${width}x${height}`, async () => { const ui = await createTestRenderer({ width, height, exitOnCtrlC: false }); const dialog = new ProviderDialog(ui.renderer, 'ADD PROVIDER', [{ label: 'Name', value: 'OpenAI' }, { label: 'API key', secret: true }], () => {}); const rgb = (color: { buffer: Uint16Array }) => Array.from(color.buffer).slice(0, 3).join(','); const spans = () => ui.captureSpans().lines.flatMap(line => line.spans); const check = (text: string, color: string) => { const span = spans().find(span => span.text.includes(text)); assert.ok(span, text); assert.equal(rgb(span.fg), color); assert.equal(rgb(span.bg), '13,16,14'); }; const key = (name: string) => dialog.key({ name, sequence: '', ctrl: false, meta: false, shift: false }); try { await ui.renderOnce(); check('Name', '125,134,124'); check('OpenAI', '220,225,216'); check('[', '255,211,78'); check('Enter API key', '125,134,124'); key('tab'); await ui.renderOnce(); check('Enter API key', '125,134,124'); const keyboard = ui.captureCharFrame(); key('up'); await ui.renderOnce(); const lines = ui.captureCharFrame().split('\n'), y = lines.findIndex(line => line.includes('Enter API key')); await ui.mockMouse.click(lines[y]!.indexOf('Enter API key'), y); await ui.renderOnce(); assert.equal(ui.captureCharFrame(), keyboard); dialog.updateSecretLength(4); await ui.renderOnce(); check('••••', '220,225,216'); key('tab'); await ui.renderOnce(); check('Save', '220,225,216'); check('Tab next', '125,134,124'); for (const span of spans()) assert.notEqual(rgb(span.bg), '220,225,216'); } finally { dialog.cancel(); ui.renderer.destroy(); } }); for (const [width, height] of [[120, 40], [60, 20]]) test(`choice arrows and notice actions have separate boundaries at ${width}x${height}`, async () => { const ui = await createTestRenderer({ width, height, exitOnCtrlC: false }); let dialog = new ProviderDialog(ui.renderer, 'ADD PROVIDER', [{ label: 'Type', value: 'openai', choices: ['openai', 'anthropic'] }], () => {}, 'Continue'); try { await ui.renderOnce(); const lines = ui.captureCharFrame().split('\n'); assert.doesNotMatch(lines.find(line => line.includes('Type'))!, /←|→/); assert.match(lines.find(line => line.includes('openai'))!, /\[ openai +← → \]/); assert.match(lines.find(line => line.includes('Continue'))!, /\[ Continue +\]/); dialog.cancel(); dialog = new ProviderDialog(ui.renderer, 'NOTICE', [], () => {}, 'Close', 'Nothing changed.'); await ui.renderOnce(); assert.match(ui.captureCharFrame(), /\[ Close +\]/); const help = ui.captureSpans().lines.flatMap(line => line.spans).find(span => span.text.includes('Enter or Esc close'))!; assert.deepEqual(Array.from(help.fg.buffer).slice(0, 3), [125, 134, 124]); assert.deepEqual(Array.from(help.bg.buffer).slice(0, 3), [13, 16, 14]); } finally { dialog.cancel(); ui.renderer.destroy(); } }); for (const [width, height] of [[120, 40], [60, 20]]) test(`all provider actions stay above output with matching pointer focus at ${width}x${height}`, async () => { const ui = await createTestRenderer({ width, height, exitOnCtrlC: false }), f = fixture(); f.snapshot.rows[0] = { id: 'p', name: 'Workspace', type: 'databricks_v2', endpoint: 'https://fixture.cloud.databricks.com', state: 'SAVED', detail: 'Sign in explicitly, then test.' }; f.snapshot.databricksHost = f.snapshot.rows[0]!.endpoint; const shell = new OpenTuiShell(ui.renderer, f.inventory, undefined, f.client); try { for (let i = 0; i < 3; i++) ui.mockInput.pressArrow('right'); ui.mockInput.pressEnter(); for (let i = 0; i < 4; i++) ui.mockInput.pressArrow('down'); await ui.renderOnce(); const lines = () => ui.captureCharFrame().split('\n'); const actionRow = (name: string) => lines().findIndex(line => line.includes(name)); const names = ['Edit configuration', 'Refresh models', 'Sign in to Databricks']; const heading = actionRow('AVAILABLE ACTIONS'); assert.ok(heading < height / 2); for (const [i, name] of names.entries()) assert.equal(actionRow(name), heading + i + 1); const spans = () => ui.captureSpans().lines.flatMap(line => line.spans); const rgb = (value: { buffer: Uint16Array }) => Array.from(value.buffer).slice(0, 3).join(','); assert.equal(rgb(spans().find(span => span.text.includes('AVAILABLE ACTIONS'))!.fg), '255,211,78'); ui.mockInput.pressTab(); ui.mockInput.pressArrow('down'); await ui.renderOnce(); const focused = spans().find(span => span.text.includes('› Refresh models'))!; assert.equal(rgb(focused.fg), '255,211,78'); assert.equal(rgb(focused.bg), '13,16,14'); const keyboard = ui.captureCharFrame(); ui.mockInput.pressTab(); await ui.renderOnce(); const y = actionRow('Refresh models'), x = lines()[y]!.indexOf('Refresh models'); await ui.mockMouse.click(x, y); await ui.renderOnce(); assert.equal(ui.captureCharFrame(), keyboard); assert.equal(f.requests.at(-1)?.action, 'models'); f.snapshot.resultTarget = 'p'; f.snapshot.phase = 'error'; f.snapshot.message = 'Denied. Check access. Reload before retrying.'; f.emit(); await ui.renderOnce(); assert.match(ui.captureCharFrame(), /FAILED: Denied/); assert.ok(actionRow('FAILED') > actionRow('Sign in to Databricks')); assert.doesNotMatch(ui.captureCharFrame(), /Set up Codex|Set up Pi|Test provider|Load models/); let recoveryVisible = false; for (let i = 0; i < 10; i++) { recoveryVisible ||= /retrying/.test(ui.captureCharFrame()); for (const [index, name] of names.entries()) assert.equal(actionRow(name), heading + index + 1); ui.mockInput.pressKey('\x1b[6~'); await ui.renderOnce(); } assert.ok(recoveryVisible); } finally { shell.close(); } }); for (const [width, height] of [[120, 40], [60, 20]]) test(`provider inventory has no global Add command and retains saved accounts at ${width}x${height}`, async () => { const ui = await createTestRenderer({ width, height, exitOnCtrlC: false }), f = fixture(); f.snapshot.rows.push({ ...f.snapshot.rows[0]!, id: 'second', name: 'Second' }); const before = structuredClone(f.snapshot); const shell = new OpenTuiShell(ui.renderer, f.inventory, undefined, f.client); try { for (let i = 0; i < 3; i++) ui.mockInput.pressArrow('right'); ui.mockInput.pressEnter(); await ui.renderOnce(); assert.doesNotMatch(ui.captureCharFrame(), /Add provider/); assert.match(ui.captureCharFrame(), /Fixture/); assert.match(ui.captureCharFrame(), /Second/); ui.mockInput.pressArrow('down'); ui.mockInput.pressTab(); ui.mockInput.pressEnter(); await ui.renderOnce(); assert.match(ui.captureCharFrame(), /EDIT PROVIDER/); assert.match(ui.captureCharFrame(), /Second/); ui.mockInput.pressEscape(); await new Promise(resolve => setTimeout(resolve, 50)); await ui.renderOnce(); assert.doesNotMatch(ui.captureCharFrame(), /EDIT PROVIDER/); ui.mockInput.pressTab(); for (let i = 0; i < 5; i++) ui.mockInput.pressArrow('down'); ui.mockInput.pressTab(); ui.mockInput.pressEnter(); await ui.renderOnce(); assert.deepEqual(f.requests.at(-1), { action: 'reload' }); assert.doesNotMatch(ui.captureCharFrame(), /Add provider|ADD PROVIDER/); const lines = ui.captureCharFrame().split('\n'); const y = lines.findIndex(line => line.includes('› Reload providers')); assert.ok(y >= 0, ui.captureCharFrame()); const count = f.requests.length; await ui.mockMouse.click(lines[y]!.indexOf('Reload providers'), y); await ui.renderOnce(); assert.equal(f.requests.length, count + 1); assert.deepEqual(f.requests.at(-1), { action: 'reload' }); assert.deepEqual(f.snapshot, before); } finally { shell.close(); } }); for (const [width, height] of [[120, 40], [60, 20]]) test(`provider status is complete before scrolling at ${width}x${height}`, async () => { const ui = await createTestRenderer({ width, height, exitOnCtrlC: false }), f = fixture(); f.snapshot.rows[0]!.state = 'Connected'; f.snapshot.rows[0]!.modelCount = 1; f.snapshot.rows[0]!.detail = 'Model catalog reachable. Model execution has not been tested.'; const shell = new OpenTuiShell(ui.renderer, f.inventory, undefined, f.client); try { for (let i = 0; i < 3; i++) ui.mockInput.pressArrow('right'); ui.mockInput.pressEnter(); await ui.renderOnce(); assert.match(ui.captureCharFrame(), /Connected · 1 model/); assert.doesNotMatch(ui.captureCharFrame(), /1 models/); if (height === 20) assert.match(ui.captureCharFrame(), /Execution not tested/); f.snapshot.rows[0]!.state = 'Failed'; delete f.snapshot.rows[0]!.modelCount; f.snapshot.rows[0]!.detail = 'Could not load models. Check the API key, endpoint, network and model access, then retry Refresh models.'; f.snapshot.resultTarget = 'p'; f.snapshot.phase = 'error'; f.snapshot.message = 'Provider saved. Existing runs are unchanged. Model catalog failed.'; f.emit(); await ui.renderOnce(); const frame = ui.captureCharFrame(); assert.match(frame, /Check API key, endpoint/); assert.match(frame.replace(/[│\s]+/g, ' '), /Then Refresh models/); if (height === 20) { assert.match(frame, /Catalog unavailable/); assert.doesNotMatch(frame, /Check th|DETAILS/); } ui.mockInput.pressArrow('down'); ui.mockInput.pressTab(); ui.mockInput.pressEnter(); await ui.renderOnce(); assert.match(ui.captureCharFrame(), /CONFIGURE PROVIDER/); assert.doesNotMatch(ui.captureCharFrame(), /ADD PROVIDER/); } finally { shell.close(); } }); for (const [width, height] of [[120, 40], [60, 20]]) test(`header navigation paints Providers and cancels on exit without Enter at ${width}x${height}`, async () => { const ui = await createTestRenderer({ width, height, exitOnCtrlC: false }), f = fixture(); let cancelled = 0; f.client.cancel = () => { cancelled++; }; const shell = new OpenTuiShell(ui.renderer, f.inventory, undefined, f.client); try { for (let i = 0; i < 3; i++) ui.mockInput.pressArrow('right'); await ui.renderOnce(); assert.equal(shell.state.focus, 'header'); assert.match(ui.captureCharFrame(), /MODEL ACCOUNTS/); assert.deepEqual(f.requests, [{ action: 'reload' }]); ui.mockInput.pressArrow('right'); await ui.renderOnce(); assert.equal(shell.state.mode, 'owner'); assert.equal(shell.state.focus, 'header'); assert.doesNotMatch(ui.captureCharFrame(), /MODEL ACCOUNTS/); assert.equal(cancelled, 1); ui.mockInput.pressArrow('left'); await ui.renderOnce(); assert.match(ui.captureCharFrame(), /MODEL ACCOUNTS/); assert.equal(f.requests.length, 2); ui.mockInput.pressEnter(); await ui.renderOnce(); assert.equal(shell.state.focus, 'list'); assert.equal(f.requests.length, 2); assert.ok(f.requests.every(request => request.action === 'reload')); } finally { shell.close(); } }); for (const [width, height] of [[120, 40], [60, 20]]) test(`saved mask is retained until replacement, clear or cancellation at ${width}x${height}`, async () => { const ui = await createTestRenderer({ width, height, exitOnCtrlC: false }); const calls: { action: string; value?: string }[] = []; let dialog: ProviderDialog; let secret = ''; const open = () => new ProviderDialog(ui.renderer, 'EDIT PROVIDER', [{ label: 'API key', secret: true, savedSecret: true }], (action, value) => { calls.push({ action, value }); secret = action === 'clear' ? '' : action === 'backspace' ? secret.slice(0, -1) : secret + value; dialog.updateSecretLength(secret.length); }); const key = (name: string, ctrl = false) => dialog.key({ name, ctrl, sequence: '', shift: false, meta: false }); try { dialog = open(); dialog.updateSecretLength(0); await ui.renderOnce(); assert.match(ui.captureCharFrame(), /••••••••••••/); key('return'); key('return'); assert.deepEqual(await dialog.done, {}); assert.equal(calls.length, 0); dialog = open(); dialog.paste('replacement'); await ui.renderOnce(); assert.equal(secret, 'replacement'); assert.match(ui.captureCharFrame(), /••••••••••• /); assert.doesNotMatch(ui.captureCharFrame(), /replacement/); key('u', true); await ui.renderOnce(); assert.equal(secret, ''); assert.match(ui.captureCharFrame(), /Enter API key/); dialog.cancel(); assert.equal(await dialog.done, undefined); dialog = open(); key('backspace'); await ui.renderOnce(); assert.match(ui.captureCharFrame(), /Enter API key/); dialog.paste('cancelled'); dialog.cancel(); assert.equal(secret, ''); assert.equal(await dialog.done, undefined); dialog = open(); await ui.renderOnce(); assert.match(ui.captureCharFrame(), /••••••••••••/); dialog.cancel(); } finally { ui.renderer.destroy(); } });