fixed tests
This commit is contained in:
288
tests/frontend/MediaHandler.test.ts
Normal file
288
tests/frontend/MediaHandler.test.ts
Normal file
@@ -0,0 +1,288 @@
|
||||
import { test, expect, describe, beforeEach, afterEach } from "bun:test";
|
||||
|
||||
// Mock MediaStream and related APIs
|
||||
class MockMediaStreamTrack {
|
||||
kind: string;
|
||||
id: string;
|
||||
label: string = '';
|
||||
enabled: boolean = true;
|
||||
muted: boolean = false;
|
||||
readyState: 'live' | 'ended' = 'live';
|
||||
|
||||
constructor(kind: string) {
|
||||
this.kind = kind;
|
||||
this.id = Math.random().toString(36);
|
||||
}
|
||||
|
||||
stop() {
|
||||
this.readyState = 'ended';
|
||||
}
|
||||
|
||||
clone() {
|
||||
return new MockMediaStreamTrack(this.kind);
|
||||
}
|
||||
}
|
||||
|
||||
class MockMediaStream {
|
||||
id: string;
|
||||
active: boolean = true;
|
||||
private tracks: MockMediaStreamTrack[] = [];
|
||||
|
||||
constructor(tracks?: MockMediaStreamTrack[]) {
|
||||
this.id = Math.random().toString(36);
|
||||
if (tracks) {
|
||||
this.tracks = [...tracks];
|
||||
}
|
||||
}
|
||||
|
||||
getTracks() {
|
||||
return [...this.tracks];
|
||||
}
|
||||
|
||||
getVideoTracks() {
|
||||
return this.tracks.filter(track => track.kind === 'video');
|
||||
}
|
||||
|
||||
getAudioTracks() {
|
||||
return this.tracks.filter(track => track.kind === 'audio');
|
||||
}
|
||||
|
||||
addTrack(track: MockMediaStreamTrack) {
|
||||
this.tracks.push(track);
|
||||
}
|
||||
|
||||
removeTrack(track: MockMediaStreamTrack) {
|
||||
const index = this.tracks.indexOf(track);
|
||||
if (index > -1) {
|
||||
this.tracks.splice(index, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MockHTMLVideoElement {
|
||||
srcObject: MockMediaStream | null = null;
|
||||
autoplay: boolean = false;
|
||||
muted: boolean = false;
|
||||
|
||||
play() {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
pause() {}
|
||||
}
|
||||
|
||||
// Mock navigator.mediaDevices
|
||||
let mockGetUserMediaCalls: any[] = [];
|
||||
const mockGetUserMedia = (constraints: any) => {
|
||||
mockGetUserMediaCalls.push(constraints);
|
||||
const tracks = [];
|
||||
if (constraints.video) tracks.push(new MockMediaStreamTrack('video'));
|
||||
if (constraints.audio) tracks.push(new MockMediaStreamTrack('audio'));
|
||||
return Promise.resolve(new MockMediaStream(tracks));
|
||||
};
|
||||
|
||||
(global as any).navigator = {
|
||||
mediaDevices: {
|
||||
getUserMedia: mockGetUserMedia
|
||||
}
|
||||
};
|
||||
|
||||
// Mock document.getElementById
|
||||
let getElementByIdReturn: any = null;
|
||||
(global as any).document = {
|
||||
getElementById: () => getElementByIdReturn
|
||||
};
|
||||
|
||||
// Import after mocking
|
||||
import { MediaHandler } from "../../public/js/services/MediaHandler.ts";
|
||||
|
||||
describe("MediaHandler", () => {
|
||||
let mediaHandler: MediaHandler;
|
||||
let mockVideoElement: MockHTMLVideoElement;
|
||||
|
||||
beforeEach(() => {
|
||||
mockVideoElement = new MockHTMLVideoElement();
|
||||
getElementByIdReturn = mockVideoElement;
|
||||
mockGetUserMediaCalls.length = 0; // Clear calls array
|
||||
});
|
||||
|
||||
test("should initialize with video element", () => {
|
||||
mediaHandler = new MediaHandler('testVideo');
|
||||
|
||||
// The MediaHandler should have found and stored the video element
|
||||
expect(mediaHandler.getLocalVideo()).toBe(mockVideoElement);
|
||||
});
|
||||
|
||||
test("should initialize without video element", () => {
|
||||
mediaHandler = new MediaHandler();
|
||||
|
||||
expect(mediaHandler.getLocalVideo()).toBeNull();
|
||||
});
|
||||
|
||||
test("should get local stream successfully", async () => {
|
||||
mediaHandler = new MediaHandler('testVideo');
|
||||
|
||||
const mockTracks = [
|
||||
new MockMediaStreamTrack('video'),
|
||||
new MockMediaStreamTrack('audio')
|
||||
];
|
||||
const mockStream = new MockMediaStream(mockTracks);
|
||||
|
||||
// Override mock to return this specific stream and track calls
|
||||
const originalGetUserMedia = (global as any).navigator.mediaDevices.getUserMedia;
|
||||
const getUserMediaCalls: any[] = [];
|
||||
(global as any).navigator.mediaDevices.getUserMedia = (constraints: any) => {
|
||||
getUserMediaCalls.push(constraints);
|
||||
return Promise.resolve(mockStream);
|
||||
};
|
||||
|
||||
const stream = await mediaHandler.getLocalStream();
|
||||
|
||||
expect(getUserMediaCalls).toHaveLength(1);
|
||||
expect(getUserMediaCalls[0]).toEqual({
|
||||
video: {
|
||||
width: { ideal: 1280 },
|
||||
height: { ideal: 720 },
|
||||
frameRate: { ideal: 30 }
|
||||
},
|
||||
audio: true
|
||||
});
|
||||
|
||||
expect(stream).toBe(mockStream);
|
||||
expect(mockVideoElement.srcObject).toBe(mockStream);
|
||||
|
||||
// Restore original
|
||||
(global as any).navigator.mediaDevices.getUserMedia = originalGetUserMedia;
|
||||
});
|
||||
|
||||
test("should handle getUserMedia error", async () => {
|
||||
mediaHandler = new MediaHandler('testVideo');
|
||||
|
||||
const error = new Error('Camera access denied');
|
||||
|
||||
// Override mock to reject with error
|
||||
const originalGetUserMedia = (global as any).navigator.mediaDevices.getUserMedia;
|
||||
const originalConsoleError = console.error;
|
||||
const errorLogs: any[] = [];
|
||||
console.error = (...args: any[]) => errorLogs.push(args);
|
||||
|
||||
(global as any).navigator.mediaDevices.getUserMedia = () => Promise.reject(error);
|
||||
|
||||
await expect(mediaHandler.getLocalStream()).rejects.toThrow('Camera access denied');
|
||||
|
||||
expect(errorLogs.length).toBeGreaterThan(0);
|
||||
expect(errorLogs[0][0]).toBe('Error accessing media devices:');
|
||||
expect(errorLogs[0][1]).toBe(error);
|
||||
|
||||
// Restore originals
|
||||
console.error = originalConsoleError;
|
||||
(global as any).navigator.mediaDevices.getUserMedia = originalGetUserMedia;
|
||||
});
|
||||
|
||||
test("should stop local stream", async () => {
|
||||
mediaHandler = new MediaHandler('testVideo');
|
||||
|
||||
const mockTracks = [
|
||||
new MockMediaStreamTrack('video'),
|
||||
new MockMediaStreamTrack('audio')
|
||||
];
|
||||
const mockStream = new MockMediaStream(mockTracks);
|
||||
|
||||
// Override mock to return this specific stream
|
||||
const originalGetUserMedia = (global as any).navigator.mediaDevices.getUserMedia;
|
||||
(global as any).navigator.mediaDevices.getUserMedia = () => Promise.resolve(mockStream);
|
||||
|
||||
await mediaHandler.getLocalStream();
|
||||
|
||||
// Track the initial state of the tracks
|
||||
const initialStates = mockTracks.map(track => track.readyState);
|
||||
|
||||
mediaHandler.stopLocalStream();
|
||||
|
||||
// Check that all tracks were stopped (readyState should be 'ended')
|
||||
mockTracks.forEach(track => {
|
||||
expect(track.readyState).toBe('ended');
|
||||
});
|
||||
|
||||
expect(mockVideoElement.srcObject).toBeNull();
|
||||
expect(mediaHandler.getCurrentStream()).toBeNull();
|
||||
|
||||
// Restore original
|
||||
(global as any).navigator.mediaDevices.getUserMedia = originalGetUserMedia;
|
||||
});
|
||||
|
||||
test("should handle stopping stream when no stream exists", () => {
|
||||
mediaHandler = new MediaHandler('testVideo');
|
||||
|
||||
expect(() => {
|
||||
mediaHandler.stopLocalStream();
|
||||
}).not.toThrow();
|
||||
|
||||
expect(mockVideoElement.srcObject).toBeNull();
|
||||
});
|
||||
|
||||
test("should get current stream", async () => {
|
||||
mediaHandler = new MediaHandler('testVideo');
|
||||
|
||||
expect(mediaHandler.getCurrentStream()).toBeNull();
|
||||
|
||||
const mockStream = new MockMediaStream([new MockMediaStreamTrack('video')]);
|
||||
|
||||
// Override mock to return this specific stream
|
||||
const originalGetUserMedia = (global as any).navigator.mediaDevices.getUserMedia;
|
||||
(global as any).navigator.mediaDevices.getUserMedia = () => Promise.resolve(mockStream);
|
||||
|
||||
await mediaHandler.getLocalStream();
|
||||
|
||||
// Restore original
|
||||
(global as any).navigator.mediaDevices.getUserMedia = originalGetUserMedia;
|
||||
|
||||
expect(mediaHandler.getCurrentStream()).toBe(mockStream);
|
||||
});
|
||||
|
||||
test("should work without video element", async () => {
|
||||
getElementByIdReturn = null;
|
||||
mediaHandler = new MediaHandler('nonExistentVideo');
|
||||
|
||||
const mockStream = new MockMediaStream([new MockMediaStreamTrack('video')]);
|
||||
|
||||
// Override mock to return this specific stream
|
||||
const originalGetUserMedia = (global as any).navigator.mediaDevices.getUserMedia;
|
||||
(global as any).navigator.mediaDevices.getUserMedia = () => Promise.resolve(mockStream);
|
||||
|
||||
const stream = await mediaHandler.getLocalStream();
|
||||
|
||||
expect(stream).toBe(mockStream);
|
||||
expect(mediaHandler.getLocalVideo()).toBeNull();
|
||||
|
||||
// Restore original
|
||||
(global as any).navigator.mediaDevices.getUserMedia = originalGetUserMedia;
|
||||
});
|
||||
|
||||
test("should handle multiple calls to getLocalStream", async () => {
|
||||
mediaHandler = new MediaHandler('testVideo');
|
||||
|
||||
const mockStream1 = new MockMediaStream([new MockMediaStreamTrack('video')]);
|
||||
const mockStream2 = new MockMediaStream([new MockMediaStreamTrack('video')]);
|
||||
|
||||
let callCount = 0;
|
||||
const originalGetUserMedia = (global as any).navigator.mediaDevices.getUserMedia;
|
||||
(global as any).navigator.mediaDevices.getUserMedia = () => {
|
||||
callCount++;
|
||||
return Promise.resolve(callCount === 1 ? mockStream1 : mockStream2);
|
||||
};
|
||||
|
||||
const stream1 = await mediaHandler.getLocalStream();
|
||||
const stream2 = await mediaHandler.getLocalStream();
|
||||
|
||||
expect(stream1).toBe(mockStream1);
|
||||
expect(stream2).toBe(mockStream2);
|
||||
expect(callCount).toBe(2);
|
||||
|
||||
// The video element should have the latest stream
|
||||
expect(mockVideoElement.srcObject).toBe(mockStream2);
|
||||
|
||||
// Restore original
|
||||
(global as any).navigator.mediaDevices.getUserMedia = originalGetUserMedia;
|
||||
});
|
||||
});
|
||||
198
tests/frontend/UIController.test.ts
Normal file
198
tests/frontend/UIController.test.ts
Normal file
@@ -0,0 +1,198 @@
|
||||
import { test, expect, describe, beforeEach } from "bun:test";
|
||||
|
||||
// Simple mock function for tests
|
||||
function mockFn() {
|
||||
let callCount = 0;
|
||||
const fn = () => { callCount++; };
|
||||
|
||||
Object.defineProperty(fn, 'toHaveBeenCalledTimes', {
|
||||
value: (expected: number) => {
|
||||
if (callCount !== expected) {
|
||||
throw new Error(`Expected ${expected} calls, got ${callCount}`);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
return fn;
|
||||
}
|
||||
|
||||
// Mock HTML elements
|
||||
class MockHTMLElement {
|
||||
public textContent: string = '';
|
||||
public className: string = '';
|
||||
public disabled: boolean = false;
|
||||
private eventListeners: { [key: string]: (() => void)[] } = {};
|
||||
|
||||
addEventListener(event: string, handler: () => void) {
|
||||
if (!this.eventListeners[event]) {
|
||||
this.eventListeners[event] = [];
|
||||
}
|
||||
this.eventListeners[event].push(handler);
|
||||
}
|
||||
|
||||
click() {
|
||||
const handlers = this.eventListeners['click'] || [];
|
||||
handlers.forEach(handler => handler());
|
||||
}
|
||||
}
|
||||
|
||||
// Mock document.getElementById
|
||||
const mockElements: { [id: string]: MockHTMLElement } = {};
|
||||
const getElementByIdCalls: string[] = [];
|
||||
(global as any).document = {
|
||||
getElementById: (id: string) => {
|
||||
getElementByIdCalls.push(id);
|
||||
return mockElements[id] || null;
|
||||
}
|
||||
};
|
||||
|
||||
// Import after mocking
|
||||
import { UIController } from "../../public/js/services/UIController.ts";
|
||||
|
||||
describe("UIController", () => {
|
||||
let uiController: UIController;
|
||||
let statusElement: MockHTMLElement;
|
||||
let subscribersElement: MockHTMLElement;
|
||||
let startButton: MockHTMLElement;
|
||||
let stopButton: MockHTMLElement;
|
||||
|
||||
beforeEach(() => {
|
||||
// Reset mock elements
|
||||
statusElement = new MockHTMLElement();
|
||||
subscribersElement = new MockHTMLElement();
|
||||
startButton = new MockHTMLElement();
|
||||
stopButton = new MockHTMLElement();
|
||||
|
||||
mockElements['status'] = statusElement;
|
||||
mockElements['subscribers'] = subscribersElement;
|
||||
mockElements['startBtn'] = startButton;
|
||||
mockElements['stopBtn'] = stopButton;
|
||||
|
||||
// Clear call history
|
||||
getElementByIdCalls.length = 0;
|
||||
|
||||
uiController = new UIController('status', 'subscribers', 'startBtn', 'stopBtn');
|
||||
});
|
||||
|
||||
test("should initialize with all elements", () => {
|
||||
expect(getElementByIdCalls).toContain('status');
|
||||
expect(getElementByIdCalls).toContain('subscribers');
|
||||
expect(getElementByIdCalls).toContain('startBtn');
|
||||
expect(getElementByIdCalls).toContain('stopBtn');
|
||||
});
|
||||
|
||||
test("should initialize with minimal elements", () => {
|
||||
getElementByIdCalls.length = 0; // Clear previous calls
|
||||
const minimalController = new UIController('status');
|
||||
expect(getElementByIdCalls).toContain('status');
|
||||
});
|
||||
|
||||
test("should update status correctly", () => {
|
||||
uiController.updateStatus('Connected', 'connected');
|
||||
|
||||
expect(statusElement.textContent).toBe('Connected');
|
||||
expect(statusElement.className).toBe('status connected');
|
||||
});
|
||||
|
||||
test("should update subscribers count", () => {
|
||||
uiController.updateSubscribersCount(5);
|
||||
|
||||
expect(subscribersElement.textContent).toBe('Subscribers: 5');
|
||||
});
|
||||
|
||||
test("should handle missing subscribers element", () => {
|
||||
const controllerWithoutSubs = new UIController('status');
|
||||
|
||||
expect(() => {
|
||||
controllerWithoutSubs.updateSubscribersCount(5);
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
test("should set button states", () => {
|
||||
uiController.setButtonStates(false, true);
|
||||
|
||||
expect(startButton.disabled).toBe(true);
|
||||
expect(stopButton.disabled).toBe(false);
|
||||
|
||||
uiController.setButtonStates(true, false);
|
||||
|
||||
expect(startButton.disabled).toBe(false);
|
||||
expect(stopButton.disabled).toBe(true);
|
||||
});
|
||||
|
||||
test("should handle missing buttons", () => {
|
||||
const controllerWithoutButtons = new UIController('status');
|
||||
|
||||
expect(() => {
|
||||
controllerWithoutButtons.setButtonStates(true, false);
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
test("should add button click handlers", () => {
|
||||
const startHandler = mockFn();
|
||||
const stopHandler = mockFn();
|
||||
|
||||
uiController.onButtonClick('startBtn', startHandler);
|
||||
uiController.onButtonClick('stopBtn', stopHandler);
|
||||
|
||||
startButton.click();
|
||||
expect(startHandler.toHaveBeenCalledTimes(1)).toBe(true);
|
||||
|
||||
stopButton.click();
|
||||
expect(stopHandler.toHaveBeenCalledTimes(1)).toBe(true);
|
||||
});
|
||||
|
||||
test("should handle non-existent button click handlers", () => {
|
||||
const handler = mockFn();
|
||||
|
||||
expect(() => {
|
||||
uiController.onButtonClick('nonExistentBtn', handler);
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
test("should handle multiple clicks", () => {
|
||||
const handler = mockFn();
|
||||
uiController.onButtonClick('startBtn', handler);
|
||||
|
||||
startButton.click();
|
||||
startButton.click();
|
||||
startButton.click();
|
||||
|
||||
expect(handler.toHaveBeenCalledTimes(3)).toBe(true);
|
||||
});
|
||||
|
||||
test("should support multiple handlers on same button", () => {
|
||||
const handler1 = mockFn();
|
||||
const handler2 = mockFn();
|
||||
|
||||
uiController.onButtonClick('startBtn', handler1);
|
||||
uiController.onButtonClick('startBtn', handler2);
|
||||
|
||||
startButton.click();
|
||||
|
||||
expect(handler1.toHaveBeenCalledTimes(1)).toBe(true);
|
||||
expect(handler2.toHaveBeenCalledTimes(1)).toBe(true);
|
||||
});
|
||||
|
||||
test("should update status with different classes", () => {
|
||||
uiController.updateStatus('Connecting...', 'waiting');
|
||||
expect(statusElement.className).toBe('status waiting');
|
||||
|
||||
uiController.updateStatus('Connected', 'connected');
|
||||
expect(statusElement.className).toBe('status connected');
|
||||
|
||||
uiController.updateStatus('Error occurred', 'error');
|
||||
expect(statusElement.className).toBe('status error');
|
||||
});
|
||||
|
||||
test("should update subscribers count with zero", () => {
|
||||
uiController.updateSubscribersCount(0);
|
||||
expect(subscribersElement.textContent).toBe('Subscribers: 0');
|
||||
});
|
||||
|
||||
test("should update subscribers count with large numbers", () => {
|
||||
uiController.updateSubscribersCount(1000);
|
||||
expect(subscribersElement.textContent).toBe('Subscribers: 1000');
|
||||
});
|
||||
});
|
||||
205
tests/frontend/WebSocketClient.test.ts
Normal file
205
tests/frontend/WebSocketClient.test.ts
Normal file
@@ -0,0 +1,205 @@
|
||||
import { test, expect, describe, beforeEach, afterEach } from "bun:test";
|
||||
|
||||
// Mock WebSocket for testing
|
||||
class MockWebSocket {
|
||||
public readyState = WebSocket.CONNECTING;
|
||||
public url: string;
|
||||
public sentMessages: string[] = [];
|
||||
|
||||
public onopen: (() => void) | null = null;
|
||||
public onclose: (() => void) | null = null;
|
||||
public onmessage: ((event: { data: string }) => void) | null = null;
|
||||
public onerror: ((error: any) => void) | null = null;
|
||||
|
||||
constructor(url: string) {
|
||||
this.url = url;
|
||||
// Simulate successful connection
|
||||
setTimeout(() => {
|
||||
this.readyState = WebSocket.OPEN;
|
||||
if (this.onopen) {
|
||||
this.onopen();
|
||||
}
|
||||
}, 10);
|
||||
}
|
||||
|
||||
send(data: string) {
|
||||
if (this.readyState === WebSocket.OPEN) {
|
||||
this.sentMessages.push(data);
|
||||
}
|
||||
}
|
||||
|
||||
close() {
|
||||
this.readyState = WebSocket.CLOSED;
|
||||
if (this.onclose) {
|
||||
this.onclose();
|
||||
}
|
||||
}
|
||||
|
||||
simulateMessage(data: any) {
|
||||
if (this.onmessage) {
|
||||
this.onmessage({ data: JSON.stringify(data) });
|
||||
}
|
||||
}
|
||||
|
||||
simulateError(error: any) {
|
||||
if (this.onerror) {
|
||||
this.onerror(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mock the global WebSocket
|
||||
(global as any).WebSocket = MockWebSocket;
|
||||
|
||||
// Import after mocking
|
||||
import { WebSocketClient } from "../../public/js/services/WebSocketClient.ts";
|
||||
import type { ISignalingMessage } from "../../public/js/interfaces/IWebRTCClient.ts";
|
||||
|
||||
describe("WebSocketClient", () => {
|
||||
let wsClient: WebSocketClient;
|
||||
let mockWs: MockWebSocket;
|
||||
|
||||
beforeEach(() => {
|
||||
wsClient = new WebSocketClient('publisher');
|
||||
// Access the internal WebSocket instance for testing
|
||||
});
|
||||
|
||||
test("should connect as publisher", async () => {
|
||||
const connectPromise = wsClient.connect();
|
||||
|
||||
// Wait for connection
|
||||
await connectPromise;
|
||||
|
||||
expect(wsClient.isConnected()).toBe(true);
|
||||
});
|
||||
|
||||
test("should connect as subscriber", async () => {
|
||||
const subscriberClient = new WebSocketClient('subscriber');
|
||||
await subscriberClient.connect();
|
||||
|
||||
expect(subscriberClient.isConnected()).toBe(true);
|
||||
});
|
||||
|
||||
test("should handle connection error", async () => {
|
||||
const errorClient = new WebSocketClient('publisher');
|
||||
|
||||
// Start connection but simulate error before it completes
|
||||
const connectPromise = errorClient.connect();
|
||||
|
||||
// Simulate error during connection
|
||||
setTimeout(() => {
|
||||
// Access the internal WebSocket and trigger error
|
||||
const ws = (errorClient as any).ws;
|
||||
if (ws && ws.onerror) {
|
||||
ws.onerror(new Error('Connection failed'));
|
||||
}
|
||||
}, 5);
|
||||
|
||||
await expect(connectPromise).rejects.toThrow();
|
||||
});
|
||||
|
||||
test("should send messages when connected", async () => {
|
||||
await wsClient.connect();
|
||||
|
||||
const message: ISignalingMessage = {
|
||||
type: 'offer',
|
||||
data: { sdp: 'test-sdp' }
|
||||
};
|
||||
|
||||
wsClient.sendMessage(message);
|
||||
|
||||
const ws = (wsClient as any).ws as MockWebSocket;
|
||||
expect(ws.sentMessages).toHaveLength(1);
|
||||
expect(JSON.parse(ws.sentMessages[0])).toEqual(message);
|
||||
});
|
||||
|
||||
test("should not send messages when disconnected", () => {
|
||||
const message: ISignalingMessage = {
|
||||
type: 'offer',
|
||||
data: { sdp: 'test-sdp' }
|
||||
};
|
||||
|
||||
wsClient.sendMessage(message);
|
||||
|
||||
// Should not have sent anything since not connected
|
||||
const ws = (wsClient as any).ws;
|
||||
expect(ws).toBeNull();
|
||||
});
|
||||
|
||||
test("should handle incoming messages", async () => {
|
||||
await wsClient.connect();
|
||||
|
||||
const receivedMessages: ISignalingMessage[] = [];
|
||||
wsClient.onMessage('join', (message) => {
|
||||
receivedMessages.push(message);
|
||||
});
|
||||
|
||||
const testMessage: ISignalingMessage = {
|
||||
type: 'join',
|
||||
data: { clientId: 'test-id', role: 'publisher' }
|
||||
};
|
||||
|
||||
const ws = (wsClient as any).ws as MockWebSocket;
|
||||
ws.simulateMessage(testMessage);
|
||||
|
||||
expect(receivedMessages).toHaveLength(1);
|
||||
expect(receivedMessages[0]).toEqual(testMessage);
|
||||
});
|
||||
|
||||
test("should handle multiple message types", async () => {
|
||||
await wsClient.connect();
|
||||
|
||||
const joinMessages: ISignalingMessage[] = [];
|
||||
const offerMessages: ISignalingMessage[] = [];
|
||||
|
||||
wsClient.onMessage('join', (message) => joinMessages.push(message));
|
||||
wsClient.onMessage('offer', (message) => offerMessages.push(message));
|
||||
|
||||
const ws = (wsClient as any).ws as MockWebSocket;
|
||||
|
||||
ws.simulateMessage({ type: 'join', data: { clientId: 'test' } });
|
||||
ws.simulateMessage({ type: 'offer', data: { sdp: 'test-sdp' } });
|
||||
ws.simulateMessage({ type: 'join', data: { clientId: 'test2' } });
|
||||
|
||||
expect(joinMessages).toHaveLength(2);
|
||||
expect(offerMessages).toHaveLength(1);
|
||||
});
|
||||
|
||||
test("should disconnect properly", async () => {
|
||||
await wsClient.connect();
|
||||
expect(wsClient.isConnected()).toBe(true);
|
||||
|
||||
wsClient.disconnect();
|
||||
expect(wsClient.isConnected()).toBe(false);
|
||||
});
|
||||
|
||||
test("should handle malformed JSON messages gracefully", async () => {
|
||||
await wsClient.connect();
|
||||
|
||||
// Mock console.error to verify error logging
|
||||
const originalConsoleError = console.error;
|
||||
const errorLogs: any[] = [];
|
||||
console.error = (...args: any[]) => errorLogs.push(args);
|
||||
|
||||
const ws = (wsClient as any).ws as MockWebSocket;
|
||||
if (ws.onmessage) {
|
||||
ws.onmessage({ data: 'invalid-json' });
|
||||
}
|
||||
|
||||
expect(errorLogs.length).toBeGreaterThan(0);
|
||||
expect(errorLogs[0][0]).toBe('Failed to parse WebSocket message:');
|
||||
|
||||
// Restore console.error
|
||||
console.error = originalConsoleError;
|
||||
});
|
||||
|
||||
test("should handle close event", async () => {
|
||||
await wsClient.connect();
|
||||
expect(wsClient.isConnected()).toBe(true);
|
||||
|
||||
const ws = (wsClient as any).ws as MockWebSocket;
|
||||
ws.close();
|
||||
|
||||
expect(wsClient.isConnected()).toBe(false);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user