feat: add mocap puzzle debug and drag support
Some checks failed
CI / verify (pull_request) Has been cancelled
Some checks failed
CI / verify (pull_request) Has been cancelled
This commit is contained in:
@@ -25,6 +25,25 @@ vi.mock('../ResolvedAssetImage', () => ({
|
||||
ResolvedAssetImage: () => null,
|
||||
}));
|
||||
|
||||
const mocapMock = vi.hoisted(() => ({
|
||||
state: 'grab',
|
||||
x: 0.42,
|
||||
y: 0.58,
|
||||
}));
|
||||
|
||||
vi.mock('../../services/useMocapInput', () => ({
|
||||
useMocapInput: () => ({
|
||||
status: 'connected',
|
||||
latestCommand: {
|
||||
actions: [mocapMock.state],
|
||||
primaryHand: {x: mocapMock.x, y: mocapMock.y, state: mocapMock.state},
|
||||
parseWarnings: [],
|
||||
},
|
||||
rawPacketPreview: {text: '{"hands":[{"state":"grab"}]}', receivedAtMs: 1},
|
||||
error: null,
|
||||
}),
|
||||
}));
|
||||
|
||||
function createAuthValue() {
|
||||
return {
|
||||
user: null,
|
||||
@@ -138,6 +157,150 @@ const clearedRun: PuzzleRunSnapshot = {
|
||||
},
|
||||
};
|
||||
|
||||
test('拼图界面显示 mocap 连接状态和最近动作调试信息', () => {
|
||||
renderPuzzleRuntime(
|
||||
<PuzzleRuntimeShell
|
||||
run={{
|
||||
...clearedRun,
|
||||
currentLevel: {
|
||||
...clearedRun.currentLevel!,
|
||||
status: 'playing',
|
||||
},
|
||||
}}
|
||||
onBack={vi.fn()}
|
||||
onSwapPieces={vi.fn()}
|
||||
onDragPiece={vi.fn()}
|
||||
onAdvanceNextLevel={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
|
||||
const debugPanel = screen.getByTestId('puzzle-mocap-debug');
|
||||
expect(within(debugPanel).getByText('mocap: connected')).toBeTruthy();
|
||||
expect(within(debugPanel).getByText('动作: grab')).toBeTruthy();
|
||||
expect(within(debugPanel).getByText('手势: grab @ 0.42, 0.58')).toBeTruthy();
|
||||
expect(within(debugPanel).getByText('解析: 无')).toBeTruthy();
|
||||
expect(within(debugPanel).getByText(/原始:/)).toBeTruthy();
|
||||
});
|
||||
|
||||
test('拼图界面在 mocap open_palm 时显示体感光标', () => {
|
||||
mocapMock.state = 'open_palm';
|
||||
mocapMock.x = 0.42;
|
||||
mocapMock.y = 0.58;
|
||||
renderPuzzleRuntime(
|
||||
<PuzzleRuntimeShell
|
||||
run={{
|
||||
...clearedRun,
|
||||
currentLevel: {
|
||||
...clearedRun.currentLevel!,
|
||||
status: 'playing',
|
||||
startedAtMs: Date.now(),
|
||||
remainingMs: 300_000,
|
||||
timeLimitMs: 300_000,
|
||||
},
|
||||
}}
|
||||
onBack={vi.fn()}
|
||||
onSwapPieces={vi.fn()}
|
||||
onDragPiece={vi.fn()}
|
||||
onAdvanceNextLevel={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
|
||||
const cursor = screen.getByTestId('puzzle-mocap-cursor');
|
||||
expect(cursor).toBeTruthy();
|
||||
expect(cursor).toHaveStyle({left: '42%', top: '58%'});
|
||||
mocapMock.state = 'grab';
|
||||
});
|
||||
|
||||
test('抓握时会触发拖拽提交并在松开时落子', () => {
|
||||
mocapMock.state = 'grab';
|
||||
mocapMock.x = 0.34;
|
||||
mocapMock.y = 0.34;
|
||||
const onDragPiece = vi.fn();
|
||||
const playingRun: PuzzleRunSnapshot = {
|
||||
...clearedRun,
|
||||
currentLevel: {
|
||||
...clearedRun.currentLevel!,
|
||||
status: 'playing',
|
||||
startedAtMs: Date.now(),
|
||||
remainingMs: 300_000,
|
||||
timeLimitMs: 300_000,
|
||||
board: {
|
||||
...clearedRun.currentLevel!.board,
|
||||
allTilesResolved: false,
|
||||
pieces: clearedRun.currentLevel!.board.pieces.map((piece) =>
|
||||
piece.pieceId === 'piece-0'
|
||||
? {...piece, currentRow: 0, currentCol: 0}
|
||||
: piece,
|
||||
),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const { container } = renderPuzzleRuntime(
|
||||
<PuzzleRuntimeShell
|
||||
run={playingRun}
|
||||
onBack={vi.fn()}
|
||||
onSwapPieces={vi.fn()}
|
||||
onDragPiece={onDragPiece}
|
||||
onAdvanceNextLevel={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
|
||||
const piece = container.querySelector('[data-piece-id="piece-0"]') as HTMLElement | null;
|
||||
if (!piece) {
|
||||
throw new Error('缺少测试拼图片');
|
||||
}
|
||||
const board = container.querySelector('[data-testid="puzzle-board"]') as HTMLElement | null;
|
||||
if (!board) {
|
||||
throw new Error('缺少测试棋盘');
|
||||
}
|
||||
board.getBoundingClientRect = () => ({
|
||||
x: 0,
|
||||
y: 0,
|
||||
left: 0,
|
||||
top: 0,
|
||||
right: 300,
|
||||
bottom: 300,
|
||||
width: 300,
|
||||
height: 300,
|
||||
toJSON: () => ({}),
|
||||
} as DOMRect);
|
||||
|
||||
act(() => {
|
||||
dispatchPointerEvent(piece, 'pointerdown', {
|
||||
pointerId: 11,
|
||||
clientX: 40,
|
||||
clientY: 40,
|
||||
});
|
||||
});
|
||||
act(() => {
|
||||
dispatchPointerEvent(piece, 'pointermove', {
|
||||
pointerId: 11,
|
||||
clientX: 70,
|
||||
clientY: 70,
|
||||
});
|
||||
});
|
||||
act(() => {
|
||||
dispatchPointerEvent(piece, 'pointermove', {
|
||||
pointerId: 11,
|
||||
clientX: 140,
|
||||
clientY: 140,
|
||||
});
|
||||
});
|
||||
act(() => {
|
||||
dispatchPointerEvent(piece, 'pointerup', {
|
||||
pointerId: 11,
|
||||
clientX: 140,
|
||||
clientY: 140,
|
||||
});
|
||||
});
|
||||
|
||||
expect(onDragPiece).toHaveBeenCalledTimes(1);
|
||||
expect(onDragPiece).toHaveBeenCalledWith(
|
||||
expect.objectContaining({pieceId: 'piece-0'}),
|
||||
);
|
||||
});
|
||||
|
||||
test('通关后显示结算弹窗、排行榜和下一关按钮', () => {
|
||||
vi.useFakeTimers();
|
||||
const onAdvanceNextLevel = vi.fn();
|
||||
|
||||
Reference in New Issue
Block a user