93 lines
3.5 KiB
TypeScript
93 lines
3.5 KiB
TypeScript
import { describe, expect, test } from 'claude-code/testing'
|
|
|
|
import { createCompanion } from './core'
|
|
import { fakeClock, memoryFs } from './fs'
|
|
import { keywordEvent, MOOD_EVENTS } from './mood'
|
|
import { classify, regexTag, RULES } from './rules'
|
|
import { extractTag, isTag, stripTag, TAGS } from './tags'
|
|
import type { CompanionCore } from './types'
|
|
|
|
const MIKO: CompanionCore = {
|
|
id: 'miko',
|
|
name: '月見ミコ',
|
|
facts: {
|
|
birthday: 'unknown', zodiac: 'unknown', bloodType: 'unknown', hair: 'unknown',
|
|
face: 'unknown', height: 'unknown', weight: 'unknown', measurements: 'unknown',
|
|
},
|
|
neuroticism: 50,
|
|
baseline: { p: 0, a: 0, d: 0 },
|
|
}
|
|
|
|
describe('正則', () => {
|
|
test('明確情況判出對應標籤,優先序為責罵 > 感謝 > 完成', () => {
|
|
expect(regexTag('你很煩')).toBe('scold')
|
|
expect(regexTag('謝謝你')).toBe('thanks')
|
|
expect(regexTag('tests passed')).toBe('success')
|
|
expect(regexTag('謝謝,但你真笨')).toBe('scold')
|
|
expect(regexTag('請幫我看一下這個函式')).toBeNull()
|
|
})
|
|
|
|
test('keywordEvent 與舊行為相容', () => {
|
|
expect(keywordEvent('thanks a lot')).toBe('thanks')
|
|
expect(keywordEvent('hello')).toBeNull()
|
|
})
|
|
})
|
|
|
|
describe('白名單', () => {
|
|
test('每個標籤都有規則,MOOD_EVENTS 由規則表驅動', () => {
|
|
for (const t of TAGS) expect(RULES[t]).toBeDefined()
|
|
expect(Object.keys(RULES).length).toBe(TAGS.length)
|
|
expect(MOOD_EVENTS).toBe(RULES)
|
|
})
|
|
|
|
test('既有八個心情事件的數值不變', () => {
|
|
expect(RULES.thanks).toEqual({ p: 0.15, a: 0.05, d: 0.05 })
|
|
expect(RULES.scold).toEqual({ p: -0.2, a: 0.15, d: -0.1 })
|
|
expect(RULES.lateNight).toEqual({ p: -0.05, a: -0.1, d: 0 })
|
|
})
|
|
|
|
test('白名單外的標籤被拒絕,也不含戀愛與親密類', () => {
|
|
expect(isTag('thanks')).toBe(true)
|
|
expect(isTag('kiss')).toBe(false)
|
|
expect(isTag(undefined)).toBe(false)
|
|
expect(extractTag('好喔 <評估:kiss>')).toBeNull()
|
|
for (const t of ['confess', 'flirt', 'praiseLooks', 'seenThrough']) expect(isTag(t)).toBe(false)
|
|
})
|
|
|
|
test('回覆末尾的標籤可取出與移除', () => {
|
|
expect(extractTag('好的!\n<評估:amused>')).toBe('amused')
|
|
expect(extractTag('<評估:amused> 後面還有字')).toBeNull()
|
|
expect(stripTag('好的!\n<評估:amused>')).toBe('好的!')
|
|
})
|
|
})
|
|
|
|
describe('缺失退回', () => {
|
|
test('正則優先;正則沒判出才用模型標籤;模型標籤缺失或不合法就沒有標籤', () => {
|
|
expect(classify('謝謝', 'scold')).toBe('thanks')
|
|
expect(classify('今天好累', 'soothe')).toBe('soothe')
|
|
expect(classify('今天好累', 'bogus')).toBeNull()
|
|
expect(classify('今天好累', undefined)).toBeNull()
|
|
})
|
|
|
|
test('模型補標進入心情;正則已判出時不重複套用;不合法標籤沒有效果', async () => {
|
|
const mk = () => createCompanion({ fs: memoryFs(), clock: fakeClock(), root: '/h/.companion', builtins: [MIKO] })
|
|
const a = mk()
|
|
await a.ensureActivated()
|
|
await a.onPrompt('今天好累')
|
|
await a.onTurnComplete(false, '辛苦了\n<評估:soothe>')
|
|
expect((await a.view())!.mood.p).toBeCloseTo(RULES.soothe.p, 5)
|
|
|
|
const b = mk()
|
|
await b.ensureActivated()
|
|
await b.onPrompt('謝謝你')
|
|
await b.onTurnComplete(false, '不客氣\n<評估:scold>')
|
|
expect((await b.view())!.mood.p).toBeCloseTo(RULES.thanks.p, 5)
|
|
|
|
const c = mk()
|
|
await c.ensureActivated()
|
|
await c.onPrompt('今天好累')
|
|
await c.onTurnComplete(false, '辛苦了\n<評估:kiss>')
|
|
expect((await c.view())!.mood.p).toBe(0)
|
|
})
|
|
})
|