feat: 同步精简接口并统一表单校验

This commit is contained in:
GJ
2026-09-10 22:34:47 +08:00
parent 82d7bde631
commit 2eea0ea72e
66 changed files with 1611 additions and 1100 deletions
+34 -17
View File
@@ -65,9 +65,7 @@ const fixture: ProjectDetail = {
updatedAt: '2026-08-27T00:00:00Z',
episodes: [{ episode: 1, title: '来信', content: '<script>不要执行模型内容</script>\n第一场:旧书店。' }],
characters: [{ id: 'character', name: '林知夏' }],
world: { era: '当代' },
reviews: [],
tasks: []
world: { era: '当代' }
}
let wrapper: VueWrapper | undefined
@@ -219,7 +217,14 @@ describe('内容级滚动下的生产反馈', () => {
getProductionSession(fixture.id).receipt = {
kind: 'retry',
title: '重试失败视频任务',
result: { totalFailed: 1, retried: 0, failed: 1, failures: [{ shotId: 'shot-1', error: '供应商不可用' }] }
result: {
totalFailed: 1,
retryable: 1,
skipped: 0,
retried: 0,
failed: 1,
failures: [{ shotId: 'shot-1', error: '供应商不可用' }]
}
}
await flushPromises()
expect(wrapper.get('[data-workspace-tools]').attributes('aria-expanded')).toBe('true')
@@ -392,7 +397,10 @@ describe('视觉风格与主体身份工作区', () => {
vi.stubGlobal('fetch', fetcher)
await mountAssets('style')
await wrapper!.get('[aria-label="风格图片地址"]').setValue('javascript:alert(1)')
expect(button('登记参考图').disabled).toBe(true)
button('登记参考图').click()
await flushPromises()
expect(document.querySelector('.n-form-item-feedback--error')?.textContent).toContain('请填写有效的')
expect(fetcher.mock.calls.some(([, init]) => init?.method === 'POST')).toBe(false)
await wrapper!.get('[aria-label="风格图片地址"]').setValue('/storage/style.png')
button('登记参考图').click()
await flushPromises()
@@ -437,7 +445,10 @@ describe('视觉风格与主体身份工作区', () => {
expect(wrapper!.text()).toContain('默认采用中国人物选角基线')
expect(wrapper!.text()).toContain('人物风格与选角背景')
await wrapper!.get('#style-constraints').setValue('{"不应静默丢弃":true}')
expect(button('保存视觉风格').disabled).toBe(true)
button('保存视觉风格').click()
await flushPromises()
expect(document.querySelector('.n-form-item-feedback--error')?.textContent).toContain('JSON 字符串数组')
expect(fetcher.mock.calls.some(([, init]) => init?.method === 'PUT')).toBe(false)
await wrapper!.get('#style-constraints').setValue('["真人写实"]')
await wrapper!.get('#style-lock').trigger('click')
await wrapper!.get('#style-name').setValue('雨夜风格')
@@ -611,7 +622,7 @@ describe('视觉风格与主体身份工作区', () => {
return jsonResponse(identityImageFixture({ id: 'candidate' }))
}
if (String(url).endsWith('/identity/images'))
return jsonResponse([identityImageFixture({ id: 'candidate', enabled: selected, isAnchor: selected })])
return jsonResponse([identityImageFixture({ id: 'candidate', isAnchor: selected })])
return jsonResponse(identityFixture())
})
vi.stubGlobal('fetch', fetcher)
@@ -697,7 +708,7 @@ describe('视觉风格与主体身份工作区', () => {
it('Character 确认选角原子切换 Anchor 并锁定 Identity,不调用普通母版接口', async () => {
let locked = false
let selected = false
const candidate = identityImageFixture({ id: 'casting-1', enabled: false, isAnchor: false })
const candidate = identityImageFixture({ id: 'casting-1', isAnchor: false })
const fetcher = vi.fn<typeof fetch>().mockImplementation(async (url, init) => {
const path = String(url)
if (path.endsWith('/subject-forms')) return jsonResponse([formFixture()])
@@ -725,14 +736,15 @@ describe('视觉风格与主体身份工作区', () => {
}
]
})
if (path.endsWith('/identity/images'))
return jsonResponse([{ ...candidate, enabled: selected, isAnchor: selected }])
if (path.endsWith('/identity/images')) return jsonResponse([{ ...candidate, isAnchor: selected }])
if (init?.method === 'PUT' && path.endsWith('/casting')) {
locked = true
selected = true
return jsonResponse({
identity: { ...identityFixture({ isLocked: true }), images: undefined },
anchor: { ...candidate, enabled: true }
identityId: candidate.identityId,
subjectId: 'subject-db-1',
isLocked: true,
anchor: { ...candidate, isAnchor: true }
})
}
return jsonResponse(identityFixture({ isLocked: locked }))
@@ -834,9 +846,11 @@ describe('工作台页面交互', () => {
selectControl(wrapper!, 'aria-label', '筛选主体类型').vm.$emit('update:value', 'all')
await filters.get('input[aria-label="搜索形态图片"]').setValue('旧书店')
expect(filters.get('.filter-result-count').text()).toBe('1 个形态')
selectControl(wrapper!, 'aria-label', '筛选图片状态').vm.$emit('update:value', 'stale')
await flushPromises()
expect(filters.get('.filter-result-count').text()).toBe('0 个形态')
expect(selectControl(wrapper!, 'aria-label', '筛选图片状态').props('options')).not.toContainEqual({
label: '身份过期',
value: 'stale'
})
expect(filters.get('.filter-result-count').text()).toBe('1 个形态')
expect(fetcher.mock.calls.every(([, init]) => init?.method === 'GET')).toBe(true)
})
@@ -924,7 +938,9 @@ describe('工作台页面交互', () => {
await flushPromises()
document.querySelector<HTMLInputElement>('#confirm-image-cost')!.click()
await flushPromises()
document.querySelector('form')!.dispatchEvent(new Event('submit', { bubbles: true, cancelable: true }))
document
.querySelector('.app-dialog form')!
.dispatchEvent(new Event('submit', { bubbles: true, cancelable: true }))
await flushPromises()
const post = fetcher.mock.calls.find(([, init]) => init?.method === 'POST')!
expect(post[0]).toBe('/api/subject-forms/form-db-1/images')
@@ -1382,7 +1398,8 @@ describe('工作台页面交互', () => {
new Response(
JSON.stringify({
data: {
id: 'shot-db-1-1',
shotId: 'shot-db-1-1',
updatedAt: '2026-09-10T00:00:00Z',
videoPrompt: '<script>模型内容</script>',
negativePrompt: '模糊',
status: 'prompt_ready'