Files
makelore/tests/unit/youth-form-projection.test.ts
brother7 945b40de11
Some checks failed
Electron E2E / Electron E2E (macos-latest) (push) Has been cancelled
Electron E2E / Electron E2E (ubuntu-latest) (push) Has been cancelled
Electron E2E / Electron E2E (windows-latest) (push) Has been cancelled
feat(design): integrate conversation-first creation flow
2026-09-04 07:39:58 +08:00

289 lines
10 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import type {
DesignAsset,
DesignCompilationIssue,
DesignGenerationTask,
DesignSpecificationValues,
} from '../../shared/image-workspace';
import {
DESIGN_SPECIFICATION_PATHS,
YOUTH_FIELD_TIERS,
projectYouthCreationCard,
} from '@/pages/ImageCanvas/youth-form-projection';
import {
designFormFixture,
designQuoteFixture,
designValuesFixture,
designWorkspaceFixture,
} from '../fixtures/design-workspace-v2';
function workspaceWithValues(values: DesignSpecificationValues) {
return designWorkspaceFixture({
form: designFormFixture({
specification: {
schema_version: 1,
values,
field_decisions: {},
},
}),
turns: [],
});
}
function emptyValues(): DesignSpecificationValues {
const values = structuredClone(designValuesFixture);
values.intent.media = null;
values.intent.creation_intent = null;
values.intent.purpose = null;
values.intent.channel = null;
values.intent.audience = null;
values.content.concept = null;
values.output.aspect_ratio = null;
values.output.orientation = null;
return values;
}
function taskFixture(
overrides: Partial<DesignGenerationTask> = {},
): DesignGenerationTask {
const quote = designQuoteFixture();
return {
taskId: 'task-1',
quoteId: quote.quoteId,
status: 'running',
taskRevision: 1,
specificationRevision: quote.specificationRevision,
specificationRevisionId: quote.specificationRevisionId,
specificationDigest: quote.specificationDigest,
medium: quote.medium,
compilerVersion: quote.compilerVersion,
outputSummary: quote.outputSummary,
maximumCustomerChargeAtoms: quote.maximumCustomerChargeAtoms,
customerBilling: {
quotedAtoms: 1200,
heldAtoms: 1200,
chargedAtoms: 0,
refundedAtoms: 0,
pendingResolutionAtoms: 0,
},
customerHoldExpiresAt: '2030-01-02T03:04:05.000Z',
resolutionDeadlineAt: null,
progress: {
stage: 'generating',
completedRequiredSteps: 0,
totalRequiredSteps: 1,
},
executionHealth: 'normal',
cancellation: { state: 'available', outcome: null },
issues: [],
resultAssetIds: [],
nextActions: ['cancel'],
createdAt: '2026-09-02T09:00:00.000Z',
...overrides,
};
}
describe('youth Design projection', () => {
it('classifies every current Specification field into exactly one presentation tier', () => {
expect(Object.keys(YOUTH_FIELD_TIERS).sort()).toEqual(
[...DESIGN_SPECIFICATION_PATHS].sort(),
);
expect(new Set(Object.values(YOUTH_FIELD_TIERS))).toEqual(new Set(['A', 'B', 'C']));
});
it('keeps a fresh Workspace simple instead of projecting professional defaults', () => {
const model = projectYouthCreationCard(workspaceWithValues(emptyValues()));
expect(model.phase).toBe('empty');
expect(model.summary).toBeNull();
expect(model.medium).toBeNull();
expect(model.destination).toBeNull();
expect(model.style).toBeNull();
expect(model.references).toEqual([]);
expect(model.readinessMessage).toBe('等你的第一个想法');
});
it('projects a concise card from canonical creator meaning', () => {
const values = structuredClone(designValuesFixture);
values.content.concept = '一只熊猫宇航员在月球上踢球';
values.content.emotional_tone = '有趣、梦幻、颜色明亮';
values.visual.art_direction = '手绘故事书';
values.constraints.must_include = ['远处的地球'];
values.constraints.must_avoid = ['真实人物'];
values.output.aspect_ratio = '9:16';
values.output.orientation = 'portrait';
const model = projectYouthCreationCard(workspaceWithValues(values));
expect(model.phase).toBe('ready');
expect(model.summary).toBe('一只熊猫宇航员在月球上踢球');
expect(model.medium).toBe('image');
expect(model.destination?.label).toBe('手机竖屏');
expect(model.style?.label).toBe('手绘故事书');
expect(model.mustInclude).toEqual(['远处的地球']);
expect(model.mustAvoid).toEqual(['真实人物']);
expect(model.readinessMessage).toBe('想法已经清楚,可以检查制作方案');
expect(model.canRequestQuote).toBe(true);
});
it('invites a server check without claiming an incomplete idea can already be made', () => {
const values = structuredClone(designValuesFixture);
values.content.concept = '一只会修理月亮的小狐狸';
values.output.aspect_ratio = null;
values.output.orientation = null;
const model = projectYouthCreationCard(workspaceWithValues(values));
expect(model.phase).toBe('ready');
expect(model.canRequestQuote).toBe(true);
expect(model.readinessMessage).toBe('想法已经清楚,可以检查制作方案');
expect(model.readinessMessage).not.toContain('可以开始制作');
});
it('projects concrete quote blockers without exposing server decision prompts', () => {
const workspace = designWorkspaceFixture({
form: designFormFixture({
decisionPrompts: [{
id: 'question-1',
kind: 'question',
basedOnSpecificationRevisionId: 'specification-revision-3',
targetPaths: ['content.emotional_tone'],
title: '你想让画面更像哪一种?',
body: '选一个最接近的感觉,也可以交给我。',
options: [
{ id: 'cartoon', label: '有趣的卡通', description: null },
{ id: 'cinema', label: '像电影一样', description: null },
{ id: 'delegate', label: '你帮我选', description: null },
],
}, {
id: 'question-2',
kind: 'question',
basedOnSpecificationRevisionId: 'specification-revision-3',
targetPaths: ['visual.palette'],
title: '第二个问题',
body: '现在不该显示',
options: [],
}],
}),
});
const blockers: DesignCompilationIssue[] = [{
code: 'aspect_ratio_required',
message: 'Choose an aspect ratio supported by the frozen route.',
severity: 'blocker',
path: 'output.aspect_ratio',
}];
const model = projectYouthCreationCard(workspace, { quoteBlockers: blockers });
expect(model.phase).toBe('exploring');
expect(model.blockers).toEqual([{
...blockers[0],
message: '先选择作品形状。',
}]);
expect(JSON.stringify(model.blockers)).not.toContain('frozen route');
expect(model.blockerCount).toBe(1);
expect(model.readinessMessage).toBe('还差 1 个关键选择');
expect(model.canRequestQuote).toBe(false);
});
it('uses Quote and Task resource state without overwriting the creative summary', () => {
const workspace = designWorkspaceFixture({
form: designFormFixture({ activeQuotes: [designQuoteFixture()] }),
});
const confirmingModel = projectYouthCreationCard(workspace);
expect(confirmingModel.phase).toBe('confirming');
expect(confirmingModel.canRequestQuote).toBe(false);
const generating = designWorkspaceFixture({ tasks: [taskFixture()] });
const generatingModel = projectYouthCreationCard(generating);
expect(generatingModel.phase).toBe('generating');
expect(generatingModel.summary).toBe(designValuesFixture.content.concept);
expect(generatingModel.readinessMessage).toBe('正在制作,请稍等一下');
const resultAsset: DesignAsset = {
assetId: 'asset-result',
workspaceId: 'workspace-1',
role: 'generated',
mediaType: 'image',
mimeType: 'image/png',
width: 1024,
height: 1365,
durationMilliseconds: null,
generationTaskId: 'task-1',
createdAt: '2026-09-02T09:05:00.000Z',
contentPath: '/api/design/assets/asset-result/content',
};
const completed = designWorkspaceFixture({
tasks: [taskFixture({
status: 'succeeded',
progress: {
stage: 'completed',
completedRequiredSteps: 1,
totalRequiredSteps: 1,
},
resultAssetIds: ['asset-result'],
nextActions: ['view_result', 'revise_design', 'generate_again'],
})],
assets: [resultAsset],
});
expect(projectYouthCreationCard(completed).phase).toBe('result');
expect(projectYouthCreationCard(completed).readinessMessage).toBe('完成了,看看你的作品吧');
});
it('summarizes bound references and video choices without exposing technical timing', () => {
const values = structuredClone(designValuesFixture);
values.intent.media = 'video';
values.video.total_duration_seconds = 10;
values.video.pacing = '很有活力';
values.video.end_state = '机器人端出一盘星星饼干';
values.video.shots = [{
id: 'shot-1',
start_milliseconds: 0,
end_milliseconds: 10_000,
story_beat: '机器人在厨房里做饭',
framing: 'close-up',
camera: 'dolly-in',
subject_motion: '快速搅拌',
environment_motion: null,
focus_behavior: null,
transition: null,
}];
values.references = [{
id: 'reference-1',
asset_id: 'asset-reference',
asset_revision: null,
role: 'subject_identity',
preserve: ['圆脸', '红围巾'],
adapt: [],
do_not_copy: [],
reviewed_observations: ['白色背景'],
}];
const workspace = workspaceWithValues(values);
workspace.assets = [{
assetId: 'asset-reference',
workspaceId: 'workspace-1',
role: 'uploaded',
mediaType: 'image',
mimeType: 'image/png',
width: 600,
height: 600,
durationMilliseconds: null,
generationTaskId: null,
createdAt: '2026-09-02T09:00:00.000Z',
contentPath: '/api/design/assets/asset-reference/content',
}];
const model = projectYouthCreationCard(workspace);
expect(model.video).toEqual({
durationSeconds: 10,
pacing: '很有活力',
endState: '机器人端出一盘星星饼干',
storyBeats: ['机器人在厨房里做饭'],
});
expect(model.references[0]).toMatchObject({
assetId: 'asset-reference',
preserve: ['圆脸', '红围巾'],
});
expect(JSON.stringify(model)).not.toContain('milliseconds');
expect(JSON.stringify(model)).not.toContain('dolly-in');
});
});