import { Call , CallAgent , CallClient , DeviceManager , UserFacingDiagnosticsFeature , IncomingCall , LatestMediaDiagnostics , LatestNetworkDiagnostics , LocalVideoStream , PropertyChangedEvent , RecordingCallFeature , RemoteParticipant , RemoteVideoStream , TranscriptionCallFeature , CallFeatureFactory , CallFeature } from '@azure/communication-calling' ; import { CommunicationTokenCredential } from '@azure/communication-common' ; import { AccessToken } from '@azure/core-auth' ; import EventEmitter from 'events' ; import { CallClientState } from './CallClientState' ; import { CallContext } from './CallContext' ; import { InternalCallContext } from './InternalCallContext' ; import { createStatefulCallClientWithDeps , StatefulCallClient } from './StatefulCallClient' ; let backupFreezeFunction ; private export function mockoutObjectFreeze ( ) { beforeEach ( ( ) => { backupFreezeFunction = Object . freeze ; Object . freeze = function ( obj ) { return obj ; } ; } ) ; afterEach ( ( ) => { Object . freeze = backupFreezeFunction ; } ) ; } private export interface MockEmitter { emitter : EventEmitter ; emit ( event , data ? ) ; } private export type Mutable < T > = { - readonly [ k in keyof T ] : T [ k ] ; } ; private export type MockRemoteVideoStream = Mutable < RemoteVideoStream > & MockEmitter ; private export type MockIncomingCall = Mutable < IncomingCall > & MockEmitter ; private export const stubCommunicationTokenCredential = ( ) : CommunicationTokenCredential => { return { getToken : ( ) : Promise < AccessToken > => { throw new Error ( 'Not implemented' ) ; } , dispose : ( ) : void => { } } ; } ; private export class MockRecordingCallFeatureImpl implements RecordingCallFeature { public name = 'Recording' ; public isRecordingActive = false ; public emitter = new EventEmitter ( ) ; on ( event , listener ) { this . emitter . on ( event , listener ) ; } off ( event , listener ) { this . emitter . off ( event , listener ) ; } dispose ( ) { } } private export class MockTranscriptionCallFeatureImpl implements TranscriptionCallFeature { public name = 'Transcription' ; public isTranscriptionActive = false ; public emitter = new EventEmitter ( ) ; on ( event , listener ) { this . emitter . on ( event , listener ) ; } off ( event , listener ) { this . emitter . off ( event , listener ) ; } dispose ( ) { } } private export class StubDiagnosticsCallFeatureImpl implements UserFacingDiagnosticsFeature { public name = 'Diagnosticss' ; public media = { getLatest ( ) { return { } ; } , on ( ) { } , off ( ) { } } ; public network = { getLatest ( ) { return { } ; } , on ( ) { } , off ( ) { } } ; dispose ( ) { } } private export function addMockEmitter ( object ) { object . emitter = new EventEmitter ( ) ; object . on = ( event , listener ) : void => { object . emitter . on ( event , listener ) ; } ; object . off = ( event , listener ) : void => { object . emitter . off ( event , listener ) ; } ; object . emit = ( event , payload ? ) : void => { object . emitter . emit ( event , payload ) ; } ; return object ; } private export interface MockCall extends Mutable < Call > , MockEmitter { testHelperPushRemoteParticipant ( participant ) ; testHelperPopRemoteParticipant ( ) : RemoteParticipant ; testHelperPushLocalVideoStream ( stream ) : void ; testHelperPopLocalVideoStream ( ) : LocalVideoStream ; } private export function createMockCall ( mockCallId = 'defaultCallID' ) { return addMockEmitter ( { id : mockCallId , remoteParticipants : [ ] as RemoteParticipant [ ] , localVideoStreams : [ ] as ReadonlyArray < LocalVideoStream > , feature : createMockApiFeatures ( new Map ( ) ) , testHelperPushRemoteParticipant ( participant ) { this . remoteParticipants . push ( participant ) ; this . emit ( 'remoteParticipantsUpdated' , { added : [ participant ] , removed : [ ] } ) ; } , testHelperPopRemoteParticipant ( ) { const participant = this . remoteParticipants . pop ( ) ; this . emit ( 'remoteParticipantsUpdated' , { added : [ ] , removed : [ participant ] } ) ; return participant ; } , testHelperPushLocalVideoStream ( stream ) { this . localVideoStreams = [ stream ] ; this . emit ( 'localVideoStreamsUpdated' , { added : [ stream ] , removed : [ ] } ) ; } , testHelperPopLocalVideoStream ( ) { const stream = this . localVideoStreams . pop ( ) ; this . emit ( 'localVideoStreamsUpdated' , { added : [ ] , removed : [ stream ] } ) ; return stream ; } } ) as MockCall ; } private export interface MockRemoteParticipant extends Mutable < RemoteParticipant > { emit ( event , data ? ) ; testHelperPushVideoStream ( stream ) : void ; testHelperPopVideoStream ( ) : RemoteVideoStream ; } private export function createMockRemoteParticipant ( mockCommunicationUserId = 'defaulRemoteParticipantId' ) { return addMockEmitter ( { identifier : { kind : 'communicationUser' , communicationUserId : mockCommunicationUserId } , videoStreams : [ ] as ReadonlyArray < RemoteVideoStream > , testHelperPushVideoStream ( stream ) { this . videoStreams . push ( stream ) ; this . emit ( 'videoStreamsUpdated' , { added : [ stream ] , removed : [ ] } ) ; } , testHelperPopVideoStream ( ) { const stream = this . videoStreams . pop ( ) ; this . emit ( 'videoStreamsUpdated' , { added : [ ] , removed : [ stream ] } ) ; return stream ; } } ) as MockRemoteParticipant ; } private export function createMockIncomingCall ( mockCallId ) { const mockIncomingCall = { id : mockCallId } as MockIncomingCall ; return addMockEmitter ( mockIncomingCall ) ; } private export function createMockRemoteVideoStream ( id = 42 ) { return addMockEmitter ( { id , mediaStreamType : 'Video' } ) as MockRemoteVideoStream ; } private export function createMockRemoteScreenshareStream ( id = 42 ) { return addMockEmitter ( { id , mediaStreamType : 'ScreenSharing' } ) as MockRemoteVideoStream ; } private export function createMockApiFeatures ( cache ) { return < FeatureT extends CallFeature > ( cls ) : FeatureT => { for ( const [ key , feature ] of cache . entries ( ) ) { if ( cls && key . callApiCtor === cls . callApiCtor ) { return feature as FeatureT ; } } const generic = addMockEmitter ( { name : 'Default' , isRecordingActive : false , isTranscriptionActive : false , media : { getLatest ( ) { return { } ; } , on ( ) { } , off ( ) { } } , network : { getLatest ( ) { return { } ; } , on ( ) { } , off ( ) { } } } ) ; return generic ; } ; } function waitMilliseconds ( duration ) { return new Promise ( ( resolve ) => { setTimeout ( ( ) => { resolve ( ) ; } , duration ) ; } ) ; } const BREAK_CONDITION_TIMEOUT_MILLESEC = 4000 ; private export async function waitWithBreakCondition ( breakCondition ) { const start = new Date ( ) ; for ( let now = new Date ( ) ; + now - + start < BREAK_CONDITION_TIMEOUT_MILLESEC ; now = new Date ( ) ) { if ( breakCondition ( ) ) { return true ; } await waitMilliseconds ( 10 ) ; } return false ; } private export const createMockCallClient = ( callAgent ? , deviceManager ? ) : CallClient => { return { getDeviceManager : ( ) : Promise < DeviceManager > => { if ( ! deviceManager ) { throw new Error ( 'deviceManager not set' ) ; } return Promise . resolve ( deviceManager ) ; } , createCallAgent : ( ) : Promise < CallAgent > => { if ( ! callAgent ) { throw new Error ( 'callAgent not set' ) ; } return Promise . resolve ( callAgent ) ; } } as unknown as CallClient ; } ; private export interface MockCallAgent extends Mutable < CallAgent > , MockEmitter { /**
     * Add given call to calls and trigger an event to notify clients.
     */ testHelperPushCall ( call ) : void ; testHelperPopCall ( ) : void ; } private export const createMockCallAgent = ( displayName = 'defaultDisplayName' ) : MockCallAgent => { return addMockEmitter ( { calls : [ ] as Call [ ] , displayName : displayName , testHelperPushCall ( call ) { this . calls . push ( call ) ; this . emit ( 'callsUpdated' , { added : [ call ] , removed : [ ] } ) ; } , testHelperPopCall ( ) { const call = this . calls . pop ( ) ; this . emit ( 'callsUpdated' , { added : [ ] , removed : [ call ] } ) ; } } ) as MockCallAgent ; } ; private export class StateChangeListener { state : CallClientState ; onChangeCalledCount = 0 ; constructor ( client ) { this . state = client . getState ( ) ; client . onStateChange ( this . onChange . bind ( this ) ) ; } private onChange ( newState ) { this . onChangeCalledCount ++ ; this . state = newState ; } } private export const createStatefulCallClientWithAgent = ( agent ) : StatefulCallClient => { return createStatefulCallClientWithBaseClient ( createMockCallClient ( agent ) ) ; } ; private export const createStatefulCallClientWithBaseClient = ( client ) : StatefulCallClient => { return createStatefulCallClientWithDeps ( client , new CallContext ( { kind : 'communicationUser' , communicationUserId : 'defaultUserId' } ) , new InternalCallContext ( ) ) ; } ;