feat(edutainment): refresh baby object match flow
This commit is contained in:
@@ -48,6 +48,8 @@ const mocapMock = vi.hoisted(() => ({
|
||||
rightShoulder?: { x: number; y: number } | null;
|
||||
leftElbow?: { x: number; y: number } | null;
|
||||
rightElbow?: { x: number; y: number } | null;
|
||||
leftWrist?: { x: number; y: number } | null;
|
||||
rightWrist?: { x: number; y: number } | null;
|
||||
};
|
||||
},
|
||||
receivedAtMs: 1,
|
||||
@@ -242,16 +244,18 @@ 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.queryByRole('heading', { name: '来到圆圈这里' })).toBeNull();
|
||||
expect(screen.getByText('欢迎你,小朋友,见到你真开心')).toBeTruthy();
|
||||
expect(screen.queryByLabelText('绿色圆环')).toBeNull();
|
||||
expect(screen.getByText('请横屏体验')).toBeTruthy();
|
||||
});
|
||||
|
||||
test('shows narration first before revealing the step cue', async () => {
|
||||
test('shows the first subtitle before revealing the step cue', async () => {
|
||||
vi.useFakeTimers();
|
||||
render(<ChildMotionWarmupDemo />);
|
||||
|
||||
expect(screen.getByText('来到圆圈这里')).toBeTruthy();
|
||||
expect(screen.getByText('欢迎你,小朋友,见到你真开心')).toBeTruthy();
|
||||
expect(screen.queryByText('来圆圈这里和我打个招呼吧')).toBeNull();
|
||||
expect(screen.queryByLabelText('绿色圆环')).toBeNull();
|
||||
expect(screen.getByTestId('child-motion-stage').dataset.stepPhase).toBe('intro');
|
||||
|
||||
@@ -261,6 +265,25 @@ test('shows narration first before revealing the step cue', async () => {
|
||||
expect(screen.getByTestId('child-motion-stage').dataset.stepPhase).toBe('active');
|
||||
});
|
||||
|
||||
test('switches the center step subtitle to the second line after a two second pause', async () => {
|
||||
vi.useFakeTimers();
|
||||
render(<ChildMotionWarmupDemo />);
|
||||
|
||||
expect(screen.queryByRole('heading', { name: '来到圆圈这里' })).toBeNull();
|
||||
expect(screen.getByText('欢迎你,小朋友,见到你真开心')).toBeTruthy();
|
||||
expect(screen.queryByText('来圆圈这里和我打个招呼吧')).toBeNull();
|
||||
|
||||
await advanceWarmupTime(1999);
|
||||
|
||||
expect(screen.getByText('欢迎你,小朋友,见到你真开心')).toBeTruthy();
|
||||
expect(screen.queryByText('来圆圈这里和我打个招呼吧')).toBeNull();
|
||||
|
||||
await advanceWarmupTime(1);
|
||||
|
||||
expect(screen.queryByText('欢迎你,小朋友,见到你真开心')).toBeNull();
|
||||
expect(screen.getByText('来圆圈这里和我打个招呼吧')).toBeTruthy();
|
||||
});
|
||||
|
||||
test('re-entering within the same runtime session opens the start button', () => {
|
||||
markChildMotionWarmupCompletedInRuntime();
|
||||
|
||||
@@ -299,6 +322,50 @@ test('developer keyboard input moves the avatar and triggers jump state', () =>
|
||||
expect(avatar.className).toContain('child-motion-avatar--jumping');
|
||||
});
|
||||
|
||||
test('developer pointer input renders baby object hand indicators in warmup', async () => {
|
||||
vi.useFakeTimers();
|
||||
render(<ChildMotionWarmupDemo />);
|
||||
|
||||
const stage = screen.getByTestId('child-motion-stage');
|
||||
vi.spyOn(stage, 'getBoundingClientRect').mockReturnValue({
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 1000,
|
||||
height: 500,
|
||||
top: 0,
|
||||
right: 1000,
|
||||
bottom: 500,
|
||||
left: 0,
|
||||
toJSON: () => ({}),
|
||||
});
|
||||
|
||||
await revealCurrentStepCue();
|
||||
const pointerDownEvent = new Event('pointerdown', {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
});
|
||||
Object.defineProperties(pointerDownEvent, {
|
||||
button: { value: 0 },
|
||||
buttons: { value: 1 },
|
||||
clientX: { value: 250 },
|
||||
clientY: { value: 150 },
|
||||
pointerId: { value: 1 },
|
||||
});
|
||||
await act(async () => {
|
||||
stage.dispatchEvent(pointerDownEvent);
|
||||
});
|
||||
|
||||
const leftHand = screen.getByTestId('child-motion-left-hand-indicator');
|
||||
expect(leftHand.className).toContain('baby-object-runtime__hand--left');
|
||||
expect(leftHand.getAttribute('style')).toContain(
|
||||
'--baby-object-hand-x: 25%',
|
||||
);
|
||||
expect(leftHand.getAttribute('style')).toContain(
|
||||
'--baby-object-hand-y: 30%',
|
||||
);
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
test('mocap body center dampens small jitter before moving the avatar', async () => {
|
||||
setMocapBodyCenter(0.5);
|
||||
const { rerender } = render(<ChildMotionWarmupDemo />);
|
||||
@@ -325,6 +392,68 @@ test('mocap body center dampens small jitter before moving the avatar', async ()
|
||||
expect(style).not.toContain('left: 34%');
|
||||
});
|
||||
|
||||
test('mocap hand positions render with baby object hand indicators in body-side mapping', async () => {
|
||||
setMocapCameraHandTrackPoint({ cameraSide: 'right', x: 0.24, y: 0.36 });
|
||||
const { rerender } = render(<ChildMotionWarmupDemo />);
|
||||
|
||||
await act(async () => {
|
||||
rerender(<ChildMotionWarmupDemo />);
|
||||
});
|
||||
|
||||
const leftHand = await screen.findByTestId(
|
||||
'child-motion-left-hand-indicator',
|
||||
);
|
||||
expect(leftHand.className).toContain('baby-object-runtime__hand--left');
|
||||
expect(leftHand.getAttribute('style')).toContain(
|
||||
'--baby-object-hand-x: 24%',
|
||||
);
|
||||
expect(leftHand.getAttribute('style')).toContain(
|
||||
'--baby-object-hand-y: 36%',
|
||||
);
|
||||
expect(screen.queryByTestId('child-motion-right-hand-indicator')).toBeNull();
|
||||
});
|
||||
|
||||
test('mocap hand indicators prefer skeleton wrist nodes in warmup', async () => {
|
||||
const cameraRightHand = {
|
||||
x: 0.24,
|
||||
y: 0.36,
|
||||
state: 'unknown',
|
||||
side: 'right',
|
||||
wrist: { x: 0.27, y: 0.39 },
|
||||
};
|
||||
mocapMock.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 },
|
||||
rightWrist: { x: 0.64, y: 0.25 },
|
||||
},
|
||||
hands: [cameraRightHand],
|
||||
primaryHand: cameraRightHand,
|
||||
leftHand: null,
|
||||
rightHand: cameraRightHand,
|
||||
};
|
||||
mocapMock.receivedAtMs += 1;
|
||||
const { rerender } = render(<ChildMotionWarmupDemo />);
|
||||
|
||||
await act(async () => {
|
||||
rerender(<ChildMotionWarmupDemo />);
|
||||
});
|
||||
|
||||
const leftHand = await screen.findByTestId(
|
||||
'child-motion-left-hand-indicator',
|
||||
);
|
||||
expect(leftHand.getAttribute('style')).toContain(
|
||||
'--baby-object-hand-x: 64%',
|
||||
);
|
||||
expect(leftHand.getAttribute('style')).toContain(
|
||||
'--baby-object-hand-y: 25%',
|
||||
);
|
||||
});
|
||||
|
||||
test('mocap body center keeps the warmup flow on the motion data source', async () => {
|
||||
vi.useFakeTimers();
|
||||
setMocapBodyCenter(0.5);
|
||||
@@ -440,6 +569,33 @@ test('mocap greeting requires a real horizontal wave track', async () => {
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
test('greeting completion goes to warmup intro without praise float text', async () => {
|
||||
vi.useFakeTimers();
|
||||
const { rerender, unmount } = render(<ChildMotionWarmupDemo />);
|
||||
|
||||
await revealCurrentStepCue();
|
||||
await completeCurrentPositionStepByHold();
|
||||
await vi.waitFor(() => {
|
||||
expect(screen.getByText('打个招呼')).toBeTruthy();
|
||||
});
|
||||
|
||||
await revealCurrentStepCue();
|
||||
await completeGreetingByWaveTrack(rerender);
|
||||
|
||||
expect(screen.queryByText('真棒')).toBeNull();
|
||||
|
||||
await advanceWarmupTime(900);
|
||||
await vi.waitFor(() => {
|
||||
expect(screen.getByText('准备热身')).toBeTruthy();
|
||||
});
|
||||
expect(screen.queryByText('真棒')).toBeNull();
|
||||
|
||||
await act(async () => {
|
||||
unmount();
|
||||
});
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
test('mocap arm swing steps require body-side mapping and vertical open arm motion', async () => {
|
||||
vi.useFakeTimers();
|
||||
const { rerender, unmount } = render(<ChildMotionWarmupDemo />);
|
||||
@@ -477,6 +633,14 @@ test('mocap arm swing steps require body-side mapping and vertical open arm moti
|
||||
});
|
||||
|
||||
await revealCurrentStepCue();
|
||||
expect(screen.getByTestId('child-motion-arm-swing-guide-left')).toBeTruthy();
|
||||
expect(screen.queryByTestId('child-motion-arm-swing-guide-right')).toBeNull();
|
||||
expect(
|
||||
screen
|
||||
.getByTestId('child-motion-arm-swing-guide-left')
|
||||
.querySelector('.child-motion-gesture-guide__arm-swing-paw-asset'),
|
||||
).toBeTruthy();
|
||||
|
||||
await sendMocapCameraHandTrack(rerender, 'left', [
|
||||
{ x: 0.78, y: 0.5 },
|
||||
{ x: 0.86, y: 0.5 },
|
||||
@@ -505,6 +669,14 @@ test('mocap arm swing steps require body-side mapping and vertical open arm moti
|
||||
});
|
||||
|
||||
await revealCurrentStepCue();
|
||||
expect(screen.getByTestId('child-motion-arm-swing-guide-right')).toBeTruthy();
|
||||
expect(screen.queryByTestId('child-motion-arm-swing-guide-left')).toBeNull();
|
||||
expect(
|
||||
screen
|
||||
.getByTestId('child-motion-arm-swing-guide-right')
|
||||
.querySelector('.child-motion-gesture-guide__arm-swing-paw-asset'),
|
||||
).toBeTruthy();
|
||||
|
||||
await sendMocapCameraHandTrack(rerender, 'right', [
|
||||
{ x: 0.2, y: 0.5 },
|
||||
{ x: 0.16, y: 0.42 },
|
||||
@@ -519,8 +691,9 @@ test('mocap arm swing steps require body-side mapping and vertical open arm moti
|
||||
|
||||
await advanceWarmupTime(900);
|
||||
await vi.waitFor(() => {
|
||||
expect(screen.getByRole('heading', { name: '原地跳一下' })).toBeTruthy();
|
||||
expect(screen.getByRole('heading', { name: '热身完成' })).toBeTruthy();
|
||||
});
|
||||
expect(screen.queryByRole('heading', { name: '原地跳一下' })).toBeNull();
|
||||
await advanceWarmupTime(720);
|
||||
await act(async () => {
|
||||
unmount();
|
||||
|
||||
Reference in New Issue
Block a user