Files
Genarrative/src/components/creation-home/creationShowcaseModel.test.ts
T
kdletters 51c47c952b 调整前台精选分类展示
前台精选分类新增全部并作为默认 Tab。

移除素材包 Tab 和素材包组合逻辑,未设置分类素材保留在全部中。

活动卡支持从 imageObjectKey 兜底图片地址。

精选卡片改为网格布局并让图片素材铺满预览区域。

更新前台精选模型和创作主页测试。
2026-07-06 18:58:58 +08:00

539 lines
16 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import type { EditorProjectResourceSnapshot } from '../../services/image-editor/editorProjectClient';
import { buildCreationShowcaseItems } from './creationShowcaseModel';
function createResource(
overrides: Partial<EditorProjectResourceSnapshot> & Record<string, unknown>,
): EditorProjectResourceSnapshot {
return {
resourceId: 'resource-1',
projectId: 'project-1',
label: '素材',
imageSrc: 'data:image/png;base64,asset',
width: 512,
height: 512,
sourceType: 'generated',
prompt: null,
actualPrompt: null,
model: null,
provider: null,
taskId: null,
...overrides,
} as EditorProjectResourceSnapshot;
}
describe('creationShowcaseModel', () => {
it('only builds showcase items from generated project resources', () => {
const items = buildCreationShowcaseItems({
activeTab: 'all',
projectResources: [
createResource({
resourceId: 'generated-1',
label: '画布生成素材',
sourceType: 'generated',
prompt: '真实画布生成',
}),
createResource({
resourceId: 'uploaded-1',
label: '上传素材',
sourceType: 'uploaded',
prompt: '上传不应展示',
}),
createResource({
resourceId: 'mock-1',
label: 'Mock 素材',
sourceType: 'mock_generated',
prompt: 'Mock 不应展示',
}),
],
});
expect(items).toHaveLength(1);
expect(items[0]?.label).toBe('画布生成素材');
});
it('filters out generated showcase records with display disabled', () => {
const items = buildCreationShowcaseItems({
activeTab: 'all',
projectResources: [
createResource({
resourceId: 'public-generated',
label: '公开素材',
sourceType: 'generated',
publicShowcaseEnabled: true,
}),
createResource({
resourceId: 'private-generated',
label: '不公开素材',
sourceType: 'generated',
publicShowcaseEnabled: false,
}),
],
});
expect(items).toHaveLength(1);
expect(items[0]?.label).toBe('公开素材');
});
it('prepends enabled campaign cards and keeps showcase metrics', () => {
const items = buildCreationShowcaseItems({
activeTab: 'all',
campaign: {
enabled: true,
title: '活动精选',
imageSrc: '/campaign.png',
prompt: '活动提示词',
author: '运营',
costText: '50泥点',
},
projectResources: [
createResource({
resourceId: 'showcase-1',
showcaseId: 'showcase-1',
assetId: 'asset-1',
label: '审核通过素材',
sourceType: 'generated',
publicShowcaseEnabled: true,
likeCount: 12,
generationCostMudPoints: 20,
}),
],
});
expect(items).toHaveLength(2);
expect(items[0]).toMatchObject({
id: 'campaign:global',
label: '活动精选',
campaign: true,
cost: '50泥点',
});
expect(items[1]).toMatchObject({
id: 'asset:asset-1',
label: '审核通过素材',
likeCount: 12,
showcaseId: 'showcase-1',
cost: '20泥点',
});
});
it('uses manually assigned showcase categories before asset kind grouping', () => {
const projectResources = [
createResource({
resourceId: 'manual-ui-category',
showcaseId: 'showcase-ui',
label: '人工分类 UI',
sourceType: 'generated',
assetKind: 'character',
showcaseCategory: 'ui',
generationCostMudPoints: 20,
}),
];
expect(
buildCreationShowcaseItems({
activeTab: 'ui',
projectResources,
}).map((item) => item.label),
).toEqual(['人工分类 UI']);
expect(
buildCreationShowcaseItems({
activeTab: 'characters',
projectResources,
}),
).toHaveLength(0);
});
it('uses public author fields without falling back to internal owner user id', () => {
const items = buildCreationShowcaseItems({
activeTab: 'characters',
projectResources: [
createResource({
resourceId: 'display-author-resource',
label: '展示名作者素材',
assetKind: 'character',
ownerUserId: 'user-author-1',
authorDisplayName: '作者昵称',
authorPublicUserCode: 'SY-00000001',
}),
createResource({
resourceId: 'code-author-resource',
label: '陶泥号作者素材',
assetKind: 'character',
ownerUserId: 'user-author-2',
authorPublicUserCode: 'SY-00000002',
}),
createResource({
resourceId: 'owner-only-resource',
label: '内部id素材',
assetKind: 'character',
ownerUserId: 'user-author-3',
authorName: 'user-author-3',
ownerName: 'user-author-3',
}),
],
});
expect(items.map((item) => item.author)).toEqual([
'作者昵称',
'SY-00000002',
'-',
]);
});
it('deduplicates canvas copies that point back to the same generated resource', () => {
const items = buildCreationShowcaseItems({
activeTab: 'characters',
projectResources: [
createResource({
resourceId: 'resource-original',
label: '原始角色图',
imageSrc: '/generated/character.png',
objectKey: 'generated/character.png',
assetObjectId: 'asset-object-character',
assetKind: 'character',
sourceType: 'generated',
publicShowcaseEnabled: true,
}),
createResource({
resourceId: 'resource-copy',
label: '拖拽副本角色图',
imageSrc:
'https://oss.example.test/generated/character.png?x-oss-signature=1',
objectKey: '/generated/character.png',
assetObjectId: 'asset-object-character',
assetKind: 'character',
sourceType: 'generated',
sourceResourceId: 'resource-original',
publicShowcaseEnabled: true,
}),
],
});
expect(items).toHaveLength(1);
expect(items[0]?.previews).toHaveLength(1);
expect(items[0]?.previews[0]?.id).toBe('resource-original');
});
it('shows every real asset in the all tab without requiring a category', () => {
const items = buildCreationShowcaseItems({
activeTab: 'all',
projectResources: [
createResource({
resourceId: 'spec-1',
label: '角色规范图',
assetKind: 'spec',
prompt: '规范提示词',
priceMudPoints: 5,
authorDisplayName: '创作者A',
}),
createResource({
resourceId: 'character-1',
label: '角色主图',
assetKind: 'character',
priceMudPoints: 12,
generationInputs: {
fields: [{ title: '角色设定', value: '角色提示词' }],
references: [
{
title: '角色规范',
label: '角色规范图',
refType: 'project-resource',
refId: 'spec-1',
},
],
},
}),
],
});
expect(items.map((item) => item.label)).toEqual(['角色规范图', '角色主图']);
expect(items[0]?.prompt).toBe('规范提示词');
expect(items[0]?.author).toBe('创作者A');
expect(items[0]?.cost).toBe('5泥点');
expect(items[1]?.cost).toBe('12泥点');
});
it('keeps unset category visible in all tab and out of concrete category tabs', () => {
const items = buildCreationShowcaseItems({
activeTab: 'all',
projectResources: [
createResource({
resourceId: 'spec-1',
label: '未设置分类素材',
assetKind: 'spec',
showcaseCategory: null,
}),
createResource({
resourceId: 'ui-1',
label: '人工 UI 素材',
assetKind: 'character',
showcaseCategory: 'ui',
}),
],
});
expect(items.map((item) => item.label)).toEqual([
'未设置分类素材',
'人工 UI 素材',
]);
expect(
buildCreationShowcaseItems({
activeTab: 'ui',
projectResources: [
createResource({
resourceId: 'spec-1',
label: '未设置分类素材',
assetKind: 'spec',
showcaseCategory: null,
}),
createResource({
resourceId: 'ui-1',
label: '人工 UI 素材',
assetKind: 'character',
showcaseCategory: 'ui',
}),
],
}).map((item) => item.label),
).toEqual(['人工 UI 素材']);
});
it('groups character animations under the referenced character image', () => {
const items = buildCreationShowcaseItems({
activeTab: 'characters',
projectResources: [
createResource({
resourceId: 'character-1',
label: '游戏角色图',
assetKind: 'character',
prompt: '角色提示词',
}),
createResource({
resourceId: 'animation-1',
label: '待机动画',
assetKind: 'character-animation',
imageSrc: 'data:image/png;base64,animation',
generationInputs: {
fields: [{ title: '动作描述', value: '待机动作' }],
references: [
{
title: '角色图片',
label: '游戏角色图',
refType: 'project-resource',
refId: 'character-1',
},
],
},
}),
],
});
expect(items).toHaveLength(1);
expect(items[0]?.label).toBe('游戏角色图');
expect(items[0]?.previews.map((preview) => preview.label)).toEqual([
'游戏角色图',
'待机动画',
]);
});
it('groups UI design extraction assets under the referenced UI image', () => {
const items = buildCreationShowcaseItems({
activeTab: 'ui',
projectResources: [
createResource({
resourceId: 'ui-design-1',
label: '游戏UI图',
assetKind: 'ui-design',
prompt: 'UI提示词',
}),
createResource({
resourceId: 'icon-sheet-1',
label: 'UI子素材图集',
assetKind: 'icon-spritesheet',
generationInputs: {
fields: [{ title: '提取提示词', value: '提取UI子素材' }],
references: [
{
title: 'UI设计图',
label: '游戏UI图',
refType: 'project-resource',
refId: 'ui-design-1',
},
],
},
}),
createResource({
resourceId: 'icon-1',
label: '开始按钮',
assetKind: 'icon',
generationInputs: {
fields: [{ title: '提取提示词', value: '提取UI子素材' }],
references: [
{
title: 'UI设计图',
label: '游戏UI图',
refType: 'project-resource',
refId: 'ui-design-1',
},
],
},
}),
],
});
expect(items).toHaveLength(1);
expect(items[0]?.label).toBe('游戏UI图');
expect(items[0]?.previews.map((preview) => preview.label)).toEqual([
'游戏UI图',
'UI子素材图集',
'开始按钮',
]);
});
it('counts a spritesheet task only once when UI children share the same task', () => {
const items = buildCreationShowcaseItems({
activeTab: 'ui',
projectResources: [
createResource({
resourceId: 'ui-design-1',
label: '游戏UI图',
assetKind: 'ui-design',
model: 'gemini-3.1-flash-image-preview',
taskId: 'task-ui-design',
}),
createResource({
resourceId: 'icon-sheet-1',
label: 'UI子素材图集',
assetKind: 'icon-spritesheet',
model: 'gpt-image-2',
taskId: 'task-ui-extract',
generationInputs: {
fields: [{ title: '提取提示词', value: '提取UI子素材' }],
references: [
{
title: 'UI设计图',
label: '游戏UI图',
refType: 'project-resource',
refId: 'ui-design-1',
},
],
},
}),
createResource({
resourceId: 'icon-1',
label: '开始按钮',
assetKind: 'icon',
model: 'gpt-image-2',
taskId: 'task-ui-extract',
generationInputs: {
fields: [{ title: '提取提示词', value: '提取UI子素材' }],
references: [
{
title: 'UI设计图',
label: '游戏UI图',
refType: 'project-resource',
refId: 'ui-design-1',
},
],
},
}),
],
});
expect(items).toHaveLength(1);
expect(items[0]?.cost).toBe('15泥点');
});
it('keeps music assets as audio preview items', () => {
const items = buildCreationShowcaseItems({
activeTab: 'music',
projectResources: [
createResource({
resourceId: 'music-1',
label: '背景音乐',
imageSrc: '/generated-editor-audios/music.mp3',
assetKind: 'background-music',
prompt: '舒缓背景音乐',
priceMudPoints: '5',
}),
],
fallbackAuthorName: '当前用户',
});
expect(items).toHaveLength(1);
expect(items[0]?.previews[0]?.mediaType).toBe('audio');
expect(items[0]?.previews[0]?.coverSrc).toBe(
'/creation-home/audio-asset-cover.png',
);
expect(items[0]?.author).toBe('当前用户');
expect(items[0]?.cost).toBe('5泥点');
});
it('keeps character animation videos out of the marketing tab', () => {
const items = buildCreationShowcaseItems({
activeTab: 'marketing',
projectResources: [
createResource({
resourceId: 'animation-1',
label: '角色动画',
assetKind: 'character-animation',
imageSrc: '/generated-editor-videos/animation.mp4',
}),
createResource({
resourceId: 'poster-1',
label: '上线美宣视频',
assetKind: 'publication-material',
imageSrc: '/generated-editor-videos/poster.mp4',
}),
],
});
expect(items).toHaveLength(1);
expect(items[0]?.label).toBe('上线美宣视频');
expect(items[0]?.previews[0]?.mediaType).toBe('video');
});
it('infers costs from generated asset kinds when stored price is absent', () => {
const items = buildCreationShowcaseItems({
activeTab: 'characters',
projectResources: [
createResource({
resourceId: 'character-1',
label: '游戏角色图',
imageSrc: '/generated-editor-assets/character.png',
objectKey: 'generated-editor-assets/character.png',
assetKind: 'character',
model: 'gpt-image-2',
prompt: '角色提示词',
}),
createResource({
resourceId: 'animation-1',
label: '待机动画',
assetKind: 'character-animation',
imageSrc: '/generated-editor-videos/animation.mp4',
objectKey: 'generated-editor-videos/animation.mp4',
model: 'seedance2.0-fast',
durationSeconds: 6,
generationInputs: {
fields: [{ title: '清晰度', value: '720p' }],
references: [
{
title: '角色图片',
label: '游戏角色图',
refType: 'project-resource',
refId: 'character-1',
},
],
},
}),
],
});
expect(items).toHaveLength(1);
expect(items[0]?.cost).toBe('123泥点');
expect(items[0]?.previews[0]?.objectKey).toBe(
'generated-editor-assets/character.png',
);
});
});