@webex/plugin-meetings 3.0.0-beta.115 → 3.0.0-beta.117

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/dist/breakouts/breakout.js +23 -6
  2. package/dist/breakouts/breakout.js.map +1 -1
  3. package/dist/breakouts/index.js +178 -139
  4. package/dist/breakouts/index.js.map +1 -1
  5. package/dist/constants.js +1 -0
  6. package/dist/constants.js.map +1 -1
  7. package/dist/locus-info/mediaSharesUtils.js +15 -1
  8. package/dist/locus-info/mediaSharesUtils.js.map +1 -1
  9. package/dist/meeting/index.js +73 -103
  10. package/dist/meeting/index.js.map +1 -1
  11. package/dist/meeting/locusMediaRequest.js +3 -0
  12. package/dist/meeting/locusMediaRequest.js.map +1 -1
  13. package/dist/meeting/muteState.js +1 -1
  14. package/dist/meeting/muteState.js.map +1 -1
  15. package/dist/meeting/request.js +27 -20
  16. package/dist/meeting/request.js.map +1 -1
  17. package/dist/meeting/util.js +463 -426
  18. package/dist/meeting/util.js.map +1 -1
  19. package/dist/members/index.js +4 -1
  20. package/dist/members/index.js.map +1 -1
  21. package/dist/members/request.js +75 -45
  22. package/dist/members/request.js.map +1 -1
  23. package/dist/members/util.js +308 -317
  24. package/dist/members/util.js.map +1 -1
  25. package/dist/types/constants.d.ts +1 -0
  26. package/dist/types/meeting/index.d.ts +20 -21
  27. package/dist/types/meeting/locusMediaRequest.d.ts +2 -0
  28. package/dist/types/meeting/request.d.ts +16 -8
  29. package/dist/types/meeting/util.d.ts +75 -1
  30. package/dist/types/members/request.d.ts +56 -11
  31. package/dist/types/members/util.d.ts +209 -1
  32. package/package.json +19 -19
  33. package/src/breakouts/breakout.ts +26 -4
  34. package/src/breakouts/index.ts +32 -17
  35. package/src/constants.ts +1 -0
  36. package/src/locus-info/mediaSharesUtils.ts +16 -0
  37. package/src/meeting/index.ts +20 -42
  38. package/src/meeting/locusMediaRequest.ts +6 -0
  39. package/src/meeting/muteState.ts +1 -1
  40. package/src/meeting/request.ts +26 -17
  41. package/src/meeting/util.ts +446 -410
  42. package/src/members/index.ts +7 -1
  43. package/src/members/request.ts +61 -21
  44. package/src/members/util.ts +316 -326
  45. package/test/unit/spec/breakouts/breakout.ts +26 -7
  46. package/test/unit/spec/breakouts/index.ts +48 -3
  47. package/test/unit/spec/meeting/index.js +53 -33
  48. package/test/unit/spec/meeting/locusMediaRequest.ts +25 -3
  49. package/test/unit/spec/meeting/muteState.js +5 -2
  50. package/test/unit/spec/meeting/request.js +215 -42
  51. package/test/unit/spec/meeting/utils.js +151 -7
  52. package/test/unit/spec/members/index.js +22 -1
  53. package/test/unit/spec/members/request.js +167 -35
@@ -494,6 +494,21 @@ describe('plugin-meetings', () => {
494
494
  });
495
495
  });
496
496
 
497
+ describe('#_setManageGroups', () => {
498
+ it('do nothing if breakout info is empty', () => {
499
+ breakouts._setManageGroups();
500
+ assert.equal(breakouts.manageGroups, undefined);
501
+ breakouts._setManageGroups({body: null});
502
+ assert.equal(breakouts.manageGroups, undefined);
503
+ breakouts._setManageGroups({body: {groups: null}});
504
+ assert.equal(breakouts.manageGroups, undefined);
505
+ });
506
+ it('set the groups into manageGroups if has groups in side breakout info', () => {
507
+ breakouts._setManageGroups({body: {groups: [{id: 'groupId1'}]}});
508
+ assert.deepEqual(breakouts.manageGroups, [{id: 'groupId1'}]);
509
+ });
510
+ });
511
+
497
512
  describe('#queryRosters', () => {
498
513
  it('makes the expected query', async () => {
499
514
  webex.request.returns(
@@ -724,6 +739,12 @@ describe('plugin-meetings', () => {
724
739
 
725
740
  describe('#update', () => {
726
741
  it('makes the request as expected', async () => {
742
+ const mockedReturnBody = getBOResponse('OPEN');
743
+ webex.request.returns(
744
+ Promise.resolve({
745
+ body: mockedReturnBody,
746
+ })
747
+ );
727
748
  breakouts.editLock = {
728
749
  token: 'token1',
729
750
  };
@@ -731,6 +752,7 @@ describe('plugin-meetings', () => {
731
752
  id: 'groupId',
732
753
  sessions: [{name: 'Session 1'}],
733
754
  };
755
+ breakouts._setManageGroups = sinon.stub();
734
756
  const result = await breakouts.update(params);
735
757
  assert.calledOnceWithExactly(webex.request, {
736
758
  method: 'PUT',
@@ -740,6 +762,9 @@ describe('plugin-meetings', () => {
740
762
  groups: [params],
741
763
  },
742
764
  });
765
+ assert.calledOnceWithExactly(breakouts._setManageGroups, {
766
+ body: mockedReturnBody,
767
+ });
743
768
  });
744
769
  it('makes the request as expected when unlockEdit is true', async () => {
745
770
  breakouts.editLock = {
@@ -844,9 +869,10 @@ describe('plugin-meetings', () => {
844
869
 
845
870
  describe('#start', () => {
846
871
  it('should start breakout sessions', async () => {
872
+ const mockedReturnBody = getBOResponse('OPEN');
847
873
  webex.request.returns(
848
874
  Promise.resolve({
849
- body: getBOResponse('OPEN'),
875
+ body: mockedReturnBody,
850
876
  })
851
877
  );
852
878
 
@@ -854,6 +880,7 @@ describe('plugin-meetings', () => {
854
880
  await breakouts.getBreakout();
855
881
 
856
882
  const result = await breakouts.start();
883
+ breakouts._setManageGroups = sinon.stub();
857
884
  await breakouts.start({id: 'id', someOtherParam: 'someOtherParam'});
858
885
 
859
886
  const arg = webex.request.getCall(1).args[0];
@@ -877,7 +904,8 @@ describe('plugin-meetings', () => {
877
904
  someOtherParam: 'someOtherParam',
878
905
  duration: BREAKOUTS.DEFAULT_DURATION,
879
906
  });
880
- assert.deepEqual(result, {body: getBOResponse('OPEN')});
907
+ assert.deepEqual(result, {body: mockedReturnBody});
908
+ assert.calledWithExactly(breakouts._setManageGroups, {body: mockedReturnBody})
881
909
  });
882
910
 
883
911
  it('rejects when edit lock token mismatch', async () => {
@@ -911,6 +939,8 @@ describe('plugin-meetings', () => {
911
939
  await breakouts.getBreakout();
912
940
 
913
941
  const result = await breakouts.end();
942
+
943
+ breakouts._setManageGroups = sinon.stub();
914
944
  await breakouts.end({id: 'id', someOtherParam: 'someOtherParam'});
915
945
  const arg = webex.request.getCall(1).args[0];
916
946
  const argObj1 = arg.body.groups[0];
@@ -926,6 +956,7 @@ describe('plugin-meetings', () => {
926
956
  someOtherParam: 'someOtherParam',
927
957
  });
928
958
  assert.deepEqual(result, {body: getBOResponse('CLOSING')});
959
+ assert.calledOnceWithExactly(breakouts._setManageGroups, {body: getBOResponse('CLOSING')});
929
960
  });
930
961
 
931
962
  it('rejects when edit lock token mismatch', async () => {
@@ -956,6 +987,8 @@ describe('plugin-meetings', () => {
956
987
 
957
988
  breakouts.set('url', 'url');
958
989
  const result = await breakouts.getBreakout();
990
+
991
+ breakouts._setManageGroups = sinon.stub();
959
992
  await breakouts.getBreakout(true);
960
993
  const arg1 = webex.request.getCall(0).args[0];
961
994
  const arg2 = webex.request.getCall(1).args[0];
@@ -966,6 +999,7 @@ describe('plugin-meetings', () => {
966
999
  assert.deepEqual(result, {body: getBOResponse('PENDING')});
967
1000
  assert.deepEqual(breakouts.manageGroups, result.body.groups);
968
1001
  assert.equal(breakouts.breakoutGroupId, 'groupId');
1002
+ assert.calledOnceWithExactly(breakouts._setManageGroups, {body: getBOResponse('PENDING')});
969
1003
  });
970
1004
 
971
1005
  it('breakoutGroupId should be empty if it is CLOSED group', async () => {
@@ -1050,6 +1084,7 @@ describe('plugin-meetings', () => {
1050
1084
  })
1051
1085
  );
1052
1086
 
1087
+ breakouts._setManageGroups = sinon.stub();
1053
1088
  const result = await breakouts.clearSessions();
1054
1089
  assert.calledOnceWithExactly(webex.request, {
1055
1090
  method: 'PUT',
@@ -1063,7 +1098,17 @@ describe('plugin-meetings', () => {
1063
1098
  },
1064
1099
  });
1065
1100
 
1066
- assert.equal(breakouts.manageGroups[0].status, 'CLOSE');
1101
+ assert.calledOnceWithExactly(breakouts._setManageGroups, {
1102
+ body: {
1103
+ groups: [
1104
+ {
1105
+ id: '455556a4-37cd-4baa-89bc-8730581a1cc0',
1106
+ status: 'CLOSE',
1107
+ type: 'BREAKOUT',
1108
+ },
1109
+ ],
1110
+ },
1111
+ });
1067
1112
  });
1068
1113
 
1069
1114
  it('rejects when edit lock token mismatch', async () => {
@@ -40,8 +40,10 @@ import EventsScope from '@webex/plugin-meetings/src/common/events/events-scope';
40
40
  import Meetings, {CONSTANTS} from '@webex/plugin-meetings';
41
41
  import Meeting from '@webex/plugin-meetings/src/meeting';
42
42
  import Members from '@webex/plugin-meetings/src/members';
43
+ import * as MembersImport from '@webex/plugin-meetings/src/members';
43
44
  import Roap from '@webex/plugin-meetings/src/roap';
44
45
  import MeetingRequest from '@webex/plugin-meetings/src/meeting/request';
46
+ import * as MeetingRequestImport from '@webex/plugin-meetings/src/meeting/request';
45
47
  import LocusInfo from '@webex/plugin-meetings/src/locus-info';
46
48
  import MediaProperties from '@webex/plugin-meetings/src/media/properties';
47
49
  import MeetingUtil from '@webex/plugin-meetings/src/meeting/util';
@@ -169,6 +171,8 @@ describe('plugin-meetings', () => {
169
171
  let test3;
170
172
  let test4;
171
173
  let testDestination;
174
+ let membersSpy;
175
+ let meetingRequestSpy;
172
176
 
173
177
  beforeEach(() => {
174
178
  webex = new MockWebex({
@@ -203,6 +207,8 @@ describe('plugin-meetings', () => {
203
207
  webex.internal.metrics.submitClientMetrics = sinon.stub().returns(Promise.resolve());
204
208
  webex.meetings.uploadLogs = sinon.stub().returns(Promise.resolve());
205
209
  webex.internal.llm.on = sinon.stub();
210
+ membersSpy = sinon.spy(MembersImport, 'default');
211
+ meetingRequestSpy = sinon.spy(MeetingRequestImport, 'default');
206
212
 
207
213
  TriggerProxy.trigger = sinon.stub().returns(true);
208
214
  Metrics.postEvent = sinon.stub();
@@ -256,6 +262,16 @@ describe('plugin-meetings', () => {
256
262
  assert.equal(meeting.deviceUrl, uuid3);
257
263
  assert.deepEqual(meeting.meetingInfo, {});
258
264
  assert.instanceOf(meeting.members, Members);
265
+ assert.calledOnceWithExactly(
266
+ membersSpy,
267
+ {
268
+ locusUrl: meeting.locusUrl,
269
+ receiveSlotManager: meeting.receiveSlotManager,
270
+ mediaRequestManagers: meeting.mediaRequestManagers,
271
+ meeting,
272
+ },
273
+ {parent: meeting.webex}
274
+ );
259
275
  assert.instanceOf(meeting.roap, Roap);
260
276
  assert.instanceOf(meeting.reconnectionManager, ReconnectionManager);
261
277
  assert.isNull(meeting.audio);
@@ -270,6 +286,13 @@ describe('plugin-meetings', () => {
270
286
  assert.isNull(meeting.hostId);
271
287
  assert.isNull(meeting.policy);
272
288
  assert.instanceOf(meeting.meetingRequest, MeetingRequest);
289
+ assert.calledOnceWithExactly(
290
+ meetingRequestSpy,
291
+ {
292
+ meeting,
293
+ },
294
+ {parent: meeting.webex}
295
+ );
273
296
  assert.instanceOf(meeting.locusInfo, LocusInfo);
274
297
  assert.equal(meeting.fetchMeetingInfoTimeoutId, undefined);
275
298
  assert.instanceOf(meeting.mediaProperties, MediaProperties);
@@ -2963,7 +2986,6 @@ describe('plugin-meetings', () => {
2963
2986
  meeting.meetingRequest.dialOut = sinon
2964
2987
  .stub()
2965
2988
  .returns(Promise.resolve({body: {locus: 'testData'}}));
2966
- meeting.locusInfo.onFullLocus = sinon.stub().returns(Promise.resolve());
2967
2989
  });
2968
2990
 
2969
2991
  it('with no parameters triggers dial-in, delegating request to meetingRequest correctly', async () => {
@@ -2976,11 +2998,9 @@ describe('plugin-meetings', () => {
2976
2998
  locusUrl: meeting.locusUrl,
2977
2999
  clientUrl: meeting.deviceUrl,
2978
3000
  });
2979
- assert.calledWith(meeting.locusInfo.onFullLocus, 'testData');
2980
3001
  assert.notCalled(meeting.meetingRequest.dialOut);
2981
3002
 
2982
3003
  meeting.meetingRequest.dialIn.resetHistory();
2983
- meeting.locusInfo.onFullLocus.resetHistory();
2984
3004
 
2985
3005
  // try again. the dial in urls should match
2986
3006
  await meeting.usePhoneAudio();
@@ -2991,7 +3011,6 @@ describe('plugin-meetings', () => {
2991
3011
  locusUrl: meeting.locusUrl,
2992
3012
  clientUrl: meeting.deviceUrl,
2993
3013
  });
2994
- assert.calledWith(meeting.locusInfo.onFullLocus, 'testData');
2995
3014
  assert.notCalled(meeting.meetingRequest.dialOut);
2996
3015
  });
2997
3016
 
@@ -3008,11 +3027,9 @@ describe('plugin-meetings', () => {
3008
3027
  clientUrl: meeting.deviceUrl,
3009
3028
  phoneNumber,
3010
3029
  });
3011
- assert.calledWith(meeting.locusInfo.onFullLocus, 'testData');
3012
3030
  assert.notCalled(meeting.meetingRequest.dialIn);
3013
3031
 
3014
3032
  meeting.meetingRequest.dialOut.resetHistory();
3015
- meeting.locusInfo.onFullLocus.resetHistory();
3016
3033
 
3017
3034
  // try again. the dial out urls should match
3018
3035
  await meeting.usePhoneAudio(phoneNumber);
@@ -3024,7 +3041,6 @@ describe('plugin-meetings', () => {
3024
3041
  clientUrl: meeting.deviceUrl,
3025
3042
  phoneNumber,
3026
3043
  });
3027
- assert.calledWith(meeting.locusInfo.onFullLocus, 'testData');
3028
3044
  assert.notCalled(meeting.meetingRequest.dialIn);
3029
3045
  });
3030
3046
 
@@ -5376,28 +5392,7 @@ describe('plugin-meetings', () => {
5376
5392
  checkParseMeetingInfo(expectedInfoToParse);
5377
5393
  });
5378
5394
  });
5379
- describe('#parseLocus', () => {
5380
- describe('when CALL and participants', () => {
5381
- beforeEach(() => {
5382
- meeting.setLocus = sinon.stub().returns(true);
5383
- MeetingUtil.getLocusPartner = sinon.stub().returns({person: {sipUrl: uuid3}});
5384
- });
5385
- it('should parse the locus object and set meeting properties and return null', () => {
5386
- meeting.type = 'CALL';
5387
- meeting.parseLocus({url: url1, participants: [{id: uuid1}], self: {id: uuid2}});
5388
- assert.calledOnce(meeting.setLocus);
5389
- assert.calledWith(meeting.setLocus, {
5390
- url: url1,
5391
- participants: [{id: uuid1}],
5392
- self: {id: uuid2},
5393
- });
5394
- assert.calledOnce(MeetingUtil.getLocusPartner);
5395
- assert.calledWith(MeetingUtil.getLocusPartner, [{id: uuid1}], {id: uuid2});
5396
- assert.deepEqual(meeting.partner, {person: {sipUrl: uuid3}});
5397
- assert.equal(meeting.sipUri, uuid3);
5398
- });
5399
- });
5400
- });
5395
+
5401
5396
  describe('#setCorrelationId', () => {
5402
5397
  it('should set the correlationId and return undefined', () => {
5403
5398
  assert.ok(meeting.correlationId);
@@ -5748,8 +5743,9 @@ describe('plugin-meetings', () => {
5748
5743
  beforeEach(() => {
5749
5744
  meeting.locusInfo.initialSetup = sinon.stub().returns(true);
5750
5745
  });
5746
+
5751
5747
  it('should read the locus object, set on the meeting and return null', () => {
5752
- meeting.parseLocus({
5748
+ meeting.setLocus({
5753
5749
  mediaConnections: [test1],
5754
5750
  locusUrl: url1,
5755
5751
  locusId: uuid1,
@@ -5774,6 +5770,7 @@ describe('plugin-meetings', () => {
5774
5770
  assert.equal(meeting.hostId, uuid4);
5775
5771
  });
5776
5772
  });
5773
+
5777
5774
  describe('preferred video device', () => {
5778
5775
  describe('#getVideoDeviceId', () => {
5779
5776
  it('returns the preferred video device', () => {
@@ -5863,7 +5860,7 @@ describe('plugin-meetings', () => {
5863
5860
  'https://board-a.wbx2.com/board/api/v1/channels/977a7330-54f4-11eb-b1ef-91f5eefc7bf3',
5864
5861
  };
5865
5862
 
5866
- const generateContent = (beneficiaryId = null, disposition = null) => ({
5863
+ const generateContent = (beneficiaryId = null, disposition = null,annotation = undefined) => ({
5867
5864
  beneficiaryId,
5868
5865
  disposition,
5869
5866
  });
@@ -5880,7 +5877,8 @@ describe('plugin-meetings', () => {
5880
5877
  beneficiaryId,
5881
5878
  resourceUrl,
5882
5879
  isAccepting,
5883
- otherBeneficiaryId
5880
+ otherBeneficiaryId,
5881
+ annotation,
5884
5882
  ) => {
5885
5883
  const newPayload = cloneDeep(payload);
5886
5884
 
@@ -5906,7 +5904,7 @@ describe('plugin-meetings', () => {
5906
5904
  if (isGranting) {
5907
5905
  if (isContent) {
5908
5906
  activeSharingId.content = beneficiaryId;
5909
- newPayload.current.content = generateContent(beneficiaryId, FLOOR_ACTION.GRANTED);
5907
+ newPayload.current.content = generateContent(beneficiaryId, FLOOR_ACTION.GRANTED,annotation);
5910
5908
 
5911
5909
  if (isEqual(newPayload.current, newPayload.previous)) {
5912
5910
  eventTrigger.member = null;
@@ -6533,6 +6531,28 @@ describe('plugin-meetings', () => {
6533
6531
  });
6534
6532
  });
6535
6533
 
6534
+ describe('annotation policy', () => {
6535
+
6536
+ it('Scenario #1: blank annotation', () => {
6537
+ const data1 = generateData(blankPayload, true, true, USER_IDS.ME);
6538
+ const data2 = generateData(data1.payload, false, true, USER_IDS.ME);
6539
+ const data3 = generateData(data2.payload, true, true, USER_IDS.ME);
6540
+ const data4 = generateData(data3.payload, false, true, USER_IDS.ME);
6541
+
6542
+ payloadTestHelper([data1, data2, data3, data4]);
6543
+ });
6544
+
6545
+ it('Scenario #2: annotation', () => {
6546
+ const annotationInfo = {version: '1', policy: 'Approval'};
6547
+ const data1 = generateData(blankPayload, true, true, USER_IDS.ME, annotationInfo);
6548
+ const data2 = generateData(data1.payload, false, true, USER_IDS.ME);
6549
+ const data3 = generateData(data2.payload, true, true, USER_IDS.ME);
6550
+ const data4 = generateData(data3.payload, false, true, USER_IDS.ME);
6551
+
6552
+ payloadTestHelper([data1, data2, data3, data4]);
6553
+ });
6554
+ });
6555
+
6536
6556
  describe('Desktop A --> Desktop B', () => {
6537
6557
  it('Scenario #1: you share desktop A and then share desktop B', () => {
6538
6558
  const data1 = generateData(blankPayload, true, true, USER_IDS.ME);
@@ -67,8 +67,8 @@ describe('LocusMediaRequest.send()', () => {
67
67
  muteOptions: {},
68
68
  };
69
69
 
70
- const createExpectedLocalMuteBody = (expectedMute:{audioMuted: boolean, videoMuted: boolean}) => {
71
- return {
70
+ const createExpectedLocalMuteBody = (expectedMute:{audioMuted: boolean, videoMuted: boolean}, sequence = undefined) => {
71
+ const body: any = {
72
72
  device: {
73
73
  url: 'deviceUrl',
74
74
  deviceType: 'deviceType',
@@ -85,7 +85,13 @@ describe('LocusMediaRequest.send()', () => {
85
85
  clientMediaPreferences: {
86
86
  preferTranscoding: true,
87
87
  },
88
+ };
89
+
90
+ if (sequence) {
91
+ body.sequence = sequence;
88
92
  }
93
+
94
+ return body;
89
95
  };
90
96
 
91
97
  beforeEach(() => {
@@ -108,7 +114,7 @@ describe('LocusMediaRequest.send()', () => {
108
114
  webexRequestStub = sinon.stub(locusMediaRequest, 'request').resolves(fakeLocusResponse);
109
115
  })
110
116
 
111
- const sendLocalMute = (muteOptions) => locusMediaRequest.send({...exampleLocalMuteRequestBody, muteOptions});
117
+ const sendLocalMute = (muteOptions, overrides={}) => locusMediaRequest.send({...exampleLocalMuteRequestBody, ...overrides, muteOptions});
112
118
 
113
119
  const sendRoapMessage = (messageType) => {
114
120
  const request = cloneDeep(exampleRoapRequestBody);
@@ -150,6 +156,22 @@ describe('LocusMediaRequest.send()', () => {
150
156
  });
151
157
  });
152
158
 
159
+ it('sends a local mute request with sequence', async () => {
160
+ await ensureConfluenceCreated();
161
+
162
+ const sequence = {some: 'sequence data'};
163
+
164
+ const result = await sendLocalMute({audioMuted: false, videoMuted: false}, {sequence});
165
+
166
+ assert.equal(result, fakeLocusResponse);
167
+
168
+ assert.calledOnceWithExactly(webexRequestStub, {
169
+ method: 'PUT',
170
+ uri: 'fakeMeetingSelfUrl/media',
171
+ body: createExpectedLocalMuteBody({audioMuted: false, videoMuted: false}, sequence),
172
+ });
173
+ });
174
+
153
175
  it('sends a local mute request with the last audio/video mute values when called multiple times in same processing cycle', async () => {
154
176
  await ensureConfluenceCreated();
155
177
 
@@ -36,7 +36,7 @@ describe('plugin-meetings', () => {
36
36
  unmuteVideoAllowed: true,
37
37
 
38
38
  locusInfo: {
39
- onFullLocus: sinon.stub(),
39
+ onDeltaLocus: sinon.stub(),
40
40
  },
41
41
  members: {
42
42
  selfId: 'fake self id',
@@ -351,11 +351,14 @@ describe('plugin-meetings', () => {
351
351
  await testUtils.waitUntil(200);
352
352
  assert.isFalse(clientPromiseResolved);
353
353
 
354
+ meeting.locusInfo.onDeltaLocus.resetHistory();
355
+
354
356
  // now allow the server response to arrive, this should trigger the client promise to get resolved
355
- serverResponseResolve();
357
+ serverResponseResolve(fakeLocus);
356
358
  await testUtils.flushPromises();
357
359
 
358
360
  assert.isTrue(clientPromiseResolved);
361
+ assert.calledOnceWithExactly(meeting.locusInfo.onDeltaLocus, fakeLocus);
359
362
  });
360
363
 
361
364
  it('rejects client request promise if server request for local mute fails', async () => {