Files
bare-operating-system/packages/bare-os-coreutils/test/edit-tui-sdk.test.mjs
T
2026-08-18 18:11:34 -04:00

170 lines
4.6 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/services/bare-os-tui-sdk.js'
const __dirname = dirname(fileURLToPath(import.meta.url))
const lib = (name) => join(__dirname, '../lib', name)
async function loadEditTui() {
const parts = await Promise.all([
readFile(lib('edit-ansi.js'), 'utf8'),
readFile(lib('edit-highlight.js'), 'utf8'),
readFile(lib('edit-buffer.js'), 'utf8'),
readFile(lib('edit-tui.js'), 'utf8')
])
const hook = {}
new Function(
'exports',
parts.join('\n') +
'\nexports.bareEditCreateTuiApp = bareEditCreateTuiApp;\nexports.bareEditTuiRunOpts = bareEditTuiRunOpts;\nexports.bareEditIsGotoChord = bareEditIsGotoChord;\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 ctx = {
env: {},
console: { error() {}, log() {} },
replStdin: makeStream(true, writes),
replStdout: makeStream(true, writes),
b4a: {
from(s) {
return Buffer.from(String(s))
},
toString(b) {
return Buffer.from(b).toString('utf8')
}
},
vfs: {
async writeFile() {}
},
suspendReplForSubprocess() {},
resumeReplAfterSubprocess() {},
...(extra || {})
}
attachBareOsTuiSdk(ctx)
return ctx
}
test('edit-tui source prefers ctx.tui.run with cell buffer', async (t) => {
const src = await readFile(lib('edit-tui.js'), 'utf8')
t.ok(src.includes('ctx.tui.run'), 'TEA path uses ctx.tui.run')
t.ok(src.includes("buffer: 'cell'"), 'uses cell buffer')
t.ok(src.includes('bareEditCreateTuiApp'), 'exports TEA factory')
t.ok(src.includes('bareOsRunEditTuiLegacy'), 'keeps pre-SDK fallback')
})
test('edit TEA app inserts, marks dirty, and quits on ctrl+x', async (t) => {
const hook = await loadEditTui()
const ctx = makeCtx()
const app = hook.bareEditCreateTuiApp(ctx, {
path: '/tmp/note.js',
initialText: 'const x = 1\n',
argv0: 'edit'
})
t.is(app.buf.dirty, false)
app.update(ctx.tui.decode('a')[0])
t.ok(app.buf.dirty)
t.ok(app.buf.lines[0].startsWith('a'))
const view = ctx.tui.style.stripAnsi(app.view())
t.ok(view.includes('edit'), 'status shows argv0')
t.ok(view.includes('[Modified]'))
app.update(ctx.tui.decode('\x18')[0])
t.is(app.mode, 'quit_confirm')
const no = app.update(ctx.tui.decode('n')[0])
t.is(typeof no[1], 'function')
t.is(no[1]().type, 'quit')
const clean = hook.bareEditCreateTuiApp(ctx, {
path: '/tmp/clean.txt',
initialText: 'ok',
argv0: 'edit'
})
const q = clean.update(ctx.tui.decode('\x18')[0])
t.is(q[1]().type, 'quit')
})
test('edit TEA app help overlay and search', async (t) => {
const hook = await loadEditTui()
const ctx = makeCtx()
const app = hook.bareEditCreateTuiApp(ctx, {
path: 'a.js',
initialText: 'hello\nworld\n',
argv0: 'edit'
})
app.update(ctx.tui.decode('\x07')[0])
t.is(app.mode, 'help')
const ovs = app.overlays({ width: 80, height: 24 })
t.ok(ovs.length >= 1)
t.ok(ctx.tui.style.stripAnsi(ovs[0].text).includes('Bare OS edit'))
app.update(ctx.tui.decode('x')[0])
t.is(app.mode, 'edit')
app.update(ctx.tui.decode('\x17')[0])
t.is(app.mode, 'prompt_search')
app.update(ctx.tui.decode('w')[0])
app.update(ctx.tui.decode('o')[0])
app.update(ctx.tui.decode('\r')[0])
t.is(app.mode, 'edit')
t.is(app.buf.row, 1)
t.is(app.buf.col, 0)
})
test('edit TEA app save command writes vfs', async (t) => {
const hook = await loadEditTui()
let written = ''
const ctx = makeCtx({
vfs: {
async writeFile(_p, buf) {
written = Buffer.from(buf).toString('utf8')
}
}
})
const app = hook.bareEditCreateTuiApp(ctx, {
path: '/tmp/a.txt',
initialText: 'hi',
argv0: 'edit'
})
app.update(ctx.tui.decode('!')[0])
const pair = app.update(ctx.tui.decode('\x13')[0])
t.ok(typeof pair[1] === 'function')
const msg = await pair[1]()
app.update(msg)
t.ok(msg.ok)
t.ok(written.startsWith('!hi'))
t.absent(app.buf.dirty)
})
test('kernel edit bin includes ctx.tui.run after build', async (t) => {
const bin = await readFile(
join(__dirname, '../../../kernel/bin/edit'),
'utf8'
)
t.ok(bin.includes('ctx.tui.run'), 'kernel edit uses ctx.tui.run')
t.ok(bin.includes("buffer: 'cell'"), 'kernel edit uses cell buffer')
})