97 lines
4.8 KiB
TypeScript
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('不使用口癖')
|
|
})
|