fix: polish bark battle creation flow

This commit is contained in:
kdletters
2026-05-22 05:00:07 +08:00
parent 01da85a577
commit bf82f04b64
73 changed files with 9362 additions and 2663 deletions

View File

@@ -1,10 +1,16 @@
import { createElement } from 'react';
import { renderToStaticMarkup } from 'react-dom/server';
import { expect, test, vi } from 'vitest';
import type { BabyObjectMatchDraft } from '../../../packages/shared/src/contracts/edutainmentBabyObject';
import {
buildCreationWorkShelfItems,
getCreationWorkShelfItemTime,
hasBarkBattleRequiredImages,
isPersistedBarkBattleDraftGenerating,
type CreationWorkShelfItem,
} from './creationWorkShelf';
import { CustomWorldWorkCard } from './CustomWorldWorkCard';
test('buildCreationWorkShelfItems maps visual novel items with VN public code', () => {
const items = buildCreationWorkShelfItems({
@@ -50,6 +56,253 @@ test('buildCreationWorkShelfItems maps visual novel items with VN public code',
expect(items[1]?.publicWorkCode).toBeNull();
});
test('buildCreationWorkShelfItems keeps published bark battle over duplicate draft', () => {
const items = buildCreationWorkShelfItems({
rpgItems: [],
bigFishItems: [],
puzzleItems: [],
barkBattleItems: [
{
workId: 'bark-battle-work-1',
draftId: 'bark-battle-draft-1',
ownerUserId: 'user-1',
authorDisplayName: '草稿作者',
title: '汪汪测试杯',
summary: '',
themeDescription: '阳光草坪声浪竞技场',
playerImageDescription: '戴红色围巾的柯基选手',
opponentImageDescription: '蓝色护目镜哈士奇对手',
playerCharacterImageSrc: '/generated-bark-battle/player.png',
opponentCharacterImageSrc: '/generated-bark-battle/opponent.png',
uiBackgroundImageSrc: '/generated-bark-battle/background.png',
difficultyPreset: 'normal',
status: 'draft',
generationStatus: 'ready',
publishReady: true,
playCount: 0,
updatedAt: '2026-05-14T10:01:00.000Z',
publishedAt: null,
},
{
workId: 'bark-battle-work-1',
draftId: 'bark-battle-draft-1',
ownerUserId: 'user-1',
authorDisplayName: '测试玩家',
title: '汪汪测试杯',
summary: '',
themeDescription: '阳光草坪声浪竞技场',
playerImageDescription: '戴红色围巾的柯基选手',
opponentImageDescription: '蓝色护目镜哈士奇对手',
playerCharacterImageSrc: '/generated-bark-battle/player.png',
opponentCharacterImageSrc: '/generated-bark-battle/opponent.png',
uiBackgroundImageSrc: '/generated-bark-battle/background.png',
difficultyPreset: 'normal',
status: 'published',
generationStatus: 'ready',
publishReady: true,
playCount: 0,
updatedAt: '2026-05-14T10:02:00.000Z',
publishedAt: '2026-05-14T10:02:00.000Z',
},
],
});
expect(items).toHaveLength(1);
expect(items[0]?.kind).toBe('bark-battle');
expect(items[0]?.status).toBe('published');
expect(items[0]?.publicWorkCode).toBe('BB-TLEWORK1');
expect(items[0]?.authorDisplayName).toBe('测试玩家');
});
test('buildCreationWorkShelfItems keeps separate bark battle draft and published works visible', () => {
const items = buildCreationWorkShelfItems({
rpgItems: [],
bigFishItems: [],
puzzleItems: [],
barkBattleItems: [
{
workId: 'BB-DRAFT001',
draftId: 'bark-battle-draft-visible',
ownerUserId: 'user-1',
authorDisplayName: '草稿作者',
title: '草稿声浪赛',
summary: '',
themeDescription: '草地声浪挑战',
playerImageDescription: '柯基选手',
opponentImageDescription: '哈士奇对手',
playerCharacterImageSrc: '/draft-player.png',
opponentCharacterImageSrc: '/draft-opponent.png',
uiBackgroundImageSrc: '/draft-background.png',
difficultyPreset: 'easy',
status: 'draft',
generationStatus: 'ready',
publishReady: false,
playCount: 0,
updatedAt: '2026-05-20T00:00:00.000Z',
publishedAt: null,
},
{
workId: 'BB-PUB00001',
draftId: 'bark-battle-draft-published',
ownerUserId: 'user-1',
authorDisplayName: '发布作者',
title: '已发布声浪赛',
summary: '',
themeDescription: '霓虹声浪挑战',
playerImageDescription: '柴犬选手',
opponentImageDescription: '机器人对手',
playerCharacterImageSrc: '/published-player.png',
opponentCharacterImageSrc: '/published-opponent.png',
uiBackgroundImageSrc: '/published-background.png',
difficultyPreset: 'normal',
status: 'published',
generationStatus: 'ready',
publishReady: true,
playCount: 3,
updatedAt: '2026-05-21T00:00:00.000Z',
publishedAt: '2026-05-21T00:00:00.000Z',
},
],
});
expect(items).toHaveLength(2);
expect(items.find((item) => item.status === 'draft')?.id).toBe('BB-DRAFT001');
expect(items.find((item) => item.status === 'published')?.id).toBe(
'BB-PUB00001',
);
expect(items.find((item) => item.status === 'published')?.publicWorkCode).toBe(
'BB-PUB00001',
);
});
test('buildCreationWorkShelfItems gives bark battle draft cover from character or reference fallback', () => {
const items = buildCreationWorkShelfItems({
rpgItems: [],
bigFishItems: [],
puzzleItems: [],
barkBattleItems: [
{
workId: 'BB-COVER001',
draftId: 'bark-battle-draft-cover',
ownerUserId: 'user-1',
authorDisplayName: '草稿作者',
title: '角色封面声浪赛',
summary: '',
themeDescription: '草地声浪挑战',
playerImageDescription: '柯基选手',
opponentImageDescription: '哈士奇对手',
playerCharacterImageSrc: '/draft-player-cover.png',
opponentCharacterImageSrc: '/draft-opponent-cover.png',
uiBackgroundImageSrc: null,
difficultyPreset: 'easy',
status: 'draft',
generationStatus: 'partial_failed',
publishReady: false,
playCount: 0,
updatedAt: '2026-05-20T00:00:00.000Z',
publishedAt: null,
},
{
workId: 'BB-COVER002',
draftId: 'bark-battle-draft-cover-fallback',
ownerUserId: 'user-1',
authorDisplayName: '草稿作者',
title: '默认封面声浪赛',
summary: '',
themeDescription: '夜市声浪挑战',
playerImageDescription: '柴犬选手',
opponentImageDescription: '机器人对手',
playerCharacterImageSrc: null,
opponentCharacterImageSrc: null,
uiBackgroundImageSrc: null,
difficultyPreset: 'normal',
status: 'draft',
generationStatus: 'pending_assets',
publishReady: false,
playCount: 0,
updatedAt: '2026-05-19T00:00:00.000Z',
publishedAt: null,
},
],
});
expect(items.find((item) => item.id === 'BB-COVER001')?.coverImageSrc).toBe(
'/draft-player-cover.png',
);
expect(items.find((item) => item.id === 'BB-COVER001')?.coverCharacterImageSrcs).toEqual([
'/draft-player-cover.png',
'/draft-opponent-cover.png',
]);
expect(items.find((item) => item.id === 'BB-COVER002')?.coverImageSrc).toBe(
'/creation-type-references/bark-battle.webp',
);
});
test('buildCreationWorkShelfItems keeps bark battle draft author display name', () => {
const items = buildCreationWorkShelfItems({
rpgItems: [],
bigFishItems: [],
puzzleItems: [],
barkBattleItems: [
{
workId: 'bark-battle-work-draft-author',
draftId: 'bark-battle-draft-author',
ownerUserId: 'user-1',
authorDisplayName: '草稿作者',
title: '草稿声浪赛',
summary: '',
themeDescription: '草地声浪挑战',
playerImageDescription: '柯基选手',
opponentImageDescription: '哈士奇对手',
playerCharacterImageSrc: '/player.png',
opponentCharacterImageSrc: '/opponent.png',
uiBackgroundImageSrc: '/background.png',
difficultyPreset: 'easy',
status: 'draft',
generationStatus: 'ready',
publishReady: false,
playCount: 0,
updatedAt: '2026-05-20T00:00:00.000Z',
publishedAt: null,
},
],
});
expect(items[0]?.kind).toBe('bark-battle');
expect(items[0]?.status).toBe('draft');
expect(items[0]?.authorDisplayName).toBe('草稿作者');
});
test('buildCreationWorkShelfItems falls back unknown authors to player label', () => {
const items = buildCreationWorkShelfItems({
rpgItems: [],
bigFishItems: [],
puzzleItems: [],
match3dItems: [
{
workId: 'match3d-work-author-fallback',
profileId: 'match3d-profile-author-fallback',
ownerUserId: 'user-1',
gameName: '水果抓大鹅',
themeText: '水果',
summary: '把水果从透明罐里抓出来。',
tags: [],
coverImageSrc: null,
clearCount: 0,
difficulty: 1,
publicationStatus: 'published',
playCount: 0,
updatedAt: '2026-05-20T00:00:00.000Z',
publishedAt: '2026-05-20T00:00:00.000Z',
publishReady: true,
},
],
});
expect(items[0]?.kind).toBe('match3d');
expect(items[0]?.authorDisplayName).toBe('玩家');
});
test('buildCreationWorkShelfItems attaches open and delete actions through shelf adapters', () => {
const onOpenPuzzleDetail = vi.fn();
const onDeletePuzzle = vi.fn();
@@ -672,6 +925,159 @@ test('buildCreationWorkShelfItems uses match3d transparent container reference a
);
});
test('buildCreationWorkShelfItems maps bark battle works with scene role cover and BB code', () => {
const onOpenBarkBattleDetail = vi.fn();
const items = buildCreationWorkShelfItems({
rpgItems: [],
bigFishItems: [],
puzzleItems: [],
barkBattleItems: [
{
workId: 'bark-battle-work-12345678',
draftId: 'bark-battle-draft-1',
ownerUserId: 'user-1',
authorDisplayName: '玩家',
title: '公园声浪赛',
summary: '柯基和哈士奇比拼声浪。',
themeDescription: '傍晚公园擂台',
playerImageDescription: '红围巾柯基',
opponentImageDescription: '蓝头带哈士奇',
playerCharacterImageSrc: '/generated-bark-battle/player.png',
opponentCharacterImageSrc: '/generated-bark-battle/opponent.png',
uiBackgroundImageSrc: '/generated-bark-battle/background.png',
difficultyPreset: 'normal',
status: 'published',
generationStatus: 'ready',
publishReady: true,
playCount: 6,
updatedAt: '2026-05-20T00:00:00.000Z',
publishedAt: '2026-05-20T00:00:00.000Z',
},
],
onOpenBarkBattleDetail,
});
const item = items[0];
item?.actions.open();
expect(item?.kind).toBe('bark-battle');
expect(item?.publicWorkCode).toBe('BB-12345678');
expect(item?.sharePath).toContain('/works/detail?work=BB-12345678');
expect(item?.coverImageSrc).toBe('/generated-bark-battle/background.png');
expect(item?.coverRenderMode).toBe('scene_with_roles');
expect(item?.coverCharacterImageSrcs).toEqual([
'/generated-bark-battle/player.png',
'/generated-bark-battle/opponent.png',
]);
expect(onOpenBarkBattleDetail).toHaveBeenCalledWith(
expect.objectContaining({ workId: 'bark-battle-work-12345678' }),
);
});
test('bark battle draft generating state follows pending assets or missing three images', () => {
const draft = {
workId: 'bark-battle-work-draft',
draftId: 'bark-battle-draft-1',
ownerUserId: 'user-1',
authorDisplayName: '玩家',
title: '草稿声浪赛',
summary: '',
themeDescription: '草地',
playerImageDescription: '柯基',
opponentImageDescription: '哈士奇',
playerCharacterImageSrc: '/player.png',
opponentCharacterImageSrc: null,
uiBackgroundImageSrc: '/background.png',
difficultyPreset: 'easy' as const,
status: 'draft' as const,
generationStatus: 'pending_assets',
publishReady: false,
playCount: 0,
updatedAt: '2026-05-20T00:00:00.000Z',
publishedAt: null,
};
expect(hasBarkBattleRequiredImages(draft)).toBe(false);
expect(isPersistedBarkBattleDraftGenerating(draft)).toBe(true);
expect(
isPersistedBarkBattleDraftGenerating({
...draft,
opponentCharacterImageSrc: '/opponent.png',
generationStatus: 'ready',
}),
).toBe(false);
});
test('CustomWorldWorkCard renders author for draft and published works', () => {
const buildItem = (
status: CreationWorkShelfItem['status'],
authorDisplayName: string,
): CreationWorkShelfItem => ({
id: `card-${status}`,
kind: 'bark-battle',
status,
authorDisplayName,
title: status === 'draft' ? '草稿声浪赛' : '发布声浪赛',
summary: '一场轻快的汪汪声浪对决。',
updatedAt: '2026-05-20T00:00:00.000Z',
coverImageSrc: null,
coverRenderMode: 'image',
coverCharacterImageSrcs: [],
publicWorkCode: null,
sharePath: null,
openActionLabel: status === 'draft' ? '继续创作' : '查看详情',
canDelete: false,
canShare: false,
badges: [
{ id: 'status', label: status === 'draft' ? '草稿' : '已发布', tone: 'neutral' },
{ id: 'type', label: '汪汪', tone: 'neutral' },
],
metrics: [],
actions: { open: () => {} },
source: {
kind: 'bark-battle',
item: {
workId: `bark-battle-${status}`,
draftId: `draft-${status}`,
ownerUserId: 'user-1',
authorDisplayName,
title: status === 'draft' ? '草稿声浪赛' : '发布声浪赛',
summary: '一场轻快的汪汪声浪对决。',
themeDescription: '公园舞台',
playerImageDescription: '柯基选手',
opponentImageDescription: '哈士奇对手',
playerCharacterImageSrc: null,
opponentCharacterImageSrc: null,
uiBackgroundImageSrc: null,
difficultyPreset: 'normal',
status,
generationStatus: 'ready',
publishReady: status === 'published',
playCount: 0,
updatedAt: '2026-05-20T00:00:00.000Z',
publishedAt: status === 'published' ? '2026-05-20T00:00:00.000Z' : null,
},
},
});
const draftHtml = renderToStaticMarkup(
createElement(CustomWorldWorkCard, {
item: buildItem('draft', '草稿作者'),
onOpen: () => {},
}),
);
const publishedHtml = renderToStaticMarkup(
createElement(CustomWorldWorkCard, {
item: buildItem('published', '发布作者'),
onOpen: () => {},
}),
);
expect(draftHtml).toContain('作者:草稿作者');
expect(publishedHtml).toContain('作者:发布作者');
});
test('getCreationWorkShelfItemTime parses backend seconds.microsZ values', () => {
expect(getCreationWorkShelfItemTime('1778457601.234567Z')).toBe(
1778457601234.567,