收口创作流程统一总计划并修复等待页窄屏裁切

This commit is contained in:
2026-05-31 05:57:34 +00:00
parent 551d436919
commit c193a352df
53 changed files with 2192 additions and 161 deletions

View File

@@ -0,0 +1,212 @@
/* @vitest-environment jsdom */
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { expect, test, vi } from 'vitest';
import type {
JumpHopRuntimeRunSnapshotResponse,
JumpHopWorkProfileResponse,
} from '../../../packages/shared/src/contracts/jumpHop';
import { JumpHopRuntimeShell } from './JumpHopRuntimeShell';
const profile: JumpHopWorkProfileResponse = {
summary: {
runtimeKind: 'jump-hop',
workId: 'work-1',
profileId: 'profile-1',
ownerUserId: 'user-1',
sourceSessionId: 'session-1',
workTitle: '云端跳台',
workDescription: '一路跳到星星。',
themeTags: ['云朵'],
difficulty: 'standard',
stylePreset: 'paper-toy',
coverImageSrc: 'data:image/png;base64,cover',
publicationStatus: 'draft',
playCount: 0,
updatedAt: '2026-05-30T10:00:00.000Z',
publishedAt: null,
publishReady: true,
generationStatus: 'ready',
},
draft: {
templateId: 'jump-hop',
templateName: '跳一跳',
profileId: 'profile-1',
workTitle: '云端跳台',
workDescription: '一路跳到星星。',
themeTags: ['云朵'],
difficulty: 'standard',
stylePreset: 'paper-toy',
characterPrompt: '纸片小兔',
tilePrompt: '云朵平台',
endMoodPrompt: '星光门',
characterAsset: {
assetId: 'character-1',
imageSrc: 'data:image/png;base64,character',
imageObjectKey: 'jump-hop/character.png',
assetObjectId: 'asset-character',
generationProvider: 'vector-engine-gpt-image-2',
prompt: '角色图',
width: 1024,
height: 1024,
},
tileAtlasAsset: {
assetId: 'tiles-1',
imageSrc: 'data:image/png;base64,tiles',
imageObjectKey: 'jump-hop/tiles.png',
assetObjectId: 'asset-tiles',
generationProvider: 'vector-engine-gpt-image-2',
prompt: '地块图',
width: 1024,
height: 1024,
},
tileAssets: [
{
tileType: 'start',
imageSrc: 'data:image/png;base64,tile-start',
imageObjectKey: 'jump-hop/tile-start.png',
assetObjectId: 'asset-tile-start',
sourceAtlasCell: 'A1',
visualWidth: 128,
visualHeight: 96,
topSurfaceRadius: 24,
landingRadius: 28,
},
],
path: {
seed: 'jump-hop-seed',
difficulty: 'standard',
platforms: [
{
platformId: 'platform-1',
tileType: 'start',
x: 0,
y: 0,
width: 48,
height: 36,
landingRadius: 22,
perfectRadius: 12,
scoreValue: 1,
},
],
finishIndex: 0,
cameraPreset: 'default',
scoring: {
chargeToDistanceRatio: 1.2,
maxChargeMs: 1800,
hitBonus: 20,
perfectBonus: 50,
},
},
coverComposite: 'data:image/png;base64,cover',
generationStatus: 'ready',
},
path: {
seed: 'jump-hop-seed',
difficulty: 'standard',
platforms: [
{
platformId: 'platform-1',
tileType: 'start',
x: 0,
y: 0,
width: 48,
height: 36,
landingRadius: 22,
perfectRadius: 12,
scoreValue: 1,
},
],
finishIndex: 0,
cameraPreset: 'default',
scoring: {
chargeToDistanceRatio: 1.2,
maxChargeMs: 1800,
hitBonus: 20,
perfectBonus: 50,
},
},
characterAsset: {
assetId: 'character-1',
imageSrc: 'data:image/png;base64,character',
imageObjectKey: 'jump-hop/character.png',
assetObjectId: 'asset-character',
generationProvider: 'vector-engine-gpt-image-2',
prompt: '角色图',
width: 1024,
height: 1024,
},
tileAtlasAsset: {
assetId: 'tiles-1',
imageSrc: 'data:image/png;base64,tiles',
imageObjectKey: 'jump-hop/tiles.png',
assetObjectId: 'asset-tiles',
generationProvider: 'vector-engine-gpt-image-2',
prompt: '地块图',
width: 1024,
height: 1024,
},
tileAssets: [
{
tileType: 'start',
imageSrc: 'data:image/png;base64,tile-start',
imageObjectKey: 'jump-hop/tile-start.png',
assetObjectId: 'asset-tile-start',
sourceAtlasCell: 'A1',
visualWidth: 128,
visualHeight: 96,
topSurfaceRadius: 24,
landingRadius: 28,
},
],
};
const run: JumpHopRuntimeRunSnapshotResponse = {
runId: 'run-1',
profileId: 'profile-1',
ownerUserId: 'user-1',
status: 'playing',
currentPlatformIndex: 0,
score: 0,
combo: 0,
path: profile.path,
lastJump: null,
startedAtMs: 1000,
finishedAtMs: null,
};
test('jump hop runtime shell supports jump, restart and exit actions', async () => {
const user = userEvent.setup();
const onJump = vi.fn().mockResolvedValue(undefined);
const onRestart = vi.fn();
const onExit = vi.fn();
render(
<JumpHopRuntimeShell
profile={profile}
run={run}
onJump={onJump}
onRestart={onRestart}
onExit={onExit}
/>,
);
await user.pointer([
{ target: screen.getByRole('button', { name: '起跳' }), keys: '[MouseLeft>]' },
]);
await user.pointer([
{ target: screen.getByRole('button', { name: '起跳' }), keys: '[/MouseLeft]' },
]);
await waitFor(() => {
expect(onJump).toHaveBeenCalledWith({ chargeMs: expect.any(Number) });
});
await user.click(screen.getByRole('button', { name: '重开' }));
await user.click(screen.getByRole('button', { name: '返回' }));
expect(onRestart).toHaveBeenCalledTimes(1);
expect(onExit).toHaveBeenCalledTimes(1);
});