Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 30 additions & 22 deletions ui/mobile/src/__tests__/hooks/usePoseStream.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// usePoseStream is a React hook that uses useEffect, zustand stores, and wsService.
// We test its interface shape and the module export.
import React from 'react';
import { Text } from 'react-native';
import { render, screen } from '@testing-library/react-native';

jest.mock('@/services/ws.service', () => ({
wsService: {
Expand All @@ -10,36 +11,43 @@ jest.mock('@/services/ws.service', () => ({
},
}));

import { usePoseStream } from '@/hooks/usePoseStream';
import { usePoseStore } from '@/stores/poseStore';

const HookConsumer = () => {
const { connectionStatus, lastFrame, isSimulated } = usePoseStream();

return React.createElement(
React.Fragment,
null,
React.createElement(Text, { testID: 'status' }, connectionStatus),
React.createElement(Text, { testID: 'simulated' }, String(isSimulated)),
React.createElement(Text, { testID: 'frame' }, lastFrame ? 'present' : 'none'),
);
};

describe('usePoseStream', () => {
beforeEach(() => {
usePoseStore.getState().reset();
const { wsService } = require('@/services/ws.service');
wsService.subscribe.mockClear();
wsService.connect.mockClear();
});

it('module exports usePoseStream function', () => {
const mod = require('@/hooks/usePoseStream');
expect(typeof mod.usePoseStream).toBe('function');
});

it('exports UsePoseStreamResult interface (module shape)', () => {
// Verify the module has the expected named exports
const mod = require('@/hooks/usePoseStream');
expect(mod).toHaveProperty('usePoseStream');
});
it('returns the expected store-backed values', () => {
render(React.createElement(HookConsumer));

it('usePoseStream has the expected return type shape', () => {
// We cannot call hooks outside of React components, but we can verify
// the store provides the data the hook returns.
const state = usePoseStore.getState();
expect(state).toHaveProperty('connectionStatus');
expect(state).toHaveProperty('lastFrame');
expect(state).toHaveProperty('isSimulated');
expect(screen.getByTestId('status').props.children).toBe('disconnected');
expect(screen.getByTestId('simulated').props.children).toBe('false');
expect(screen.getByTestId('frame').props.children).toBe('none');
});

it('wsService.subscribe is callable', () => {
it('does not subscribe or reconnect on render', () => {
const { wsService } = require('@/services/ws.service');
const unsub = wsService.subscribe(jest.fn());
expect(typeof unsub).toBe('function');

render(React.createElement(HookConsumer));

expect(wsService.subscribe).not.toHaveBeenCalled();
expect(wsService.connect).not.toHaveBeenCalled();
});
});
17 changes: 0 additions & 17 deletions ui/mobile/src/hooks/usePoseStream.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
import { useEffect } from 'react';
import { wsService } from '@/services/ws.service';
import { usePoseStore } from '@/stores/poseStore';
import { useSettingsStore } from '@/stores/settingsStore';

export interface UsePoseStreamResult {
connectionStatus: ReturnType<typeof usePoseStore.getState>['connectionStatus'];
Expand All @@ -13,20 +10,6 @@ export function usePoseStream(): UsePoseStreamResult {
const connectionStatus = usePoseStore((state) => state.connectionStatus);
const lastFrame = usePoseStore((state) => state.lastFrame);
const isSimulated = usePoseStore((state) => state.isSimulated);
const serverUrl = useSettingsStore((state) => state.serverUrl);

useEffect(() => {
const unsubscribe = wsService.subscribe((frame) => {
usePoseStore.getState().handleFrame(frame);
});

// Auto-connect to sensing server on mount
wsService.connect(serverUrl);

return () => {
unsubscribe();
};
}, [serverUrl]);

return { connectionStatus, lastFrame, isSimulated };
}