fix: anima-extract 改用 jsc-anima 專屬快取 clone,不論在哪個專案都對 jsc-anima 開 PR
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
+70
-14
@@ -17,25 +17,29 @@ const reply = (c: Extracted['characters'][number]) =>
|
||||
|
||||
type Setup = { files: Map<string, string>; cmds: string[][]; prompts: string[] }
|
||||
|
||||
let cloneMissing = false
|
||||
let registered: string[] = []
|
||||
const probed = new Set<string>()
|
||||
let sessionCwd = '/repo'
|
||||
const setup = (on: never, answers: string[], files = new Map<string, string>()): Setup => {
|
||||
const s: Setup = { files, cmds: [], prompts: [] }
|
||||
const o = on as unknown as (n: string, h: (...a: never[]) => unknown) => void
|
||||
mock.store(on)
|
||||
o('command.register', () => ({ value: { name: 'x' } }))
|
||||
o('command.register', ((_$: unknown, e: { name: string }) => { registered.push(e.name); return { value: { name: e.name } } }) as never)
|
||||
o('session.start', () => ({ cwd: '/repo' }))
|
||||
o('session.cwd', (() => ({ value: '/repo' })) as never)
|
||||
o('fs.exists', ((_$: unknown, e: { path: string }) => ({ value: s.files.has(e.path) })) as never)
|
||||
o('session.cwd', (() => ({ value: sessionCwd })) as never)
|
||||
mock.env(on, { HOME: '/home/u' })
|
||||
o('fs.exists', ((_$: unknown, e: { path: string }) => (e.path.endsWith('/characters/index.json') && probed.add(e.path), { value: s.files.has(e.path) || (e.path === '/home/u/.cache/jsc-anima/repo/.git' && !cloneMissing) })) as never)
|
||||
o('fs.read', ((_$: unknown, e: { path: string }) => ({ value: s.files.get(e.path) ?? '' })) as never)
|
||||
o('fs.write', ((_$: unknown, e: { path: string; text: string }) => { s.files.set(e.path, e.text); return { value: undefined } }) as never)
|
||||
o('model.complete', ((_$: unknown, e: { prompt: string }) => {
|
||||
s.prompts.push(e.prompt)
|
||||
return { value: { isAnswered: true, text: answers.shift() ?? '', usage: {} } }
|
||||
}) as never)
|
||||
o('env.get', (() => ({ value: undefined })) as never)
|
||||
o('process.run', ((_$: unknown, e: { argv: string[] }) => {
|
||||
o('process.run', ((_$: unknown, e: { argv: string[]; }) => {
|
||||
s.cmds.push(e.argv)
|
||||
const j = e.argv.join(' ')
|
||||
const out = j.includes('--abbrev-ref') ? 'issue-4' : j.includes('symbolic-ref') ? 'origin/main' : j.includes('get-url') ? 'https://gitea.example/o/r.git' : j.startsWith('tea logins') ? 'admin https://gitea.example u' : j.startsWith('tea pulls') ? 'PR#1' : ''
|
||||
const out = j.includes('--abbrev-ref') ? 'issue-4' : j.includes('symbolic-ref') ? 'origin/main' : j.includes('get-url') ? 'https://gitea.jsc.idv.tw/jiantw83/jsc-anima.git' : j.startsWith('tea logins') ? 'admin https://gitea.jsc.idv.tw u' : j.startsWith('tea pulls') ? 'PR#1' : ''
|
||||
return { value: { exitCode: 0, stdout: out, stderr: '' } }
|
||||
}) as never)
|
||||
return s
|
||||
@@ -50,18 +54,18 @@ test('萃取:先確認代號、再看變更摘要、最後寫檔並建立 PR',
|
||||
expect(t1).toContain('ts')
|
||||
expect(t1).toContain('aoi')
|
||||
expect(t1).toContain('尚未建立')
|
||||
expect(s.files.has('/repo/characters/ts/aoi.json')).toBe(false)
|
||||
expect(s.files.has('/home/u/.cache/jsc-anima/repo/characters/ts/aoi.json')).toBe(false)
|
||||
|
||||
const t2 = await run($ as never, 'confirm work=testy aoi=ao')
|
||||
expect(t2).toContain('變更摘要')
|
||||
expect(s.files.has('/repo/characters/testy/ao.json')).toBe(false)
|
||||
expect(s.files.has('/home/u/.cache/jsc-anima/repo/characters/testy/ao.json')).toBe(false)
|
||||
|
||||
const t3 = await run($ as never, 'confirm')
|
||||
expect(t3).toContain('已建立 PR')
|
||||
const pack = JSON.parse(s.files.get('/repo/characters/testy/ao.json')!)
|
||||
const pack = JSON.parse(s.files.get('/home/u/.cache/jsc-anima/repo/characters/testy/ao.json')!)
|
||||
expect(pack.timeline[0].chapter).toBe('ch1')
|
||||
expect(pack.timeline[0].age).toBe(16)
|
||||
expect(JSON.parse(s.files.get('/repo/characters/testy/work.json')!).terms[0].term).toBe('魔核')
|
||||
expect(JSON.parse(s.files.get('/home/u/.cache/jsc-anima/repo/characters/testy/work.json')!).terms[0].term).toBe('魔核')
|
||||
const pr = s.cmds.find(c => c[0] === 'tea' && c[1] === 'pulls')!
|
||||
expect(pr).toContain('main')
|
||||
expect(pr).toContain('feat/extract-testy-ch1')
|
||||
@@ -79,14 +83,14 @@ test('同章節重跑覆蓋、新章節追加、基礎設定不同標記待確
|
||||
}
|
||||
await write('ch1.txt')
|
||||
await write('ch1.txt')
|
||||
expect(JSON.parse(s.files.get('/repo/characters/ts/aoi.json')!).timeline.length).toBe(1)
|
||||
expect(JSON.parse(s.files.get('/home/u/.cache/jsc-anima/repo/characters/ts/aoi.json')!).timeline.length).toBe(1)
|
||||
|
||||
await run($ as never, 'ch2.txt')
|
||||
const summary = await run($ as never, 'confirm')
|
||||
expect(summary).toContain('追加')
|
||||
expect(summary).toContain('待確認')
|
||||
await run($ as never, 'confirm')
|
||||
const pack = JSON.parse(s.files.get('/repo/characters/ts/aoi.json')!)
|
||||
const pack = JSON.parse(s.files.get('/home/u/.cache/jsc-anima/repo/characters/ts/aoi.json')!)
|
||||
expect(pack.timeline.map((t: { chapter: string }) => t.chapter)).toEqual(['ch1', 'ch2'])
|
||||
expect(pack.persona.tone).toBe('溫柔')
|
||||
expect(pack.conflicts[0]).toMatchObject({ field: 'persona.tone', proposed: '傲嬌', status: '待確認' })
|
||||
@@ -112,10 +116,10 @@ test('貼上章節內容:paste <章節名稱> 加下一行起的內容,並
|
||||
expect(s.prompts[0]).toContain('這是貼上的章節內容。')
|
||||
await run($ as never, 'confirm')
|
||||
await run($ as never, 'confirm')
|
||||
const pack = JSON.parse(s.files.get('/repo/characters/ts/aoi.json')!)
|
||||
const pack = JSON.parse(s.files.get('/home/u/.cache/jsc-anima/repo/characters/ts/aoi.json')!)
|
||||
expect(pack.timeline[0].chapter).toBe('第一章')
|
||||
expect(pack.language).toBe('繁體中文,少量原作口癖')
|
||||
expect(JSON.parse(s.files.get('/repo/characters/index.json')!).packs[0]).toEqual({ work: 'ts', workName: '測試物語', slug: 'aoi' })
|
||||
expect(JSON.parse(s.files.get('/home/u/.cache/jsc-anima/repo/characters/index.json')!).packs[0]).toEqual({ work: 'ts', workName: '測試物語', slug: 'aoi' })
|
||||
})
|
||||
|
||||
test('欄位含長段引號內容視為抄錄原作台詞,不寫檔', async ($, on) => {
|
||||
@@ -126,3 +130,55 @@ test('欄位含長段引號內容視為抄錄原作台詞,不寫檔', async ($
|
||||
expect(await run($ as never, 'confirm')).toContain('疑似抄錄原作台詞')
|
||||
expect([...s.files.keys()].some(k => k.includes('characters'))).toBe(false)
|
||||
})
|
||||
|
||||
test('在非 jsc-anima 目錄執行:git 與 PR 都走 jsc-anima 的快取 clone,不碰 session 目錄', async ($, on) => {
|
||||
sessionCwd = '/other/proj'
|
||||
try {
|
||||
const s = setup(on as never, [reply(char())], new Map([['/other/proj/ch1.txt', '第一段']]))
|
||||
await $.session.start({ source: 'startup', cwd: '/other/proj' } as never)
|
||||
await run($ as never, '/other/proj/ch1.txt')
|
||||
await run($ as never, 'confirm')
|
||||
expect(await run($ as never, 'confirm')).toContain('已建立 PR')
|
||||
expect(s.cmds.some(c => c.includes('fetch'))).toBe(true)
|
||||
const pr = s.cmds.find(c => c[0] === 'tea' && c[1] === 'pulls')!
|
||||
expect(pr).toContain('jiantw83/jsc-anima')
|
||||
expect(s.files.has('/other/proj/characters/index.json')).toBe(false)
|
||||
expect(JSON.parse(s.files.get('/home/u/.cache/jsc-anima/repo/characters/index.json')!).packs[0].slug).toBe('aoi')
|
||||
} finally {
|
||||
sessionCwd = '/repo'
|
||||
}
|
||||
})
|
||||
|
||||
test('快取 clone 不存在時先 clone jsc-anima', async ($, on) => {
|
||||
cloneMissing = true
|
||||
try {
|
||||
const s = setup(on as never, [reply(char())], new Map([['/repo/ch1.txt', 'a']]))
|
||||
await $.session.start({ source: 'startup', cwd: '/repo' } as never)
|
||||
await run($ as never, 'ch1.txt')
|
||||
const clone = s.cmds.find(c => c.includes('clone'))!
|
||||
expect(clone).toContain('https://gitea.jsc.idv.tw/jiantw83/jsc-anima.git')
|
||||
} finally {
|
||||
cloneMissing = false
|
||||
}
|
||||
})
|
||||
|
||||
test('paste:CRLF 換行也能分出章節名稱與內容', async ($, on) => {
|
||||
const s = setup(on as never, [reply(char())])
|
||||
await $.session.start({ source: 'startup', cwd: '/repo' } as never)
|
||||
await run($ as never, 'paste 第二章\r\n第一行\r\n第二行')
|
||||
expect(s.prompts[0]).toContain('第一行')
|
||||
})
|
||||
|
||||
test('合併並更新外掛後:啟動時從外掛目錄的 index.json 註冊 {作品代號}-{角色代號} 並可啟動', async ($, on) => {
|
||||
const s = setup(on as never, [reply(char())], new Map([['/repo/ch1.txt', 'a']]))
|
||||
registered = []
|
||||
await $.session.start({ source: 'startup', cwd: '/repo' } as never)
|
||||
await run($ as never, 'ch1.txt')
|
||||
await run($ as never, 'confirm')
|
||||
await run($ as never, 'confirm')
|
||||
// 模擬 PR 合併、外掛更新:clone 內的 characters/ 內容出現在外掛目錄。
|
||||
const root = ([...probed].find(q => !q.startsWith('/home/u/')) ?? '').replace('/characters/index.json', '')
|
||||
for (const [k, v] of [...s.files]) if (k.startsWith('/home/u/.cache/jsc-anima/repo/characters/')) s.files.set(k.replace('/home/u/.cache/jsc-anima/repo', root), v)
|
||||
await $.session.start({ source: 'startup', cwd: '/anywhere' } as never)
|
||||
expect(registered).toContain('ts-aoi')
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user