@webex/plugin-meetings 3.8.1 → 3.9.0-webinar5k.1

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 (296) hide show
  1. package/README.md +26 -13
  2. package/dist/breakouts/breakout.js +1 -1
  3. package/dist/breakouts/index.js +1 -1
  4. package/dist/constants.js +16 -3
  5. package/dist/constants.js.map +1 -1
  6. package/dist/controls-options-manager/enums.js +1 -0
  7. package/dist/controls-options-manager/enums.js.map +1 -1
  8. package/dist/controls-options-manager/types.js.map +1 -1
  9. package/dist/controls-options-manager/util.js +26 -0
  10. package/dist/controls-options-manager/util.js.map +1 -1
  11. package/dist/hashTree/constants.js +23 -0
  12. package/dist/hashTree/constants.js.map +1 -0
  13. package/dist/hashTree/hashTree.js +516 -0
  14. package/dist/hashTree/hashTree.js.map +1 -0
  15. package/dist/hashTree/hashTreeParser.js +521 -0
  16. package/dist/hashTree/hashTreeParser.js.map +1 -0
  17. package/dist/interpretation/index.js +1 -1
  18. package/dist/interpretation/siLanguage.js +1 -1
  19. package/dist/locus-info/controlsUtils.js +11 -3
  20. package/dist/locus-info/controlsUtils.js.map +1 -1
  21. package/dist/locus-info/index.js +331 -59
  22. package/dist/locus-info/index.js.map +1 -1
  23. package/dist/media/index.js +2 -2
  24. package/dist/media/index.js.map +1 -1
  25. package/dist/meeting/brbState.js +17 -14
  26. package/dist/meeting/brbState.js.map +1 -1
  27. package/dist/meeting/in-meeting-actions.js +5 -1
  28. package/dist/meeting/in-meeting-actions.js.map +1 -1
  29. package/dist/meeting/index.js +264 -125
  30. package/dist/meeting/index.js.map +1 -1
  31. package/dist/meeting/muteState.js +2 -5
  32. package/dist/meeting/muteState.js.map +1 -1
  33. package/dist/meeting/request.js +19 -0
  34. package/dist/meeting/request.js.map +1 -1
  35. package/dist/meeting/request.type.js.map +1 -1
  36. package/dist/meeting/util.js +8 -11
  37. package/dist/meeting/util.js.map +1 -1
  38. package/dist/meetings/index.js +6 -2
  39. package/dist/meetings/index.js.map +1 -1
  40. package/dist/member/types.js.map +1 -1
  41. package/dist/members/collection.js +13 -0
  42. package/dist/members/collection.js.map +1 -1
  43. package/dist/members/index.js +44 -23
  44. package/dist/members/index.js.map +1 -1
  45. package/dist/members/request.js +3 -3
  46. package/dist/members/request.js.map +1 -1
  47. package/dist/members/util.js +18 -6
  48. package/dist/members/util.js.map +1 -1
  49. package/dist/metrics/constants.js +1 -0
  50. package/dist/metrics/constants.js.map +1 -1
  51. package/dist/multistream/sendSlotManager.js +32 -2
  52. package/dist/multistream/sendSlotManager.js.map +1 -1
  53. package/dist/reachability/index.js +5 -10
  54. package/dist/reachability/index.js.map +1 -1
  55. package/dist/types/constants.d.ts +12 -0
  56. package/dist/types/controls-options-manager/enums.d.ts +2 -1
  57. package/dist/types/controls-options-manager/types.d.ts +4 -1
  58. package/dist/types/hashTree/constants.d.ts +8 -0
  59. package/dist/types/hashTree/hashTree.d.ts +128 -0
  60. package/dist/types/hashTree/hashTreeParser.d.ts +152 -0
  61. package/dist/types/locus-info/index.d.ts +93 -3
  62. package/dist/types/meeting/brbState.d.ts +0 -1
  63. package/dist/types/meeting/in-meeting-actions.d.ts +4 -0
  64. package/dist/types/meeting/index.d.ts +36 -3
  65. package/dist/types/meeting/request.d.ts +9 -1
  66. package/dist/types/meeting/request.type.d.ts +74 -0
  67. package/dist/types/meeting/util.d.ts +3 -3
  68. package/dist/types/member/types.d.ts +1 -0
  69. package/dist/types/members/collection.d.ts +6 -0
  70. package/dist/types/members/index.d.ts +15 -3
  71. package/dist/types/members/request.d.ts +1 -1
  72. package/dist/types/members/util.d.ts +5 -2
  73. package/dist/types/metrics/constants.d.ts +1 -0
  74. package/dist/types/multistream/sendSlotManager.d.ts +16 -0
  75. package/dist/types/reachability/index.d.ts +2 -2
  76. package/dist/webinar/index.js +1 -1
  77. package/package.json +26 -25
  78. package/src/constants.ts +16 -0
  79. package/src/controls-options-manager/enums.ts +1 -0
  80. package/src/controls-options-manager/types.ts +6 -1
  81. package/src/controls-options-manager/util.ts +31 -0
  82. package/src/hashTree/constants.ts +12 -0
  83. package/src/hashTree/hashTree.ts +460 -0
  84. package/src/hashTree/hashTreeParser.ts +556 -0
  85. package/src/locus-info/controlsUtils.ts +15 -0
  86. package/src/locus-info/index.ts +434 -58
  87. package/src/media/index.ts +2 -2
  88. package/src/meeting/brbState.ts +13 -9
  89. package/src/meeting/in-meeting-actions.ts +8 -0
  90. package/src/meeting/index.ts +193 -39
  91. package/src/meeting/muteState.ts +2 -6
  92. package/src/meeting/request.ts +16 -0
  93. package/src/meeting/request.type.ts +64 -0
  94. package/src/meeting/util.ts +17 -20
  95. package/src/meetings/index.ts +17 -3
  96. package/src/member/types.ts +1 -0
  97. package/src/members/collection.ts +11 -0
  98. package/src/members/index.ts +33 -7
  99. package/src/members/request.ts +2 -2
  100. package/src/members/util.ts +14 -3
  101. package/src/metrics/constants.ts +1 -0
  102. package/src/multistream/sendSlotManager.ts +34 -2
  103. package/src/reachability/index.ts +5 -13
  104. package/test/unit/spec/controls-options-manager/util.js +58 -0
  105. package/test/unit/spec/hashTree/hashTree.ts +394 -0
  106. package/test/unit/spec/hashTree/hashTreeParser.ts +156 -0
  107. package/test/unit/spec/locus-info/controlsUtils.js +52 -0
  108. package/test/unit/spec/locus-info/index.js +547 -54
  109. package/test/unit/spec/media/index.ts +107 -0
  110. package/test/unit/spec/meeting/brbState.ts +23 -4
  111. package/test/unit/spec/meeting/in-meeting-actions.ts +4 -0
  112. package/test/unit/spec/meeting/index.js +647 -46
  113. package/test/unit/spec/meeting/request.js +71 -0
  114. package/test/unit/spec/members/index.js +33 -10
  115. package/test/unit/spec/members/request.js +2 -2
  116. package/test/unit/spec/members/utils.js +27 -7
  117. package/test/unit/spec/multistream/sendSlotManager.ts +59 -0
  118. package/test/unit/spec/reachability/index.ts +2 -6
  119. package/dist/annotation/annotation.types.d.ts +0 -42
  120. package/dist/annotation/constants.d.ts +0 -31
  121. package/dist/annotation/index.d.ts +0 -117
  122. package/dist/breakouts/breakout.d.ts +0 -8
  123. package/dist/breakouts/collection.d.ts +0 -5
  124. package/dist/breakouts/edit-lock-error.d.ts +0 -15
  125. package/dist/breakouts/events.d.ts +0 -8
  126. package/dist/breakouts/index.d.ts +0 -5
  127. package/dist/breakouts/request.d.ts +0 -22
  128. package/dist/breakouts/utils.d.ts +0 -15
  129. package/dist/common/browser-detection.d.ts +0 -9
  130. package/dist/common/collection.d.ts +0 -48
  131. package/dist/common/config.d.ts +0 -2
  132. package/dist/common/errors/captcha-error.d.ts +0 -15
  133. package/dist/common/errors/intent-to-join.d.ts +0 -16
  134. package/dist/common/errors/join-meeting.d.ts +0 -17
  135. package/dist/common/errors/media.d.ts +0 -15
  136. package/dist/common/errors/no-meeting-info.d.ts +0 -14
  137. package/dist/common/errors/parameter.d.ts +0 -15
  138. package/dist/common/errors/password-error.d.ts +0 -15
  139. package/dist/common/errors/permission.d.ts +0 -14
  140. package/dist/common/errors/reclaim-host-role-error.d.ts +0 -60
  141. package/dist/common/errors/reclaim-host-role-error.js +0 -158
  142. package/dist/common/errors/reclaim-host-role-error.js.map +0 -1
  143. package/dist/common/errors/reclaim-host-role-errors.d.ts +0 -60
  144. package/dist/common/errors/reconnection-in-progress.d.ts +0 -9
  145. package/dist/common/errors/reconnection-in-progress.js +0 -35
  146. package/dist/common/errors/reconnection-in-progress.js.map +0 -1
  147. package/dist/common/errors/reconnection.d.ts +0 -15
  148. package/dist/common/errors/stats.d.ts +0 -15
  149. package/dist/common/errors/webex-errors.d.ts +0 -81
  150. package/dist/common/errors/webex-meetings-error.d.ts +0 -20
  151. package/dist/common/events/events-scope.d.ts +0 -17
  152. package/dist/common/events/events.d.ts +0 -12
  153. package/dist/common/events/trigger-proxy.d.ts +0 -2
  154. package/dist/common/events/util.d.ts +0 -2
  155. package/dist/common/logs/logger-config.d.ts +0 -2
  156. package/dist/common/logs/logger-proxy.d.ts +0 -2
  157. package/dist/common/logs/request.d.ts +0 -34
  158. package/dist/common/queue.d.ts +0 -32
  159. package/dist/config.d.ts +0 -73
  160. package/dist/constants.d.ts +0 -952
  161. package/dist/controls-options-manager/constants.d.ts +0 -4
  162. package/dist/controls-options-manager/enums.d.ts +0 -5
  163. package/dist/controls-options-manager/index.d.ts +0 -120
  164. package/dist/controls-options-manager/types.d.ts +0 -43
  165. package/dist/controls-options-manager/util.d.ts +0 -7
  166. package/dist/index.d.ts +0 -4
  167. package/dist/interceptors/index.d.ts +0 -2
  168. package/dist/interceptors/locusRetry.d.ts +0 -27
  169. package/dist/interpretation/collection.d.ts +0 -5
  170. package/dist/interpretation/index.d.ts +0 -5
  171. package/dist/interpretation/siLanguage.d.ts +0 -5
  172. package/dist/locus-info/controlsUtils.d.ts +0 -2
  173. package/dist/locus-info/embeddedAppsUtils.d.ts +0 -2
  174. package/dist/locus-info/fullState.d.ts +0 -2
  175. package/dist/locus-info/hostUtils.d.ts +0 -2
  176. package/dist/locus-info/index.d.ts +0 -269
  177. package/dist/locus-info/infoUtils.d.ts +0 -2
  178. package/dist/locus-info/mediaSharesUtils.d.ts +0 -2
  179. package/dist/locus-info/parser.d.ts +0 -212
  180. package/dist/locus-info/selfUtils.d.ts +0 -2
  181. package/dist/media/index.d.ts +0 -32
  182. package/dist/media/properties.d.ts +0 -108
  183. package/dist/media/util.d.ts +0 -2
  184. package/dist/mediaQualityMetrics/config.d.ts +0 -233
  185. package/dist/mediaQualityMetrics/config.js +0 -513
  186. package/dist/mediaQualityMetrics/config.js.map +0 -1
  187. package/dist/meeting/effectsState.d.ts +0 -42
  188. package/dist/meeting/effectsState.js +0 -260
  189. package/dist/meeting/effectsState.js.map +0 -1
  190. package/dist/meeting/in-meeting-actions.d.ts +0 -79
  191. package/dist/meeting/index.d.ts +0 -1622
  192. package/dist/meeting/locusMediaRequest.d.ts +0 -74
  193. package/dist/meeting/muteState.d.ts +0 -116
  194. package/dist/meeting/request.d.ts +0 -257
  195. package/dist/meeting/request.type.d.ts +0 -11
  196. package/dist/meeting/state.d.ts +0 -9
  197. package/dist/meeting/util.d.ts +0 -2
  198. package/dist/meeting/voicea-meeting.d.ts +0 -16
  199. package/dist/meeting-info/collection.d.ts +0 -20
  200. package/dist/meeting-info/index.d.ts +0 -57
  201. package/dist/meeting-info/meeting-info-v2.d.ts +0 -93
  202. package/dist/meeting-info/request.d.ts +0 -22
  203. package/dist/meeting-info/util.d.ts +0 -2
  204. package/dist/meeting-info/utilv2.d.ts +0 -2
  205. package/dist/meetings/collection.d.ts +0 -23
  206. package/dist/meetings/index.d.ts +0 -296
  207. package/dist/meetings/meetings.types.d.ts +0 -4
  208. package/dist/meetings/request.d.ts +0 -27
  209. package/dist/meetings/util.d.ts +0 -18
  210. package/dist/member/index.d.ts +0 -148
  211. package/dist/member/member.types.d.ts +0 -11
  212. package/dist/member/member.types.js +0 -18
  213. package/dist/member/member.types.js.map +0 -1
  214. package/dist/member/types.d.ts +0 -32
  215. package/dist/member/util.d.ts +0 -2
  216. package/dist/members/collection.d.ts +0 -24
  217. package/dist/members/index.d.ts +0 -308
  218. package/dist/members/request.d.ts +0 -58
  219. package/dist/members/types.d.ts +0 -25
  220. package/dist/members/util.d.ts +0 -2
  221. package/dist/metrics/config.d.ts +0 -169
  222. package/dist/metrics/config.js +0 -289
  223. package/dist/metrics/config.js.map +0 -1
  224. package/dist/metrics/constants.d.ts +0 -59
  225. package/dist/metrics/index.d.ts +0 -152
  226. package/dist/multistream/mediaRequestManager.d.ts +0 -119
  227. package/dist/multistream/receiveSlot.d.ts +0 -68
  228. package/dist/multistream/receiveSlotManager.d.ts +0 -56
  229. package/dist/multistream/remoteMedia.d.ts +0 -72
  230. package/dist/multistream/remoteMediaGroup.d.ts +0 -49
  231. package/dist/multistream/remoteMediaManager.d.ts +0 -300
  232. package/dist/multistream/sendSlotManager.d.ts +0 -69
  233. package/dist/networkQualityMonitor/index.d.ts +0 -70
  234. package/dist/networkQualityMonitor/index.js +0 -226
  235. package/dist/networkQualityMonitor/index.js.map +0 -1
  236. package/dist/peer-connection-manager/index.d.ts +0 -6
  237. package/dist/peer-connection-manager/index.js +0 -671
  238. package/dist/peer-connection-manager/index.js.map +0 -1
  239. package/dist/peer-connection-manager/util.d.ts +0 -6
  240. package/dist/peer-connection-manager/util.js +0 -110
  241. package/dist/peer-connection-manager/util.js.map +0 -1
  242. package/dist/personal-meeting-room/index.d.ts +0 -47
  243. package/dist/personal-meeting-room/request.d.ts +0 -14
  244. package/dist/personal-meeting-room/util.d.ts +0 -2
  245. package/dist/reachability/clusterReachability.d.ts +0 -109
  246. package/dist/reachability/index.d.ts +0 -139
  247. package/dist/reachability/request.d.ts +0 -35
  248. package/dist/reachability/util.d.ts +0 -8
  249. package/dist/reactions/constants.d.ts +0 -3
  250. package/dist/reactions/reactions.d.ts +0 -4
  251. package/dist/reactions/reactions.type.d.ts +0 -32
  252. package/dist/reconnection-manager/index.d.ts +0 -112
  253. package/dist/recording-controller/enums.d.ts +0 -7
  254. package/dist/recording-controller/index.d.ts +0 -193
  255. package/dist/recording-controller/util.d.ts +0 -13
  256. package/dist/roap/collection.d.ts +0 -10
  257. package/dist/roap/collection.js +0 -63
  258. package/dist/roap/collection.js.map +0 -1
  259. package/dist/roap/handler.d.ts +0 -47
  260. package/dist/roap/handler.js +0 -279
  261. package/dist/roap/handler.js.map +0 -1
  262. package/dist/roap/index.d.ts +0 -116
  263. package/dist/roap/request.d.ts +0 -35
  264. package/dist/roap/state.d.ts +0 -9
  265. package/dist/roap/state.js +0 -127
  266. package/dist/roap/state.js.map +0 -1
  267. package/dist/roap/turnDiscovery.d.ts +0 -81
  268. package/dist/roap/util.d.ts +0 -2
  269. package/dist/roap/util.js +0 -76
  270. package/dist/roap/util.js.map +0 -1
  271. package/dist/rtcMetrics/constants.d.ts +0 -4
  272. package/dist/rtcMetrics/constants.js +0 -11
  273. package/dist/rtcMetrics/constants.js.map +0 -1
  274. package/dist/rtcMetrics/index.d.ts +0 -61
  275. package/dist/rtcMetrics/index.js +0 -197
  276. package/dist/rtcMetrics/index.js.map +0 -1
  277. package/dist/statsAnalyzer/global.d.ts +0 -118
  278. package/dist/statsAnalyzer/global.js +0 -127
  279. package/dist/statsAnalyzer/global.js.map +0 -1
  280. package/dist/statsAnalyzer/index.d.ts +0 -193
  281. package/dist/statsAnalyzer/index.js +0 -1019
  282. package/dist/statsAnalyzer/index.js.map +0 -1
  283. package/dist/statsAnalyzer/mqaUtil.d.ts +0 -22
  284. package/dist/statsAnalyzer/mqaUtil.js +0 -181
  285. package/dist/statsAnalyzer/mqaUtil.js.map +0 -1
  286. package/dist/transcription/index.d.ts +0 -64
  287. package/dist/types/common/errors/reconnection-in-progress.d.ts +0 -9
  288. package/dist/types/mediaQualityMetrics/config.d.ts +0 -241
  289. package/dist/types/networkQualityMonitor/index.d.ts +0 -70
  290. package/dist/types/rtcMetrics/constants.d.ts +0 -4
  291. package/dist/types/rtcMetrics/index.d.ts +0 -71
  292. package/dist/types/statsAnalyzer/global.d.ts +0 -36
  293. package/dist/types/statsAnalyzer/index.d.ts +0 -217
  294. package/dist/types/statsAnalyzer/mqaUtil.d.ts +0 -48
  295. package/dist/webinar/collection.d.ts +0 -16
  296. package/dist/webinar/index.d.ts +0 -5
@@ -0,0 +1,394 @@
1
+ import HashTree from '@webex/plugin-meetings/src/hashTree/hashTree';
2
+ import {EMPTY_HASH} from '@webex/plugin-meetings/src/hashTree/constants';
3
+
4
+ import { expect } from "@webex/test-helper-chai";
5
+
6
+ // Define a type for the leaf data items used in tests
7
+ type LeafDataItem = {
8
+ type: string;
9
+ id: number;
10
+ version: number;
11
+ };
12
+
13
+ describe('HashTree', () => {
14
+ it('should initialize with empty leaves and hashes', () => {
15
+ const leafData: LeafDataItem[] = [];
16
+ const numLeaves = 4;
17
+ const hashTree = new HashTree(leafData, numLeaves);
18
+
19
+ expect(hashTree.leaves).to.deep.equal(new Array(numLeaves).fill(null).map(() => ({})));
20
+ expect(hashTree.leafHashes).to.deep.equal(new Array(numLeaves).fill(EMPTY_HASH));
21
+ expect(hashTree.getLeafCount()).to.equal(numLeaves);
22
+ expect(hashTree.getTotalItemCount()).to.equal(0);
23
+ });
24
+
25
+ it('constructor should allow 0 leaves', () => {
26
+ const leafData: LeafDataItem[] = [];
27
+ const numLeaves = 0;
28
+ const hashTree = new HashTree(leafData, numLeaves);
29
+ expect(hashTree.getLeafCount()).to.equal(0);
30
+ expect(hashTree.getRootHash()).to.equal(EMPTY_HASH);
31
+ expect(hashTree.getHashes()).to.deep.equal([EMPTY_HASH]);
32
+ });
33
+
34
+ it('number of leaves must be 0 or a power of 2', () => {
35
+ const leafData: LeafDataItem[] = [];
36
+ const numLeaves = 3; // Not a power of 2
37
+ expect(() => new HashTree(leafData, numLeaves)).to.throw('Number of leaves must be a power of 2, saw 3');
38
+ const numLeavesNegative = -1;
39
+ expect(() => new HashTree(leafData, numLeavesNegative)).to.throw('Number of leaves must be a power of 2, saw -1');
40
+ });
41
+
42
+ it('should have the correct hashes after putting ObjectIds using constructor', () => {
43
+ const oids: LeafDataItem[] = [
44
+ {type: 'participant', id: 1, version: 3} // Hashes to bucket 1 % 4 = 1
45
+ ];
46
+ // numLeaves is 4. Item id 1 % 4 = 1. So, leafHashes[1] will be updated.
47
+ // leafHashes[0], leafHashes[2], leafHashes[3] remain EMPTY_HASH.
48
+ const tree = new HashTree(oids, 4);
49
+
50
+ // These are the expected hash values from the Java reference for a similar structure.
51
+ // The actual values depend on the specific XXHash128 implementation and input serialization.
52
+ // For this test, we'll use the previously provided values, assuming they are correct for the TS implementation.
53
+ expect(tree.getHashes()).to.deep.equal([
54
+ "24a75d115a0a90ddb376a02b435c780f", // Root hash
55
+ "457eeb22808eadfcff92ee47d67acbbf", // Internal node (children: leaf 0, leaf 1)
56
+ "b113a76304e3a7121afecfe1606ee1c1", // Internal node (children: leaf 2, leaf 3)
57
+ EMPTY_HASH, // Leaf 0 hash (empty)
58
+ "42df811f5a902c5b6bfcf50c7004e275", // Leaf 1 hash (for item {type: 'participant', id: 1, version: 3})
59
+ EMPTY_HASH, // Leaf 2 hash (empty)
60
+ EMPTY_HASH // Leaf 3 hash (empty)
61
+ ]);
62
+ expect(tree.getRootHash()).to.equal("24a75d115a0a90ddb376a02b435c780f");
63
+ });
64
+
65
+ it('should have the correct hashes after putting multiple ObjectIds using constructor', () => {
66
+ const oids: LeafDataItem[] = [
67
+ {type: "typeA", id: 1, version: 3}, // Leaf 1 (1 % 4 = 1)
68
+ {type: "typeA", id: 6, version: 2}, // Leaf 2 (6 % 4 = 2)
69
+ {type: "typeA", id: 7, version: 1}, // Leaf 3 (7 % 4 = 3)
70
+ {type: "typeB", id: 11, version: 4},// Leaf 3 (11 % 4 = 3)
71
+ ];
72
+ const tree = new HashTree(oids, 4);
73
+
74
+ // Corrected expected hashes based on the test failure output
75
+ expect(tree.getHashes()).to.deep.equal([
76
+ "c8415198d4abca6f885fe974e9b3729d", // Root
77
+ "457eeb22808eadfcff92ee47d67acbbf", // Internal node (L0, L1)
78
+ "5c9ba182a069c16a77a1928fce52dad8", // Internal node (L2, L3)
79
+ EMPTY_HASH, // Leaf 0 (empty)
80
+ "42df811f5a902c5b6bfcf50c7004e275", // Leaf 1 (item id 1)
81
+ "feb384d8ac6374ffdbee92a9f48f2b40", // Leaf 2 (item id 6)
82
+ "ebfa4f7e104e1e30fbb6b8857ccb685d" // Leaf 3 (items id 7, 11)
83
+ ]);
84
+ expect(tree.getRootHash()).to.equal("c8415198d4abca6f885fe974e9b3729d");
85
+ });
86
+
87
+ it('should putItems and compute hashes correctly', () => {
88
+ const initialLeafData: LeafDataItem[] = [];
89
+ const numLeaves = 4;
90
+ const hashTree = new HashTree(initialLeafData, numLeaves);
91
+
92
+ const itemsToPut: LeafDataItem[] = [
93
+ { type: 'participant', id: 1, version: 1 }, // bucket 1
94
+ { type: 'participant', id: 2, version: 1 }, // bucket 2
95
+ ];
96
+ const results = hashTree.putItems(itemsToPut);
97
+
98
+ expect(results).to.deep.equal([true, true]);
99
+ expect(hashTree.leaves[1]['participant'][1]).to.deep.equal({ type: 'participant', id: 1, version: 1 });
100
+ expect(hashTree.leaves[2]['participant'][2]).to.deep.equal({ type: 'participant', id: 2, version: 1 });
101
+ expect(hashTree.leafHashes[0]).to.equal(EMPTY_HASH);
102
+ expect(hashTree.leafHashes[1]).to.not.equal(EMPTY_HASH);
103
+ expect(hashTree.leafHashes[2]).to.not.equal(EMPTY_HASH);
104
+ expect(hashTree.leafHashes[3]).to.equal(EMPTY_HASH);
105
+ expect(hashTree.getTotalItemCount()).to.equal(2);
106
+ });
107
+
108
+ it('putItem should add a single item and update hash', () => {
109
+ const hashTree = new HashTree([], 2);
110
+ const item: LeafDataItem = {type: 'data', id: 3, version: 1}; // bucket 1
111
+
112
+ const result = hashTree.putItem(item);
113
+ expect(result).to.be.true;
114
+ expect(hashTree.leaves[1]['data'][3]).to.deep.equal(item);
115
+ expect(hashTree.leafHashes[1]).to.not.equal(EMPTY_HASH);
116
+ expect(hashTree.getTotalItemCount()).to.equal(1);
117
+
118
+ const itemSameVersion = {type: 'data', id: 3, version: 1};
119
+ const resultSame = hashTree.putItem(itemSameVersion);
120
+ expect(resultSame).to.be.false; // Not updated as version is not newer
121
+
122
+ const itemNewerVersion = {type: 'data', id: 3, version: 2};
123
+ const resultNewer = hashTree.putItem(itemNewerVersion);
124
+ expect(resultNewer).to.be.true;
125
+ expect(hashTree.leaves[1]['data'][3].version).to.equal(2);
126
+ });
127
+
128
+ it('putItem should return false for tree with 0 leaves', () => {
129
+ const hashTree = new HashTree([], 0);
130
+ const item: LeafDataItem = {type: 'data', id: 1, version: 1};
131
+ expect(hashTree.putItem(item)).to.be.false;
132
+ });
133
+
134
+ it('putItems should return array of false for tree with 0 leaves if items are provided', () => {
135
+ const hashTree = new HashTree([], 0);
136
+ const items: LeafDataItem[] = [{type: 'data', id: 1, version: 1}];
137
+ expect(hashTree.putItems(items)).to.deep.equal([false]);
138
+ });
139
+
140
+
141
+ it('should have correct root hash after putting one item', () => {
142
+ const leafData: LeafDataItem[] = [{type: 'participant', id: 1, version: 10}]; // bucket 1 (1 % 2 = 1)
143
+ const numLeaves = 2;
144
+ const hashTree = new HashTree(leafData, numLeaves);
145
+
146
+ expect(hashTree.leaves[1]['participant'][1]).to.deep.equal({
147
+ type: 'participant',
148
+ id: 1,
149
+ version: 10,
150
+ });
151
+ // This hash is from the original test.
152
+ expect(hashTree.getRootHash()).to.equal('e1cb70c75b488d87cbc8f74934a4290b');
153
+ });
154
+
155
+ it('removeItem should remove an item and update hash', () => {
156
+ const items: LeafDataItem[] = [{type: 'p', id: 1, version: 1}];
157
+ const hashTree = new HashTree(items, 2); // item in bucket 1
158
+ expect(hashTree.getTotalItemCount()).to.equal(1);
159
+ const oldRootHash = hashTree.getRootHash();
160
+
161
+ const result = hashTree.removeItem({type: 'p', id: 1, version: 1});
162
+ expect(result).to.be.true;
163
+ expect(hashTree.getTotalItemCount()).to.equal(0);
164
+ expect(hashTree.leaves[1]['p']).to.be.undefined;
165
+ expect(hashTree.leafHashes[1]).to.equal(EMPTY_HASH);
166
+ expect(hashTree.getRootHash()).to.not.equal(oldRootHash);
167
+ // After removing the only item, it should be like an empty tree with 2 leaves
168
+ const emptyTree = new HashTree([], 2);
169
+ expect(hashTree.getRootHash()).to.equal(emptyTree.getRootHash());
170
+
171
+ const resultNotFound = hashTree.removeItem({type: 'p', id: 1, version: 1});
172
+ expect(resultNotFound).to.be.false;
173
+ });
174
+
175
+ it('removeItem should only remove if version is <= existing (as per new logic)', () => {
176
+ const itemV1 = {type: 'test', id: 5, version: 1}; // bucket 1 (5%2=1)
177
+ const hashTree = new HashTree([itemV1], 2);
178
+
179
+ // Try to remove with older version - should fail if strict "version must be >=" is used for removal item
180
+ // The current removeItem logic: existingItem.version <= item.version for removal
181
+ let removed = hashTree.removeItem({type: 'test', id: 5, version: 0});
182
+ expect(removed).to.be.false;
183
+
184
+ removed = hashTree.removeItem({type: 'test', id: 5, version: 1}); // same version
185
+ expect(removed).to.be.true;
186
+ expect(hashTree.getTotalItemCount()).to.equal(0);
187
+
188
+ hashTree.putItem(itemV1); // re-add
189
+ expect(hashTree.getTotalItemCount()).to.equal(1);
190
+ removed = hashTree.removeItem({type: 'test', id: 5, version: 2}); // newer version in request
191
+ expect(removed).to.be.true;
192
+ expect(hashTree.getTotalItemCount()).to.equal(0);
193
+ });
194
+
195
+ it('removeItem should return false for tree with 0 leaves', () => {
196
+ const hashTree = new HashTree([], 0);
197
+ const item: LeafDataItem = {type: 'data', id: 1, version: 1};
198
+ expect(hashTree.removeItem(item)).to.be.false;
199
+ });
200
+
201
+ it('removeItems should process multiple items', () => {
202
+ const items: LeafDataItem[] = [
203
+ {type: 'a', id: 1, version: 2}, // bucket 1
204
+ {type: 'b', id: 2, version: 2} // bucket 0
205
+ ];
206
+ const hashTree = new HashTree(items, 2);
207
+ expect(hashTree.getTotalItemCount()).to.equal(2);
208
+
209
+ const itemsToRemove: LeafDataItem[] = [
210
+ {type: 'a', id: 1, version: 3}, // remove with newer version (original logic)
211
+ {type: 'b', id: 2, version: 1}, // attempt remove with older version (should fail by original logic)
212
+ {type: 'c', id: 3, version: 1} // item not present
213
+ ];
214
+ const results = hashTree.removeItems(itemsToRemove);
215
+ expect(results).to.deep.equal([true, false, false]);
216
+ expect(hashTree.getTotalItemCount()).to.equal(1); // item 'b' should remain
217
+ expect(hashTree.leaves[1]['a']).to.be.undefined;
218
+ expect(hashTree.leaves[0]['b'][2]).to.deep.equal({type: 'b', id: 2, version: 2});
219
+ });
220
+
221
+ it('removeItems should return array of false for tree with 0 leaves if items are provided', () => {
222
+ const hashTree = new HashTree([], 0);
223
+ const items: LeafDataItem[] = [{type: 'data', id: 1, version: 1}];
224
+ expect(hashTree.removeItems(items)).to.deep.equal([false]);
225
+ });
226
+
227
+ it('returns the correct root hash for an empty tree (0 leaves)', () => {
228
+ const hashTree = new HashTree([], 0);
229
+ expect(hashTree.getRootHash()).to.equal(EMPTY_HASH);
230
+ });
231
+
232
+ it('returns the correct root hash for an empty tree with 2 leaves', () => {
233
+ const hashTree = new HashTree([], 2);
234
+ // This hash is from the original test.
235
+ expect(hashTree.getRootHash()).to.equal('b113a76304e3a7121afecfe1606ee1c1');
236
+ });
237
+
238
+ it('returns the correct root hash for an empty tree with 4 leaves', () => {
239
+ const hashTree = new HashTree([], 4);
240
+ // This hash is from the original test.
241
+ expect(hashTree.getRootHash()).to.equal('b5df9b92242752424d87053a14e6222d');
242
+ });
243
+
244
+ describe('getLeafData', () => {
245
+ it('should return items from a specific leaf', () => {
246
+ const items: LeafDataItem[] = [
247
+ {type: 't1', id: 0, version: 1}, // leaf 0
248
+ {type: 't2', id: 1, version: 1}, // leaf 1
249
+ {type: 't1', id: 2, version: 1}, // leaf 0
250
+ ];
251
+ const tree = new HashTree(items, 2);
252
+ const leaf0Data = tree.getLeafData(0);
253
+ expect(leaf0Data).to.have.deep.members([
254
+ {type: 't1', id: 0, version: 1},
255
+ {type: 't1', id: 2, version: 1}
256
+ ]);
257
+ expect(leaf0Data.length).to.equal(2);
258
+
259
+ const leaf1Data = tree.getLeafData(1);
260
+ expect(leaf1Data).to.have.deep.members([{type: 't2', id: 1, version: 1}]);
261
+ expect(leaf1Data.length).to.equal(1);
262
+ });
263
+
264
+ it('should return empty array for invalid leaf index or empty leaf', () => {
265
+ const tree = new HashTree([{type: 't', id: 0, version: 1}], 2); // item in leaf 0
266
+ expect(tree.getLeafData(1)).to.deep.equal([]); // leaf 1 is empty
267
+ expect(tree.getLeafData(2)).to.deep.equal([]); // invalid index
268
+ expect(tree.getLeafData(-1)).to.deep.equal([]); // invalid index
269
+ });
270
+
271
+ it('should return empty array for tree with 0 leaves', () => {
272
+ const tree = new HashTree([], 0);
273
+ expect(tree.getLeafData(0)).to.deep.equal([]);
274
+ });
275
+ });
276
+
277
+ describe('resize', () => {
278
+ it('should resize the tree and redistribute items', () => {
279
+ const items: LeafDataItem[] = [
280
+ {type: 'a', id: 0, version: 1}, // old leaf 0 (0%2=0)
281
+ {type: 'b', id: 1, version: 1}, // old leaf 1 (1%2=1)
282
+ {type: 'c', id: 2, version: 1}, // old leaf 0 (2%2=0)
283
+ {type: 'd', id: 3, version: 1}, // old leaf 1 (3%2=1)
284
+ ];
285
+ const tree = new HashTree(items, 2);
286
+ expect(tree.getLeafCount()).to.equal(2);
287
+ expect(tree.getTotalItemCount()).to.equal(4);
288
+ const originalRootHash = tree.getRootHash();
289
+
290
+ const resized = tree.resize(4);
291
+ expect(resized).to.be.true;
292
+ expect(tree.getLeafCount()).to.equal(4);
293
+ expect(tree.getTotalItemCount()).to.equal(4); // count should remain same
294
+
295
+ // Check redistribution
296
+ // id:0 -> 0%4 = 0
297
+ // id:1 -> 1%4 = 1
298
+ // id:2 -> 2%4 = 2
299
+ // id:3 -> 3%4 = 3
300
+ expect(tree.getLeafData(0)).to.deep.include({type: 'a', id: 0, version: 1});
301
+ expect(tree.getLeafData(1)).to.deep.include({type: 'b', id: 1, version: 1});
302
+ expect(tree.getLeafData(2)).to.deep.include({type: 'c', id: 2, version: 1});
303
+ expect(tree.getLeafData(3)).to.deep.include({type: 'd', id: 3, version: 1});
304
+ expect(tree.getRootHash()).to.not.equal(originalRootHash); // Hash should change
305
+ });
306
+
307
+ it('should return false if size does not change', () => {
308
+ const tree = new HashTree([], 4);
309
+ expect(tree.resize(4)).to.be.false;
310
+ });
311
+
312
+ it('should throw error for invalid new number of leaves', () => {
313
+ const tree = new HashTree([], 4);
314
+ expect(() => tree.resize(3)).to.throw('New number of leaves must be 0 or a power of 2');
315
+ });
316
+
317
+ it('should handle resize to 0 leaves', () => {
318
+ const items: LeafDataItem[] = [{type: 'a', id: 0, version: 1}];
319
+ const tree = new HashTree(items, 2);
320
+ expect(tree.getTotalItemCount()).to.equal(1);
321
+ tree.resize(0);
322
+ expect(tree.getLeafCount()).to.equal(0);
323
+ expect(tree.getTotalItemCount()).to.equal(0);
324
+ expect(tree.getRootHash()).to.equal(EMPTY_HASH);
325
+ expect(tree.leaves.length).to.equal(0);
326
+ expect(tree.leafHashes.length).to.equal(0);
327
+ });
328
+
329
+ it('should handle resize from 0 leaves', () => {
330
+ const tree = new HashTree([], 0);
331
+ tree.resize(2);
332
+ expect(tree.getLeafCount()).to.equal(2);
333
+ expect(tree.getTotalItemCount()).to.equal(0);
334
+ const emptyTree = new HashTree([], 2);
335
+ expect(tree.getRootHash()).to.equal(emptyTree.getRootHash());
336
+ });
337
+ });
338
+
339
+ describe('diffHashes', () => {
340
+ it('should return empty array if hashes are identical', () => {
341
+ const items: LeafDataItem[] = [{type: 'x', id: 1, version: 1}];
342
+ const tree1 = new HashTree(items, 2);
343
+ const tree2 = new HashTree(items, 2);
344
+ expect(tree1.diffHashes(tree2.getHashes())).to.deep.equal([]);
345
+ });
346
+
347
+ it('should return differing leaf indices', () => {
348
+ const tree1 = new HashTree([{type: 'x', id: 0, version: 1}], 4); // item in leaf 0
349
+ const tree2 = new HashTree([{type: 'y', id: 1, version: 1}], 4); // item in leaf 1
350
+ // tree1: leaf 0 has item, leaves 1,2,3 empty
351
+ // tree2: leaf 1 has item, leaves 0,2,3 empty
352
+ // Expected diffs: leaf 0 (present in 1, not in 2), leaf 1 (present in 2, not in 1)
353
+ const diff = tree1.diffHashes(tree2.getHashes());
354
+ expect(diff).to.include.members([0, 1]);
355
+ // If one leaf's hash is EMPTY_HASH and the other's is a computed hash, they are different.
356
+ });
357
+
358
+ it('should return all leaf indices if externalHashes is for a different structure (e.g. too short)', () => {
359
+ const tree = new HashTree([{type: 'x', id: 0, version: 1}], 4);
360
+ const externalHashesShort = [EMPTY_HASH, EMPTY_HASH]; // Too short for 4 leaves + internal nodes
361
+ expect(tree.diffHashes(externalHashesShort)).to.deep.equal([0,1,2,3]);
362
+ });
363
+
364
+ it('should handle diff for 0-leaf trees', () => {
365
+ const tree0 = new HashTree([], 0);
366
+ expect(tree0.diffHashes([EMPTY_HASH])).to.deep.equal([]);
367
+ expect(tree0.diffHashes(["some_other_hash"])).to.deep.equal([]); // No leaves to differ
368
+ const tree2 = new HashTree([],2);
369
+ // Comparing a 0-leaf tree with a 2-leaf tree's hashes
370
+ expect(tree0.diffHashes(tree2.getHashes())).to.deep.equal([]);
371
+ });
372
+
373
+ it('should correctly identify differences when one leaf changes', () => {
374
+ const initialItems: LeafDataItem[] = [
375
+ { type: 'a', id: 0, version: 1 }, // leaf 0
376
+ { type: 'b', id: 1, version: 1 } // leaf 1
377
+ ];
378
+ const tree1 = new HashTree(initialItems, 2);
379
+ const tree1Hashes = tree1.getHashes();
380
+
381
+ const modifiedItems: LeafDataItem[] = [
382
+ { type: 'a', id: 0, version: 1 }, // leaf 0 (same)
383
+ { type: 'b', id: 1, version: 2 } // leaf 1 (changed version)
384
+ ];
385
+ const tree2 = new HashTree(modifiedItems, 2);
386
+
387
+ const diff1_2 = tree1.diffHashes(tree2.getHashes());
388
+ expect(diff1_2).to.deep.equal([1]); // Leaf 1 should differ
389
+
390
+ const diff2_1 = tree2.diffHashes(tree1Hashes);
391
+ expect(diff2_1).to.deep.equal([1]);
392
+ });
393
+ });
394
+ });
@@ -0,0 +1,156 @@
1
+ import HashTreeParser from '@webex/plugin-meetings/src/hashTree/hashTreeParser';
2
+ import HashTree from '@webex/plugin-meetings/src/hashTree/hashTree';
3
+ import { expect } from "@webex/test-helper-chai";
4
+
5
+ const exampleInitialLocus = {
6
+ dataSets: [
7
+ {
8
+ url: 'https://locus-a.wbx2.com/locus/api/v1/loci/97d64a5f/datasets/main',
9
+ root: '9bb9d5a911a74d53a915b4dfbec7329f',
10
+ version: 51118,
11
+ leafCount: 16,
12
+ name: 'main',
13
+ idleMs: 1000,
14
+ backoff: {maxMs: 1000, exponent: 2}
15
+ },
16
+ {
17
+ url: 'https://locus-a.wbx2.com/locus/api/v1/loci/97d64a5f/participant/713e9f99/datasets/self',
18
+ root: '5b8cc7ffda1346d2bfb1c0b60b8ab601',
19
+ version: 89891,
20
+ leafCount: 1,
21
+ name: 'self',
22
+ idleMs: 1000,
23
+ backoff: {maxMs: 1000, exponent: 2}
24
+ },
25
+ {
26
+ url: 'https://locus-a.wbx2.com/locus/api/v1/loci/97d64a5f/datasets/atd-unmuted',
27
+ root: '9279d2e149da43a1b8e2cd7cbf77f9f0',
28
+ version: 91277,
29
+ leafCount: 16,
30
+ name: 'atd-unmuted',
31
+ idleMs: 1000,
32
+ backoff: {maxMs: 1000, exponent: 2}
33
+ },
34
+ ],
35
+ locus: {
36
+ url: 'https://locus-a.wbx2.com/locus/api/v1/loci/97d64a5f',
37
+ htMeta: {
38
+ elementId: {
39
+ type: 'LOCUS',
40
+ id: 0,
41
+ version: 5678,
42
+ },
43
+ dataSetNames: ['main'],
44
+ },
45
+ participants: [
46
+ {
47
+ url: 'https://locus-a.wbx2.com/locus/api/v1/loci/97d64a5f/participant/11941033',
48
+ person: {},
49
+ htMeta: {
50
+ elementId: {
51
+ type: 'PARTICIPANT',
52
+ id: 14,
53
+ version: 5678,
54
+ },
55
+ dataSetNames: ['atd-active', 'attendees', 'atd-unmuted'],
56
+ },
57
+ },
58
+ ],
59
+ self: {
60
+ url: 'https://locus-a.wbx2.com/locus/api/v1/loci/97d64a5f/participant/11941033',
61
+ visibleDataSets: ['main', 'self', 'atd-unmuted'],
62
+ person: {},
63
+ htMeta: {
64
+ elementId: {
65
+ type: 'SELF',
66
+ id: 4,
67
+ version: 5678,
68
+ },
69
+ dataSetNames: ['self'],
70
+ },
71
+ },
72
+ },
73
+ };
74
+
75
+ describe('HashTreeParser', () => {
76
+ it('should correctly initialize trees from initialLocus data', () => {
77
+ const parser = new HashTreeParser({initialLocus: exampleInitialLocus, webexRequest: () => Promise.resolve(), locusInfoUpdateCallback : () => {}});
78
+
79
+ // Check that the correct number of trees are created
80
+ expect(Object.keys(parser.dataSets).length).to.equal(3);
81
+
82
+ // Verify the 'main' tree
83
+ const mainTree = parser.dataSets.main.hashTree;
84
+ expect(mainTree).to.be.instanceOf(HashTree);
85
+ const expectedMainLeaves = new Array(16).fill(null).map(() => ({}));
86
+ expectedMainLeaves[0 % 16] = { LOCUS: { 0: { type: 'LOCUS', id: 0, version: 5678 } } };
87
+ expect(mainTree.leaves).to.deep.equal(expectedMainLeaves);
88
+ expect(mainTree.numLeaves).to.equal(16);
89
+
90
+ // Verify the 'self' tree
91
+ const selfTree = parser.dataSets.self.hashTree;
92
+ expect(selfTree).to.be.instanceOf(HashTree);
93
+ const expectedSelfLeaves = new Array(1).fill(null).map(() => ({}));
94
+ expectedSelfLeaves[4 % 1] = { SELF: { 4: { type: 'SELF', id: 4, version: 5678 } } };
95
+ expect(selfTree.leaves).to.deep.equal(expectedSelfLeaves);
96
+ expect(selfTree.numLeaves).to.equal(1);
97
+
98
+ // Verify the 'atd-unmuted' tree
99
+ const atdUnmutedTree = parser.dataSets['atd-unmuted'].hashTree;
100
+ expect(atdUnmutedTree).to.be.instanceOf(HashTree);
101
+ const expectedAtdUnmutedLeaves = new Array(16).fill(null).map(() => ({}));
102
+ expectedAtdUnmutedLeaves[14 % 16] = { PARTICIPANT: { 14: { type: 'PARTICIPANT', id: 14, version: 5678 } } };
103
+ expect(atdUnmutedTree.leaves).to.deep.equal(expectedAtdUnmutedLeaves);
104
+ expect(atdUnmutedTree.numLeaves).to.equal(16);
105
+
106
+ // Ensure no other trees were created
107
+ expect(parser.dataSets['atd-active']).to.be.undefined;
108
+ expect(parser.dataSets.attendees).to.be.undefined;
109
+ });
110
+
111
+ it('should handle datasets with no corresponding metadata found', () => {
112
+ const modifiedLocus = JSON.parse(JSON.stringify(exampleInitialLocus));
113
+ // Remove a participant meta to simulate missing data for 'atd-unmuted'
114
+ modifiedLocus.locus.participants = [];
115
+ // Add a new dataset that won't have corresponding metadata
116
+ modifiedLocus.dataSets.push({
117
+ url: 'https://locus-a.wbx2.com/locus/api/v1/loci/97d64a5f/datasets/empty-set',
118
+ root: 'f00f00f00f00f00f00f00f00f00f00f0',
119
+ version: 1,
120
+ leafCount: 4,
121
+ name: 'empty-set',
122
+ });
123
+
124
+
125
+ const parser = new HashTreeParser({initialLocus: modifiedLocus, webexRequest: () => Promise.resolve(), locusInfoUpdateCallback : () => {}});
126
+
127
+ expect(Object.keys(parser.dataSets).length).to.equal(4); // main, self, atd-unmuted (now empty), empty-set
128
+
129
+ // 'main' and 'self' should be populated as before
130
+ const mainTree = parser.dataSets.main.hashTree;
131
+ const expectedMainLeaves = new Array(16).fill(null).map(() => ({}));
132
+ expectedMainLeaves[0 % 16] = { LOCUS: { 0: { type: 'LOCUS', id: 0, version: 5678 } } };
133
+ expect(mainTree.leaves).to.deep.equal(expectedMainLeaves);
134
+ expect(mainTree.numLeaves).to.equal(16);
135
+
136
+ const selfTree = parser.dataSets.self.hashTree;
137
+ const expectedSelfLeaves = new Array(1).fill(null).map(() => ({}));
138
+ expectedSelfLeaves[4 % 1] = { SELF: { 4: { type: 'SELF', id: 4, version: 5678 } } };
139
+ expect(selfTree.leaves).to.deep.equal(expectedSelfLeaves);
140
+ expect(selfTree.numLeaves).to.equal(1);
141
+
142
+ // 'atd-unmuted' metadata was removed from locus, so leaves should be empty
143
+ const atdUnmutedTree = parser.dataSets['atd-unmuted'].hashTree;
144
+ expect(atdUnmutedTree).to.be.instanceOf(HashTree);
145
+ const expectedAtdUnmutedEmptyLeaves = new Array(16).fill(null).map(() => ({}));
146
+ expect(atdUnmutedTree.leaves).to.deep.equal(expectedAtdUnmutedEmptyLeaves);
147
+ expect(atdUnmutedTree.numLeaves).to.equal(16); // leafCount from dataSet definition
148
+
149
+ // 'empty-set' was added to dataSets but has no metadata in locus
150
+ const emptySetTree = parser.dataSets['empty-set'].hashTree;
151
+ expect(emptySetTree).to.be.instanceOf(HashTree);
152
+ const expectedEmptySetLeaves = new Array(4).fill(null).map(() => ({})); // leafCount is 4
153
+ expect(emptySetTree.leaves).to.deep.equal(expectedEmptySetLeaves);
154
+ expect(emptySetTree.numLeaves).to.equal(4);
155
+ });
156
+ });
@@ -164,6 +164,14 @@ describe('plugin-meetings', () => {
164
164
 
165
165
  assert.equal(parsedControls.rdcControl.enabled, newControls.rdcControl.enabled);
166
166
  });
167
+
168
+ it('should parse the pollingQAControl control', () => {
169
+ const newControls = {pollingQAControl: {enabled: true}};
170
+
171
+ const parsedControls = ControlsUtils.parse(newControls);
172
+
173
+ assert.equal(parsedControls.pollingQAControl.enabled, newControls.pollingQAControl.enabled);
174
+ });
167
175
 
168
176
  describe('videoEnabled', () => {
169
177
  it('returns expected', () => {
@@ -411,6 +419,50 @@ describe('plugin-meetings', () => {
411
419
  assert.equal(updates.hasRemoteDesktopControlChanged, true);
412
420
  });
413
421
 
422
+ it('returns hasPollingQAControlChanged = true when changed', () => {
423
+ const newControls = {pollingQAControl: {enabled: true}};
424
+
425
+ const {updates} = ControlsUtils.getControls(defaultControls, newControls);
426
+
427
+ assert.equal(updates.hasPollingQAControlChanged, true);
428
+ });
429
+
430
+ it('returns false when previous spoken language is undefined and current is a invalid value', () => {
431
+ const previous = { transcribe: undefined };
432
+ const current = { transcribe: { spokenLanguage: null } };
433
+
434
+ const {updates} = ControlsUtils.getControls(previous, current);
435
+
436
+ assert.equal(updates.hasTranscribeSpokenLanguageChanged, false);
437
+ });
438
+
439
+ it('detects spoken language change when previous is undefined and current is a valid value', () => {
440
+ const previous = { transcribe: undefined };
441
+ const current = { transcribe: { spokenLanguage: 'en-US' } };
442
+
443
+ const {updates} = ControlsUtils.getControls(previous, current);
444
+
445
+ assert.equal(updates.hasTranscribeSpokenLanguageChanged, true);
446
+ });
447
+
448
+ it('returns false when spoken language changes to a same value', () => {
449
+ const previous = { transcribe: {caption: true, spokenLanguage: 'en-US' } };
450
+ const current = { transcribe: {caption: true, spokenLanguage: 'en-US' } };
451
+
452
+ const {updates} = ControlsUtils.getControls(previous, current);
453
+
454
+ assert.equal(updates.hasTranscribeSpokenLanguageChanged, false);
455
+ });
456
+
457
+ it('returns true when spoken language changes to a different value', () => {
458
+ const previous = { transcribe: {caption: true, spokenLanguage: 'en-US' } };
459
+ const current = { transcribe: {caption: true, spokenLanguage: 'fr-FR' } };
460
+
461
+ const {updates} = ControlsUtils.getControls(previous, current);
462
+
463
+ assert.equal(updates.hasTranscribeSpokenLanguageChanged, true);
464
+ });
465
+
414
466
  describe('videoEnabled', () => {
415
467
  const testVideoEnabled = (oldControls, newControls, updatedProperty) => {
416
468
  const result = ControlsUtils.getControls(oldControls, newControls);