feat: 收口组件样式并调整移动端侧栏
This commit is contained in:
@@ -0,0 +1,187 @@
|
||||
import { defineComponent } from 'vue'
|
||||
import { flushPromises, mount, type VueWrapper } from '@vue/test-utils'
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import { projectContextKey } from '@/features/projects/context'
|
||||
import { testProjectContext } from '@/testing/project-context'
|
||||
import { getOperation } from '@/features/workflows/operations'
|
||||
import { formFixture, imageFixture } from '@/features/subject-images/testing/fixtures'
|
||||
import { currentIdentityAnchorId, getImageSession, isPrimaryIdentityStale } from '@/features/subject-images/model'
|
||||
import { useSubjectImages } from '@/features/subject-images/useSubjectImages'
|
||||
import FormPromptDialog from '@/features/subject-images/components/FormPromptDialog.vue'
|
||||
import GenerateImageDialog from '@/features/subject-images/components/GenerateImageDialog.vue'
|
||||
|
||||
let wrapper: VueWrapper | undefined
|
||||
const projectId = 'capability-test'
|
||||
/** 从实际挂载的确认弹窗查找操作按钮。 */
|
||||
function find(label: string) {
|
||||
return [...document.querySelectorAll('button')].find(item => item.textContent?.trim() === label)!
|
||||
}
|
||||
afterEach(() => {
|
||||
wrapper?.unmount()
|
||||
wrapper = undefined
|
||||
document.body.innerHTML = ''
|
||||
vi.unstubAllGlobals()
|
||||
Object.assign(getOperation(projectId), { pending: false, error: '', notice: '', label: '' })
|
||||
Object.assign(getImageSession(projectId), { receipt: null, promptReceipt: null })
|
||||
})
|
||||
|
||||
async function setup() {
|
||||
let service!: ReturnType<typeof useSubjectImages>
|
||||
const context = testProjectContext()
|
||||
const form = formFixture(projectId)
|
||||
const fetcher = vi.fn<typeof fetch>().mockImplementation(async (url, init) => {
|
||||
const path = String(url)
|
||||
let data: unknown = [form]
|
||||
if (init?.method === 'POST') {
|
||||
if (path.endsWith('/generation-prompts'))
|
||||
data = {
|
||||
total: 2,
|
||||
targetCount: 2,
|
||||
generated: 1,
|
||||
skipped: 0,
|
||||
failed: 1,
|
||||
failures: [{ subjectFormId: 'form-failed', error: '模型拒绝' }]
|
||||
}
|
||||
else if (path.endsWith('/generation-prompt')) {
|
||||
form.generationPrompt = '正式提示词'
|
||||
data = { id: form.id, subjectId: form.subjectId, generationPrompt: form.generationPrompt }
|
||||
} else if (path.includes('/subject-forms/')) data = imageFixture()
|
||||
else
|
||||
data = {
|
||||
total: 3,
|
||||
targetCount: 1,
|
||||
generated: 1,
|
||||
skipped: 0,
|
||||
failed: 0,
|
||||
failures: [],
|
||||
eligibleCount: 3,
|
||||
remaining: 2
|
||||
}
|
||||
}
|
||||
return new Response(JSON.stringify({ data }))
|
||||
})
|
||||
vi.stubGlobal('fetch', fetcher)
|
||||
wrapper = mount(
|
||||
defineComponent({
|
||||
setup() {
|
||||
service = useSubjectImages()
|
||||
return () => null
|
||||
}
|
||||
}),
|
||||
{ global: { provide: { [projectContextKey as symbol]: context } } }
|
||||
)
|
||||
await flushPromises()
|
||||
return {
|
||||
service,
|
||||
context,
|
||||
form,
|
||||
fetcher,
|
||||
posts: () => fetcher.mock.calls.filter(([, init]) => init?.method === 'POST')
|
||||
}
|
||||
}
|
||||
|
||||
describe('形态正式提示词与批量配置', () => {
|
||||
it('单个使用正式形态 ID 和 force,生成提示词不会调用图片接口', async () => {
|
||||
const { service, posts } = await setup()
|
||||
await service.generatePrompt('unknown-form', false)
|
||||
expect(posts()).toHaveLength(0)
|
||||
await service.generatePrompt('form-db-1', false)
|
||||
expect(posts()).toHaveLength(1)
|
||||
expect(posts()[0]?.[0]).toBe('/api/subject-forms/form-db-1/generation-prompt')
|
||||
expect(JSON.parse(String(posts()[0]?.[1]?.body))).toEqual({ force: false })
|
||||
expect(service.forms.value[0]?.generationPrompt).toBe('正式提示词')
|
||||
await service.generatePrompt('form-db-1', true)
|
||||
expect(JSON.parse(String(posts()[1]?.[1]?.body))).toEqual({ force: true })
|
||||
})
|
||||
it('提示词接口返回空正文时显示失败,不误报保存成功', async () => {
|
||||
const { service, fetcher } = await setup()
|
||||
fetcher.mockResolvedValueOnce(
|
||||
new Response(JSON.stringify({ data: { id: 'form-db-1', subjectId: 'subject-db-1', generationPrompt: '' } }))
|
||||
)
|
||||
await service.generatePrompt('form-db-1', false)
|
||||
expect(getOperation(projectId).error).toContain('未确认正式提示词已保存')
|
||||
})
|
||||
it('批量提示词保留部分失败回执,不覆盖图片回执且不传图片上限', async () => {
|
||||
const { service, posts } = await setup()
|
||||
service.limit.value = 1
|
||||
service.promptConcurrency.value = 4
|
||||
service.promptForce.value = true
|
||||
await service.generatePrompts()
|
||||
expect(posts()[0]?.[0]).toBe('/api/projects/capability-test/subject-forms/generation-prompts')
|
||||
expect(JSON.parse(String(posts()[0]?.[1]?.body))).toEqual({ concurrency: 4, force: true })
|
||||
expect(service.session.value.promptReceipt?.result.failures[0]?.error).toBe('模型拒绝')
|
||||
expect(service.session.value.receipt).toBeNull()
|
||||
})
|
||||
it('图片数量上限可选,非法上限和并发阻止提交', async () => {
|
||||
const { service, posts } = await setup()
|
||||
for (const limit of [0, -1, 1.5]) {
|
||||
service.limit.value = limit
|
||||
await service.generateProject()
|
||||
}
|
||||
expect(posts()).toHaveLength(0)
|
||||
service.limit.value = 1
|
||||
await service.generateProject()
|
||||
expect(JSON.parse(String(posts()[0]?.[1]?.body))).toEqual({
|
||||
concurrency: 2,
|
||||
force: false,
|
||||
limit: 1
|
||||
})
|
||||
expect(service.session.value.receipt?.result.remaining).toBe(2)
|
||||
service.limit.value = ''
|
||||
await service.generateProject()
|
||||
expect(JSON.parse(String(posts()[1]?.[1]?.body))).not.toHaveProperty('limit')
|
||||
service.promptConcurrency.value = 0
|
||||
await service.generatePrompts()
|
||||
expect(posts()).toHaveLength(2)
|
||||
})
|
||||
it.each(['draft', 'generating', 'need_review', 'failed'] as const)('%s 剧本不能生成提示词和图片', async status => {
|
||||
const { service, context, posts } = await setup()
|
||||
context.data.value!.project.status = status
|
||||
await service.generatePrompt('form-db-1', true)
|
||||
await service.generatePrompts()
|
||||
await service.generateProject()
|
||||
expect(posts()).toHaveLength(0)
|
||||
})
|
||||
it.each(['character', 'scene', 'prop'])('%s 已锁定母版才参与过期判断,刷新只新增候选', async module => {
|
||||
const { service, form, posts } = await setup()
|
||||
form.subject.module = module
|
||||
form.subject.identity = { id: 'identity', isLocked: false, images: [{ id: 'anchor-new' }] }
|
||||
expect(currentIdentityAnchorId(form)).toBeUndefined()
|
||||
expect(isPrimaryIdentityStale(form)).toBe(false)
|
||||
form.subject.identity.isLocked = true
|
||||
expect(isPrimaryIdentityStale(form)).toBe(true)
|
||||
await service.query.refresh()
|
||||
await service.generateStale()
|
||||
expect(JSON.parse(String(posts()[0]?.[1]?.body))).toEqual({ setPrimary: false })
|
||||
})
|
||||
it('正式提示词确认必须勾选,未确认不发送事件', async () => {
|
||||
const form = formFixture()
|
||||
wrapper = mount(FormPromptDialog, { attachTo: document.body, props: { open: true, disabled: false, form } })
|
||||
await flushPromises()
|
||||
find('生成正式提示词').click()
|
||||
await flushPromises()
|
||||
expect(find('确认生成正式提示词').disabled).toBe(true)
|
||||
expect(wrapper.emitted('generate')).toBeUndefined()
|
||||
document.querySelector<HTMLElement>('.app-dialog [role="checkbox"]')!.click()
|
||||
await flushPromises()
|
||||
find('确认生成正式提示词').click()
|
||||
await flushPromises()
|
||||
expect(wrapper.emitted('generate')).toEqual([['form-db-1', false]])
|
||||
})
|
||||
it('缺少正式和原始提示词仍可确认生图,由后端补齐而非前端编造', async () => {
|
||||
const form = formFixture()
|
||||
form.appearancePrompt = null
|
||||
form.generationPrompt = null
|
||||
wrapper = mount(GenerateImageDialog, { attachTo: document.body, props: { open: true, disabled: false, form } })
|
||||
await flushPromises()
|
||||
document.querySelector<HTMLElement>('#confirm-image-cost')!.click()
|
||||
await flushPromises()
|
||||
const submit = [...document.querySelectorAll('button')].find(
|
||||
item => item.textContent?.trim() === '确认生成图片'
|
||||
)!
|
||||
expect(submit.disabled).toBe(false)
|
||||
submit.click()
|
||||
await flushPromises()
|
||||
expect(wrapper.emitted('generate')).toEqual([['form-db-1', { setPrimary: false }]])
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user