feat: add edutainment drawing and visual package flows
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
/* @vitest-environment jsdom */
|
||||
|
||||
import { act, fireEvent, render, screen } from '@testing-library/react';
|
||||
import type { ReactElement } from 'react';
|
||||
import { afterEach, beforeEach, expect, test, vi } from 'vitest';
|
||||
|
||||
import { ChildMotionWarmupDemo } from './ChildMotionWarmupDemo';
|
||||
@@ -13,11 +14,41 @@ 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;
|
||||
hands?: Array<{
|
||||
x: number;
|
||||
y: number;
|
||||
state: string;
|
||||
side: string;
|
||||
wrist?: { x: number; y: number } | null;
|
||||
}>;
|
||||
primaryHand?: {
|
||||
x: number;
|
||||
y: number;
|
||||
state: string;
|
||||
side: string;
|
||||
wrist?: { x: number; y: number } | null;
|
||||
} | null;
|
||||
leftHand?: {
|
||||
x: number;
|
||||
y: number;
|
||||
state: string;
|
||||
side: string;
|
||||
wrist?: { x: number; y: number } | null;
|
||||
} | null;
|
||||
rightHand?: {
|
||||
x: number;
|
||||
y: number;
|
||||
state: string;
|
||||
side: string;
|
||||
wrist?: { x: number; y: number } | null;
|
||||
} | null;
|
||||
bodyCenter?: { x: number; y: number } | null;
|
||||
bodyJoints?: {
|
||||
leftShoulder?: { x: number; y: number } | null;
|
||||
rightShoulder?: { x: number; y: number } | null;
|
||||
leftElbow?: { x: number; y: number } | null;
|
||||
rightElbow?: { x: number; y: number } | null;
|
||||
};
|
||||
},
|
||||
receivedAtMs: 1,
|
||||
}));
|
||||
@@ -66,15 +97,170 @@ afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
function setMocapBodyCenter(x: number) {
|
||||
mocapMock.command = {
|
||||
actions: [],
|
||||
bodyCenter: { x, y: 0.6 },
|
||||
hands: [],
|
||||
primaryHand: null,
|
||||
leftHand: null,
|
||||
rightHand: null,
|
||||
};
|
||||
mocapMock.receivedAtMs += 1;
|
||||
}
|
||||
|
||||
async function advanceWarmupTime(ms: number) {
|
||||
await act(async () => {
|
||||
vi.advanceTimersByTime(ms);
|
||||
});
|
||||
}
|
||||
|
||||
async function revealCurrentStepCue() {
|
||||
await advanceWarmupTime(1100);
|
||||
}
|
||||
|
||||
async function completeCurrentPositionStepByHold() {
|
||||
await advanceWarmupTime(2200);
|
||||
await advanceWarmupTime(900);
|
||||
}
|
||||
|
||||
async function completeCurrentNarrationStep() {
|
||||
await revealCurrentStepCue();
|
||||
await advanceWarmupTime(1000);
|
||||
await advanceWarmupTime(900);
|
||||
}
|
||||
|
||||
async function sendMocapLeftHandTrack(
|
||||
rerender: (ui: ReactElement) => void,
|
||||
points: number[],
|
||||
options: { raised?: boolean } = {},
|
||||
) {
|
||||
for (const x of points) {
|
||||
const y = options.raised ? 0.34 : 0.72;
|
||||
const wrist = { x, y };
|
||||
mocapMock.command = {
|
||||
actions: [],
|
||||
bodyCenter: { x: 0.5, y: 0.7 },
|
||||
bodyJoints: {
|
||||
leftShoulder: { x: 0.4, y: 0.42 },
|
||||
leftElbow: { x: 0.36, y: 0.5 },
|
||||
},
|
||||
hands: [{ x, y, state: 'unknown', side: 'left', wrist }],
|
||||
primaryHand: { x, y, state: 'unknown', side: 'left', wrist },
|
||||
leftHand: { x, y, state: 'unknown', side: 'left', wrist },
|
||||
rightHand: null,
|
||||
};
|
||||
mocapMock.receivedAtMs += 1;
|
||||
await act(async () => {
|
||||
rerender(<ChildMotionWarmupDemo />);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function setMocapCameraHandTrackPoint({
|
||||
cameraSide,
|
||||
x,
|
||||
y,
|
||||
}: {
|
||||
cameraSide: 'left' | 'right';
|
||||
x: number;
|
||||
y: number;
|
||||
}) {
|
||||
const wrist = { x, y };
|
||||
const hand = { x, y, state: 'unknown', side: cameraSide, wrist };
|
||||
const command = {
|
||||
actions: [],
|
||||
bodyCenter: { x: 0.5, y: 0.7 },
|
||||
bodyJoints: {
|
||||
leftShoulder: { x: 0.62, y: 0.48 },
|
||||
leftElbow: { x: 0.7, y: 0.5 },
|
||||
rightShoulder: { x: 0.38, y: 0.48 },
|
||||
rightElbow: { x: 0.3, y: 0.5 },
|
||||
},
|
||||
hands: [hand],
|
||||
primaryHand: hand,
|
||||
leftHand: null as null | typeof hand,
|
||||
rightHand: null as null | typeof hand,
|
||||
};
|
||||
|
||||
if (cameraSide === 'left') {
|
||||
command.leftHand = hand;
|
||||
} else {
|
||||
command.rightHand = hand;
|
||||
}
|
||||
|
||||
mocapMock.command = command;
|
||||
mocapMock.receivedAtMs += 1;
|
||||
}
|
||||
|
||||
async function sendMocapCameraHandTrack(
|
||||
rerender: (ui: ReactElement) => void,
|
||||
cameraSide: 'left' | 'right',
|
||||
points: Array<{ x: number; y: number }>,
|
||||
) {
|
||||
for (const point of points) {
|
||||
setMocapCameraHandTrackPoint({ cameraSide, ...point });
|
||||
await act(async () => {
|
||||
rerender(<ChildMotionWarmupDemo />);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function sendPlayerLeftArmSwingTrack(
|
||||
rerender: (ui: ReactElement) => void,
|
||||
) {
|
||||
await sendMocapCameraHandTrack(rerender, 'right', [
|
||||
{ x: 0.2, y: 0.5 },
|
||||
{ x: 0.16, y: 0.42 },
|
||||
{ x: 0.13, y: 0.34 },
|
||||
{ x: 0.15, y: 0.43 },
|
||||
{ x: 0.19, y: 0.51 },
|
||||
]);
|
||||
}
|
||||
|
||||
async function sendPlayerRightArmSwingTrack(
|
||||
rerender: (ui: ReactElement) => void,
|
||||
) {
|
||||
await sendMocapCameraHandTrack(rerender, 'left', [
|
||||
{ x: 0.8, y: 0.5 },
|
||||
{ x: 0.84, y: 0.42 },
|
||||
{ x: 0.87, y: 0.34 },
|
||||
{ x: 0.85, y: 0.43 },
|
||||
{ x: 0.81, y: 0.51 },
|
||||
]);
|
||||
}
|
||||
|
||||
async function completeGreetingByWaveTrack(
|
||||
rerender: (ui: ReactElement) => void,
|
||||
) {
|
||||
await sendMocapLeftHandTrack(rerender, [0.42, 0.51, 0.58, 0.49, 0.43], {
|
||||
raised: true,
|
||||
});
|
||||
}
|
||||
|
||||
test('renders the warmup stage and starts with the center ring step', () => {
|
||||
render(<ChildMotionWarmupDemo />);
|
||||
|
||||
expect(screen.getByTestId('child-motion-demo')).toBeTruthy();
|
||||
expect(screen.getByText('来到圆圈这里')).toBeTruthy();
|
||||
expect(screen.getByLabelText('绿色圆环')).toBeTruthy();
|
||||
expect(screen.queryByLabelText('绿色圆环')).toBeNull();
|
||||
expect(screen.getByText('请横屏体验')).toBeTruthy();
|
||||
});
|
||||
|
||||
test('shows narration first before revealing the step cue', async () => {
|
||||
vi.useFakeTimers();
|
||||
render(<ChildMotionWarmupDemo />);
|
||||
|
||||
expect(screen.getByText('来到圆圈这里')).toBeTruthy();
|
||||
expect(screen.queryByLabelText('绿色圆环')).toBeNull();
|
||||
expect(screen.getByTestId('child-motion-stage').dataset.stepPhase).toBe('intro');
|
||||
|
||||
await advanceWarmupTime(1000);
|
||||
|
||||
expect(screen.getByLabelText('绿色圆环')).toBeTruthy();
|
||||
expect(screen.getByTestId('child-motion-stage').dataset.stepPhase).toBe('active');
|
||||
});
|
||||
|
||||
test('re-entering within the same runtime session opens the start button', () => {
|
||||
markChildMotionWarmupCompletedInRuntime();
|
||||
|
||||
@@ -113,16 +299,35 @@ test('developer keyboard input moves the avatar and triggers jump state', () =>
|
||||
expect(avatar.className).toContain('child-motion-avatar--jumping');
|
||||
});
|
||||
|
||||
test('mocap body center dampens small jitter before moving the avatar', async () => {
|
||||
setMocapBodyCenter(0.5);
|
||||
const { rerender } = render(<ChildMotionWarmupDemo />);
|
||||
|
||||
expect(screen.getByTestId('child-motion-avatar').getAttribute('style')).toContain(
|
||||
'left: 50%',
|
||||
);
|
||||
|
||||
setMocapBodyCenter(0.508);
|
||||
await act(async () => {
|
||||
rerender(<ChildMotionWarmupDemo />);
|
||||
});
|
||||
expect(screen.getByTestId('child-motion-avatar').getAttribute('style')).toContain(
|
||||
'left: 50%',
|
||||
);
|
||||
|
||||
setMocapBodyCenter(0.34);
|
||||
await act(async () => {
|
||||
rerender(<ChildMotionWarmupDemo />);
|
||||
});
|
||||
|
||||
const style = screen.getByTestId('child-motion-avatar').getAttribute('style');
|
||||
expect(style).toContain('left: 46.5%');
|
||||
expect(style).not.toContain('left: 34%');
|
||||
});
|
||||
|
||||
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,
|
||||
};
|
||||
setMocapBodyCenter(0.5);
|
||||
const { rerender, unmount } = render(<ChildMotionWarmupDemo />);
|
||||
|
||||
expect(screen.queryByText('摄像头暂不可用,已切换到本地演示')).toBeNull();
|
||||
@@ -131,63 +336,39 @@ test('mocap body center keeps the warmup flow on the motion data source', async
|
||||
'left: 50%',
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
vi.advanceTimersByTime(2100);
|
||||
await vi.runOnlyPendingTimersAsync();
|
||||
});
|
||||
await revealCurrentStepCue();
|
||||
await completeCurrentPositionStepByHold();
|
||||
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 revealCurrentStepCue();
|
||||
await completeGreetingByWaveTrack(rerender);
|
||||
await advanceWarmupTime(900);
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(screen.getByText('准备热身')).toBeTruthy();
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
vi.advanceTimersByTime(1000);
|
||||
await vi.runOnlyPendingTimersAsync();
|
||||
});
|
||||
await completeCurrentNarrationStep();
|
||||
|
||||
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 revealCurrentStepCue();
|
||||
for (const targetX of [0.34, 0.34, 0.34, 0.34, 0.34]) {
|
||||
setMocapBodyCenter(targetX);
|
||||
await act(async () => {
|
||||
rerender(<ChildMotionWarmupDemo />);
|
||||
});
|
||||
}
|
||||
await vi.waitFor(() => {
|
||||
expect(screen.getByTestId('child-motion-avatar').getAttribute('style')).toContain(
|
||||
'left: 34%',
|
||||
'left: 37',
|
||||
);
|
||||
});
|
||||
await act(async () => {
|
||||
vi.advanceTimersByTime(2100);
|
||||
await vi.runOnlyPendingTimersAsync();
|
||||
});
|
||||
await completeCurrentPositionStepByHold();
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(screen.getByRole('heading', { name: '回到中间来' })).toBeTruthy();
|
||||
@@ -199,18 +380,17 @@ test('mocap body center keeps the warmup flow on the motion data source', async
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
test('mocap open palm completes the greeting wave step', async () => {
|
||||
test('mocap greeting requires a real horizontal wave track', async () => {
|
||||
vi.useFakeTimers();
|
||||
const { rerender, unmount } = render(<ChildMotionWarmupDemo />);
|
||||
|
||||
await act(async () => {
|
||||
vi.advanceTimersByTime(2100);
|
||||
await vi.runOnlyPendingTimersAsync();
|
||||
});
|
||||
await revealCurrentStepCue();
|
||||
await completeCurrentPositionStepByHold();
|
||||
await vi.waitFor(() => {
|
||||
expect(screen.getByText('打个招呼')).toBeTruthy();
|
||||
});
|
||||
|
||||
await revealCurrentStepCue();
|
||||
mocapMock.command = {
|
||||
actions: ['open_palm'],
|
||||
hands: [{ x: 0.46, y: 0.34, state: 'open_palm', side: 'left' }],
|
||||
@@ -222,7 +402,35 @@ test('mocap open palm completes the greeting wave step', async () => {
|
||||
await act(async () => {
|
||||
rerender(<ChildMotionWarmupDemo />);
|
||||
});
|
||||
await advanceWarmupTime(900);
|
||||
expect(screen.getByRole('heading', { name: '打个招呼' })).toBeTruthy();
|
||||
|
||||
await sendMocapLeftHandTrack(rerender, [0.42, 0.51, 0.58, 0.49, 0.43], {
|
||||
raised: false,
|
||||
});
|
||||
await advanceWarmupTime(900);
|
||||
expect(screen.getByRole('heading', { name: '打个招呼' })).toBeTruthy();
|
||||
|
||||
for (const x of [0.42, 0.51, 0.58, 0.49, 0.43]) {
|
||||
const wrist = { x, y: 0.34 };
|
||||
mocapMock.command = {
|
||||
actions: [],
|
||||
bodyCenter: { x: 0.5, y: 0.7 },
|
||||
hands: [{ x, y: 0.34, state: 'unknown', side: 'left', wrist }],
|
||||
primaryHand: { x, y: 0.34, state: 'unknown', side: 'left', wrist },
|
||||
leftHand: { x, y: 0.34, state: 'unknown', side: 'left', wrist },
|
||||
rightHand: null,
|
||||
};
|
||||
mocapMock.receivedAtMs += 1;
|
||||
await act(async () => {
|
||||
rerender(<ChildMotionWarmupDemo />);
|
||||
});
|
||||
}
|
||||
await advanceWarmupTime(900);
|
||||
expect(screen.getByRole('heading', { name: '打个招呼' })).toBeTruthy();
|
||||
|
||||
await completeGreetingByWaveTrack(rerender);
|
||||
await advanceWarmupTime(900);
|
||||
await vi.waitFor(() => {
|
||||
expect(screen.getByText('准备热身')).toBeTruthy();
|
||||
});
|
||||
@@ -232,117 +440,89 @@ test('mocap open palm completes the greeting wave step', async () => {
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
test('mocap hand tracks complete left and right wave steps only after movement is visible', async () => {
|
||||
test('mocap arm swing steps require body-side mapping and vertical open arm motion', async () => {
|
||||
vi.useFakeTimers();
|
||||
const { rerender, unmount } = render(<ChildMotionWarmupDemo />);
|
||||
|
||||
const advancePositionStep = async (key: string, code: string) => {
|
||||
await revealCurrentStepCue();
|
||||
await act(async () => {
|
||||
fireEvent.keyDown(window, { key, code });
|
||||
});
|
||||
await act(async () => {
|
||||
vi.advanceTimersByTime(2100);
|
||||
await vi.runOnlyPendingTimersAsync();
|
||||
});
|
||||
await completeCurrentPositionStepByHold();
|
||||
await act(async () => {
|
||||
fireEvent.keyUp(window, { key, code });
|
||||
});
|
||||
};
|
||||
|
||||
await act(async () => {
|
||||
vi.advanceTimersByTime(2100);
|
||||
await vi.runOnlyPendingTimersAsync();
|
||||
});
|
||||
await revealCurrentStepCue();
|
||||
await completeCurrentPositionStepByHold();
|
||||
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 revealCurrentStepCue();
|
||||
await completeGreetingByWaveTrack(rerender);
|
||||
|
||||
await act(async () => {
|
||||
vi.advanceTimersByTime(1000);
|
||||
await vi.runOnlyPendingTimersAsync();
|
||||
});
|
||||
await advanceWarmupTime(900);
|
||||
await completeCurrentNarrationStep();
|
||||
await advancePositionStep('a', 'KeyA');
|
||||
await act(async () => {
|
||||
vi.advanceTimersByTime(120);
|
||||
await vi.runOnlyPendingTimersAsync();
|
||||
vi.advanceTimersByTime(2100);
|
||||
await vi.runOnlyPendingTimersAsync();
|
||||
});
|
||||
await revealCurrentStepCue();
|
||||
await completeCurrentPositionStepByHold();
|
||||
await advancePositionStep('d', 'KeyD');
|
||||
await act(async () => {
|
||||
vi.advanceTimersByTime(120);
|
||||
await vi.runOnlyPendingTimersAsync();
|
||||
vi.advanceTimersByTime(2100);
|
||||
await vi.runOnlyPendingTimersAsync();
|
||||
});
|
||||
await revealCurrentStepCue();
|
||||
await completeCurrentPositionStepByHold();
|
||||
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 revealCurrentStepCue();
|
||||
await sendMocapCameraHandTrack(rerender, 'left', [
|
||||
{ x: 0.78, y: 0.5 },
|
||||
{ x: 0.86, y: 0.5 },
|
||||
{ x: 0.79, y: 0.5 },
|
||||
{ x: 0.87, y: 0.5 },
|
||||
{ x: 0.8, y: 0.5 },
|
||||
]);
|
||||
await advanceWarmupTime(900);
|
||||
expect(screen.getByRole('heading', { name: '挥动左手' })).toBeTruthy();
|
||||
|
||||
await sendMocapCameraHandTrack(rerender, 'right', [
|
||||
{ x: 0.32, y: 0.74 },
|
||||
{ x: 0.24, y: 0.74 },
|
||||
{ x: 0.31, y: 0.74 },
|
||||
{ x: 0.23, y: 0.74 },
|
||||
{ x: 0.3, y: 0.74 },
|
||||
]);
|
||||
await advanceWarmupTime(900);
|
||||
expect(screen.getByRole('heading', { name: '挥动左手' })).toBeTruthy();
|
||||
|
||||
await sendPlayerLeftArmSwingTrack(rerender);
|
||||
|
||||
await advanceWarmupTime(900);
|
||||
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 revealCurrentStepCue();
|
||||
await sendMocapCameraHandTrack(rerender, 'right', [
|
||||
{ x: 0.2, y: 0.5 },
|
||||
{ x: 0.16, y: 0.42 },
|
||||
{ x: 0.13, y: 0.34 },
|
||||
{ x: 0.15, y: 0.43 },
|
||||
{ x: 0.19, y: 0.51 },
|
||||
]);
|
||||
await advanceWarmupTime(900);
|
||||
expect(screen.getByRole('heading', { name: '挥动右手' })).toBeTruthy();
|
||||
|
||||
await sendPlayerRightArmSwingTrack(rerender);
|
||||
|
||||
await advanceWarmupTime(900);
|
||||
await vi.waitFor(() => {
|
||||
expect(screen.getByRole('heading', { name: '原地跳一下' })).toBeTruthy();
|
||||
});
|
||||
await advanceWarmupTime(720);
|
||||
await act(async () => {
|
||||
vi.advanceTimersByTime(720);
|
||||
await vi.runOnlyPendingTimersAsync();
|
||||
unmount();
|
||||
});
|
||||
vi.useRealTimers();
|
||||
|
||||
Reference in New Issue
Block a user