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