Redux state: Allow multiple calls to be stored

This commit is contained in:
Evan Hahn 2020-11-06 11:36:37 -06:00 committed by GitHub
parent 753e0279c6
commit 3468de255d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 1191 additions and 515 deletions

View file

@ -2,28 +2,309 @@
// SPDX-License-Identifier: AGPL-3.0-only
import { assert } from 'chai';
import {
CallDetailsType,
actions,
getEmptyState,
isCallActive,
reducer,
} from '../../../state/ducks/calling';
import * as sinon from 'sinon';
import { reducer as rootReducer } from '../../../state/reducer';
import { noopAction } from '../../../state/ducks/noop';
import { actions, getEmptyState, reducer } from '../../../state/ducks/calling';
import { calling as callingService } from '../../../services/calling';
import { CallState } from '../../../types/Calling';
describe('calling duck', () => {
const stateWithDirectCall = {
...getEmptyState(),
callsByConversation: {
'fake-direct-call-conversation-id': {
conversationId: 'fake-direct-call-conversation-id',
callState: CallState.Accepted,
isIncoming: false,
isVideoCall: false,
hasRemoteVideo: false,
},
},
};
const stateWithActiveDirectCall = {
...stateWithDirectCall,
activeCallState: {
conversationId: 'fake-direct-call-conversation-id',
hasLocalAudio: true,
hasLocalVideo: false,
participantsList: false,
pip: false,
settingsDialogOpen: false,
},
};
const stateWithIncomingDirectCall = {
...getEmptyState(),
callsByConversation: {
'fake-direct-call-conversation-id': {
conversationId: 'fake-direct-call-conversation-id',
callState: CallState.Ringing,
isIncoming: true,
isVideoCall: false,
hasRemoteVideo: false,
},
},
};
const getEmptyRootState = () => rootReducer(undefined, noopAction());
beforeEach(function beforeEach() {
this.sandbox = sinon.createSandbox();
});
afterEach(function afterEach() {
this.sandbox.restore();
});
describe('actions', () => {
describe('acceptCall', () => {
const { acceptCall } = actions;
beforeEach(function beforeEach() {
this.callingServiceAccept = this.sandbox
.stub(callingService, 'accept')
.resolves();
});
it('dispatches an ACCEPT_CALL_PENDING action', async () => {
const dispatch = sinon.spy();
await acceptCall({
conversationId: '123',
asVideoCall: true,
})(dispatch, getEmptyRootState, null);
sinon.assert.calledOnce(dispatch);
sinon.assert.calledWith(dispatch, {
type: 'calling/ACCEPT_CALL_PENDING',
payload: {
conversationId: '123',
asVideoCall: true,
},
});
await acceptCall({
conversationId: '456',
asVideoCall: false,
})(dispatch, getEmptyRootState, null);
sinon.assert.calledTwice(dispatch);
sinon.assert.calledWith(dispatch, {
type: 'calling/ACCEPT_CALL_PENDING',
payload: {
conversationId: '456',
asVideoCall: false,
},
});
});
it('asks the calling service to accept the call', async function test() {
const dispatch = sinon.spy();
await acceptCall({
conversationId: '123',
asVideoCall: true,
})(dispatch, getEmptyRootState, null);
sinon.assert.calledOnce(this.callingServiceAccept);
sinon.assert.calledWith(this.callingServiceAccept, '123', true);
await acceptCall({
conversationId: '456',
asVideoCall: false,
})(dispatch, getEmptyRootState, null);
sinon.assert.calledTwice(this.callingServiceAccept);
sinon.assert.calledWith(this.callingServiceAccept, '456', 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, getEmptyRootState, 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,
participantsList: false,
pip: false,
settingsDialogOpen: false,
});
});
});
describe('setLocalAudio', () => {
const { setLocalAudio } = actions;
beforeEach(function beforeEach() {
this.callingServiceSetOutgoingAudio = this.sandbox.stub(
callingService,
'setOutgoingAudio'
);
});
it('dispatches a SET_LOCAL_AUDIO_FULFILLED action', () => {
const dispatch = sinon.spy();
setLocalAudio({ enabled: true })(dispatch, getEmptyRootState, null);
sinon.assert.calledOnce(dispatch);
sinon.assert.calledWith(dispatch, {
type: 'calling/SET_LOCAL_AUDIO_FULFILLED',
payload: { enabled: true },
});
});
it('updates the outgoing audio for the active call', function test() {
const dispatch = sinon.spy();
setLocalAudio({ enabled: false })(
dispatch,
() => ({
...getEmptyRootState(),
calling: stateWithActiveDirectCall,
}),
null
);
sinon.assert.calledOnce(this.callingServiceSetOutgoingAudio);
sinon.assert.calledWith(
this.callingServiceSetOutgoingAudio,
'fake-direct-call-conversation-id',
false
);
setLocalAudio({ enabled: true })(
dispatch,
() => ({
...getEmptyRootState(),
calling: stateWithActiveDirectCall,
}),
null
);
sinon.assert.calledTwice(this.callingServiceSetOutgoingAudio);
sinon.assert.calledWith(
this.callingServiceSetOutgoingAudio,
'fake-direct-call-conversation-id',
true
);
});
it('updates the local audio state with SET_LOCAL_AUDIO_FULFILLED', () => {
const dispatch = sinon.spy();
setLocalAudio({ enabled: false })(dispatch, getEmptyRootState, null);
const action = dispatch.getCall(0).args[0];
const result = reducer(stateWithActiveDirectCall, action);
assert.isFalse(result.activeCallState?.hasLocalAudio);
});
});
describe('showCallLobby', () => {
const { showCallLobby } = actions;
it('saves the call and makes it active', () => {
const result = reducer(
getEmptyState(),
showCallLobby({
conversationId: 'fake-conversation-id',
isVideoCall: true,
})
);
assert.deepEqual(result.callsByConversation['fake-conversation-id'], {
conversationId: 'fake-conversation-id',
isIncoming: false,
isVideoCall: true,
});
assert.deepEqual(result.activeCallState, {
conversationId: 'fake-conversation-id',
hasLocalAudio: true,
hasLocalVideo: true,
participantsList: false,
pip: false,
settingsDialogOpen: false,
});
});
});
describe('startCall', () => {
const { startCall } = actions;
beforeEach(function beforeEach() {
this.callingStartOutgoingCall = this.sandbox.stub(
callingService,
'startOutgoingCall'
);
});
it('asks the calling service to start an outgoing call', function test() {
startCall({
conversationId: '123',
hasLocalAudio: true,
hasLocalVideo: false,
});
sinon.assert.calledOnce(this.callingStartOutgoingCall);
sinon.assert.calledWith(
this.callingStartOutgoingCall,
'123',
true,
false
);
});
it('saves the call and makes it active', () => {
const result = reducer(
getEmptyState(),
startCall({
conversationId: 'fake-conversation-id',
hasLocalAudio: true,
hasLocalVideo: false,
})
);
assert.deepEqual(result.callsByConversation['fake-conversation-id'], {
conversationId: 'fake-conversation-id',
callState: CallState.Prering,
isIncoming: false,
isVideoCall: false,
});
assert.deepEqual(result.activeCallState, {
conversationId: 'fake-conversation-id',
hasLocalAudio: true,
hasLocalVideo: false,
participantsList: false,
pip: false,
settingsDialogOpen: false,
});
});
});
describe('toggleSettings', () => {
const { toggleSettings } = actions;
it('toggles the settings dialog', () => {
const afterOneToggle = reducer(getEmptyState(), toggleSettings());
const afterOneToggle = reducer(
stateWithActiveDirectCall,
toggleSettings()
);
const afterTwoToggles = reducer(afterOneToggle, toggleSettings());
const afterThreeToggles = reducer(afterTwoToggles, toggleSettings());
assert.isTrue(afterOneToggle.settingsDialogOpen);
assert.isFalse(afterTwoToggles.settingsDialogOpen);
assert.isTrue(afterThreeToggles.settingsDialogOpen);
assert.isTrue(afterOneToggle.activeCallState?.settingsDialogOpen);
assert.isFalse(afterTwoToggles.activeCallState?.settingsDialogOpen);
assert.isTrue(afterThreeToggles.activeCallState?.settingsDialogOpen);
});
});
@ -31,16 +312,19 @@ describe('calling duck', () => {
const { toggleParticipants } = actions;
it('toggles the participants list', () => {
const afterOneToggle = reducer(getEmptyState(), toggleParticipants());
const afterOneToggle = reducer(
stateWithActiveDirectCall,
toggleParticipants()
);
const afterTwoToggles = reducer(afterOneToggle, toggleParticipants());
const afterThreeToggles = reducer(
afterTwoToggles,
toggleParticipants()
);
assert.isTrue(afterOneToggle.participantsList);
assert.isFalse(afterTwoToggles.participantsList);
assert.isTrue(afterThreeToggles.participantsList);
assert.isTrue(afterOneToggle.activeCallState?.participantsList);
assert.isFalse(afterTwoToggles.activeCallState?.participantsList);
assert.isTrue(afterThreeToggles.activeCallState?.participantsList);
});
});
@ -48,111 +332,13 @@ describe('calling duck', () => {
const { togglePip } = actions;
it('toggles the PiP', () => {
const afterOneToggle = reducer(getEmptyState(), togglePip());
const afterOneToggle = reducer(stateWithActiveDirectCall, togglePip());
const afterTwoToggles = reducer(afterOneToggle, togglePip());
const afterThreeToggles = reducer(afterTwoToggles, togglePip());
assert.isTrue(afterOneToggle.pip);
assert.isFalse(afterTwoToggles.pip);
assert.isTrue(afterThreeToggles.pip);
});
});
});
describe('helpers', () => {
describe('isCallActive', () => {
const fakeCallDetails: CallDetailsType = {
id: 'fake-call',
title: 'Fake Call',
callId: 123,
isIncoming: false,
isVideoCall: false,
};
it('returns false if there are no call details', () => {
assert.isFalse(isCallActive(getEmptyState()));
});
it('returns false if an incoming call is in a pre-reing state', () => {
assert.isFalse(
isCallActive({
...getEmptyState(),
callDetails: {
...fakeCallDetails,
isIncoming: true,
},
callState: CallState.Prering,
})
);
});
it('returns true if an outgoing call is in a pre-reing state', () => {
assert.isTrue(
isCallActive({
...getEmptyState(),
callDetails: {
...fakeCallDetails,
isIncoming: false,
},
callState: CallState.Prering,
})
);
});
it('returns false if an incoming call is ringing', () => {
assert.isFalse(
isCallActive({
...getEmptyState(),
callDetails: {
...fakeCallDetails,
isIncoming: true,
},
callState: CallState.Ringing,
})
);
});
it('returns true if an outgoing call is ringing', () => {
assert.isTrue(
isCallActive({
...getEmptyState(),
callDetails: {
...fakeCallDetails,
isIncoming: false,
},
callState: CallState.Ringing,
})
);
});
it('returns true if a call is in an accepted state', () => {
assert.isTrue(
isCallActive({
...getEmptyState(),
callDetails: fakeCallDetails,
callState: CallState.Accepted,
})
);
});
it('returns true if a call is in a reconnecting state', () => {
assert.isTrue(
isCallActive({
...getEmptyState(),
callDetails: fakeCallDetails,
callState: CallState.Reconnecting,
})
);
});
it('returns false if a call is in an ended state', () => {
assert.isFalse(
isCallActive({
...getEmptyState(),
callDetails: fakeCallDetails,
callState: CallState.Ended,
})
);
assert.isTrue(afterOneToggle.activeCallState?.pip);
assert.isFalse(afterTwoToggles.activeCallState?.pip);
assert.isTrue(afterThreeToggles.activeCallState?.pip);
});
});
});