/* @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( , ); 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); });