Read the script
import type { On } from 'claude-code'
import { describe, expect, mock, test } from 'claude-code/testing'
import type { Engine } from 'claude-code/testing'
const PROPS = { hasSurvey: false, isWorking: false, maxRows: 20 } as never
const NO_BAND = 'no implementation for ui.render'
const deckOf = (source: string, batons: object[] = [{ label: 'line-a', prompt: 'resume line-a' }]) => ({
v: 1, source, repo: 'cortex',
status: [{ text: '有 2 個 commit 尚未推送', warn: true }],
actions: [
{ label: '載入記憶', prompt: 'load memory' },
{ label: '最近紀錄', prompt: 'recent raws' },
{ label: '處理待辦', prompt: 'process backlog' },
],
batons,
handoff: 'write a baton',
})
const hookText = (deck: unknown) =>
'[Cortexes] front\n\n<!-- cortex:menu -->\n' +
`<!-- cortex:deck ${JSON.stringify(deck)} -->\n` +
'在你第一次回覆使用者時,呈現以下格式:\n<!-- /cortex:menu -->\nOTHER PLUGIN'
/** Records each row's text as it reaches the engine, after the plugin's rewrite. */
function recordRows(on: On): string[] {
const rows: string[] = []
on('session.append', ($, e, next) => {
rows.push(e.message.content.map(b => (b.type === 'text' ? b.text : `[${b.type}]`)).join('|'))
return next(e)
})
return rows
}
let rowId = 0
/** Appends one row; nothing beneath the test stores rows, so the engine's refusal is expected. */
async function append($: Engine, texts: string[], door: 'hook-context' | 'prompt' = 'hook-context') {
const content = texts.map(text => ({ type: 'text', text }))
const message = door === 'prompt'
? { type: 'user', role: 'user', content }
: { type: 'attachment', name: 'hook_additional_context', content }
const origin = door === 'prompt' ? { kind: 'person' } : { kind: 'hook' }
try {
await $.session.append({ door, origin, uuid: `row-${++rowId}`, message } as never)
} catch {
// "no implementation for session.append": the test stands for the engine
}
}
/** Records submitted prompts and answers them as the engine would. */
function recordSubmits(on: On): { text: string; asUser?: boolean }[] {
const subs: { text: string; asUser?: boolean }[] = []
on('prompt.submit', ($, e) => {
subs.push({ text: e.text, asUser: (e.origin as { asUser?: boolean } | undefined)?.asUser })
return { text: e.text }
})
return subs
}
function recordToasts(on: On): string[] {
const toasts: string[] = []
on('ui.toast', ($, e) => {
toasts.push((e as { text: string }).text)
return { value: undefined } as never
})
return toasts
}
function answerMeasure(on: On): void {
on('session.measure', ($, e) => ({ changed: e.changed }))
}
const measure = ($: Engine, pct: number) =>
$.session.measure({ context: { window: 200000, tokens: pct * 2000, percent: pct }, rateLimits: [], changed: [] } as never)
const mountBand = ($: Engine, surface: 'terminal' | 'desktop' = 'terminal') =>
$.ui.mount({ plugin: 'cortexes', surface, component: 'AbovePrompt', props: PROPS })
describe('menu block', () => {
test('a hook-context row loses only the fenced block', async ($, on) => {
const rows = recordRows(on)
await append($, [hookText(deckOf('startup'))])
expect(rows).toEqual(['[Cortexes] front\n\nOTHER PLUGIN'])
})
test('the block is found in a later text block too', async ($, on) => {
const rows = recordRows(on)
await append($, ['OTHER PLUGIN FIRST', hookText(deckOf('startup'))])
expect(rows).toEqual(['OTHER PLUGIN FIRST|[Cortexes] front\n\nOTHER PLUGIN'])
})
test('rows from other doors keep the block', async ($, on) => {
const rows = recordRows(on)
const text = hookText(deckOf('startup'))
await append($, [text], 'prompt')
expect(rows).toEqual([text])
})
test('a row whose deck is not a deck stays whole and draws no band', async ($, on) => {
const rows = recordRows(on)
const text = hookText({ ...deckOf('startup'), v: 2 })
await append($, [text])
expect(rows).toEqual([text])
await expect(mountBand($)).rejects.toThrow(NO_BAND)
})
})
describe('band', () => {
for (const surface of ['terminal', 'desktop'] as const) {
test(`draws the deck and sends a button's prompt as the user (${surface})`, async ($, on) => {
recordRows(on)
const subs = recordSubmits(on)
await append($, [hookText(deckOf('startup'))])
const ui = await mountBand($, surface)
expect((await ui.find({ type: 'Text', text: 'cortexes' }))?.props.color).toBe('claude')
expect((await ui.find({ type: 'Text', text: '· 有 2 個 commit 尚未推送' }))?.props.color).toBe('warning')
expect(await ui.find({ type: 'Button', text: '接續交接 line-a' })).toBeDefined()
await ui.press({ key: 'action-0' })
expect(subs).toEqual([{ text: 'load memory', asUser: true }])
await expect(mountBand($, surface)).rejects.toThrow(NO_BAND)
})
}
test('the menu is a framed card of numbered buttons', async ($, on) => {
recordRows(on)
await append($, [hookText(deckOf('startup'))])
const ui = await mountBand($)
expect(await ui.drawn()).toMatchObject({ props: { borderStyle: 'round', borderColor: 'subtle' } })
const hotkeys = await Promise.all(['action-0', 'action-1', 'action-2', 'baton-0']
.map(async key => (await ui.find({ key }))?.props))
expect(hotkeys.map(p => [p?.plain, p?.hotkey])).toEqual([[true, '1'], [true, '2'], [true, '3'], [true, '4']])
expect((await ui.find({ key: 'hide' }))?.props).toMatchObject({ plain: true, dimColor: true })
expect((await ui.find({ key: 'hide' }))?.props.hotkey).toBeUndefined()
})
test('a baton shows its summary dim beside the button', async ($, on) => {
recordRows(on)
const batons = [{ label: 'line-a', summary: 'what line-a was doing', prompt: 'resume line-a' }]
await append($, [hookText(deckOf('startup', batons))])
const summary = await (await mountBand($)).find({ type: 'Text', text: 'what line-a was doing' })
expect(summary?.props).toMatchObject({ dimColor: true, wrap: 'truncate-end' })
})
test('a baton button sends its resu