/** Offline connectivity diagnostic; not in the normal unit suite. * Actual React controls + Zustand commands -> stdin bridge -> actual Python * Specification kernel / production capability builder / compiler. Upload and * transport are simulated. No live gateway, reasoner, paid task or provider. */ import { spawnSync } from 'node:child_process'; import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react'; import { afterEach, beforeEach, expect, it, vi } from 'vitest'; import { YouthCreationCard } from '@/pages/ImageCanvas/YouthCreationCard'; import { DesignPlanHistory } from '@/pages/ImageCanvas/DesignPlanHistory'; import { useImageWorkspaceStore } from '@/stores/image-workspace'; import { ImageWorkspaceApiError, submitImageWorkspaceCommand, uploadImageWorkspaceAsset, } from '@/lib/image-workspace'; import { designCapabilitiesFixture, designFormFixture, designQuoteFixture, designWorkspaceFixture, } from '../../tests/fixtures/design-workspace-v2'; import type { DesignAsset, DesignCompilationIssue, DesignSpecification, DesignUserFieldOperation, } from '../../shared/image-workspace'; vi.mock('@/lib/image-workspace', async (original) => ({ ...await original(), submitImageWorkspaceCommand: vi.fn(), uploadImageWorkspaceAsset: vi.fn(), resolveImageWorkspaceAssetUrl: vi.fn().mockResolvedValue('blob:offline-preview'), })); vi.mock('@/lib/host-api', async (original) => ({ ...await original(), setDesktopBackgroundLease: vi.fn(), })); vi.mock('@/stores/auth', () => ({ useAuthStore: { getState: () => ({ isAuthenticated: () => true }) }, })); function serverStep( specification: DesignSpecification | null, inputKind: 'direct_edit' | 'prepare_generation', operations: DesignUserFieldOperation[] = [], ) { const result = spawnSync(process.env.DESIGN_DIAGNOSTIC_PYTHON!, [ '-X', 'utf8', '-m', 'scripts.diagnostics.design_media_blockers', '--json-step', ], { cwd: process.env.DESIGN_DIAGNOSTIC_SERVER, input: JSON.stringify({ specification, input_kind: inputKind, operations }), encoding: 'utf8', }); if (result.status !== 0) throw new Error(result.stderr || String(result.error)); return JSON.parse(result.stdout) as { specification: DesignSpecification; ready: boolean; blockers: DesignCompilationIssue[]; }; } const asset: DesignAsset = { assetId: 'image-1', workspaceId: 'workspace-1', role: 'uploaded', mediaType: 'image', mimeType: 'image/png', width: 720, height: 1280, durationMilliseconds: null, generationTaskId: null, createdAt: '2026-09-16T00:00:00Z', contentPath: '/api/design/assets/image-1/content', }; function LiveCard() { const workspace = useImageWorkspaceStore((state) => state.workspace)!; const blockers = useImageWorkspaceStore((state) => state.quoteBlockers); return ; } beforeEach(() => { vi.clearAllMocks(); useImageWorkspaceStore.getState().reset(); }); afterEach(cleanup); it('reaches video confirmation through the real prepare-generation bridge', async () => { const initial = serverStep(null, 'direct_edit', [ { kind: 'set', path: 'intent.media', value: 'image' }, { kind: 'set', path: 'content.concept', value: '沙漠里的小动物围圈玩游戏,天上的云在飘。' }, { kind: 'set', path: 'output.aspect_ratio', value: '9:16' }, ]); let workspace = designWorkspaceFixture({ form: designFormFixture({ specification: initial.specification, activeQuotes: [], }) }); const commands: string[] = []; expect(initial.ready).toBe(true); useImageWorkspaceStore.setState({ workspace, activeWorkspaceId: workspace.workspace.workspaceId, status: 'ready', }); vi.mocked(uploadImageWorkspaceAsset).mockResolvedValue(asset); vi.mocked(submitImageWorkspaceCommand).mockImplementation(async (command) => { commands.push(command.kind === 'apply_input' ? command.input.kind : command.kind); const inputKind = command.kind === 'apply_input' ? command.input.kind : 'direct_edit'; const operations = command.kind === 'apply_input' && ( command.input.kind === 'direct_edit' || command.input.kind === 'prepare_generation' ) ? command.input.operations ?? [] : []; const step = serverStep(workspace.form.specification, inputKind, operations); workspace = { ...workspace, assets: operations.some((operation) => operation.path === 'references') ? [asset] : workspace.assets, workspace: { ...workspace.workspace, workspaceViewRevision: workspace.workspace.workspaceViewRevision + 1 }, form: { ...workspace.form, specification: step.specification, specificationRevision: workspace.form.specificationRevision + 1, directionRevision: workspace.form.directionRevision + 1 }, }; useImageWorkspaceStore.setState({ workspace, quoteBlockers: step.blockers }); if (!step.ready && command.kind === 'request_quote') { throw new ImageWorkspaceApiError(409, 'design_quote_blocked', 'offline blocked'); } if (command.kind === 'request_quote') { const quote = designQuoteFixture(); workspace.form.activeQuotes = [{ ...quote, medium: 'video', specificationRevision: workspace.form.specificationRevision, outputSummary: { ...quote.outputSummary, medium: 'video', aspectRatio: '9:16', durationSeconds: 6, deliveryFormat: 'mp4' } }]; } return { workspace, runId: 'offline-run', clientOperationId: command.clientOperationId }; }); render(); fireEvent.change(screen.getByRole('combobox', { name: '类型' }), { target: { value: 'video' } }); await waitFor(() => expect(useImageWorkspaceStore.getState().workspace?.form.specification.values.intent.media).toBe('video')); fireEvent.change(screen.getByLabelText('选择参考图片'), { target: { files: [new File(['offline-upload'], 'start.png', { type: 'image/png' })] }, }); await screen.findByText('已选用'); expect(useImageWorkspaceStore.getState().workspace?.form.specification.values.video.first_frame_asset_id).toBe('image-1'); expect(useImageWorkspaceStore.getState().workspace?.form.specification.values.video.shots.length).toBe(1); await waitFor(() => expect(screen.getByRole('button', { name: '准备制作方案' })).toBeEnabled()); fireEvent.click(screen.getByRole('button', { name: '准备制作方案' })); await waitFor(() => expect(commands).toContain('request_quote')); console.log('FLOW_COMMANDS', commands); console.log('FLOW_BLOCKERS', useImageWorkspaceStore.getState().quoteBlockers.map((item) => item.code)); expect(commands.filter((kind) => kind === 'prepare_generation')).toHaveLength(3); expect(useImageWorkspaceStore.getState().quoteBlockers).toEqual([]); expect(screen.getByRole('button', { name: '确认并开始制作' })).toBeEnabled(); fireEvent.click(screen.getByRole('button', { name: '确认并开始制作' })); await waitFor(() => expect(commands).toContain('confirm_generation')); }); it('lets a user play a completed video directly in the active history rail', async () => { const video: DesignAsset = { ...asset, assetId: 'video-1', role: 'generated', mediaType: 'video', mimeType: 'video/mp4', durationMilliseconds: 6000, generationTaskId: 'task-video', contentPath: '/api/design/assets/video-1/content' }; const workspace = designWorkspaceFixture(); workspace.assets = [video]; // Presentation fixture only; no claim that the provider generated these bytes. workspace.tasks = [{ taskId: 'task-video', status: 'succeeded', medium: 'video', createdAt: video.createdAt, maximumCustomerChargeAtoms: 500, resultAssetIds: ['video-1'], issues: [], outputSummary: { medium: 'video', aspectRatio: '9:16', outputCount: 1, durationSeconds: 6 }, progress: { stage: 'completed', completedRequiredSteps: 2, totalRequiredSteps: 2 }, } as typeof workspace.tasks[number]]; const { container } = render(); await screen.findAllByLabelText('视频作品预览'); fireEvent.click(screen.getByText('制作记录')); const players = [...container.querySelectorAll('video')]; expect(players.length).toBeGreaterThan(0); expect(players.some((player) => player.controls) || screen.queryByRole('button', { name: /播放|查看视频/ }) !== null).toBe(true); });