UNPKG

@signalwire/js

Version:
673 lines (603 loc) 23.8 kB
import { actions, componentActions } from '@signalwire/core' import { VideoRoomSession, createVideoRoomSessionObject, } from './VideoRoomSession' import { configureFullStack, dispatchMockedRoomSubscribed } from '../testUtils' describe('Room Object', () => { let store: any let room: VideoRoomSession let stack: ReturnType<typeof configureFullStack> const callId = 'call-id' const setupRoomForTests = () => { const mockPeer = { uuid: callId, onRemoteSdp: jest.fn(), } // @ts-expect-error room.getRTCPeerById = jest.fn((_id: string) => mockPeer) // @ts-expect-error room.runRTCPeerWorkers(callId) } beforeEach(() => { stack = configureFullStack() store = stack.store room = createVideoRoomSessionObject({ store, }) store.dispatch( componentActions.upsert({ id: callId, nodeId: 'node-id', roomId: 'room-id', roomSessionId: 'room-session-id', memberId: 'member-id', }) ) // @ts-expect-error room.execute = jest.fn() setupRoomForTests() // mock a room.subscribed event dispatchMockedRoomSubscribed({ session: stack.session, callId, roomId: 'room-id', roomSessionId: 'room-session-id', memberId: 'member-id', }) }) afterEach(() => { stack.destroy() }) it('should have all the custom methods defined', () => { expect(room.audioMute).toBeDefined() expect(room.audioUnmute).toBeDefined() expect(room.videoMute).toBeDefined() expect(room.videoUnmute).toBeDefined() expect(room.deaf).toBeDefined() expect(room.undeaf).toBeDefined() expect(room.setInputVolume).toBeDefined() expect(room.setOutputVolume).toBeDefined() expect(room.setMicrophoneVolume).toBeDefined() expect(room.setSpeakerVolume).toBeDefined() expect(room.setInputSensitivity).toBeDefined() expect(room.removeMember).toBeDefined() expect(room.getMembers).toBeDefined() expect(room.getLayouts).toBeDefined() expect(room.setLayout).toBeDefined() expect(room.hideVideoMuted).toBeDefined() expect(room.showVideoMuted).toBeDefined() expect(room.lock).toBeDefined() expect(room.unlock).toBeDefined() expect(room.getRecordings).toBeDefined() expect(room.startRecording).toBeDefined() expect(room.getPlaybacks).toBeDefined() expect(room.play).toBeDefined() expect(room.setMeta).toBeDefined() expect(room.setMemberMeta).toBeDefined() }) describe('getRecordings', () => { it('should return an array of recordings', async () => { const recordingList = [{ id: 'recordingOne' }, { id: 'recordingTwo' }] // @ts-expect-error ;(room.execute as jest.Mock).mockResolvedValueOnce({ recordings: recordingList, }) store.dispatch( componentActions.upsert({ id: callId, nodeId: 'node-id', roomId: '6e83849b-5cc2-4fc6-80ed-448113c8a426', roomSessionId: '8e03ac25-8622-411a-95fc-f897b34ac9e7', memberId: 'member-id', }) ) const result = await room.getRecordings() result.recordings.forEach((recording, index) => { expect(recording.id).toEqual(recordingList[index].id) expect(typeof recording.pause).toBe('function') expect(typeof recording.resume).toBe('function') expect(typeof recording.stop).toBe('function') }) }) }) describe('startRecording', () => { it('should return an interactive object', async () => { // @ts-expect-error ;(room.execute as jest.Mock).mockResolvedValueOnce({ code: '200', message: 'Recording started', recording: { id: 'c22d7223-5a01-49fe-8da0-46bec8e75e32', }, }) const recording = await room.startRecording() // @ts-expect-error expect(room.execute).toHaveBeenLastCalledWith({ method: 'video.recording.start', params: { room_session_id: 'room-session-id', }, }) // @ts-expect-error recording.execute = jest.fn() expect(recording.id).toEqual('c22d7223-5a01-49fe-8da0-46bec8e75e32') expect(recording.roomSessionId).toEqual('room-session-id') expect(recording.pause).toBeDefined() expect(recording.resume).toBeDefined() expect(recording.stop).toBeDefined() const baseExecuteParams = { method: '', params: { room_session_id: 'room-session-id', recording_id: 'c22d7223-5a01-49fe-8da0-46bec8e75e32', }, } await recording.pause() // @ts-expect-error expect(recording.execute).toHaveBeenLastCalledWith({ ...baseExecuteParams, method: 'video.recording.pause', }) await recording.resume() // @ts-expect-error expect(recording.execute).toHaveBeenLastCalledWith({ ...baseExecuteParams, method: 'video.recording.resume', }) await recording.stop() // @ts-expect-error expect(recording.execute).toHaveBeenLastCalledWith({ ...baseExecuteParams, method: 'video.recording.stop', }) }) it('should work with simulataneous recordings', async () => { // @ts-expect-error ;(room.execute as jest.Mock).mockResolvedValueOnce({ code: '200', message: 'Recording started', recording: { id: 'first-recording', }, }) // @ts-expect-error ;(room.execute as jest.Mock).mockResolvedValueOnce({ code: '200', message: 'Recording started', recording: { id: 'second-recording', }, }) const firstRecording = await room.startRecording() // @ts-expect-error firstRecording.execute = jest.fn() const secondRecording = await room.startRecording() // @ts-expect-error secondRecording.execute = jest.fn() expect(firstRecording.id).toEqual('first-recording') expect(firstRecording.roomSessionId).toEqual('room-session-id') await firstRecording.stop() // @ts-expect-error expect(firstRecording.execute).toHaveBeenLastCalledWith({ method: 'video.recording.stop', params: { room_session_id: 'room-session-id', recording_id: 'first-recording', }, }) expect(secondRecording.id).toEqual('second-recording') expect(secondRecording.roomSessionId).toEqual('room-session-id') await secondRecording.stop() // @ts-expect-error expect(secondRecording.execute).toHaveBeenLastCalledWith({ method: 'video.recording.stop', params: { room_session_id: 'room-session-id', recording_id: 'second-recording', }, }) }) }) describe('playback methods', () => { it('getPlaybacks should return an array of playbacks', async () => { const playbacks = [{ id: 'playbackOne' }, { id: 'playbackTwo' }] // @ts-expect-error ;(room.execute as jest.Mock).mockResolvedValueOnce({ playbacks, }) store.dispatch( componentActions.upsert({ id: callId, nodeId: 'node-id', roomId: '6e83849b-5cc2-4fc6-80ed-448113c8a426', roomSessionId: '8e03ac25-8622-411a-95fc-f897b34ac9e7', memberId: 'member-id', }) ) const result = await room.getPlaybacks() result.playbacks.forEach((playback, index) => { expect(playback.id).toEqual(playbacks[index].id) expect(typeof playback.forward).toBe('function') expect(typeof playback.pause).toBe('function') expect(typeof playback.resume).toBe('function') expect(typeof playback.rewind).toBe('function') expect(typeof playback.seek).toBe('function') expect(typeof playback.setVolume).toBe('function') expect(typeof playback.stop).toBe('function') }) }) it('play should return an interactive object', async () => { // @ts-expect-error ;(room.execute as jest.Mock).mockResolvedValueOnce({ code: '200', message: 'Playback started', playback: { id: 'c22d7223-5a01-49fe-8da0-46bec8e75e32', state: 'playing', started_at: 1234, }, }) const playback = await room.play({ url: 'rtmp://jest.example.com/bla', volume: 5, }) // @ts-expect-error playback.execute = jest.fn() // @ts-expect-error expect(room.execute).toHaveBeenLastCalledWith({ method: 'video.playback.start', params: { room_session_id: 'room-session-id', url: 'rtmp://jest.example.com/bla', volume: 5, }, }) expect(playback.id).toEqual('c22d7223-5a01-49fe-8da0-46bec8e75e32') expect(playback.roomSessionId).toEqual('room-session-id') expect(playback.pause).toBeDefined() expect(playback.resume).toBeDefined() expect(playback.stop).toBeDefined() const baseExecuteParams = { method: '', params: { room_session_id: 'room-session-id', playback_id: 'c22d7223-5a01-49fe-8da0-46bec8e75e32', }, } await playback.pause() // @ts-expect-error expect(playback.execute).toHaveBeenLastCalledWith({ ...baseExecuteParams, method: 'video.playback.pause', }) await playback.resume() // @ts-expect-error expect(playback.execute).toHaveBeenLastCalledWith({ ...baseExecuteParams, method: 'video.playback.resume', }) await playback.stop() // @ts-expect-error expect(playback.execute).toHaveBeenLastCalledWith({ ...baseExecuteParams, method: 'video.playback.stop', }) await playback.setVolume(30) // @ts-expect-error expect(playback.execute).toHaveBeenLastCalledWith({ method: 'video.playback.set_volume', params: { room_session_id: 'room-session-id', playback_id: 'c22d7223-5a01-49fe-8da0-46bec8e75e32', volume: 30, }, }) }) it('play should work with simulataneous playbacks', async () => { // @ts-expect-error ;(room.execute as jest.Mock).mockResolvedValueOnce({ code: '200', message: 'Playback started', playback: { id: 'first-playback', state: 'playing', started_at: 1234, }, }) // @ts-expect-error ;(room.execute as jest.Mock).mockResolvedValueOnce({ code: '200', message: 'Playback started', playback: { id: 'second-playback', state: 'playing', started_at: 1234, }, }) const firstPlayback = await room.play({ url: 'url-one', }) // @ts-expect-error firstPlayback.execute = jest.fn() const secondPlayback = await room.play({ url: 'url-two', }) // @ts-expect-error secondPlayback.execute = jest.fn() expect(firstPlayback.id).toEqual('first-playback') expect(firstPlayback.roomSessionId).toEqual('room-session-id') await firstPlayback.stop() // @ts-expect-error expect(firstPlayback.execute).toHaveBeenLastCalledWith({ method: 'video.playback.stop', params: { room_session_id: 'room-session-id', playback_id: 'first-playback', }, }) expect(secondPlayback.id).toEqual('second-playback') expect(secondPlayback.roomSessionId).toEqual('room-session-id') await secondPlayback.stop() // @ts-expect-error expect(secondPlayback.execute).toHaveBeenLastCalledWith({ method: 'video.playback.stop', params: { room_session_id: 'room-session-id', playback_id: 'second-playback', }, }) }) }) describe('as event emitter', () => { it('should listen on the talking events', () => { const { store, session, emitter, destroy } = configureFullStack() room = createVideoRoomSessionObject({ store, // @ts-expect-error emitter, }) // @ts-expect-error room.execute = jest.fn() store.dispatch( componentActions.upsert({ id: callId, nodeId: 'node-id', roomId: '6e83849b-5cc2-4fc6-80ed-448113c8a426', roomSessionId: '8e03ac25-8622-411a-95fc-f897b34ac9e7', memberId: 'member-id', }) ) setupRoomForTests() // mock a room.subscribed event dispatchMockedRoomSubscribed({ session, callId, roomId: '6e83849b-5cc2-4fc6-80ed-448113c8a426', roomSessionId: '8e03ac25-8622-411a-95fc-f897b34ac9e7', memberId: 'member-id', }) const startedHandler = jest.fn() room.on('member.talking.started', startedHandler) // deprecated room.on('member.talking.start', startedHandler) const endedHandler = jest.fn() room.on('member.talking.ended', endedHandler) // deprecated room.on('member.talking.stop', endedHandler) const globalHandler = jest.fn() room.on('member.talking', globalHandler) const talkingTrue = JSON.parse( '{"jsonrpc":"2.0","id":"9050e4f8-b08e-4e39-9796-bfb6e83c2a2d","method":"signalwire.event","params":{"params":{"room_session_id":"8e03ac25-8622-411a-95fc-f897b34ac9e7","room_id":"6e83849b-5cc2-4fc6-80ed-448113c8a426","member":{"id":"a3693340-6f42-4cab-b18e-8e2a22695698","room_session_id":"8e03ac25-8622-411a-95fc-f897b34ac9e7","room_id":"6e83849b-5cc2-4fc6-80ed-448113c8a426","talking":true}},"timestamp":1627374612.9585,"event_type":"video.member.talking","event_channel":"room.0a324e3c-5e2f-443a-a333-10bf005f249e"}}' ) session.dispatch(actions.socketMessageAction(talkingTrue)) expect(startedHandler).toHaveBeenCalledTimes(2) expect(globalHandler).toHaveBeenCalledTimes(1) const talkingFalse = JSON.parse( '{"jsonrpc":"2.0","id":"9050e4f8-b08e-4e39-9796-bfb6e83c2a2d","method":"signalwire.event","params":{"params":{"room_session_id":"8e03ac25-8622-411a-95fc-f897b34ac9e7","room_id":"6e83849b-5cc2-4fc6-80ed-448113c8a426","member":{"id":"a3693340-6f42-4cab-b18e-8e2a22695698","room_session_id":"8e03ac25-8622-411a-95fc-f897b34ac9e7","room_id":"6e83849b-5cc2-4fc6-80ed-448113c8a426","talking":false}},"timestamp":1627374612.9585,"event_type":"video.member.talking","event_channel":"room.0a324e3c-5e2f-443a-a333-10bf005f249e"}}' ) session.dispatch(actions.socketMessageAction(talkingFalse)) expect(endedHandler).toHaveBeenCalledTimes(2) expect(globalHandler).toHaveBeenCalledTimes(2) expect(globalHandler).toHaveBeenNthCalledWith( 1, talkingTrue.params.params ) expect(globalHandler).toHaveBeenNthCalledWith( 2, talkingFalse.params.params ) destroy() }) it('should handle the room.subscribed event with nested fields', (done) => { const roomId = 'd8caec4b-ddc9-4806-b2d0-e7c7d5cefe79' const roomSessionId = '638a54a7-61d8-4db0-bc24-426aee5cebcd' const recordingId = 'd1ae1822-5a5d-4950-8693-e59dc5dd96e0' const memberId = '465ea212-c456-423b-9bcc-838c5e1b2851' const { store, session, emitter, destroy } = configureFullStack() room = createVideoRoomSessionObject({ store, // @ts-expect-error emitter, }) // @ts-expect-error room.execute = jest.fn() setupRoomForTests() room.on('room.joined', (params) => { /** Test same keys between room_session and room for backwards compat. */ const keys = ['room_session', 'room'] as const keys.forEach(async (key) => { expect(params[key].name).toEqual('bu') expect(params[key].room_id).toEqual(roomId) expect(params[key].recording).toBe(true) expect(params[key].hide_video_muted).toBe(false) // @ts-expect-error expect(params[key].meta).toStrictEqual({}) const { members, recordings } = params[key] // Test members and member object expect(members).toHaveLength(1) expect(members[0].id).toEqual(memberId) expect(members[0].name).toEqual('edo') expect(members[0].visible).toEqual(false) expect(members[0].audio_muted).toEqual(false) expect(members[0].video_muted).toEqual(false) expect(members[0].deaf).toEqual(false) expect(members[0].input_volume).toEqual(0) expect(members[0].output_volume).toEqual(0) expect(members[0].input_sensitivity).toEqual(11.11111111111111) expect(members[0].meta).toStrictEqual({}) // Test recordings and recording object expect(recordings).toHaveLength(1) const recordingObj = recordings?.[0] expect(recordingObj.id).toEqual(recordingId) expect(recordingObj.state).toEqual('recording') expect(recordingObj.duration).toBeNull() expect(recordingObj.startedAt).toBeInstanceOf(Date) expect(recordingObj.endedAt).toBeUndefined() // When state is recording const execMock = jest.fn() const _clearMock = () => { execMock.mockClear() recordingObj.execute = execMock } _clearMock() await recordingObj.pause() expect(execMock).toHaveBeenCalledTimes(1) expect(execMock).toHaveBeenCalledWith({ method: 'video.recording.pause', params: { recording_id: recordingId, room_session_id: roomSessionId, }, }) _clearMock() await recordingObj.resume() expect(execMock).toHaveBeenCalledTimes(1) expect(execMock).toHaveBeenCalledWith({ method: 'video.recording.resume', params: { recording_id: recordingId, room_session_id: roomSessionId, }, }) _clearMock() await recordingObj.stop() expect(execMock).toHaveBeenCalledTimes(1) expect(execMock).toHaveBeenCalledWith({ method: 'video.recording.stop', params: { recording_id: recordingId, room_session_id: roomSessionId, }, }) }) /** Test specific keys between room_session and room for backwards compat. */ expect(params.room.room_session_id).toEqual(roomSessionId) expect(params.room_session.id).toEqual(roomSessionId) /** Test RoomSession properties */ expect(room.roomId).toEqual(roomId) expect(room.roomSessionId).toEqual(roomSessionId) expect(room.memberId).toEqual(memberId) destroy() done() }) /** * Mock `call_id` to match the event with "room.__uuid" */ const roomSubscribed = JSON.parse( `{"jsonrpc":"2.0","id":"d8a9fb9a-ad28-4a0a-8caa-5e06ec22f856","method":"signalwire.event","params":{"event_type":"video.room.subscribed","timestamp":1650960870.216,"event_channel":"EC_4d2c491d-bf96-4802-9008-c360a51155a2","params":{"call_id":"${callId}","member_id":"465ea212-c456-423b-9bcc-838c5e1b2851","room_session":{"room_id":"d8caec4b-ddc9-4806-b2d0-e7c7d5cefe79","id":"638a54a7-61d8-4db0-bc24-426aee5cebcd","event_channel":"EC_4d2c491d-bf96-4802-9008-c360a51155a2","name":"bu","recording":true,"hide_video_muted":false,"display_name":"bu","meta":{},"recordings":[{"id":"d1ae1822-5a5d-4950-8693-e59dc5dd96e0","state":"recording","duration":null,"started_at":1650960870.033,"ended_at":null}],"members":[{"id":"465ea212-c456-423b-9bcc-838c5e1b2851","room_id":"d8caec4b-ddc9-4806-b2d0-e7c7d5cefe79","room_session_id":"638a54a7-61d8-4db0-bc24-426aee5cebcd","name":"edo","type":"member","parent_id":"","requested_position":"auto","visible":false,"audio_muted":false,"video_muted":false,"deaf":false,"input_volume":0,"output_volume":0,"input_sensitivity":11.11111111111111,"meta":{}}]},"room":{"room_id":"d8caec4b-ddc9-4806-b2d0-e7c7d5cefe79","event_channel":"EC_4d2c491d-bf96-4802-9008-c360a51155a2","name":"bu","recording":true,"hide_video_muted":false,"display_name":"bu","meta":{},"recordings":[{"id":"d1ae1822-5a5d-4950-8693-e59dc5dd96e0","state":"recording","duration":null,"started_at":1650960870.033,"ended_at":null}],"members":[{"id":"465ea212-c456-423b-9bcc-838c5e1b2851","room_id":"d8caec4b-ddc9-4806-b2d0-e7c7d5cefe79","room_session_id":"638a54a7-61d8-4db0-bc24-426aee5cebcd","name":"edo","type":"member","parent_id":"","requested_position":"auto","visible":false,"audio_muted":false,"video_muted":false,"deaf":false,"input_volume":0,"output_volume":0,"input_sensitivity":11.11111111111111,"meta":{}}],"room_session_id":"638a54a7-61d8-4db0-bc24-426aee5cebcd"}}}}` ) // mock a room.subscribed event session.dispatch(actions.socketMessageAction(roomSubscribed)) }) }) describe('meta methods', () => { it('should allow to set the meta field on the RoomSession', async () => { const { store, session, emitter, destroy } = configureFullStack() session.execute = jest.fn().mockResolvedValue({ code: '200', message: 'OK', }) room = createVideoRoomSessionObject({ store, // @ts-expect-error emitter, }) store.dispatch( componentActions.upsert({ id: callId, nodeId: 'node-id', roomId: '6e83849b-5cc2-4fc6-80ed-448113c8a426', roomSessionId: '8e03ac25-8622-411a-95fc-f897b34ac9e7', memberId: 'member-id', }) ) setupRoomForTests() // mock a room.subscribed event dispatchMockedRoomSubscribed({ session, callId, roomId: '6e83849b-5cc2-4fc6-80ed-448113c8a426', roomSessionId: '8e03ac25-8622-411a-95fc-f897b34ac9e7', memberId: 'member-id', }) const result = await room.setMeta({ foo: 'bar' }) expect(result).toBeUndefined() expect(session.execute).toHaveBeenLastCalledWith({ jsonrpc: '2.0', id: expect.any(String), method: 'video.set_meta', params: { room_session_id: '8e03ac25-8622-411a-95fc-f897b34ac9e7', meta: { foo: 'bar' }, }, }) destroy() }) it('should allow to set the meta field on the Member', async () => { const { store, session, emitter, destroy } = configureFullStack() session.execute = jest.fn().mockResolvedValue({ code: '200', message: 'OK', }) room = createVideoRoomSessionObject({ store, // @ts-expect-error emitter, }) store.dispatch( componentActions.upsert({ id: callId, nodeId: 'node-id', roomId: '6e83849b-5cc2-4fc6-80ed-448113c8a426', roomSessionId: '8e03ac25-8622-411a-95fc-f897b34ac9e7', memberId: 'member-id', }) ) setupRoomForTests() // mock a room.subscribed event dispatchMockedRoomSubscribed({ session, callId, roomId: '6e83849b-5cc2-4fc6-80ed-448113c8a426', roomSessionId: '8e03ac25-8622-411a-95fc-f897b34ac9e7', memberId: 'member-id', }) const result = await room.setMemberMeta({ memberId: 'uuid', meta: { displayName: 'jest' }, }) expect(result).toBeUndefined() expect(session.execute).toHaveBeenLastCalledWith({ jsonrpc: '2.0', id: expect.any(String), method: 'video.member.set_meta', params: { room_session_id: '8e03ac25-8622-411a-95fc-f897b34ac9e7', member_id: 'uuid', meta: { displayName: 'jest' }, }, }) destroy() }) }) })