feat(i18n): compose pairing records during merges
This commit is contained in:
1 parent
05cbc185f0
commit
542c029c80
13 files changed
+1049
-77
No files matched your search
@@ -1,11 +1,16 @@
|
||||
/** Regression tests for bilingual snapshots, corpus scope, and structure. */
|
||||
|
||||
import { execFileSync, spawnSync } from 'node:child_process'
|
||||
import { mkdtempSync, rmSync } from 'node:fs'
|
||||
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { gitBlobHash, storeGitBlob } from './translation-pairing-git.ts'
|
||||
import { gitBlobHash, readGitIndexBlob, storeGitBlob } from './translation-pairing-git.ts'
|
||||
import {
|
||||
parseTranslationPairingRecord,
|
||||
renderTranslationPairingRecord,
|
||||
translationPairPaths,
|
||||
} from './translation-pairing-record.ts'
|
||||
import {
|
||||
isTranslationScopeFile,
|
||||
pairAnchorOfArgument,
|
||||
@@ -74,6 +79,28 @@ describe('translation pairing snapshots', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('reads staged bytes independently of the working tree', () => {
|
||||
const root = mkdtempSync(join(tmpdir(), 'dsh-translation-pairing-index-'))
|
||||
try {
|
||||
execFileSync('git', ['init', '--quiet', root], {
|
||||
env: { ...process.env, GIT_DEFAULT_HASH: 'sha1' },
|
||||
})
|
||||
execFileSync('git', ['-C', root, 'config', 'user.email', 'pairing@example.test'])
|
||||
execFileSync('git', ['-C', root, 'config', 'user.name', 'Pairing Test'])
|
||||
writeFileSync(join(root, 'owner.md'), 'staged')
|
||||
execFileSync('git', ['-C', root, 'add', 'owner.md'])
|
||||
writeFileSync(join(root, 'owner.md'), 'unstaged')
|
||||
|
||||
const indexed = readGitIndexBlob(root, 'owner.md')
|
||||
|
||||
expect(indexed?.content.toString('utf8')).toBe('staged')
|
||||
expect(indexed?.objectId).toBe(gitBlobHash(Buffer.from('staged')))
|
||||
expect(readGitIndexBlob(root, 'absent.md')).toBeUndefined()
|
||||
} finally {
|
||||
rmSync(root, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
it.skipIf(!supportsSha256ObjectFormat)('rejects an object format that pairing records cannot represent', () => {
|
||||
const root = mkdtempSync(join(tmpdir(), 'dsh-translation-pairing-'))
|
||||
try {
|
||||
@@ -113,6 +140,32 @@ describe('translation pairing manifest', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('translation pairing records', () => {
|
||||
const paths = translationPairPaths('docs/foo.md')
|
||||
const record = {
|
||||
sourceHash: '1'.repeat(40),
|
||||
zhHash: '2'.repeat(40),
|
||||
}
|
||||
|
||||
it('round-trips the canonical two-hash record', () => {
|
||||
expect(parseTranslationPairingRecord(renderTranslationPairingRecord(paths, record), paths)).toEqual(record)
|
||||
})
|
||||
|
||||
it('rejects duplicate or unexpected keys', () => {
|
||||
expect(parseTranslationPairingRecord([
|
||||
`foo.md: ${'1'.repeat(40)}`,
|
||||
`foo.md: ${'3'.repeat(40)}`,
|
||||
`foo.zh.md: ${'2'.repeat(40)}`,
|
||||
'',
|
||||
].join('\n'), paths)).toBeUndefined()
|
||||
expect(parseTranslationPairingRecord([
|
||||
`foo.md: ${'1'.repeat(40)}`,
|
||||
`bar.zh.md: ${'2'.repeat(40)}`,
|
||||
'',
|
||||
].join('\n'), paths)).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe('translation scope discovery', () => {
|
||||
it.each([
|
||||
'README.md',
|
||||
@@ -186,28 +239,56 @@ describe('pair CLI arguments', () => {
|
||||
|
||||
it('scopes a check to named pairs and dedupes the three spellings', () => {
|
||||
expect(parseTranslationPairingCliArgs(['docs/foo.zh.md', 'docs/foo.i18n.yaml', 'docs/bar.md'])).toEqual({
|
||||
input: 'worktree',
|
||||
mode: 'check',
|
||||
scope: 'pairs',
|
||||
anchors: ['docs/bar.md', 'docs/foo.md'],
|
||||
})
|
||||
expect(parseTranslationPairingCliArgs([])).toEqual({ mode: 'check', scope: 'corpus', anchors: [] })
|
||||
expect(parseTranslationPairingCliArgs([])).toEqual({
|
||||
input: 'worktree',
|
||||
mode: 'check',
|
||||
scope: 'corpus',
|
||||
anchors: [],
|
||||
})
|
||||
})
|
||||
|
||||
it('requires --write to name confirmed pairs or opt into --all', () => {
|
||||
expect(() => parseTranslationPairingCliArgs(['--write'])).toThrow('requires the pair(s) you confirmed')
|
||||
expect(parseTranslationPairingCliArgs(['--write', 'docs/foo.md'])).toEqual({
|
||||
input: 'worktree',
|
||||
mode: 'write',
|
||||
scope: 'pairs',
|
||||
anchors: ['docs/foo.md'],
|
||||
})
|
||||
expect(parseTranslationPairingCliArgs(['--write', '--all'])).toEqual({ mode: 'write', scope: 'corpus', anchors: [] })
|
||||
expect(parseTranslationPairingCliArgs(['--write', '--all'])).toEqual({
|
||||
input: 'worktree',
|
||||
mode: 'write',
|
||||
scope: 'corpus',
|
||||
anchors: [],
|
||||
})
|
||||
expect(() => parseTranslationPairingCliArgs(['--write', '--all', 'docs/foo.md'])).toThrow('not both')
|
||||
})
|
||||
|
||||
it('keeps --list corpus-only and rejects unknown flags', () => {
|
||||
expect(parseTranslationPairingCliArgs(['--list'])).toEqual({ mode: 'list', scope: 'corpus', anchors: [] })
|
||||
expect(parseTranslationPairingCliArgs(['--list'])).toEqual({
|
||||
input: 'worktree',
|
||||
mode: 'list',
|
||||
scope: 'corpus',
|
||||
anchors: [],
|
||||
})
|
||||
expect(() => parseTranslationPairingCliArgs(['--list', 'docs/foo.md'])).toThrow('takes no other flags or paths')
|
||||
expect(() => parseTranslationPairingCliArgs(['--all'])).toThrow('--all only applies to --write')
|
||||
expect(() => parseTranslationPairingCliArgs(['--frobnicate'])).toThrow('unknown flag(s): --frobnicate')
|
||||
})
|
||||
|
||||
it('makes cached verification a named, read-only index check', () => {
|
||||
expect(parseTranslationPairingCliArgs(['--cached', 'docs/foo.i18n.yaml'])).toEqual({
|
||||
input: 'index',
|
||||
mode: 'check',
|
||||
scope: 'pairs',
|
||||
anchors: ['docs/foo.md'],
|
||||
})
|
||||
expect(() => parseTranslationPairingCliArgs(['--cached'])).toThrow('requires the staged pair paths')
|
||||
expect(() => parseTranslationPairingCliArgs(['--cached', '--write', 'docs/foo.md'])).toThrow('read-only')
|
||||
})
|
||||
})
|
||||
Reference in new issue
Block a user