feat(jump-hop): optimize generated assets and runtime background
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
/* @vitest-environment jsdom */
|
||||
|
||||
import { act, fireEvent, render, screen } from '@testing-library/react';
|
||||
import { act, fireEvent, render, screen, within } from '@testing-library/react';
|
||||
import { beforeEach, expect, test, vi } from 'vitest';
|
||||
|
||||
import type {
|
||||
@@ -229,7 +229,31 @@ test('跳一跳蓄力时角色沿拖拽方向拉伸', async () => {
|
||||
);
|
||||
});
|
||||
|
||||
test('跳一跳运行态需要三维场景宿主和排行榜面板', () => {
|
||||
test('跳一跳运行态游玩中只保留得分并隐藏常驻排行榜', () => {
|
||||
const runtimeRequestOptions = {
|
||||
runtimeGuestToken: 'runtime-guest-token',
|
||||
};
|
||||
|
||||
render(
|
||||
<JumpHopRuntimeShell
|
||||
profile={buildProfile()}
|
||||
run={buildRun()}
|
||||
runtimeRequestOptions={runtimeRequestOptions}
|
||||
onJump={vi.fn().mockResolvedValue(undefined)}
|
||||
onRestart={() => {}}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(useJumpHopLeaderboard).not.toHaveBeenCalled();
|
||||
expect(screen.getByTestId('jump-hop-three-scene')).toBeTruthy();
|
||||
expect(screen.queryByTestId('jump-hop-runtime-leaderboard')).toBeNull();
|
||||
expect(screen.queryByRole('button', { name: /重开/ })).toBeNull();
|
||||
expect(screen.queryByText('进行中')).toBeNull();
|
||||
expect(screen.queryByText('00:00')).toBeNull();
|
||||
expect(screen.queryByRole('button', { name: /^起跳$/ })).toBeNull();
|
||||
});
|
||||
|
||||
test('跳一跳运行态失败后在弹窗中展示排行榜', () => {
|
||||
const runtimeRequestOptions = {
|
||||
runtimeGuestToken: 'runtime-guest-token',
|
||||
};
|
||||
@@ -255,7 +279,7 @@ test('跳一跳运行态需要三维场景宿主和排行榜面板', () => {
|
||||
render(
|
||||
<JumpHopRuntimeShell
|
||||
profile={buildProfile()}
|
||||
run={buildRun()}
|
||||
run={buildFailedRun()}
|
||||
runtimeRequestOptions={runtimeRequestOptions}
|
||||
onJump={vi.fn().mockResolvedValue(undefined)}
|
||||
onRestart={() => {}}
|
||||
@@ -266,12 +290,12 @@ test('跳一跳运行态需要三维场景宿主和排行榜面板', () => {
|
||||
'jump-hop-profile-test',
|
||||
runtimeRequestOptions,
|
||||
);
|
||||
expect(screen.getByTestId('jump-hop-three-scene')).toBeTruthy();
|
||||
expect(screen.getByTestId('jump-hop-runtime-leaderboard')).toBeTruthy();
|
||||
expect(screen.getByText('player-1')).toBeTruthy();
|
||||
expect(screen.getByText('8 跳')).toBeTruthy();
|
||||
expect(screen.getByText('00:08')).toBeTruthy();
|
||||
expect(screen.queryByRole('button', { name: /^起跳$/ })).toBeNull();
|
||||
expect(screen.getByRole('dialog', { name: '失败' })).toBeTruthy();
|
||||
const leaderboard = screen.getByTestId('jump-hop-runtime-leaderboard');
|
||||
expect(leaderboard).toBeTruthy();
|
||||
expect(within(leaderboard).getByText('player-1')).toBeTruthy();
|
||||
expect(within(leaderboard).getByText('8 跳')).toBeTruthy();
|
||||
expect(within(leaderboard).getByText('00:08')).toBeTruthy();
|
||||
});
|
||||
|
||||
test('跳一跳角色层永远压在地块层之上', () => {
|
||||
@@ -356,6 +380,7 @@ test('跳一跳运行态直接渲染生成的地块切片图片', () => {
|
||||
|
||||
const tileImages = screen.getAllByTestId('jump-hop-tile-image');
|
||||
expect(tileImages).toHaveLength(3);
|
||||
expect(document.querySelector('.jump-hop-runtime__fallback-tile')).toBeNull();
|
||||
const generatedReadUrlCalls = vi
|
||||
.mocked(useResolvedAssetReadUrl)
|
||||
.mock.calls.filter(([source]) =>
|
||||
@@ -379,6 +404,25 @@ test('跳一跳运行态直接渲染生成的地块切片图片', () => {
|
||||
}
|
||||
});
|
||||
|
||||
test('跳一跳运行态提前预加载下一屏地块且不在真实图片加载前露出原型方块', () => {
|
||||
render(
|
||||
<JumpHopRuntimeShell
|
||||
profile={buildProfile({ tileAssets: buildTileAssets() })}
|
||||
run={buildRunWithExtraPreviewPlatform()}
|
||||
onJump={vi.fn().mockResolvedValue(undefined)}
|
||||
onRestart={() => {}}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getAllByTestId('jump-hop-tile-image')).toHaveLength(3);
|
||||
expect(document.querySelector('.jump-hop-runtime__fallback-tile')).toBeNull();
|
||||
const preloadImages = screen.getAllByTestId('jump-hop-tile-preload-image');
|
||||
expect(preloadImages.length).toBeGreaterThan(0);
|
||||
expect(preloadImages[0]?.getAttribute('src')).toContain(
|
||||
'/generated-jump-hop-assets/jump-hop-profile-test/tile-',
|
||||
);
|
||||
});
|
||||
|
||||
test('跳一跳运行态首块地块落在中下方并且后续两块向中央和上方展开', () => {
|
||||
render(
|
||||
<JumpHopRuntimeShell
|
||||
@@ -679,6 +723,14 @@ test('跳一跳松手后先播放飞行动画再切换到下一块地块', async
|
||||
expect(styleText).toMatch(
|
||||
/data-platform-advancing='true'\]\s+\.jump-hop-runtime__platform[\s\S]*transform 1440ms cubic-bezier/,
|
||||
);
|
||||
expect(document.querySelector('.jump-hop-runtime__fallback-tile')).toBeNull();
|
||||
const advancingCharacterRule = styleText.match(
|
||||
/\.jump-hop-runtime__stage\[data-platform-advancing='true'\]\s+\.jump-hop-runtime__character\s*\{(?<body>[\s\S]*?)\}/,
|
||||
)?.groups?.body;
|
||||
expect(advancingCharacterRule).toContain('transform 120ms ease');
|
||||
expect(advancingCharacterRule).toContain('opacity 160ms ease');
|
||||
expect(advancingCharacterRule).not.toContain('left');
|
||||
expect(advancingCharacterRule).not.toContain('top');
|
||||
expect(screen.getByTestId('jump-hop-three-scene').parentElement).toBe(
|
||||
cameraLayer,
|
||||
);
|
||||
@@ -823,6 +875,50 @@ function buildRun(): JumpHopRuntimeRunSnapshotResponse {
|
||||
};
|
||||
}
|
||||
|
||||
function buildFailedRun(): JumpHopRuntimeRunSnapshotResponse {
|
||||
return {
|
||||
...buildRun(),
|
||||
status: 'failed',
|
||||
successfulJumpCount: 8,
|
||||
durationMs: 8123,
|
||||
score: 8,
|
||||
combo: 0,
|
||||
lastJump: {
|
||||
chargeMs: 420,
|
||||
jumpDistance: 1.62,
|
||||
targetPlatformIndex: 1,
|
||||
landedX: 0,
|
||||
landedY: 0,
|
||||
result: 'miss',
|
||||
},
|
||||
finishedAtMs: 9123,
|
||||
};
|
||||
}
|
||||
|
||||
function buildRunWithExtraPreviewPlatform(): JumpHopRuntimeRunSnapshotResponse {
|
||||
const run = buildRun();
|
||||
return {
|
||||
...run,
|
||||
path: {
|
||||
...run.path,
|
||||
platforms: [
|
||||
...run.path.platforms,
|
||||
{
|
||||
platformId: 'p3',
|
||||
tileType: 'normal',
|
||||
x: 0.5,
|
||||
y: 3.6,
|
||||
width: 1,
|
||||
height: 1,
|
||||
landingRadius: 0.5,
|
||||
perfectRadius: 0.2,
|
||||
scoreValue: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function buildTileAssets() {
|
||||
return Array.from({ length: 25 }, (_, index) => {
|
||||
const tileNumber = String(index + 1).padStart(2, '0');
|
||||
@@ -845,6 +941,8 @@ function buildTileAssets() {
|
||||
|
||||
function buildProfile(options: {
|
||||
tileAssets?: JumpHopWorkProfileResponse['tileAssets'];
|
||||
coverComposite?: string | null;
|
||||
coverImageSrc?: string | null;
|
||||
} = {}): JumpHopWorkProfileResponse {
|
||||
const characterAsset = {
|
||||
assetId: 'builtin',
|
||||
@@ -869,7 +967,7 @@ function buildProfile(options: {
|
||||
themeTags: ['测试'],
|
||||
difficulty: 'standard',
|
||||
stylePreset: 'minimal-blocks',
|
||||
coverImageSrc: null,
|
||||
coverImageSrc: options.coverImageSrc ?? null,
|
||||
publicationStatus: 'draft',
|
||||
playCount: 0,
|
||||
updatedAt: '2026-05-27T00:00:00Z',
|
||||
@@ -901,7 +999,7 @@ function buildProfile(options: {
|
||||
tileAtlasAsset: characterAsset,
|
||||
tileAssets: options.tileAssets ?? [],
|
||||
path: buildRun().path,
|
||||
coverComposite: null,
|
||||
coverComposite: options.coverComposite ?? null,
|
||||
generationStatus: 'ready',
|
||||
},
|
||||
path: buildRun().path,
|
||||
@@ -917,3 +1015,46 @@ function buildProfile(options: {
|
||||
tileAssets: options.tileAssets ?? [],
|
||||
};
|
||||
}
|
||||
|
||||
test('跳一跳运行态使用 image2 背景底图铺满舞台底层', () => {
|
||||
const backgroundSource =
|
||||
'/generated-jump-hop-assets/jump-hop-profile-test/background/image.png';
|
||||
|
||||
render(
|
||||
<JumpHopRuntimeShell
|
||||
profile={buildProfile({ coverComposite: backgroundSource })}
|
||||
run={buildRun()}
|
||||
onJump={vi.fn().mockResolvedValue(undefined)}
|
||||
onRestart={() => {}}
|
||||
/>,
|
||||
);
|
||||
|
||||
const backgroundImage = screen.getByTestId('jump-hop-stage-background-image');
|
||||
expect(backgroundImage.getAttribute('src')).toBe(backgroundSource);
|
||||
const backdrop = document.querySelector('.jump-hop-runtime__scene-backdrop');
|
||||
expect(backdrop?.getAttribute('data-has-background')).toBe('true');
|
||||
expect(useResolvedAssetReadUrl).toHaveBeenCalledWith(
|
||||
backgroundSource,
|
||||
expect.objectContaining({
|
||||
refreshKey: backgroundSource,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
test('跳一跳运行态忽略旧 cover composite 占位背景', () => {
|
||||
render(
|
||||
<JumpHopRuntimeShell
|
||||
profile={buildProfile({
|
||||
coverComposite:
|
||||
'/generated-jump-hop-assets/jump-hop-profile-test/cover-composite.png',
|
||||
})}
|
||||
run={buildRun()}
|
||||
onJump={vi.fn().mockResolvedValue(undefined)}
|
||||
onRestart={() => {}}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.queryByTestId('jump-hop-stage-background-image')).toBeNull();
|
||||
const backdrop = document.querySelector('.jump-hop-runtime__scene-backdrop');
|
||||
expect(backdrop?.getAttribute('data-has-background')).toBe('false');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user