Files
Genarrative/src/components/creative-agent/CreativeAgentWorkspace.test.tsx
2026-05-14 14:21:17 +08:00

250 lines
7.3 KiB
TypeScript

// @vitest-environment jsdom
import { fireEvent, render, screen } from '@testing-library/react';
import { expect, test, vi } from 'vitest';
import type {
CreativeAgentSessionSnapshot,
CreativeAgentSseEvent,
CreativeAgentStage,
} from '../../../packages/shared/src/contracts/creativeAgent';
import type { PuzzleCreativeTemplateProtocol } from '../../../packages/shared/src/contracts/puzzleCreativeTemplate';
import { CreativeAgentStageTimeline } from './CreativeAgentStageTimeline';
import { resolveCreativeAgentTargetSelectionStage } from './creativeAgentViewModel';
import { CreativeAgentWorkspace } from './CreativeAgentWorkspace';
function createTemplate(
overrides: Partial<PuzzleCreativeTemplateProtocol> = {},
): PuzzleCreativeTemplateProtocol {
return {
templateId: 'puzzle.default-creative',
title: '创意拼图',
summary: '把图文灵感做成拼图。',
previewImageSrc: null,
supportedLevelMode: 'single_or_multi',
minLevelCount: 1,
maxLevelCount: 6,
defaultLevelCount: 1,
costRange: {
minPoints: 2,
maxPoints: 12,
pricingUnit: 'point',
reason: '按关卡数估算',
},
requiredDraftFields: ['workTitle'],
imagePolicy: {
allowUploadedImageDirectly: true,
allowGeneratedImages: true,
allowPerLevelReferenceImage: true,
defaultCandidateCountPerLevel: 1,
},
...overrides,
};
}
function createSession(
overrides: Partial<CreativeAgentSessionSnapshot> = {},
): CreativeAgentSessionSnapshot {
return {
sessionId: 'creative-session-1',
stage: 'target_ready',
inputSummary: {
text: '做一个生日拼图',
entryContext: 'creation_home',
images: [],
materialSummary: '做一个生日拼图',
unsupportedCapabilities: [
{
playType: 'rpg',
title: 'RPG',
status: 'unsupported',
reason: 'Phase 1 暂不开放',
},
],
},
messages: [
{
id: 'assistant-1',
role: 'assistant',
kind: 'chat',
text: '拼图草稿已准备好。',
createdAt: '2026-05-05T10:00:00.000Z',
},
],
puzzleTemplateCatalog: [],
puzzleTemplateSelection: null,
puzzleImageGenerationPlan: null,
targetBinding: {
playType: 'puzzle',
targetSessionId: 'puzzle-session-1',
targetStage: 'puzzle-result',
resultProfileId: 'puzzle-profile-1',
},
updatedAt: '2026-05-05T10:00:00.000Z',
...overrides,
};
}
test('target ready session exposes the puzzle result entry action', () => {
const onOpenTarget = vi.fn();
const eventLog: CreativeAgentSseEvent[] = [
{
event: 'puzzle_cost_range',
data: {
sessionId: 'creative-session-1',
costRange: {
minPoints: 2,
maxPoints: 12,
pricingUnit: 'point',
reason: '按关卡数估算',
},
},
},
];
render(
<CreativeAgentWorkspace
session={createSession()}
isBusy={false}
isStreaming={false}
error={null}
eventLog={eventLog}
onBack={() => {}}
onSubmitMessage={() => {}}
onConfirmTemplate={() => {}}
onCancelTemplate={() => {}}
onOpenTarget={onOpenTarget}
/>,
);
expect(screen.getByText('拼图草稿已就绪')).toBeTruthy();
expect(screen.getByText('可以进入结果页继续编辑')).toBeTruthy();
expect(screen.getByText('预计 2-12 泥点')).toBeTruthy();
fireEvent.click(screen.getByRole('button', { name: '打开草稿' }));
expect(onOpenTarget).toHaveBeenCalledTimes(1);
});
test('waiting confirmation shows template catalog before template config dialog', () => {
const onConfirmTemplate = vi.fn();
render(
<CreativeAgentWorkspace
session={createSession({
stage: 'waiting_template_confirmation',
targetBinding: null,
puzzleTemplateCatalog: [
createTemplate(),
createTemplate({
templateId: 'puzzle.travel-memory',
title: '旅行记忆拼图',
summary: '把一次出行拆成地点、风景和故事节点拼图。',
defaultLevelCount: 3,
costRange: {
minPoints: 4,
maxPoints: 16,
pricingUnit: 'point',
reason: '按旅行节点估算',
},
}),
],
})}
isBusy={false}
isStreaming={false}
error={null}
eventLog={[]}
onBack={() => {}}
onSubmitMessage={() => {}}
onConfirmTemplate={onConfirmTemplate}
onOpenTarget={() => {}}
/>,
);
expect(screen.getByRole('button', { name: //u })).toBeTruthy();
expect(screen.getByRole('button', { name: //u })).toBeTruthy();
expect(screen.queryByRole('dialog', { name: '确认拼图模板' })).toBeNull();
fireEvent.click(screen.getByRole('button', { name: //u }));
expect(screen.getByRole('dialog', { name: '确认拼图模板' })).toBeTruthy();
expect(screen.getByText('预计 4 到 16 泥点')).toBeTruthy();
fireEvent.click(screen.getByRole('button', { name: //u }));
expect(onConfirmTemplate).toHaveBeenCalledWith(
expect.objectContaining({
templateId: 'puzzle.travel-memory',
selectedLevelMode: 'multi_level',
plannedLevelCount: 3,
}),
);
});
test('switching creative session clears pending template config dialog', () => {
const firstSession = createSession({
sessionId: 'creative-session-first',
stage: 'waiting_template_confirmation',
targetBinding: null,
puzzleTemplateCatalog: [createTemplate()],
});
const secondSession = createSession({
sessionId: 'creative-session-second',
stage: 'waiting_template_confirmation',
targetBinding: null,
puzzleTemplateCatalog: [],
});
const { rerender } = render(
<CreativeAgentWorkspace
session={firstSession}
isBusy={false}
isStreaming={false}
error={null}
eventLog={[]}
onBack={() => {}}
onSubmitMessage={() => {}}
onConfirmTemplate={() => {}}
onOpenTarget={() => {}}
/>,
);
fireEvent.click(screen.getByRole('button', { name: //u }));
expect(screen.getByRole('dialog', { name: '确认拼图模板' })).toBeTruthy();
rerender(
<CreativeAgentWorkspace
session={secondSession}
isBusy={false}
isStreaming={false}
error={null}
eventLog={[]}
onBack={() => {}}
onSubmitMessage={() => {}}
onConfirmTemplate={() => {}}
onOpenTarget={() => {}}
/>,
);
expect(screen.queryByRole('dialog', { name: '确认拼图模板' })).toBeNull();
});
test('target ready puzzle result binding resolves to puzzle-result stage', () => {
expect(resolveCreativeAgentTargetSelectionStage('puzzle-result')).toBe(
'puzzle-result',
);
expect(
resolveCreativeAgentTargetSelectionStage('puzzle-agent-workspace'),
).toBe('puzzle-agent-workspace');
});
test('target ready timeline renders completed labels instead of active labels', () => {
render(<CreativeAgentStageTimeline stage={'target_ready' as CreativeAgentStage} />);
expect(screen.getByText('素材已理解')).toBeTruthy();
expect(screen.getByText('构思已完成')).toBeTruthy();
expect(screen.getByText('草稿已生成')).toBeTruthy();
expect(screen.queryByText('正在理解素材')).toBeNull();
expect(screen.queryByText('正在构思')).toBeNull();
});