@webex/plugin-meetings 3.0.0-beta.16 → 3.0.0-beta.18

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 (156) hide show
  1. package/dist/breakouts/breakout.js +116 -0
  2. package/dist/breakouts/breakout.js.map +1 -0
  3. package/dist/breakouts/collection.js +23 -0
  4. package/dist/breakouts/collection.js.map +1 -0
  5. package/dist/breakouts/index.js +226 -0
  6. package/dist/breakouts/index.js.map +1 -0
  7. package/dist/config.js +4 -1
  8. package/dist/config.js.map +1 -1
  9. package/dist/constants.js +43 -6
  10. package/dist/constants.js.map +1 -1
  11. package/dist/locus-info/controlsUtils.js +2 -1
  12. package/dist/locus-info/controlsUtils.js.map +1 -1
  13. package/dist/locus-info/index.js +48 -0
  14. package/dist/locus-info/index.js.map +1 -1
  15. package/dist/locus-info/parser.js +1 -0
  16. package/dist/locus-info/parser.js.map +1 -1
  17. package/dist/locus-info/selfUtils.js +19 -11
  18. package/dist/locus-info/selfUtils.js.map +1 -1
  19. package/dist/media/index.js +3 -3
  20. package/dist/media/index.js.map +1 -1
  21. package/dist/media/properties.js +4 -4
  22. package/dist/media/properties.js.map +1 -1
  23. package/dist/meeting/in-meeting-actions.js +5 -1
  24. package/dist/meeting/in-meeting-actions.js.map +1 -1
  25. package/dist/meeting/index.js +652 -459
  26. package/dist/meeting/index.js.map +1 -1
  27. package/dist/meeting/request.js +25 -44
  28. package/dist/meeting/request.js.map +1 -1
  29. package/dist/meeting/request.type.js.map +1 -1
  30. package/dist/meeting/util.js +22 -57
  31. package/dist/meeting/util.js.map +1 -1
  32. package/dist/meeting-info/meeting-info-v2.js +2 -0
  33. package/dist/meeting-info/meeting-info-v2.js.map +1 -1
  34. package/dist/meetings/index.js +28 -18
  35. package/dist/meetings/index.js.map +1 -1
  36. package/dist/meetings/request.js +14 -12
  37. package/dist/meetings/request.js.map +1 -1
  38. package/dist/member/index.js +9 -0
  39. package/dist/member/index.js.map +1 -1
  40. package/dist/member/util.js +14 -1
  41. package/dist/member/util.js.map +1 -1
  42. package/dist/members/index.js +8 -6
  43. package/dist/members/index.js.map +1 -1
  44. package/dist/members/request.js +3 -1
  45. package/dist/members/request.js.map +1 -1
  46. package/dist/multistream/mediaRequestManager.js +46 -6
  47. package/dist/multistream/mediaRequestManager.js.map +1 -1
  48. package/dist/multistream/multistreamMedia.js +4 -0
  49. package/dist/multistream/multistreamMedia.js.map +1 -1
  50. package/dist/multistream/receiveSlot.js +3 -3
  51. package/dist/multistream/receiveSlot.js.map +1 -1
  52. package/dist/multistream/receiveSlotManager.js +8 -6
  53. package/dist/multistream/receiveSlotManager.js.map +1 -1
  54. package/dist/multistream/remoteMedia.js.map +1 -1
  55. package/dist/multistream/remoteMediaGroup.js.map +1 -1
  56. package/dist/multistream/remoteMediaManager.js +168 -63
  57. package/dist/multistream/remoteMediaManager.js.map +1 -1
  58. package/dist/reachability/index.js +63 -51
  59. package/dist/reachability/index.js.map +1 -1
  60. package/dist/reactions/constants.js +13 -0
  61. package/dist/reactions/constants.js.map +1 -0
  62. package/dist/reactions/reactions.type.js.map +1 -1
  63. package/dist/reconnection-manager/index.js +25 -12
  64. package/dist/reconnection-manager/index.js.map +1 -1
  65. package/dist/recording-controller/enums.js +17 -0
  66. package/dist/recording-controller/enums.js.map +1 -0
  67. package/dist/recording-controller/index.js +343 -0
  68. package/dist/recording-controller/index.js.map +1 -0
  69. package/dist/recording-controller/util.js +63 -0
  70. package/dist/recording-controller/util.js.map +1 -0
  71. package/dist/roap/request.js +88 -68
  72. package/dist/roap/request.js.map +1 -1
  73. package/dist/roap/turnDiscovery.js +72 -47
  74. package/dist/roap/turnDiscovery.js.map +1 -1
  75. package/dist/statsAnalyzer/index.js +3 -3
  76. package/dist/statsAnalyzer/index.js.map +1 -1
  77. package/dist/statsAnalyzer/mqaUtil.js +18 -6
  78. package/dist/statsAnalyzer/mqaUtil.js.map +1 -1
  79. package/package.json +24 -19
  80. package/src/breakouts/README.md +190 -0
  81. package/src/breakouts/breakout.ts +110 -0
  82. package/src/breakouts/collection.ts +19 -0
  83. package/src/breakouts/index.ts +225 -0
  84. package/src/config.ts +4 -1
  85. package/src/constants.ts +39 -1
  86. package/src/locus-info/controlsUtils.ts +2 -0
  87. package/src/locus-info/index.ts +59 -1
  88. package/src/locus-info/parser.ts +1 -0
  89. package/src/locus-info/selfUtils.ts +8 -0
  90. package/src/media/index.ts +1 -2
  91. package/src/media/properties.ts +6 -9
  92. package/src/meeting/in-meeting-actions.ts +8 -0
  93. package/src/meeting/index.ts +360 -111
  94. package/src/meeting/request.ts +9 -31
  95. package/src/meeting/request.type.ts +2 -0
  96. package/src/meeting/util.ts +25 -60
  97. package/src/meeting-info/meeting-info-v2.ts +2 -0
  98. package/src/meetings/index.ts +10 -5
  99. package/src/meetings/request.ts +1 -1
  100. package/src/member/index.ts +9 -0
  101. package/src/member/util.ts +14 -1
  102. package/src/members/index.ts +1 -0
  103. package/src/members/request.ts +1 -0
  104. package/src/multistream/mediaRequestManager.ts +79 -15
  105. package/src/multistream/multistreamMedia.ts +4 -0
  106. package/src/multistream/receiveSlot.ts +17 -12
  107. package/src/multistream/receiveSlotManager.ts +22 -21
  108. package/src/multistream/remoteMedia.ts +1 -1
  109. package/src/multistream/remoteMediaGroup.ts +2 -2
  110. package/src/multistream/remoteMediaManager.ts +150 -37
  111. package/src/reachability/index.ts +16 -13
  112. package/src/reactions/constants.ts +4 -0
  113. package/src/reactions/reactions.type.ts +25 -0
  114. package/src/reconnection-manager/index.ts +18 -9
  115. package/src/recording-controller/enums.ts +8 -0
  116. package/src/recording-controller/index.ts +315 -0
  117. package/src/recording-controller/util.ts +58 -0
  118. package/src/roap/request.ts +78 -73
  119. package/src/roap/turnDiscovery.ts +8 -6
  120. package/src/statsAnalyzer/index.ts +4 -4
  121. package/src/statsAnalyzer/mqaUtil.ts +6 -0
  122. package/test/unit/spec/breakouts/breakout.ts +119 -0
  123. package/test/unit/spec/breakouts/collection.ts +15 -0
  124. package/test/unit/spec/breakouts/index.ts +293 -0
  125. package/test/unit/spec/locus-info/controlsUtils.js +20 -0
  126. package/test/unit/spec/locus-info/index.js +103 -0
  127. package/test/unit/spec/locus-info/selfConstant.js +25 -0
  128. package/test/unit/spec/locus-info/selfUtils.js +84 -0
  129. package/test/unit/spec/media/index.ts +1 -1
  130. package/test/unit/spec/media/properties.ts +9 -9
  131. package/test/unit/spec/meeting/effectsState.js +5 -1
  132. package/test/unit/spec/meeting/in-meeting-actions.ts +5 -1
  133. package/test/unit/spec/meeting/index.js +241 -50
  134. package/test/unit/spec/meeting/request.js +17 -0
  135. package/test/unit/spec/meeting/utils.js +28 -122
  136. package/test/unit/spec/meetings/index.js +1 -0
  137. package/test/unit/spec/member/util.js +26 -1
  138. package/test/unit/spec/multistream/mediaRequestManager.ts +312 -50
  139. package/test/unit/spec/multistream/receiveSlot.ts +6 -6
  140. package/test/unit/spec/multistream/receiveSlotManager.ts +13 -13
  141. package/test/unit/spec/multistream/remoteMedia.ts +2 -2
  142. package/test/unit/spec/multistream/remoteMediaGroup.ts +5 -5
  143. package/test/unit/spec/multistream/remoteMediaManager.ts +354 -65
  144. package/test/unit/spec/reachability/index.ts +58 -24
  145. package/test/unit/spec/reconnection-manager/index.js +42 -13
  146. package/test/unit/spec/recording-controller/index.js +231 -0
  147. package/test/unit/spec/recording-controller/util.js +102 -0
  148. package/test/unit/spec/roap/index.ts +2 -1
  149. package/test/unit/spec/roap/request.ts +114 -0
  150. package/test/unit/spec/roap/turnDiscovery.ts +45 -29
  151. package/test/unit/spec/stats-analyzer/index.js +2 -2
  152. package/test/utils/webex-test-users.js +1 -0
  153. package/tsconfig.json +6 -0
  154. package/dist/media/internal-media-core-wrapper.js +0 -18
  155. package/dist/media/internal-media-core-wrapper.js.map +0 -1
  156. package/src/media/internal-media-core-wrapper.ts +0 -9
@@ -0,0 +1,119 @@
1
+ import {assert, expect} from '@webex/test-helper-chai';
2
+ import Breakout from '@webex/plugin-meetings/src/breakouts/breakout';
3
+ import Breakouts from '@webex/plugin-meetings/src/breakouts';
4
+ import Members from '@webex/plugin-meetings/src/members';
5
+ import MockWebex from '@webex/test-helper-mock-webex';
6
+ import sinon from "sinon";
7
+
8
+
9
+ describe('plugin-meetings', () => {
10
+ describe('breakout', () => {
11
+
12
+ let webex;
13
+ let breakout;
14
+ let breakouts;
15
+
16
+ beforeEach(() => {
17
+ // @ts-ignore
18
+ webex = new MockWebex({});
19
+ webex.internal.llm.on = sinon.stub();
20
+ webex.internal.mercury.on = sinon.stub();
21
+ breakouts = new Breakouts({}, {parent: webex});
22
+ breakout = new Breakout({}, {parent: breakouts});
23
+ breakout.groupId = 'groupId';
24
+ breakout.sessionId = 'sessionId';
25
+ breakout.url = 'url';
26
+ webex.request = sinon.stub().returns(Promise.resolve('REQUEST_RETURN_VALUE'));
27
+ });
28
+
29
+ describe('initialize', () => {
30
+ it('creates the object correctly', () => {
31
+ assert.instanceOf(breakout.members, Members);
32
+ })
33
+ });
34
+
35
+ describe('#join', () => {
36
+ it('makes the request as expected', async () => {
37
+ const result = await breakout.join()
38
+
39
+ assert.calledOnceWithExactly(webex.request, {
40
+ method: 'POST',
41
+ uri: 'url/move',
42
+ body: {
43
+ groupId: 'groupId',
44
+ sessionId: 'sessionId'
45
+ }
46
+ });
47
+
48
+ assert.equal(result, 'REQUEST_RETURN_VALUE')
49
+ });
50
+ });
51
+
52
+ describe('#leave', () => {
53
+ it('throws error if in main sesson', async () => {
54
+
55
+ breakout.set('sessionType', 'MAIN');
56
+
57
+ const fn = () => {
58
+ breakout.leave();
59
+ }
60
+
61
+ expect(fn).to.throw(/Cannot leave the main session/);
62
+ });
63
+
64
+ it('throws error if there is no main session', async () => {
65
+ const fn = () => {
66
+ breakout.leave();
67
+ }
68
+
69
+ expect(fn).to.throw(/Cannot leave, no main session found/);
70
+ });
71
+
72
+ it('joins the main session if in a breakout', async () => {
73
+ breakout.parent.breakouts.add({
74
+ sessionType: 'MAIN'
75
+ });
76
+
77
+ const mainSession = breakouts.breakouts.models[0];
78
+
79
+ mainSession.join = sinon.stub().returns('JOIN_RETURN_VALUE');
80
+
81
+ const result = await breakout.leave();
82
+
83
+ assert.calledOnceWithExactly(mainSession.join);
84
+ assert.equal(result, 'JOIN_RETURN_VALUE');
85
+ })
86
+ });
87
+
88
+ describe('#askForHelp', () => {
89
+ it('makes the request as expected', async () => {
90
+ const result = await breakout.askForHelp()
91
+
92
+ assert.calledOnceWithExactly(webex.request, {
93
+ method: 'POST',
94
+ uri: 'url/help',
95
+ body: {
96
+ groupId: 'groupId',
97
+ sessionId: 'sessionId'
98
+ }
99
+ });
100
+
101
+ assert.equal(result, 'REQUEST_RETURN_VALUE')
102
+ });
103
+ });
104
+
105
+ describe('#parseRoster', () => {
106
+ it('calls locusParticipantsUpdate', () => {
107
+ breakout.members = {
108
+ locusParticipantsUpdate: sinon.stub()
109
+ };
110
+
111
+ const locusData = {some: 'data'};
112
+ const result = breakout.parseRoster(locusData);
113
+
114
+ assert.calledOnceWithExactly(breakout.members.locusParticipantsUpdate, locusData);
115
+ assert.equal(result, undefined);
116
+ })
117
+ })
118
+ });
119
+ });
@@ -0,0 +1,15 @@
1
+ import {assert} from '@webex/test-helper-chai';
2
+ import Breakout from '@webex/plugin-meetings/src/breakouts/breakout';
3
+ import BreakoutCollection from '@webex/plugin-meetings/src/breakouts/collection';
4
+
5
+ describe('plugin-meetings', () => {
6
+ describe('BreakoutCollection', () => {
7
+ it('the breakout collection is as expected', () => {
8
+ const collection = new BreakoutCollection();
9
+
10
+ assert.equal(collection.model, Breakout);
11
+ assert.equal(collection.namespace, 'Meetings');
12
+ assert.equal(collection.mainIndex, 'sessionId');
13
+ });
14
+ });
15
+ });
@@ -0,0 +1,293 @@
1
+ import {assert} from '@webex/test-helper-chai';
2
+ import Breakout from '@webex/plugin-meetings/src/breakouts/breakout';
3
+ import Breakouts from '@webex/plugin-meetings/src/breakouts';
4
+ import BreakoutCollection from '@webex/plugin-meetings/src/breakouts/collection';
5
+ import LoggerProxy from '@webex/plugin-meetings/src/common/logs/logger-proxy';
6
+ import {BREAKOUTS} from '@webex/plugin-meetings/src/constants';
7
+ import sinon from "sinon";
8
+ import MockWebex from '@webex/test-helper-mock-webex';
9
+ import testUtils from '../../../utils/testUtils';
10
+
11
+
12
+ describe('plugin-meetings', () => {
13
+ describe('Breakouts', () => {
14
+ let webex;
15
+ let breakouts;
16
+
17
+ beforeEach(() => {
18
+ // @ts-ignore
19
+ webex = new MockWebex({});
20
+ webex.internal.llm.on = sinon.stub();
21
+ webex.internal.mercury.on = sinon.stub();
22
+ breakouts = new Breakouts({}, {parent: webex});
23
+ webex.request = sinon.stub().returns(Promise.resolve('REQUEST_RETURN_VALUE'));
24
+ });
25
+
26
+ describe('#initialize', () => {
27
+ it('creates Breakouts as expected', () => {
28
+ assert.equal(breakouts.namespace, 'Meetings');
29
+ });
30
+
31
+ it('emits BREAKOUTS_CLOSING event when the status is CLOSING', () => {
32
+ let called = false;
33
+ breakouts.listenTo(breakouts, BREAKOUTS.EVENTS.BREAKOUTS_CLOSING, () => {
34
+ called = true;
35
+ });
36
+
37
+ breakouts.set('status', 'something');
38
+
39
+ assert.isFalse(called);
40
+
41
+ breakouts.set({'status': BREAKOUTS.STATUS.CLOSING});
42
+
43
+ assert.isTrue(called);
44
+ });
45
+
46
+ it('debounces querying rosters on add', () => {
47
+ breakouts.debouncedQueryRosters = sinon.stub();
48
+ breakouts.breakouts.add({sessionType: 'MAIN'});
49
+
50
+ assert.calledOnceWithExactly(breakouts.debouncedQueryRosters);
51
+ });
52
+ });
53
+
54
+ describe('#listenToBroadcastMessages', () => {
55
+ it('triggers message event when a message received', () => {
56
+ const call = webex.internal.llm.on.getCall(0);
57
+ const callback = call.args[1];
58
+
59
+ assert.equal(call.args[0], 'event:breakout.message');
60
+
61
+ let message;
62
+
63
+ breakouts.listenTo(breakouts, BREAKOUTS.EVENTS.MESSAGE, (event) => {
64
+ message = event;
65
+ })
66
+
67
+ breakouts.currentBreakoutSession.sessionId = 'sessionId';
68
+
69
+ callback({
70
+ data: {
71
+ senderUserId: 'senderUserId',
72
+ sentTime: 'sentTime',
73
+ message: 'message',
74
+ }
75
+ });
76
+
77
+ assert.deepEqual(message, {
78
+ senderUserId: "senderUserId",
79
+ sentTime: 'sentTime',
80
+ message: 'message',
81
+ sessionId: 'sessionId'
82
+ });
83
+ });
84
+ });
85
+
86
+ describe('#listenToBreakoutRosters', () => {
87
+ it('triggers member update event when a roster received', () => {
88
+ const call = webex.internal.mercury.on.getCall(0);
89
+ const callback = call.args[1];
90
+
91
+ assert.equal(call.args[0], 'event:breakout.roster');
92
+
93
+ let called = false;
94
+
95
+ breakouts.listenTo(breakouts, BREAKOUTS.EVENTS.MEMBERS_UPDATE, () => {
96
+ called = true;
97
+ })
98
+ breakouts.handleRosterUpdate = sinon.stub();
99
+
100
+ callback({
101
+ data: {
102
+ locus: 'locus'
103
+ }
104
+ });
105
+
106
+ assert.isTrue(called);
107
+ assert.calledOnceWithExactly(breakouts.handleRosterUpdate, 'locus');
108
+ });
109
+ });
110
+
111
+ describe('#updateBreakout', () => {
112
+ it('updates the current breakout session', () => {
113
+ breakouts.updateBreakout({
114
+ sessionId: 'sessionId',
115
+ groupId: 'groupId',
116
+ sessionType: 'sessionType',
117
+ url: 'url',
118
+ name: 'name',
119
+ allowBackToMain: true,
120
+ delayCloseTime: 10,
121
+ enableBreakoutSession: true,
122
+ startTime: 'startTime',
123
+ status: 'active',
124
+ locusUrl: 'locusUrl'
125
+ });
126
+
127
+ assert.equal(breakouts.allowBackToMain, true);
128
+ assert.equal(breakouts.delayCloseTime, 10);
129
+ assert.equal(breakouts.enableBreakoutSession, true);
130
+ assert.equal(breakouts.groupId, 'groupId');
131
+ assert.equal(breakouts.name, 'name');
132
+ assert.equal(breakouts.sessionId, 'sessionId');
133
+ assert.equal(breakouts.startTime, 'startTime');
134
+ assert.equal(breakouts.status, 'active');
135
+ assert.equal(breakouts.url, 'url');
136
+ assert.equal(breakouts.locusUrl, 'locusUrl');
137
+
138
+ assert.equal(breakouts.currentBreakoutSession.sessionId, 'sessionId');
139
+ assert.equal(breakouts.currentBreakoutSession.groupId, 'groupId');
140
+ assert.equal(breakouts.currentBreakoutSession.name, 'name');
141
+ assert.equal(breakouts.currentBreakoutSession.current, true);
142
+ assert.equal(breakouts.currentBreakoutSession.sessionType, 'sessionType');
143
+ assert.equal(breakouts.currentBreakoutSession.url, 'url');
144
+ assert.equal(breakouts.currentBreakoutSession.active, false);
145
+ assert.equal(breakouts.currentBreakoutSession.allowed, false);
146
+ assert.equal(breakouts.currentBreakoutSession.assigned, false);
147
+ assert.equal(breakouts.currentBreakoutSession.assignedCurrent, false);
148
+ assert.equal(breakouts.currentBreakoutSession.requested, false);
149
+ });
150
+ });
151
+
152
+ describe('#updateBreakoutSessions', () => {
153
+
154
+ const checkBreakout = (breakout, sessionId, state) => {
155
+ assert.deepEqual(breakout.attributes, {
156
+ active: false,
157
+ allowed: false,
158
+ assigned: false,
159
+ assignedCurrent: false,
160
+ current: false,
161
+ ready: true,
162
+ requested: false,
163
+ url: 'url',
164
+ sessionId,
165
+ ...{[state]: true}
166
+ });
167
+ }
168
+
169
+ it('works', () => {
170
+
171
+ breakouts.set('url', 'url');
172
+
173
+ const payload = {
174
+ breakoutSessions: {
175
+ active: [{sessionId: 'sessionId1'}],
176
+ assigned: [{sessionId: 'sessionId2'}],
177
+ allowed: [{sessionId: 'sessionId3'}],
178
+ assignedCurrent: [{sessionId: 'sessionId4'}],
179
+ requested: [{sessionId: 'sessionId5'}],
180
+ }
181
+ }
182
+
183
+ breakouts.updateBreakoutSessions(payload);
184
+
185
+ checkBreakout(breakouts.breakouts.get('sessionId1'), 'sessionId1', 'active');
186
+ checkBreakout(breakouts.breakouts.get('sessionId2'), 'sessionId2', 'assigned');
187
+ checkBreakout(breakouts.breakouts.get('sessionId3'), 'sessionId3', 'allowed');
188
+ checkBreakout(breakouts.breakouts.get('sessionId4'), 'sessionId4', 'assignedCurrent');
189
+ checkBreakout(breakouts.breakouts.get('sessionId5'), 'sessionId5', 'requested');
190
+ })
191
+ });
192
+
193
+ describe('#locusUrlUpdate', () => {
194
+ it('sets the locus url', () => {
195
+ breakouts.locusUrlUpdate('newUrl');
196
+
197
+ assert.equal(breakouts.locusUrl, 'newUrl');
198
+ });
199
+ });
200
+
201
+ describe('#cleanUp', () => {
202
+ it('stops listening', () => {
203
+ breakouts.stopListening = sinon.stub();
204
+
205
+ breakouts.cleanUp();
206
+
207
+ assert.calledOnceWithExactly(breakouts.stopListening);
208
+ });
209
+ });
210
+
211
+ describe('#handleRosterUpdate', () => {
212
+ it('does not break if it cannot find the session', () => {
213
+ breakouts.handleRosterUpdate({controls: {breakout: {sessionId: 'sessionId'}}});
214
+ });
215
+
216
+ it('calls parse roster if it can find the session', () => {
217
+ breakouts.breakouts.add({sessionId: 'sessionId'});
218
+
219
+ const breakout = breakouts.breakouts.models[0];
220
+ breakout.parseRoster = sinon.stub();
221
+
222
+ const locus = {controls: {breakout: {sessionId: 'sessionId'}}}
223
+
224
+ breakouts.handleRosterUpdate(locus);
225
+ assert.calledOnceWithExactly(breakout.parseRoster, locus);
226
+ });
227
+ });
228
+
229
+ describe('#queryRosters', () => {
230
+
231
+ it('makes the expected query', async () => {
232
+
233
+ webex.request.returns(Promise.resolve({
234
+ body: {
235
+ rosters: [{
236
+ locus: 'locus1'
237
+ }, {
238
+ locus: 'locus2'
239
+ }]
240
+ }
241
+ }));
242
+
243
+ breakouts.set('url', 'url');
244
+ breakouts.set('locusUrl', 'test');
245
+
246
+ breakouts.handleRosterUpdate = sinon.stub();
247
+
248
+ const result = await breakouts.queryRosters();
249
+
250
+ assert.calledOnceWithExactly(webex.request, {
251
+ uri: 'url/roster',
252
+ qs: { locusUrl: 'dGVzdA==' }
253
+ });
254
+ assert.calledTwice(breakouts.handleRosterUpdate);
255
+
256
+ assert.deepEqual(breakouts.handleRosterUpdate.getCall(0).args, ['locus1']);
257
+ assert.deepEqual(breakouts.handleRosterUpdate.getCall(1).args, ['locus2']);
258
+ });
259
+
260
+ it('logs the error if the query fails', async () => {
261
+ const error = new Error('something went wrong')
262
+ webex.request.rejects(error);
263
+ LoggerProxy.logger.error = sinon.stub();
264
+
265
+ breakouts.set('url', 'url');
266
+ breakouts.set('locusUrl', 'test');
267
+
268
+ breakouts.handleRosterUpdate = sinon.stub();
269
+
270
+ const result = await breakouts.queryRosters();
271
+ await testUtils.flushPromises();
272
+
273
+ assert.calledOnceWithExactly(webex.request, {
274
+ uri: 'url/roster',
275
+ qs: {locusUrl: 'dGVzdA=='},
276
+ });
277
+ assert.calledOnceWithExactly(
278
+ LoggerProxy.logger.error,
279
+ 'Meeting:breakouts#queryRosters failed',
280
+ error
281
+ );
282
+ });
283
+ });
284
+
285
+ describe('isInMainSession', () => {
286
+ it('returns true when sessionType is MAIN', () => {
287
+ assert.equal(breakouts.isInMainSession, false);
288
+ breakouts.set('sessionType', BREAKOUTS.SESSION_TYPES.MAIN)
289
+ assert.equal(breakouts.isInMainSession, true);
290
+ });
291
+ });
292
+ });
293
+ });
@@ -78,5 +78,25 @@ describe('plugin-meetings', () => {
78
78
 
79
79
  assert.equal(updates.hasEntryExitToneChanged, false);
80
80
  });
81
+
82
+ it('returns hasBreakoutChanged = true when it has changed', () => {
83
+ const newControls = {
84
+ breakout: 'breakout'
85
+ };
86
+
87
+ const {updates} = ControlsUtils.getControls({breakout: 'old breakout'}, newControls);
88
+
89
+ assert.equal(updates.hasBreakoutChanged, true);
90
+ });
91
+
92
+ it('returns hasBreakoutChanged = false when it has not changed', () => {
93
+ const newControls = {
94
+ breakout: 'breakout'
95
+ };
96
+
97
+ const {updates} = ControlsUtils.getControls({breakout: 'breakout'}, newControls);
98
+
99
+ assert.equal(updates.hasBreakoutChanged, false);
100
+ });
81
101
  });
82
102
  });
@@ -277,6 +277,25 @@ describe('plugin-meetings', () => {
277
277
  );
278
278
  });
279
279
 
280
+ it('should update the breakout state', () => {
281
+ locusInfo.emitScoped = sinon.stub();
282
+ newControls.breakout = 'new breakout';
283
+
284
+ locusInfo.updateControls(newControls);
285
+
286
+ assert.calledWith(
287
+ locusInfo.emitScoped,
288
+ {
289
+ file: 'locus-info',
290
+ function: 'updateControls',
291
+ },
292
+ LOCUSINFO.EVENTS.CONTROLS_MEETING_BREAKOUT_UPDATED,
293
+ {
294
+ breakout: 'new breakout'
295
+ }
296
+ );
297
+ });
298
+
280
299
  it('should update the transcript state', () => {
281
300
  locusInfo.emitScoped = sinon.stub();
282
301
  locusInfo.controls = {
@@ -664,6 +683,41 @@ describe('plugin-meetings', () => {
664
683
  );
665
684
  });
666
685
 
686
+ it('should trigger SELF_MEETING_BREAKOUTS_CHANGED when breakouts changed', () => {
687
+ locusInfo.self = self;
688
+ const selfWithBreakoutsChanged = cloneDeep(self);
689
+
690
+ selfWithBreakoutsChanged.controls.breakout.sessions.active[0].name = 'new name';
691
+
692
+ locusInfo.emitScoped = sinon.stub();
693
+ locusInfo.updateSelf(selfWithBreakoutsChanged, []);
694
+
695
+ assert.calledWith(
696
+ locusInfo.emitScoped,
697
+ {
698
+ file: 'locus-info',
699
+ function: 'updateSelf',
700
+ },
701
+ LOCUSINFO.EVENTS.SELF_MEETING_BREAKOUTS_CHANGED,
702
+ {
703
+ breakoutSessions: {
704
+ active: [{
705
+ name: 'new name',
706
+ groupId: '0e73abb8-5584-49d8-be8d-806d2a8247ca',
707
+ sessionId: '1cf41ab1-2e57-4d95-b7e9-5613acddfb0f',
708
+ sessionType: 'BREAKOUT'
709
+ }],
710
+ allowed: [{
711
+ name: 'Breakout session 2',
712
+ groupId: '0e73abb8-5584-49d8-be8d-806d2a8247ca',
713
+ sessionId: '1cf41ab1-2e57-4d95-b7e9-5613acddfb0f',
714
+ sessionType: 'BREAKOUT'
715
+ }]
716
+ }
717
+ }
718
+ );
719
+ });
720
+
667
721
  it('should trigger SELF_REMOTE_MUTE_STATUS_UPDATED if muted and disallowUnmute changed', () => {
668
722
  locusInfo.self = self;
669
723
  const selfWithMutedByOthersAndDissalowUnmute = cloneDeep(self);
@@ -1147,6 +1201,55 @@ describe('plugin-meetings', () => {
1147
1201
  assert.isFunction(locusParser.onDeltaAction);
1148
1202
  });
1149
1203
 
1204
+ it('#updateLocusInfo ignores breakout LEFT message', () => {
1205
+ const newLocus = {
1206
+ self: {
1207
+ reason: 'MOVED',
1208
+ state: 'LEFT'
1209
+ }
1210
+ };
1211
+
1212
+ locusInfo.updateControls = sinon.stub();
1213
+ locusInfo.updateConversationUrl = sinon.stub();
1214
+ locusInfo.updateCreated = sinon.stub();
1215
+ locusInfo.updateFullState = sinon.stub();
1216
+ locusInfo.updateHostInfo = sinon.stub();
1217
+ locusInfo.updateMeetingInfo = sinon.stub();
1218
+ locusInfo.updateMediaShares = sinon.stub();
1219
+ locusInfo.updateParticipantsUrl = sinon.stub();
1220
+ locusInfo.updateReplace = sinon.stub();
1221
+ locusInfo.updateSelf = sinon.stub();
1222
+ locusInfo.updateLocusUrl = sinon.stub();
1223
+ locusInfo.updateAclUrl = sinon.stub();
1224
+ locusInfo.updateBasequence = sinon.stub();
1225
+ locusInfo.updateSequence = sinon.stub();
1226
+ locusInfo.updateMemberShip = sinon.stub();
1227
+ locusInfo.updateIdentifiers = sinon.stub();
1228
+ locusInfo.updateEmbeddedApps = sinon.stub();
1229
+ locusInfo.compareAndUpdate = sinon.stub();
1230
+
1231
+ locusInfo.updateLocusInfo(newLocus);
1232
+
1233
+ assert.notCalled(locusInfo.updateControls);
1234
+ assert.notCalled(locusInfo.updateConversationUrl);
1235
+ assert.notCalled(locusInfo.updateCreated);
1236
+ assert.notCalled(locusInfo.updateFullState);
1237
+ assert.notCalled(locusInfo.updateHostInfo);
1238
+ assert.notCalled(locusInfo.updateMeetingInfo);
1239
+ assert.notCalled(locusInfo.updateMediaShares);
1240
+ assert.notCalled(locusInfo.updateParticipantsUrl);
1241
+ assert.notCalled(locusInfo.updateReplace);
1242
+ assert.notCalled(locusInfo.updateSelf);
1243
+ assert.notCalled(locusInfo.updateLocusUrl);
1244
+ assert.notCalled(locusInfo.updateAclUrl);
1245
+ assert.notCalled(locusInfo.updateBasequence);
1246
+ assert.notCalled(locusInfo.updateSequence);
1247
+ assert.notCalled(locusInfo.updateMemberShip);
1248
+ assert.notCalled(locusInfo.updateIdentifiers);
1249
+ assert.notCalled(locusInfo.updateEmbeddedApps);
1250
+ assert.notCalled(locusInfo.compareAndUpdate);
1251
+ });
1252
+
1150
1253
  it('onFullLocus() updates the working-copy of locus parser', () => {
1151
1254
  const eventType = 'fakeEvent';
1152
1255
 
@@ -109,6 +109,31 @@ export const self = {
109
109
  requestedToUnmute: false,
110
110
  meta: {},
111
111
  },
112
+ breakout: {
113
+ sessions: {
114
+ active: [
115
+ {
116
+ name: 'Breakout session 2',
117
+ groupId: '0e73abb8-5584-49d8-be8d-806d2a8247ca',
118
+ sessionId: '1cf41ab1-2e57-4d95-b7e9-5613acddfb0f',
119
+ sessionType: 'BREAKOUT'
120
+ },
121
+ ],
122
+ allowed: [
123
+ {
124
+ name: 'Breakout session 2',
125
+ groupId: '0e73abb8-5584-49d8-be8d-806d2a8247ca',
126
+ sessionId: '1cf41ab1-2e57-4d95-b7e9-5613acddfb0f',
127
+ sessionType: 'BREAKOUT'
128
+ },
129
+ ]
130
+ },
131
+ meta: {
132
+ modifiedBy: '347ef89e-e1be-40a3-849c-731bdd935e62',
133
+ lastModified: '2023-01-10T10:10:06.813Z',
134
+ readOnly: true
135
+ }
136
+ },
112
137
  localRecord: {
113
138
  recording: false,
114
139
  },