213 lines
6.1 KiB
TypeScript
213 lines
6.1 KiB
TypeScript
/* @vitest-environment jsdom */
|
||
|
||
import { act, fireEvent, render, screen } from '@testing-library/react';
|
||
import { afterEach, beforeEach, expect, test, vi } from 'vitest';
|
||
|
||
import type { PuzzleAgentSessionSnapshot } from '../../../packages/shared/src/contracts/puzzleAgentSession';
|
||
import { PuzzleAgentWorkspace } from './PuzzleAgentWorkspace';
|
||
|
||
const baseSession: PuzzleAgentSessionSnapshot = {
|
||
sessionId: 'puzzle-session-1',
|
||
currentTurn: 3,
|
||
progressPercent: 62,
|
||
stage: 'collecting_anchors',
|
||
anchorPack: {
|
||
themePromise: {
|
||
key: 'themePromise',
|
||
label: '题材承诺',
|
||
value: '雾港遗迹拼图',
|
||
status: 'confirmed',
|
||
},
|
||
visualSubject: {
|
||
key: 'visualSubject',
|
||
label: '画面主体',
|
||
value: '潮雾中的灯塔与断桥',
|
||
status: 'confirmed',
|
||
},
|
||
visualMood: {
|
||
key: 'visualMood',
|
||
label: '视觉气质',
|
||
value: '',
|
||
status: 'missing',
|
||
},
|
||
compositionHooks: {
|
||
key: 'compositionHooks',
|
||
label: '拼图记忆点',
|
||
value: '',
|
||
status: 'missing',
|
||
},
|
||
tagsAndForbidden: {
|
||
key: 'tagsAndForbidden',
|
||
label: '标签与禁忌',
|
||
value: '',
|
||
status: 'missing',
|
||
},
|
||
},
|
||
draft: null,
|
||
messages: [
|
||
{
|
||
id: 'message-1',
|
||
role: 'assistant',
|
||
kind: 'chat',
|
||
text: '旧会话消息不再渲染为聊天入口。',
|
||
createdAt: '2026-04-24T10:00:00.000Z',
|
||
},
|
||
],
|
||
lastAssistantReply: '旧会话消息不再渲染为聊天入口。',
|
||
publishedProfileId: null,
|
||
suggestedActions: [],
|
||
resultPreview: null,
|
||
updatedAt: '2026-04-24T10:00:00.000Z',
|
||
};
|
||
|
||
beforeEach(() => {
|
||
if (!Element.prototype.scrollIntoView) {
|
||
Element.prototype.scrollIntoView = () => {};
|
||
}
|
||
});
|
||
|
||
afterEach(() => {
|
||
vi.useRealTimers();
|
||
});
|
||
|
||
test('puzzle workspace submits the work form instead of agent chat', () => {
|
||
const onCreateFromForm = vi.fn();
|
||
|
||
render(
|
||
<PuzzleAgentWorkspace
|
||
session={null}
|
||
onBack={() => {}}
|
||
onSubmitMessage={() => {}}
|
||
onExecuteAction={() => {}}
|
||
onCreateFromForm={onCreateFromForm}
|
||
/>,
|
||
);
|
||
|
||
fireEvent.change(screen.getByLabelText('作品名称'), {
|
||
target: { value: '暖灯猫街' },
|
||
});
|
||
fireEvent.change(screen.getByLabelText('作品描述'), {
|
||
target: { value: '一套雨夜猫街主题拼图。' },
|
||
});
|
||
fireEvent.change(screen.getByLabelText('画面描述'), {
|
||
target: { value: '一只猫在雨夜灯牌下回头。' },
|
||
});
|
||
fireEvent.click(screen.getByRole('button', { name: /生成草稿/u }));
|
||
|
||
expect(onCreateFromForm).toHaveBeenCalledWith({
|
||
seedText: '暖灯猫街',
|
||
workTitle: '暖灯猫街',
|
||
workDescription: '一套雨夜猫街主题拼图。',
|
||
pictureDescription: '一只猫在雨夜灯牌下回头。',
|
||
referenceImageSrc: null,
|
||
});
|
||
expect(screen.queryByRole('button', { name: '补充剩余设定' })).toBeNull();
|
||
expect(screen.queryByText('旧会话消息不再渲染为聊天入口。')).toBeNull();
|
||
});
|
||
|
||
test('puzzle workspace falls back to compile action for restored sessions', () => {
|
||
const onExecuteAction = vi.fn();
|
||
const onCreateFromForm = vi.fn();
|
||
|
||
render(
|
||
<PuzzleAgentWorkspace
|
||
session={baseSession}
|
||
onBack={() => {}}
|
||
onSubmitMessage={() => {}}
|
||
onExecuteAction={onExecuteAction}
|
||
onCreateFromForm={onCreateFromForm}
|
||
/>,
|
||
);
|
||
|
||
fireEvent.click(screen.getByRole('button', { name: /生成草稿/u }));
|
||
|
||
expect(onCreateFromForm).not.toHaveBeenCalled();
|
||
expect(onExecuteAction).toHaveBeenCalledWith({
|
||
action: 'compile_puzzle_draft',
|
||
promptText: '潮雾中的灯塔与断桥',
|
||
workTitle: '雾港遗迹拼图',
|
||
workDescription: '雾港遗迹拼图',
|
||
pictureDescription: '潮雾中的灯塔与断桥',
|
||
referenceImageSrc: null,
|
||
candidateCount: 1,
|
||
});
|
||
});
|
||
|
||
test('puzzle workspace restores form draft fields and autosaves edits', () => {
|
||
vi.useFakeTimers();
|
||
const onAutoSaveForm = vi.fn();
|
||
const formDraftSession: PuzzleAgentSessionSnapshot = {
|
||
...baseSession,
|
||
seedText:
|
||
'作品名称:旧街拼图\n作品描述:旧街雨夜的拼图草稿。\n画面描述:旧街灯牌下的猫。',
|
||
draft: {
|
||
workTitle: '旧街拼图',
|
||
workDescription: '旧街雨夜的拼图草稿。',
|
||
levelName: '旧街灯牌',
|
||
summary: '旧街雨夜的拼图草稿。',
|
||
themeTags: ['旧街', '雨夜', '猫'],
|
||
forbiddenDirectives: [],
|
||
creatorIntent: null,
|
||
anchorPack: baseSession.anchorPack,
|
||
candidates: [],
|
||
selectedCandidateId: null,
|
||
coverImageSrc: null,
|
||
coverAssetId: null,
|
||
generationStatus: 'idle',
|
||
levels: [
|
||
{
|
||
levelId: 'puzzle-level-1',
|
||
levelName: '旧街灯牌',
|
||
pictureDescription: '旧街灯牌下的猫。',
|
||
candidates: [],
|
||
selectedCandidateId: null,
|
||
coverImageSrc: null,
|
||
coverAssetId: null,
|
||
generationStatus: 'idle',
|
||
},
|
||
],
|
||
formDraft: {
|
||
workTitle: '旧街拼图',
|
||
workDescription: '旧街雨夜的拼图草稿。',
|
||
pictureDescription: '旧街灯牌下的猫。',
|
||
},
|
||
},
|
||
};
|
||
|
||
render(
|
||
<PuzzleAgentWorkspace
|
||
session={formDraftSession}
|
||
onBack={() => {}}
|
||
onSubmitMessage={() => {}}
|
||
onExecuteAction={() => {}}
|
||
onAutoSaveForm={onAutoSaveForm}
|
||
/>,
|
||
);
|
||
|
||
expect((screen.getByLabelText('作品名称') as HTMLInputElement).value).toBe(
|
||
'旧街拼图',
|
||
);
|
||
expect((screen.getByLabelText('作品描述') as HTMLTextAreaElement).value).toBe(
|
||
'旧街雨夜的拼图草稿。',
|
||
);
|
||
expect((screen.getByLabelText('画面描述') as HTMLTextAreaElement).value).toBe(
|
||
'旧街灯牌下的猫。',
|
||
);
|
||
|
||
fireEvent.change(screen.getByLabelText('画面描述'), {
|
||
target: { value: '旧街灯牌下的猫和发光雨伞。' },
|
||
});
|
||
|
||
act(() => {
|
||
vi.advanceTimersByTime(700);
|
||
});
|
||
|
||
expect(onAutoSaveForm).toHaveBeenCalledWith({
|
||
seedText: '旧街拼图',
|
||
workTitle: '旧街拼图',
|
||
workDescription: '旧街雨夜的拼图草稿。',
|
||
pictureDescription: '旧街灯牌下的猫和发光雨伞。',
|
||
referenceImageSrc: null,
|
||
});
|
||
});
|