fix: polish bark battle creation flow
This commit is contained in:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user