feat: connect child motion warmup to mocap input
Some checks failed
CI / verify (push) Has been cancelled
Some checks failed
CI / verify (push) Has been cancelled
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
/* @vitest-environment jsdom */
|
||||
|
||||
import { fireEvent, render, screen } from '@testing-library/react';
|
||||
import { act, fireEvent, render, screen } from '@testing-library/react';
|
||||
import { afterEach, beforeEach, expect, test, vi } from 'vitest';
|
||||
|
||||
import { ChildMotionWarmupDemo } from './ChildMotionWarmupDemo';
|
||||
@@ -9,9 +9,40 @@ import {
|
||||
resetChildMotionWarmupRuntimeSession,
|
||||
} from './childMotionWarmupModel';
|
||||
|
||||
const mocapMock = vi.hoisted(() => ({
|
||||
status: 'connected' as 'idle' | 'connecting' | 'connected' | 'error',
|
||||
command: null as null | {
|
||||
actions: string[];
|
||||
hands?: Array<{ x: number; y: number; state: string; side: string }>;
|
||||
primaryHand?: { x: number; y: number; state: string; side: string } | null;
|
||||
leftHand?: { x: number; y: number; state: string; side: string } | null;
|
||||
rightHand?: { x: number; y: number; state: string; side: string } | null;
|
||||
bodyCenter?: { x: number; y: number } | null;
|
||||
},
|
||||
receivedAtMs: 1,
|
||||
}));
|
||||
|
||||
vi.mock('../../services/useMocapInput', () => ({
|
||||
useMocapInput: ({ enabled }: { enabled: boolean }) => ({
|
||||
status: enabled ? mocapMock.status : 'idle',
|
||||
latestCommand: enabled ? mocapMock.command : null,
|
||||
rawPacketPreview:
|
||||
enabled && mocapMock.command
|
||||
? {
|
||||
text: JSON.stringify(mocapMock.command),
|
||||
receivedAtMs: mocapMock.receivedAtMs,
|
||||
}
|
||||
: null,
|
||||
error: null,
|
||||
}),
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
resetChildMotionWarmupRuntimeSession();
|
||||
vi.restoreAllMocks();
|
||||
mocapMock.status = 'connected';
|
||||
mocapMock.command = null;
|
||||
mocapMock.receivedAtMs = 1;
|
||||
Object.defineProperty(navigator, 'mediaDevices', {
|
||||
configurable: true,
|
||||
value: undefined,
|
||||
@@ -19,6 +50,7 @@ beforeEach(() => {
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
@@ -57,6 +89,241 @@ test('developer keyboard input moves the avatar and triggers jump state', () =>
|
||||
expect(avatar.className).toContain('child-motion-avatar--jumping');
|
||||
});
|
||||
|
||||
test('mocap body center keeps the warmup flow on the motion data source', async () => {
|
||||
vi.useFakeTimers();
|
||||
mocapMock.command = {
|
||||
actions: [],
|
||||
bodyCenter: { x: 0.5, y: 0.6 },
|
||||
hands: [],
|
||||
primaryHand: null,
|
||||
leftHand: null,
|
||||
rightHand: null,
|
||||
};
|
||||
const { rerender, unmount } = render(<ChildMotionWarmupDemo />);
|
||||
|
||||
expect(screen.queryByText('摄像头暂不可用,已切换到本地演示')).toBeNull();
|
||||
expect(screen.queryByText('动作数据已连接,等待识别')).toBeNull();
|
||||
expect(screen.getByTestId('child-motion-avatar').getAttribute('style')).toContain(
|
||||
'left: 50%',
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
vi.advanceTimersByTime(2100);
|
||||
await vi.runOnlyPendingTimersAsync();
|
||||
});
|
||||
await vi.waitFor(() => {
|
||||
expect(screen.getByText('打个招呼')).toBeTruthy();
|
||||
});
|
||||
|
||||
mocapMock.command = {
|
||||
actions: ['open_palm'],
|
||||
bodyCenter: { x: 0.5, y: 0.6 },
|
||||
hands: [{ x: 0.48, y: 0.34, state: 'open_palm', side: 'left' }],
|
||||
primaryHand: { x: 0.48, y: 0.34, state: 'open_palm', side: 'left' },
|
||||
leftHand: { x: 0.48, y: 0.34, state: 'open_palm', side: 'left' },
|
||||
rightHand: null,
|
||||
};
|
||||
mocapMock.receivedAtMs += 1;
|
||||
await act(async () => {
|
||||
rerender(<ChildMotionWarmupDemo />);
|
||||
vi.advanceTimersByTime(1000);
|
||||
await vi.runOnlyPendingTimersAsync();
|
||||
});
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(screen.getByText('准备热身')).toBeTruthy();
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
vi.advanceTimersByTime(1000);
|
||||
await vi.runOnlyPendingTimersAsync();
|
||||
});
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(screen.getByRole('heading', { name: '向左一步' })).toBeTruthy();
|
||||
});
|
||||
|
||||
mocapMock.command = {
|
||||
actions: [],
|
||||
bodyCenter: { x: 0.34, y: 0.6 },
|
||||
hands: [],
|
||||
primaryHand: null,
|
||||
leftHand: null,
|
||||
rightHand: null,
|
||||
};
|
||||
mocapMock.receivedAtMs += 1;
|
||||
await act(async () => {
|
||||
rerender(<ChildMotionWarmupDemo />);
|
||||
});
|
||||
await vi.waitFor(() => {
|
||||
expect(screen.getByTestId('child-motion-avatar').getAttribute('style')).toContain(
|
||||
'left: 34%',
|
||||
);
|
||||
});
|
||||
await act(async () => {
|
||||
vi.advanceTimersByTime(2100);
|
||||
await vi.runOnlyPendingTimersAsync();
|
||||
});
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(screen.getByRole('heading', { name: '回到中间来' })).toBeTruthy();
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
unmount();
|
||||
});
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
test('mocap open palm completes the greeting wave step', async () => {
|
||||
vi.useFakeTimers();
|
||||
const { rerender, unmount } = render(<ChildMotionWarmupDemo />);
|
||||
|
||||
await act(async () => {
|
||||
vi.advanceTimersByTime(2100);
|
||||
await vi.runOnlyPendingTimersAsync();
|
||||
});
|
||||
await vi.waitFor(() => {
|
||||
expect(screen.getByText('打个招呼')).toBeTruthy();
|
||||
});
|
||||
|
||||
mocapMock.command = {
|
||||
actions: ['open_palm'],
|
||||
hands: [{ x: 0.46, y: 0.34, state: 'open_palm', side: 'left' }],
|
||||
primaryHand: { x: 0.46, y: 0.34, state: 'open_palm', side: 'left' },
|
||||
leftHand: { x: 0.46, y: 0.34, state: 'open_palm', side: 'left' },
|
||||
rightHand: null,
|
||||
};
|
||||
mocapMock.receivedAtMs += 1;
|
||||
await act(async () => {
|
||||
rerender(<ChildMotionWarmupDemo />);
|
||||
});
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(screen.getByText('准备热身')).toBeTruthy();
|
||||
});
|
||||
await act(async () => {
|
||||
unmount();
|
||||
});
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
test('mocap hand tracks complete left and right wave steps only after movement is visible', async () => {
|
||||
vi.useFakeTimers();
|
||||
const { rerender, unmount } = render(<ChildMotionWarmupDemo />);
|
||||
|
||||
const advancePositionStep = async (key: string, code: string) => {
|
||||
await act(async () => {
|
||||
fireEvent.keyDown(window, { key, code });
|
||||
});
|
||||
await act(async () => {
|
||||
vi.advanceTimersByTime(2100);
|
||||
await vi.runOnlyPendingTimersAsync();
|
||||
});
|
||||
await act(async () => {
|
||||
fireEvent.keyUp(window, { key, code });
|
||||
});
|
||||
};
|
||||
|
||||
await act(async () => {
|
||||
vi.advanceTimersByTime(2100);
|
||||
await vi.runOnlyPendingTimersAsync();
|
||||
});
|
||||
await vi.waitFor(() => {
|
||||
expect(screen.getByText('打个招呼')).toBeTruthy();
|
||||
});
|
||||
|
||||
mocapMock.command = {
|
||||
actions: ['open_palm'],
|
||||
hands: [{ x: 0.48, y: 0.34, state: 'open_palm', side: 'left' }],
|
||||
primaryHand: { x: 0.48, y: 0.34, state: 'open_palm', side: 'left' },
|
||||
leftHand: { x: 0.48, y: 0.34, state: 'open_palm', side: 'left' },
|
||||
rightHand: null,
|
||||
};
|
||||
mocapMock.receivedAtMs += 1;
|
||||
await act(async () => {
|
||||
rerender(<ChildMotionWarmupDemo />);
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
vi.advanceTimersByTime(1000);
|
||||
await vi.runOnlyPendingTimersAsync();
|
||||
});
|
||||
await advancePositionStep('a', 'KeyA');
|
||||
await act(async () => {
|
||||
vi.advanceTimersByTime(120);
|
||||
await vi.runOnlyPendingTimersAsync();
|
||||
vi.advanceTimersByTime(2100);
|
||||
await vi.runOnlyPendingTimersAsync();
|
||||
});
|
||||
await advancePositionStep('d', 'KeyD');
|
||||
await act(async () => {
|
||||
vi.advanceTimersByTime(120);
|
||||
await vi.runOnlyPendingTimersAsync();
|
||||
vi.advanceTimersByTime(2100);
|
||||
await vi.runOnlyPendingTimersAsync();
|
||||
});
|
||||
await vi.waitFor(() => {
|
||||
expect(screen.getByRole('heading', { name: '挥动左手' })).toBeTruthy();
|
||||
});
|
||||
|
||||
mocapMock.command = {
|
||||
actions: [],
|
||||
leftHand: { x: 0.3, y: 0.38, state: 'unknown', side: 'left' },
|
||||
primaryHand: { x: 0.3, y: 0.38, state: 'unknown', side: 'left' },
|
||||
rightHand: null,
|
||||
};
|
||||
mocapMock.receivedAtMs += 1;
|
||||
await act(async () => {
|
||||
rerender(<ChildMotionWarmupDemo />);
|
||||
});
|
||||
mocapMock.command = {
|
||||
actions: [],
|
||||
leftHand: { x: 0.39, y: 0.36, state: 'unknown', side: 'left' },
|
||||
primaryHand: { x: 0.39, y: 0.36, state: 'unknown', side: 'left' },
|
||||
rightHand: null,
|
||||
};
|
||||
mocapMock.receivedAtMs += 1;
|
||||
await act(async () => {
|
||||
rerender(<ChildMotionWarmupDemo />);
|
||||
});
|
||||
mocapMock.command = {
|
||||
actions: [],
|
||||
leftHand: { x: 0.31, y: 0.34, state: 'unknown', side: 'left' },
|
||||
primaryHand: { x: 0.31, y: 0.34, state: 'unknown', side: 'left' },
|
||||
rightHand: null,
|
||||
};
|
||||
mocapMock.receivedAtMs += 1;
|
||||
await act(async () => {
|
||||
rerender(<ChildMotionWarmupDemo />);
|
||||
});
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(screen.getByRole('heading', { name: '挥动右手' })).toBeTruthy();
|
||||
});
|
||||
|
||||
mocapMock.command = {
|
||||
actions: ['right_hand_wave'],
|
||||
leftHand: null,
|
||||
primaryHand: { x: 0.64, y: 0.35, state: 'unknown', side: 'right' },
|
||||
rightHand: { x: 0.64, y: 0.35, state: 'unknown', side: 'right' },
|
||||
};
|
||||
mocapMock.receivedAtMs += 1;
|
||||
await act(async () => {
|
||||
rerender(<ChildMotionWarmupDemo />);
|
||||
});
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(screen.getByRole('heading', { name: '原地跳一下' })).toBeTruthy();
|
||||
});
|
||||
await act(async () => {
|
||||
vi.advanceTimersByTime(720);
|
||||
await vi.runOnlyPendingTimersAsync();
|
||||
unmount();
|
||||
});
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
test('connects camera stream and releases it on unmount', async () => {
|
||||
const stopTrack = vi.fn();
|
||||
const stream = {
|
||||
@@ -79,7 +346,8 @@ test('connects camera stream and releases it on unmount', async () => {
|
||||
|
||||
const { unmount } = render(<ChildMotionWarmupDemo />);
|
||||
|
||||
expect(await screen.findByText('正在连接摄像头')).toBeTruthy();
|
||||
expect(screen.queryByText('摄像头暂不可用,已切换到本地演示')).toBeNull();
|
||||
expect(screen.getByText('动作数据已连接,等待识别')).toBeTruthy();
|
||||
await vi.waitFor(() => {
|
||||
expect(getUserMedia).toHaveBeenCalledWith({
|
||||
audio: false,
|
||||
|
||||
Reference in New Issue
Block a user