Files
Genarrative/src/components/square-hole-runtime/SquareHoleRuntimeShell.test.tsx
kdletters b601b3b57e 收口运行态状态提示组件
新增 PlatformRuntimeStatusToast 统一运行态短错误、成功和反馈 toast
迁移跳一跳、拼图、敲木鱼、方洞和宝贝爱画运行态状态 chip
补充公共组件与运行态回归测试,并更新 PlatformUiKit 文档和 Hermes 决策记录
2026-06-10 11:24:40 +08:00

157 lines
4.1 KiB
TypeScript

/* @vitest-environment jsdom */
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import { beforeEach, expect, test, vi } from 'vitest';
import type {
DropSquareHoleShapeRequest,
SquareHoleRunSnapshot,
} from '../../../packages/shared/src/contracts/squareHoleRuntime';
import { SquareHoleRuntimeShell } from './SquareHoleRuntimeShell';
function buildRun(): SquareHoleRunSnapshot {
return {
runId: 'run-1',
profileId: 'profile-1',
ownerUserId: 'user-1',
status: 'running',
snapshotVersion: 3,
startedAtMs: Date.now(),
durationLimitMs: 60_000,
remainingMs: 60_000,
totalShapeCount: 8,
completedShapeCount: 0,
combo: 0,
bestCombo: 0,
score: 0,
ruleLabel: '把当前选项投入指定洞口',
backgroundImageSrc: null,
currentShape: {
shapeId: 'shape-1',
shapeKind: 'square',
label: '当前选项',
targetHoleId: 'hole-b',
color: '#38bdf8',
imageSrc: null,
},
holes: [
{
holeId: 'hole-a',
holeKind: 'hole-a',
label: '洞口 A',
x: 0.28,
y: 0.32,
imageSrc: null,
},
{
holeId: 'hole-b',
holeKind: 'hole-b',
label: '洞口 B',
x: 0.66,
y: 0.52,
imageSrc: null,
},
],
lastFeedback: null,
};
}
function renderRuntime() {
const run = buildRun();
const onDropShape = vi.fn(async (_payload: DropSquareHoleShapeRequest) => ({
feedback: {
accepted: true,
rejectReason: null,
message: '已投入',
},
run,
}));
render(
<SquareHoleRuntimeShell
run={run}
onBack={vi.fn()}
onRestart={vi.fn()}
onDropShape={onDropShape}
/>,
);
return { onDropShape };
}
beforeEach(() => {
Object.defineProperty(HTMLElement.prototype, 'setPointerCapture', {
configurable: true,
value: vi.fn(),
});
Object.defineProperty(HTMLElement.prototype, 'releasePointerCapture', {
configurable: true,
value: vi.fn(),
});
});
test('点击洞口会提交该洞口选择', async () => {
const { onDropShape } = renderRuntime();
fireEvent.click(screen.getByRole('button', { name: '投入 洞口 B' }));
await waitFor(() => expect(onDropShape).toHaveBeenCalledTimes(1));
expect(onDropShape.mock.calls[0]?.[0]).toMatchObject({
runId: 'run-1',
holeId: 'hole-b',
clientSnapshotVersion: 3,
});
});
test('错误提示使用公共运行态 toast', () => {
const run = buildRun();
render(
<SquareHoleRuntimeShell
run={run}
error="投放失败"
onBack={vi.fn()}
onRestart={vi.fn()}
onDropShape={vi.fn()}
/>,
);
const toast = screen.getByRole('alert');
expect(toast.textContent).toBe('投放失败');
expect(toast.className).toContain('platform-runtime-status-toast');
});
test('引导高亮不会默认指向当前正确洞口', () => {
renderRuntime();
const correctHole = screen.getByRole('button', { name: '投入 洞口 B' });
const hintedHole = screen.getByRole('button', { name: '投入 洞口 A' });
expect(correctHole.className).not.toContain('ring-2');
expect(hintedHole.className).toContain('ring-2');
});
test('拖拽当前选项到洞口上松开会提交该洞口选择', async () => {
const { onDropShape } = renderRuntime();
const shape = screen.getByRole('button', { name: '拖拽当前选项' });
const hole = screen.getByRole('button', { name: '投入 洞口 A' });
const elementsFromPoint = vi.fn(() => [hole]);
Object.defineProperty(document, 'elementsFromPoint', {
configurable: true,
value: elementsFromPoint,
});
fireEvent.pointerDown(shape, { pointerId: 1, clientX: 20, clientY: 20 });
fireEvent.pointerMove(shape, { pointerId: 1, clientX: 140, clientY: 160 });
fireEvent.pointerUp(shape, { pointerId: 1, clientX: 140, clientY: 160 });
await waitFor(() => expect(onDropShape).toHaveBeenCalledTimes(1));
expect(onDropShape.mock.calls[0]?.[0]).toMatchObject({
runId: 'run-1',
holeId: 'hole-a',
clientSnapshotVersion: 3,
});
expect(elementsFromPoint).toHaveBeenCalled();
});