feat: 同步精简接口并统一表单校验
This commit is contained in:
@@ -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'
|
||||
|
||||
Reference in New Issue
Block a user