25761b84a3
合并 Expo 移动壳与 Tauri 桌面壳工程 保留创作主页、项目入口和现有生产发布脚本 同步 HostBridge 文档、依赖锁文件和原生壳门禁
903 lines
25 KiB
TypeScript
903 lines
25 KiB
TypeScript
import { describe, expect, test } from 'vitest';
|
|
|
|
import type { BigFishWorkSummary } from '../../../packages/shared/src/contracts/bigFishWorkSummary';
|
|
import type { JumpHopWorkSummaryResponse } from '../../../packages/shared/src/contracts/jumpHop';
|
|
import type { Match3DWorkSummary } from '../../../packages/shared/src/contracts/match3dWorks';
|
|
import type { PuzzleWorkSummary } from '../../../packages/shared/src/contracts/puzzleWorkSummary';
|
|
import type { SquareHoleWorkSummary } from '../../../packages/shared/src/contracts/squareHoleWorks';
|
|
import type { VisualNovelWorkSummary } from '../../../packages/shared/src/contracts/visualNovel';
|
|
import type { WoodenFishWorkSummaryResponse } from '../../../packages/shared/src/contracts/woodenFish';
|
|
import { buildCreationWorkShelfItems } from '../custom-world-home/creationWorkShelf';
|
|
import {
|
|
buildCreationWorkShelfRuntimeState,
|
|
buildPendingPuzzleWorks,
|
|
buildPuzzleResultProfileId,
|
|
buildPuzzleResultWorkId,
|
|
collectVisibleDraftNoticeKeys,
|
|
countUnreadDraftGenerationUpdates,
|
|
createPendingDraftShelfState,
|
|
type DraftGenerationNoticeMap,
|
|
getGenerationNoticeShelfKeys,
|
|
hasUnreadDraftGenerationUpdates,
|
|
mergeBigFishWorkSummary,
|
|
mergePuzzleWorkSummary,
|
|
resolveBigFishDraftOpenIntent,
|
|
resolveJumpHopDraftOpenIntent,
|
|
resolveMatch3DDraftOpenIntent,
|
|
resolvePuzzleDraftOpenIntent,
|
|
resolveSquareHoleDraftOpenIntent,
|
|
resolveVisualNovelDraftOpenIntent,
|
|
resolveWoodenFishDraftOpenIntent,
|
|
} from './platformDraftGenerationShelfModel';
|
|
|
|
describe('platformDraftGenerationShelfModel', () => {
|
|
test('resolvePuzzleDraftOpenIntent sends published puzzle without session to detail', () => {
|
|
expect(
|
|
resolvePuzzleDraftOpenIntent({
|
|
item: buildPuzzleWork({
|
|
sourceSessionId: null,
|
|
publicationStatus: 'published',
|
|
}),
|
|
notices: {},
|
|
generation: emptyGenerationFacts(),
|
|
}),
|
|
).toMatchObject({
|
|
type: 'open-published-detail',
|
|
});
|
|
});
|
|
|
|
test('resolvePuzzleDraftOpenIntent restores failed puzzle generation with notice copy', () => {
|
|
expect(
|
|
resolvePuzzleDraftOpenIntent({
|
|
item: buildPuzzleWork(),
|
|
notices: {
|
|
'puzzle:puzzle-session-base': {
|
|
status: 'failed',
|
|
seen: false,
|
|
message: '首图生成失败。',
|
|
},
|
|
},
|
|
generation: emptyGenerationFacts(),
|
|
}),
|
|
).toMatchObject({
|
|
type: 'failed-generation',
|
|
source: 'restored',
|
|
errorMessage: '首图生成失败。',
|
|
});
|
|
});
|
|
|
|
test('resolvePuzzleDraftOpenIntent prefers active generation before restoring draft', () => {
|
|
expect(
|
|
resolvePuzzleDraftOpenIntent({
|
|
item: buildPuzzleWork(),
|
|
notices: {},
|
|
generation: emptyGenerationFacts({
|
|
activeSessionId: 'puzzle-session-base',
|
|
hasActiveGenerationRunning: true,
|
|
}),
|
|
}),
|
|
).toMatchObject({
|
|
type: 'active-generation',
|
|
});
|
|
});
|
|
|
|
test('resolvePuzzleDraftOpenIntent does not lock a puzzle draft that already has a cover', () => {
|
|
expect(
|
|
resolvePuzzleDraftOpenIntent({
|
|
item: buildPuzzleWork({
|
|
coverImageSrc: '/media/puzzle-cover.png',
|
|
}),
|
|
notices: {
|
|
'puzzle:puzzle-session-base': {
|
|
status: 'generating',
|
|
seen: false,
|
|
},
|
|
},
|
|
generation: emptyGenerationFacts(),
|
|
}),
|
|
).toMatchObject({
|
|
type: 'restore-draft',
|
|
});
|
|
});
|
|
|
|
test('resolvePuzzleDraftOpenIntent ignores stale generating notice after puzzle draft is ready', () => {
|
|
expect(
|
|
resolvePuzzleDraftOpenIntent({
|
|
item: buildPuzzleWork({
|
|
generationStatus: 'ready',
|
|
coverImageSrc: '/generated-puzzle-assets/session/cover.png',
|
|
levels: [
|
|
{
|
|
levelId: 'level-1',
|
|
levelName: '金色数字舞台',
|
|
pictureDescription: '123456',
|
|
pictureReference: null,
|
|
candidates: [],
|
|
selectedCandidateId: null,
|
|
coverImageSrc: '/generated-puzzle-assets/session/cover.png',
|
|
coverAssetId: 'asset-1',
|
|
generationStatus: 'ready',
|
|
},
|
|
],
|
|
}),
|
|
notices: {
|
|
'puzzle:puzzle-session-base': {
|
|
status: 'generating',
|
|
seen: false,
|
|
},
|
|
},
|
|
generation: emptyGenerationFacts(),
|
|
}),
|
|
).toMatchObject({
|
|
type: 'restore-draft',
|
|
});
|
|
});
|
|
|
|
test('resolvePuzzleDraftOpenIntent restores generating page from notice even before persisted status catches up', () => {
|
|
expect(
|
|
resolvePuzzleDraftOpenIntent({
|
|
item: buildPuzzleWork({
|
|
generationStatus: 'idle',
|
|
}),
|
|
notices: {
|
|
'puzzle:puzzle-session-base': {
|
|
status: 'generating',
|
|
seen: false,
|
|
},
|
|
},
|
|
generation: emptyGenerationFacts(),
|
|
}),
|
|
).toMatchObject({
|
|
type: 'restore-generating',
|
|
});
|
|
});
|
|
|
|
test('resolveMatch3DDraftOpenIntent opens published work detail unless forced into draft', () => {
|
|
const item = buildMatch3DWork({
|
|
publicationStatus: 'published',
|
|
});
|
|
|
|
expect(
|
|
resolveMatch3DDraftOpenIntent({
|
|
item,
|
|
notices: {},
|
|
generation: emptyGenerationFacts(),
|
|
}),
|
|
).toMatchObject({
|
|
type: 'open-published-detail',
|
|
});
|
|
|
|
expect(
|
|
resolveMatch3DDraftOpenIntent({
|
|
item,
|
|
notices: {},
|
|
forceDraft: true,
|
|
generation: emptyGenerationFacts(),
|
|
}),
|
|
).toMatchObject({
|
|
type: 'restore-draft',
|
|
});
|
|
});
|
|
|
|
test('resolveMatch3DDraftOpenIntent starts ready unread draft before failure fallback', () => {
|
|
expect(
|
|
resolveMatch3DDraftOpenIntent({
|
|
item: buildMatch3DWork(),
|
|
notices: {
|
|
'match3d:match3d-session-base': {
|
|
status: 'ready',
|
|
seen: false,
|
|
},
|
|
},
|
|
generation: emptyGenerationFacts({
|
|
hasBackgroundGenerationFailure: true,
|
|
}),
|
|
}),
|
|
).toMatchObject({
|
|
type: 'ready-unread',
|
|
});
|
|
});
|
|
|
|
test('resolveMatch3DDraftOpenIntent restores persisted generating draft', () => {
|
|
expect(
|
|
resolveMatch3DDraftOpenIntent({
|
|
item: buildMatch3DWork({
|
|
generationStatus: 'generating',
|
|
}),
|
|
notices: {},
|
|
generation: emptyGenerationFacts(),
|
|
}),
|
|
).toMatchObject({
|
|
type: 'restore-generating',
|
|
});
|
|
});
|
|
|
|
test('resolveBigFishDraftOpenIntent reopens active generating session before restoring draft', () => {
|
|
expect(
|
|
resolveBigFishDraftOpenIntent({
|
|
item: buildBigFishWork(),
|
|
activeSessionId: 'big-fish-session-base',
|
|
hasActiveGenerationRunning: true,
|
|
}),
|
|
).toMatchObject({
|
|
type: 'active-generation',
|
|
sourceSessionId: 'big-fish-session-base',
|
|
});
|
|
|
|
expect(
|
|
resolveBigFishDraftOpenIntent({
|
|
item: buildBigFishWork(),
|
|
activeSessionId: 'other-session',
|
|
hasActiveGenerationRunning: true,
|
|
}),
|
|
).toMatchObject({
|
|
type: 'restore-draft',
|
|
sourceSessionId: 'big-fish-session-base',
|
|
});
|
|
});
|
|
|
|
test('resolveSquareHoleDraftOpenIntent handles published, missing, active and restore states', () => {
|
|
expect(
|
|
resolveSquareHoleDraftOpenIntent({
|
|
item: buildSquareHoleWork({ publicationStatus: 'published' }),
|
|
activeSessionId: null,
|
|
hasActiveGenerationRunning: false,
|
|
isGenerationReady: false,
|
|
}),
|
|
).toMatchObject({
|
|
type: 'open-published-detail',
|
|
});
|
|
|
|
expect(
|
|
resolveSquareHoleDraftOpenIntent({
|
|
item: buildSquareHoleWork({ sourceSessionId: null }),
|
|
forceDraft: true,
|
|
activeSessionId: null,
|
|
hasActiveGenerationRunning: false,
|
|
isGenerationReady: false,
|
|
}),
|
|
).toMatchObject({
|
|
type: 'missing-session',
|
|
});
|
|
|
|
expect(
|
|
resolveSquareHoleDraftOpenIntent({
|
|
item: buildSquareHoleWork(),
|
|
activeSessionId: 'square-hole-session-base',
|
|
hasActiveGenerationRunning: true,
|
|
isGenerationReady: false,
|
|
}),
|
|
).toMatchObject({
|
|
type: 'active-generation',
|
|
sourceSessionId: 'square-hole-session-base',
|
|
});
|
|
|
|
expect(
|
|
resolveSquareHoleDraftOpenIntent({
|
|
item: buildSquareHoleWork(),
|
|
activeSessionId: 'other-session',
|
|
hasActiveGenerationRunning: false,
|
|
isGenerationReady: false,
|
|
}),
|
|
).toMatchObject({
|
|
type: 'restore-draft',
|
|
shouldClearGenerationState: true,
|
|
});
|
|
});
|
|
|
|
test('resolveVisualNovelDraftOpenIntent handles published, active, current result and load detail states', () => {
|
|
expect(
|
|
resolveVisualNovelDraftOpenIntent({
|
|
item: buildVisualNovelWork({ publishStatus: 'published' }),
|
|
activeSessionId: null,
|
|
hasActiveGenerationRunning: false,
|
|
hasActiveSessionDraft: false,
|
|
}),
|
|
).toMatchObject({
|
|
type: 'open-published-detail',
|
|
});
|
|
|
|
expect(
|
|
resolveVisualNovelDraftOpenIntent({
|
|
item: buildVisualNovelWork(),
|
|
forceDraft: true,
|
|
activeSessionId: 'visual-novel-profile-base',
|
|
hasActiveGenerationRunning: true,
|
|
hasActiveSessionDraft: false,
|
|
}),
|
|
).toMatchObject({
|
|
type: 'active-generation',
|
|
profileId: 'visual-novel-profile-base',
|
|
});
|
|
|
|
expect(
|
|
resolveVisualNovelDraftOpenIntent({
|
|
item: buildVisualNovelWork(),
|
|
forceDraft: true,
|
|
activeSessionId: 'visual-novel-profile-base',
|
|
hasActiveGenerationRunning: false,
|
|
hasActiveSessionDraft: true,
|
|
}),
|
|
).toMatchObject({
|
|
type: 'current-result',
|
|
profileId: 'visual-novel-profile-base',
|
|
});
|
|
|
|
expect(
|
|
resolveVisualNovelDraftOpenIntent({
|
|
item: buildVisualNovelWork(),
|
|
forceDraft: true,
|
|
activeSessionId: 'other-profile',
|
|
hasActiveGenerationRunning: false,
|
|
hasActiveSessionDraft: false,
|
|
}),
|
|
).toMatchObject({
|
|
type: 'load-detail',
|
|
profileId: 'visual-novel-profile-base',
|
|
});
|
|
});
|
|
|
|
test('resolveJumpHopDraftOpenIntent handles published, failed current generation, generating and detail states', () => {
|
|
expect(
|
|
resolveJumpHopDraftOpenIntent({
|
|
item: buildJumpHopWork({ publicationStatus: 'published' }),
|
|
notices: {},
|
|
generation: emptyGenerationFacts(),
|
|
}),
|
|
).toMatchObject({
|
|
type: 'open-published-detail',
|
|
});
|
|
|
|
expect(
|
|
resolveJumpHopDraftOpenIntent({
|
|
item: buildJumpHopWork(),
|
|
notices: {
|
|
'jump-hop:jump-hop-session-base': {
|
|
status: 'failed',
|
|
seen: false,
|
|
},
|
|
},
|
|
generation: emptyGenerationFacts({
|
|
activeSessionId: 'jump-hop-session-base',
|
|
hasActiveGenerationFailure: true,
|
|
}),
|
|
}),
|
|
).toMatchObject({
|
|
type: 'active-failed-generation',
|
|
});
|
|
|
|
expect(
|
|
resolveJumpHopDraftOpenIntent({
|
|
item: buildJumpHopWork({ generationStatus: 'generating' }),
|
|
notices: {},
|
|
generation: emptyGenerationFacts(),
|
|
}),
|
|
).toMatchObject({
|
|
type: 'restore-generating',
|
|
});
|
|
|
|
expect(
|
|
resolveJumpHopDraftOpenIntent({
|
|
item: buildJumpHopWork({ generationStatus: 'generating' }),
|
|
notices: {
|
|
'jump-hop:jump-hop-session-base': {
|
|
status: 'failed',
|
|
seen: false,
|
|
},
|
|
},
|
|
generation: emptyGenerationFacts(),
|
|
}),
|
|
).toMatchObject({
|
|
type: 'load-detail',
|
|
});
|
|
|
|
expect(
|
|
resolveJumpHopDraftOpenIntent({
|
|
item: buildJumpHopWork({ sourceSessionId: null }),
|
|
notices: {
|
|
'jump-hop:jump-hop-work-base': {
|
|
status: 'failed',
|
|
seen: false,
|
|
},
|
|
},
|
|
generation: emptyGenerationFacts({
|
|
activeSessionId: null,
|
|
hasActiveGenerationFailure: true,
|
|
}),
|
|
}),
|
|
).toMatchObject({
|
|
type: 'load-detail',
|
|
});
|
|
});
|
|
|
|
test('resolveWoodenFishDraftOpenIntent uses profile fallback and failure fallback stage', () => {
|
|
expect(
|
|
resolveWoodenFishDraftOpenIntent({
|
|
item: buildWoodenFishWork({
|
|
sourceSessionId: null,
|
|
generationStatus: 'generating',
|
|
}),
|
|
notices: {
|
|
'wooden-fish:wooden-fish-profile-base': {
|
|
status: 'failed',
|
|
seen: false,
|
|
},
|
|
},
|
|
generation: emptyGenerationFacts({
|
|
activeSessionId: 'wooden-fish-profile-base',
|
|
hasActiveGenerationFailure: true,
|
|
}),
|
|
}),
|
|
).toMatchObject({
|
|
type: 'active-failed-generation',
|
|
});
|
|
|
|
expect(
|
|
resolveWoodenFishDraftOpenIntent({
|
|
item: buildWoodenFishWork({ generationStatus: 'generating' }),
|
|
notices: {},
|
|
generation: emptyGenerationFacts(),
|
|
}),
|
|
).toMatchObject({
|
|
type: 'restore-generating',
|
|
});
|
|
|
|
expect(
|
|
resolveWoodenFishDraftOpenIntent({
|
|
item: buildWoodenFishWork(),
|
|
notices: {
|
|
'wooden-fish:wooden-fish-session-base': {
|
|
status: 'failed',
|
|
seen: false,
|
|
},
|
|
},
|
|
generation: emptyGenerationFacts(),
|
|
}),
|
|
).toMatchObject({
|
|
type: 'load-detail',
|
|
failureFallbackStage: 'wooden-fish-workspace',
|
|
});
|
|
|
|
expect(
|
|
resolveWoodenFishDraftOpenIntent({
|
|
item: buildWoodenFishWork(),
|
|
notices: {},
|
|
generation: emptyGenerationFacts(),
|
|
}),
|
|
).toMatchObject({
|
|
type: 'load-detail',
|
|
failureFallbackStage: 'wooden-fish-generating',
|
|
});
|
|
});
|
|
|
|
test('buildPendingPuzzleWorks creates failed puzzle placeholder with stable ids and fallback title', () => {
|
|
const pending = buildPendingPuzzleWorks(
|
|
{
|
|
'puzzle-session-ocean': createPendingDraftShelfState(
|
|
'failed',
|
|
false,
|
|
'2026-06-03T08:00:00.000Z',
|
|
),
|
|
},
|
|
[],
|
|
);
|
|
|
|
expect(pending).toHaveLength(1);
|
|
expect(pending[0]).toMatchObject({
|
|
workId: 'puzzle-work-ocean',
|
|
profileId: 'puzzle-profile-ocean',
|
|
sourceSessionId: 'puzzle-session-ocean',
|
|
workTitle: '拼图草稿',
|
|
summary: '拼图草稿生成失败,可重新打开处理。',
|
|
generationStatus: 'failed',
|
|
});
|
|
});
|
|
|
|
test('buildPendingPuzzleWorks skips pending item when backend shelf already has the session', () => {
|
|
const pending = buildPendingPuzzleWorks(
|
|
{
|
|
'puzzle-session-ocean': createPendingDraftShelfState(
|
|
'generating',
|
|
false,
|
|
'2026-06-03T08:00:00.000Z',
|
|
),
|
|
},
|
|
[buildPuzzleWork({ sourceSessionId: 'puzzle-session-ocean' })],
|
|
);
|
|
|
|
expect(pending).toEqual([]);
|
|
});
|
|
|
|
test('mergePuzzleWorkSummary only replaces the matching profile', () => {
|
|
const current = buildPuzzleWork({
|
|
profileId: 'puzzle-profile-1',
|
|
workTitle: '旧拼图',
|
|
});
|
|
const updated = buildPuzzleWork({
|
|
profileId: 'puzzle-profile-1',
|
|
workTitle: '新拼图',
|
|
});
|
|
const other = buildPuzzleWork({
|
|
profileId: 'puzzle-profile-2',
|
|
workTitle: '别的拼图',
|
|
});
|
|
|
|
expect(mergePuzzleWorkSummary(current, updated)).toBe(updated);
|
|
expect(mergePuzzleWorkSummary(current, other)).toBe(current);
|
|
});
|
|
|
|
test('mergeBigFishWorkSummary only replaces the matching source session', () => {
|
|
const current = buildBigFishWork({
|
|
sourceSessionId: 'big-fish-session-1',
|
|
title: '旧大鱼',
|
|
});
|
|
const updated = buildBigFishWork({
|
|
sourceSessionId: 'big-fish-session-1',
|
|
title: '新大鱼',
|
|
});
|
|
const other = buildBigFishWork({
|
|
sourceSessionId: 'big-fish-session-2',
|
|
title: '别的大鱼',
|
|
});
|
|
|
|
expect(mergeBigFishWorkSummary(current, updated)).toBe(updated);
|
|
expect(mergeBigFishWorkSummary(current, other)).toBe(current);
|
|
});
|
|
|
|
test('buildCreationWorkShelfRuntimeState lets failure notice override persisted generating puzzle copy', () => {
|
|
const [item] = buildCreationWorkShelfItems({
|
|
rpgItems: [],
|
|
bigFishItems: [],
|
|
puzzleItems: [
|
|
buildPuzzleWork({
|
|
workId: 'puzzle-work-empty',
|
|
profileId: 'puzzle-profile-empty',
|
|
sourceSessionId: 'puzzle-session-empty',
|
|
workTitle: '',
|
|
workDescription: '',
|
|
levelName: '',
|
|
summary: '正在生成拼图草稿。',
|
|
generationStatus: 'generating',
|
|
}),
|
|
],
|
|
});
|
|
expect(item).toBeTruthy();
|
|
|
|
const noticeKeys = getGenerationNoticeShelfKeys(item!);
|
|
const notices = Object.fromEntries(
|
|
noticeKeys.map((key) => [
|
|
key,
|
|
{ status: 'failed', seen: false },
|
|
]),
|
|
) as DraftGenerationNoticeMap;
|
|
|
|
const state = buildCreationWorkShelfRuntimeState({
|
|
item: item!,
|
|
notices,
|
|
pendingShelfItems: {
|
|
puzzle: {
|
|
'puzzle-session-empty': createPendingDraftShelfState(
|
|
'failed',
|
|
false,
|
|
'2026-06-03T08:00:00.000Z',
|
|
{ summary: '图片生成超时,可重新打开处理。' },
|
|
),
|
|
},
|
|
},
|
|
});
|
|
|
|
expect(state).toMatchObject({
|
|
isGenerating: false,
|
|
hasGenerationFailure: true,
|
|
generationFailureSummary: '拼图草稿生成失败,可重新打开处理。',
|
|
hasUnreadUpdate: false,
|
|
suppressPersistedGenerating: true,
|
|
titleOverride: '拼图草稿',
|
|
summaryOverride: '图片生成超时,可重新打开处理。',
|
|
});
|
|
});
|
|
|
|
test('collectVisibleDraftNoticeKeys and hasUnreadDraftGenerationUpdates share unread dot rule', () => {
|
|
const puzzle = buildPuzzleWork({
|
|
workId: 'puzzle-work-ocean',
|
|
profileId: 'puzzle-profile-ocean',
|
|
sourceSessionId: 'puzzle-session-ocean',
|
|
});
|
|
const visibleKeys = collectVisibleDraftNoticeKeys({
|
|
rpgItems: [],
|
|
bigFishItems: [],
|
|
jumpHopItems: [],
|
|
woodenFishItems: [],
|
|
match3dItems: [],
|
|
squareHoleItems: [],
|
|
puzzleItems: [puzzle],
|
|
visualNovelItems: [],
|
|
barkBattleItems: [],
|
|
babyObjectMatchItems: [],
|
|
});
|
|
|
|
expect(visibleKeys).toContain('puzzle:puzzle-work-ocean');
|
|
expect(visibleKeys).toContain('puzzle:puzzle-profile-ocean');
|
|
expect(visibleKeys).toContain('puzzle:puzzle-session-ocean');
|
|
expect(buildPuzzleResultWorkId('puzzle-session-ocean')).toBe(
|
|
'puzzle-work-ocean',
|
|
);
|
|
expect(buildPuzzleResultProfileId('puzzle-session-ocean')).toBe(
|
|
'puzzle-profile-ocean',
|
|
);
|
|
|
|
expect(
|
|
hasUnreadDraftGenerationUpdates(
|
|
{
|
|
'puzzle:puzzle-profile-ocean': {
|
|
status: 'ready',
|
|
seen: false,
|
|
},
|
|
},
|
|
visibleKeys,
|
|
),
|
|
).toBe(true);
|
|
expect(
|
|
hasUnreadDraftGenerationUpdates(
|
|
{
|
|
'puzzle:puzzle-profile-ocean': {
|
|
status: 'ready',
|
|
seen: true,
|
|
},
|
|
},
|
|
visibleKeys,
|
|
),
|
|
).toBe(false);
|
|
|
|
expect(
|
|
countUnreadDraftGenerationUpdates(
|
|
{
|
|
'puzzle:puzzle-work-ocean': {
|
|
status: 'ready',
|
|
seen: false,
|
|
},
|
|
'puzzle:puzzle-profile-ocean': {
|
|
status: 'ready',
|
|
seen: false,
|
|
},
|
|
'puzzle:puzzle-session-ocean': {
|
|
status: 'ready',
|
|
seen: false,
|
|
},
|
|
},
|
|
{
|
|
rpgItems: [],
|
|
bigFishItems: [],
|
|
jumpHopItems: [],
|
|
woodenFishItems: [],
|
|
match3dItems: [],
|
|
squareHoleItems: [],
|
|
puzzleItems: [puzzle],
|
|
visualNovelItems: [],
|
|
barkBattleItems: [],
|
|
babyObjectMatchItems: [],
|
|
},
|
|
),
|
|
).toBe(1);
|
|
expect(
|
|
countUnreadDraftGenerationUpdates(
|
|
{
|
|
'puzzle:puzzle-profile-ocean': {
|
|
status: 'failed',
|
|
seen: false,
|
|
},
|
|
'puzzle:puzzle-session-other': {
|
|
status: 'ready',
|
|
seen: false,
|
|
},
|
|
},
|
|
{
|
|
rpgItems: [],
|
|
bigFishItems: [],
|
|
jumpHopItems: [],
|
|
woodenFishItems: [],
|
|
match3dItems: [],
|
|
squareHoleItems: [],
|
|
puzzleItems: [puzzle],
|
|
visualNovelItems: [],
|
|
barkBattleItems: [],
|
|
babyObjectMatchItems: [],
|
|
},
|
|
),
|
|
).toBe(0);
|
|
});
|
|
});
|
|
|
|
function emptyGenerationFacts(
|
|
overrides: Partial<Parameters<typeof resolvePuzzleDraftOpenIntent>[0]['generation']> = {},
|
|
): Parameters<typeof resolvePuzzleDraftOpenIntent>[0]['generation'] {
|
|
return {
|
|
activeSessionId: null,
|
|
hasActiveGenerationFailure: false,
|
|
hasActiveGenerationRunning: false,
|
|
hasBackgroundGenerationFailure: false,
|
|
hasBackgroundGenerationRunning: false,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function buildPuzzleWork(
|
|
overrides: Partial<PuzzleWorkSummary> = {},
|
|
): PuzzleWorkSummary {
|
|
return {
|
|
workId: 'puzzle-work-base',
|
|
profileId: 'puzzle-profile-base',
|
|
ownerUserId: 'user-1',
|
|
sourceSessionId: 'puzzle-session-base',
|
|
authorDisplayName: '测试作者',
|
|
workTitle: '潮雾拼图',
|
|
workDescription: '潮雾港口拼图。',
|
|
levelName: '潮雾拼图',
|
|
summary: '潮雾港口拼图。',
|
|
themeTags: [],
|
|
coverImageSrc: null,
|
|
coverAssetId: null,
|
|
publicationStatus: 'draft',
|
|
updatedAt: '2026-06-03T08:00:00.000Z',
|
|
publishedAt: null,
|
|
playCount: 0,
|
|
remixCount: 0,
|
|
likeCount: 0,
|
|
publishReady: false,
|
|
levels: [],
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function buildMatch3DWork(
|
|
overrides: Partial<Match3DWorkSummary> = {},
|
|
): Match3DWorkSummary {
|
|
return {
|
|
workId: 'match3d-work-base',
|
|
profileId: 'match3d-profile-base',
|
|
ownerUserId: 'user-1',
|
|
sourceSessionId: 'match3d-session-base',
|
|
gameName: '潮雾抓大鹅',
|
|
themeText: '潮雾港口',
|
|
summary: '潮雾港口抓大鹅。',
|
|
tags: [],
|
|
coverImageSrc: null,
|
|
referenceImageSrc: null,
|
|
clearCount: 0,
|
|
difficulty: 1,
|
|
publicationStatus: 'draft',
|
|
playCount: 0,
|
|
updatedAt: '2026-06-03T08:00:00.000Z',
|
|
publishedAt: null,
|
|
publishReady: false,
|
|
generationStatus: 'ready',
|
|
generatedItemAssets: [],
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function buildBigFishWork(
|
|
overrides: Partial<BigFishWorkSummary> = {},
|
|
): BigFishWorkSummary {
|
|
return {
|
|
workId: 'big-fish-work-base',
|
|
sourceSessionId: 'big-fish-session-base',
|
|
ownerUserId: 'user-1',
|
|
authorDisplayName: '测试作者',
|
|
title: '潮雾大鱼',
|
|
subtitle: '潮雾港口',
|
|
summary: '潮雾港口大鱼吃小鱼。',
|
|
coverImageSrc: null,
|
|
status: 'draft',
|
|
updatedAt: '2026-06-03T08:00:00.000Z',
|
|
publishedAt: null,
|
|
playCount: 0,
|
|
remixCount: 0,
|
|
likeCount: 0,
|
|
publishReady: false,
|
|
levelCount: 1,
|
|
levelMainImageReadyCount: 0,
|
|
levelMotionReadyCount: 0,
|
|
backgroundReady: false,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function buildSquareHoleWork(
|
|
overrides: Partial<SquareHoleWorkSummary> = {},
|
|
): SquareHoleWorkSummary {
|
|
return {
|
|
workId: 'square-hole-work-base',
|
|
profileId: 'square-hole-profile-base',
|
|
ownerUserId: 'user-1',
|
|
sourceSessionId: 'square-hole-session-base',
|
|
gameName: '潮雾方洞',
|
|
themeText: '潮雾港口',
|
|
twistRule: '避开雾门',
|
|
summary: '潮雾港口方洞挑战。',
|
|
tags: [],
|
|
coverImageSrc: null,
|
|
backgroundPrompt: '潮雾港口',
|
|
backgroundImageSrc: null,
|
|
shapeOptions: [],
|
|
holeOptions: [],
|
|
shapeCount: 1,
|
|
difficulty: 1,
|
|
publicationStatus: 'draft',
|
|
playCount: 0,
|
|
updatedAt: '2026-06-03T08:00:00.000Z',
|
|
publishedAt: null,
|
|
publishReady: false,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function buildVisualNovelWork(
|
|
overrides: Partial<VisualNovelWorkSummary> = {},
|
|
): VisualNovelWorkSummary {
|
|
return {
|
|
runtimeKind: 'visual-novel',
|
|
profileId: 'visual-novel-profile-base',
|
|
ownerUserId: 'user-1',
|
|
title: '潮雾视觉小说',
|
|
description: '潮雾港口视觉小说。',
|
|
coverImageSrc: null,
|
|
tags: [],
|
|
publishStatus: 'draft',
|
|
publishReady: false,
|
|
playCount: 0,
|
|
updatedAt: '2026-06-03T08:00:00.000Z',
|
|
publishedAt: null,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function buildJumpHopWork(
|
|
overrides: Partial<JumpHopWorkSummaryResponse> = {},
|
|
): JumpHopWorkSummaryResponse {
|
|
return {
|
|
runtimeKind: 'jump-hop',
|
|
workId: 'jump-hop-work-base',
|
|
profileId: 'jump-hop-profile-base',
|
|
ownerUserId: 'user-1',
|
|
sourceSessionId: 'jump-hop-session-base',
|
|
themeText: '潮雾港口',
|
|
workTitle: '潮雾跳一跳',
|
|
workDescription: '潮雾港口跳一跳。',
|
|
themeTags: [],
|
|
difficulty: 'standard',
|
|
stylePreset: 'minimal-blocks',
|
|
coverImageSrc: null,
|
|
publicationStatus: 'draft',
|
|
playCount: 0,
|
|
updatedAt: '2026-06-03T08:00:00.000Z',
|
|
publishedAt: null,
|
|
publishReady: false,
|
|
generationStatus: 'ready',
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function buildWoodenFishWork(
|
|
overrides: Partial<WoodenFishWorkSummaryResponse> = {},
|
|
): WoodenFishWorkSummaryResponse {
|
|
return {
|
|
runtimeKind: 'wooden-fish',
|
|
workId: 'wooden-fish-work-base',
|
|
profileId: 'wooden-fish-profile-base',
|
|
ownerUserId: 'user-1',
|
|
sourceSessionId: 'wooden-fish-session-base',
|
|
workTitle: '潮雾敲木鱼',
|
|
workDescription: '潮雾港口敲木鱼。',
|
|
themeTags: ['敲木鱼'],
|
|
coverImageSrc: null,
|
|
publicationStatus: 'draft',
|
|
playCount: 0,
|
|
updatedAt: '2026-06-03T08:00:00.000Z',
|
|
publishedAt: null,
|
|
publishReady: false,
|
|
generationStatus: 'ready',
|
|
...overrides,
|
|
};
|
|
}
|