Files
pepperplus-ui/test/pages/monitoringPage/MonitoringPageComponents.test.tsx
2026-01-18 12:49:14 +01:00

227 lines
7.4 KiB
TypeScript

import React from 'react';
import { render, screen, fireEvent, act } from '@testing-library/react';
import '@testing-library/jest-dom';
// Corrected Imports
import {
GestureControls,
SpeechPresets,
DirectSpeechInput,
StatusList,
RobotConnected
} from '../../../src/pages/MonitoringPage/MonitoringPageComponents';
import * as MonitoringAPI from '../../../src/pages/MonitoringPage/MonitoringPageAPI';
// Mock the API Call function with the correct path
jest.mock('../../../src/pages/MonitoringPage/MonitoringPageAPI', () => ({
sendAPICall: jest.fn(),
}));
describe('MonitoringPageComponents', () => {
beforeEach(() => {
jest.clearAllMocks();
});
describe('GestureControls', () => {
test('renders and sends gesture command', () => {
render(<GestureControls />);
fireEvent.change(screen.getByRole('combobox'), {
target: { value: 'animations/Stand/Gestures/Hey_1' }
});
// Click button
fireEvent.click(screen.getByText('Actuate'));
// Expect the API to be called with that new value
expect(MonitoringAPI.sendAPICall).toHaveBeenCalledWith('gesture', 'animations/Stand/Gestures/Hey_1');
});
});
describe('SpeechPresets', () => {
test('renders buttons and sends speech command', () => {
render(<SpeechPresets />);
const btn = screen.getByText('"Hello, I\'m Pepper"');
fireEvent.click(btn);
expect(MonitoringAPI.sendAPICall).toHaveBeenCalledWith('speech', "Hello, I'm Pepper");
});
});
describe('DirectSpeechInput', () => {
test('inputs text and sends on button click', () => {
render(<DirectSpeechInput />);
const input = screen.getByPlaceholderText('Type message...');
fireEvent.change(input, { target: { value: 'Custom text' } });
fireEvent.click(screen.getByText('Send'));
expect(MonitoringAPI.sendAPICall).toHaveBeenCalledWith('speech', 'Custom text');
expect(input).toHaveValue(''); // Should clear
});
test('sends on Enter key', () => {
render(<DirectSpeechInput />);
const input = screen.getByPlaceholderText('Type message...');
fireEvent.change(input, { target: { value: 'Enter text' } });
fireEvent.keyDown(input, { key: 'Enter', code: 'Enter' });
expect(MonitoringAPI.sendAPICall).toHaveBeenCalledWith('speech', 'Enter text');
});
test('does not send empty text', () => {
render(<DirectSpeechInput />);
fireEvent.click(screen.getByText('Send'));
expect(MonitoringAPI.sendAPICall).not.toHaveBeenCalled();
});
});
describe('StatusList', () => {
const mockSet = jest.fn();
const items = [
{ id: '1', name: 'Item 1' },
{ id: '2', name: 'Item 2' }
];
test('renders list items', () => {
render(<StatusList title="Test List" items={items} type="goal" activeIds={{}} />);
expect(screen.getByText('Test List')).toBeInTheDocument();
expect(screen.getByText('Item 1')).toBeInTheDocument();
});
test('Goals: click override on inactive item calls API', () => {
render(
<StatusList
title="Goals"
items={items}
type="goal"
activeIds={{}}
setActiveIds={mockSet}
/>
);
// Click the X (inactive)
const indicator = screen.getAllByText('❌')[0];
fireEvent.click(indicator);
expect(MonitoringAPI.sendAPICall).toHaveBeenCalledWith('override', '1');
expect(mockSet).toHaveBeenCalled();
});
test('Conditional Norms: click override on ACTIVE item unachieves', () => {
render(
<StatusList
title="CN"
items={items}
type="cond_norm"
activeIds={{ '1': true }}
/>
);
const indicator = screen.getByText('✔️'); // It is active
fireEvent.click(indicator);
expect(MonitoringAPI.sendAPICall).toHaveBeenCalledWith('override_unachieve', '1');
});
test('Current Goal highlighting', () => {
render(
<StatusList
title="Goals"
items={items}
type="goal"
activeIds={{}}
currentGoalIndex={0}
/>
);
// Using regex to handle the "(Current)" text
expect(screen.getByText(/Item 1/)).toBeInTheDocument();
expect(screen.getByText(/(Current)/)).toBeInTheDocument();
});
});
describe('RobotConnected', () => {
let mockEventSource: any;
beforeAll(() => {
Object.defineProperty(window, 'EventSource', {
writable: true,
value: jest.fn().mockImplementation(() => ({
close: jest.fn(),
onmessage: null,
})),
});
});
beforeEach(() => {
mockEventSource = new window.EventSource('url');
(window.EventSource as unknown as jest.Mock).mockClear();
(window.EventSource as unknown as jest.Mock).mockImplementation(() => mockEventSource);
});
test('displays disconnected initially', () => {
render(<RobotConnected />);
expect(screen.getByText('● Robot is disconnected')).toBeInTheDocument();
});
test('updates to connected when SSE receives true', async () => {
render(<RobotConnected />);
act(() => {
if(mockEventSource.onmessage) {
mockEventSource.onmessage({ data: 'true' } as MessageEvent);
}
});
expect(await screen.findByText('● Robot is connected')).toBeInTheDocument();
});
test('handles invalid JSON gracefully', async () => {
const consoleSpy = jest.spyOn(console, 'log').mockImplementation(() => {});
render(<RobotConnected />);
act(() => {
if(mockEventSource.onmessage) {
mockEventSource.onmessage({ data: 'invalid-json' } as MessageEvent);
}
});
// Should catch error and log it, state remains disconnected
expect(consoleSpy).toHaveBeenCalledWith('Ping message not in correct format:', 'invalid-json');
consoleSpy.mockRestore();
});
test('logs error if state update fails (inner catch block)', async () => {
const consoleSpy = jest.spyOn(console, 'log').mockImplementation(() => {});
// 1. Force useState to return a setter that throws an error
const mockThrowingSetter = jest.fn(() => { throw new Error('Forced State Error'); });
// We use mockImplementation to return [currentState, throwingSetter]
const useStateSpy = jest.spyOn(React, 'useState')
.mockImplementation(() => [null, mockThrowingSetter]);
render(<RobotConnected />);
// 2. Trigger the event with VALID JSON ("true")
// This passes the first JSON.parse try/catch,
// but fails when calling setConnected(true) because of our mock.
await act(async () => {
if (mockEventSource.onmessage) {
mockEventSource.onmessage({ data: 'true' } as MessageEvent);
}
});
// 3. Verify the specific error log from line 205
expect(consoleSpy).toHaveBeenCalledWith("couldnt extract connected from incoming ping data");
// Cleanup spies
useStateSpy.mockRestore();
consoleSpy.mockRestore();
});
});
});