Receive rings for group calls

This commit is contained in:
Evan Hahn 2021-08-20 11:06:15 -05:00 committed by GitHub
parent fe040a2873
commit 79c976668b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 2112 additions and 359 deletions

View file

@ -95,6 +95,20 @@ describe('calling duck', () => {
},
};
const stateWithIncomingGroupCall = {
...stateWithGroupCall,
callsByConversation: {
...stateWithGroupCall.callsByConversation,
'fake-group-call-conversation-id': {
...stateWithGroupCall.callsByConversation[
'fake-group-call-conversation-id'
],
ringId: BigInt(123),
ringerUuid: '789',
},
},
};
const stateWithActiveGroupCall = {
...stateWithGroupCall,
activeCallState: {
@ -318,81 +332,191 @@ describe('calling duck', () => {
beforeEach(function beforeEach() {
this.callingServiceAccept = this.sandbox
.stub(callingService, 'accept')
.stub(callingService, 'acceptDirectCall')
.resolves();
this.callingServiceJoin = this.sandbox.stub(
callingService,
'joinGroupCall'
);
});
it('dispatches an ACCEPT_CALL_PENDING action', async () => {
const dispatch = sinon.spy();
describe('accepting a direct call', () => {
const getState = () => ({
...getEmptyRootState(),
calling: stateWithIncomingDirectCall,
});
await acceptCall({
conversationId: '123',
asVideoCall: true,
})(dispatch, getEmptyRootState, null);
it('dispatches an ACCEPT_CALL_PENDING action', async () => {
const dispatch = sinon.spy();
sinon.assert.calledOnce(dispatch);
sinon.assert.calledWith(dispatch, {
type: 'calling/ACCEPT_CALL_PENDING',
payload: {
conversationId: '123',
await acceptCall({
conversationId: 'fake-direct-call-conversation-id',
asVideoCall: true,
},
});
})(dispatch, getState, null);
await acceptCall({
conversationId: '456',
asVideoCall: false,
})(dispatch, getEmptyRootState, null);
sinon.assert.calledOnce(dispatch);
sinon.assert.calledWith(dispatch, {
type: 'calling/ACCEPT_CALL_PENDING',
payload: {
conversationId: 'fake-direct-call-conversation-id',
asVideoCall: true,
},
});
sinon.assert.calledTwice(dispatch);
sinon.assert.calledWith(dispatch, {
type: 'calling/ACCEPT_CALL_PENDING',
payload: {
conversationId: '456',
await acceptCall({
conversationId: 'fake-direct-call-conversation-id',
asVideoCall: false,
},
})(dispatch, getState, null);
sinon.assert.calledTwice(dispatch);
sinon.assert.calledWith(dispatch, {
type: 'calling/ACCEPT_CALL_PENDING',
payload: {
conversationId: 'fake-direct-call-conversation-id',
asVideoCall: false,
},
});
});
it('asks the calling service to accept the call', async function test() {
const dispatch = sinon.spy();
await acceptCall({
conversationId: 'fake-direct-call-conversation-id',
asVideoCall: true,
})(dispatch, getState, null);
sinon.assert.calledOnce(this.callingServiceAccept);
sinon.assert.calledWith(
this.callingServiceAccept,
'fake-direct-call-conversation-id',
true
);
await acceptCall({
conversationId: 'fake-direct-call-conversation-id',
asVideoCall: false,
})(dispatch, getState, null);
sinon.assert.calledTwice(this.callingServiceAccept);
sinon.assert.calledWith(
this.callingServiceAccept,
'fake-direct-call-conversation-id',
false
);
});
it('updates the active call state with ACCEPT_CALL_PENDING', async () => {
const dispatch = sinon.spy();
await acceptCall({
conversationId: 'fake-direct-call-conversation-id',
asVideoCall: true,
})(dispatch, getState, null);
const action = dispatch.getCall(0).args[0];
const result = reducer(stateWithIncomingDirectCall, action);
assert.deepEqual(result.activeCallState, {
conversationId: 'fake-direct-call-conversation-id',
hasLocalAudio: true,
hasLocalVideo: true,
isInSpeakerView: false,
showParticipantsList: false,
safetyNumberChangedUuids: [],
pip: false,
settingsDialogOpen: false,
});
});
});
it('asks the calling service to accept the call', async function test() {
const dispatch = sinon.spy();
describe('accepting a group call', () => {
const getState = () => ({
...getEmptyRootState(),
calling: stateWithIncomingGroupCall,
});
await acceptCall({
conversationId: '123',
asVideoCall: true,
})(dispatch, getEmptyRootState, null);
it('dispatches an ACCEPT_CALL_PENDING action', async () => {
const dispatch = sinon.spy();
sinon.assert.calledOnce(this.callingServiceAccept);
sinon.assert.calledWith(this.callingServiceAccept, '123', true);
await acceptCall({
conversationId: 'fake-group-call-conversation-id',
asVideoCall: true,
})(dispatch, getState, null);
await acceptCall({
conversationId: '456',
asVideoCall: false,
})(dispatch, getEmptyRootState, null);
sinon.assert.calledOnce(dispatch);
sinon.assert.calledWith(dispatch, {
type: 'calling/ACCEPT_CALL_PENDING',
payload: {
conversationId: 'fake-group-call-conversation-id',
asVideoCall: true,
},
});
sinon.assert.calledTwice(this.callingServiceAccept);
sinon.assert.calledWith(this.callingServiceAccept, '456', false);
});
await acceptCall({
conversationId: 'fake-group-call-conversation-id',
asVideoCall: false,
})(dispatch, getState, null);
it('updates the active call state with ACCEPT_CALL_PENDING', async () => {
const dispatch = sinon.spy();
await acceptCall({
conversationId: 'fake-direct-call-conversation-id',
asVideoCall: true,
})(dispatch, getEmptyRootState, null);
const action = dispatch.getCall(0).args[0];
sinon.assert.calledTwice(dispatch);
sinon.assert.calledWith(dispatch, {
type: 'calling/ACCEPT_CALL_PENDING',
payload: {
conversationId: 'fake-group-call-conversation-id',
asVideoCall: false,
},
});
});
const result = reducer(stateWithIncomingDirectCall, action);
it('asks the calling service to join the call', async function test() {
const dispatch = sinon.spy();
assert.deepEqual(result.activeCallState, {
conversationId: 'fake-direct-call-conversation-id',
hasLocalAudio: true,
hasLocalVideo: true,
isInSpeakerView: false,
showParticipantsList: false,
safetyNumberChangedUuids: [],
pip: false,
settingsDialogOpen: false,
await acceptCall({
conversationId: 'fake-group-call-conversation-id',
asVideoCall: true,
})(dispatch, getState, null);
sinon.assert.calledOnce(this.callingServiceJoin);
sinon.assert.calledWith(
this.callingServiceJoin,
'fake-group-call-conversation-id',
true,
true
);
await acceptCall({
conversationId: 'fake-group-call-conversation-id',
asVideoCall: false,
})(dispatch, getState, null);
sinon.assert.calledTwice(this.callingServiceJoin);
sinon.assert.calledWith(
this.callingServiceJoin,
'fake-group-call-conversation-id',
true,
false
);
});
it('updates the active call state with ACCEPT_CALL_PENDING', async () => {
const dispatch = sinon.spy();
await acceptCall({
conversationId: 'fake-group-call-conversation-id',
asVideoCall: true,
})(dispatch, getState, null);
const action = dispatch.getCall(0).args[0];
const result = reducer(stateWithIncomingGroupCall, action);
assert.deepEqual(result.activeCallState, {
conversationId: 'fake-group-call-conversation-id',
hasLocalAudio: true,
hasLocalVideo: true,
isInSpeakerView: false,
showParticipantsList: false,
safetyNumberChangedUuids: [],
pip: false,
settingsDialogOpen: false,
});
});
});
});
@ -441,6 +565,201 @@ describe('calling duck', () => {
});
});
describe('cancelIncomingGroupCallRing', () => {
const { cancelIncomingGroupCallRing } = actions;
it('does nothing if there is no associated group call', () => {
const state = getEmptyState();
const action = cancelIncomingGroupCallRing({
conversationId: 'garbage',
ringId: BigInt(1),
});
const result = reducer(state, action);
assert.strictEqual(result, state);
});
it("does nothing if the ring to cancel isn't the same one", () => {
const action = cancelIncomingGroupCallRing({
conversationId: 'fake-group-call-conversation-id',
ringId: BigInt(999),
});
const result = reducer(stateWithIncomingGroupCall, action);
assert.strictEqual(result, stateWithIncomingGroupCall);
});
it("removes the call from the state if it's not connected", () => {
const state = {
...stateWithGroupCall,
callsByConversation: {
...stateWithGroupCall.callsByConversation,
'fake-group-call-conversation-id': {
...stateWithGroupCall.callsByConversation[
'fake-group-call-conversation-id'
],
connectionState: GroupCallConnectionState.NotConnected,
ringId: BigInt(123),
ringerUuid: '789',
},
},
};
const action = cancelIncomingGroupCallRing({
conversationId: 'fake-group-call-conversation-id',
ringId: BigInt(123),
});
const result = reducer(state, action);
assert.notProperty(
result.callsByConversation,
'fake-group-call-conversation-id'
);
});
it("removes the ring state, but not the call, if it's connected", () => {
const action = cancelIncomingGroupCallRing({
conversationId: 'fake-group-call-conversation-id',
ringId: BigInt(123),
});
const result = reducer(stateWithIncomingGroupCall, action);
const call =
result.callsByConversation['fake-group-call-conversation-id'];
// It'd be nice to do this with an assert, but Chai doesn't understand it.
if (call?.callMode !== CallMode.Group) {
throw new Error('Expected to find a group call');
}
assert.isUndefined(call.ringId);
assert.isUndefined(call.ringerUuid);
});
});
describe('declineCall', () => {
const { declineCall } = actions;
let declineDirectCall: sinon.SinonStub;
let declineGroupCall: sinon.SinonStub;
beforeEach(function beforeEach() {
declineDirectCall = this.sandbox.stub(
callingService,
'declineDirectCall'
);
declineGroupCall = this.sandbox.stub(
callingService,
'declineGroupCall'
);
});
describe('declining a direct call', () => {
const getState = () => ({
...getEmptyRootState(),
calling: stateWithIncomingDirectCall,
});
it('dispatches a DECLINE_DIRECT_CALL action', () => {
const dispatch = sinon.spy();
declineCall({ conversationId: 'fake-direct-call-conversation-id' })(
dispatch,
getState,
null
);
sinon.assert.calledOnce(dispatch);
sinon.assert.calledWith(dispatch, {
type: 'calling/DECLINE_DIRECT_CALL',
payload: {
conversationId: 'fake-direct-call-conversation-id',
},
});
});
it('asks the calling service to decline the call', () => {
const dispatch = sinon.spy();
declineCall({ conversationId: 'fake-direct-call-conversation-id' })(
dispatch,
getState,
null
);
sinon.assert.calledOnce(declineDirectCall);
sinon.assert.calledWith(
declineDirectCall,
'fake-direct-call-conversation-id'
);
});
it('removes the call from the state', () => {
const dispatch = sinon.spy();
declineCall({ conversationId: 'fake-direct-call-conversation-id' })(
dispatch,
getState,
null
);
const action = dispatch.getCall(0).args[0];
const result = reducer(stateWithIncomingGroupCall, action);
assert.notProperty(
result.callsByConversation,
'fake-direct-call-conversation-id'
);
});
});
describe('declining a group call', () => {
const getState = () => ({
...getEmptyRootState(),
calling: stateWithIncomingGroupCall,
});
it('dispatches a CANCEL_INCOMING_GROUP_CALL_RING action', () => {
const dispatch = sinon.spy();
declineCall({ conversationId: 'fake-group-call-conversation-id' })(
dispatch,
getState,
null
);
sinon.assert.calledOnce(dispatch);
sinon.assert.calledWith(dispatch, {
type: 'calling/CANCEL_INCOMING_GROUP_CALL_RING',
payload: {
conversationId: 'fake-group-call-conversation-id',
ringId: BigInt(123),
},
});
});
it('asks the calling service to decline the call', () => {
const dispatch = sinon.spy();
declineCall({ conversationId: 'fake-group-call-conversation-id' })(
dispatch,
getState,
null
);
sinon.assert.calledOnce(declineGroupCall);
sinon.assert.calledWith(
declineGroupCall,
'fake-group-call-conversation-id',
BigInt(123)
);
});
// NOTE: The state effects of this action are tested with
// `cancelIncomingGroupCallRing`.
});
});
describe('groupCallStateChange', () => {
const { groupCallStateChange } = actions;
@ -475,7 +794,7 @@ describe('calling duck', () => {
assert.deepEqual(result, getEmptyState());
});
it('removes the call from the map of conversations if the call is not connected and has no peeked participants', () => {
it('removes the call from the map of conversations if the call is not connected and has no peeked participants or ringer', () => {
const result = reducer(
stateWithGroupCall,
getAction({
@ -659,6 +978,29 @@ describe('calling duck', () => {
);
});
it('saves a call to the map of conversations if the call had a ringer, even if it was otherwise ignorable', () => {
const result = reducer(
stateWithIncomingGroupCall,
getAction({
conversationId: 'fake-group-call-conversation-id',
connectionState: GroupCallConnectionState.NotConnected,
joinState: GroupCallJoinState.NotJoined,
hasLocalAudio: false,
hasLocalVideo: false,
peekInfo: {
uuids: [],
maxDevices: 16,
deviceCount: 0,
},
remoteParticipants: [],
})
);
assert.isDefined(
result.callsByConversation['fake-group-call-conversation-id']
);
});
it('updates a call in the map of conversations', () => {
const result = reducer(
stateWithGroupCall,
@ -714,6 +1056,108 @@ describe('calling duck', () => {
);
});
it("keeps the existing ring state if you haven't joined the call", () => {
const state = {
...stateWithGroupCall,
callsByConversation: {
...stateWithGroupCall.callsByConversation,
'fake-group-call-conversation-id': {
...stateWithGroupCall.callsByConversation[
'fake-group-call-conversation-id'
],
ringId: BigInt(456),
ringerUuid: '55addfd8-09ed-4f5b-b42e-01058898d13b',
},
},
};
const result = reducer(
state,
getAction({
conversationId: 'fake-group-call-conversation-id',
connectionState: GroupCallConnectionState.Connected,
joinState: GroupCallJoinState.NotJoined,
hasLocalAudio: true,
hasLocalVideo: false,
peekInfo: {
uuids: ['1b9e4d42-1f56-45c5-b6f4-d1be5a54fefa'],
maxDevices: 16,
deviceCount: 1,
},
remoteParticipants: [
{
uuid: '123',
demuxId: 456,
hasRemoteAudio: false,
hasRemoteVideo: true,
presenting: false,
sharingScreen: false,
videoAspectRatio: 16 / 9,
},
],
})
);
assert.include(
result.callsByConversation['fake-group-call-conversation-id'],
{
callMode: CallMode.Group,
ringId: BigInt(456),
ringerUuid: '55addfd8-09ed-4f5b-b42e-01058898d13b',
}
);
});
it("removes the ring state if you've joined the call", () => {
const state = {
...stateWithGroupCall,
callsByConversation: {
...stateWithGroupCall.callsByConversation,
'fake-group-call-conversation-id': {
...stateWithGroupCall.callsByConversation[
'fake-group-call-conversation-id'
],
ringId: BigInt(456),
ringerUuid: '55addfd8-09ed-4f5b-b42e-01058898d13b',
},
},
};
const result = reducer(
state,
getAction({
conversationId: 'fake-group-call-conversation-id',
connectionState: GroupCallConnectionState.Connected,
joinState: GroupCallJoinState.Joined,
hasLocalAudio: true,
hasLocalVideo: false,
peekInfo: {
uuids: ['1b9e4d42-1f56-45c5-b6f4-d1be5a54fefa'],
maxDevices: 16,
deviceCount: 1,
},
remoteParticipants: [
{
uuid: '123',
demuxId: 456,
hasRemoteAudio: false,
hasRemoteVideo: true,
presenting: false,
sharingScreen: false,
videoAspectRatio: 16 / 9,
},
],
})
);
assert.notProperty(
result.callsByConversation['fake-group-call-conversation-id'],
'ringId'
);
assert.notProperty(
result.callsByConversation['fake-group-call-conversation-id'],
'ringerUuid'
);
});
it("if no call is active, doesn't touch the active call state", () => {
const result = reducer(
stateWithGroupCall,
@ -910,6 +1354,88 @@ describe('calling duck', () => {
});
});
describe('receiveIncomingGroupCall', () => {
const { receiveIncomingGroupCall } = actions;
it('does nothing if the call was already ringing', () => {
const action = receiveIncomingGroupCall({
conversationId: 'fake-group-call-conversation-id',
ringId: BigInt(456),
ringerUuid: '208b8ce6-3a73-48ee-9c8a-32e6196f6e96',
});
const result = reducer(stateWithIncomingGroupCall, action);
assert.strictEqual(result, stateWithIncomingGroupCall);
});
it('does nothing if the call was already joined', () => {
const state = {
...stateWithGroupCall,
callsByConversation: {
...stateWithGroupCall.callsByConversation,
'fake-group-call-conversation-id': {
...stateWithGroupCall.callsByConversation[
'fake-group-call-conversation-id'
],
joinState: GroupCallJoinState.Joined,
},
},
};
const action = receiveIncomingGroupCall({
conversationId: 'fake-group-call-conversation-id',
ringId: BigInt(456),
ringerUuid: '208b8ce6-3a73-48ee-9c8a-32e6196f6e96',
});
const result = reducer(state, action);
assert.strictEqual(result, state);
});
it('creates a new group call if one did not exist', () => {
const action = receiveIncomingGroupCall({
conversationId: 'fake-group-call-conversation-id',
ringId: BigInt(456),
ringerUuid: '208b8ce6-3a73-48ee-9c8a-32e6196f6e96',
});
const result = reducer(getEmptyState(), action);
assert.deepEqual(
result.callsByConversation['fake-group-call-conversation-id'],
{
callMode: CallMode.Group,
conversationId: 'fake-group-call-conversation-id',
connectionState: GroupCallConnectionState.NotConnected,
joinState: GroupCallJoinState.NotJoined,
peekInfo: {
uuids: [],
maxDevices: Infinity,
deviceCount: 0,
},
remoteParticipants: [],
ringId: BigInt(456),
ringerUuid: '208b8ce6-3a73-48ee-9c8a-32e6196f6e96',
}
);
});
it('attaches ring state to an existing call', () => {
const action = receiveIncomingGroupCall({
conversationId: 'fake-group-call-conversation-id',
ringId: BigInt(456),
ringerUuid: '208b8ce6-3a73-48ee-9c8a-32e6196f6e96',
});
const result = reducer(stateWithGroupCall, action);
assert.include(
result.callsByConversation['fake-group-call-conversation-id'],
{
ringId: BigInt(456),
ringerUuid: '208b8ce6-3a73-48ee-9c8a-32e6196f6e96',
}
);
});
});
describe('setLocalAudio', () => {
const { setLocalAudio } = actions;
@ -1187,6 +1713,55 @@ describe('calling duck', () => {
deviceCount: 1,
});
});
it("doesn't overwrite an existing group call's ring state if it was set previously", () => {
const result = reducer(
{
...stateWithGroupCall,
callsByConversation: {
'fake-group-call-conversation-id': {
...stateWithGroupCall.callsByConversation[
'fake-group-call-conversation-id'
],
ringId: BigInt(987),
ringerUuid: 'd59f05f7-3be8-4d44-a1e8-0d7cb5677ed8',
},
},
},
showCallLobby({
callMode: CallMode.Group,
conversationId: 'fake-group-call-conversation-id',
hasLocalAudio: true,
hasLocalVideo: true,
connectionState: GroupCallConnectionState.Connected,
joinState: GroupCallJoinState.NotJoined,
peekInfo: undefined,
remoteParticipants: [
{
uuid: '123',
demuxId: 123,
hasRemoteAudio: true,
hasRemoteVideo: true,
presenting: false,
sharingScreen: false,
videoAspectRatio: 4 / 3,
},
],
})
);
const call =
result.callsByConversation['fake-group-call-conversation-id'];
// It'd be nice to do this with an assert, but Chai doesn't understand it.
if (call?.callMode !== CallMode.Group) {
throw new Error('Expected to find a group call');
}
assert.strictEqual(call.ringId, BigInt(987));
assert.strictEqual(
call.ringerUuid,
'd59f05f7-3be8-4d44-a1e8-0d7cb5677ed8'
);
});
});
describe('startCall', () => {