diff --git a/game-studio/src/views/create/Task.spec.ts b/game-studio/src/views/create/Task.spec.ts new file mode 100644 index 00000000..b332ab07 --- /dev/null +++ b/game-studio/src/views/create/Task.spec.ts @@ -0,0 +1,87 @@ +/** + * 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') + }) +})