@webex/plugin-meetings 3.0.0-beta.25 → 3.0.0-beta.27

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 (46) hide show
  1. package/dist/breakouts/breakout.js +1 -1
  2. package/dist/breakouts/index.js +92 -2
  3. package/dist/breakouts/index.js.map +1 -1
  4. package/dist/constants.js +5 -1
  5. package/dist/constants.js.map +1 -1
  6. package/dist/controls-options-manager/constants.js +14 -0
  7. package/dist/controls-options-manager/constants.js.map +1 -0
  8. package/dist/controls-options-manager/enums.js +15 -0
  9. package/dist/controls-options-manager/enums.js.map +1 -0
  10. package/dist/controls-options-manager/index.js +203 -0
  11. package/dist/controls-options-manager/index.js.map +1 -0
  12. package/dist/controls-options-manager/util.js +28 -0
  13. package/dist/controls-options-manager/util.js.map +1 -0
  14. package/dist/meeting/in-meeting-actions.js +8 -0
  15. package/dist/meeting/in-meeting-actions.js.map +1 -1
  16. package/dist/meeting/index.js +61 -13
  17. package/dist/meeting/index.js.map +1 -1
  18. package/dist/meetings/util.js +17 -0
  19. package/dist/meetings/util.js.map +1 -1
  20. package/dist/types/constants.d.ts +4 -0
  21. package/dist/types/controls-options-manager/constants.d.ts +4 -0
  22. package/dist/types/controls-options-manager/enums.d.ts +5 -0
  23. package/dist/types/controls-options-manager/index.d.ts +120 -0
  24. package/dist/types/controls-options-manager/util.d.ts +7 -0
  25. package/dist/types/meeting/in-meeting-actions.d.ts +8 -0
  26. package/dist/types/meeting/index.d.ts +18 -0
  27. package/package.json +18 -18
  28. package/src/breakouts/index.ts +73 -1
  29. package/src/constants.ts +4 -0
  30. package/src/controls-options-manager/constants.ts +5 -0
  31. package/src/controls-options-manager/enums.ts +6 -0
  32. package/src/controls-options-manager/index.ts +183 -0
  33. package/src/controls-options-manager/util.ts +20 -0
  34. package/src/meeting/in-meeting-actions.ts +16 -0
  35. package/src/meeting/index.ts +50 -0
  36. package/src/meetings/util.ts +24 -0
  37. package/test/integration/spec/converged-space-meetings.js +176 -0
  38. package/test/unit/spec/breakouts/index.ts +76 -0
  39. package/test/unit/spec/controls-options-manager/index.js +124 -0
  40. package/test/unit/spec/controls-options-manager/util.js +66 -0
  41. package/test/unit/spec/meeting/in-meeting-actions.ts +8 -0
  42. package/test/unit/spec/meeting/index.js +15 -0
  43. package/test/utils/constants.js +9 -0
  44. package/test/utils/testUtils.js +13 -6
  45. package/test/utils/webex-config.js +4 -0
  46. package/test/utils/webex-test-users.js +6 -3
@@ -0,0 +1,20 @@
1
+ import {DISPLAY_HINTS} from '../constants';
2
+
3
+ const canSetMuteOnEntry = (displayHints: Array<string>): boolean =>
4
+ displayHints.includes(DISPLAY_HINTS.ENABLE_MUTE_ON_ENTRY);
5
+
6
+ const canSetDisallowUnmute = (displayHints: Array<string>): boolean =>
7
+ displayHints.includes(DISPLAY_HINTS.ENABLE_HARD_MUTE);
8
+
9
+ const canUnsetMuteOnEntry = (displayHints: Array<string>): boolean =>
10
+ displayHints.includes(DISPLAY_HINTS.DISABLE_MUTE_ON_ENTRY);
11
+
12
+ const canUnsetDisallowUnmute = (displayHints: Array<string>): boolean =>
13
+ displayHints.includes(DISPLAY_HINTS.DISABLE_HARD_MUTE);
14
+
15
+ export default {
16
+ canSetMuteOnEntry,
17
+ canSetDisallowUnmute,
18
+ canUnsetMuteOnEntry,
19
+ canUnsetDisallowUnmute,
20
+ };
@@ -13,6 +13,10 @@ interface IInMeetingActions {
13
13
  canAdmitParticipant?: boolean;
14
14
  canLock?: boolean;
15
15
  canUnlock?: boolean;
16
+ canSetMuteOnEntry?: boolean;
17
+ canUnsetMuteOnEntry?: boolean;
18
+ canSetDisallowUnmute?: boolean;
19
+ canUnsetDisallowUnmute?: boolean;
16
20
  canAssignHost?: boolean;
17
21
  canStartRecording?: boolean;
18
22
  canPauseRecording?: boolean;
@@ -59,6 +63,14 @@ export default class InMeetingActions implements IInMeetingActions {
59
63
 
60
64
  canStopRecording = null;
61
65
 
66
+ canSetMuteOnEntry = null;
67
+
68
+ canUnsetMuteOnEntry = null;
69
+
70
+ canSetDisallowUnmute = null;
71
+
72
+ canUnsetDisallowUnmute = null;
73
+
62
74
  canRaiseHand = null;
63
75
 
64
76
  canLowerAllHands = null;
@@ -99,6 +111,10 @@ export default class InMeetingActions implements IInMeetingActions {
99
111
  canLock: this.canLock,
100
112
  canUnlock: this.canUnlock,
101
113
  canAssignHost: this.canAssignHost,
114
+ canSetMuteOnEntry: this.canSetMuteOnEntry,
115
+ canUnsetMuteOnEntry: this.canUnsetMuteOnEntry,
116
+ canSetDisallowUnmute: this.canSetDisallowUnmute,
117
+ canUnsetDisallowUnmute: this.canUnsetDisallowUnmute,
102
118
  canStartRecording: this.canStartRecording,
103
119
  canPauseRecording: this.canPauseRecording,
104
120
  canResumeRecording: this.canResumeRecording,
@@ -37,6 +37,7 @@ import MeetingRequest from './request';
37
37
  import Members from '../members/index';
38
38
  import MeetingUtil from './util';
39
39
  import RecordingUtil from '../recording-controller/util';
40
+ import ControlsOptionsUtil from '../controls-options-manager/util';
40
41
  import MediaUtil from '../media/util';
41
42
  import Transcription from '../transcription';
42
43
  import {Reactions, SkinTones} from '../reactions/reactions';
@@ -106,6 +107,7 @@ import Breakouts from '../breakouts';
106
107
  import InMeetingActions from './in-meeting-actions';
107
108
  import {REACTION_RELAY_TYPES} from '../reactions/constants';
108
109
  import RecordingController from '../recording-controller';
110
+ import ControlsOptionsManager from '../controls-options-manager';
109
111
 
110
112
  const {isBrowser} = BrowserDetection();
111
113
 
@@ -485,6 +487,7 @@ export default class Meeting extends StatelessWebexPlugin {
485
487
  recording: any;
486
488
  remoteMediaManager: RemoteMediaManager | null;
487
489
  recordingController: RecordingController;
490
+ controlsOptionsManager: ControlsOptionsManager;
488
491
  requiredCaptcha: any;
489
492
  receiveSlotManager: ReceiveSlotManager;
490
493
  shareStatus: string;
@@ -1098,6 +1101,18 @@ export default class Meeting extends StatelessWebexPlugin {
1098
1101
  displayHints: [],
1099
1102
  });
1100
1103
 
1104
+ /**
1105
+ * The class that helps to control recording functions: start, stop, pause, resume, etc
1106
+ * @instance
1107
+ * @type {ControlsOptionsManager}
1108
+ * @public
1109
+ * @memberof Meeting
1110
+ */
1111
+ this.controlsOptionsManager = new ControlsOptionsManager(this.meetingRequest, {
1112
+ locusUrl: this.locusInfo?.url,
1113
+ displayHints: [],
1114
+ });
1115
+
1101
1116
  this.setUpLocusInfoListeners();
1102
1117
  this.locusInfo.init(attrs.locus ? attrs.locus : {});
1103
1118
  this.hasJoinedOnce = false;
@@ -2187,6 +2202,7 @@ export default class Meeting extends StatelessWebexPlugin {
2187
2202
  this.locusUrl = payload;
2188
2203
  this.locusId = this.locusUrl?.split('/').pop();
2189
2204
  this.recordingController.setLocusUrl(this.locusUrl);
2205
+ this.controlsOptionsManager.setLocusUrl(this.locusUrl);
2190
2206
  });
2191
2207
  }
2192
2208
 
@@ -2203,6 +2219,7 @@ export default class Meeting extends StatelessWebexPlugin {
2203
2219
  this.locusInfo.on(LOCUSINFO.EVENTS.LINKS_SERVICES, (payload) => {
2204
2220
  this.recordingController.setServiceUrl(payload?.services?.record?.url);
2205
2221
  this.recordingController.setSessionId(this.locusInfo?.fullState?.sessionId);
2222
+ this.breakouts.breakoutServiceUrlUpdate(payload?.services?.breakout?.url);
2206
2223
  });
2207
2224
  }
2208
2225
 
@@ -2252,6 +2269,16 @@ export default class Meeting extends StatelessWebexPlugin {
2252
2269
  canAdmitParticipant: MeetingUtil.canAdmitParticipant(payload.info.userDisplayHints),
2253
2270
  canLock: MeetingUtil.canUserLock(payload.info.userDisplayHints),
2254
2271
  canUnlock: MeetingUtil.canUserUnlock(payload.info.userDisplayHints),
2272
+ canSetDisallowUnmute: ControlsOptionsUtil.canSetDisallowUnmute(
2273
+ payload.info.userDisplayHints
2274
+ ),
2275
+ canUnsetDisallowUnmute: ControlsOptionsUtil.canUnsetDisallowUnmute(
2276
+ payload.info.userDisplayHints
2277
+ ),
2278
+ canSetMuteOnEntry: ControlsOptionsUtil.canSetMuteOnEntry(payload.info.userDisplayHints),
2279
+ canUnsetMuteOnEntry: ControlsOptionsUtil.canUnsetMuteOnEntry(
2280
+ payload.info.userDisplayHints
2281
+ ),
2255
2282
  canStartRecording: RecordingUtil.canUserStart(payload.info.userDisplayHints),
2256
2283
  canStopRecording: RecordingUtil.canUserStop(payload.info.userDisplayHints),
2257
2284
  canPauseRecording: RecordingUtil.canUserPause(payload.info.userDisplayHints),
@@ -2288,6 +2315,7 @@ export default class Meeting extends StatelessWebexPlugin {
2288
2315
  });
2289
2316
 
2290
2317
  this.recordingController.setDisplayHints(payload.info.userDisplayHints);
2318
+ this.controlsOptionsManager.setDisplayHints(payload.info.userDisplayHints);
2291
2319
 
2292
2320
  if (changed) {
2293
2321
  Trigger.trigger(
@@ -6131,6 +6159,28 @@ export default class Meeting extends StatelessWebexPlugin {
6131
6159
  return this.recordingController.startRecording();
6132
6160
  }
6133
6161
 
6162
+ /**
6163
+ * set the mute on entry flag for participants if you're the host
6164
+ * @returns {Promise}
6165
+ * @param {boolean} enabled
6166
+ * @public
6167
+ * @memberof Meeting
6168
+ */
6169
+ public setMuteOnEntry(enabled: boolean) {
6170
+ return this.controlsOptionsManager.setMuteOnEntry(enabled);
6171
+ }
6172
+
6173
+ /**
6174
+ * set the disallow unmute flag for participants if you're the host
6175
+ * @returns {Promise}
6176
+ * @param {boolean} enabled
6177
+ * @public
6178
+ * @memberof Meeting
6179
+ */
6180
+ public setDisallowUnmute(enabled: boolean) {
6181
+ return this.controlsOptionsManager.setDisallowUnmute(enabled);
6182
+ }
6183
+
6134
6184
  /**
6135
6185
  * End the recording of this meeting
6136
6186
  * @returns {Promise}
@@ -58,12 +58,36 @@ MeetingsUtil.handleRoapMercury = (envelope, meetingCollection) => {
58
58
  errorCause,
59
59
  };
60
60
 
61
+ const mediaServer = MeetingsUtil.getMediaServer(roapMessage.sdp);
62
+
61
63
  meeting.mediaProperties.webrtcMediaConnection.roapMessageReceived(roapMessage);
64
+
65
+ if (mediaServer) {
66
+ meeting.mediaProperties.webrtcMediaConnection.mediaServer = mediaServer;
67
+ }
62
68
  }
63
69
  }
64
70
  }
65
71
  };
66
72
 
73
+ MeetingsUtil.getMediaServer = (sdp) => {
74
+ let mediaServer;
75
+
76
+ // Attempt to collect the media server from the roap message.
77
+ try {
78
+ mediaServer = sdp
79
+ .split('\r\n')
80
+ .find((line) => line.startsWith('o='))
81
+ .split(' ')
82
+ .shift()
83
+ .replace('o=', '');
84
+ } catch {
85
+ mediaServer = undefined;
86
+ }
87
+
88
+ return mediaServer;
89
+ };
90
+
67
91
  MeetingsUtil.checkForCorrelationId = (deviceUrl, locus) => {
68
92
  let devices = [];
69
93
 
@@ -0,0 +1,176 @@
1
+ import { config } from 'dotenv';
2
+ import 'jsdom-global/register';
3
+ import {assert} from '@webex/test-helper-chai';
4
+ import {skipInNode} from '@webex/test-helper-mocha';
5
+ import BrowserDetection from '@webex/plugin-meetings/dist/common/browser-detection';
6
+
7
+ import {MEDIA_SERVERS} from '../../utils/constants';
8
+ import testUtils from '../../utils/testUtils';
9
+ import webexTestUsers from '../../utils/webex-test-users';
10
+
11
+ config();
12
+
13
+ skipInNode(describe)('plugin-meetings', () => {
14
+ const {isBrowser} = BrowserDetection();
15
+
16
+ // `addMedia()` fails on FF, this needs to be debuged and fixed in a later change
17
+ if (!isBrowser('firefox')) {
18
+ describe('converged-space-meeting', () => {
19
+ let shouldSkip = false;
20
+ let users, alice, bob, chris;
21
+ let meeting = null;
22
+ let space = null;
23
+ let mediaReadyListener = null;
24
+
25
+ before('setup users', async () => {
26
+ const userSet = await webexTestUsers.generateTestUsers({
27
+ count: 3,
28
+ whistler: process.env.WHISTLER || process.env.JENKINS,
29
+ config
30
+ });
31
+
32
+ users = userSet;
33
+ alice = users[0];
34
+ bob = users[1];
35
+ chris = users[2];
36
+ alice.name = 'alice';
37
+ bob.name = 'bob';
38
+ chris.name = 'chris';
39
+
40
+ const aliceSync = testUtils.syncAndEndMeeting(alice);
41
+ const bobSync = testUtils.syncAndEndMeeting(bob);
42
+ const chrisSync = testUtils.syncAndEndMeeting(chris);
43
+
44
+ await aliceSync;
45
+ await bobSync;
46
+ await chrisSync;
47
+ });
48
+
49
+ // Skip a test in this series if one failed.
50
+ // This beforeEach() instance function must use the `function` declaration to preserve the
51
+ // `this` context. `() => {}` will not generate the correct `this` context
52
+ beforeEach('check if should skip test', function() {
53
+ if (shouldSkip) {
54
+ this.skip();
55
+ }
56
+ });
57
+
58
+ // Store to the describe scope if a test has failed for skipping.
59
+ // This beforeEach() instance function must use the `function` declaration to preserve the
60
+ // `this` context. `() => {}` will not generate the correct `this` context
61
+ afterEach('check if test failed', function() {
62
+ if (this.currentTest.state === 'failed') {
63
+ shouldSkip = true;
64
+ }
65
+ });
66
+
67
+ it('user "alice" starts a space', async () => {
68
+ const conversation = await alice.webex.internal.conversation.create({
69
+ participants: [bob, chris],
70
+ });
71
+
72
+ assert.lengthOf(conversation.participants.items, 3);
73
+ assert.lengthOf(conversation.activities.items, 1);
74
+
75
+ space = conversation;
76
+
77
+ const destinationWithType = await alice.webex.meetings.meetingInfo.fetchMeetingInfo(space.url, 'CONVERSATION_URL');
78
+ const destinationWithoutType = await alice.webex.meetings.meetingInfo.fetchMeetingInfo(space.url);
79
+
80
+ assert.exists(destinationWithoutType);
81
+ assert.exists(destinationWithType);
82
+ assert.exists(destinationWithoutType.body.meetingNumber);
83
+ assert.exists(destinationWithType.body.meetingNumber);
84
+ });
85
+
86
+ it('user "alice" starts a meeting', async () => {
87
+ const wait = testUtils.waitForEvents([{
88
+ scope: alice.webex.meetings,
89
+ event: 'meeting:added',
90
+ user: alice,
91
+ }]);
92
+
93
+ const createdMeeting = await testUtils.delayedPromise(alice.webex.meetings.create(space.url));
94
+
95
+ await wait;
96
+
97
+ assert.exists(createdMeeting);
98
+
99
+ meeting = createdMeeting;
100
+ });
101
+
102
+ it('user "alice" joins the meeting', async () => {
103
+ const wait = testUtils.waitForEvents([
104
+ {scope: bob.webex.meetings, event: 'meeting:added', user: bob},
105
+ {scope: chris.webex.meetings, event: 'meeting:added', user: chris},
106
+ ]);
107
+
108
+ await testUtils.delayedPromise(alice.meeting.join({enableMultistream: true}));
109
+
110
+ await wait;
111
+
112
+ assert.isTrue(!!alice.webex.meetings.meetingCollection.meetings[meeting.id].joinedWith);
113
+ });
114
+
115
+ it('users "bob" and "chris" join the meeting', async () => {
116
+ await testUtils.waitForStateChange(alice.meeting, 'JOINED');
117
+
118
+ const bobIdle = testUtils.waitForStateChange(bob.meeting, 'IDLE');
119
+ const chrisIdle = testUtils.waitForStateChange(chris.meeting, 'IDLE');
120
+
121
+ await bobIdle;
122
+ await chrisIdle;
123
+
124
+ const bobJoined = testUtils.waitForStateChange(bob.meeting, 'JOINED');
125
+ const chrisJoined = testUtils.waitForStateChange(chris.meeting, 'JOINED');
126
+ const bobJoin = bob.meeting.join({enableMultistream: true});
127
+ const chrisJoin = chris.meeting.join({enableMultistream: true});
128
+
129
+ await bobJoin;
130
+ await chrisJoin;
131
+ await bobJoined;
132
+ await chrisJoined;
133
+
134
+ assert.exists(bob.meeting.joinedWith);
135
+ assert.exists(chris.meeting.joinedWith);
136
+ });
137
+
138
+ it('users "alice", "bob", and "chris" add media', async () => {
139
+ mediaReadyListener = testUtils.waitForEvents([
140
+ {scope: alice.meeting, event: 'media:negotiated'},
141
+ {scope: bob.meeting, event: 'media:negotiated'},
142
+ {scope: chris.meeting, event: 'media:negotiated'},
143
+ ]);
144
+
145
+ const addMediaAlice = testUtils.addMedia(alice, {expectedMediaReadyTypes: ['local']});
146
+ const addMediaBob = testUtils.addMedia(bob, {expectedMediaReadyTypes: ['local']});
147
+ const addMediaChris = testUtils.addMedia(chris, {expectedMediaReadyTypes: ['local']});
148
+
149
+ await addMediaAlice;
150
+ await addMediaBob;
151
+ await addMediaChris;
152
+
153
+ assert.isTrue(alice.meeting.mediaProperties.mediaDirection.sendAudio);
154
+ assert.isTrue(alice.meeting.mediaProperties.mediaDirection.sendVideo);
155
+ assert.isTrue(alice.meeting.mediaProperties.mediaDirection.receiveAudio);
156
+ assert.isTrue(alice.meeting.mediaProperties.mediaDirection.receiveVideo);
157
+ assert.isTrue(bob.meeting.mediaProperties.mediaDirection.sendAudio);
158
+ assert.isTrue(bob.meeting.mediaProperties.mediaDirection.sendVideo);
159
+ assert.isTrue(bob.meeting.mediaProperties.mediaDirection.receiveAudio);
160
+ assert.isTrue(bob.meeting.mediaProperties.mediaDirection.receiveVideo);
161
+ assert.isTrue(chris.meeting.mediaProperties.mediaDirection.sendAudio);
162
+ assert.isTrue(chris.meeting.mediaProperties.mediaDirection.sendVideo);
163
+ assert.isTrue(chris.meeting.mediaProperties.mediaDirection.receiveAudio);
164
+ assert.isTrue(chris.meeting.mediaProperties.mediaDirection.receiveVideo);
165
+ });
166
+
167
+ it(`users "alice", "bob", and "chris" should be using the "${MEDIA_SERVERS.HOMER}" media server`, async () => {
168
+ await mediaReadyListener;
169
+
170
+ assert.equal(alice.meeting.mediaProperties.webrtcMediaConnection.mediaServer, MEDIA_SERVERS.HOMER);
171
+ assert.equal(bob.meeting.mediaProperties.webrtcMediaConnection.mediaServer, MEDIA_SERVERS.HOMER);
172
+ assert.equal(chris.meeting.mediaProperties.webrtcMediaConnection.mediaServer, MEDIA_SERVERS.HOMER);
173
+ });
174
+ });
175
+ }
176
+ });
@@ -20,6 +20,9 @@ describe('plugin-meetings', () => {
20
20
  webex.internal.llm.on = sinon.stub();
21
21
  webex.internal.mercury.on = sinon.stub();
22
22
  breakouts = new Breakouts({}, {parent: webex});
23
+ breakouts.locusUrl = 'locusUrl';
24
+ breakouts.breakoutServiceUrl = 'breakoutServiceUrl';
25
+ breakouts.url = 'url';
23
26
  webex.request = sinon.stub().returns(Promise.resolve('REQUEST_RETURN_VALUE'));
24
27
  });
25
28
 
@@ -289,5 +292,78 @@ describe('plugin-meetings', () => {
289
292
  assert.equal(breakouts.isInMainSession, true);
290
293
  });
291
294
  });
295
+
296
+ describe('#breakoutServiceUrlUpdate', () => {
297
+ it('sets the breakoutService url', () => {
298
+ breakouts.breakoutServiceUrlUpdate('newBreakoutServiceUrl');
299
+ assert.equal(breakouts.breakoutServiceUrl, 'newBreakoutServiceUrl/breakout/');
300
+ });
301
+ });
302
+
303
+ describe('#toggleBreakout', () => {
304
+ it('enableBreakoutSession is undefined, run enableBreakouts then toggleBreakout', async() => {
305
+ breakouts.enableBreakoutSession = undefined;
306
+ breakouts.enableBreakouts = sinon.stub().resolves(({body: {
307
+ sessionId: 'sessionId',
308
+ groupId: 'groupId',
309
+ name: 'name',
310
+ current: true,
311
+ sessionType: 'sessionType',
312
+ url: 'url'
313
+ }}))
314
+ breakouts.updateBreakout = sinon.stub().resolves();
315
+ breakouts.doToggleBreakout = sinon.stub().resolves();
316
+
317
+ await breakouts.toggleBreakout(false);
318
+ assert.calledOnceWithExactly(breakouts.enableBreakouts);
319
+ assert.calledOnceWithExactly(breakouts.updateBreakout, {
320
+ sessionId: 'sessionId',
321
+ groupId: 'groupId',
322
+ name: 'name',
323
+ current: true,
324
+ sessionType: 'sessionType',
325
+ url: 'url'
326
+ });
327
+ assert.calledOnceWithExactly(breakouts.doToggleBreakout, false);
328
+ });
329
+
330
+ it('enableBreakoutSession is exist, run toggleBreakout', async() => {
331
+ breakouts.enableBreakoutSession = true;
332
+ breakouts.doToggleBreakout = sinon.stub().resolves();
333
+ await breakouts.toggleBreakout(true);
334
+ assert.calledOnceWithExactly(breakouts.doToggleBreakout, true);
335
+ });
336
+ });
337
+
338
+ describe('enableBreakouts', () => {
339
+ it('makes the request as expected', async () => {
340
+ const result = await breakouts.enableBreakouts();
341
+ breakouts.set('breakoutServiceUrl', 'breakoutServiceUrl');
342
+ assert.calledOnceWithExactly(webex.request, {
343
+ method: 'POST',
344
+ uri: 'breakoutServiceUrl',
345
+ body: {
346
+ locusUrl: 'locusUrl'
347
+ }
348
+ });
349
+
350
+ assert.equal(result, 'REQUEST_RETURN_VALUE');
351
+ });
352
+ });
353
+
354
+ describe('doToggleBreakout', () => {
355
+ it('makes the request as expected', async () => {
356
+ const result = await breakouts.doToggleBreakout(true);
357
+ assert.calledOnceWithExactly(webex.request, {
358
+ method: 'PUT',
359
+ uri: 'url',
360
+ body: {
361
+ enableBreakoutSession: true
362
+ }
363
+ });
364
+
365
+ assert.equal(result, 'REQUEST_RETURN_VALUE');
366
+ });
367
+ });
292
368
  });
293
369
  });
@@ -0,0 +1,124 @@
1
+ import ControlsOptionsManager from '@webex/plugin-meetings/src/controls-options-manager';
2
+ import sinon from 'sinon';
3
+ import {assert} from '@webex/test-helper-chai';
4
+ import { HTTP_VERBS } from '@webex/plugin-meetings/src/constants';
5
+
6
+ describe('plugin-meetings', () => {
7
+ describe('controls-options-manager tests', () => {
8
+ describe('index', () => {
9
+ let request;
10
+
11
+ describe('class tests', () => {
12
+ it('can set and extract new values later on', () => {
13
+ const manager = new ControlsOptionsManager({});
14
+ assert.isUndefined(manager.getLocusUrl());
15
+ manager.set({
16
+ locusUrl: 'test/id',
17
+ })
18
+ assert(manager.getLocusUrl(), 'test/id');
19
+ });
20
+ });
21
+
22
+ describe('Mute On Entry', () => {
23
+ let manager;
24
+
25
+ beforeEach(() => {
26
+ request = {
27
+ request: sinon.stub().returns(Promise.resolve()),
28
+ };
29
+
30
+ manager = new ControlsOptionsManager(request);
31
+
32
+ manager.set({
33
+ locusUrl: 'test/id',
34
+ displayHints: [],
35
+ })
36
+ });
37
+
38
+ describe('setMuteOnEntry', () => {
39
+ it('rejects when correct display hint is not present enabled=false', () => {
40
+ const result = manager.setMuteOnEntry(false);
41
+
42
+ assert.notCalled(request.request);
43
+
44
+ assert.isRejected(result);
45
+ });
46
+
47
+ it('rejects when correct display hint is not present enabled=true', () => {
48
+ const result = manager.setMuteOnEntry(true);
49
+
50
+ assert.notCalled(request.request);
51
+
52
+ assert.isRejected(result);
53
+ });
54
+
55
+ it('can set mute on entry when the display hint is available enabled=true', () => {
56
+ manager.setDisplayHints(['ENABLE_MUTE_ON_ENTRY']);
57
+
58
+ const result = manager.setMuteOnEntry(true);
59
+
60
+ assert.calledWith(request.request, { uri: 'test/id/controls',
61
+ body: { muteOnEntry: { enabled: true } },
62
+ method: HTTP_VERBS.PATCH});
63
+
64
+ assert.deepEqual(result, request.request.firstCall.returnValue);
65
+ });
66
+
67
+ it('can set mute on entry when the display hint is available enabled=false', () => {
68
+ manager.setDisplayHints(['DISABLE_MUTE_ON_ENTRY']);
69
+
70
+ const result = manager.setMuteOnEntry(false);
71
+
72
+ assert.calledWith(request.request, { uri: 'test/id/controls',
73
+ body: { muteOnEntry: { enabled: false } },
74
+ method: HTTP_VERBS.PATCH});
75
+
76
+ assert.deepEqual(result, request.request.firstCall.returnValue);
77
+ });
78
+ });
79
+
80
+ describe('setDisallowUnmute', () => {
81
+ it('rejects when correct display hint is not present enabled=false', () => {
82
+ const result = manager.setDisallowUnmute(false);
83
+
84
+ assert.notCalled(request.request);
85
+
86
+ assert.isRejected(result);
87
+ });
88
+
89
+ it('rejects when correct display hint is not present enabled=true', () => {
90
+ const result = manager.setDisallowUnmute(true);
91
+
92
+ assert.notCalled(request.request);
93
+
94
+ assert.isRejected(result);
95
+ });
96
+
97
+ it('can set mute on entry when the display hint is available enabled=true', () => {
98
+ manager.setDisplayHints(['ENABLE_HARD_MUTE']);
99
+
100
+ const result = manager.setDisallowUnmute(true);
101
+
102
+ assert.calledWith(request.request, { uri: 'test/id/controls',
103
+ body: { disallowUnmute: { enabled: true } },
104
+ method: HTTP_VERBS.PATCH});
105
+
106
+ assert.deepEqual(result, request.request.firstCall.returnValue);
107
+ });
108
+
109
+ it('can set mute on entry when the display hint is available enabled=false', () => {
110
+ manager.setDisplayHints(['DISABLE_HARD_MUTE']);
111
+
112
+ const result = manager.setDisallowUnmute(false);
113
+
114
+ assert.calledWith(request.request, { uri: 'test/id/controls',
115
+ body: { disallowUnmute: { enabled: false } },
116
+ method: HTTP_VERBS.PATCH});
117
+
118
+ assert.deepEqual(result, request.request.firstCall.returnValue);
119
+ });
120
+ });
121
+ });
122
+ });
123
+ });
124
+ });
@@ -0,0 +1,66 @@
1
+ import ControlsOptionsUtil from '@webex/plugin-meetings/src/controls-options-manager/util';
2
+ import { assert } from 'chai';
3
+
4
+ describe('plugin-meetings', () => {
5
+ describe('controls-option-manager tests', () => {
6
+ describe('util tests', () => {
7
+
8
+ let locusInfo;
9
+
10
+ beforeEach(() => {
11
+ locusInfo = {
12
+ parsedLocus: {
13
+ info: {
14
+ userDisplayHints: [],
15
+ },
16
+ },
17
+ };
18
+ });
19
+
20
+ describe('canUserSetMuteOnEntry', () => {
21
+ it('can set mute on entry enable', () => {
22
+ locusInfo.parsedLocus.info.userDisplayHints.push('ENABLE_MUTE_ON_ENTRY');
23
+
24
+ assert.equal(ControlsOptionsUtil.canSetMuteOnEntry(locusInfo.parsedLocus.info.userDisplayHints), true);
25
+ });
26
+
27
+ it('can set mute on entry disable', () => {
28
+ locusInfo.parsedLocus.info.userDisplayHints.push('DISABLE_MUTE_ON_ENTRY');
29
+
30
+ assert.equal(ControlsOptionsUtil.canUnsetMuteOnEntry(locusInfo.parsedLocus.info.userDisplayHints), true);
31
+ });
32
+
33
+ it('rejects when correct display hint is not present for setting mute on entry', () => {
34
+ assert.equal(ControlsOptionsUtil.canSetMuteOnEntry(locusInfo.parsedLocus.info.userDisplayHints), false);
35
+ });
36
+
37
+ it('rejects when correct display hint is not present for unsetting mute on entry', () => {
38
+ assert.equal(ControlsOptionsUtil.canUnsetMuteOnEntry(locusInfo.parsedLocus.info.userDisplayHints), false);
39
+ });
40
+ });
41
+
42
+ describe('canSetDisallowUnmute', () => {
43
+ it('can set disallow unmute enable', () => {
44
+ locusInfo.parsedLocus.info.userDisplayHints.push('ENABLE_HARD_MUTE');
45
+
46
+ assert.equal(ControlsOptionsUtil.canSetDisallowUnmute(locusInfo.parsedLocus.info.userDisplayHints), true);
47
+ });
48
+
49
+ it('can set disallow unmute disable', () => {
50
+ locusInfo.parsedLocus.info.userDisplayHints.push('DISABLE_HARD_MUTE');
51
+
52
+ assert.equal(ControlsOptionsUtil.canUnsetDisallowUnmute(locusInfo.parsedLocus.info.userDisplayHints), true);
53
+ });
54
+
55
+ it('rejects when correct display hint is not present', () => {
56
+ assert.equal(ControlsOptionsUtil.canSetDisallowUnmute(locusInfo.parsedLocus.info.userDisplayHints), false);
57
+ });
58
+
59
+ it('rejects when correct display hint is not present', () => {
60
+ assert.equal(ControlsOptionsUtil.canUnsetDisallowUnmute(locusInfo.parsedLocus.info.userDisplayHints), false);
61
+ });
62
+ });
63
+
64
+ });
65
+ });
66
+ });
@@ -13,6 +13,10 @@ describe('plugin-meetings', () => {
13
13
  canStartRecording: null,
14
14
  canPauseRecording: null,
15
15
  canResumeRecording: null,
16
+ canSetMuteOnEntry: null,
17
+ canUnsetMuteOnEntry: null,
18
+ canSetDisallowUnmute: null,
19
+ canUnsetDisallowUnmute: null,
16
20
  canStopRecording: null,
17
21
  canRaiseHand: null,
18
22
  canLowerAllHands: null,
@@ -51,6 +55,10 @@ describe('plugin-meetings', () => {
51
55
  'canPauseRecording',
52
56
  'canResumeRecording',
53
57
  'canStopRecording',
58
+ 'canSetMuteOnEntry',
59
+ 'canUnsetMuteOnEntry',
60
+ 'canSetDisallowUnmute',
61
+ 'canUnsetDisallowUnmute',
54
62
  'canRaiseHand',
55
63
  'canLowerAllHands',
56
64
  'canLowerSomeoneElsesHand',