import { readFileSync } from 'node:fs' import * as path from 'node:path' import { describe, expect, it } from 'vitest' import { buildPromptContext, renderPromptTemplate } from './promptTemplate' const brainstormTemplate = readFileSync( path.resolve(__dirname, '../../prompts/brainstorm.md'), 'utf-8', ) describe('buildPromptContext', () => { it('把 state 全量摊平进上下文', () => { const ctx = buildPromptContext({ branch: 'feature/x', pr: '12' }, { issueNumber: '7' }) expect(ctx).toEqual({ branch: 'feature/x', pr: '12', issueNumber: '7' }) }) it('额外变量里的 undefined 不覆盖 state 已有的值', () => { const ctx = buildPromptContext({ planFile: '.claude/plans/a.md' }, { planFile: undefined }) expect(ctx.planFile).toBe('.claude/plans/a.md') }) it('state 缺失时退化成只有额外变量', () => { expect(buildPromptContext(undefined, { nonce: 'abc' })).toEqual({ nonce: 'abc' }) }) }) describe('renderPromptTemplate', () => { it('不做 HTML 转义,引号和 & 原样进提示词', () => { expect(renderPromptTemplate('说 "{{x}}" & 走', { x: 'a { expect(() => renderPromptTemplate('{{#if x}}没闭合', {})).toThrow() }) }) describe('默认头脑风暴模板', () => { it('有工单号时走续聊分支,没有时走新建工单分支', () => { const cont = renderPromptTemplate(brainstormTemplate, { issueNumber: '42' }) expect(cont).toContain('/ecc:grilling 讨论下 42 号工单') expect(cont).not.toContain('opencli spx issue create') const fresh = renderPromptTemplate(brainstormTemplate, { userRequest: '加个按钮', nonce: 'n1' }) expect(fresh).toContain('加个按钮') expect(fresh).toContain('') expect(fresh).not.toContain('/ecc:grilling 讨论下') }) it('工单挂了原型插件才出现原型段落', () => { const withProto = renderPromptTemplate(brainstormTemplate, { issueNumber: '42', prototypeFile: 'proto/manifest.json', prototypeSessionId: 'sid-1', }) expect(withProto).toContain('工单已有原型插件 proto/manifest.json') expect(withProto).toContain('原型会话 id:sid-1') const withoutProto = renderPromptTemplate(brainstormTemplate, { issueNumber: '42' }) expect(withoutProto).not.toContain('原型插件') }) it('没有原型会话 id 时不留下半句「原型会话 id:」', () => { const out = renderPromptTemplate(brainstormTemplate, { issueNumber: '42', prototypeFile: 'proto/manifest.json', }) expect(out).toContain('原型会话终端名:issue-42-原型') expect(out).not.toContain('原型会话 id') }) it('渲染结果里不残留未替换的占位符', () => { const out = renderPromptTemplate(brainstormTemplate, { issueNumber: '42', prototypeFile: 'proto/manifest.json', prototypeSessionId: 'sid-1', }) expect(out).not.toMatch(/\{\{/) }) })