This commit is contained in:
2026-05-10 22:20:54 +08:00
parent d6219f1a0c
commit 192accd796
92 changed files with 7045 additions and 1559 deletions

View File

@@ -67,6 +67,7 @@ import {
} from '../../services/match3d-works';
import {
createPuzzleAgentSession,
executePuzzleAgentAction,
getPuzzleAgentSession,
} from '../../services/puzzle-agent';
import {
@@ -463,9 +464,17 @@ vi.mock('../puzzle-agent/PuzzleAgentWorkspace', () => ({
vi.mock('../puzzle-result/PuzzleResultView', () => ({
PuzzleResultView: ({
isBusy,
onExecuteAction,
session,
onBack,
}: {
isBusy?: boolean;
onExecuteAction: (payload: {
action: string;
levelId?: string;
promptText?: string;
}) => void;
session: { draft?: { levelName: string } | null };
onBack: () => void;
}) => (
@@ -475,6 +484,21 @@ vi.mock('../puzzle-result/PuzzleResultView', () => ({
<input readOnly value={session.draft?.levelName ?? ''} />
</label>
<button
type="button"
onClick={() => {
onExecuteAction({
action: 'generate_puzzle_images',
levelId: 'puzzle-level-1',
promptText: '重新生成猫街',
});
}}
>
</button>
<button type="button" disabled={isBusy}>
</button>
<button type="button" onClick={onBack}>
</button>
@@ -550,14 +574,36 @@ vi.mock('../big-fish-result/BigFishResultView', () => ({
vi.mock('../match3d-creation/Match3DAgentWorkspace', () => ({
Match3DAgentWorkspace: ({
session,
onCreateFromForm,
}: {
session: { sessionId: string; messages: Array<{ text: string }> } | null;
onCreateFromForm?: (payload: {
seedText: string;
themeText: string;
referenceImageSrc: string | null;
clearCount: number;
difficulty: number;
}) => void;
}) => (
<div className="match3d-agent-workspace-mock">
<div>{session?.sessionId ?? 'missing-session'}</div>
{session?.messages.map((message) => (
<div key={`${session.sessionId}-${message.text}`}>{message.text}</div>
))}
<button
type="button"
onClick={() => {
onCreateFromForm?.({
seedText: '赛博水果摊题材消除9次难度6',
themeText: '赛博水果摊',
referenceImageSrc: null,
clearCount: 9,
difficulty: 6,
});
}}
>
稿
</button>
</div>
),
}));
@@ -2355,6 +2401,9 @@ test('create tab shows template tabs and embeds puzzle form by default', async (
expect(
screen.getByRole('tab', { name: 'AIRP' }).querySelector('img')?.src,
).toContain('/creation-type-references/airp.webp');
expect(
screen.getByRole('tab', { name: '抓大鹅' }).querySelector('img')?.src,
).toContain('/creation-type-references/match3d.webp');
expect(
screen.getByRole('tab', { name: '拼图' }).querySelector('.text-white'),
).toBeTruthy();
@@ -2364,12 +2413,29 @@ test('create tab shows template tabs and embeds puzzle form by default', async (
expect(screen.queryByRole('button', { name: //u })).toBeNull();
expect(screen.queryByPlaceholderText('问一问百梦')).toBeNull();
expect(screen.queryByRole('button', { name: //u })).toBeNull();
expect(screen.queryByRole('tab', { name: //u })).toBeNull();
expect(createRpgCreationSession).not.toHaveBeenCalled();
expect(match3dCreationClient.createSession).not.toHaveBeenCalled();
expect(createPuzzleAgentSession).not.toHaveBeenCalled();
});
test('create tab switches match3d into the embedded entry form', async () => {
const user = userEvent.setup();
render(<TestWrapper withAuth />);
await openCreateTemplateHub(user);
await user.click(screen.getByRole('tab', { name: '抓大鹅' }));
expect(
screen.getByRole('tab', { name: '抓大鹅' }).getAttribute('aria-selected'),
).toBe('true');
expect(
await screen.findByText('抓大鹅工作区missing-session'),
).toBeTruthy();
expect(createPuzzleAgentSession).not.toHaveBeenCalled();
expect(match3dCreationClient.createSession).not.toHaveBeenCalled();
});
test('embedded puzzle form routes through requireAuth while logged out', async () => {
const user = userEvent.setup();
const requireAuth = vi.fn();
@@ -2826,6 +2892,159 @@ test('owned public puzzle detail edits original draft instead of remixing', asyn
expect(getPuzzleAgentSession).toHaveBeenCalledWith('puzzle-session-1');
expect(remixPuzzleGalleryWork).not.toHaveBeenCalled();
expect(await screen.findByText('拼图结果页')).toBeTruthy();
vi.mocked(executePuzzleAgentAction).mockResolvedValueOnce({
operation: {
operationId: 'puzzle-image-generation-1',
type: 'generate_puzzle_images',
status: 'running',
phaseLabel: '生成中',
phaseDetail: '正在生成拼图画面',
progress: 0.3,
},
session: {
sessionId: 'puzzle-session-1',
currentTurn: 3,
progressPercent: 88,
stage: 'ready_to_publish',
anchorPack: {
themePromise: {
key: 'theme_promise',
label: '主题承诺',
value: '雨夜猫街',
status: 'confirmed',
},
visualSubject: {
key: 'visual_subject',
label: '视觉主体',
value: '屋檐下的猫',
status: 'confirmed',
},
visualMood: {
key: 'visual_mood',
label: '视觉气质',
value: '温暖',
status: 'confirmed',
},
compositionHooks: {
key: 'composition_hooks',
label: '构图钩子',
value: '雨滴与灯牌',
status: 'confirmed',
},
tagsAndForbidden: {
key: 'tags_and_forbidden',
label: '标签与禁区',
value: '猫咪、雨夜',
status: 'confirmed',
},
},
draft: {
workTitle: '暖灯猫街作品',
workDescription: '一套雨夜猫街主题拼图。',
levelName: '雨夜猫街',
summary: '屋檐下的猫与暖灯街角。',
themeTags: ['猫咪', '雨夜', '暖灯'],
forbiddenDirectives: [],
creatorIntent: null,
anchorPack: {
themePromise: {
key: 'theme_promise',
label: '主题承诺',
value: '雨夜猫街',
status: 'confirmed',
},
visualSubject: {
key: 'visual_subject',
label: '视觉主体',
value: '屋檐下的猫',
status: 'confirmed',
},
visualMood: {
key: 'visual_mood',
label: '视觉气质',
value: '温暖',
status: 'confirmed',
},
compositionHooks: {
key: 'composition_hooks',
label: '构图钩子',
value: '雨滴与灯牌',
status: 'confirmed',
},
tagsAndForbidden: {
key: 'tags_and_forbidden',
label: '标签与禁区',
value: '猫咪、雨夜',
status: 'confirmed',
},
},
candidates: [
{
candidateId: 'candidate-1',
imageSrc: '/puzzle/candidate-1.png',
assetId: 'asset-1',
prompt: '雨夜猫咪',
actualPrompt: null,
sourceType: 'generated',
selected: true,
},
],
selectedCandidateId: 'candidate-1',
coverImageSrc: '/puzzle/candidate-1.png',
coverAssetId: 'asset-1',
generationStatus: 'generating',
levels: [
{
levelId: 'puzzle-level-1',
levelName: '雨夜猫街',
pictureDescription: '屋檐下的猫与暖灯街角。',
pictureReference: null,
candidates: [
{
candidateId: 'candidate-1',
imageSrc: '/puzzle/candidate-1.png',
assetId: 'asset-1',
prompt: '雨夜猫咪',
actualPrompt: null,
sourceType: 'generated',
selected: true,
},
],
selectedCandidateId: 'candidate-1',
coverImageSrc: '/puzzle/candidate-1.png',
coverAssetId: 'asset-1',
generationStatus: 'generating',
},
],
metadata: null,
},
messages: [],
lastAssistantReply: '大鱼结果页草稿已经生成,可以补正式资产。',
publishedProfileId: null,
suggestedActions: [],
resultPreview: {
draft: null,
publishReady: false,
blockers: [],
qualityFindings: [],
},
updatedAt: '2026-04-26T10:10:00.000Z',
},
});
await user.click(screen.getByRole('button', { name: '重新生成画面' }));
expect(executePuzzleAgentAction).toHaveBeenCalledWith(
'puzzle-session-1',
expect.objectContaining({
action: 'generate_puzzle_images',
}),
);
expect(screen.getByRole('button', { name: '新增关卡' })).toHaveProperty(
'disabled',
false,
);
});
test('logged out public detail gates big fish start before local runtime', async () => {
@@ -3027,7 +3246,6 @@ test('published puzzle works appear on home and mobile game category channel', a
});
test('home recommendation starts embedded puzzle without global auth reset on local failure', async () => {
const user = userEvent.setup();
const publishedPuzzleWork = {
workId: 'puzzle-work-public-1',
profileId: 'puzzle-profile-public-1',
@@ -3396,7 +3614,7 @@ test('embedded puzzle form timeout exits busy state and shows a readable error',
expect(streamCreativeAgentMessage).not.toHaveBeenCalled();
});
test('hidden match3d creation card stays closed even when public galleries fail', async () => {
test('match3d creation tab stays usable even when public galleries fail', async () => {
const user = userEvent.setup();
vi.mocked(listRpgEntryWorldGallery).mockRejectedValueOnce(
@@ -3411,9 +3629,7 @@ test('hidden match3d creation card stays closed even when public galleries fail'
await openCreateTemplateHub(user);
expect(screen.queryByText('读取作品广场失败')).toBeNull();
expect(screen.queryByText('读取抓大鹅广场失败')).toBeNull();
expect(
screen.queryByRole('tab', { name: /.*/u }),
).toBeNull();
expect(screen.getByRole('tab', { name: '抓大鹅' })).toBeTruthy();
expect(match3dCreationClient.createSession).not.toHaveBeenCalled();
});