Files
deepseek-harness/scripts/translation-prompt.spec.ts
T

201 lines
8.4 KiB
TypeScript
Raw Normal View History

/** Unit tests for the prompt-v4 renderer and three-section response parser. */
import { readFileSync } from 'node:fs'
import { join, resolve } from 'node:path'
import { describe, expect, it } from 'vitest'
import {
2026-07-23 22:41:53 +08:00
consumeTranslationResponse,
parseTranslationResponse,
renderTranslationPrompt,
2026-07-23 22:41:53 +08:00
renderTranslationRequest,
renderTranslationResponse,
} from './translation-prompt.ts'
const root = resolve(import.meta.dirname, '..')
const document = readFileSync(join(root, 'docs/i18n/translation-prompt.md'), 'utf8')
const terminology = '| English | 中文 |\n|---|---|\n| agent | agent |'
describe('translation prompt rendering', () => {
it('renders both directions with every placeholder resolved', () => {
2026-07-23 22:41:53 +08:00
const en = renderTranslationPrompt(document, { sourceLanguage: 'English', sourceFilename: 'guide.md', terminology })
expect(en).toContain('from English to Chinese')
expect(en).toContain(terminology)
expect(en).not.toContain('{{')
2026-07-23 21:44:41 +08:00
expect(en).toContain('plain source stays plain (必须)')
expect(en).toContain('When the target language is English, use the "English" column without a Chinese gloss')
2026-07-23 21:55:07 +08:00
expect(en).toContain('for a Chinese target, use an established Chinese rendering')
expect(en).toContain('for an English target, use the established English technical term')
expect(en).toContain('does an English target use established English terminology')
2026-07-23 21:44:41 +08:00
expect(en).toContain('The parser removes exactly one framing escape')
2026-07-23 22:41:53 +08:00
const zh = renderTranslationPrompt(document, { sourceLanguage: 'Chinese', sourceFilename: 'guide.zh.md', terminology })
expect(zh).toContain('from Chinese to English')
})
it('rejects a template with unknown or missing placeholders', () => {
const alien = document.replaceAll('{{terminology}}', '{{terms_prompt}}')
2026-07-23 22:41:53 +08:00
expect(() => renderTranslationPrompt(alien, { sourceLanguage: 'English', sourceFilename: 'guide.md', terminology })).toThrow(/unsupported placeholder/)
const missing = document.replaceAll('{{terminology}}', '')
2026-07-23 22:41:53 +08:00
expect(() => renderTranslationPrompt(missing, { sourceLanguage: 'English', sourceFilename: 'guide.md', terminology })).toThrow(/required placeholder/)
})
2026-07-23 23:12:56 +08:00
it('rejects unmatched placeholder delimiters', () => {
for (const delimiter of ['{{', '}}']) {
const malformed = document.replace('Your task is to translate', `Your task ${delimiter} is to translate`)
expect(() => renderTranslationPrompt(malformed, {
sourceLanguage: 'English',
sourceFilename: 'guide.md',
terminology,
})).toThrow(/malformed placeholder syntax/)
}
})
2026-07-23 22:41:53 +08:00
it('assembles bare few-shot turns before the real source document', () => {
const request = renderTranslationRequest(document, {
sourceLanguage: 'English',
sourceFilename: 'guide.md',
sourceDocument: '# Guide\n\nNew source.',
terminology,
examples: [{ english: '# Example\n\nEnglish.', chinese: '# 示例\n\n中文。' }],
})
expect(request.targetFilename).toBe('guide.zh.md')
expect(request.messages.map(message => message.role)).toEqual(['system', 'user', 'assistant', 'user'])
expect(request.messages.slice(1).map(message => message.content)).toEqual([
'# Example\n\nEnglish.',
'# 示例\n\n中文。',
'# Guide\n\nNew source.',
])
const reverse = renderTranslationRequest(document, {
sourceLanguage: 'Chinese',
sourceFilename: 'guide.zh.md',
sourceDocument: '# 指南\n\n新源文。',
terminology,
examples: [{ english: '# Example\n\nEnglish.', chinese: '# 示例\n\n中文。' }],
})
expect(reverse.targetFilename).toBe('guide.md')
expect(reverse.messages.slice(1).map(message => message.content)).toEqual([
'# 示例\n\n中文。',
'# Example\n\nEnglish.',
'# 指南\n\n新源文。',
])
})
})
describe('translation response sections', () => {
it('round-trips Markdown bodies', () => {
const response = { translation: '# 标题\n\n正文 **加粗**。', review: '- [Tone] 修正一处。\n- 无修正', final: '# 标题\n\n定稿。' }
expect(parseTranslationResponse(renderTranslationResponse(response))).toEqual(response)
})
it('tolerates a fenced xml wrapper around the whole response', () => {
const fenced = '```xml\n<translation>\nA\n</translation>\n\n<review>\n- 无修正\n</review>\n\n<final>\nA\n</final>\n```'
expect(parseTranslationResponse(fenced).final).toBe('A')
})
it('keeps an inline close tag inside prose from terminating the section', () => {
const doc = { translation: 'the wire format uses </translation> as its close tag', review: '- 无修正', final: 'F' }
expect(parseTranslationResponse(renderTranslationResponse(doc))).toEqual(doc)
})
2026-07-23 21:44:41 +08:00
it('round-trips wrapper-tag lines inside Markdown bodies', () => {
const doc = {
translation: '```xml\n</translation>\n```',
review: '- [Structure] Preserved `<final>` on its own line.',
final: 'literal delimiters\n</final>\n\\</final>',
}
const rendered = renderTranslationResponse(doc)
expect(parseTranslationResponse(rendered)).toEqual(doc)
expect(() => parseTranslationResponse(rendered.replace('\\</translation>', '</translation>'))).toThrow(/duplicate <translation>/)
})
it('rejects a duplicate section appearing before final', () => {
const early = '<translation>\nA\n</translation>\n<translation>\nB\n</translation>\n<review>\nR\n</review>\n<final>\nF\n</final>'
expect(() => parseTranslationResponse(early)).toThrow(/duplicate <translation>/)
})
it('rejects missing, unterminated, or duplicated sections', () => {
expect(() => parseTranslationResponse('<translation>\nA\n</translation>')).toThrow(/missing or unterminated <review>/)
expect(() => parseTranslationResponse('<translation>\nA')).toThrow(/missing or unterminated <translation>/)
const dup = '<translation>\nA\n</translation>\n<review>\nR\n</review>\n<final>\nF\n</final>\n<final>\nG\n</final>'
expect(() => parseTranslationResponse(dup)).toThrow(/duplicate <final>/)
2026-07-23 21:44:41 +08:00
expect(() => parseTranslationResponse(`${renderTranslationResponse({ translation: 'A', review: 'R', final: 'F' })}\nstray`))
.toThrow(/content is not allowed outside/)
})
2026-07-23 22:41:53 +08:00
it('inserts or corrects the target switcher after parsing a new-pair response', () => {
const response = renderTranslationResponse({
translation: '# 指南\n\n初稿。',
review: '- 无修正',
final: '# 指南\n\nEnglish | [中文](guide.zh.md)\n\n定稿。',
})
expect(consumeTranslationResponse(response, { sourceLanguage: 'English', sourceFilename: 'guide.md' }).final).toBe([
'# 指南',
'',
'[English](guide.md) | 中文',
'',
'定稿。',
'',
].join('\n'))
})
2026-07-23 23:12:56 +08:00
it('preserves YAML frontmatter before inserting the target switcher', () => {
const response = renderTranslationResponse({
translation: '# 指南\n\n初稿。',
review: '- 无修正',
final: [
'---',
'layout: home',
'---',
'',
'# 指南',
'',
'定稿。',
].join('\n'),
})
expect(consumeTranslationResponse(response, { sourceLanguage: 'English', sourceFilename: 'guide.md' }).final).toBe([
'---',
'layout: home',
'---',
'',
'# 指南',
'',
'[English](guide.md) | 中文',
'',
'定稿。',
'',
].join('\n'))
})
it('rejects unterminated YAML frontmatter before the target H1', () => {
const response = renderTranslationResponse({
translation: '# 指南\n\n初稿。',
review: '- 无修正',
final: '---\nlayout: home\n\n# 指南\n\n定稿。',
})
expect(() => consumeTranslationResponse(response, {
sourceLanguage: 'English',
sourceFilename: 'guide.md',
})).toThrow(/unterminated YAML frontmatter/)
})
2026-07-23 22:41:53 +08:00
it('rejects a source filename that contradicts the translation direction', () => {
expect(() => renderTranslationPrompt(document, {
sourceLanguage: 'Chinese',
sourceFilename: 'guide.md',
terminology,
})).toThrow(/does not match source language Chinese/)
})
it('inserts the English target switcher for a Chinese source', () => {
const response = renderTranslationResponse({
translation: '# Guide\n\nDraft.',
review: '- [None] No corrections.',
final: '# Guide\n\nFinal.',
})
expect(consumeTranslationResponse(response, {
sourceLanguage: 'Chinese',
sourceFilename: 'guide.zh.md',
}).final).toContain('\n\nEnglish | [中文](guide.zh.md)\n\n')
})
})