Files
bare-operating-system/packages/bare-os-coreutils/test/chat-tui-sdk.test.mjs
T
Raven Scott ddebf42f1c
Release rolling / release (push) Successful in 9m38s
TUI Updates p2
2026-08-12 22:55:38 -04:00

186 lines
4.8 KiB
JavaScript

import test from 'brittle'
import { readFile } from 'node:fs/promises'
import { dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'
import { attachBareOsTuiSdk } from '../../bare-os-booter/lib/bare-os-tui-sdk.js'
const __dirname = dirname(fileURLToPath(import.meta.url))
const libPath = join(__dirname, '../lib/chat-tui.js')
async function loadChatTui() {
const src = await readFile(libPath, 'utf8')
const hook = {}
new Function(
'exports',
src +
'\nexports.bareChatCreateTuiApp = bareChatCreateTuiApp;\nexports.bareOsRunChatTui = bareOsRunChatTui;\nexports.bareChatTuiRunOpts = bareChatTuiRunOpts;\n'
)(hook)
return hook
}
function makeStream(isTTY, writes) {
return {
isTTY: !!isTTY,
raw: false,
columns: 80,
rows: 24,
write(s) {
writes.push(String(s))
},
setRawMode(v) {
this.raw = !!v
},
resume() {},
pause() {},
on() {},
removeListener() {}
}
}
function makeCtx(extra) {
const writes = []
const stdin = makeStream(true, writes)
const stdout = makeStream(true, writes)
const ctx = {
env: { BARE_CHAT_STATUS_MS: '0' },
console: { error() {}, log() {} },
replStdin: stdin,
replStdout: stdout,
suspendReplForSubprocess() {},
resumeReplAfterSubprocess() {},
...(extra || {})
}
attachBareOsTuiSdk(ctx)
return ctx
}
async function drain(app, cmd) {
if (!cmd) return
if (Array.isArray(cmd)) {
for (const c of cmd) await drain(app, c)
return
}
if (typeof cmd !== 'function') return
const msg = await cmd()
if (!msg) return
const pair = app.update(msg)
if (pair && pair[1]) await drain(app, pair[1])
}
test('chat-tui source prefers ctx.tui.run', async (t) => {
const src = await readFile(libPath, 'utf8')
t.ok(src.includes('ctx.tui.run'), 'TEA path uses ctx.tui.run')
t.ok(src.includes('bareChatCreateTuiApp'), 'exports TEA factory')
t.ok(src.includes('bareOsRunChatTuiLegacy'), 'keeps pre-SDK fallback')
})
test('chat TEA app loads history, types, and sends', async (t) => {
const hook = await loadChatTui()
const sent = []
const ctx = makeCtx({
bareOsChatSend(text) {
sent.push(text)
},
bareOsChatHistory() {
return [
{
body: 'hello',
displayName: 'alice',
tsMs: 0
}
]
},
bareOsChatSubscribe() {
return function () {}
},
bareOsChatProcSnapshot() {
return {
swarmPeers: 2,
metrics: { rxEvent: 1, txEvent: 3 },
protomuxChatRxTotal: 9
}
},
bareOsChatRooms() {
return ['general']
}
})
const app = hook.bareChatCreateTuiApp(ctx, 'chat')
await drain(app, app.init())
let plain = ctx.tui.style.stripAnsi(app.view())
t.ok(plain.includes('alice'), 'history line in transcript')
t.ok(plain.includes('hello'))
t.ok(plain.includes('peers 2'), 'proc snapshot in title')
t.ok(plain.includes('rx 1'))
t.ok(plain.includes('tx 3'))
app.update(ctx.tui.decode('h')[0])
app.update(ctx.tui.decode('i')[0])
t.is(app.input.value, 'hi')
await drain(app, app.update(ctx.tui.decode('\r')[0])[1])
t.alike(sent, ['hi'])
t.is(app.input.value, '')
})
test('chat TEA app help, scroll, and incoming events', async (t) => {
const hook = await loadChatTui()
const ctx = makeCtx({
bareOsChatHistory() {
return Array.from({ length: 20 }, (_, i) => ({
body: 'm' + i,
displayName: 'n',
tsMs: i
}))
},
bareOsChatSubscribe() {
return function () {}
},
bareOsChatProcSnapshot() {
return { swarmPeers: 0 }
}
})
const app = hook.bareChatCreateTuiApp(ctx, 'chat')
app.height = 12
await drain(app, app.init())
t.is(app.mode, 'main')
t.ok(app.stickToBottom)
app.update(ctx.tui.decode('?')[0])
t.is(app.mode, 'help')
const help = ctx.tui.style.stripAnsi(app.view())
t.ok(help.includes('Press any key'))
app.update(ctx.tui.decode('x')[0])
t.is(app.mode, 'main')
app.update(ctx.tui.decode('\x1b[A')[0])
t.absent(app.stickToBottom)
t.ok(app.scrollTop < app.transcript.length)
app.update({
type: 'chat.event',
ev: { body: 'yo', displayName: 'bob', receivedAtMs: 99 }
})
const after = ctx.tui.style.stripAnsi(app.view())
t.ok(
app.transcript.some((line) => line.includes('bob') && line.includes('yo'))
)
void after
})
test('kernel chat bin includes ctx.tui.run after build', async (t) => {
const bin = await readFile(
join(__dirname, '../../../kernel/bin/chat'),
'utf8'
)
t.ok(bin.includes('ctx.tui.run'), 'kernel chat uses ctx.tui.run')
t.ok(bin.includes('bareChatCreateTuiApp'), 'kernel chat includes TEA factory')
})
test('chat TEA app maps BARE_EDIT_NO_ALTSCREEN', async (t) => {
const hook = await loadChatTui()
const ctx = makeCtx()
ctx.env.BARE_EDIT_NO_ALTSCREEN = '1'
const opts = hook.bareChatTuiRunOpts(ctx)
t.is(opts.altScreen, false)
})