Files

97 lines
4.8 KiB
TypeScript

import { expect, mock, test } from 'claude-code/testing'
import { HIDE_ORDER, bar, layout, remainColor, usedColor } from './statusline'
import type { Segment } from './statusline'
import { CHARACTERS, packToCharacter, personaSection, registerCharacters, themeColor } from './characters'
import type { PackLike } from './characters'
const seg = (id: string, text: string): Segment => ({ id, parts: [{ text }] })
test('啟動角色後 persona 注入系統提示', async ($, on) => {
mock.store(on)
on('command.register', () => ({ value: { name: 'x' } }) as never)
on('session.start', () => ({ cwd: '/tmp' }) as never)
on('command.run', () => ({ text: '' }))
on('prompt.compose', () => ({ sections: [] }))
await $.session.start({ source: 'startup', cwd: '/tmp' } as never)
await $.command.run({ command: 'sao-asuna', args: '' } as never)
const res = await $.prompt.compose({ model: 'm', promptModel: 'm', surfaces: [], tools: [], outputStyle: null, traits: [] } as never)
expect(res.sections.some(s => s.id === 'jsc-anima:persona' && s.text.includes('亞絲娜'))).toBe(true)
})
test('切換角色與離開', async ($, on) => {
mock.store(on)
on('command.register', () => ({ value: { name: 'x' } }) as never)
on('session.start', () => ({ cwd: '/tmp' }) as never)
on('command.run', () => ({ text: '' }))
on('prompt.compose', () => ({ sections: [] }))
await $.session.start({ source: 'startup', cwd: '/tmp' } as never)
await $.command.run({ command: 'sao-asuna', args: '' } as never)
await $.command.run({ command: 'sao-suguha', args: '' } as never)
const compose = () => $.prompt.compose({ model: 'm', promptModel: 'm', surfaces: [], tools: [], outputStyle: null, traits: [] } as never)
expect((await compose()).sections.find(s => s.id === 'jsc-anima:persona')?.text).toContain('直葉')
await $.command.run({ command: 'anima-off', args: '' } as never)
expect((await compose()).sections.some(s => s.id === 'jsc-anima:persona')).toBe(false)
})
test('狀態列:窄時依序整欄隱藏,不截斷', () => {
const all = [seg('character', 'AB'), seg('model', 'CD'), seg('dir', 'EF'), seg('branch', 'GH'), seg('5h', 'IJ'), seg('7d', 'KL'), seg('ctx', 'MN'), seg('sys', 'OP')]
expect(layout(all, 999).length).toBe(8)
expect(layout(all, 26).map(s => s.id)).not.toContain('sys')
const narrow = layout(all, 14).map(s => s.id)
expect(narrow).toEqual(['character', 'model', '5h', 'ctx'])
expect([...HIDE_ORDER]).toEqual(['sys', '7d', 'dir', 'branch'])
})
test('量表固定長度,剩餘越少越紅', () => {
expect(bar(0).length).toBe(bar(100).length)
expect(remainColor(10, 60, 85)).toBe('red')
expect(remainColor(90, 60, 85)).toBe('green')
expect(usedColor(70, 60, 85)).toBe('yellow')
})
test('persona 含動漫層與口頭禪,主題色可用於狀態列', () => {
const c = CHARACTERS[0]!
const text = personaSection(c, c.axes)
expect(text).toContain('動漫刻畫')
expect(text).toContain('口頭禪')
expect(themeColor(c)).toBe('red')
})
test('角色包轉成可啟動角色:代號、年齡與原作戀人', () => {
const pack: PackLike = {
slug: 'rin', name: '凜', work: 'demo',
persona: { selfRef: '我', address: '你', tone: '冷靜', catchphrase: '嗯', values: '誠實', taboos: '不說謊' },
axes: CHARACTERS[0]!.axes,
anime: { moe: 'm', speech: 's', emotions: 'e', kaomoji: 'k', themeColor: 'blue' },
timeline: [{ chapter: '第2章', age: 19, relationships: [{ with: '蓮', kind: 'romantic' }] }],
}
const c = packToCharacter(pack, '示範')
expect(c.slug).toBe('demo-rin')
expect(c.timeline).toEqual([{ chapter: 2, age: 19, romance: '蓮' }])
expect(themeColor(c)).toBe('cyan')
registerCharacters([c, c])
expect(CHARACTERS.filter(x => x.slug === 'demo-rin').length).toBe(1)
})
test('角色包:最新章節狀態、作品用語與語言設定進入 persona', () => {
const pack: PackLike = {
slug: 'mio', name: '澪', work: 'demo2',
persona: { selfRef: '我', address: '你', tone: '冷靜', catchphrase: '嗯', values: '誠實', taboos: '不說謊' },
axes: CHARACTERS[0]!.axes,
anime: { moe: 'm', speech: 's', emotions: 'e', kaomoji: 'k', themeColor: 'red' },
language: '繁體中文,不使用口癖',
timeline: [
{ chapter: '第1章', mood: '緊張', dialogueStyle: '客氣', monologue: '想變強', place: '學院', age: 16, relationships: [] },
{ chapter: '第2章', mood: '安心', dialogueStyle: '放鬆', monologue: '想守護', place: '街上', age: 16, relationships: [] },
],
}
const c = packToCharacter(pack, '示範二', [{ term: '魔核', definition: '力量來源' }])
const text = personaSection(c, c.axes)
expect(text).toContain('最新章節(第2章)')
expect(text).toContain('安心')
expect(text).not.toContain('緊張')
expect(text).toContain('魔核=力量來源')
expect(text).toContain('不使用口癖')
})