import { expect, mock, test } from 'claude-code/testing' const NOW = new Date(2026, 9, 15, 10, 0).getTime() async function setup($: any, on: any, opts: { reply?: string; cwd?: string } = {}) { const cwd = { value: opts.cwd ?? '/proj-a' } const logs: string[] = [] const prompts: string[] = [] mock.store(on) const clock = mock.clock(on, { now: NOW }) on('command.register', () => ({ value: { name: 'x' } }) as never) on('session.start', () => ({ cwd: opts.cwd ?? '/proj-a' }) as never) on('session.cwd', () => ({ value: cwd.value })) on('command.run', () => ({ text: '' })) on('prompt.compose', () => ({ sections: [] })) on('prompt.submit', (_: any, e: any) => e as never) on('ui.log', (_: any, e: any) => { logs.push(String(e.text ?? e.message ?? JSON.stringify(e))) return { value: undefined } as never }) on('model.complete', (_: any, e: any) => { prompts.push(String(e.prompt)) return { value: opts.reply === undefined ? { isAnswered: true, text: '已經處理好了', usage: {} } : { isAnswered: opts.reply !== '', text: opts.reply, usage: {} } } as never }) await $.session.start({ source: 'startup', cwd: opts.cwd ?? '/proj-a' } as never) return { logs, prompts, clock, cwd } } const todo = ($: any, args: string) => $.command.run({ command: 'anima-todo', args } as never) const activate = ($: any) => $.command.run({ command: 'sao-asuna', args: '' } as never) test('未啟動角色時待辦被拒絕並提示先啟動', async ($, on) => { await setup($, on) const r = await todo($, 'list') expect(r.text).toContain('請先啟動角色') }) test('啟動後建立、更新與列出待辦,記錄所屬專案', async ($, on) => { await setup($, on) await activate($) expect((await todo($, 'add 開發 2026-10-16 09:30 重構路由')).text).toContain('#1') expect((await todo($, 'add 提醒 2026-10-15 11:00 喝水')).text).toContain('#2') expect((await todo($, 'update 1 研究 2026-10-17 08:00 讀文件')).text).toContain('已更新') const list = (await todo($, 'list')).text expect(list).toContain('#1 [研究] 2026-10-17 08:00 讀文件') expect(list).toContain('專案:/proj-a') expect(list.indexOf('#2')).toBeLessThan(list.indexOf('#1')) }) test('類型預設四種且可自訂,未知類型被拒絕', async ($, on) => { await setup($, on) await activate($) for (const t of ['提醒', '開發', '研究', '維護']) { expect((await todo($, `add ${t} 2026-10-16 09:00 x`)).text).toContain('已建立') } expect((await todo($, 'add 運動 2026-10-16 09:00 跑步')).text).toContain('沒有「運動」') expect((await todo($, 'type 運動')).text).toContain('已新增') expect((await todo($, 'add 運動 2026-10-16 09:00 跑步')).text).toContain('已建立') }) test('參數格式錯誤時顯示用法', async ($, on) => { await setup($, on) await activate($) expect((await todo($, 'add 開發 明天 重構')).text).toContain('用法') expect((await todo($, 'add 開發 2026-02-31 09:00 x')).text).toContain('用法') }) test('到點時由角色提醒一次,確認前不執行', async ($, on) => { const { logs, prompts } = await setup($, on) await activate($) await todo($, 'add 維護 2026-10-15 09:00 清理暫存') await $.prompt.submit({ text: '嗨', command: undefined, isMeta: false } as never) expect(logs.length).toBe(1) expect(logs[0]).toContain('亞絲娜 >') expect(logs[0]).toContain('清理暫存') expect(logs[0]).toContain('run 1') await $.prompt.submit({ text: '再一次', command: undefined, isMeta: false } as never) expect(logs.length).toBe(1) expect(prompts.some(p => p.includes('清理暫存'))).toBe(false) }) test('未到點不提醒', async ($, on) => { const { logs } = await setup($, on) await activate($) await todo($, 'add 提醒 2026-10-16 09:00 明天的事') await $.prompt.submit({ text: '嗨', command: undefined, isMeta: false } as never) expect(logs.length).toBe(0) }) test('確認後執行,並由角色告知結果', async ($, on) => { const { prompts } = await setup($, on, { reply: '清好了三個暫存目錄' }) await activate($) await todo($, 'add 維護 2026-10-15 09:00 清理暫存') const r = await todo($, 'run 1') expect(prompts.some(p => p.includes('清理暫存'))).toBe(true) expect(r.text).toBe('亞絲娜 > 清好了三個暫存目錄') expect((await todo($, 'list')).text).toContain('完成') expect((await todo($, 'run 1')).text).toContain('已經完成') }) test('執行失敗時保留為待辦', async ($, on) => { await setup($, on, { reply: '' }) await activate($) await todo($, 'add 開發 2026-10-15 09:00 重構') expect((await todo($, 'run 1')).text).toContain('執行失敗') expect((await todo($, 'list')).text).not.toContain('完成') }) test('待辦全域一份:換專案仍看得到', async ($, on) => { const { cwd } = await setup($, on, { cwd: '/proj-a' }) await activate($) await todo($, 'add 研究 2026-10-16 09:00 讀論文') cwd.value = '/proj-b' await todo($, 'add 研究 2026-10-16 10:00 另一專案的事') const list = (await todo($, 'list')).text expect(list).toContain('專案:/proj-a') expect(list).toContain('專案:/proj-b') })