233 lines
7.3 KiB
TypeScript
233 lines
7.3 KiB
TypeScript
/* @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(
|
||
<Match3DAgentWorkspace
|
||
session={null}
|
||
onBack={() => {}}
|
||
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(
|
||
<Match3DAgentWorkspace
|
||
session={null}
|
||
onBack={() => {}}
|
||
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(
|
||
<Match3DAgentWorkspace
|
||
session={null}
|
||
onBack={() => {}}
|
||
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(
|
||
<Match3DAgentWorkspace
|
||
session={null}
|
||
onBack={() => {}}
|
||
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(
|
||
<Match3DAgentWorkspace
|
||
session={baseSession}
|
||
onBack={() => {}}
|
||
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,
|
||
});
|
||
});
|