/* @vitest-environment jsdom */ import { fireEvent, render, screen } from '@testing-library/react'; import { expect, test, vi } from 'vitest'; import type { Match3DAgentSessionSnapshot } from '../../../packages/shared/src/contracts/match3dAgent'; import { Match3DAgentWorkspace } from './Match3DAgentWorkspace'; const baseSession: Match3DAgentSessionSnapshot = { sessionId: 'match3d-session-1', currentTurn: 0, progressPercent: 0, stage: 'collecting_config', anchorPack: { theme: { key: 'theme', label: '题材主题', value: '水果摊', status: 'confirmed', }, clearCount: { key: 'clearCount', label: '需要消除次数', value: '8', status: 'confirmed', }, difficulty: { key: 'difficulty', label: '难度', value: '3', status: 'confirmed', }, }, config: { themeText: '水果摊', referenceImageSrc: null, clearCount: 8, difficulty: 3, assetStyleId: 'cel-cartoon', assetStyleLabel: '赛璐璐卡通', assetStylePrompt: '明亮赛璐璐卡通 2D 游戏道具风格,清晰线稿,硬边阴影,饱和配色,轮廓醒目。', generateClickSound: false, }, draft: null, messages: [ { id: 'message-1', role: 'assistant', kind: 'chat', text: '旧会话固定追问不再作为主入口。', createdAt: '2026-05-10T10:00:00.000Z', }, ], lastAssistantReply: '旧会话固定追问不再作为主入口。', publishedProfileId: null, updatedAt: '2026-05-10T10:00:00.000Z', }; test('match3d workspace submits derived entry form payload instead of agent chat', () => { const onCreateFromForm = vi.fn(); const onExecuteAction = vi.fn(); render( {}} onExecuteAction={onExecuteAction} onCreateFromForm={onCreateFromForm} />, ); expect(screen.getByText('想做个什么玩法?')).toBeTruthy(); expect(screen.getByLabelText('想做一个什么题材的抓大鹅?')).toBeTruthy(); expect(screen.getByText('2D素材风格')).toBeTruthy(); expect(screen.getByRole('button', { name: '扁平图标' })).toBeTruthy(); expect(screen.getByRole('button', { name: '自定义' })).toBeTruthy(); expect(screen.getByText('消耗10泥点')).toBeTruthy(); expect(screen.queryByRole('button', { name: '生成音效' })).toBeNull(); expect(screen.queryByText('参考图')).toBeNull(); expect(screen.queryByLabelText('上传抓大鹅参考图')).toBeNull(); expect(screen.queryByLabelText('需要消除次数')).toBeNull(); expect(screen.queryByLabelText('难度数值')).toBeNull(); expect(screen.queryByText('物品')).toBeNull(); expect(screen.queryByText('旧会话固定追问不再作为主入口。')).toBeNull(); fireEvent.change(screen.getByLabelText('想做一个什么题材的抓大鹅?'), { target: { value: '赛博水果摊' }, }); fireEvent.click(screen.getByRole('button', { name: '进阶' })); fireEvent.click(screen.getByRole('button', { name: /生成抓大鹅草稿/u })); expect(onCreateFromForm).toHaveBeenCalledWith({ seedText: '赛博水果摊题材,消除16次,难度6', themeText: '赛博水果摊', referenceImageSrc: null, clearCount: 16, difficulty: 6, assetStyleId: 'flat-icon', assetStyleLabel: '扁平图标', assetStylePrompt: '干净扁平的 2D 游戏道具图标风格,正面视角,色块清楚,边缘硬朗,高可读性,适合移动端休闲游戏素材。', generateClickSound: false, }); expect(onExecuteAction).not.toHaveBeenCalled(); }); test('match3d workspace supports custom 2d asset style prompt', () => { const onCreateFromForm = vi.fn(); render( {}} onExecuteAction={() => {}} onCreateFromForm={onCreateFromForm} />, ); fireEvent.change(screen.getByLabelText('想做一个什么题材的抓大鹅?'), { target: { value: '海底甜品店' }, }); fireEvent.click(screen.getByRole('button', { name: '自定义' })); expect(screen.getByRole('dialog', { name: '自定义风格' })).toBeTruthy(); fireEvent.change(screen.getByLabelText('自定义2D素材风格描述'), { target: { value: '透明果冻材质,边缘有柔和蓝色荧光' }, }); fireEvent.click(screen.getByRole('button', { name: '应用' })); fireEvent.click(screen.getByRole('button', { name: /生成抓大鹅草稿/u })); expect(onCreateFromForm).toHaveBeenCalledWith( expect.objectContaining({ seedText: '海底甜品店题材,消除12次,难度4', themeText: '海底甜品店', clearCount: 12, difficulty: 4, assetStyleId: 'custom', assetStyleLabel: '自定义风格', assetStylePrompt: '透明果冻材质,边缘有柔和蓝色荧光', }), ); }); test('match3d workspace submits strict pixel-retro style prompt', () => { const onCreateFromForm = vi.fn(); render( {}} onExecuteAction={() => {}} onCreateFromForm={onCreateFromForm} />, ); fireEvent.change(screen.getByLabelText('想做一个什么题材的抓大鹅?'), { target: { value: '复古水果铺' }, }); fireEvent.click(screen.getByRole('button', { name: '像素复古' })); fireEvent.click(screen.getByRole('button', { name: /生成抓大鹅草稿/u })); expect(onCreateFromForm).toHaveBeenCalledWith( expect.objectContaining({ assetStyleId: 'pixel-retro', assetStyleLabel: '像素复古', assetStylePrompt: expect.stringContaining('64x64'), }), ); expect(onCreateFromForm).toHaveBeenCalledWith( expect.objectContaining({ assetStylePrompt: expect.stringContaining('禁止抗锯齿'), }), ); }); test('match3d workspace keeps click sound generation disabled from entry form', () => { const onCreateFromForm = vi.fn(); render( {}} onExecuteAction={() => {}} onCreateFromForm={onCreateFromForm} />, ); fireEvent.change(screen.getByLabelText('想做一个什么题材的抓大鹅?'), { target: { value: '海岛甜品' }, }); fireEvent.click(screen.getByRole('button', { name: /生成抓大鹅草稿/u })); expect(onCreateFromForm).toHaveBeenCalledWith( expect.objectContaining({ themeText: '海岛甜品', generateClickSound: false, }), ); }); test('match3d workspace falls back to compile action when restored from the legacy route', () => { const onExecuteAction = vi.fn(); render( {}} onExecuteAction={onExecuteAction} />, ); expect( (screen.getByLabelText('想做一个什么题材的抓大鹅?') as HTMLTextAreaElement) .value, ).toBe('水果摊'); expect( screen.getByRole('button', { name: '轻松' }).getAttribute('aria-pressed'), ).toBe('true'); expect( screen .getByRole('button', { name: '赛璐璐卡通' }) .getAttribute('aria-pressed'), ).toBe('true'); fireEvent.click(screen.getByRole('button', { name: /生成抓大鹅草稿/u })); expect(onExecuteAction).toHaveBeenCalledWith({ action: 'match3d_compile_draft', generateClickSound: false, }); });