1
This commit is contained in:
149
src/components/big-fish-result/BigFishResultView.test.tsx
Normal file
149
src/components/big-fish-result/BigFishResultView.test.tsx
Normal file
@@ -0,0 +1,149 @@
|
||||
// @vitest-environment jsdom
|
||||
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { describe, expect, test, vi } from 'vitest';
|
||||
|
||||
import type { BigFishSessionSnapshotResponse } from '../../../packages/shared/src/contracts/bigFish';
|
||||
import { BigFishResultView } from './BigFishResultView';
|
||||
|
||||
vi.mock('../ResolvedAssetImage', () => ({
|
||||
ResolvedAssetImage: ({
|
||||
src,
|
||||
alt,
|
||||
className,
|
||||
}: {
|
||||
src?: string | null;
|
||||
alt?: string;
|
||||
className?: string;
|
||||
}) => (src ? <img src={src} alt={alt} className={className} /> : null),
|
||||
}));
|
||||
|
||||
function createSession(): BigFishSessionSnapshotResponse {
|
||||
return {
|
||||
sessionId: 'big-fish-session-1',
|
||||
currentTurn: 2,
|
||||
progressPercent: 88,
|
||||
stage: 'asset_refining',
|
||||
anchorPack: {
|
||||
gameplayPromise: {
|
||||
key: 'gameplayPromise',
|
||||
label: '玩法承诺',
|
||||
value: '弱小逆袭',
|
||||
status: 'confirmed',
|
||||
},
|
||||
ecologyVisualTheme: {
|
||||
key: 'ecologyVisualTheme',
|
||||
label: '生态与视觉母题',
|
||||
value: '深海谜境',
|
||||
status: 'confirmed',
|
||||
},
|
||||
growthLadder: {
|
||||
key: 'growthLadder',
|
||||
label: '成长阶梯',
|
||||
value: '8 级进化',
|
||||
status: 'confirmed',
|
||||
},
|
||||
riskTempo: {
|
||||
key: 'riskTempo',
|
||||
label: '风险节奏',
|
||||
value: '平衡',
|
||||
status: 'confirmed',
|
||||
},
|
||||
},
|
||||
draft: {
|
||||
title: '深海谜境',
|
||||
subtitle: '逐级吞噬成长',
|
||||
coreFun: '弱小逆袭',
|
||||
ecologyTheme: '深海谜境',
|
||||
levels: [
|
||||
{
|
||||
level: 1,
|
||||
name: '荧潮幼体',
|
||||
oneLineFantasy: '在深海荧光裂谷中寻找第一个同伴。',
|
||||
silhouetteDirection: '圆润鱼苗',
|
||||
sizeRatio: 1,
|
||||
visualPromptSeed: '深海荧光幼体',
|
||||
motionPromptSeed: '轻微摆尾',
|
||||
mergeSourceLevel: null,
|
||||
preyWindow: [1],
|
||||
threatWindow: [2],
|
||||
isFinalLevel: false,
|
||||
},
|
||||
],
|
||||
background: {
|
||||
theme: '深海谜境',
|
||||
colorMood: '深蓝与青绿',
|
||||
foregroundHints: '漂浮微粒',
|
||||
midgroundComposition: '中央留白',
|
||||
backgroundDepth: '深海纵深',
|
||||
safePlayAreaHint: '中央 70%',
|
||||
spawnEdgeHint: '四周边缘',
|
||||
backgroundPromptSeed: '深海谜境背景',
|
||||
},
|
||||
runtimeParams: {
|
||||
levelCount: 1,
|
||||
mergeCountPerUpgrade: 3,
|
||||
spawnTargetCount: 12,
|
||||
leaderMoveSpeed: 160,
|
||||
followerCatchUpSpeed: 120,
|
||||
offscreenCullSeconds: 3,
|
||||
preySpawnDeltaLevels: [1],
|
||||
threatSpawnDeltaLevels: [1],
|
||||
winLevel: 1,
|
||||
},
|
||||
},
|
||||
assetSlots: [
|
||||
{
|
||||
slotId: 'big-fish-asset-level-main',
|
||||
assetKind: 'level_main_image',
|
||||
level: 1,
|
||||
motionKey: null,
|
||||
status: 'ready',
|
||||
assetUrl:
|
||||
'/generated-big-fish-assets/big-fish-session-1/level-main-image/level-1/image.png',
|
||||
promptSnapshot: '深海荧光幼体',
|
||||
updatedAt: '2026-04-23T10:00:00.000Z',
|
||||
},
|
||||
{
|
||||
slotId: 'big-fish-asset-background',
|
||||
assetKind: 'stage_background',
|
||||
level: null,
|
||||
motionKey: null,
|
||||
status: 'ready',
|
||||
assetUrl:
|
||||
'/generated-big-fish-assets/big-fish-session-1/stage-background/image.png',
|
||||
promptSnapshot: '深海谜境背景',
|
||||
updatedAt: '2026-04-23T10:00:00.000Z',
|
||||
},
|
||||
],
|
||||
assetCoverage: {
|
||||
levelMainImageReadyCount: 1,
|
||||
levelMotionReadyCount: 0,
|
||||
backgroundReady: true,
|
||||
requiredLevelCount: 1,
|
||||
publishReady: false,
|
||||
blockers: ['还缺少 2 个基础动作'],
|
||||
},
|
||||
messages: [],
|
||||
lastAssistantReply: '主图占位图已生成。',
|
||||
publishReady: false,
|
||||
updatedAt: '2026-04-23T10:00:00.000Z',
|
||||
};
|
||||
}
|
||||
|
||||
describe('BigFishResultView', () => {
|
||||
test('renders generated formal previews with accurate status copy', () => {
|
||||
render(
|
||||
<BigFishResultView
|
||||
session={createSession()}
|
||||
onBack={() => {}}
|
||||
onExecuteAction={() => {}}
|
||||
onStartTestRun={() => {}}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByText('主图 已生成')).toBeTruthy();
|
||||
expect(screen.getByAltText('荧潮幼体')).toBeTruthy();
|
||||
expect(screen.getByAltText('深海谜境 场地背景')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -16,6 +16,7 @@ import type {
|
||||
BigFishSessionSnapshotResponse,
|
||||
ExecuteBigFishActionRequest,
|
||||
} from '../../../packages/shared/src/contracts/bigFish';
|
||||
import { ResolvedAssetImage } from '../ResolvedAssetImage';
|
||||
|
||||
type BigFishAssetStudioTarget =
|
||||
| {
|
||||
@@ -61,7 +62,10 @@ function findAssetSlot(
|
||||
}
|
||||
|
||||
function assetReadyLabel(slot: BigFishAssetSlotResponse | undefined) {
|
||||
return slot?.status === 'ready' ? '已生成' : '待生成';
|
||||
if (slot?.status !== 'ready') {
|
||||
return '待生成';
|
||||
}
|
||||
return isBigFishPlaceholderAsset(slot) ? '占位已生成' : '已生成';
|
||||
}
|
||||
|
||||
function buildLevelAssetPreview(slot: BigFishAssetSlotResponse | undefined) {
|
||||
@@ -71,15 +75,43 @@ function buildLevelAssetPreview(slot: BigFishAssetSlotResponse | undefined) {
|
||||
return null;
|
||||
}
|
||||
|
||||
function isBigFishPlaceholderAsset(slot: BigFishAssetSlotResponse | undefined) {
|
||||
return Boolean(slot?.assetUrl?.includes('/generated-big-fish/'));
|
||||
}
|
||||
|
||||
function buildStudioAssetPreview(
|
||||
slots: BigFishAssetSlotResponse[],
|
||||
target: BigFishAssetStudioTarget,
|
||||
) {
|
||||
if (target.kind === 'stage_background') {
|
||||
return buildLevelAssetPreview(findAssetSlot(slots, 'stage_background'));
|
||||
}
|
||||
if (target.kind === 'level_main_image') {
|
||||
return buildLevelAssetPreview(
|
||||
findAssetSlot(slots, 'level_main_image', target.level.level),
|
||||
);
|
||||
}
|
||||
return buildLevelAssetPreview(
|
||||
findAssetSlot(
|
||||
slots,
|
||||
'level_motion',
|
||||
target.level.level,
|
||||
target.motionKey,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
function BigFishAssetStudioModal({
|
||||
draft,
|
||||
target,
|
||||
previewUrl,
|
||||
isBusy,
|
||||
onClose,
|
||||
onExecuteAction,
|
||||
}: {
|
||||
draft: BigFishGameDraftResponse;
|
||||
target: BigFishAssetStudioTarget;
|
||||
previewUrl?: string | null;
|
||||
isBusy: boolean;
|
||||
onClose: () => void;
|
||||
onExecuteAction: (payload: ExecuteBigFishActionRequest) => void;
|
||||
@@ -140,8 +172,16 @@ function BigFishAssetStudioModal({
|
||||
{prompt}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex aspect-[9/5] items-center justify-center rounded-[1.4rem] border border-dashed border-cyan-300/50 bg-cyan-50/40 text-sm text-[var(--platform-text-base)]">
|
||||
AI 资产候选预览
|
||||
<div className="flex aspect-[9/5] items-center justify-center overflow-hidden rounded-[1.4rem] border border-dashed border-cyan-300/50 bg-cyan-50/40 text-sm text-[var(--platform-text-base)]">
|
||||
{previewUrl ? (
|
||||
<ResolvedAssetImage
|
||||
src={previewUrl}
|
||||
alt={title}
|
||||
className="h-full w-full object-cover"
|
||||
/>
|
||||
) : (
|
||||
'AI 资产候选预览'
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-end gap-2 border-t border-[var(--platform-subpanel-border)] px-4 py-4">
|
||||
@@ -160,7 +200,7 @@ function BigFishAssetStudioModal({
|
||||
className="inline-flex items-center gap-2 rounded-full bg-cyan-600 px-4 py-2 text-sm font-bold text-white disabled:opacity-45"
|
||||
>
|
||||
{isBusy ? <Loader2 className="h-4 w-4 animate-spin" /> : null}
|
||||
生成并设为正式资产
|
||||
生成并应用正式图
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -203,7 +243,7 @@ function BigFishLevelCard({
|
||||
<div className="flex gap-3 p-3">
|
||||
<div className="flex h-24 w-24 shrink-0 items-center justify-center overflow-hidden rounded-[1.15rem] bg-[radial-gradient(circle_at_center,rgba(34,211,238,0.28),transparent_68%),linear-gradient(145deg,rgba(8,47,73,0.88),rgba(15,23,42,0.94))] text-white">
|
||||
{previewUrl ? (
|
||||
<img
|
||||
<ResolvedAssetImage
|
||||
src={previewUrl}
|
||||
alt={level.name}
|
||||
className="h-full w-full object-cover"
|
||||
@@ -297,10 +337,17 @@ export function BigFishResultView({
|
||||
useState<BigFishAssetStudioTarget | null>(null);
|
||||
const draft = session.draft;
|
||||
const backgroundSlot = findAssetSlot(session.assetSlots, 'stage_background');
|
||||
const backgroundPreviewUrl = buildLevelAssetPreview(backgroundSlot);
|
||||
const blockers = useMemo(
|
||||
() => session.assetCoverage.blockers.filter(Boolean),
|
||||
[session.assetCoverage.blockers],
|
||||
);
|
||||
const studioPreviewUrl = useMemo(() => {
|
||||
if (!studioTarget) {
|
||||
return null;
|
||||
}
|
||||
return buildStudioAssetPreview(session.assetSlots, studioTarget);
|
||||
}, [session.assetSlots, studioTarget]);
|
||||
|
||||
if (!draft) {
|
||||
return (
|
||||
@@ -404,7 +451,15 @@ export function BigFishResultView({
|
||||
</div>
|
||||
<ImagePlus className="h-5 w-5 text-cyan-600" />
|
||||
</div>
|
||||
<div className="mt-3 aspect-[9/16] rounded-[1.2rem] bg-[radial-gradient(circle_at_center,rgba(34,211,238,0.2),transparent_62%),linear-gradient(180deg,rgba(8,47,73,0.88),rgba(15,23,42,0.94))]" />
|
||||
<div className="mt-3 aspect-[9/16] overflow-hidden rounded-[1.2rem] bg-[radial-gradient(circle_at_center,rgba(34,211,238,0.2),transparent_62%),linear-gradient(180deg,rgba(8,47,73,0.88),rgba(15,23,42,0.94))]">
|
||||
{backgroundPreviewUrl ? (
|
||||
<ResolvedAssetImage
|
||||
src={backgroundPreviewUrl}
|
||||
alt={`${draft.background.theme} 场地背景`}
|
||||
className="h-full w-full object-cover"
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
disabled={isBusy}
|
||||
@@ -454,6 +509,7 @@ export function BigFishResultView({
|
||||
<BigFishAssetStudioModal
|
||||
draft={draft}
|
||||
target={studioTarget}
|
||||
previewUrl={studioPreviewUrl}
|
||||
isBusy={isBusy}
|
||||
onClose={() => {
|
||||
setStudioTarget(null);
|
||||
|
||||
Reference in New Issue
Block a user