import type { FeatureResult } from './featureResult' import type { Feature } from './features' import { describe, expect, it } from 'vitest' import { featureCommand, featureMessage, itemRow, parseFeatures, placeFeature, removeFeature, renameFeature, withResult } from './features' const RESULT: FeatureResult = { groups: [{ name: '登录页面', items: [{ folder: 'web', path: 'src/Login.tsx', symbol: 'LoginPage', role: '登录表单' }] }] } const feature = (slot: number, overrides: Partial = {}): Feature => ({ id: `f${slot}`, question: `问题${slot}`, name: `问题${slot}`, slot, ...overrides }) describe('存着的功能', () => { it('认得出的留下,坏的丢掉;结果读不懂的当还没有结果', () => { const stored = [ feature(1, { result: RESULT }), { id: 'x', question: 'q', name: 'q' }, 'bad', feature(2, { result: { groups: 'bad' } as unknown as FeatureResult }), ] expect(parseFeatures(stored)).toEqual([feature(1, { result: RESULT }), feature(2)]) expect(parseFeatures(undefined)).toEqual([]) }) it('同一个栏位存了两个时只认第一个', () => { expect(parseFeatures([feature(1), { ...feature(1), id: 'other' }])).toEqual([feature(1)]) }) }) describe('新功能占栏位、重命名、换结果、删除', () => { it('和收藏共用栏位:占没被占的里面最小的,满了没有', () => { expect(placeFeature([1, 2, 4])).toBe(3) expect(placeFeature(Array.from({ length: 30 }, (_, index) => index + 1))).toBeUndefined() }) it('重命名只改那一栏的名字,换结果只换那一个,删除只去掉那一个', () => { const features = [feature(1), feature(4)] expect(renameFeature(features, 4, '续费')).toEqual([feature(1), feature(4, { name: '续费' })]) expect(withResult(features, 'f1', RESULT)).toEqual([feature(1, { result: RESULT }), feature(4)]) expect(removeFeature(features, 1)).toEqual([feature(4)]) }) }) describe('功能栏标题栏上的命令', () => { it('每个栏位各有打开对话、重命名、删除', () => { expect(featureCommand('openChat', 3)).toBe('superpowers.codeReader.openFeatureChat3') expect(featureCommand('rename', 3)).toBe('superpowers.codeReader.renameFeature3') expect(featureCommand('remove', 30)).toBe('superpowers.codeReader.removeFeature30') }) }) describe('栏里的一行条目', () => { const item = RESULT.groups[0].items[0] it('名字后面淡色的是那一句话,悬停看文件、符号和那句话', () => { expect(itemRow(item, true)).toEqual({ label: 'LoginPage', description: '登录表单', tooltip: '**src\\/Login\\.tsx** \nLoginPage \n登录表单' }) expect(itemRow({ folder: 'web', path: '.env', role: '接口地址' }, true)).toEqual({ label: '.env', description: '接口地址', tooltip: '**\\.env** \n接口地址' }) }) it('找不到的悬停先说找不到了', () => { expect(itemRow(item, false).tooltip).toBe('找不到了 \n**src\\/Login\\.tsx** \nLoginPage \n登录表单') }) }) describe('栏上方的一句话', () => { it('正在找:第一次是「正在找…」,有结果后再找是按你说的改;带上正在做的事', () => { expect(featureMessage(undefined, { status: 'running' })).toBe('正在找…') expect(featureMessage(undefined, { status: 'running', activity: '读取 a.ts' })).toBe('正在找…(读取 a.ts)') expect(featureMessage(RESULT, { status: 'running' })).toBe('正在按你说的改…') }) it('找失败了:说原因、可以在对话里接着说重试;有旧结果时旧的留着', () => { expect(featureMessage(undefined, { status: 'failed', error: '没找到 Claude Code' })).toBe('没找成:没找到 Claude Code。可以在对话里接着说,让它再找一次。') expect(featureMessage(RESULT, { status: 'failed', error: '等太久,已经停掉。' })).toBe('这次没改成:等太久,已经停掉。下面还是上一次的结果,可以在对话里接着说,让它再改一次。') }) it('没在找:有结果不说话;一个环节都没有说没找到;还没有结果(停下了、重开了窗口)说可以接着说', () => { expect(featureMessage(RESULT, undefined)).toBeUndefined() expect(featureMessage(RESULT, { status: 'stopped' })).toBeUndefined() expect(featureMessage({ groups: [] }, undefined)).toBe('没找到和这个功能直接相关的代码,可以在对话里换个说法接着说。') expect(featureMessage(undefined, { status: 'stopped' })).toBe('还没有结果,可以在对话里接着说,让它再找一次。') expect(featureMessage(undefined, undefined)).toBe('还没有结果,可以在对话里接着说,让它再找一次。') }) })