/** * Task.spec.ts —— 生成进度页组件单测(plan 002 U6)。 * 验:进入即轮询;终态完成(2)→自动跳预览(versionId+gameId);终态失败(3)→重生成→跳新任务链。 * 用真 store + mock studioApi(保 currentChain 真响应式,watch 能触发)+ mock router。跑 `npm test`。 */ import { describe, it, expect, vi, beforeEach } from 'vitest' import { mount, flushPromises } from '@vue/test-utils' import { setActivePinia, createPinia } from 'pinia' import { createI18n } from 'vue-i18n' const replace = vi.fn() const push = vi.fn() vi.mock('vue-router', () => ({ useRouter: () => ({ replace, push }) })) vi.mock('../../api/studio', () => ({ getTemplates: vi.fn(), createGame: vi.fn(), modifyGame: vi.fn(), extendGame: vi.fn(), getTaskChain: vi.fn(), regenerate: vi.fn(), })) import * as studioApi from '../../api/studio' import Task from './Task.vue' const api = studioApi as unknown as Record> const i18n = createI18n({ legacy: false, locale: 'zh-CN', messages: { 'zh-CN': {} }, missingWarn: false, fallbackWarn: false }) const AppButtonStub = { props: ['disabled', 'loading', 'block', 'size', 'type'], emits: ['click'], template: '', } function mountTask(taskId = '1001') { setActivePinia(createPinia()) return mount(Task, { props: { taskId }, global: { plugins: [i18n], stubs: { LoadingBar: true, Icon: true, AppButton: AppButtonStub, EmptyState: { template: '
' } }, }, }) } describe('Task.vue(生成进度页·studio 任务链)', () => { beforeEach(() => { vi.clearAllMocks() replace.mockClear() push.mockClear() }) it('进入即对 taskChainId 启动轮询(getTaskChain)', async () => { api.getTaskChain.mockResolvedValue({ id: 1001, gameId: 1, status: 1, progress: 20 }) mountTask('1001') await flushPromises() expect(api.getTaskChain).toHaveBeenCalledWith(1001) }) it('终态完成(2)+versionId → 自动 router.replace 跳预览(带 gameId)', async () => { api.getTaskChain.mockResolvedValue({ id: 1001, gameId: 1, status: 2, progress: 100, versionId: 9001 }) mountTask('1001') await flushPromises() expect(replace).toHaveBeenCalledOnce() const nav = replace.mock.calls[0][0] expect(nav.name).toBe('create-preview') expect(nav.params.versionId).toBe('9001') expect(nav.query.gameId).toBe('1') }) it('终态失败(3) → 不跳预览;点重生成 → regenerate 并跳新任务链', async () => { api.getTaskChain.mockResolvedValue({ id: 1001, gameId: 1, status: 3, progress: 0 }) api.regenerate.mockResolvedValue({ id: 1009, gameId: 1, status: 1, progress: 0, retryOf: 1001 }) const w = mountTask('1001') await flushPromises() expect(replace).not.toHaveBeenCalled() // 失败不跳预览 // 失败态有重生成按钮(EmptyState #action 内 primary 按钮) const btns = w.findAll('.app-btn') expect(btns.length).toBeGreaterThan(0) await btns[btns.length - 1].trigger('click') // 重生成(末按钮=primary) await flushPromises() expect(api.regenerate).toHaveBeenCalledWith(1001) const nav = replace.mock.calls[0][0] expect(nav.name).toBe('create-task') expect(nav.params.taskId).toBe('1009') }) })