138 lines
3.6 KiB
TypeScript
138 lines
3.6 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('引导高亮不会默认指向当前正确洞口', () => {
|
|
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();
|
|
});
|