- 常规参考图入口改为先弹出来源菜单,支持从画布选择和上传图片。 - 角色规范、图标规范和常规参考图来源菜单统一向上弹出。 - 画布参考图选择拦截普通图层选中逻辑,保持生成面板不隐藏。 - 补充图片编辑器交互测试与技术文档说明。
205 lines
6.1 KiB
TypeScript
205 lines
6.1 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
||
|
||
import { ApiClientError } from '../../services/apiClient';
|
||
import {
|
||
DEFAULT_IMAGE_MODEL,
|
||
buildCharacterGenerationInputs,
|
||
buildEditGenerationInputs,
|
||
buildIconGenerationInputs,
|
||
buildImageGenerationInputs,
|
||
buildQuickEditModelOptions,
|
||
buildSpecGenerationInputs,
|
||
buildSpecPrompt,
|
||
getGenerationFrameAriaLabel,
|
||
getGenerationFrameLabel,
|
||
resolveCharacterAnimationSourceImageSrc,
|
||
resolveImageGenerationErrorMessage,
|
||
} from './ImageCanvasGenerationModel';
|
||
import type {
|
||
CanvasGenerationDialogState,
|
||
CanvasLayer,
|
||
} from './ImageCanvasEditorTypes';
|
||
|
||
describe('ImageCanvasGenerationModel', () => {
|
||
it('builds user-facing generation input snapshots instead of backend prompts', () => {
|
||
expect(buildImageGenerationInputs(' 一张明亮主视觉 ')).toEqual({
|
||
fields: [{ title: '生成提示词', value: '一张明亮主视觉' }],
|
||
references: [],
|
||
});
|
||
|
||
expect(
|
||
buildSpecGenerationInputs('character', {
|
||
playSetting: '平台跳跃',
|
||
artStyle: '像素风',
|
||
bodyRatio: '3',
|
||
characterView: '右向三分之二侧身',
|
||
customPrompt: '',
|
||
}),
|
||
).toEqual({
|
||
fields: [
|
||
{ title: '玩法设定', value: '平台跳跃' },
|
||
{ title: '美术风格', value: '像素风' },
|
||
{ title: '头身比', value: '3' },
|
||
{ title: '角色视角', value: '右向三分之二侧身' },
|
||
],
|
||
references: [],
|
||
});
|
||
});
|
||
|
||
it('builds character, icon and edit reference snapshots', () => {
|
||
const sourceLayer = buildSourceLayer();
|
||
|
||
expect(
|
||
buildCharacterGenerationInputs(
|
||
'主角骑士',
|
||
{ id: 'spec', label: '角色规范', src: '/spec.png' },
|
||
[{ id: 'ref-1', label: '盔甲参考', src: '/armor.png' }],
|
||
),
|
||
).toEqual({
|
||
fields: [{ title: '角色设定', value: '主角骑士' }],
|
||
references: [
|
||
{ title: '角色形象规范', label: '角色规范', src: '/spec.png' },
|
||
{ title: '常规参考图 1', label: '盔甲参考', src: '/armor.png' },
|
||
],
|
||
});
|
||
|
||
expect(
|
||
buildIconGenerationInputs(['返回按钮', '设置按钮'], {
|
||
id: 'icon-spec',
|
||
label: '图标规范',
|
||
src: '/icon-spec.png',
|
||
}),
|
||
).toEqual({
|
||
fields: [
|
||
{ title: '素材描述 1', value: '返回按钮' },
|
||
{ title: '素材描述 2', value: '设置按钮' },
|
||
],
|
||
references: [
|
||
{ title: '图标素材规范', label: '图标规范', src: '/icon-spec.png' },
|
||
],
|
||
});
|
||
|
||
expect(
|
||
buildEditGenerationInputs('修改要求', '换成夜晚', sourceLayer),
|
||
).toEqual({
|
||
fields: [{ title: '修改要求', value: '换成夜晚' }],
|
||
references: [
|
||
{ title: '参考图', label: '原图', src: '/source.png' },
|
||
],
|
||
});
|
||
});
|
||
|
||
it('keeps generated prompts and quick edit options stable', () => {
|
||
const prompt = buildSpecPrompt('ui', {
|
||
playSetting: '消除玩法',
|
||
artStyle: '清爽卡通',
|
||
bodyRatio: '3',
|
||
characterView: '',
|
||
customPrompt: '',
|
||
});
|
||
|
||
expect(prompt).toContain('生成一张完整游戏UI规范汇总设定展板');
|
||
expect(prompt).toContain('玩法设定:消除玩法');
|
||
expect(buildSpecPrompt('custom', { ...blankSpecValues, customPrompt: '自定义' }))
|
||
.toBe('自定义');
|
||
expect(buildQuickEditModelOptions('nano-banana')).toEqual([
|
||
{ label: 'nano-banana', value: 'nano-banana' },
|
||
{ label: 'nanobanana2', value: DEFAULT_IMAGE_MODEL },
|
||
{ label: 'gpt-image-2', value: 'gpt-image-2' },
|
||
]);
|
||
});
|
||
|
||
it('adds reference image semantics and snapshots for spec generation references', () => {
|
||
const prompt = buildSpecPrompt(
|
||
'ui',
|
||
{
|
||
playSetting: '消除玩法',
|
||
artStyle: '清爽卡通',
|
||
bodyRatio: '3',
|
||
characterView: '',
|
||
customPrompt: '',
|
||
},
|
||
true,
|
||
);
|
||
|
||
expect(prompt).toContain('参考图生成规范');
|
||
expect(prompt).toContain('参考图1');
|
||
expect(prompt).toContain('生成一张完整游戏UI规范汇总设定展板');
|
||
expect(
|
||
buildSpecPrompt(
|
||
'custom',
|
||
{ ...blankSpecValues, customPrompt: '生成一张怪兽规范图' },
|
||
true,
|
||
),
|
||
).toContain('生成一张怪兽规范图');
|
||
expect(
|
||
buildSpecGenerationInputs(
|
||
'custom',
|
||
{ ...blankSpecValues, customPrompt: '生成一张怪兽规范图' },
|
||
{ id: 'spec-ref', label: '参考.png', src: '/ref.png' },
|
||
),
|
||
).toEqual({
|
||
fields: [{ title: '自定义规范提示词', value: '生成一张怪兽规范图' }],
|
||
references: [{ title: '参考图', label: '参考.png', src: '/ref.png' }],
|
||
});
|
||
});
|
||
|
||
it('uses objectKey for character animation references before falling back to src', () => {
|
||
expect(
|
||
resolveCharacterAnimationSourceImageSrc({
|
||
...buildSourceLayer(),
|
||
objectKey: 'generated/character.png',
|
||
}),
|
||
).toBe('generated/character.png');
|
||
expect(resolveCharacterAnimationSourceImageSrc(buildSourceLayer())).toBe(
|
||
'/source.png',
|
||
);
|
||
});
|
||
|
||
it('maps generation dialog mode and authorization errors to user-facing copy', () => {
|
||
const iconDialog: CanvasGenerationDialogState = {
|
||
id: 'dialog-icon',
|
||
mode: 'icon',
|
||
prompt: '',
|
||
status: 'idle',
|
||
};
|
||
|
||
expect(getGenerationFrameAriaLabel(iconDialog)).toBe('图标素材生成占位图');
|
||
expect(getGenerationFrameLabel(iconDialog)).toBe('Icon Generator');
|
||
expect(
|
||
resolveImageGenerationErrorMessage(
|
||
new ApiClientError({
|
||
message: '未授权访问(requestId: one)',
|
||
status: 401,
|
||
code: 'UNAUTHORIZED',
|
||
}),
|
||
),
|
||
).toBe('请先登录后再生成图片');
|
||
});
|
||
});
|
||
|
||
const blankSpecValues = {
|
||
playSetting: '',
|
||
artStyle: '',
|
||
bodyRatio: '3',
|
||
characterView: '',
|
||
customPrompt: '',
|
||
};
|
||
|
||
function buildSourceLayer(): CanvasLayer {
|
||
return {
|
||
id: 'layer-source',
|
||
resourceId: 'resource-source',
|
||
title: '原图',
|
||
src: '/source.png',
|
||
x: 0,
|
||
y: 0,
|
||
width: 512,
|
||
height: 512,
|
||
originalWidth: 512,
|
||
originalHeight: 512,
|
||
zIndex: 1,
|
||
sourceType: 'uploaded',
|
||
};
|
||
}
|