Introduce persistent generationStatus to work summaries (puzzle & match3d) and propagate generation recovery rules across docs and frontend/backends so "generating" is restored from server-side work summary rather than ephemeral front-end notices. Update API server image/asset handling (improve match3d material sheet green/alpha decontamination and promote generatedItemAssets background fields) and add runtime improvements: alpha-based hotspot hit-testing, tray insertion/three-match animation behavior, and session re-read on client-side VectorEngine timeouts/lock-screen interruptions. Many docs, tests and related frontend modules updated/added to reflect these contract and behavior changes.
683 lines
21 KiB
TypeScript
683 lines
21 KiB
TypeScript
import { expect, test, vi } from 'vitest';
|
||
|
||
import type { BabyObjectMatchDraft } from '../../../packages/shared/src/contracts/edutainmentBabyObject';
|
||
import {
|
||
buildCreationWorkShelfItems,
|
||
getCreationWorkShelfItemTime,
|
||
} from './creationWorkShelf';
|
||
|
||
test('buildCreationWorkShelfItems maps visual novel items with VN public code', () => {
|
||
const items = buildCreationWorkShelfItems({
|
||
rpgItems: [],
|
||
bigFishItems: [],
|
||
puzzleItems: [],
|
||
visualNovelItems: [
|
||
{
|
||
runtimeKind: 'visual-novel',
|
||
profileId: 'vn-profile-demo-12345678',
|
||
ownerUserId: 'user-1',
|
||
title: '雨夜终章',
|
||
description: '失踪列车上的选择。',
|
||
coverImageSrc: '/vn-cover.png',
|
||
tags: ['悬疑', '列车'],
|
||
publishStatus: 'published',
|
||
publishReady: true,
|
||
playCount: 12,
|
||
updatedAt: '2026-05-07T00:00:00.000Z',
|
||
publishedAt: '2026-05-07T00:00:00.000Z',
|
||
},
|
||
{
|
||
runtimeKind: 'visual-novel',
|
||
profileId: 'vn-profile-draft-00000001',
|
||
ownerUserId: 'user-1',
|
||
title: '',
|
||
description: '',
|
||
coverImageSrc: null,
|
||
tags: [],
|
||
publishStatus: 'draft',
|
||
publishReady: false,
|
||
playCount: 0,
|
||
updatedAt: '2026-05-06T00:00:00.000Z',
|
||
publishedAt: null,
|
||
},
|
||
],
|
||
});
|
||
|
||
expect(items[0]?.kind).toBe('visual-novel');
|
||
expect(items[0]?.publicWorkCode).toBe('VN-12345678');
|
||
expect(items[0]?.sharePath).toContain('/works/detail?work=VN-12345678');
|
||
expect(items[1]?.status).toBe('draft');
|
||
expect(items[1]?.publicWorkCode).toBeNull();
|
||
});
|
||
|
||
test('buildCreationWorkShelfItems attaches open and delete actions through shelf adapters', () => {
|
||
const onOpenPuzzleDetail = vi.fn();
|
||
const onDeletePuzzle = vi.fn();
|
||
const puzzleWork = {
|
||
workId: 'puzzle:work-action',
|
||
profileId: 'puzzle-profile-action',
|
||
ownerUserId: 'user-1',
|
||
authorDisplayName: '测试作者',
|
||
levelName: '动作拼图',
|
||
summary: '验证作品架动作 Adapter。',
|
||
themeTags: [],
|
||
coverImageSrc: null,
|
||
publicationStatus: 'draft' as const,
|
||
updatedAt: '2026-05-08T00:00:00.000Z',
|
||
publishedAt: null,
|
||
playCount: 0,
|
||
remixCount: 0,
|
||
likeCount: 0,
|
||
publishReady: false,
|
||
};
|
||
|
||
const [item] = buildCreationWorkShelfItems({
|
||
rpgItems: [],
|
||
bigFishItems: [],
|
||
puzzleItems: [puzzleWork],
|
||
onOpenPuzzleDetail,
|
||
onDeletePuzzle,
|
||
});
|
||
|
||
item?.actions.open();
|
||
item?.actions.delete?.();
|
||
|
||
expect(onOpenPuzzleDetail).toHaveBeenCalledWith(puzzleWork);
|
||
expect(onDeletePuzzle).toHaveBeenCalledWith(puzzleWork);
|
||
});
|
||
|
||
test('buildCreationWorkShelfItems restores persisted generation state for puzzle and match3d drafts', () => {
|
||
const items = buildCreationWorkShelfItems({
|
||
rpgItems: [],
|
||
bigFishItems: [],
|
||
puzzleItems: [
|
||
{
|
||
workId: 'puzzle:generating',
|
||
profileId: 'puzzle-profile-generating',
|
||
ownerUserId: 'user-1',
|
||
sourceSessionId: 'puzzle-session-generating',
|
||
authorDisplayName: '测试作者',
|
||
levelName: '生成中拼图',
|
||
summary: '退出产品后仍应显示生成中。',
|
||
themeTags: [],
|
||
coverImageSrc: null,
|
||
publicationStatus: 'draft',
|
||
updatedAt: '2026-05-08T00:00:00.000Z',
|
||
publishedAt: null,
|
||
publishReady: false,
|
||
generationStatus: 'generating',
|
||
},
|
||
],
|
||
match3dItems: [
|
||
{
|
||
workId: 'match3d:generating',
|
||
profileId: 'match3d-profile-generating',
|
||
ownerUserId: 'user-1',
|
||
sourceSessionId: 'match3d-session-generating',
|
||
gameName: '生成中抓鹅',
|
||
themeText: '糖果厨房',
|
||
summary: '退出产品后仍应显示生成中。',
|
||
tags: [],
|
||
coverImageSrc: null,
|
||
clearCount: 18,
|
||
difficulty: 1,
|
||
publicationStatus: 'draft',
|
||
playCount: 0,
|
||
updatedAt: '2026-05-07T00:00:00.000Z',
|
||
publishReady: false,
|
||
generationStatus: 'generating',
|
||
},
|
||
],
|
||
});
|
||
|
||
expect(items.find((item) => item.kind === 'puzzle')?.isGenerating).toBe(
|
||
true,
|
||
);
|
||
expect(items.find((item) => item.kind === 'match3d')?.isGenerating).toBe(
|
||
true,
|
||
);
|
||
});
|
||
|
||
test('buildCreationWorkShelfItems maps baby object match local drafts', () => {
|
||
const onOpenBabyObjectMatchDetail = vi.fn();
|
||
const onDeleteBabyObjectMatch = vi.fn();
|
||
const baseDraft: BabyObjectMatchDraft = {
|
||
draftId: 'baby-object-draft-1',
|
||
profileId: 'baby-object-profile-12345678',
|
||
templateId: 'baby-object-match',
|
||
templateName: '宝贝识物',
|
||
workTitle: '宝贝识物',
|
||
workDescription: '苹果和香蕉识物分类',
|
||
itemNames: ['苹果', '香蕉'],
|
||
itemAssets: [
|
||
{
|
||
itemId: 'baby-object-item-1',
|
||
itemName: '苹果',
|
||
imageSrc: '/apple.png',
|
||
assetObjectId: null,
|
||
generationProvider: 'placeholder',
|
||
prompt: '苹果',
|
||
},
|
||
{
|
||
itemId: 'baby-object-item-2',
|
||
itemName: '香蕉',
|
||
imageSrc: '/banana.png',
|
||
assetObjectId: null,
|
||
generationProvider: 'placeholder',
|
||
prompt: '香蕉',
|
||
},
|
||
],
|
||
visualPackage: null,
|
||
themeTags: ['寓教于乐'],
|
||
publicationStatus: 'draft',
|
||
createdAt: '2026-05-11T00:00:00.000Z',
|
||
updatedAt: '2026-05-11T00:00:00.000Z',
|
||
publishedAt: null,
|
||
};
|
||
|
||
const items = buildCreationWorkShelfItems({
|
||
rpgItems: [],
|
||
bigFishItems: [],
|
||
puzzleItems: [],
|
||
babyObjectMatchItems: [
|
||
baseDraft,
|
||
{
|
||
...baseDraft,
|
||
draftId: 'baby-object-draft-2',
|
||
profileId: 'baby-object-profile-87654321',
|
||
publicationStatus: 'published',
|
||
publishedAt: '2026-05-11T01:00:00.000Z',
|
||
updatedAt: '2026-05-11T01:00:00.000Z',
|
||
},
|
||
],
|
||
canDeleteBabyObjectMatch: true,
|
||
onOpenBabyObjectMatchDetail,
|
||
onDeleteBabyObjectMatch,
|
||
});
|
||
|
||
items[1]?.actions.open();
|
||
items[1]?.actions.delete?.();
|
||
|
||
expect(items[0]?.kind).toBe('baby-object-match');
|
||
expect(items[0]?.status).toBe('published');
|
||
expect(items[0]?.publicWorkCode).toBe('BO-87654321');
|
||
expect(items[0]?.sharePath).toContain('/works/detail?work=BO-87654321');
|
||
expect(items[1]?.status).toBe('draft');
|
||
expect(items[1]?.publicWorkCode).toBeNull();
|
||
expect(items[1]?.canDelete).toBe(true);
|
||
expect(onOpenBabyObjectMatchDetail).toHaveBeenCalledWith(baseDraft);
|
||
expect(onDeleteBabyObjectMatch).toHaveBeenCalledWith(baseDraft);
|
||
});
|
||
|
||
test('buildCreationWorkShelfItems sorts works by latest updatedAt across timestamp formats', () => {
|
||
const items = buildCreationWorkShelfItems({
|
||
rpgItems: [],
|
||
bigFishItems: [],
|
||
puzzleItems: [
|
||
{
|
||
workId: 'puzzle:older',
|
||
profileId: 'puzzle-profile-older',
|
||
ownerUserId: 'user-1',
|
||
authorDisplayName: '测试作者',
|
||
levelName: '旧草稿',
|
||
summary: '较早修改。',
|
||
themeTags: [],
|
||
coverImageSrc: null,
|
||
publicationStatus: 'draft',
|
||
updatedAt: '2026-05-07T00:00:00.000Z',
|
||
publishedAt: null,
|
||
publishReady: false,
|
||
},
|
||
{
|
||
workId: 'puzzle:newer',
|
||
profileId: 'puzzle-profile-newer',
|
||
ownerUserId: 'user-1',
|
||
authorDisplayName: '测试作者',
|
||
levelName: '新草稿',
|
||
summary: '较晚修改。',
|
||
themeTags: [],
|
||
coverImageSrc: null,
|
||
publicationStatus: 'draft',
|
||
updatedAt: '1778457601.234567Z',
|
||
publishedAt: null,
|
||
publishReady: false,
|
||
},
|
||
],
|
||
});
|
||
|
||
expect(items.map((item) => item.id)).toEqual([
|
||
'puzzle:newer',
|
||
'puzzle:older',
|
||
]);
|
||
});
|
||
|
||
test('buildCreationWorkShelfItems falls back to available gameplay images as covers', () => {
|
||
const items = buildCreationWorkShelfItems({
|
||
rpgItems: [],
|
||
bigFishItems: [],
|
||
puzzleItems: [
|
||
{
|
||
workId: 'puzzle:level-cover',
|
||
profileId: 'puzzle-profile-level-cover',
|
||
ownerUserId: 'user-1',
|
||
authorDisplayName: '测试作者',
|
||
levelName: '关卡封面拼图',
|
||
summary: '作品自身封面为空时使用关卡正式图。',
|
||
themeTags: [],
|
||
coverImageSrc: null,
|
||
publicationStatus: 'draft',
|
||
updatedAt: '2026-05-08T00:00:00.000Z',
|
||
publishedAt: null,
|
||
publishReady: false,
|
||
levels: [
|
||
{
|
||
levelId: 'level-1',
|
||
levelName: '第一关',
|
||
pictureDescription: '港口雨夜。',
|
||
candidates: [
|
||
{
|
||
candidateId: 'candidate-1',
|
||
imageSrc: '/puzzle-candidate.png',
|
||
assetId: 'asset-1',
|
||
prompt: '港口雨夜',
|
||
sourceType: 'generated',
|
||
selected: true,
|
||
},
|
||
],
|
||
selectedCandidateId: 'candidate-1',
|
||
coverImageSrc: null,
|
||
coverAssetId: null,
|
||
generationStatus: 'ready',
|
||
},
|
||
],
|
||
},
|
||
],
|
||
match3dItems: [
|
||
{
|
||
workId: 'match3d:asset-cover',
|
||
profileId: 'match3d-profile-asset-cover',
|
||
ownerUserId: 'user-1',
|
||
gameName: '素材封面抓鹅',
|
||
themeText: '糖果厨房',
|
||
summary: '作品自身封面为空时使用素材图。',
|
||
tags: [],
|
||
coverImageSrc: null,
|
||
clearCount: 18,
|
||
difficulty: 1,
|
||
publicationStatus: 'draft',
|
||
playCount: 0,
|
||
updatedAt: '2026-05-07T00:00:00.000Z',
|
||
publishReady: false,
|
||
generatedItemAssets: [
|
||
{
|
||
itemId: 'item-1',
|
||
itemName: '糖果',
|
||
imageSrc: '/match3d-item.png',
|
||
status: 'image_ready',
|
||
},
|
||
],
|
||
},
|
||
],
|
||
squareHoleItems: [
|
||
{
|
||
workId: 'square-hole:background-cover',
|
||
profileId: 'square-hole-profile-background-cover',
|
||
ownerUserId: 'user-1',
|
||
gameName: '背景封面方洞',
|
||
themeText: '星空玩具箱',
|
||
twistRule: '旋转洞口',
|
||
summary: '作品自身封面为空时使用背景图。',
|
||
tags: [],
|
||
coverImageSrc: null,
|
||
backgroundPrompt: '星空玩具箱',
|
||
backgroundImageSrc: '/square-hole-background.png',
|
||
shapeOptions: [],
|
||
holeOptions: [],
|
||
shapeCount: 3,
|
||
difficulty: 1,
|
||
publicationStatus: 'draft',
|
||
playCount: 0,
|
||
updatedAt: '2026-05-06T00:00:00.000Z',
|
||
publishReady: false,
|
||
},
|
||
],
|
||
});
|
||
|
||
expect(items.find((item) => item.kind === 'puzzle')?.coverImageSrc).toBe(
|
||
'/puzzle-candidate.png',
|
||
);
|
||
expect(items.find((item) => item.kind === 'match3d')?.coverImageSrc).toBe(
|
||
'/match3d-item.png',
|
||
);
|
||
expect(items.find((item) => item.kind === 'square-hole')?.coverImageSrc).toBe(
|
||
'/square-hole-background.png',
|
||
);
|
||
});
|
||
|
||
test('buildCreationWorkShelfItems uses generated object keys as cover sources', () => {
|
||
const items = buildCreationWorkShelfItems({
|
||
rpgItems: [],
|
||
bigFishItems: [],
|
||
puzzleItems: [
|
||
{
|
||
workId: 'puzzle:level-object-key',
|
||
profileId: 'puzzle-profile-level-object-key',
|
||
ownerUserId: 'user-1',
|
||
authorDisplayName: '测试作者',
|
||
levelName: '关卡对象拼图',
|
||
summary: '作品摘要带关卡图对象路径时用关卡图做卡片背景。',
|
||
themeTags: [],
|
||
coverImageSrc: null,
|
||
publicationStatus: 'draft',
|
||
updatedAt: '2026-05-08T00:00:00.000Z',
|
||
publishedAt: null,
|
||
publishReady: false,
|
||
levels: [
|
||
{
|
||
levelId: 'level-1',
|
||
levelName: '第一关',
|
||
pictureDescription: '港口雨夜。',
|
||
candidates: [
|
||
{
|
||
candidateId: 'candidate-1',
|
||
imageSrc: '',
|
||
assetId: 'asset-1',
|
||
prompt: '港口雨夜',
|
||
sourceType: 'generated',
|
||
selected: true,
|
||
},
|
||
],
|
||
selectedCandidateId: 'candidate-1',
|
||
coverImageSrc:
|
||
'generated-puzzle-assets/session/profile/level-cover.png',
|
||
coverAssetId: null,
|
||
generationStatus: 'ready',
|
||
},
|
||
],
|
||
},
|
||
],
|
||
match3dItems: [
|
||
{
|
||
workId: 'match3d:object-key-cover',
|
||
profileId: 'match3d-profile-object-key-cover',
|
||
ownerUserId: 'user-1',
|
||
gameName: '对象路径抓鹅',
|
||
themeText: '糖果厨房',
|
||
summary: '背景图或物品图只有 object key 时也应展示。',
|
||
tags: [],
|
||
coverImageSrc: null,
|
||
clearCount: 18,
|
||
difficulty: 1,
|
||
publicationStatus: 'draft',
|
||
playCount: 0,
|
||
updatedAt: '2026-05-07T00:00:00.000Z',
|
||
publishReady: false,
|
||
generatedBackgroundAsset: {
|
||
prompt: '糖果厨房背景',
|
||
imageObjectKey:
|
||
'generated-match3d-assets/session/profile/background/image.png',
|
||
containerImageObjectKey:
|
||
'generated-match3d-assets/session/profile/background/container.png',
|
||
status: 'ready',
|
||
},
|
||
generatedItemAssets: [
|
||
{
|
||
itemId: 'item-1',
|
||
itemName: '糖果',
|
||
imageObjectKey:
|
||
'generated-match3d-assets/session/profile/items/item-1/image.png',
|
||
imageViews: [
|
||
{
|
||
viewId: 'view-1',
|
||
viewIndex: 1,
|
||
imageObjectKey:
|
||
'generated-match3d-assets/session/profile/items/item-1/views/view-1.png',
|
||
},
|
||
],
|
||
status: 'image_ready',
|
||
},
|
||
],
|
||
},
|
||
],
|
||
});
|
||
|
||
expect(items.find((item) => item.kind === 'puzzle')?.coverImageSrc).toBe(
|
||
'generated-puzzle-assets/session/profile/level-cover.png',
|
||
);
|
||
expect(items.find((item) => item.kind === 'match3d')?.coverImageSrc).toBe(
|
||
'generated-match3d-assets/session/profile/background/container.png',
|
||
);
|
||
});
|
||
|
||
test('buildCreationWorkShelfItems falls back to match3d item object key without background', () => {
|
||
const items = buildCreationWorkShelfItems({
|
||
rpgItems: [],
|
||
bigFishItems: [],
|
||
puzzleItems: [],
|
||
match3dItems: [
|
||
{
|
||
workId: 'match3d:item-object-key-cover',
|
||
profileId: 'match3d-profile-item-object-key-cover',
|
||
ownerUserId: 'user-1',
|
||
gameName: '物品对象路径抓鹅',
|
||
themeText: '糖果厨房',
|
||
summary: '背景图缺失时用物品视角图对象路径。',
|
||
tags: [],
|
||
coverImageSrc: null,
|
||
clearCount: 18,
|
||
difficulty: 1,
|
||
publicationStatus: 'draft',
|
||
playCount: 0,
|
||
updatedAt: '2026-05-07T00:00:00.000Z',
|
||
publishReady: false,
|
||
generatedItemAssets: [
|
||
{
|
||
itemId: 'item-1',
|
||
itemName: '糖果',
|
||
imageObjectKey:
|
||
'generated-match3d-assets/session/profile/items/item-1/image.png',
|
||
imageViews: [
|
||
{
|
||
viewId: 'view-1',
|
||
viewIndex: 1,
|
||
imageObjectKey:
|
||
'generated-match3d-assets/session/profile/items/item-1/views/view-1.png',
|
||
},
|
||
],
|
||
status: 'image_ready',
|
||
},
|
||
],
|
||
},
|
||
],
|
||
});
|
||
|
||
expect(items.find((item) => item.kind === 'match3d')?.coverImageSrc).toBe(
|
||
'generated-match3d-assets/session/profile/items/item-1/views/view-1.png',
|
||
);
|
||
});
|
||
|
||
test('buildCreationWorkShelfItems ignores puzzle theme reference cover and uses first level image', () => {
|
||
const items = buildCreationWorkShelfItems({
|
||
rpgItems: [],
|
||
bigFishItems: [],
|
||
puzzleItems: [
|
||
{
|
||
workId: 'puzzle:theme-reference-cover',
|
||
profileId: 'puzzle-profile-theme-reference-cover',
|
||
ownerUserId: 'user-1',
|
||
authorDisplayName: '测试作者',
|
||
levelName: '主题兜底拼图',
|
||
summary: '摘要里的封面是玩法参考图时,用第一关画面兜底。',
|
||
themeTags: [],
|
||
coverImageSrc: '/creation-type-references/puzzle.webp',
|
||
publicationStatus: 'draft',
|
||
updatedAt: '2026-05-08T00:00:00.000Z',
|
||
publishedAt: null,
|
||
publishReady: false,
|
||
levels: [
|
||
{
|
||
levelId: 'level-1',
|
||
levelName: '第一关',
|
||
pictureDescription: '第一关画面。',
|
||
candidates: [
|
||
{
|
||
candidateId: 'candidate-1',
|
||
imageSrc: '/puzzle-first-level-candidate.png',
|
||
assetId: 'asset-1',
|
||
prompt: '第一关画面',
|
||
sourceType: 'generated',
|
||
selected: true,
|
||
},
|
||
],
|
||
selectedCandidateId: 'candidate-1',
|
||
coverImageSrc: '/puzzle-first-level-cover.png',
|
||
coverAssetId: 'asset-1',
|
||
generationStatus: 'ready',
|
||
},
|
||
],
|
||
},
|
||
],
|
||
});
|
||
|
||
expect(items.find((item) => item.kind === 'puzzle')?.coverImageSrc).toBe(
|
||
'/puzzle-first-level-cover.png',
|
||
);
|
||
});
|
||
|
||
test('buildCreationWorkShelfItems ignores match3d theme reference cover and uses container image', () => {
|
||
const items = buildCreationWorkShelfItems({
|
||
rpgItems: [],
|
||
bigFishItems: [],
|
||
puzzleItems: [],
|
||
match3dItems: [
|
||
{
|
||
workId: 'match3d:theme-reference-cover',
|
||
profileId: 'match3d-profile-theme-reference-cover',
|
||
ownerUserId: 'user-1',
|
||
gameName: '主题兜底抓鹅',
|
||
themeText: '糖果厨房',
|
||
summary: '摘要里的封面是玩法参考图时,用UI背景图兜底。',
|
||
tags: [],
|
||
coverImageSrc: '/creation-type-references/match3d.webp',
|
||
clearCount: 18,
|
||
difficulty: 1,
|
||
publicationStatus: 'draft',
|
||
playCount: 0,
|
||
updatedAt: '2026-05-07T00:00:00.000Z',
|
||
publishReady: false,
|
||
generatedBackgroundAsset: {
|
||
prompt: '糖果厨房竖屏UI背景',
|
||
imageSrc: '/match3d-ui-background.png',
|
||
containerImageSrc: '/match3d-container.png',
|
||
status: 'image_ready',
|
||
},
|
||
generatedItemAssets: [
|
||
{
|
||
itemId: 'item-1',
|
||
itemName: '糖果',
|
||
imageSrc: '/match3d-item.png',
|
||
status: 'image_ready',
|
||
},
|
||
],
|
||
},
|
||
],
|
||
});
|
||
|
||
expect(items.find((item) => item.kind === 'match3d')?.coverImageSrc).toBe(
|
||
'/match3d-container.png',
|
||
);
|
||
});
|
||
|
||
test('buildCreationWorkShelfItems uses match3d container asset before background and item image', () => {
|
||
const items = buildCreationWorkShelfItems({
|
||
rpgItems: [],
|
||
bigFishItems: [],
|
||
puzzleItems: [],
|
||
match3dItems: [
|
||
{
|
||
workId: 'match3d:item-background-asset-cover',
|
||
profileId: 'match3d-profile-item-background-asset-cover',
|
||
ownerUserId: 'user-1',
|
||
gameName: '背景资产抓鹅',
|
||
themeText: '糖果厨房',
|
||
summary: '顶层背景缺失时,从素材携带的UI背景兜底。',
|
||
tags: [],
|
||
coverImageSrc: '/creation-type-references/match3d.webp',
|
||
clearCount: 18,
|
||
difficulty: 1,
|
||
publicationStatus: 'draft',
|
||
playCount: 0,
|
||
updatedAt: '2026-05-07T00:00:00.000Z',
|
||
publishReady: false,
|
||
generatedItemAssets: [
|
||
{
|
||
itemId: 'item-1',
|
||
itemName: '糖果',
|
||
imageSrc: '/match3d-item.png',
|
||
backgroundAsset: {
|
||
prompt: '糖果厨房竖屏UI背景',
|
||
imageObjectKey:
|
||
'generated-match3d-assets/session/profile/background/image.png',
|
||
containerImageObjectKey:
|
||
'generated-match3d-assets/session/profile/ui-container/container.png',
|
||
status: 'image_ready',
|
||
},
|
||
status: 'image_ready',
|
||
},
|
||
],
|
||
},
|
||
],
|
||
});
|
||
|
||
expect(items.find((item) => item.kind === 'match3d')?.coverImageSrc).toBe(
|
||
'generated-match3d-assets/session/profile/ui-container/container.png',
|
||
);
|
||
});
|
||
|
||
test('buildCreationWorkShelfItems uses match3d transparent container reference as last fallback', () => {
|
||
const items = buildCreationWorkShelfItems({
|
||
rpgItems: [],
|
||
bigFishItems: [],
|
||
puzzleItems: [],
|
||
match3dItems: [
|
||
{
|
||
workId: 'match3d:container-reference-fallback',
|
||
profileId: 'match3d-profile-container-reference-fallback',
|
||
ownerUserId: 'user-1',
|
||
sourceSessionId: 'session-1',
|
||
gameName: '水果抓大鹅',
|
||
themeText: '水果',
|
||
summary: '',
|
||
tags: [],
|
||
coverImageSrc: null,
|
||
referenceImageSrc: null,
|
||
backgroundPrompt: '',
|
||
backgroundImageSrc: null,
|
||
backgroundImageObjectKey: null,
|
||
generatedBackgroundAsset: null,
|
||
generatedItemAssets: [],
|
||
clearCount: 3,
|
||
difficulty: 2,
|
||
publicationStatus: 'draft',
|
||
publishReady: false,
|
||
playCount: 0,
|
||
updatedAt: '2026-05-01T00:00:00.000Z',
|
||
publishedAt: null,
|
||
},
|
||
],
|
||
});
|
||
|
||
expect(items.find((item) => item.kind === 'match3d')?.coverImageSrc).toBe(
|
||
'/match3d-background-references/pot-fused-reference.png',
|
||
);
|
||
});
|
||
|
||
test('getCreationWorkShelfItemTime parses backend seconds.microsZ values', () => {
|
||
expect(getCreationWorkShelfItemTime('1778457601.234567Z')).toBe(
|
||
1778457601234.567,
|
||
);
|
||
expect(getCreationWorkShelfItemTime('2026-05-07T00:00:00.000Z')).toBe(
|
||
new Date('2026-05-07T00:00:00.000Z').getTime(),
|
||
);
|
||
});
|