213 lines
5.5 KiB
TypeScript
213 lines
5.5 KiB
TypeScript
/* @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);
|
|
});
|