@webex/plugin-meetings 3.11.0 → 3.12.0-next.2
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.
- package/dist/aiEnableRequest/index.js +184 -0
- package/dist/aiEnableRequest/index.js.map +1 -0
- package/dist/aiEnableRequest/utils.js +36 -0
- package/dist/aiEnableRequest/utils.js.map +1 -0
- package/dist/annotation/index.js +14 -5
- package/dist/annotation/index.js.map +1 -1
- package/dist/breakouts/breakout.js +1 -1
- package/dist/breakouts/index.js +1 -1
- package/dist/config.js +7 -2
- package/dist/config.js.map +1 -1
- package/dist/constants.js +28 -6
- package/dist/constants.js.map +1 -1
- package/dist/hashTree/constants.js +3 -1
- package/dist/hashTree/constants.js.map +1 -1
- package/dist/hashTree/hashTree.js +18 -0
- package/dist/hashTree/hashTree.js.map +1 -1
- package/dist/hashTree/hashTreeParser.js +850 -410
- package/dist/hashTree/hashTreeParser.js.map +1 -1
- package/dist/hashTree/types.js +4 -2
- package/dist/hashTree/types.js.map +1 -1
- package/dist/hashTree/utils.js +10 -0
- package/dist/hashTree/utils.js.map +1 -1
- package/dist/index.js +11 -2
- package/dist/index.js.map +1 -1
- package/dist/interceptors/constant.js +12 -0
- package/dist/interceptors/constant.js.map +1 -0
- package/dist/interceptors/dataChannelAuthToken.js +290 -0
- package/dist/interceptors/dataChannelAuthToken.js.map +1 -0
- package/dist/interceptors/index.js +7 -0
- package/dist/interceptors/index.js.map +1 -1
- package/dist/interceptors/utils.js +27 -0
- package/dist/interceptors/utils.js.map +1 -0
- package/dist/interpretation/index.js +2 -2
- package/dist/interpretation/index.js.map +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/locus-info/controlsUtils.js +5 -3
- package/dist/locus-info/controlsUtils.js.map +1 -1
- package/dist/locus-info/index.js +522 -131
- package/dist/locus-info/index.js.map +1 -1
- package/dist/locus-info/selfUtils.js +1 -0
- package/dist/locus-info/selfUtils.js.map +1 -1
- package/dist/locus-info/types.js.map +1 -1
- package/dist/media/MediaConnectionAwaiter.js +57 -1
- package/dist/media/MediaConnectionAwaiter.js.map +1 -1
- package/dist/media/properties.js +4 -2
- package/dist/media/properties.js.map +1 -1
- package/dist/meeting/in-meeting-actions.js +7 -1
- package/dist/meeting/in-meeting-actions.js.map +1 -1
- package/dist/meeting/index.js +1173 -877
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/request.js +50 -0
- package/dist/meeting/request.js.map +1 -1
- package/dist/meeting/request.type.js.map +1 -1
- package/dist/meeting/util.js +133 -3
- package/dist/meeting/util.js.map +1 -1
- package/dist/meetings/index.js +117 -48
- package/dist/meetings/index.js.map +1 -1
- package/dist/member/index.js +10 -0
- package/dist/member/index.js.map +1 -1
- package/dist/member/util.js +10 -0
- package/dist/member/util.js.map +1 -1
- package/dist/metrics/constants.js +2 -1
- package/dist/metrics/constants.js.map +1 -1
- package/dist/multistream/mediaRequestManager.js +9 -60
- package/dist/multistream/mediaRequestManager.js.map +1 -1
- package/dist/multistream/remoteMediaManager.js +11 -0
- package/dist/multistream/remoteMediaManager.js.map +1 -1
- package/dist/reachability/index.js +18 -10
- package/dist/reachability/index.js.map +1 -1
- package/dist/reactions/reactions.type.js.map +1 -1
- package/dist/reconnection-manager/index.js +0 -1
- package/dist/reconnection-manager/index.js.map +1 -1
- package/dist/types/aiEnableRequest/index.d.ts +5 -0
- package/dist/types/aiEnableRequest/utils.d.ts +2 -0
- package/dist/types/config.d.ts +4 -0
- package/dist/types/constants.d.ts +23 -1
- package/dist/types/hashTree/constants.d.ts +1 -0
- package/dist/types/hashTree/hashTree.d.ts +7 -0
- package/dist/types/hashTree/hashTreeParser.d.ts +122 -14
- package/dist/types/hashTree/types.d.ts +3 -0
- package/dist/types/hashTree/utils.d.ts +6 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/interceptors/constant.d.ts +5 -0
- package/dist/types/interceptors/dataChannelAuthToken.d.ts +43 -0
- package/dist/types/interceptors/index.d.ts +2 -1
- package/dist/types/interceptors/utils.d.ts +1 -0
- package/dist/types/locus-info/index.d.ts +60 -8
- package/dist/types/locus-info/types.d.ts +7 -0
- package/dist/types/media/MediaConnectionAwaiter.d.ts +10 -1
- package/dist/types/media/properties.d.ts +2 -1
- package/dist/types/meeting/in-meeting-actions.d.ts +6 -0
- package/dist/types/meeting/index.d.ts +61 -7
- package/dist/types/meeting/request.d.ts +16 -1
- package/dist/types/meeting/request.type.d.ts +5 -0
- package/dist/types/meeting/util.d.ts +31 -0
- package/dist/types/meetings/index.d.ts +4 -2
- package/dist/types/member/index.d.ts +1 -0
- package/dist/types/member/util.d.ts +5 -0
- package/dist/types/metrics/constants.d.ts +1 -0
- package/dist/types/multistream/mediaRequestManager.d.ts +0 -23
- package/dist/types/reactions/reactions.type.d.ts +1 -0
- package/dist/types/webinar/utils.d.ts +6 -0
- package/dist/webinar/index.js +291 -91
- package/dist/webinar/index.js.map +1 -1
- package/dist/webinar/utils.js +25 -0
- package/dist/webinar/utils.js.map +1 -0
- package/package.json +24 -23
- package/src/aiEnableRequest/README.md +84 -0
- package/src/aiEnableRequest/index.ts +170 -0
- package/src/aiEnableRequest/utils.ts +25 -0
- package/src/annotation/index.ts +27 -7
- package/src/config.ts +4 -0
- package/src/constants.ts +29 -1
- package/src/hashTree/constants.ts +1 -0
- package/src/hashTree/hashTree.ts +17 -0
- package/src/hashTree/hashTreeParser.ts +745 -252
- package/src/hashTree/types.ts +4 -0
- package/src/hashTree/utils.ts +9 -0
- package/src/index.ts +8 -1
- package/src/interceptors/constant.ts +6 -0
- package/src/interceptors/dataChannelAuthToken.ts +170 -0
- package/src/interceptors/index.ts +2 -1
- package/src/interceptors/utils.ts +16 -0
- package/src/interpretation/index.ts +2 -2
- package/src/locus-info/controlsUtils.ts +11 -0
- package/src/locus-info/index.ts +579 -113
- package/src/locus-info/selfUtils.ts +1 -0
- package/src/locus-info/types.ts +8 -0
- package/src/media/MediaConnectionAwaiter.ts +41 -1
- package/src/media/properties.ts +3 -1
- package/src/meeting/in-meeting-actions.ts +12 -0
- package/src/meeting/index.ts +291 -76
- package/src/meeting/request.ts +42 -0
- package/src/meeting/request.type.ts +6 -0
- package/src/meeting/util.ts +160 -2
- package/src/meetings/index.ts +157 -44
- package/src/member/index.ts +10 -0
- package/src/member/util.ts +12 -0
- package/src/metrics/constants.ts +1 -0
- package/src/multistream/mediaRequestManager.ts +4 -54
- package/src/multistream/remoteMediaManager.ts +13 -0
- package/src/reachability/index.ts +9 -0
- package/src/reactions/reactions.type.ts +1 -0
- package/src/reconnection-manager/index.ts +0 -1
- package/src/webinar/index.ts +191 -6
- package/src/webinar/utils.ts +16 -0
- package/test/unit/spec/aiEnableRequest/index.ts +981 -0
- package/test/unit/spec/aiEnableRequest/utils.ts +130 -0
- package/test/unit/spec/annotation/index.ts +69 -7
- package/test/unit/spec/hashTree/hashTree.ts +66 -0
- package/test/unit/spec/hashTree/hashTreeParser.ts +2225 -189
- package/test/unit/spec/interceptors/dataChannelAuthToken.ts +210 -0
- package/test/unit/spec/interceptors/utils.ts +75 -0
- package/test/unit/spec/locus-info/controlsUtils.js +29 -0
- package/test/unit/spec/locus-info/index.js +1134 -55
- package/test/unit/spec/media/MediaConnectionAwaiter.ts +41 -1
- package/test/unit/spec/media/properties.ts +12 -3
- package/test/unit/spec/meeting/in-meeting-actions.ts +8 -2
- package/test/unit/spec/meeting/index.js +829 -115
- package/test/unit/spec/meeting/request.js +70 -0
- package/test/unit/spec/meeting/utils.js +438 -26
- package/test/unit/spec/meetings/index.js +653 -32
- package/test/unit/spec/member/index.js +28 -4
- package/test/unit/spec/member/util.js +65 -27
- package/test/unit/spec/multistream/mediaRequestManager.ts +2 -85
- package/test/unit/spec/multistream/remoteMediaManager.ts +30 -0
- package/test/unit/spec/reachability/index.ts +23 -0
- package/test/unit/spec/reconnection-manager/index.js +4 -8
- package/test/unit/spec/webinar/index.ts +474 -37
- package/test/unit/spec/webinar/utils.ts +39 -0
|
@@ -5,7 +5,7 @@ import {assert} from '@webex/test-helper-chai';
|
|
|
5
5
|
import MockWebex from '@webex/test-helper-mock-webex';
|
|
6
6
|
import testUtils from '../../../utils/testUtils';
|
|
7
7
|
import Meetings from '@webex/plugin-meetings';
|
|
8
|
-
import LocusInfo from '@webex/plugin-meetings/src/locus-info';
|
|
8
|
+
import LocusInfo, {createLocusFromHashTreeMessage, findMeetingForHashTreeMessage} from '@webex/plugin-meetings/src/locus-info';
|
|
9
9
|
import SelfUtils from '@webex/plugin-meetings/src/locus-info/selfUtils';
|
|
10
10
|
import InfoUtils from '@webex/plugin-meetings/src/locus-info/infoUtils';
|
|
11
11
|
import EmbeddedAppsUtils from '@webex/plugin-meetings/src/locus-info/embeddedAppsUtils';
|
|
@@ -29,7 +29,8 @@ import {
|
|
|
29
29
|
} from '../../../../src/constants';
|
|
30
30
|
|
|
31
31
|
import {self, selfWithInactivity} from './selfConstant';
|
|
32
|
-
import {
|
|
32
|
+
import {MEETING_REMOVED_REASON} from '@webex/plugin-meetings/src/constants';
|
|
33
|
+
import LoggerProxy from '@webex/plugin-meetings/src/common/logs/logger-proxy';
|
|
33
34
|
|
|
34
35
|
describe('plugin-meetings', () => {
|
|
35
36
|
describe('LocusInfo index', () => {
|
|
@@ -104,9 +105,10 @@ describe('plugin-meetings', () => {
|
|
|
104
105
|
});
|
|
105
106
|
|
|
106
107
|
const createHashTreeMessage = (visibleDataSets) => ({
|
|
108
|
+
locusUrl: 'http://locus-url.com',
|
|
107
109
|
locusStateElements: [
|
|
108
110
|
{
|
|
109
|
-
htMeta: {elementId: {type: '
|
|
111
|
+
htMeta: {elementId: {type: 'metadata'}},
|
|
110
112
|
data: {visibleDataSets},
|
|
111
113
|
},
|
|
112
114
|
],
|
|
@@ -114,6 +116,7 @@ describe('plugin-meetings', () => {
|
|
|
114
116
|
});
|
|
115
117
|
|
|
116
118
|
const createLocusWithVisibleDataSets = (visibleDataSets) => ({
|
|
119
|
+
url: 'http://locus-url.com',
|
|
117
120
|
self: {visibleDataSets},
|
|
118
121
|
participants: [],
|
|
119
122
|
links: {
|
|
@@ -136,8 +139,12 @@ describe('plugin-meetings', () => {
|
|
|
136
139
|
HashTreeParserStub,
|
|
137
140
|
sinon.match({
|
|
138
141
|
initialLocus: {
|
|
139
|
-
locus:
|
|
140
|
-
dataSets:
|
|
142
|
+
locus: null,
|
|
143
|
+
dataSets: hashTreeMessage.dataSets,
|
|
144
|
+
},
|
|
145
|
+
metadata: {
|
|
146
|
+
htMeta: hashTreeMessage.locusStateElements[0].htMeta,
|
|
147
|
+
visibleDataSets,
|
|
141
148
|
},
|
|
142
149
|
webexRequest: sinon.match.func,
|
|
143
150
|
locusInfoUpdateCallback: sinon.match.func,
|
|
@@ -169,11 +176,16 @@ describe('plugin-meetings', () => {
|
|
|
169
176
|
const visibleDataSets = ['dataset1', 'dataset2'];
|
|
170
177
|
const locus = createLocusWithVisibleDataSets(visibleDataSets);
|
|
171
178
|
const dataSets = [{name: 'dataset1', url: 'http://dataset-url.com'}];
|
|
179
|
+
const metadata = {
|
|
180
|
+
htMeta: {elementId: {type: 'metadata'}},
|
|
181
|
+
visibleDataSets,
|
|
182
|
+
};
|
|
172
183
|
|
|
173
184
|
await locusInfo.initialSetup({
|
|
174
185
|
trigger: 'join-response',
|
|
175
186
|
locus,
|
|
176
187
|
dataSets,
|
|
188
|
+
metadata,
|
|
177
189
|
});
|
|
178
190
|
|
|
179
191
|
assert.calledOnceWithExactly(
|
|
@@ -183,6 +195,7 @@ describe('plugin-meetings', () => {
|
|
|
183
195
|
locus,
|
|
184
196
|
dataSets,
|
|
185
197
|
},
|
|
198
|
+
metadata,
|
|
186
199
|
webexRequest: sinon.match.func,
|
|
187
200
|
locusInfoUpdateCallback: sinon.match.func,
|
|
188
201
|
debugId: sinon.match.string,
|
|
@@ -220,12 +233,13 @@ describe('plugin-meetings', () => {
|
|
|
220
233
|
HashTreeParserStub,
|
|
221
234
|
sinon.match({
|
|
222
235
|
initialLocus: {
|
|
223
|
-
locus:
|
|
236
|
+
locus: null,
|
|
224
237
|
dataSets: [],
|
|
225
238
|
},
|
|
226
239
|
webexRequest: sinon.match.func,
|
|
227
240
|
locusInfoUpdateCallback: sinon.match.func,
|
|
228
241
|
debugId: sinon.match.string,
|
|
242
|
+
metadata: null,
|
|
229
243
|
})
|
|
230
244
|
);
|
|
231
245
|
assert.calledOnceWithExactly(mockHashTreeParser.initializeFromGetLociResponse, locus);
|
|
@@ -249,6 +263,30 @@ describe('plugin-meetings', () => {
|
|
|
249
263
|
assert.isTrue(locusInfo.emitChange);
|
|
250
264
|
});
|
|
251
265
|
|
|
266
|
+
it('throws if called with "locus-message" and Metadata object without visibleDataSets', async () => {
|
|
267
|
+
const hashTreeMessage = {
|
|
268
|
+
locusStateElements: [
|
|
269
|
+
{
|
|
270
|
+
htMeta: {elementId: {type: 'Metadata'}},
|
|
271
|
+
data: {},
|
|
272
|
+
},
|
|
273
|
+
],
|
|
274
|
+
dataSets: [{name: 'dataset1', url: 'test-url'}],
|
|
275
|
+
};
|
|
276
|
+
try {
|
|
277
|
+
await locusInfo.initialSetup({
|
|
278
|
+
trigger: 'locus-message',
|
|
279
|
+
hashTreeMessage,
|
|
280
|
+
});
|
|
281
|
+
assert.fail('should have thrown an error');
|
|
282
|
+
} catch (error) {
|
|
283
|
+
assert.equal(
|
|
284
|
+
error.message,
|
|
285
|
+
'Metadata object with visibleDataSets is missing in the message'
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
|
|
252
290
|
describe('should setup correct locusInfoUpdateCallback when creating HashTreeParser', () => {
|
|
253
291
|
const OBJECTS_UPDATED = HashTreeParserModule.LocusInfoUpdateType.OBJECTS_UPDATED;
|
|
254
292
|
const MEETING_ENDED = HashTreeParserModule.LocusInfoUpdateType.MEETING_ENDED;
|
|
@@ -263,10 +301,11 @@ describe('plugin-meetings', () => {
|
|
|
263
301
|
await locusInfo.initialSetup({
|
|
264
302
|
trigger: 'locus-message',
|
|
265
303
|
hashTreeMessage: {
|
|
304
|
+
locusUrl: 'fake-locus-url',
|
|
266
305
|
locusStateElements: [
|
|
267
306
|
{
|
|
268
|
-
htMeta: {elementId: {type: '
|
|
269
|
-
data: {visibleDataSets: ['dataset1']},
|
|
307
|
+
htMeta: {elementId: {type: 'Metadata'}},
|
|
308
|
+
data: {visibleDataSets: [{name: 'dataset1', url: 'test-url'}]},
|
|
270
309
|
},
|
|
271
310
|
],
|
|
272
311
|
dataSets: [{name: 'dataset1', url: 'test-url'}],
|
|
@@ -293,6 +332,16 @@ describe('plugin-meetings', () => {
|
|
|
293
332
|
htMeta: {elementId: {type: 'mediashare', id: 'fake-ht-mediaShare-2', version: 1}},
|
|
294
333
|
},
|
|
295
334
|
];
|
|
335
|
+
locusInfo.embeddedApps = [
|
|
336
|
+
{
|
|
337
|
+
id: 'fake-embedded-app-1',
|
|
338
|
+
htMeta: {elementId: {type: 'embeddedapp', id: 'fake-ht-embeddedApp-1', version: 1}},
|
|
339
|
+
},
|
|
340
|
+
{
|
|
341
|
+
id: 'fake-embedded-app-2',
|
|
342
|
+
htMeta: {elementId: {type: 'embeddedapp', id: 'fake-ht-embeddedApp-2', version: 1}},
|
|
343
|
+
},
|
|
344
|
+
];
|
|
296
345
|
locusInfo.meetings = {id: 'fake-meetings'};
|
|
297
346
|
locusInfo.participants = [
|
|
298
347
|
{id: 'fake-participant-1', name: 'Participant One'},
|
|
@@ -311,6 +360,13 @@ describe('plugin-meetings', () => {
|
|
|
311
360
|
locusInfo.url = 'fake-locus-url';
|
|
312
361
|
locusInfo.htMeta = {elementId: {type: 'locus', id: 'fake-ht-locus-id', version: 1}};
|
|
313
362
|
|
|
363
|
+
const createdHashTreeParser = locusInfo.hashTreeParsers.get('fake-locus-url');
|
|
364
|
+
|
|
365
|
+
assert.isDefined(createdHashTreeParser);
|
|
366
|
+
// this flag would have been set to true on the first callback triggered by initialSetup() wa called earlier
|
|
367
|
+
// it's not because we're mocking HashTreeParser, so we have to set it manually here
|
|
368
|
+
createdHashTreeParser.initializedFromHashTree = true;
|
|
369
|
+
|
|
314
370
|
// setup the default expected locus info state that each test builds upon
|
|
315
371
|
expectedLocusInfo = {
|
|
316
372
|
controls: {id: 'fake-controls'},
|
|
@@ -328,8 +384,18 @@ describe('plugin-meetings', () => {
|
|
|
328
384
|
htMeta: {elementId: {type: 'mediashare', id: 'fake-ht-mediaShare-2', version: 1}},
|
|
329
385
|
},
|
|
330
386
|
],
|
|
387
|
+
embeddedApps: [
|
|
388
|
+
{
|
|
389
|
+
id: 'fake-embedded-app-1',
|
|
390
|
+
htMeta: {elementId: {type: 'embeddedapp', id: 'fake-ht-embeddedApp-1', version: 1}},
|
|
391
|
+
},
|
|
392
|
+
{
|
|
393
|
+
id: 'fake-embedded-app-2',
|
|
394
|
+
htMeta: {elementId: {type: 'embeddedapp', id: 'fake-ht-embeddedApp-2', version: 1}},
|
|
395
|
+
},
|
|
396
|
+
],
|
|
331
397
|
meetings: {id: 'fake-meetings'},
|
|
332
|
-
jsSdkMeta: {removedParticipantIds: []},
|
|
398
|
+
jsSdkMeta: {removedParticipantIds: [], forceReplaceMembers: false},
|
|
333
399
|
participants: [], // empty means there were no participant updates
|
|
334
400
|
replaces: {id: 'fake-replaces'},
|
|
335
401
|
self: {id: 'fake-self'},
|
|
@@ -504,11 +570,12 @@ describe('plugin-meetings', () => {
|
|
|
504
570
|
self: {id: 'fake-self'},
|
|
505
571
|
links: {id: 'fake-links'},
|
|
506
572
|
mediaShares: expectedLocusInfo.mediaShares,
|
|
573
|
+
embeddedApps: expectedLocusInfo.embeddedApps,
|
|
507
574
|
// and now the new fields
|
|
508
575
|
...newLocus,
|
|
509
576
|
htMeta: newLocusHtMeta,
|
|
510
577
|
participants: [], // empty means there were no participant updates
|
|
511
|
-
jsSdkMeta: {removedParticipantIds: []}, // no participants were removed
|
|
578
|
+
jsSdkMeta: {removedParticipantIds: [], forceReplaceMembers: false}, // no participants were removed
|
|
512
579
|
});
|
|
513
580
|
});
|
|
514
581
|
|
|
@@ -536,6 +603,7 @@ describe('plugin-meetings', () => {
|
|
|
536
603
|
self: 'new-self',
|
|
537
604
|
participants: 'new-participants',
|
|
538
605
|
mediaShares: 'new-mediaShares',
|
|
606
|
+
embeddedApps: 'new-embeddedApps',
|
|
539
607
|
},
|
|
540
608
|
},
|
|
541
609
|
],
|
|
@@ -551,8 +619,9 @@ describe('plugin-meetings', () => {
|
|
|
551
619
|
self: {id: 'fake-self'},
|
|
552
620
|
links: {id: 'fake-links'},
|
|
553
621
|
mediaShares: expectedLocusInfo.mediaShares,
|
|
622
|
+
embeddedApps: expectedLocusInfo.embeddedApps,
|
|
554
623
|
participants: [], // empty means there were no participant updates
|
|
555
|
-
jsSdkMeta: {removedParticipantIds: []}, // no participants were removed
|
|
624
|
+
jsSdkMeta: {removedParticipantIds: [], forceReplaceMembers: false}, // no participants were removed
|
|
556
625
|
...newLocus,
|
|
557
626
|
htMeta: newLocusHtMeta,
|
|
558
627
|
});
|
|
@@ -586,11 +655,12 @@ describe('plugin-meetings', () => {
|
|
|
586
655
|
self: {id: 'fake-self'},
|
|
587
656
|
links: {id: 'fake-links'},
|
|
588
657
|
mediaShares: expectedLocusInfo.mediaShares,
|
|
658
|
+
embeddedApps: expectedLocusInfo.embeddedApps,
|
|
589
659
|
// and now the new fields
|
|
590
660
|
...newLocus,
|
|
591
661
|
htMeta: newLocusHtMeta,
|
|
592
662
|
participants: [], // empty means there were no participant updates
|
|
593
|
-
jsSdkMeta: {removedParticipantIds: []}, // no participants were removed
|
|
663
|
+
jsSdkMeta: {removedParticipantIds: [], forceReplaceMembers: false}, // no participants were removed
|
|
594
664
|
});
|
|
595
665
|
});
|
|
596
666
|
|
|
@@ -678,7 +748,7 @@ describe('plugin-meetings', () => {
|
|
|
678
748
|
assert.calledOnceWithExactly(onDeltaLocusStub, {
|
|
679
749
|
...expectedLocusInfo,
|
|
680
750
|
participants: [newParticipant, updatedParticipant2],
|
|
681
|
-
jsSdkMeta: {removedParticipantIds: ['fake-participant-1']},
|
|
751
|
+
jsSdkMeta: {removedParticipantIds: ['fake-participant-1'], forceReplaceMembers: false},
|
|
682
752
|
});
|
|
683
753
|
// and that the hashTreeObjectId2ParticipantId map was updated correctly
|
|
684
754
|
assert.isUndefined(locusInfo.hashTreeObjectId2ParticipantId.get('fake-ht-participant-1'));
|
|
@@ -725,6 +795,39 @@ describe('plugin-meetings', () => {
|
|
|
725
795
|
});
|
|
726
796
|
});
|
|
727
797
|
|
|
798
|
+
it('should process locus update correctly when called with updated EMBEDDEDAPP objects', () => {
|
|
799
|
+
const newEmbeddedApp = {
|
|
800
|
+
id: 'new-embedded-app-3',
|
|
801
|
+
htMeta: {elementId: {type: 'embeddedapp', id: 'fake-ht-embeddedApp-3', version: 100}},
|
|
802
|
+
};
|
|
803
|
+
const updatedEmbeddedApp2 = {
|
|
804
|
+
id: 'fake-embedded-app-2',
|
|
805
|
+
someNewProp: 'newValue',
|
|
806
|
+
htMeta: {elementId: {type: 'embeddedapp', id: 'fake-ht-embeddedApp-2', version: 100}},
|
|
807
|
+
};
|
|
808
|
+
// simulate an update from the HashTreeParser (normally this would be triggered by incoming locus messages)
|
|
809
|
+
// with 1 embedded app added, 1 updated, and 1 removed
|
|
810
|
+
locusInfoUpdateCallback(OBJECTS_UPDATED, {
|
|
811
|
+
updatedObjects: [
|
|
812
|
+
{htMeta: {elementId: {type: 'embeddedapp', id: 'fake-ht-embeddedApp-1'}}, data: null},
|
|
813
|
+
{
|
|
814
|
+
htMeta: {elementId: {type: 'embeddedapp', id: 'fake-ht-embeddedApp-2'}},
|
|
815
|
+
data: updatedEmbeddedApp2,
|
|
816
|
+
},
|
|
817
|
+
{
|
|
818
|
+
htMeta: {elementId: {type: 'embeddedapp', id: 'fake-ht-embeddedApp-3'}},
|
|
819
|
+
data: newEmbeddedApp,
|
|
820
|
+
},
|
|
821
|
+
],
|
|
822
|
+
});
|
|
823
|
+
|
|
824
|
+
// check onDeltaLocus() was called with correctly updated locus info
|
|
825
|
+
assert.calledOnceWithExactly(onDeltaLocusStub, {
|
|
826
|
+
...expectedLocusInfo,
|
|
827
|
+
embeddedApps: [updatedEmbeddedApp2, newEmbeddedApp],
|
|
828
|
+
});
|
|
829
|
+
});
|
|
830
|
+
|
|
728
831
|
it('should process locus update correctly when called with a combination of various updated objects', () => {
|
|
729
832
|
const newSelf = {
|
|
730
833
|
id: 'new-self',
|
|
@@ -841,6 +944,78 @@ describe('plugin-meetings', () => {
|
|
|
841
944
|
MEETING_REMOVED_REASON.SELF_REMOVED
|
|
842
945
|
);
|
|
843
946
|
});
|
|
947
|
+
|
|
948
|
+
// this could happen if meeting gets destroyed while we're doing some async hash tree operation like a sync
|
|
949
|
+
it('should handle MEETING_ENDED correctly when meeting is not found in the collection', () => {
|
|
950
|
+
const collectionGetStub = sinon
|
|
951
|
+
.stub(locusInfo.webex.meetings.meetingCollection, 'get')
|
|
952
|
+
.returns(null);
|
|
953
|
+
const destroyStub = sinon.stub(locusInfo.webex.meetings, 'destroy');
|
|
954
|
+
|
|
955
|
+
// simulate an update from the HashTreeParser (normally this would be triggered by incoming locus messages)
|
|
956
|
+
locusInfoUpdateCallback(MEETING_ENDED);
|
|
957
|
+
|
|
958
|
+
assert.calledOnceWithExactly(collectionGetStub, locusInfo.meetingId);
|
|
959
|
+
assert.notCalled(destroyStub);
|
|
960
|
+
});
|
|
961
|
+
|
|
962
|
+
it('should set forceReplaceMembers to true on the first update for a locusUrl (initializedFromHashTree is false)', () => {
|
|
963
|
+
const createdHashTreeParser = locusInfo.hashTreeParsers.get('fake-locus-url');
|
|
964
|
+
createdHashTreeParser.initializedFromHashTree = false;
|
|
965
|
+
|
|
966
|
+
locusInfoUpdateCallback(OBJECTS_UPDATED, {
|
|
967
|
+
updatedObjects: [
|
|
968
|
+
{
|
|
969
|
+
htMeta: {elementId: {type: 'self'}},
|
|
970
|
+
data: {id: 'new-self'},
|
|
971
|
+
},
|
|
972
|
+
],
|
|
973
|
+
});
|
|
974
|
+
|
|
975
|
+
assert.calledOnce(onDeltaLocusStub);
|
|
976
|
+
assert.equal(onDeltaLocusStub.firstCall.args[0].jsSdkMeta.forceReplaceMembers, true);
|
|
977
|
+
assert.isTrue(createdHashTreeParser.initializedFromHashTree);
|
|
978
|
+
});
|
|
979
|
+
|
|
980
|
+
it('should set forceReplaceMembers to false on subsequent updates (initializedFromHashTree is true)', () => {
|
|
981
|
+
locusInfoUpdateCallback(OBJECTS_UPDATED, {
|
|
982
|
+
updatedObjects: [
|
|
983
|
+
{
|
|
984
|
+
htMeta: {elementId: {type: 'self'}},
|
|
985
|
+
data: {id: 'new-self'},
|
|
986
|
+
},
|
|
987
|
+
],
|
|
988
|
+
});
|
|
989
|
+
|
|
990
|
+
assert.calledOnce(onDeltaLocusStub);
|
|
991
|
+
assert.equal(onDeltaLocusStub.firstCall.args[0].jsSdkMeta.forceReplaceMembers, false);
|
|
992
|
+
});
|
|
993
|
+
|
|
994
|
+
it('should copy participant data to self when participant matches self identity and state is LEFT with reason MOVED', () => {
|
|
995
|
+
locusInfo.self = {id: 'fake-self', identity: 'user-123'};
|
|
996
|
+
|
|
997
|
+
locusInfoUpdateCallback(OBJECTS_UPDATED, {
|
|
998
|
+
updatedObjects: [
|
|
999
|
+
{
|
|
1000
|
+
htMeta: {elementId: {type: 'participant', id: 99}},
|
|
1001
|
+
data: {
|
|
1002
|
+
id: 'participant-matching-self',
|
|
1003
|
+
identity: 'user-123',
|
|
1004
|
+
state: 'LEFT',
|
|
1005
|
+
reason: 'MOVED',
|
|
1006
|
+
roles: ['MODERATOR'],
|
|
1007
|
+
},
|
|
1008
|
+
},
|
|
1009
|
+
],
|
|
1010
|
+
});
|
|
1011
|
+
|
|
1012
|
+
assert.calledOnce(onDeltaLocusStub);
|
|
1013
|
+
const passedLocus = onDeltaLocusStub.firstCall.args[0];
|
|
1014
|
+
|
|
1015
|
+
assert.equal(passedLocus.self.identity, 'user-123');
|
|
1016
|
+
assert.equal(passedLocus.self.state, 'LEFT');
|
|
1017
|
+
assert.equal(passedLocus.self.reason, 'MOVED');
|
|
1018
|
+
});
|
|
844
1019
|
});
|
|
845
1020
|
});
|
|
846
1021
|
|
|
@@ -1076,7 +1251,7 @@ describe('plugin-meetings', () => {
|
|
|
1076
1251
|
it('should trigger the CONTROLS_POLLING_QA_CHANGED event when necessary', () => {
|
|
1077
1252
|
locusInfo.controls = {};
|
|
1078
1253
|
locusInfo.emitScoped = sinon.stub();
|
|
1079
|
-
newControls.pollingQAControl = {
|
|
1254
|
+
newControls.pollingQAControl = {enabled: true};
|
|
1080
1255
|
locusInfo.updateControls(newControls);
|
|
1081
1256
|
|
|
1082
1257
|
assert.calledWith(
|
|
@@ -1366,6 +1541,34 @@ describe('plugin-meetings', () => {
|
|
|
1366
1541
|
);
|
|
1367
1542
|
});
|
|
1368
1543
|
|
|
1544
|
+
it('should emit CONTROLS_AI_SUMMARY_NOTIFICATION_UPDATED when aiSummaryNotification changes', () => {
|
|
1545
|
+
locusInfo.emitScoped = sinon.stub();
|
|
1546
|
+
locusInfo.controls = {
|
|
1547
|
+
transcribe: {
|
|
1548
|
+
transcribing: false,
|
|
1549
|
+
caption: false,
|
|
1550
|
+
aiSummaryNotification: false,
|
|
1551
|
+
},
|
|
1552
|
+
};
|
|
1553
|
+
newControls.transcribe.transcribing = false;
|
|
1554
|
+
newControls.transcribe.caption = false;
|
|
1555
|
+
newControls.transcribe.aiSummaryNotification = true;
|
|
1556
|
+
|
|
1557
|
+
locusInfo.updateControls(newControls);
|
|
1558
|
+
|
|
1559
|
+
assert.calledWith(
|
|
1560
|
+
locusInfo.emitScoped,
|
|
1561
|
+
{
|
|
1562
|
+
file: 'locus-info',
|
|
1563
|
+
function: 'updateControls',
|
|
1564
|
+
},
|
|
1565
|
+
LOCUSINFO.EVENTS.CONTROLS_AI_SUMMARY_NOTIFICATION_UPDATED,
|
|
1566
|
+
{
|
|
1567
|
+
aiSummaryNotification: true,
|
|
1568
|
+
}
|
|
1569
|
+
);
|
|
1570
|
+
});
|
|
1571
|
+
|
|
1369
1572
|
it('should update the transcribe spoken language', () => {
|
|
1370
1573
|
locusInfo.emitScoped = sinon.stub();
|
|
1371
1574
|
locusInfo.controls = {
|
|
@@ -1631,7 +1834,6 @@ describe('plugin-meetings', () => {
|
|
|
1631
1834
|
);
|
|
1632
1835
|
});
|
|
1633
1836
|
|
|
1634
|
-
|
|
1635
1837
|
it('should call with participant display name', () => {
|
|
1636
1838
|
const failureParticipant = [
|
|
1637
1839
|
{
|
|
@@ -1656,7 +1858,7 @@ describe('plugin-meetings', () => {
|
|
|
1656
1858
|
displayName: 'Test User',
|
|
1657
1859
|
}
|
|
1658
1860
|
);
|
|
1659
|
-
})
|
|
1861
|
+
});
|
|
1660
1862
|
});
|
|
1661
1863
|
|
|
1662
1864
|
describe('#updateSelf', () => {
|
|
@@ -2457,8 +2659,8 @@ describe('plugin-meetings', () => {
|
|
|
2457
2659
|
{
|
|
2458
2660
|
isInitializing: !self,
|
|
2459
2661
|
}
|
|
2460
|
-
|
|
2461
|
-
|
|
2662
|
+
);
|
|
2663
|
+
});
|
|
2462
2664
|
|
|
2463
2665
|
const checkMeetingInfoUpdatedCalled = (expected, payload) => {
|
|
2464
2666
|
const expectedArgs = [
|
|
@@ -2835,6 +3037,333 @@ describe('plugin-meetings', () => {
|
|
|
2835
3037
|
});
|
|
2836
3038
|
});
|
|
2837
3039
|
|
|
3040
|
+
describe('#createHashTreeParser', () => {
|
|
3041
|
+
let HashTreeParserStub;
|
|
3042
|
+
|
|
3043
|
+
beforeEach(() => {
|
|
3044
|
+
HashTreeParserStub = sinon
|
|
3045
|
+
.stub(HashTreeParserModule, 'default')
|
|
3046
|
+
.returns({
|
|
3047
|
+
initializeFromMessage: sinon.stub().resolves(),
|
|
3048
|
+
initializeFromGetLociResponse: sinon.stub().resolves(),
|
|
3049
|
+
state: 'active',
|
|
3050
|
+
stop: sinon.stub(),
|
|
3051
|
+
handleMessage: sinon.stub(),
|
|
3052
|
+
});
|
|
3053
|
+
});
|
|
3054
|
+
|
|
3055
|
+
const setupParserViaInitialSetup = async (locusUrl = 'http://locus-url-A.com') => {
|
|
3056
|
+
await locusInfo.initialSetup({
|
|
3057
|
+
trigger: 'locus-message',
|
|
3058
|
+
hashTreeMessage: {
|
|
3059
|
+
locusUrl,
|
|
3060
|
+
locusStateElements: [
|
|
3061
|
+
{
|
|
3062
|
+
htMeta: {elementId: {type: 'Metadata'}},
|
|
3063
|
+
data: {visibleDataSets: [{name: 'dataset1', url: 'test-url'}]},
|
|
3064
|
+
},
|
|
3065
|
+
],
|
|
3066
|
+
dataSets: [{name: 'dataset1', url: 'test-url'}],
|
|
3067
|
+
},
|
|
3068
|
+
});
|
|
3069
|
+
};
|
|
3070
|
+
|
|
3071
|
+
it('should stop existing active parsers when creating a new one', async () => {
|
|
3072
|
+
await setupParserViaInitialSetup('http://locus-url-A.com');
|
|
3073
|
+
|
|
3074
|
+
const firstParser = locusInfo.hashTreeParsers.get('http://locus-url-A.com').parser;
|
|
3075
|
+
|
|
3076
|
+
await setupParserViaInitialSetup('http://locus-url-B.com');
|
|
3077
|
+
|
|
3078
|
+
assert.calledOnce(firstParser.stop);
|
|
3079
|
+
});
|
|
3080
|
+
|
|
3081
|
+
it('should set replacedAt on existing entries when replacedAt is provided', async () => {
|
|
3082
|
+
await setupParserViaInitialSetup('http://locus-url-A.com');
|
|
3083
|
+
|
|
3084
|
+
// Call createHashTreeParser with replacedAt via parse -> handleHashTreeParserSwitch
|
|
3085
|
+
// which calls createHashTreeParser with replacedAt from the self element
|
|
3086
|
+
locusInfo.webex.internal.device.url = 'http://device-url.com';
|
|
3087
|
+
const message = {
|
|
3088
|
+
locusUrl: 'http://locus-url-B.com',
|
|
3089
|
+
locusStateElements: [
|
|
3090
|
+
{
|
|
3091
|
+
htMeta: {elementId: {type: 'Metadata'}},
|
|
3092
|
+
data: {visibleDataSets: [{name: 'dataset1', url: 'test-url'}]},
|
|
3093
|
+
},
|
|
3094
|
+
{
|
|
3095
|
+
htMeta: {elementId: {type: 'Self'}},
|
|
3096
|
+
data: {
|
|
3097
|
+
devices: [{url: 'http://device-url.com', replaces: [{locusUrl: 'http://locus-url-A.com', replacedAt: '2026-01-01T00:00:00Z'}]}],
|
|
3098
|
+
},
|
|
3099
|
+
},
|
|
3100
|
+
],
|
|
3101
|
+
dataSets: [{name: 'dataset1', url: 'test-url'}],
|
|
3102
|
+
};
|
|
3103
|
+
|
|
3104
|
+
locusInfo.parse(mockMeeting, {
|
|
3105
|
+
eventType: LOCUSEVENT.HASH_TREE_DATA_UPDATED,
|
|
3106
|
+
stateElementsMessage: message,
|
|
3107
|
+
});
|
|
3108
|
+
|
|
3109
|
+
assert.equal(locusInfo.hashTreeParsers.get('http://locus-url-A.com').replacedAt, '2026-01-01T00:00:00Z');
|
|
3110
|
+
});
|
|
3111
|
+
|
|
3112
|
+
it('should not set replacedAt on existing entries when replacedAt is not provided', async () => {
|
|
3113
|
+
await setupParserViaInitialSetup('http://locus-url-A.com');
|
|
3114
|
+
|
|
3115
|
+
await setupParserViaInitialSetup('http://locus-url-B.com');
|
|
3116
|
+
|
|
3117
|
+
assert.isUndefined(locusInfo.hashTreeParsers.get('http://locus-url-A.com').replacedAt);
|
|
3118
|
+
});
|
|
3119
|
+
|
|
3120
|
+
it('should store the new parser in hashTreeParsers map with the correct locusUrl key', async () => {
|
|
3121
|
+
await setupParserViaInitialSetup('http://locus-url-A.com');
|
|
3122
|
+
|
|
3123
|
+
assert.isTrue(locusInfo.hashTreeParsers.has('http://locus-url-A.com'));
|
|
3124
|
+
assert.isDefined(locusInfo.hashTreeParsers.get('http://locus-url-A.com').parser);
|
|
3125
|
+
});
|
|
3126
|
+
|
|
3127
|
+
it('should clear hashTreeObjectId2ParticipantId when creating a new parser', async () => {
|
|
3128
|
+
await setupParserViaInitialSetup('http://locus-url-A.com');
|
|
3129
|
+
locusInfo.hashTreeObjectId2ParticipantId.set(1, 'participant-1');
|
|
3130
|
+
|
|
3131
|
+
await setupParserViaInitialSetup('http://locus-url-B.com');
|
|
3132
|
+
|
|
3133
|
+
assert.equal(locusInfo.hashTreeObjectId2ParticipantId.size, 0);
|
|
3134
|
+
});
|
|
3135
|
+
|
|
3136
|
+
it('should not stop already stopped parsers', async () => {
|
|
3137
|
+
await setupParserViaInitialSetup('http://locus-url-A.com');
|
|
3138
|
+
const firstParser = locusInfo.hashTreeParsers.get('http://locus-url-A.com').parser;
|
|
3139
|
+
firstParser.state = 'stopped';
|
|
3140
|
+
|
|
3141
|
+
await setupParserViaInitialSetup('http://locus-url-B.com');
|
|
3142
|
+
|
|
3143
|
+
assert.notCalled(firstParser.stop);
|
|
3144
|
+
});
|
|
3145
|
+
});
|
|
3146
|
+
|
|
3147
|
+
describe('#handleHashTreeParserSwitch', () => {
|
|
3148
|
+
const deviceUrl = 'http://device-url.com';
|
|
3149
|
+
const locusUrlA = 'http://locus-url-A.com';
|
|
3150
|
+
const locusUrlB = 'http://locus-url-B.com';
|
|
3151
|
+
|
|
3152
|
+
let HashTreeParserStub;
|
|
3153
|
+
|
|
3154
|
+
const createMockParser = (state = 'active') => ({
|
|
3155
|
+
state,
|
|
3156
|
+
stop: sinon.stub(),
|
|
3157
|
+
resume: sinon.stub(),
|
|
3158
|
+
handleMessage: sinon.stub(),
|
|
3159
|
+
});
|
|
3160
|
+
|
|
3161
|
+
const createSelfElementWithReplaces = (replacedLocusUrl, replacedAt) => ({
|
|
3162
|
+
htMeta: {elementId: {type: 'Self'}},
|
|
3163
|
+
data: {
|
|
3164
|
+
devices: [{url: deviceUrl, replaces: [{locusUrl: replacedLocusUrl, replacedAt}]}],
|
|
3165
|
+
},
|
|
3166
|
+
});
|
|
3167
|
+
|
|
3168
|
+
const createMetadataElement = () => ({
|
|
3169
|
+
htMeta: {elementId: {type: 'Metadata'}},
|
|
3170
|
+
data: {visibleDataSets: [{name: 'dataset1', url: 'test-url'}]},
|
|
3171
|
+
});
|
|
3172
|
+
|
|
3173
|
+
beforeEach(() => {
|
|
3174
|
+
locusInfo.webex.internal.device.url = deviceUrl;
|
|
3175
|
+
HashTreeParserStub = sinon
|
|
3176
|
+
.stub(HashTreeParserModule, 'default')
|
|
3177
|
+
.returns(createMockParser());
|
|
3178
|
+
});
|
|
3179
|
+
|
|
3180
|
+
it('should create a new parser when no entry exists for locusUrl and metadata has visibleDataSets', () => {
|
|
3181
|
+
// set up an existing parser for a different url
|
|
3182
|
+
locusInfo.hashTreeParsers.set(locusUrlA, {parser: createMockParser(), initializedFromHashTree: true});
|
|
3183
|
+
|
|
3184
|
+
const message = {
|
|
3185
|
+
locusUrl: locusUrlB,
|
|
3186
|
+
locusStateElements: [createMetadataElement()],
|
|
3187
|
+
dataSets: [{name: 'dataset1', url: 'test-url'}],
|
|
3188
|
+
};
|
|
3189
|
+
|
|
3190
|
+
locusInfo.parse(mockMeeting, {
|
|
3191
|
+
eventType: LOCUSEVENT.HASH_TREE_DATA_UPDATED,
|
|
3192
|
+
stateElementsMessage: message,
|
|
3193
|
+
});
|
|
3194
|
+
|
|
3195
|
+
assert.isTrue(locusInfo.hashTreeParsers.has(locusUrlB));
|
|
3196
|
+
});
|
|
3197
|
+
|
|
3198
|
+
it('should return true when no entry exists even if no metadata is available', () => {
|
|
3199
|
+
locusInfo.hashTreeParsers.set(locusUrlA, {parser: createMockParser(), initializedFromHashTree: true});
|
|
3200
|
+
const parserA = locusInfo.hashTreeParsers.get(locusUrlA).parser;
|
|
3201
|
+
|
|
3202
|
+
const message = {
|
|
3203
|
+
locusUrl: locusUrlB,
|
|
3204
|
+
locusStateElements: [],
|
|
3205
|
+
dataSets: [],
|
|
3206
|
+
};
|
|
3207
|
+
|
|
3208
|
+
locusInfo.parse(mockMeeting, {
|
|
3209
|
+
eventType: LOCUSEVENT.HASH_TREE_DATA_UPDATED,
|
|
3210
|
+
stateElementsMessage: message,
|
|
3211
|
+
});
|
|
3212
|
+
|
|
3213
|
+
// no new parser created since no metadata
|
|
3214
|
+
assert.isFalse(locusInfo.hashTreeParsers.has(locusUrlB));
|
|
3215
|
+
// the existing parser's handleMessage should NOT have been called
|
|
3216
|
+
assert.notCalled(parserA.handleMessage);
|
|
3217
|
+
});
|
|
3218
|
+
|
|
3219
|
+
it('should resume a stopped parser when replaces info is newer', () => {
|
|
3220
|
+
const parserA = createMockParser('stopped');
|
|
3221
|
+
const parserB = createMockParser('active');
|
|
3222
|
+
locusInfo.hashTreeParsers.set(locusUrlA, {parser: parserA, replacedAt: '2026-01-01T00:00:00Z', initializedFromHashTree: true});
|
|
3223
|
+
locusInfo.hashTreeParsers.set(locusUrlB, {parser: parserB, initializedFromHashTree: true});
|
|
3224
|
+
|
|
3225
|
+
const message = {
|
|
3226
|
+
locusUrl: locusUrlA,
|
|
3227
|
+
locusStateElements: [
|
|
3228
|
+
createSelfElementWithReplaces(locusUrlB, '2026-02-01T00:00:00Z'),
|
|
3229
|
+
createMetadataElement(),
|
|
3230
|
+
],
|
|
3231
|
+
dataSets: [{name: 'dataset1', url: 'test-url'}],
|
|
3232
|
+
};
|
|
3233
|
+
|
|
3234
|
+
locusInfo.parse(mockMeeting, {
|
|
3235
|
+
eventType: LOCUSEVENT.HASH_TREE_DATA_UPDATED,
|
|
3236
|
+
stateElementsMessage: message,
|
|
3237
|
+
});
|
|
3238
|
+
|
|
3239
|
+
assert.calledOnce(parserA.resume);
|
|
3240
|
+
assert.calledOnce(parserB.stop);
|
|
3241
|
+
});
|
|
3242
|
+
|
|
3243
|
+
it('should not resume a stopped parser when replaces info is not newer', () => {
|
|
3244
|
+
const parserA = createMockParser('stopped');
|
|
3245
|
+
const parserB = createMockParser('active');
|
|
3246
|
+
locusInfo.hashTreeParsers.set(locusUrlA, {parser: parserA, replacedAt: '2026-03-01T00:00:00Z', initializedFromHashTree: true});
|
|
3247
|
+
locusInfo.hashTreeParsers.set(locusUrlB, {parser: parserB, initializedFromHashTree: true});
|
|
3248
|
+
|
|
3249
|
+
const message = {
|
|
3250
|
+
locusUrl: locusUrlA,
|
|
3251
|
+
locusStateElements: [
|
|
3252
|
+
createSelfElementWithReplaces(locusUrlB, '2026-01-01T00:00:00Z'),
|
|
3253
|
+
],
|
|
3254
|
+
dataSets: [{name: 'dataset1', url: 'test-url'}],
|
|
3255
|
+
};
|
|
3256
|
+
|
|
3257
|
+
locusInfo.parse(mockMeeting, {
|
|
3258
|
+
eventType: LOCUSEVENT.HASH_TREE_DATA_UPDATED,
|
|
3259
|
+
stateElementsMessage: message,
|
|
3260
|
+
});
|
|
3261
|
+
|
|
3262
|
+
assert.notCalled(parserA.resume);
|
|
3263
|
+
assert.notCalled(parserB.stop);
|
|
3264
|
+
});
|
|
3265
|
+
|
|
3266
|
+
it('should return true for a stopped parser with no replaces info', () => {
|
|
3267
|
+
const parserA = createMockParser('stopped');
|
|
3268
|
+
locusInfo.hashTreeParsers.set(locusUrlA, {parser: parserA, initializedFromHashTree: true});
|
|
3269
|
+
|
|
3270
|
+
const message = {
|
|
3271
|
+
locusUrl: locusUrlA,
|
|
3272
|
+
locusStateElements: [],
|
|
3273
|
+
dataSets: [],
|
|
3274
|
+
};
|
|
3275
|
+
|
|
3276
|
+
locusInfo.parse(mockMeeting, {
|
|
3277
|
+
eventType: LOCUSEVENT.HASH_TREE_DATA_UPDATED,
|
|
3278
|
+
stateElementsMessage: message,
|
|
3279
|
+
});
|
|
3280
|
+
|
|
3281
|
+
assert.notCalled(parserA.resume);
|
|
3282
|
+
assert.notCalled(parserA.handleMessage);
|
|
3283
|
+
});
|
|
3284
|
+
|
|
3285
|
+
it('should return false when the entry exists and parser is active', () => {
|
|
3286
|
+
const parserA = createMockParser('active');
|
|
3287
|
+
locusInfo.hashTreeParsers.set(locusUrlA, {parser: parserA, initializedFromHashTree: true});
|
|
3288
|
+
|
|
3289
|
+
const message = {
|
|
3290
|
+
locusUrl: locusUrlA,
|
|
3291
|
+
locusStateElements: [],
|
|
3292
|
+
dataSets: [],
|
|
3293
|
+
};
|
|
3294
|
+
|
|
3295
|
+
locusInfo.parse(mockMeeting, {
|
|
3296
|
+
eventType: LOCUSEVENT.HASH_TREE_DATA_UPDATED,
|
|
3297
|
+
stateElementsMessage: message,
|
|
3298
|
+
});
|
|
3299
|
+
|
|
3300
|
+
assert.calledOnceWithExactly(parserA.handleMessage, message);
|
|
3301
|
+
});
|
|
3302
|
+
|
|
3303
|
+
it('should pass replacedAt from replaces to createHashTreeParser when creating a new parser', () => {
|
|
3304
|
+
locusInfo.hashTreeParsers.set(locusUrlA, {parser: createMockParser(), initializedFromHashTree: true});
|
|
3305
|
+
|
|
3306
|
+
const message = {
|
|
3307
|
+
locusUrl: locusUrlB,
|
|
3308
|
+
locusStateElements: [
|
|
3309
|
+
createMetadataElement(),
|
|
3310
|
+
createSelfElementWithReplaces(locusUrlA, '2026-05-01T00:00:00Z'),
|
|
3311
|
+
],
|
|
3312
|
+
dataSets: [{name: 'dataset1', url: 'test-url'}],
|
|
3313
|
+
};
|
|
3314
|
+
|
|
3315
|
+
locusInfo.parse(mockMeeting, {
|
|
3316
|
+
eventType: LOCUSEVENT.HASH_TREE_DATA_UPDATED,
|
|
3317
|
+
stateElementsMessage: message,
|
|
3318
|
+
});
|
|
3319
|
+
|
|
3320
|
+
assert.isTrue(locusInfo.hashTreeParsers.has(locusUrlB));
|
|
3321
|
+
assert.equal(locusInfo.hashTreeParsers.get(locusUrlA).replacedAt, '2026-05-01T00:00:00Z');
|
|
3322
|
+
});
|
|
3323
|
+
});
|
|
3324
|
+
|
|
3325
|
+
describe('#handleHashTreeMessage', () => {
|
|
3326
|
+
it('should call handleHashTreeParserSwitch and not call handleMessage if parser was switched', () => {
|
|
3327
|
+
const locusUrlA = 'http://locus-url-A.com';
|
|
3328
|
+
const locusUrlB = 'http://locus-url-B.com';
|
|
3329
|
+
const parserA = {state: 'stopped', handleMessage: sinon.stub(), resume: sinon.stub(), stop: sinon.stub()};
|
|
3330
|
+
locusInfo.hashTreeParsers.set(locusUrlA, {parser: parserA, initializedFromHashTree: true});
|
|
3331
|
+
|
|
3332
|
+
// message for a stopped parser without replaces -> handleHashTreeParserSwitch returns true
|
|
3333
|
+
const message = {
|
|
3334
|
+
locusUrl: locusUrlA,
|
|
3335
|
+
locusStateElements: [],
|
|
3336
|
+
dataSets: [],
|
|
3337
|
+
};
|
|
3338
|
+
|
|
3339
|
+
locusInfo.parse(mockMeeting, {
|
|
3340
|
+
eventType: LOCUSEVENT.HASH_TREE_DATA_UPDATED,
|
|
3341
|
+
stateElementsMessage: message,
|
|
3342
|
+
});
|
|
3343
|
+
|
|
3344
|
+
assert.notCalled(parserA.handleMessage);
|
|
3345
|
+
});
|
|
3346
|
+
|
|
3347
|
+
it('should call handleMessage on the correct parser when no switch occurs', () => {
|
|
3348
|
+
const locusUrlA = 'http://locus-url-A.com';
|
|
3349
|
+
const parserA = {state: 'active', handleMessage: sinon.stub()};
|
|
3350
|
+
locusInfo.hashTreeParsers.set(locusUrlA, {parser: parserA, initializedFromHashTree: true});
|
|
3351
|
+
|
|
3352
|
+
const message = {
|
|
3353
|
+
locusUrl: locusUrlA,
|
|
3354
|
+
locusStateElements: [],
|
|
3355
|
+
dataSets: [],
|
|
3356
|
+
};
|
|
3357
|
+
|
|
3358
|
+
locusInfo.parse(mockMeeting, {
|
|
3359
|
+
eventType: LOCUSEVENT.HASH_TREE_DATA_UPDATED,
|
|
3360
|
+
stateElementsMessage: message,
|
|
3361
|
+
});
|
|
3362
|
+
|
|
3363
|
+
assert.calledOnceWithExactly(parserA.handleMessage, message);
|
|
3364
|
+
});
|
|
3365
|
+
});
|
|
3366
|
+
|
|
2838
3367
|
describe('#handleLocusAPIResponse', () => {
|
|
2839
3368
|
it('calls handleLocusDelta when we are not using hash trees', () => {
|
|
2840
3369
|
const fakeLocus = {eventType: LOCUSEVENT.DIFFERENCE};
|
|
@@ -2846,7 +3375,7 @@ describe('plugin-meetings', () => {
|
|
|
2846
3375
|
assert.calledWith(locusInfo.handleLocusDelta, fakeLocus, mockMeeting);
|
|
2847
3376
|
});
|
|
2848
3377
|
it('calls hash tree parser when we are using hash trees', () => {
|
|
2849
|
-
const fakeLocus = {eventType: LOCUSEVENT.DIFFERENCE};
|
|
3378
|
+
const fakeLocus = {eventType: LOCUSEVENT.DIFFERENCE, url: 'http://locus-url.com'};
|
|
2850
3379
|
const fakeDataSets = [{name: 'dataset1', url: 'http://test.com'}];
|
|
2851
3380
|
const responseBody = {locus: fakeLocus, dataSets: fakeDataSets};
|
|
2852
3381
|
|
|
@@ -2854,7 +3383,10 @@ describe('plugin-meetings', () => {
|
|
|
2854
3383
|
const mockHashTreeParser = {
|
|
2855
3384
|
handleLocusUpdate: sinon.stub(),
|
|
2856
3385
|
};
|
|
2857
|
-
locusInfo.
|
|
3386
|
+
locusInfo.hashTreeParsers.set(fakeLocus.url, {
|
|
3387
|
+
parser: mockHashTreeParser,
|
|
3388
|
+
initializedFromHashTree: true,
|
|
3389
|
+
});
|
|
2858
3390
|
|
|
2859
3391
|
sinon.stub(locusInfo, 'onDeltaLocus');
|
|
2860
3392
|
|
|
@@ -2862,6 +3394,43 @@ describe('plugin-meetings', () => {
|
|
|
2862
3394
|
|
|
2863
3395
|
assert.calledOnceWithExactly(mockHashTreeParser.handleLocusUpdate, responseBody);
|
|
2864
3396
|
});
|
|
3397
|
+
|
|
3398
|
+
it('should handle unwrapped LocusDTO (without locus wrapper) when hash tree parser exists', () => {
|
|
3399
|
+
const fakeLocus = {url: 'http://locus-url.com', fullState: {state: 'ACTIVE'}};
|
|
3400
|
+
const mockHashTreeParser = {handleLocusUpdate: sinon.stub()};
|
|
3401
|
+
locusInfo.hashTreeParsers.set(fakeLocus.url, {
|
|
3402
|
+
parser: mockHashTreeParser,
|
|
3403
|
+
initializedFromHashTree: true,
|
|
3404
|
+
});
|
|
3405
|
+
|
|
3406
|
+
locusInfo.handleLocusAPIResponse(mockMeeting, fakeLocus);
|
|
3407
|
+
|
|
3408
|
+
assert.calledOnceWithExactly(mockHashTreeParser.handleLocusUpdate, {locus: fakeLocus});
|
|
3409
|
+
});
|
|
3410
|
+
|
|
3411
|
+
it('should handle unwrapped LocusDTO in classic mode (no hash tree parser)', () => {
|
|
3412
|
+
const fakeLocus = {url: 'http://locus-url.com', fullState: {state: 'ACTIVE'}};
|
|
3413
|
+
sinon.stub(locusInfo, 'handleLocusDelta');
|
|
3414
|
+
|
|
3415
|
+
locusInfo.handleLocusAPIResponse(mockMeeting, fakeLocus);
|
|
3416
|
+
|
|
3417
|
+
assert.calledOnceWithExactly(locusInfo.handleLocusDelta, fakeLocus, mockMeeting);
|
|
3418
|
+
});
|
|
3419
|
+
|
|
3420
|
+
it('should send mismatch metric when hash tree parser exists but dataSets are missing in wrapped response', () => {
|
|
3421
|
+
const fakeLocus = {url: 'http://locus-url.com'};
|
|
3422
|
+
const mockHashTreeParser = {handleLocusUpdate: sinon.stub()};
|
|
3423
|
+
locusInfo.hashTreeParsers.set(fakeLocus.url, {
|
|
3424
|
+
parser: mockHashTreeParser,
|
|
3425
|
+
initializedFromHashTree: true,
|
|
3426
|
+
});
|
|
3427
|
+
sinon.stub(locusInfo, 'sendClassicVsHashTreeMismatchMetric');
|
|
3428
|
+
|
|
3429
|
+
locusInfo.handleLocusAPIResponse(mockMeeting, {locus: fakeLocus});
|
|
3430
|
+
|
|
3431
|
+
assert.calledOnce(locusInfo.sendClassicVsHashTreeMismatchMetric);
|
|
3432
|
+
assert.calledOnce(mockHashTreeParser.handleLocusUpdate);
|
|
3433
|
+
});
|
|
2865
3434
|
});
|
|
2866
3435
|
|
|
2867
3436
|
describe('#LocusDeltaEvents', () => {
|
|
@@ -2923,28 +3492,28 @@ describe('plugin-meetings', () => {
|
|
|
2923
3492
|
assert.isFunction(locusParser.onDeltaAction);
|
|
2924
3493
|
});
|
|
2925
3494
|
|
|
2926
|
-
it(
|
|
3495
|
+
it('#updateLocusInfo invokes updateLocusUrl before updateMeetingInfo', () => {
|
|
2927
3496
|
const callOrder = [];
|
|
2928
|
-
sinon.stub(locusInfo,
|
|
2929
|
-
sinon.stub(locusInfo,
|
|
2930
|
-
sinon.stub(locusInfo,
|
|
2931
|
-
sinon.stub(locusInfo,
|
|
2932
|
-
sinon.stub(locusInfo,
|
|
2933
|
-
sinon.stub(locusInfo,
|
|
2934
|
-
callOrder.push(
|
|
3497
|
+
sinon.stub(locusInfo, 'updateControls');
|
|
3498
|
+
sinon.stub(locusInfo, 'updateConversationUrl');
|
|
3499
|
+
sinon.stub(locusInfo, 'updateCreated');
|
|
3500
|
+
sinon.stub(locusInfo, 'updateFullState');
|
|
3501
|
+
sinon.stub(locusInfo, 'updateHostInfo');
|
|
3502
|
+
sinon.stub(locusInfo, 'updateMeetingInfo').callsFake(() => {
|
|
3503
|
+
callOrder.push('updateMeetingInfo');
|
|
2935
3504
|
});
|
|
2936
|
-
sinon.stub(locusInfo,
|
|
2937
|
-
sinon.stub(locusInfo,
|
|
2938
|
-
sinon.stub(locusInfo,
|
|
2939
|
-
sinon.stub(locusInfo,
|
|
2940
|
-
callOrder.push(
|
|
3505
|
+
sinon.stub(locusInfo, 'updateMediaShares');
|
|
3506
|
+
sinon.stub(locusInfo, 'updateReplaces');
|
|
3507
|
+
sinon.stub(locusInfo, 'updateSelf');
|
|
3508
|
+
sinon.stub(locusInfo, 'updateLocusUrl').callsFake(() => {
|
|
3509
|
+
callOrder.push('updateLocusUrl');
|
|
2941
3510
|
});
|
|
2942
|
-
sinon.stub(locusInfo,
|
|
2943
|
-
sinon.stub(locusInfo,
|
|
2944
|
-
sinon.stub(locusInfo,
|
|
2945
|
-
sinon.stub(locusInfo,
|
|
2946
|
-
sinon.stub(locusInfo,
|
|
2947
|
-
sinon.stub(locusInfo,
|
|
3511
|
+
sinon.stub(locusInfo, 'updateAclUrl');
|
|
3512
|
+
sinon.stub(locusInfo, 'updateBasequence');
|
|
3513
|
+
sinon.stub(locusInfo, 'updateSequence');
|
|
3514
|
+
sinon.stub(locusInfo, 'updateEmbeddedApps');
|
|
3515
|
+
sinon.stub(locusInfo, 'updateLinks');
|
|
3516
|
+
sinon.stub(locusInfo, 'compareAndUpdate');
|
|
2948
3517
|
|
|
2949
3518
|
locusInfo.updateLocusInfo(locus);
|
|
2950
3519
|
|
|
@@ -3000,7 +3569,7 @@ describe('plugin-meetings', () => {
|
|
|
3000
3569
|
it('#updateLocusInfo puts the Locus DTO top level properties at the right place in LocusInfo class', () => {
|
|
3001
3570
|
// this test verifies that the top-level properties of Locus DTO are copied
|
|
3002
3571
|
// into LocusInfo class and set as top level properties too
|
|
3003
|
-
// this is important, because the code handling Locus
|
|
3572
|
+
// this is important, because the code handling Locus hash trees relies on it, see updateFromHashTree()
|
|
3004
3573
|
const info = {id: 'info id'};
|
|
3005
3574
|
const fullState = {id: 'fullState id'};
|
|
3006
3575
|
const links = {services: {id: 'service links'}, resources: {id: 'resource links'}};
|
|
@@ -3039,7 +3608,7 @@ describe('plugin-meetings', () => {
|
|
|
3039
3608
|
sandbox.stub(locusInfo, 'handleOneOnOneEvent');
|
|
3040
3609
|
sandbox.stub(locusParser, 'isNewFullLocus').returns(true);
|
|
3041
3610
|
|
|
3042
|
-
locusInfo.onFullLocus(fakeLocus, eventType);
|
|
3611
|
+
locusInfo.onFullLocus('test', fakeLocus, eventType);
|
|
3043
3612
|
|
|
3044
3613
|
assert.equal(fakeLocus, locusParser.workingCopy);
|
|
3045
3614
|
});
|
|
@@ -3060,7 +3629,7 @@ describe('plugin-meetings', () => {
|
|
|
3060
3629
|
|
|
3061
3630
|
sandbox.stub(locusParser, 'isNewFullLocus').returns(false);
|
|
3062
3631
|
|
|
3063
|
-
locusInfo.onFullLocus(fakeLocus, eventType);
|
|
3632
|
+
locusInfo.onFullLocus('test', fakeLocus, eventType);
|
|
3064
3633
|
|
|
3065
3634
|
spies.forEach((spy) => {
|
|
3066
3635
|
assert.notCalled(spy);
|
|
@@ -3210,7 +3779,11 @@ describe('plugin-meetings', () => {
|
|
|
3210
3779
|
}).then(() => {
|
|
3211
3780
|
assert.calledOnceWithExactly(meeting.meetingRequest.getLocusDTO, {url: 'oldLocusUrl'});
|
|
3212
3781
|
|
|
3213
|
-
assert.calledOnceWithExactly(
|
|
3782
|
+
assert.calledOnceWithExactly(
|
|
3783
|
+
meeting.locusInfo.onFullLocus,
|
|
3784
|
+
'classic Locus sync',
|
|
3785
|
+
fakeFullLocusDto
|
|
3786
|
+
);
|
|
3214
3787
|
assert.calledOnce(locusInfo.locusParser.resume);
|
|
3215
3788
|
});
|
|
3216
3789
|
});
|
|
@@ -3308,7 +3881,11 @@ describe('plugin-meetings', () => {
|
|
|
3308
3881
|
});
|
|
3309
3882
|
|
|
3310
3883
|
assert.notCalled(meeting.locusInfo.handleLocusDelta);
|
|
3311
|
-
assert.calledOnceWithExactly(
|
|
3884
|
+
assert.calledOnceWithExactly(
|
|
3885
|
+
meeting.locusInfo.onFullLocus,
|
|
3886
|
+
'classic Locus sync',
|
|
3887
|
+
fakeFullLocusDto
|
|
3888
|
+
);
|
|
3312
3889
|
assert.calledOnce(locusInfo.locusParser.resume);
|
|
3313
3890
|
});
|
|
3314
3891
|
});
|
|
@@ -3484,7 +4061,11 @@ describe('plugin-meetings', () => {
|
|
|
3484
4061
|
url: 'fake locus DELTA url',
|
|
3485
4062
|
});
|
|
3486
4063
|
assert.notCalled(meeting.locusInfo.handleLocusDelta);
|
|
3487
|
-
assert.calledOnceWithExactly(
|
|
4064
|
+
assert.calledOnceWithExactly(
|
|
4065
|
+
meeting.locusInfo.onFullLocus,
|
|
4066
|
+
'classic Locus sync',
|
|
4067
|
+
fakeFullLocusDto
|
|
4068
|
+
);
|
|
3488
4069
|
assert.calledOnce(locusInfo.locusParser.resume);
|
|
3489
4070
|
});
|
|
3490
4071
|
});
|
|
@@ -3505,6 +4086,137 @@ describe('plugin-meetings', () => {
|
|
|
3505
4086
|
|
|
3506
4087
|
assert.isNull(locusInfo.mainSessionLocusCache);
|
|
3507
4088
|
});
|
|
4089
|
+
|
|
4090
|
+
it('should map participant with htMeta.elementId.id of 0 (falsy number) to hashTreeObjectId2ParticipantId', () => {
|
|
4091
|
+
const locus = {
|
|
4092
|
+
url: 'url',
|
|
4093
|
+
participants: [
|
|
4094
|
+
{
|
|
4095
|
+
id: 'participant-zero',
|
|
4096
|
+
htMeta: {elementId: {id: 0}},
|
|
4097
|
+
},
|
|
4098
|
+
],
|
|
4099
|
+
};
|
|
4100
|
+
|
|
4101
|
+
sinon.stub(locusInfo.locusParser, 'isNewFullLocus').returns(true);
|
|
4102
|
+
sinon.stub(locusInfo, 'updateLocusInfo');
|
|
4103
|
+
sinon.stub(locusInfo, 'updateParticipants');
|
|
4104
|
+
sinon.stub(locusInfo, 'isMeetingActive');
|
|
4105
|
+
sinon.stub(locusInfo, 'handleOneOnOneEvent');
|
|
4106
|
+
sinon.stub(locusInfo, 'updateEmbeddedApps');
|
|
4107
|
+
locusInfo.locusParser.workingCopy = null;
|
|
4108
|
+
|
|
4109
|
+
locusInfo.onFullLocus('test', locus);
|
|
4110
|
+
|
|
4111
|
+
assert.equal(locusInfo.hashTreeObjectId2ParticipantId.get(0), 'participant-zero');
|
|
4112
|
+
});
|
|
4113
|
+
});
|
|
4114
|
+
|
|
4115
|
+
describe('#onDeltaLocus', () => {
|
|
4116
|
+
it('should use forceReplaceMembers from jsSdkMeta when it is defined', () => {
|
|
4117
|
+
sinon.stub(locusInfo, 'mergeParticipants');
|
|
4118
|
+
sinon.stub(locusInfo, 'updateLocusInfo').returns(true);
|
|
4119
|
+
sinon.stub(locusInfo, 'updateParticipants');
|
|
4120
|
+
sinon.stub(locusInfo, 'isMeetingActive');
|
|
4121
|
+
|
|
4122
|
+
locusInfo.onDeltaLocus({
|
|
4123
|
+
participants: [],
|
|
4124
|
+
jsSdkMeta: {forceReplaceMembers: true, removedParticipantIds: []},
|
|
4125
|
+
});
|
|
4126
|
+
|
|
4127
|
+
assert.calledOnceWithExactly(locusInfo.updateParticipants, [], [], true);
|
|
4128
|
+
});
|
|
4129
|
+
|
|
4130
|
+
it('should fall back to isNeedReplaceMembers when forceReplaceMembers is not in jsSdkMeta', () => {
|
|
4131
|
+
sinon.stub(locusInfo, 'mergeParticipants');
|
|
4132
|
+
sinon.stub(locusInfo, 'updateLocusInfo').returns(true);
|
|
4133
|
+
sinon.stub(locusInfo, 'updateParticipants');
|
|
4134
|
+
sinon.stub(locusInfo, 'isMeetingActive');
|
|
4135
|
+
|
|
4136
|
+
locusInfo.onDeltaLocus({participants: []});
|
|
4137
|
+
|
|
4138
|
+
// without jsSdkMeta.forceReplaceMembers, uses ControlsUtils.isNeedReplaceMembers result (false by default)
|
|
4139
|
+
assert.calledOnceWithExactly(locusInfo.updateParticipants, [], undefined, false);
|
|
4140
|
+
});
|
|
4141
|
+
|
|
4142
|
+
it('should not call updateParticipants when updateLocusInfo returns false', () => {
|
|
4143
|
+
sinon.stub(locusInfo, 'mergeParticipants');
|
|
4144
|
+
sinon.stub(locusInfo, 'updateLocusInfo').returns(false);
|
|
4145
|
+
sinon.stub(locusInfo, 'updateParticipants');
|
|
4146
|
+
sinon.stub(locusInfo, 'isMeetingActive');
|
|
4147
|
+
|
|
4148
|
+
locusInfo.onDeltaLocus({participants: [], self: {state: 'LEFT', reason: 'MOVED'}});
|
|
4149
|
+
|
|
4150
|
+
assert.notCalled(locusInfo.updateParticipants);
|
|
4151
|
+
});
|
|
4152
|
+
|
|
4153
|
+
it('should call updateParticipants when updateLocusInfo returns true', () => {
|
|
4154
|
+
sinon.stub(locusInfo, 'mergeParticipants');
|
|
4155
|
+
sinon.stub(locusInfo, 'updateLocusInfo').returns(true);
|
|
4156
|
+
sinon.stub(locusInfo, 'updateParticipants');
|
|
4157
|
+
sinon.stub(locusInfo, 'isMeetingActive');
|
|
4158
|
+
|
|
4159
|
+
locusInfo.onDeltaLocus({participants: [{id: 'p1'}]});
|
|
4160
|
+
|
|
4161
|
+
assert.calledOnce(locusInfo.updateParticipants);
|
|
4162
|
+
});
|
|
4163
|
+
|
|
4164
|
+
[
|
|
4165
|
+
{forceReplaceMembers: true, selfInParticipants: false, expectedSelfCopied: true},
|
|
4166
|
+
{forceReplaceMembers: true, selfInParticipants: true, expectedSelfCopied: false},
|
|
4167
|
+
{forceReplaceMembers: false, selfInParticipants: false, expectedSelfCopied: false},
|
|
4168
|
+
{forceReplaceMembers: false, selfInParticipants: true, expectedSelfCopied: false},
|
|
4169
|
+
].forEach(({forceReplaceMembers, selfInParticipants, expectedSelfCopied}) => {
|
|
4170
|
+
it(`should ${expectedSelfCopied ? '' : 'not '}copy self into participants when forceReplaceMembers=${forceReplaceMembers} and self ${selfInParticipants ? 'is' : 'is not'} in participants`, () => {
|
|
4171
|
+
const self = {identity: 'selfId', state: 'JOINED', devices: [], status: {}};
|
|
4172
|
+
const participant = {identity: selfInParticipants ? 'selfId' : 'other'};
|
|
4173
|
+
const locus = {
|
|
4174
|
+
participants: [participant],
|
|
4175
|
+
self,
|
|
4176
|
+
jsSdkMeta: {forceReplaceMembers, removedParticipantIds: []},
|
|
4177
|
+
};
|
|
4178
|
+
|
|
4179
|
+
locusInfo.onDeltaLocus(locus);
|
|
4180
|
+
|
|
4181
|
+
const expectedParticipants = expectedSelfCopied ? [participant, self] : [participant];
|
|
4182
|
+
assert.deepEqual(locus.participants, expectedParticipants);
|
|
4183
|
+
});
|
|
4184
|
+
});
|
|
4185
|
+
});
|
|
4186
|
+
|
|
4187
|
+
describe('#updateLocusInfo', () => {
|
|
4188
|
+
it('should return false when self.reason is MOVED and self.state is LEFT', () => {
|
|
4189
|
+
sinon.stub(locusInfo, 'updateControls');
|
|
4190
|
+
|
|
4191
|
+
const result = locusInfo.updateLocusInfo({self: {reason: 'MOVED', state: 'LEFT'}});
|
|
4192
|
+
|
|
4193
|
+
assert.isFalse(result);
|
|
4194
|
+
assert.notCalled(locusInfo.updateControls);
|
|
4195
|
+
});
|
|
4196
|
+
|
|
4197
|
+
it('should return true when self is not in MOVED/LEFT state', () => {
|
|
4198
|
+
sinon.stub(locusInfo, 'updateControls');
|
|
4199
|
+
sinon.stub(locusInfo, 'updateConversationUrl');
|
|
4200
|
+
sinon.stub(locusInfo, 'updateCreated');
|
|
4201
|
+
sinon.stub(locusInfo, 'updateFullState');
|
|
4202
|
+
sinon.stub(locusInfo, 'updateHostInfo');
|
|
4203
|
+
sinon.stub(locusInfo, 'updateLocusUrl');
|
|
4204
|
+
sinon.stub(locusInfo, 'updateMeetingInfo');
|
|
4205
|
+
sinon.stub(locusInfo, 'updateMediaShares');
|
|
4206
|
+
sinon.stub(locusInfo, 'updateReplaces');
|
|
4207
|
+
sinon.stub(locusInfo, 'updateSelf');
|
|
4208
|
+
sinon.stub(locusInfo, 'updateAclUrl');
|
|
4209
|
+
sinon.stub(locusInfo, 'updateBasequence');
|
|
4210
|
+
sinon.stub(locusInfo, 'updateSequence');
|
|
4211
|
+
sinon.stub(locusInfo, 'updateEmbeddedApps');
|
|
4212
|
+
sinon.stub(locusInfo, 'updateLinks');
|
|
4213
|
+
sinon.stub(locusInfo, 'compareAndUpdate');
|
|
4214
|
+
|
|
4215
|
+
const result = locusInfo.updateLocusInfo({self: {state: 'JOINED'}});
|
|
4216
|
+
|
|
4217
|
+
assert.isTrue(result);
|
|
4218
|
+
assert.calledOnce(locusInfo.updateControls);
|
|
4219
|
+
});
|
|
3508
4220
|
});
|
|
3509
4221
|
|
|
3510
4222
|
describe('#getTheLocusToUpdate', () => {
|
|
@@ -3856,7 +4568,7 @@ describe('plugin-meetings', () => {
|
|
|
3856
4568
|
|
|
3857
4569
|
describe('#updateLocusUrl', () => {
|
|
3858
4570
|
it('trigger LOCUS_INFO_UPDATE_URL event with isMainLocus is true as default', () => {
|
|
3859
|
-
const fakeUrl =
|
|
4571
|
+
const fakeUrl = 'https://fake.com/locus';
|
|
3860
4572
|
locusInfo.emitScoped = sinon.stub();
|
|
3861
4573
|
locusInfo.updateLocusUrl(fakeUrl);
|
|
3862
4574
|
|
|
@@ -3869,12 +4581,12 @@ describe('plugin-meetings', () => {
|
|
|
3869
4581
|
EVENTS.LOCUS_INFO_UPDATE_URL,
|
|
3870
4582
|
{
|
|
3871
4583
|
url: fakeUrl,
|
|
3872
|
-
isMainLocus: true
|
|
3873
|
-
}
|
|
4584
|
+
isMainLocus: true,
|
|
4585
|
+
}
|
|
3874
4586
|
);
|
|
3875
4587
|
});
|
|
3876
4588
|
it('trigger LOCUS_INFO_UPDATE_URL event with isMainLocus is false', () => {
|
|
3877
|
-
const fakeUrl =
|
|
4589
|
+
const fakeUrl = 'https://fake.com/locus';
|
|
3878
4590
|
locusInfo.emitScoped = sinon.stub();
|
|
3879
4591
|
locusInfo.updateLocusUrl(fakeUrl, false);
|
|
3880
4592
|
|
|
@@ -3887,8 +4599,8 @@ describe('plugin-meetings', () => {
|
|
|
3887
4599
|
EVENTS.LOCUS_INFO_UPDATE_URL,
|
|
3888
4600
|
{
|
|
3889
4601
|
url: fakeUrl,
|
|
3890
|
-
isMainLocus: false
|
|
3891
|
-
}
|
|
4602
|
+
isMainLocus: false,
|
|
4603
|
+
}
|
|
3892
4604
|
);
|
|
3893
4605
|
});
|
|
3894
4606
|
});
|
|
@@ -3940,8 +4652,8 @@ describe('plugin-meetings', () => {
|
|
|
3940
4652
|
|
|
3941
4653
|
sinon.stub(locusInfo, 'updateParticipants');
|
|
3942
4654
|
sinon.stub(locusInfo, 'isMeetingActive');
|
|
3943
|
-
|
|
3944
|
-
|
|
4655
|
+
sinon.stub(locusInfo, 'handleOneOnOneEvent');
|
|
4656
|
+
updateLocusInfoStub = sinon.stub(locusInfo, 'updateLocusInfo');
|
|
3945
4657
|
syncRequestStub = sinon.stub().resolves({body: {}});
|
|
3946
4658
|
|
|
3947
4659
|
mockMeeting.locusInfo = locusInfo;
|
|
@@ -3950,7 +4662,7 @@ describe('plugin-meetings', () => {
|
|
|
3950
4662
|
getLocusDTO: syncRequestStub,
|
|
3951
4663
|
};
|
|
3952
4664
|
|
|
3953
|
-
locusInfo.onFullLocus({
|
|
4665
|
+
locusInfo.onFullLocus('test', {
|
|
3954
4666
|
sequence: {
|
|
3955
4667
|
rangeStart: 0,
|
|
3956
4668
|
rangeEnd: 0,
|
|
@@ -4188,7 +4900,9 @@ describe('plugin-meetings', () => {
|
|
|
4188
4900
|
|
|
4189
4901
|
describe('#parse', () => {
|
|
4190
4902
|
it('handles hash tree messages correctly', () => {
|
|
4903
|
+
const fakeLocusUrl = 'http://locus-url.com';
|
|
4191
4904
|
const fakeHashTreeMessage = {
|
|
4905
|
+
locusUrl: fakeLocusUrl,
|
|
4192
4906
|
locusStateElements: [
|
|
4193
4907
|
{
|
|
4194
4908
|
htMeta: {elementId: {type: 'self'}},
|
|
@@ -4207,12 +4921,377 @@ describe('plugin-meetings', () => {
|
|
|
4207
4921
|
const mockHashTreeParser = {
|
|
4208
4922
|
handleMessage: sinon.stub(),
|
|
4209
4923
|
};
|
|
4210
|
-
locusInfo.
|
|
4924
|
+
locusInfo.hashTreeParsers.set(fakeLocusUrl, {
|
|
4925
|
+
parser: mockHashTreeParser,
|
|
4926
|
+
initializedFromHashTree: true,
|
|
4927
|
+
});
|
|
4211
4928
|
|
|
4212
4929
|
locusInfo.parse(mockMeeting, data);
|
|
4213
4930
|
|
|
4214
4931
|
assert.calledOnceWithExactly(mockHashTreeParser.handleMessage, fakeHashTreeMessage);
|
|
4215
4932
|
});
|
|
4933
|
+
|
|
4934
|
+
it('ignores hash tree event when hashTreeParser is not created yet', () => {
|
|
4935
|
+
const data = {
|
|
4936
|
+
eventType: LOCUSEVENT.HASH_TREE_DATA_UPDATED,
|
|
4937
|
+
stateElementsMessage: {
|
|
4938
|
+
locusStateElements: [],
|
|
4939
|
+
dataSets: [],
|
|
4940
|
+
},
|
|
4941
|
+
};
|
|
4942
|
+
|
|
4943
|
+
const loggerSpy = sinon.spy(LoggerProxy.logger, 'info');
|
|
4944
|
+
const getTheLocusToUpdateStub = sinon.stub(locusInfo, 'getTheLocusToUpdate');
|
|
4945
|
+
|
|
4946
|
+
// Ensure we're not using hash trees
|
|
4947
|
+
assert.equal(locusInfo.hashTreeParsers.size, 0);
|
|
4948
|
+
|
|
4949
|
+
locusInfo.parse(mockMeeting, data);
|
|
4950
|
+
|
|
4951
|
+
assert.calledWith(
|
|
4952
|
+
loggerSpy,
|
|
4953
|
+
'Locus-info:index#parse --> received locus hash tree event before hashTreeParser is created'
|
|
4954
|
+
);
|
|
4955
|
+
assert.notCalled(getTheLocusToUpdateStub);
|
|
4956
|
+
});
|
|
4957
|
+
});
|
|
4958
|
+
});
|
|
4959
|
+
|
|
4960
|
+
describe('#createLocusFromHashTreeMessage', () => {
|
|
4961
|
+
const LOCUS_URL = 'https://locus.example.com/loci/abc-123';
|
|
4962
|
+
|
|
4963
|
+
const createElement = (type, data) => ({
|
|
4964
|
+
htMeta: {elementId: {type, id: 1, version: 1}},
|
|
4965
|
+
data,
|
|
4966
|
+
});
|
|
4967
|
+
|
|
4968
|
+
it('returns locus with url and empty participants when no locusStateElements', () => {
|
|
4969
|
+
const result = createLocusFromHashTreeMessage({locusUrl: LOCUS_URL});
|
|
4970
|
+
|
|
4971
|
+
assert.deepEqual(result.locus, {participants: [], url: LOCUS_URL});
|
|
4972
|
+
assert.isUndefined(result.metadata);
|
|
4973
|
+
});
|
|
4974
|
+
|
|
4975
|
+
it('skips elements without data', () => {
|
|
4976
|
+
const result = createLocusFromHashTreeMessage({
|
|
4977
|
+
locusUrl: LOCUS_URL,
|
|
4978
|
+
locusStateElements: [{htMeta: {elementId: {type: 'Self', id: 1, version: 1}}, data: null}],
|
|
4979
|
+
});
|
|
4980
|
+
|
|
4981
|
+
assert.deepEqual(result.locus, {participants: [], url: LOCUS_URL});
|
|
4982
|
+
});
|
|
4983
|
+
|
|
4984
|
+
[
|
|
4985
|
+
{type: 'Self', locusKey: 'self', data: {id: 'self-1', state: 'JOINED'}},
|
|
4986
|
+
{type: 'Info', locusKey: 'info', data: {webExMeetingId: '123'}},
|
|
4987
|
+
{type: 'FullState', locusKey: 'fullState', data: {state: 'ACTIVE'}},
|
|
4988
|
+
{type: 'Links', locusKey: 'links', data: {resources: {}}},
|
|
4989
|
+
].forEach(({type, locusKey, data}) => {
|
|
4990
|
+
it(`maps ${type} element to locus.${locusKey}`, () => {
|
|
4991
|
+
const result = createLocusFromHashTreeMessage({
|
|
4992
|
+
locusUrl: LOCUS_URL,
|
|
4993
|
+
locusStateElements: [createElement(type, data)],
|
|
4994
|
+
});
|
|
4995
|
+
|
|
4996
|
+
assert.deepEqual(result.locus[locusKey], data);
|
|
4997
|
+
});
|
|
4998
|
+
});
|
|
4999
|
+
|
|
5000
|
+
it('pushes Participant elements to locus.participants', () => {
|
|
5001
|
+
const p1 = {id: 'p1', state: 'JOINED'};
|
|
5002
|
+
const p2 = {id: 'p2', state: 'LEFT'};
|
|
5003
|
+
|
|
5004
|
+
const result = createLocusFromHashTreeMessage({
|
|
5005
|
+
locusUrl: LOCUS_URL,
|
|
5006
|
+
locusStateElements: [createElement('Participant', p1), createElement('Participant', p2)],
|
|
5007
|
+
});
|
|
5008
|
+
|
|
5009
|
+
assert.deepEqual(result.locus.participants, [p1, p2]);
|
|
5010
|
+
});
|
|
5011
|
+
|
|
5012
|
+
it('pushes MediaShare elements to locus.mediaShares array', () => {
|
|
5013
|
+
const share1 = {name: 'whiteboard'};
|
|
5014
|
+
const share2 = {name: 'content'};
|
|
5015
|
+
|
|
5016
|
+
const result = createLocusFromHashTreeMessage({
|
|
5017
|
+
locusUrl: LOCUS_URL,
|
|
5018
|
+
locusStateElements: [
|
|
5019
|
+
createElement('MediaShare', share1),
|
|
5020
|
+
createElement('MediaShare', share2),
|
|
5021
|
+
],
|
|
5022
|
+
});
|
|
5023
|
+
|
|
5024
|
+
assert.deepEqual(result.locus.mediaShares, [share1, share2]);
|
|
5025
|
+
});
|
|
5026
|
+
|
|
5027
|
+
it('pushes EmbeddedApp elements to locus.embeddedApps array', () => {
|
|
5028
|
+
const app = {appId: 'app-1', state: 'STARTED'};
|
|
5029
|
+
|
|
5030
|
+
const result = createLocusFromHashTreeMessage({
|
|
5031
|
+
locusUrl: LOCUS_URL,
|
|
5032
|
+
locusStateElements: [createElement('EmbeddedApp', app)],
|
|
5033
|
+
});
|
|
5034
|
+
|
|
5035
|
+
assert.deepEqual(result.locus.embeddedApps, [app]);
|
|
5036
|
+
});
|
|
5037
|
+
|
|
5038
|
+
it('merges ControlEntry elements into locus.controls', () => {
|
|
5039
|
+
const control1 = {record: {recording: true}};
|
|
5040
|
+
const control2 = {lock: {locked: false}};
|
|
5041
|
+
|
|
5042
|
+
const result = createLocusFromHashTreeMessage({
|
|
5043
|
+
locusUrl: LOCUS_URL,
|
|
5044
|
+
locusStateElements: [
|
|
5045
|
+
createElement('ControlEntry', control1),
|
|
5046
|
+
createElement('ControlEntry', control2),
|
|
5047
|
+
],
|
|
5048
|
+
});
|
|
5049
|
+
|
|
5050
|
+
assert.deepEqual(result.locus.controls, {record: {recording: true}, lock: {locked: false}});
|
|
5051
|
+
});
|
|
5052
|
+
|
|
5053
|
+
it('spreads Locus element data onto top level but removes managed keys', () => {
|
|
5054
|
+
const locusData = {
|
|
5055
|
+
url: 'should-be-overridden',
|
|
5056
|
+
someTopLevelField: 'value',
|
|
5057
|
+
// these are managed by other ObjectTypes and should be removed
|
|
5058
|
+
links: {should: 'be removed'},
|
|
5059
|
+
info: {should: 'be removed'},
|
|
5060
|
+
fullState: {should: 'be removed'},
|
|
5061
|
+
self: {should: 'be removed'},
|
|
5062
|
+
participants: [{should: 'be removed'}],
|
|
5063
|
+
mediaShares: [{should: 'be removed'}],
|
|
5064
|
+
controls: {should: 'be removed'},
|
|
5065
|
+
embeddedApps: [{should: 'be removed'}],
|
|
5066
|
+
};
|
|
5067
|
+
|
|
5068
|
+
const result = createLocusFromHashTreeMessage({
|
|
5069
|
+
locusUrl: LOCUS_URL,
|
|
5070
|
+
locusStateElements: [createElement('Locus', locusData)],
|
|
5071
|
+
});
|
|
5072
|
+
|
|
5073
|
+
assert.equal(result.locus.someTopLevelField, 'value');
|
|
5074
|
+
assert.deepEqual(result.locus.participants, []);
|
|
5075
|
+
assert.isUndefined(result.locus.links);
|
|
5076
|
+
assert.isUndefined(result.locus.info);
|
|
5077
|
+
assert.isUndefined(result.locus.fullState);
|
|
5078
|
+
assert.isUndefined(result.locus.self);
|
|
5079
|
+
assert.isUndefined(result.locus.mediaShares);
|
|
5080
|
+
assert.isUndefined(result.locus.controls);
|
|
5081
|
+
assert.isUndefined(result.locus.embeddedApps);
|
|
5082
|
+
});
|
|
5083
|
+
|
|
5084
|
+
it('extracts Metadata element as metadata in the result', () => {
|
|
5085
|
+
const metadataData = {visibleDataSets: [{name: 'ds1', url: 'http://ds1.url'}]};
|
|
5086
|
+
const htMeta = {elementId: {type: 'Metadata', id: 99, version: 3}};
|
|
5087
|
+
|
|
5088
|
+
const result = createLocusFromHashTreeMessage({
|
|
5089
|
+
locusUrl: LOCUS_URL,
|
|
5090
|
+
locusStateElements: [{htMeta, data: metadataData}],
|
|
5091
|
+
});
|
|
5092
|
+
|
|
5093
|
+
assert.deepEqual(result.metadata, {...metadataData, htMeta});
|
|
5094
|
+
assert.isUndefined(result.locus.metadata);
|
|
5095
|
+
});
|
|
5096
|
+
|
|
5097
|
+
it('handles a message with multiple element types', () => {
|
|
5098
|
+
const selfData = {id: 'self-1'};
|
|
5099
|
+
const participantData = {id: 'p1'};
|
|
5100
|
+
const infoData = {webExMeetingId: '456'};
|
|
5101
|
+
|
|
5102
|
+
const result = createLocusFromHashTreeMessage({
|
|
5103
|
+
locusUrl: LOCUS_URL,
|
|
5104
|
+
locusStateElements: [
|
|
5105
|
+
createElement('Self', selfData),
|
|
5106
|
+
createElement('Participant', participantData),
|
|
5107
|
+
createElement('Info', infoData),
|
|
5108
|
+
],
|
|
5109
|
+
});
|
|
5110
|
+
|
|
5111
|
+
assert.deepEqual(result.locus.self, selfData);
|
|
5112
|
+
assert.deepEqual(result.locus.participants, [participantData]);
|
|
5113
|
+
assert.deepEqual(result.locus.info, infoData);
|
|
5114
|
+
assert.equal(result.locus.url, LOCUS_URL);
|
|
5115
|
+
});
|
|
5116
|
+
|
|
5117
|
+
it('ignores unknown element types', () => {
|
|
5118
|
+
const result = createLocusFromHashTreeMessage({
|
|
5119
|
+
locusUrl: LOCUS_URL,
|
|
5120
|
+
locusStateElements: [createElement('UnknownType', {foo: 'bar'})],
|
|
5121
|
+
});
|
|
5122
|
+
|
|
5123
|
+
assert.deepEqual(result.locus, {participants: [], url: LOCUS_URL});
|
|
5124
|
+
});
|
|
5125
|
+
});
|
|
5126
|
+
|
|
5127
|
+
describe('findMeetingForHashTreeMessage', () => {
|
|
5128
|
+
const deviceUrl = 'https://devices.example.com/device1';
|
|
5129
|
+
|
|
5130
|
+
function createMockMeetingCollection(meetings) {
|
|
5131
|
+
return {
|
|
5132
|
+
getAll: () => meetings,
|
|
5133
|
+
};
|
|
5134
|
+
}
|
|
5135
|
+
|
|
5136
|
+
function createMockMeeting(id, hashTreeParsersMap) {
|
|
5137
|
+
return {
|
|
5138
|
+
id,
|
|
5139
|
+
locusInfo: {
|
|
5140
|
+
hashTreeParsers: hashTreeParsersMap,
|
|
5141
|
+
},
|
|
5142
|
+
};
|
|
5143
|
+
}
|
|
5144
|
+
|
|
5145
|
+
function createSelfElement(devices) {
|
|
5146
|
+
return {
|
|
5147
|
+
htMeta: {elementId: {type: 'Self'}},
|
|
5148
|
+
data: {
|
|
5149
|
+
devices,
|
|
5150
|
+
},
|
|
5151
|
+
};
|
|
5152
|
+
}
|
|
5153
|
+
|
|
5154
|
+
it('returns the meeting when locusUrl matches a hashTreeParser directly', () => {
|
|
5155
|
+
const locusUrl = 'https://locus.example.com/loci/abc123';
|
|
5156
|
+
const parsersMap = new Map([[locusUrl, {state: 'active'}]]);
|
|
5157
|
+
const meeting = createMockMeeting('meeting1', parsersMap);
|
|
5158
|
+
const collection = createMockMeetingCollection({meeting1: meeting});
|
|
5159
|
+
|
|
5160
|
+
const message = {locusUrl, locusStateElements: []};
|
|
5161
|
+
|
|
5162
|
+
const result = findMeetingForHashTreeMessage(message, collection, deviceUrl);
|
|
5163
|
+
|
|
5164
|
+
assert.equal(result, meeting);
|
|
5165
|
+
});
|
|
5166
|
+
|
|
5167
|
+
it('returns undefined when no meeting matches and message has no locusStateElements', () => {
|
|
5168
|
+
const locusUrl = 'https://locus.example.com/loci/unknown';
|
|
5169
|
+
const parsersMap = new Map([['https://locus.example.com/loci/other', {state: 'active'}]]);
|
|
5170
|
+
const meeting = createMockMeeting('meeting1', parsersMap);
|
|
5171
|
+
const collection = createMockMeetingCollection({meeting1: meeting});
|
|
5172
|
+
|
|
5173
|
+
const message = {locusUrl};
|
|
5174
|
+
|
|
5175
|
+
const result = findMeetingForHashTreeMessage(message, collection, deviceUrl);
|
|
5176
|
+
|
|
5177
|
+
assert.isUndefined(result);
|
|
5178
|
+
});
|
|
5179
|
+
|
|
5180
|
+
it('returns undefined when no meeting matches and self element has no replaces', () => {
|
|
5181
|
+
const locusUrl = 'https://locus.example.com/loci/unknown';
|
|
5182
|
+
const parsersMap = new Map([['https://locus.example.com/loci/other', {state: 'active'}]]);
|
|
5183
|
+
const meeting = createMockMeeting('meeting1', parsersMap);
|
|
5184
|
+
const collection = createMockMeetingCollection({meeting1: meeting});
|
|
5185
|
+
|
|
5186
|
+
const selfElement = createSelfElement([{url: deviceUrl}]);
|
|
5187
|
+
const message = {locusUrl, locusStateElements: [selfElement]};
|
|
5188
|
+
|
|
5189
|
+
const result = findMeetingForHashTreeMessage(message, collection, deviceUrl);
|
|
5190
|
+
|
|
5191
|
+
assert.isUndefined(result);
|
|
5192
|
+
});
|
|
5193
|
+
|
|
5194
|
+
it('returns the meeting when locusUrl from replaces matches a hashTreeParser', () => {
|
|
5195
|
+
const oldLocusUrl = 'https://locus.example.com/loci/old';
|
|
5196
|
+
const newLocusUrl = 'https://locus.example.com/loci/new';
|
|
5197
|
+
const parsersMap = new Map([[oldLocusUrl, {state: 'active'}]]);
|
|
5198
|
+
const meeting = createMockMeeting('meeting1', parsersMap);
|
|
5199
|
+
const collection = createMockMeetingCollection({meeting1: meeting});
|
|
5200
|
+
|
|
5201
|
+
const selfElement = createSelfElement([
|
|
5202
|
+
{url: deviceUrl, replaces: [{locusUrl: oldLocusUrl}]},
|
|
5203
|
+
]);
|
|
5204
|
+
const message = {locusUrl: newLocusUrl, locusStateElements: [selfElement]};
|
|
5205
|
+
|
|
5206
|
+
const result = findMeetingForHashTreeMessage(message, collection, deviceUrl);
|
|
5207
|
+
|
|
5208
|
+
assert.equal(result, meeting);
|
|
5209
|
+
});
|
|
5210
|
+
|
|
5211
|
+
it('returns undefined when replaces locusUrl does not match any hashTreeParser', () => {
|
|
5212
|
+
const oldLocusUrl = 'https://locus.example.com/loci/old';
|
|
5213
|
+
const newLocusUrl = 'https://locus.example.com/loci/new';
|
|
5214
|
+
const parsersMap = new Map([
|
|
5215
|
+
['https://locus.example.com/loci/something-else', {state: 'active'}],
|
|
5216
|
+
]);
|
|
5217
|
+
const meeting = createMockMeeting('meeting1', parsersMap);
|
|
5218
|
+
const collection = createMockMeetingCollection({meeting1: meeting});
|
|
5219
|
+
|
|
5220
|
+
const selfElement = createSelfElement([
|
|
5221
|
+
{url: deviceUrl, replaces: [{locusUrl: oldLocusUrl}]},
|
|
5222
|
+
]);
|
|
5223
|
+
const message = {locusUrl: newLocusUrl, locusStateElements: [selfElement]};
|
|
5224
|
+
|
|
5225
|
+
const result = findMeetingForHashTreeMessage(message, collection, deviceUrl);
|
|
5226
|
+
|
|
5227
|
+
assert.isUndefined(result);
|
|
5228
|
+
});
|
|
5229
|
+
|
|
5230
|
+
it('returns undefined when meetingCollection is empty', () => {
|
|
5231
|
+
const collection = createMockMeetingCollection({});
|
|
5232
|
+
const message = {locusUrl: 'https://locus.example.com/loci/abc', locusStateElements: []};
|
|
5233
|
+
|
|
5234
|
+
const result = findMeetingForHashTreeMessage(message, collection, deviceUrl);
|
|
5235
|
+
|
|
5236
|
+
assert.isUndefined(result);
|
|
5237
|
+
});
|
|
5238
|
+
|
|
5239
|
+
it('checks multiple meetings and returns the correct one', () => {
|
|
5240
|
+
const targetLocusUrl = 'https://locus.example.com/loci/target';
|
|
5241
|
+
const meeting1 = createMockMeeting(
|
|
5242
|
+
'meeting1',
|
|
5243
|
+
new Map([['https://locus.example.com/loci/other', {state: 'active'}]])
|
|
5244
|
+
);
|
|
5245
|
+
const meeting2 = createMockMeeting(
|
|
5246
|
+
'meeting2',
|
|
5247
|
+
new Map([[targetLocusUrl, {state: 'active'}]])
|
|
5248
|
+
);
|
|
5249
|
+
const collection = createMockMeetingCollection({meeting1, meeting2});
|
|
5250
|
+
|
|
5251
|
+
const message = {locusUrl: targetLocusUrl, locusStateElements: []};
|
|
5252
|
+
|
|
5253
|
+
const result = findMeetingForHashTreeMessage(message, collection, deviceUrl);
|
|
5254
|
+
|
|
5255
|
+
assert.equal(result, meeting2);
|
|
5256
|
+
});
|
|
5257
|
+
|
|
5258
|
+
it('ignores devices that do not match deviceUrl when looking for replaces', () => {
|
|
5259
|
+
const oldLocusUrl = 'https://locus.example.com/loci/old';
|
|
5260
|
+
const newLocusUrl = 'https://locus.example.com/loci/new';
|
|
5261
|
+
const parsersMap = new Map([[oldLocusUrl, {state: 'active'}]]);
|
|
5262
|
+
const meeting = createMockMeeting('meeting1', parsersMap);
|
|
5263
|
+
const collection = createMockMeetingCollection({meeting1: meeting});
|
|
5264
|
+
|
|
5265
|
+
// self element has replaces, but on a different device
|
|
5266
|
+
const selfElement = createSelfElement([
|
|
5267
|
+
{url: 'https://devices.example.com/other-device', replaces: [{locusUrl: oldLocusUrl}]},
|
|
5268
|
+
]);
|
|
5269
|
+
const message = {locusUrl: newLocusUrl, locusStateElements: [selfElement]};
|
|
5270
|
+
|
|
5271
|
+
const result = findMeetingForHashTreeMessage(message, collection, deviceUrl);
|
|
5272
|
+
|
|
5273
|
+
assert.isUndefined(result);
|
|
5274
|
+
});
|
|
5275
|
+
|
|
5276
|
+
it('does not use self element if it is not of type Self', () => {
|
|
5277
|
+
const oldLocusUrl = 'https://locus.example.com/loci/old';
|
|
5278
|
+
const newLocusUrl = 'https://locus.example.com/loci/new';
|
|
5279
|
+
const parsersMap = new Map([[oldLocusUrl, {state: 'active'}]]);
|
|
5280
|
+
const meeting = createMockMeeting('meeting1', parsersMap);
|
|
5281
|
+
const collection = createMockMeetingCollection({meeting1: meeting});
|
|
5282
|
+
|
|
5283
|
+
// element has replaces data but is not of type Self
|
|
5284
|
+
const nonSelfElement = {
|
|
5285
|
+
htMeta: {elementId: {type: 'Participant'}},
|
|
5286
|
+
data: {
|
|
5287
|
+
devices: [{url: deviceUrl, replaces: [{locusUrl: oldLocusUrl}]}],
|
|
5288
|
+
},
|
|
5289
|
+
};
|
|
5290
|
+
const message = {locusUrl: newLocusUrl, locusStateElements: [nonSelfElement]};
|
|
5291
|
+
|
|
5292
|
+
const result = findMeetingForHashTreeMessage(message, collection, deviceUrl);
|
|
5293
|
+
|
|
5294
|
+
assert.isUndefined(result);
|
|
4216
5295
|
});
|
|
4217
5296
|
});
|
|
4218
5297
|
});
|