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(); 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(); 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(); 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(); 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(); 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(); expect(screen.getByText('Test List')).toBeInTheDocument(); expect(screen.getByText('Item 1')).toBeInTheDocument(); }); test('Goals: click override on inactive item calls API', () => { render( ); // 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( ); const indicator = screen.getByText('✔️'); // It is active fireEvent.click(indicator); expect(MonitoringAPI.sendAPICall).toHaveBeenCalledWith('override_unachieve', '1'); }); test('Current Goal highlighting', () => { render( ); // 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(); expect(screen.getByText('● Robot is disconnected')).toBeInTheDocument(); }); test('updates to connected when SSE receives true', async () => { render(); 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(); 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(); // 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(); }); }); });