186 lines
4.8 KiB
JavaScript
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)
|
|
})
|