You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-08-07 23:02:56 +03:00
Test placing a call in a group call (#2593)
* Test placing a call in a group call Refactors a bit of the call testing stuff Fixes https://github.com/vector-im/element-call/issues/521 * Unused imports * Use expect.toHaveBeenCalledWith() * Types * More types * Add comment on mock typing * Use toHaveBeenCalledWith() * Initialise groupcall & room in beforeEach * Initialise mockMediahandler sensibly * Add type params to mock * Rename mute tests * Move comment * Join / leave in parallel * Remove leftover expect
This commit is contained in:
@@ -70,20 +70,41 @@ export class MockAudioContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class MockRTCPeerConnection {
|
export class MockRTCPeerConnection {
|
||||||
|
private static instances: MockRTCPeerConnection[] = [];
|
||||||
|
|
||||||
|
private negotiationNeededListener: () => void;
|
||||||
|
private needsNegotiation = false;
|
||||||
localDescription: RTCSessionDescription;
|
localDescription: RTCSessionDescription;
|
||||||
|
|
||||||
|
public static triggerAllNegotiations() {
|
||||||
|
for (const inst of this.instances) {
|
||||||
|
inst.doNegotiation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static resetInstances() {
|
||||||
|
this.instances = [];
|
||||||
|
}
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.localDescription = {
|
this.localDescription = {
|
||||||
sdp: DUMMY_SDP,
|
sdp: DUMMY_SDP,
|
||||||
type: 'offer',
|
type: 'offer',
|
||||||
toJSON: function() { },
|
toJSON: function() { },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
MockRTCPeerConnection.instances.push(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
addEventListener() { }
|
addEventListener(type: string, listener: () => void) {
|
||||||
|
if (type === 'negotiationneeded') this.negotiationNeededListener = listener;
|
||||||
|
}
|
||||||
createDataChannel(label: string, opts: RTCDataChannelInit) { return { label, ...opts }; }
|
createDataChannel(label: string, opts: RTCDataChannelInit) { return { label, ...opts }; }
|
||||||
createOffer() {
|
createOffer() {
|
||||||
return Promise.resolve({});
|
return Promise.resolve({
|
||||||
|
type: 'offer',
|
||||||
|
sdp: DUMMY_SDP,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
setRemoteDescription() {
|
setRemoteDescription() {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
@@ -93,7 +114,17 @@ export class MockRTCPeerConnection {
|
|||||||
}
|
}
|
||||||
close() { }
|
close() { }
|
||||||
getStats() { return []; }
|
getStats() { return []; }
|
||||||
addTrack(track: MockMediaStreamTrack) { return new MockRTCRtpSender(track); }
|
addTrack(track: MockMediaStreamTrack) {
|
||||||
|
this.needsNegotiation = true;
|
||||||
|
return new MockRTCRtpSender(track);
|
||||||
|
}
|
||||||
|
|
||||||
|
doNegotiation() {
|
||||||
|
if (this.needsNegotiation && this.negotiationNeededListener) {
|
||||||
|
this.needsNegotiation = false;
|
||||||
|
this.negotiationNeededListener();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MockRTCRtpSender {
|
export class MockRTCRtpSender {
|
||||||
@@ -140,6 +171,10 @@ export class MockMediaStream {
|
|||||||
this.dispatchEvent("addtrack");
|
this.dispatchEvent("addtrack");
|
||||||
}
|
}
|
||||||
removeTrack(track: MockMediaStreamTrack) { this.tracks.splice(this.tracks.indexOf(track), 1); }
|
removeTrack(track: MockMediaStreamTrack) { this.tracks.splice(this.tracks.indexOf(track), 1); }
|
||||||
|
|
||||||
|
clone() {
|
||||||
|
return new MockMediaStream(this.id, this.tracks);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MockMediaDeviceInfo {
|
export class MockMediaDeviceInfo {
|
||||||
@@ -149,6 +184,9 @@ export class MockMediaDeviceInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class MockMediaHandler {
|
export class MockMediaHandler {
|
||||||
|
public userMediaStreams: MediaStream[] = [];
|
||||||
|
public screensharingStreams: MediaStream[] = [];
|
||||||
|
|
||||||
getUserMediaStream(audio: boolean, video: boolean) {
|
getUserMediaStream(audio: boolean, video: boolean) {
|
||||||
const tracks = [];
|
const tracks = [];
|
||||||
if (audio) tracks.push(new MockMediaStreamTrack("audio_track", "audio"));
|
if (audio) tracks.push(new MockMediaStreamTrack("audio_track", "audio"));
|
||||||
@@ -160,3 +198,32 @@ export class MockMediaHandler {
|
|||||||
hasAudioDevice() { return true; }
|
hasAudioDevice() { return true; }
|
||||||
stopAllStreams() {}
|
stopAllStreams() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function installWebRTCMocks() {
|
||||||
|
global.navigator = {
|
||||||
|
mediaDevices: {
|
||||||
|
// @ts-ignore Mock
|
||||||
|
getUserMedia: () => new MockMediaStream("local_stream"),
|
||||||
|
// @ts-ignore Mock
|
||||||
|
enumerateDevices: async () => [new MockMediaDeviceInfo("audio"), new MockMediaDeviceInfo("video")],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
global.window = {
|
||||||
|
// @ts-ignore Mock
|
||||||
|
RTCPeerConnection: MockRTCPeerConnection,
|
||||||
|
// @ts-ignore Mock
|
||||||
|
RTCSessionDescription: {},
|
||||||
|
// @ts-ignore Mock
|
||||||
|
RTCIceCandidate: {},
|
||||||
|
getUserMedia: () => new MockMediaStream("local_stream"),
|
||||||
|
};
|
||||||
|
// @ts-ignore Mock
|
||||||
|
global.document = {};
|
||||||
|
|
||||||
|
// @ts-ignore Mock
|
||||||
|
global.AudioContext = MockAudioContext;
|
||||||
|
|
||||||
|
// @ts-ignore Mock
|
||||||
|
global.RTCRtpReceiver = {};
|
||||||
|
}
|
||||||
|
@@ -22,9 +22,7 @@ import {
|
|||||||
MockMediaHandler,
|
MockMediaHandler,
|
||||||
MockMediaStream,
|
MockMediaStream,
|
||||||
MockMediaStreamTrack,
|
MockMediaStreamTrack,
|
||||||
MockMediaDeviceInfo,
|
installWebRTCMocks,
|
||||||
MockRTCPeerConnection,
|
|
||||||
MockAudioContext,
|
|
||||||
} from "../../test-utils/webrtc";
|
} from "../../test-utils/webrtc";
|
||||||
import { CallFeed } from "../../../src/webrtc/callFeed";
|
import { CallFeed } from "../../../src/webrtc/callFeed";
|
||||||
|
|
||||||
@@ -48,29 +46,7 @@ describe('Call', function() {
|
|||||||
prevDocument = global.document;
|
prevDocument = global.document;
|
||||||
prevWindow = global.window;
|
prevWindow = global.window;
|
||||||
|
|
||||||
global.navigator = {
|
installWebRTCMocks();
|
||||||
mediaDevices: {
|
|
||||||
// @ts-ignore Mock
|
|
||||||
getUserMedia: () => new MockMediaStream("local_stream"),
|
|
||||||
// @ts-ignore Mock
|
|
||||||
enumerateDevices: async () => [new MockMediaDeviceInfo("audio"), new MockMediaDeviceInfo("video")],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
global.window = {
|
|
||||||
// @ts-ignore Mock
|
|
||||||
RTCPeerConnection: MockRTCPeerConnection,
|
|
||||||
// @ts-ignore Mock
|
|
||||||
RTCSessionDescription: {},
|
|
||||||
// @ts-ignore Mock
|
|
||||||
RTCIceCandidate: {},
|
|
||||||
getUserMedia: () => new MockMediaStream("local_stream"),
|
|
||||||
};
|
|
||||||
// @ts-ignore Mock
|
|
||||||
global.document = {};
|
|
||||||
|
|
||||||
// @ts-ignore Mock
|
|
||||||
global.AudioContext = MockAudioContext;
|
|
||||||
|
|
||||||
client = new TestClient("@alice:foo", "somedevice", "token", undefined, {});
|
client = new TestClient("@alice:foo", "somedevice", "token", undefined, {});
|
||||||
// We just stub out sendEvent: we're not interested in testing the client's
|
// We just stub out sendEvent: we're not interested in testing the client's
|
||||||
|
@@ -14,70 +14,95 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { EventType, GroupCallIntent, GroupCallType, Room, RoomMember } from '../../../src';
|
import { EventType, GroupCallIntent, GroupCallType, MatrixEvent, Room, RoomMember } from '../../../src';
|
||||||
import { GroupCall } from "../../../src/webrtc/groupCall";
|
import { GroupCall } from "../../../src/webrtc/groupCall";
|
||||||
import { MatrixClient } from "../../../src/client";
|
import { MatrixClient } from "../../../src/client";
|
||||||
import { MockAudioContext, MockMediaHandler } from '../../test-utils/webrtc';
|
import { installWebRTCMocks, MockMediaHandler, MockRTCPeerConnection } from '../../test-utils/webrtc';
|
||||||
|
import { ReEmitter } from '../../../src/ReEmitter';
|
||||||
|
import { TypedEventEmitter } from '../../../src/models/typed-event-emitter';
|
||||||
|
import { MediaHandler } from '../../../src/webrtc/mediaHandler';
|
||||||
|
|
||||||
const FAKE_SELF_USER_ID = "@me:test.dummy";
|
|
||||||
const FAKE_SELF_DEVICE_ID = "AAAAAA";
|
|
||||||
const FAKE_SELF_SESSION_ID = "1";
|
|
||||||
const FAKE_ROOM_ID = "!fake:test.dummy";
|
const FAKE_ROOM_ID = "!fake:test.dummy";
|
||||||
|
const FAKE_CONF_ID = "fakegroupcallid";
|
||||||
|
|
||||||
|
const FAKE_USER_ID_1 = "@alice:test.dummy";
|
||||||
|
const FAKE_DEVICE_ID_1 = "@AAAAAA";
|
||||||
|
const FAKE_SESSION_ID_1 = "alice1";
|
||||||
|
const FAKE_USER_ID_2 = "@bob:test.dummy";
|
||||||
|
const FAKE_DEVICE_ID_2 = "@BBBBBB";
|
||||||
|
const FAKE_SESSION_ID_2 = "bob1";
|
||||||
|
|
||||||
|
class MockCallMatrixClient {
|
||||||
|
public mediaHandler: MediaHandler = new MockMediaHandler() as unknown as MediaHandler;
|
||||||
|
|
||||||
|
constructor(public userId: string, public deviceId: string, public sessionId: string) {
|
||||||
|
}
|
||||||
|
|
||||||
|
groupCallEventHandler = {
|
||||||
|
groupCalls: new Map(),
|
||||||
|
};
|
||||||
|
|
||||||
|
callEventHandler = {
|
||||||
|
calls: new Map(),
|
||||||
|
};
|
||||||
|
|
||||||
|
sendStateEvent = jest.fn();
|
||||||
|
|
||||||
|
getMediaHandler() { return this.mediaHandler; }
|
||||||
|
|
||||||
|
getUserId() { return this.userId; }
|
||||||
|
|
||||||
|
getDeviceId() { return this.deviceId; }
|
||||||
|
getSessionId() { return this.sessionId; }
|
||||||
|
|
||||||
|
emit = jest.fn();
|
||||||
|
on = jest.fn();
|
||||||
|
removeListener = jest.fn();
|
||||||
|
getTurnServers = () => [];
|
||||||
|
isFallbackICEServerAllowed = () => false;
|
||||||
|
reEmitter = new ReEmitter(new TypedEventEmitter());
|
||||||
|
getUseE2eForGroupCall = () => false;
|
||||||
|
checkTurnServers = () => null;
|
||||||
|
}
|
||||||
|
|
||||||
describe('Group Call', function() {
|
describe('Group Call', function() {
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
// @ts-ignore Mock
|
installWebRTCMocks();
|
||||||
global.AudioContext = MockAudioContext;
|
});
|
||||||
|
|
||||||
|
describe('Basic functionality', function() {
|
||||||
|
let mockSendState: jest.Mock;
|
||||||
|
let mockClient: MatrixClient;
|
||||||
|
let room: Room;
|
||||||
|
let groupCall: GroupCall;
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
const typedMockClient = new MockCallMatrixClient(
|
||||||
|
FAKE_USER_ID_1, FAKE_DEVICE_ID_1, FAKE_SESSION_ID_1,
|
||||||
|
);
|
||||||
|
mockSendState = typedMockClient.sendStateEvent;
|
||||||
|
|
||||||
|
mockClient = typedMockClient as unknown as MatrixClient;
|
||||||
|
|
||||||
|
room = new Room(FAKE_ROOM_ID, mockClient, FAKE_USER_ID_1);
|
||||||
|
groupCall = new GroupCall(mockClient, room, GroupCallType.Video, false, GroupCallIntent.Prompt);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("sends state event to room when creating", async () => {
|
it("sends state event to room when creating", async () => {
|
||||||
const mockSendState = jest.fn();
|
|
||||||
|
|
||||||
const mockClient = {
|
|
||||||
sendStateEvent: mockSendState,
|
|
||||||
groupCallEventHandler: {
|
|
||||||
groupCalls: new Map(),
|
|
||||||
},
|
|
||||||
} as unknown as MatrixClient;
|
|
||||||
|
|
||||||
const room = new Room(FAKE_ROOM_ID, mockClient, FAKE_SELF_USER_ID);
|
|
||||||
const groupCall = new GroupCall(mockClient, room, GroupCallType.Video, false, GroupCallIntent.Prompt);
|
|
||||||
|
|
||||||
await groupCall.create();
|
await groupCall.create();
|
||||||
|
|
||||||
expect(mockSendState.mock.calls[0][0]).toEqual(FAKE_ROOM_ID);
|
expect(mockSendState).toHaveBeenCalledWith(
|
||||||
expect(mockSendState.mock.calls[0][1]).toEqual(EventType.GroupCallPrefix);
|
FAKE_ROOM_ID, EventType.GroupCallPrefix, expect.objectContaining({
|
||||||
expect(mockSendState.mock.calls[0][2]["m.type"]).toEqual(GroupCallType.Video);
|
"m.type": GroupCallType.Video,
|
||||||
expect(mockSendState.mock.calls[0][2]["m.intent"]).toEqual(GroupCallIntent.Prompt);
|
"m.intent": GroupCallIntent.Prompt,
|
||||||
|
}),
|
||||||
|
groupCall.groupCallId,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("sends member state event to room on enter", async () => {
|
it("sends member state event to room on enter", async () => {
|
||||||
const mockSendState = jest.fn();
|
room.currentState.members[FAKE_USER_ID_1] = {
|
||||||
const mockMediaHandler = new MockMediaHandler();
|
userId: FAKE_USER_ID_1,
|
||||||
|
|
||||||
const mockClient = {
|
|
||||||
sendStateEvent: mockSendState,
|
|
||||||
groupCallEventHandler: {
|
|
||||||
groupCalls: new Map(),
|
|
||||||
},
|
|
||||||
callEventHandler: {
|
|
||||||
calls: new Map(),
|
|
||||||
},
|
|
||||||
mediaHandler: mockMediaHandler,
|
|
||||||
getMediaHandler: () => mockMediaHandler,
|
|
||||||
getUserId: () => FAKE_SELF_USER_ID,
|
|
||||||
getDeviceId: () => FAKE_SELF_DEVICE_ID,
|
|
||||||
getSessionId: () => FAKE_SELF_SESSION_ID,
|
|
||||||
emit: jest.fn(),
|
|
||||||
on: jest.fn(),
|
|
||||||
removeListener: jest.fn(),
|
|
||||||
} as unknown as MatrixClient;
|
|
||||||
|
|
||||||
const room = new Room(FAKE_ROOM_ID, mockClient, FAKE_SELF_USER_ID);
|
|
||||||
const groupCall = new GroupCall(mockClient, room, GroupCallType.Video, false, GroupCallIntent.Prompt);
|
|
||||||
|
|
||||||
room.currentState.members[FAKE_SELF_USER_ID] = {
|
|
||||||
userId: FAKE_SELF_USER_ID,
|
|
||||||
} as unknown as RoomMember;
|
} as unknown as RoomMember;
|
||||||
|
|
||||||
await groupCall.create();
|
await groupCall.create();
|
||||||
@@ -85,14 +110,212 @@ describe('Group Call', function() {
|
|||||||
try {
|
try {
|
||||||
await groupCall.enter();
|
await groupCall.enter();
|
||||||
|
|
||||||
expect(mockSendState.mock.lastCall[0]).toEqual(FAKE_ROOM_ID);
|
expect(mockSendState).toHaveBeenCalledWith(
|
||||||
expect(mockSendState.mock.lastCall[1]).toEqual(EventType.GroupCallMemberPrefix);
|
FAKE_ROOM_ID,
|
||||||
expect(mockSendState.mock.lastCall[2]['m.calls'].length).toEqual(1);
|
EventType.GroupCallMemberPrefix,
|
||||||
expect(mockSendState.mock.lastCall[2]['m.calls'][0]["m.call_id"]).toEqual(groupCall.groupCallId);
|
expect.objectContaining({
|
||||||
expect(mockSendState.mock.lastCall[2]['m.calls'][0]['m.devices'].length).toEqual(1);
|
"m.calls": [
|
||||||
expect(mockSendState.mock.lastCall[2]['m.calls'][0]['m.devices'][0].device_id).toEqual(FAKE_SELF_DEVICE_ID);
|
expect.objectContaining({
|
||||||
|
"m.call_id": groupCall.groupCallId,
|
||||||
|
"m.devices": [
|
||||||
|
expect.objectContaining({
|
||||||
|
device_id: FAKE_DEVICE_ID_1,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
FAKE_USER_ID_1,
|
||||||
|
);
|
||||||
} finally {
|
} finally {
|
||||||
groupCall.leave();
|
groupCall.leave();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("starts with mic unmuted in regular calls", async () => {
|
||||||
|
try {
|
||||||
|
await groupCall.create();
|
||||||
|
|
||||||
|
await groupCall.initLocalCallFeed();
|
||||||
|
|
||||||
|
expect(groupCall.isMicrophoneMuted()).toEqual(false);
|
||||||
|
} finally {
|
||||||
|
groupCall.leave();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("starts with mic muted in PTT calls", async () => {
|
||||||
|
try {
|
||||||
|
// replace groupcall with a PTT one for this test
|
||||||
|
// we will probably want a dedicated test suite for PTT calls, so when we do,
|
||||||
|
// this can go in there instead.
|
||||||
|
groupCall = new GroupCall(mockClient, room, GroupCallType.Video, true, GroupCallIntent.Prompt);
|
||||||
|
|
||||||
|
await groupCall.create();
|
||||||
|
|
||||||
|
await groupCall.initLocalCallFeed();
|
||||||
|
|
||||||
|
expect(groupCall.isMicrophoneMuted()).toEqual(true);
|
||||||
|
} finally {
|
||||||
|
groupCall.leave();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("disables audio stream when audio is set to muted", async () => {
|
||||||
|
try {
|
||||||
|
await groupCall.create();
|
||||||
|
|
||||||
|
await groupCall.initLocalCallFeed();
|
||||||
|
|
||||||
|
await groupCall.setMicrophoneMuted(true);
|
||||||
|
|
||||||
|
expect(groupCall.isMicrophoneMuted()).toEqual(true);
|
||||||
|
} finally {
|
||||||
|
groupCall.leave();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("starts with video unmuted in regular calls", async () => {
|
||||||
|
try {
|
||||||
|
await groupCall.create();
|
||||||
|
|
||||||
|
await groupCall.initLocalCallFeed();
|
||||||
|
|
||||||
|
expect(groupCall.isLocalVideoMuted()).toEqual(false);
|
||||||
|
} finally {
|
||||||
|
groupCall.leave();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("disables video stream when video is set to muted", async () => {
|
||||||
|
try {
|
||||||
|
await groupCall.create();
|
||||||
|
|
||||||
|
await groupCall.initLocalCallFeed();
|
||||||
|
|
||||||
|
await groupCall.setLocalVideoMuted(true);
|
||||||
|
|
||||||
|
expect(groupCall.isLocalVideoMuted()).toEqual(true);
|
||||||
|
} finally {
|
||||||
|
groupCall.leave();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Placing calls', function() {
|
||||||
|
let groupCall1: GroupCall;
|
||||||
|
let groupCall2: GroupCall;
|
||||||
|
let client1: MatrixClient;
|
||||||
|
let client2: MatrixClient;
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
MockRTCPeerConnection.resetInstances();
|
||||||
|
|
||||||
|
client1 = new MockCallMatrixClient(
|
||||||
|
FAKE_USER_ID_1, FAKE_DEVICE_ID_1, FAKE_SESSION_ID_1,
|
||||||
|
) as unknown as MatrixClient;
|
||||||
|
|
||||||
|
client2 = new MockCallMatrixClient(
|
||||||
|
FAKE_USER_ID_2, FAKE_DEVICE_ID_2, FAKE_SESSION_ID_2,
|
||||||
|
) as unknown as MatrixClient;
|
||||||
|
|
||||||
|
client1.sendStateEvent = client2.sendStateEvent = (roomId, eventType, content, statekey) => {
|
||||||
|
if (eventType === EventType.GroupCallMemberPrefix) {
|
||||||
|
const fakeEvent = {
|
||||||
|
getContent: () => content,
|
||||||
|
getRoomId: () => FAKE_ROOM_ID,
|
||||||
|
getStateKey: () => statekey,
|
||||||
|
} as unknown as MatrixEvent;
|
||||||
|
|
||||||
|
let subMap = client1Room.currentState.events.get(eventType);
|
||||||
|
if (!subMap) {
|
||||||
|
subMap = new Map<string, MatrixEvent>();
|
||||||
|
client1Room.currentState.events.set(eventType, subMap);
|
||||||
|
client2Room.currentState.events.set(eventType, subMap);
|
||||||
|
}
|
||||||
|
// since we cheat & use the same maps for each, we can
|
||||||
|
// just add it once.
|
||||||
|
subMap.set(statekey, fakeEvent);
|
||||||
|
|
||||||
|
groupCall1.onMemberStateChanged(fakeEvent);
|
||||||
|
groupCall2.onMemberStateChanged(fakeEvent);
|
||||||
|
}
|
||||||
|
return Promise.resolve(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const client1Room = new Room(FAKE_ROOM_ID, client1, FAKE_USER_ID_1);
|
||||||
|
|
||||||
|
const client2Room = new Room(FAKE_ROOM_ID, client2, FAKE_USER_ID_2);
|
||||||
|
|
||||||
|
groupCall1 = new GroupCall(
|
||||||
|
client1, client1Room, GroupCallType.Video, false, GroupCallIntent.Prompt, FAKE_CONF_ID,
|
||||||
|
);
|
||||||
|
|
||||||
|
groupCall2 = new GroupCall(
|
||||||
|
client2, client2Room, GroupCallType.Video, false, GroupCallIntent.Prompt, FAKE_CONF_ID,
|
||||||
|
);
|
||||||
|
|
||||||
|
client1Room.currentState.members[FAKE_USER_ID_1] = {
|
||||||
|
userId: FAKE_USER_ID_1,
|
||||||
|
} as unknown as RoomMember;
|
||||||
|
client1Room.currentState.members[FAKE_USER_ID_2] = {
|
||||||
|
userId: FAKE_USER_ID_2,
|
||||||
|
} as unknown as RoomMember;
|
||||||
|
|
||||||
|
client2Room.currentState.members[FAKE_USER_ID_1] = {
|
||||||
|
userId: FAKE_USER_ID_1,
|
||||||
|
} as unknown as RoomMember;
|
||||||
|
client2Room.currentState.members[FAKE_USER_ID_2] = {
|
||||||
|
userId: FAKE_USER_ID_2,
|
||||||
|
} as unknown as RoomMember;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function() {
|
||||||
|
MockRTCPeerConnection.resetInstances();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Places a call to a peer", async function() {
|
||||||
|
await groupCall1.create();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// keep this as its own variable so we have it typed as a mock
|
||||||
|
// rather than its type in the client object
|
||||||
|
const mockSendToDevice = jest.fn<Promise<{}>, [
|
||||||
|
eventType: string,
|
||||||
|
contentMap: { [userId: string]: { [deviceId: string]: Record<string, any> } },
|
||||||
|
txnId?: string,
|
||||||
|
]>();
|
||||||
|
|
||||||
|
const toDeviceProm = new Promise<void>(resolve => {
|
||||||
|
mockSendToDevice.mockImplementation(() => {
|
||||||
|
resolve();
|
||||||
|
return Promise.resolve({});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
client1.sendToDevice = mockSendToDevice;
|
||||||
|
|
||||||
|
await Promise.all([groupCall1.enter(), groupCall2.enter()]);
|
||||||
|
|
||||||
|
MockRTCPeerConnection.triggerAllNegotiations();
|
||||||
|
|
||||||
|
await toDeviceProm;
|
||||||
|
|
||||||
|
expect(mockSendToDevice.mock.calls[0][0]).toBe("m.call.invite");
|
||||||
|
|
||||||
|
const toDeviceCallContent = mockSendToDevice.mock.calls[0][1];
|
||||||
|
expect(Object.keys(toDeviceCallContent).length).toBe(1);
|
||||||
|
expect(Object.keys(toDeviceCallContent)[0]).toBe(FAKE_USER_ID_2);
|
||||||
|
|
||||||
|
const toDeviceBobDevices = toDeviceCallContent[FAKE_USER_ID_2];
|
||||||
|
expect(Object.keys(toDeviceBobDevices).length).toBe(1);
|
||||||
|
expect(Object.keys(toDeviceBobDevices)[0]).toBe(FAKE_DEVICE_ID_2);
|
||||||
|
|
||||||
|
const bobDeviceMessage = toDeviceBobDevices[FAKE_DEVICE_ID_2];
|
||||||
|
expect(bobDeviceMessage.conf_id).toBe(FAKE_CONF_ID);
|
||||||
|
} finally {
|
||||||
|
await Promise.all([groupCall1.leave(), groupCall2.leave()]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
Reference in New Issue
Block a user