import { describe, expect, it } from 'vitest' import { headLines } from './pathAskContext' import { PATH_ASK_HEAD_LINES, pathAskFollowUp, pathAskPrompt } from './pathAskPrompt' const FILE = 'app/[[...section]]/page.tsx' const FOLDER = 'app/[[...section]]' describe('问路径时发给模型的话', () => { it('文件:写明是文件,带相对路径和开头', () => { const prompt = pathAskPrompt({ kind: 'file', path: FILE, head: { lines: ['export default function Page() {', ' return null', '}'], truncated: false }, }, '这个名字是什么意思') expect(prompt).toContain(`这是一个文件,相对路径:${FILE}`) expect(prompt).toContain('1 | export default function Page() {') expect(prompt).toContain('3 | }') expect(prompt).toContain('问题:这个名字是什么意思') expect(prompt).toContain('只读,不要改任何东西') expect(prompt).not.toContain('剩下的需要时自己读') }) it('文件开头超过上限时说明只贴了开头,相对路径仍在', () => { const lines = Array.from({ length: PATH_ASK_HEAD_LINES }, (_, index) => `line ${index}`) const prompt = pathAskPrompt({ kind: 'file', path: FILE, head: { lines, truncated: true }, }, '?') expect(prompt).toContain(`这是一个文件,相对路径:${FILE}`) expect(prompt).toContain(`上面只贴了开头 ${PATH_ASK_HEAD_LINES} 行`) expect(prompt).toContain(`80 | line ${PATH_ASK_HEAD_LINES - 1}`) expect(prompt).not.toContain('line 80') }) it('工作区外的库文件:写明在项目外、给绝对路径(相对路径它读不到)', () => { const library = '/Users/me/.local/share/uv/python/cpython-3.12/lib/python3.12/pathlib.py' const prompt = pathAskPrompt({ kind: 'file', path: library, outside: true, head: { lines: ['"""Object-oriented filesystem paths."""'], truncated: true } }, 'rglob 怎么实现的') expect(prompt).toContain(`这是项目外的一个文件(比如依赖库的源码),绝对路径:${library}`) expect(prompt).not.toContain('相对路径:') }) it('文件夹:写明是文件夹,带相对路径,只列这一层的名字', () => { const prompt = pathAskPrompt({ kind: 'folder', path: FOLDER, children: [ { name: 'components', kind: 'folder' }, { name: 'page.tsx', kind: 'file' }, ], }, '[[...section]] 是啥意思') expect(prompt).toContain(`这是一个文件夹,相对路径:${FOLDER}`) expect(prompt).toContain('- 文件夹 components\n- 文件 page.tsx') expect(prompt).toContain('只列了这一层的名字') expect(prompt).toContain('问题:[[...section]] 是啥意思') expect(prompt).not.toContain('export default') }) }) describe('问路径时回答里的代码位置', () => { it('在 git 仓库里:要求写成从仓库根算起的链接,带例子;接着问时再说一遍', () => { const subject = { kind: 'folder' as const, path: FOLDER, repoRoot: '/work/shop', children: [] } const prompt = pathAskPrompt(subject, '这里放什么') expect(prompt).toContain('提到代码位置时写成 Markdown 链接,点了能跳过去。路径从仓库根 /work/shop 算起,开头不带 / 或 ./:') expect(prompt).toContain('[文件名:起-止](路径#L起-L止)') expect(prompt).toContain('例如:[OrderService.cancel](backend/app/orders/service.py#OrderService.cancel)') expect(prompt).not.toContain('提到文件时写相对项目根目录的路径') const followUp = pathAskFollowUp(subject, '再说说') expect(followUp).toMatch(/^接着问(还是 app\/\[\[\.\.\.section\]\] 这个文件夹,需要时重新读):再说说\n\n提到代码位置时写成 Markdown 链接/) }) it('不在 git 仓库里(库源码):写行内代码的绝对路径,不写链接', () => { const library = '/Users/me/.local/share/uv/python/cpython-3.12/lib/python3.12/pathlib.py' const prompt = pathAskPrompt({ kind: 'file', path: library, outside: true, head: { lines: [], truncated: false } }, '?') expect(prompt).toContain('问的代码不在 git 仓库里,提到代码位置时写行内代码:绝对路径加行号') expect(prompt).not.toContain('Markdown 链接') }) }) describe('文件开头截取', () => { it('超过行数上限时只留前 80 行,并标明没贴完', () => { const text = Array.from({ length: 100 }, (_, index) => `L${index}`).join('\n') expect(headLines(text, false, PATH_ASK_HEAD_LINES)).toEqual({ lines: Array.from({ length: PATH_ASK_HEAD_LINES }, (_, index) => `L${index}`), truncated: true, }) }) it('字节没读完时丢掉被截断的最后一行', () => { expect(headLines('alpha\nbeta-cut', true, PATH_ASK_HEAD_LINES)).toEqual({ lines: ['alpha'], truncated: true, }) }) it('空文件没有行', () => { expect(headLines('', false, PATH_ASK_HEAD_LINES)).toEqual({ lines: [], truncated: false }) }) })