230 lines
7.6 KiB
TypeScript
230 lines
7.6 KiB
TypeScript
// This program has been developed by students from the bachelor Computer Science at Utrecht
|
|
// University within the Software Project course.
|
|
// © Copyright Utrecht University (Department of Information and Computing Sciences)
|
|
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();
|
|
});
|
|
});
|
|
});
|