@100mslive/hms-video-store
Version:
@100mslive Core SDK which abstracts the complexities of webRTC while providing a reactive store for data management with a unidirectional data flow
238 lines (212 loc) • 9.55 kB
text/typescript
import { FAKE_PEER_ID, fakeMessage, fakePeer, fakePeerList, fakeReconnectPeerList, fakeSpeakerList } from './fixtures';
import { HMSNotificationMethod } from './HMSNotificationMethod';
import { NotificationManager } from './NotificationManager';
import { AnalyticsEventsService } from '../analytics/AnalyticsEventsService';
import { AnalyticsTimer } from '../analytics/AnalyticsTimer';
import { PluginUsageTracker } from '../common/PluginUsageTracker';
import { DeviceManager } from '../device-manager';
import { EventBus } from '../events/EventBus';
import { HMSAudioListener, HMSPeerUpdate, HMSRoomUpdate, HMSUpdateListener } from '../interfaces';
import HMSRoom from '../sdk/models/HMSRoom';
import { HMSRemotePeer } from '../sdk/models/peer';
import { Store } from '../sdk/store';
import HMSTransport from '../transport';
import ITransportObserver from '../transport/ITransportObserver';
let joinHandler: jest.Mock<any, any>;
let previewHandler: jest.Mock<any, any>;
let roomUpdateHandler: jest.Mock<any, any>;
let peerUpdateHandler: jest.Mock<any, any>;
let trackUpdateHandler: jest.Mock<any, any>;
let messageReceivedHandler: jest.Mock<any, any>;
let errorHandler: jest.Mock<any, any>;
let reconnectingHandler: jest.Mock<any, any>;
let reconnectedHandler: jest.Mock<any, any>;
let roleChangeRequestHandler: jest.Mock<any, any>;
let roleUpdateHandler: jest.Mock<any, any>;
let changeTrackStateRequestHandler: jest.Mock<any, any>;
let changeMultiTrackStateRequestHandler: jest.Mock<any, any>;
let removedFromRoomHandler: jest.Mock<any, any>;
let audioUpdateHandler: jest.Mock<any, any>;
let sessionStoreUpdateHandler: jest.Mock<any, any>;
let pollsUpdateHandler: jest.Mock<any, any>;
let whiteboardUpdateHandler: jest.Mock<any, any>;
let listener: HMSUpdateListener;
let audioListener: HMSAudioListener;
const store: Store = new Store();
let notificationManager: NotificationManager;
let eventBus: EventBus;
let transport: HMSTransport;
let deviceManager: DeviceManager;
let analyticsTimer: AnalyticsTimer;
let observer: ITransportObserver;
beforeEach(() => {
joinHandler = jest.fn();
previewHandler = jest.fn();
roomUpdateHandler = jest.fn();
peerUpdateHandler = jest.fn();
trackUpdateHandler = jest.fn();
messageReceivedHandler = jest.fn();
errorHandler = jest.fn();
reconnectingHandler = jest.fn();
reconnectedHandler = jest.fn();
roleChangeRequestHandler = jest.fn();
roleUpdateHandler = jest.fn();
changeTrackStateRequestHandler = jest.fn();
changeMultiTrackStateRequestHandler = jest.fn();
removedFromRoomHandler = jest.fn();
audioUpdateHandler = jest.fn();
sessionStoreUpdateHandler = jest.fn();
pollsUpdateHandler = jest.fn();
whiteboardUpdateHandler = jest.fn();
eventBus = new EventBus();
deviceManager = new DeviceManager(store, eventBus);
analyticsTimer = new AnalyticsTimer();
observer = {
onNotification: jest.fn(),
onTrackAdd: jest.fn(),
onTrackRemove: jest.fn(),
onFailure: jest.fn(),
onStateChange: jest.fn(),
onConnected: jest.fn(),
};
const mockMediaStream = {
id: 'native-stream-id',
getVideoTracks: jest.fn(() => [
{
id: 'video-id',
kind: 'video',
getSettings: jest.fn(() => ({ deviceId: 'video-device-id' })),
addEventListener: jest.fn(() => {}),
},
]),
getAudioTracks: jest.fn(() => [
{
id: 'audio-id',
kind: 'audio',
getSettings: jest.fn(() => ({ deviceId: 'audio-device-id' })),
addEventListener: jest.fn(() => {}),
},
]),
addTrack: jest.fn(() => {}),
};
global.MediaStream = jest.fn().mockImplementation(() => mockMediaStream);
// @ts-ignore
global.HTMLCanvasElement.prototype.captureStream = jest.fn().mockImplementation(() => mockMediaStream);
transport = new HMSTransport(
observer,
deviceManager,
store,
eventBus,
new AnalyticsEventsService(store),
analyticsTimer,
new PluginUsageTracker(eventBus),
);
store.setRoom(new HMSRoom('1234'));
listener = {
onJoin: joinHandler,
onPreview: previewHandler,
onRoomUpdate: roomUpdateHandler,
onPeerUpdate: peerUpdateHandler,
onTrackUpdate: trackUpdateHandler,
onMessageReceived: messageReceivedHandler,
onError: errorHandler,
onReconnecting: reconnectingHandler,
onReconnected: reconnectedHandler,
onRoleChangeRequest: roleChangeRequestHandler,
onRoleUpdate: roleUpdateHandler,
onChangeTrackStateRequest: changeTrackStateRequestHandler,
onChangeMultiTrackStateRequest: changeMultiTrackStateRequestHandler,
onRemovedFromRoom: removedFromRoomHandler,
onSessionStoreUpdate: sessionStoreUpdateHandler,
onPollsUpdate: pollsUpdateHandler,
onWhiteboardUpdate: whiteboardUpdateHandler,
};
transport.setListener(listener);
audioListener = { onAudioLevelUpdate: audioUpdateHandler };
notificationManager = new NotificationManager(store, eventBus, transport, listener, audioListener);
});
describe('Notification Manager', () => {
describe('on-peer-join', () => {
it('should call onPeerUpdate with correct parameters', () => {
notificationManager.handleNotification({ method: HMSNotificationMethod.PEER_JOIN, params: fakePeer });
// console.log({ peer: peerUpdateHandler.mock.calls[0][1] });
expect(peerUpdateHandler).toHaveBeenCalled();
expect(peerUpdateHandler.mock.calls[0][0]).toBe(HMSPeerUpdate.PEER_JOINED);
expect(peerUpdateHandler.mock.calls[0][1]).toBeInstanceOf(HMSRemotePeer);
expect(peerUpdateHandler.mock.calls[0][1].peerId).toBe(fakePeer.peer_id);
});
it('should add peer in store', () => {
notificationManager.handleNotification({ method: HMSNotificationMethod.PEER_JOIN, params: fakePeer });
const peer = store.getPeerById(fakePeer.peer_id);
expect(peer).toBeInstanceOf(HMSRemotePeer);
expect(peer?.peerId).toBe(fakePeer.peer_id);
});
});
describe('on-peer-leave', () => {
it('should call onPeerUpdate with correct parameters', () => {
notificationManager.handleNotification({ method: HMSNotificationMethod.PEER_LEAVE, params: fakePeer });
expect(peerUpdateHandler).toHaveBeenCalled();
expect(peerUpdateHandler.mock.calls[0][0]).toBe(HMSPeerUpdate.PEER_LEFT);
expect(peerUpdateHandler.mock.calls[0][1]).toBeInstanceOf(HMSRemotePeer);
expect(peerUpdateHandler.mock.calls[0][1].peerId).toBe(fakePeer.peer_id);
});
it('should remove peer from store', () => {
const peer = store.getPeerById(fakePeer.peer_id);
expect(peer).toBeUndefined();
});
});
describe('initial peer-list', () => {
it('should call onPeerUpdate with correct parameters', () => {
notificationManager.handleNotification({ method: HMSNotificationMethod.PEER_LIST, params: fakePeerList });
expect(peerUpdateHandler).toHaveBeenCalled();
peerUpdateHandler.mock.calls.forEach(call => {
expect(call[0]).toBe(HMSPeerUpdate.PEER_LIST);
expect(call[1][0]).toBeInstanceOf(HMSRemotePeer);
});
expect(roomUpdateHandler).toHaveBeenCalled();
expect(roomUpdateHandler.mock.calls[0][0]).toBe(HMSRoomUpdate.RECORDING_STATE_UPDATED);
});
});
describe('reconnect peer-list', () => {
it('should call onPeerUpdate with correct parameters', () => {
notificationManager.handleNotification(
{ method: HMSNotificationMethod.PEER_LIST, params: fakeReconnectPeerList },
true,
);
// console.log({ reconnectPeerListMock: peerUpdateHandler.mock.calls });
expect(peerUpdateHandler).toHaveBeenCalledTimes(3);
expect(peerUpdateHandler.mock.calls[0][0]).toBe(HMSPeerUpdate.PEER_LEFT);
expect(peerUpdateHandler.mock.calls[0][1]).toBeInstanceOf(HMSRemotePeer);
expect(peerUpdateHandler.mock.calls[0][1].peerId).toBe('peer_id_3');
expect(peerUpdateHandler.mock.calls[1][0]).toBe(HMSPeerUpdate.PEER_REMOVED);
expect(peerUpdateHandler.mock.calls[1][1]).toBeInstanceOf(HMSRemotePeer);
expect(peerUpdateHandler.mock.calls[1][1].peerId).toBe('peer_id_1');
expect(peerUpdateHandler.mock.calls[2][0]).toBe(HMSPeerUpdate.PEER_LIST);
expect(peerUpdateHandler.mock.calls[2][1][0]).toBeInstanceOf(HMSRemotePeer);
expect(peerUpdateHandler.mock.calls[2][1][0].peerId).toBe('peer_id_1');
expect(roomUpdateHandler).toHaveBeenCalled();
expect(roomUpdateHandler.mock.calls[0][0]).toBe(HMSRoomUpdate.RECORDING_STATE_UPDATED);
});
});
describe('active-speakers', () => {
it('should call active speaker callbacks with correct parameters', () => {
notificationManager.handleNotification({
method: HMSNotificationMethod.ACTIVE_SPEAKERS,
params: fakeSpeakerList,
});
expect(audioUpdateHandler).toBeCalled();
expect(audioUpdateHandler.mock.calls[0][0][0].peer.peerId).toBe(FAKE_PEER_ID);
expect(peerUpdateHandler).toBeCalled();
expect(peerUpdateHandler.mock.calls[0][0]).toBe(HMSPeerUpdate.BECAME_DOMINANT_SPEAKER);
expect(peerUpdateHandler.mock.calls[0][1]).toBeInstanceOf(HMSRemotePeer);
expect(peerUpdateHandler.mock.calls[0][1].peerId).toBe(FAKE_PEER_ID);
});
});
describe('broadcast', () => {
it('should call onMessageReceived with correct parameters', () => {
notificationManager.handleNotification({ method: HMSNotificationMethod.BROADCAST, params: fakeMessage });
expect(messageReceivedHandler).toBeCalled();
expect(messageReceivedHandler.mock.calls[0][0].peer.peer_id).toBe(FAKE_PEER_ID);
});
});
});