@webex/plugin-meetings 3.7.0-next.9 → 3.7.0-wxcc.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 (124) hide show
  1. package/dist/annotation/index.js +17 -0
  2. package/dist/annotation/index.js.map +1 -1
  3. package/dist/breakouts/breakout.js +1 -1
  4. package/dist/breakouts/index.js +1 -1
  5. package/dist/common/errors/join-forbidden-error.js +52 -0
  6. package/dist/common/errors/join-forbidden-error.js.map +1 -0
  7. package/dist/common/errors/{webinar-registration-error.js → join-webinar-error.js} +12 -12
  8. package/dist/common/errors/join-webinar-error.js.map +1 -0
  9. package/dist/common/errors/multistream-not-supported-error.js +53 -0
  10. package/dist/common/errors/multistream-not-supported-error.js.map +1 -0
  11. package/dist/config.js +1 -1
  12. package/dist/config.js.map +1 -1
  13. package/dist/constants.js +46 -5
  14. package/dist/constants.js.map +1 -1
  15. package/dist/index.js +16 -11
  16. package/dist/index.js.map +1 -1
  17. package/dist/interpretation/index.js +1 -1
  18. package/dist/interpretation/siLanguage.js +1 -1
  19. package/dist/locus-info/index.js +14 -3
  20. package/dist/locus-info/index.js.map +1 -1
  21. package/dist/locus-info/selfUtils.js +30 -17
  22. package/dist/locus-info/selfUtils.js.map +1 -1
  23. package/dist/meeting/in-meeting-actions.js +2 -0
  24. package/dist/meeting/in-meeting-actions.js.map +1 -1
  25. package/dist/meeting/index.js +960 -832
  26. package/dist/meeting/index.js.map +1 -1
  27. package/dist/meeting/locusMediaRequest.js +9 -0
  28. package/dist/meeting/locusMediaRequest.js.map +1 -1
  29. package/dist/meeting/request.js +30 -0
  30. package/dist/meeting/request.js.map +1 -1
  31. package/dist/meeting/request.type.js.map +1 -1
  32. package/dist/meeting/util.js +16 -16
  33. package/dist/meeting/util.js.map +1 -1
  34. package/dist/meeting-info/meeting-info-v2.js +96 -33
  35. package/dist/meeting-info/meeting-info-v2.js.map +1 -1
  36. package/dist/meeting-info/utilv2.js +1 -1
  37. package/dist/meeting-info/utilv2.js.map +1 -1
  38. package/dist/meetings/index.js +103 -54
  39. package/dist/meetings/index.js.map +1 -1
  40. package/dist/meetings/meetings.types.js +2 -0
  41. package/dist/meetings/meetings.types.js.map +1 -1
  42. package/dist/meetings/util.js +1 -1
  43. package/dist/meetings/util.js.map +1 -1
  44. package/dist/member/index.js +9 -0
  45. package/dist/member/index.js.map +1 -1
  46. package/dist/member/types.js.map +1 -1
  47. package/dist/member/util.js +39 -28
  48. package/dist/member/util.js.map +1 -1
  49. package/dist/metrics/constants.js +3 -2
  50. package/dist/metrics/constants.js.map +1 -1
  51. package/dist/multistream/remoteMedia.js +30 -15
  52. package/dist/multistream/remoteMedia.js.map +1 -1
  53. package/dist/multistream/sendSlotManager.js +24 -0
  54. package/dist/multistream/sendSlotManager.js.map +1 -1
  55. package/dist/roap/index.js +10 -8
  56. package/dist/roap/index.js.map +1 -1
  57. package/dist/types/annotation/index.d.ts +5 -0
  58. package/dist/types/common/errors/join-forbidden-error.d.ts +15 -0
  59. package/dist/types/common/errors/{webinar-registration-error.d.ts → join-webinar-error.d.ts} +2 -2
  60. package/dist/types/common/errors/multistream-not-supported-error.d.ts +17 -0
  61. package/dist/types/constants.d.ts +38 -1
  62. package/dist/types/index.d.ts +3 -3
  63. package/dist/types/locus-info/index.d.ts +2 -1
  64. package/dist/types/meeting/in-meeting-actions.d.ts +2 -0
  65. package/dist/types/meeting/index.d.ts +19 -12
  66. package/dist/types/meeting/locusMediaRequest.d.ts +4 -0
  67. package/dist/types/meeting/request.d.ts +12 -1
  68. package/dist/types/meeting/request.type.d.ts +6 -0
  69. package/dist/types/meeting/util.d.ts +1 -1
  70. package/dist/types/meeting-info/meeting-info-v2.d.ts +27 -4
  71. package/dist/types/meetings/index.d.ts +16 -1
  72. package/dist/types/meetings/meetings.types.d.ts +8 -0
  73. package/dist/types/member/index.d.ts +1 -0
  74. package/dist/types/member/types.d.ts +7 -0
  75. package/dist/types/metrics/constants.d.ts +2 -1
  76. package/dist/types/multistream/sendSlotManager.d.ts +8 -1
  77. package/dist/webinar/index.js +354 -3
  78. package/dist/webinar/index.js.map +1 -1
  79. package/package.json +23 -22
  80. package/src/annotation/index.ts +16 -0
  81. package/src/common/errors/join-forbidden-error.ts +26 -0
  82. package/src/common/errors/join-webinar-error.ts +24 -0
  83. package/src/common/errors/multistream-not-supported-error.ts +30 -0
  84. package/src/config.ts +1 -1
  85. package/src/constants.ts +43 -3
  86. package/src/index.ts +5 -3
  87. package/src/locus-info/index.ts +20 -3
  88. package/src/locus-info/selfUtils.ts +19 -6
  89. package/src/meeting/in-meeting-actions.ts +4 -0
  90. package/src/meeting/index.ts +259 -80
  91. package/src/meeting/locusMediaRequest.ts +7 -0
  92. package/src/meeting/request.ts +26 -1
  93. package/src/meeting/request.type.ts +7 -0
  94. package/src/meeting/util.ts +8 -10
  95. package/src/meeting-info/meeting-info-v2.ts +74 -11
  96. package/src/meeting-info/utilv2.ts +3 -1
  97. package/src/meetings/index.ts +73 -20
  98. package/src/meetings/meetings.types.ts +10 -0
  99. package/src/meetings/util.ts +2 -1
  100. package/src/member/index.ts +9 -0
  101. package/src/member/types.ts +8 -0
  102. package/src/member/util.ts +34 -24
  103. package/src/metrics/constants.ts +2 -1
  104. package/src/multistream/remoteMedia.ts +28 -15
  105. package/src/multistream/sendSlotManager.ts +31 -0
  106. package/src/roap/index.ts +10 -8
  107. package/src/webinar/index.ts +197 -3
  108. package/test/unit/spec/annotation/index.ts +46 -1
  109. package/test/unit/spec/locus-info/index.js +292 -60
  110. package/test/unit/spec/locus-info/selfConstant.js +7 -0
  111. package/test/unit/spec/locus-info/selfUtils.js +91 -1
  112. package/test/unit/spec/meeting/in-meeting-actions.ts +2 -0
  113. package/test/unit/spec/meeting/index.js +722 -105
  114. package/test/unit/spec/meeting/utils.js +22 -19
  115. package/test/unit/spec/meeting-info/meetinginfov2.js +46 -4
  116. package/test/unit/spec/meeting-info/utilv2.js +17 -0
  117. package/test/unit/spec/meetings/index.js +150 -13
  118. package/test/unit/spec/meetings/utils.js +10 -0
  119. package/test/unit/spec/member/util.js +52 -11
  120. package/test/unit/spec/multistream/remoteMedia.ts +11 -7
  121. package/test/unit/spec/roap/index.ts +47 -0
  122. package/test/unit/spec/webinar/index.ts +457 -0
  123. package/dist/common/errors/webinar-registration-error.js.map +0 -1
  124. package/src/common/errors/webinar-registration-error.ts +0 -27
@@ -1,6 +1,6 @@
1
1
  import 'jsdom-global/register';
2
2
  import sinon from 'sinon';
3
- import {cloneDeep} from 'lodash';
3
+ import {cloneDeep, forEach} from 'lodash';
4
4
  import {assert} from '@webex/test-helper-chai';
5
5
  import MockWebex from '@webex/test-helper-mock-webex';
6
6
  import testUtils from '../../../utils/testUtils';
@@ -9,6 +9,7 @@ import LocusInfo 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';
12
+ import MediaSharesUtils from '@webex/plugin-meetings/src/locus-info//mediaSharesUtils';
12
13
  import LocusDeltaParser from '@webex/plugin-meetings/src/locus-info/parser';
13
14
  import Metrics from '@webex/plugin-meetings/src/metrics';
14
15
 
@@ -22,6 +23,8 @@ import {
22
23
  LOCUS,
23
24
  MEETING_STATE,
24
25
  _MEETING_,
26
+ _SIP_BRIDGE_,
27
+ _SPACE_SHARE_,
25
28
  } from '../../../../src/constants';
26
29
 
27
30
  import {self, selfWithInactivity} from './selfConstant';
@@ -101,7 +104,11 @@ describe('plugin-meetings', () => {
101
104
  },
102
105
  entryExitTone: {enabled: true, mode: 'foo'},
103
106
  video: {enabled: true},
104
- videoLayout: {overrideDefault: true, lockAttendeeViewOnStageOnly:false, stageParameters: {}},
107
+ videoLayout: {
108
+ overrideDefault: true,
109
+ lockAttendeeViewOnStageOnly: false,
110
+ stageParameters: {},
111
+ },
105
112
  webcastControl: {streaming: false},
106
113
  practiceSession: {enabled: true},
107
114
  };
@@ -528,7 +535,7 @@ describe('plugin-meetings', () => {
528
535
  manualCaptionControl: {enabled: false},
529
536
  };
530
537
 
531
- locusInfo.updateControls({manualCaptionControl: { enabled: true, }});
538
+ locusInfo.updateControls({manualCaptionControl: {enabled: true}});
532
539
 
533
540
  assert.calledWith(
534
541
  locusInfo.emitScoped,
@@ -793,6 +800,75 @@ describe('plugin-meetings', () => {
793
800
  });
794
801
 
795
802
  describe('#updateSelf', () => {
803
+ it('should trigger SELF_MEETING_BRB_CHANGED when brb state changed', () => {
804
+ locusInfo.self = undefined;
805
+
806
+ const assertBrb = (enabled) => {
807
+ const selfWithBrbChanged = cloneDeep(self);
808
+ selfWithBrbChanged.controls.brb = enabled;
809
+
810
+ locusInfo.emitScoped = sinon.stub();
811
+ locusInfo.updateSelf(selfWithBrbChanged, []);
812
+
813
+ assert.calledWith(
814
+ locusInfo.emitScoped,
815
+ {file: 'locus-info', function: 'updateSelf'},
816
+ LOCUSINFO.EVENTS.SELF_MEETING_BRB_CHANGED,
817
+ {brb: enabled}
818
+ );
819
+ };
820
+
821
+ assertBrb(true);
822
+ assertBrb(false);
823
+ });
824
+
825
+ it('should not trigger SELF_MEETING_BRB_CHANGED when brb state did not change', () => {
826
+ const assertBrbUnchanged = (value) => {
827
+ locusInfo.self = undefined;
828
+
829
+ const selfWithBrbChanged = cloneDeep(self);
830
+ selfWithBrbChanged.controls.brb = value;
831
+ locusInfo.self = selfWithBrbChanged;
832
+
833
+ locusInfo.emitScoped = sinon.stub();
834
+
835
+ const newSelf = cloneDeep(self);
836
+ newSelf.controls.brb = value;
837
+
838
+ locusInfo.updateSelf(newSelf, []);
839
+
840
+ assert.neverCalledWith(
841
+ locusInfo.emitScoped,
842
+ {file: 'locus-info', function: 'updateSelf'},
843
+ LOCUSINFO.EVENTS.SELF_MEETING_BRB_CHANGED,
844
+ {brb: value}
845
+ );
846
+ };
847
+
848
+ assertBrbUnchanged(true);
849
+ assertBrbUnchanged(false);
850
+ });
851
+
852
+ it('should not trigger SELF_MEETING_BRB_CHANGED when brb state is undefined', () => {
853
+ const selfWithBrbChanged = cloneDeep(self);
854
+ selfWithBrbChanged.controls.brb = false;
855
+ locusInfo.self = selfWithBrbChanged;
856
+
857
+ locusInfo.emitScoped = sinon.stub();
858
+
859
+ const newSelf = cloneDeep(self);
860
+ newSelf.controls.brb = undefined;
861
+
862
+ locusInfo.updateSelf(newSelf, []);
863
+
864
+ assert.neverCalledWith(
865
+ locusInfo.emitScoped,
866
+ {file: 'locus-info', function: 'updateSelf'},
867
+ LOCUSINFO.EVENTS.SELF_MEETING_BRB_CHANGED,
868
+ {brb: undefined}
869
+ );
870
+ });
871
+
796
872
  it('should trigger CONTROLS_MEETING_LAYOUT_UPDATED when the meeting layout controls change', () => {
797
873
  const layoutType = 'EXAMPLE TYPE';
798
874
 
@@ -1388,6 +1464,30 @@ describe('plugin-meetings', () => {
1388
1464
  }
1389
1465
  );
1390
1466
  });
1467
+
1468
+ it('should not trigger any events if controls is undefined', () => {
1469
+ locusInfo.self = self;
1470
+ locusInfo.emitScoped = sinon.stub();
1471
+ const newSelf = cloneDeep(self);
1472
+ newSelf.controls = undefined;
1473
+
1474
+ locusInfo.updateSelf(newSelf, []);
1475
+
1476
+ const eventsSet = new Set([
1477
+ LOCUSINFO.EVENTS.CONTROLS_MEETING_LAYOUT_UPDATED,
1478
+ LOCUSINFO.EVENTS.SELF_MEETING_BREAKOUTS_CHANGED,
1479
+ LOCUSINFO.EVENTS.SELF_MEETING_BRB_CHANGED,
1480
+ LOCUSINFO.EVENTS.SELF_MEETING_INTERPRETATION_CHANGED,
1481
+ LOCUSINFO.EVENTS.LOCAL_UNMUTE_REQUIRED,
1482
+ LOCUSINFO.EVENTS.SELF_REMOTE_MUTE_STATUS_UPDATED,
1483
+ ]);
1484
+
1485
+ // check all events that contain logic on controls existence
1486
+ locusInfo.emitScoped.getCalls().forEach((call) => {
1487
+ const eventName = call.args[1];
1488
+ assert.isFalse(eventsSet.has(eventName));
1489
+ });
1490
+ });
1391
1491
  });
1392
1492
 
1393
1493
  describe('#updateMeetingInfo', () => {
@@ -1637,6 +1737,134 @@ describe('plugin-meetings', () => {
1637
1737
  });
1638
1738
  });
1639
1739
 
1740
+ describe('#updateMediaShares', () => {
1741
+ let getMediaSharesSpy;
1742
+
1743
+ beforeEach(() => {
1744
+ // Spy on MediaSharesUtils.getMediaShares
1745
+ getMediaSharesSpy = sinon.stub(MediaSharesUtils, 'getMediaShares');
1746
+
1747
+ // Stub the emitScoped method to monitor its calls
1748
+ sinon.stub(locusInfo, 'emitScoped');
1749
+ });
1750
+
1751
+ afterEach(() => {
1752
+ getMediaSharesSpy.restore();
1753
+ locusInfo.emitScoped.restore();
1754
+ });
1755
+
1756
+ it('should update media shares and emit LOCUS_INFO_UPDATE_MEDIA_SHARES when mediaShares change', () => {
1757
+ const initialMediaShares = { audio: true, video: false };
1758
+ const newMediaShares = { audio: false, video: true };
1759
+
1760
+ locusInfo.mediaShares = initialMediaShares;
1761
+ locusInfo.parsedLocus = { mediaShares: null };
1762
+
1763
+ const parsedMediaShares = {
1764
+ current: newMediaShares,
1765
+ previous: initialMediaShares,
1766
+ };
1767
+
1768
+ // Stub MediaSharesUtils.getMediaShares to return the expected parsedMediaShares
1769
+ getMediaSharesSpy.returns(parsedMediaShares);
1770
+
1771
+ // Call the function
1772
+ locusInfo.updateMediaShares(newMediaShares);
1773
+
1774
+ // Assert that MediaSharesUtils.getMediaShares was called with correct arguments
1775
+ assert.calledWith(getMediaSharesSpy, initialMediaShares, newMediaShares);
1776
+
1777
+ // Assert that updateMeeting was called with the parsed current media shares
1778
+ assert.deepEqual(locusInfo.parsedLocus.mediaShares, newMediaShares);
1779
+ assert.deepEqual(locusInfo.mediaShares, newMediaShares);
1780
+
1781
+ // Assert that emitScoped was called with the correct event
1782
+ assert.calledWith(
1783
+ locusInfo.emitScoped,
1784
+ {
1785
+ file: 'locus-info',
1786
+ function: 'updateMediaShares',
1787
+ },
1788
+ EVENTS.LOCUS_INFO_UPDATE_MEDIA_SHARES,
1789
+ {
1790
+ current: newMediaShares,
1791
+ previous: initialMediaShares,
1792
+ forceUpdate: false,
1793
+ }
1794
+ );
1795
+ });
1796
+
1797
+ it('should force update media shares and emit LOCUS_INFO_UPDATE_MEDIA_SHARES even if shares are the same', () => {
1798
+ const initialMediaShares = { audio: true, video: false };
1799
+ locusInfo.mediaShares = initialMediaShares;
1800
+ locusInfo.parsedLocus = { mediaShares: null };
1801
+
1802
+ const parsedMediaShares = {
1803
+ current: initialMediaShares,
1804
+ previous: initialMediaShares,
1805
+ };
1806
+
1807
+ getMediaSharesSpy.returns(parsedMediaShares);
1808
+
1809
+ // Call the function with forceUpdate = true
1810
+ locusInfo.updateMediaShares(initialMediaShares, true);
1811
+
1812
+ // Assert that MediaSharesUtils.getMediaShares was called
1813
+ assert.calledWith(getMediaSharesSpy, initialMediaShares, initialMediaShares);
1814
+
1815
+ // Assert that emitScoped was called with the correct event
1816
+ assert.calledWith(
1817
+ locusInfo.emitScoped,
1818
+ {
1819
+ file: 'locus-info',
1820
+ function: 'updateMediaShares',
1821
+ },
1822
+ EVENTS.LOCUS_INFO_UPDATE_MEDIA_SHARES,
1823
+ {
1824
+ current: initialMediaShares,
1825
+ previous: initialMediaShares,
1826
+ forceUpdate: true,
1827
+ }
1828
+ );
1829
+ });
1830
+
1831
+ it('should not emit LOCUS_INFO_UPDATE_MEDIA_SHARES if mediaShares do not change and forceUpdate is false', () => {
1832
+ const initialMediaShares = { audio: true, video: false };
1833
+ locusInfo.mediaShares = initialMediaShares;
1834
+
1835
+ // Call the function with the same mediaShares and forceUpdate = false
1836
+ locusInfo.updateMediaShares(initialMediaShares);
1837
+
1838
+ // Assert that MediaSharesUtils.getMediaShares was not called
1839
+ assert.notCalled(getMediaSharesSpy);
1840
+
1841
+ // Assert that emitScoped was not called
1842
+ assert.notCalled(locusInfo.emitScoped);
1843
+ });
1844
+
1845
+ it('should update internal state correctly when mediaShares are updated', () => {
1846
+ const initialMediaShares = { audio: true, video: false };
1847
+ const newMediaShares = { audio: false, video: true };
1848
+
1849
+ locusInfo.mediaShares = initialMediaShares;
1850
+ locusInfo.parsedLocus = { mediaShares: null };
1851
+
1852
+ const parsedMediaShares = {
1853
+ current: newMediaShares,
1854
+ previous: initialMediaShares,
1855
+ };
1856
+
1857
+ getMediaSharesSpy.returns(parsedMediaShares);
1858
+
1859
+ // Call the function
1860
+ locusInfo.updateMediaShares(newMediaShares);
1861
+
1862
+ // Assert that the internal state was updated correctly
1863
+ assert.deepEqual(locusInfo.parsedLocus.mediaShares, newMediaShares);
1864
+ assert.deepEqual(locusInfo.mediaShares, newMediaShares);
1865
+ });
1866
+ });
1867
+
1640
1868
  describe('#updateEmbeddedApps()', () => {
1641
1869
  const newEmbeddedApps = [
1642
1870
  {
@@ -2414,65 +2642,69 @@ describe('plugin-meetings', () => {
2414
2642
  });
2415
2643
 
2416
2644
  describe('#isMeetingActive', () => {
2417
- it('sends client event correctly for state = inactive', () => {
2418
- locusInfo.parsedLocus = {
2419
- fullState: {
2420
- type: _CALL_,
2421
- },
2422
- };
2423
-
2424
- locusInfo.fullState = {
2425
- state: LOCUS.STATE.INACTIVE,
2426
- };
2427
-
2428
- locusInfo.isMeetingActive();
2429
-
2430
- assert.calledWith(webex.internal.newMetrics.submitClientEvent, {
2431
- name: 'client.call.remote-ended',
2432
- options: {
2433
- meetingId: locusInfo.meetingId,
2434
- },
2435
- });
2436
- });
2437
-
2438
- it('sends client event correctly for state = PARTNER_LEFT', () => {
2439
- locusInfo.getLocusPartner = sinon.stub().returns({state: MEETING_STATE.STATES.LEFT});
2440
- locusInfo.parsedLocus = {
2441
- fullState: {
2442
- type: _CALL_,
2443
- },
2444
- self: {
2445
- state: MEETING_STATE.STATES.DECLINED,
2446
- },
2447
- };
2448
- locusInfo.isMeetingActive();
2449
-
2450
- assert.calledWith(webex.internal.newMetrics.submitClientEvent, {
2451
- name: 'client.call.remote-ended',
2452
- options: {
2453
- meetingId: locusInfo.meetingId,
2454
- },
2455
- });
2456
- });
2457
-
2458
- it('sends client event correctly for state = SELF_LEFT', () => {
2459
- locusInfo.getLocusPartner = sinon.stub().returns({state: MEETING_STATE.STATES.LEFT});
2460
- locusInfo.parsedLocus = {
2461
- fullState: {
2462
- type: _CALL_,
2463
- },
2464
- self: {
2465
- state: MEETING_STATE.STATES.LEFT,
2466
- },
2467
- };
2645
+ forEach([_CALL_, _SIP_BRIDGE_, _SPACE_SHARE_], (type) => {
2646
+ describe(`type = ${type}`, () => {
2647
+ it('sends client event correctly for state = inactive', () => {
2648
+ locusInfo.parsedLocus = {
2649
+ fullState: {
2650
+ type: type,
2651
+ },
2652
+ };
2653
+
2654
+ locusInfo.fullState = {
2655
+ state: LOCUS.STATE.INACTIVE,
2656
+ };
2657
+
2658
+ locusInfo.isMeetingActive();
2659
+
2660
+ assert.calledWith(webex.internal.newMetrics.submitClientEvent, {
2661
+ name: 'client.call.remote-ended',
2662
+ options: {
2663
+ meetingId: locusInfo.meetingId,
2664
+ },
2665
+ });
2666
+ });
2468
2667
 
2469
- locusInfo.isMeetingActive();
2668
+ it('sends client event correctly for state = PARTNER_LEFT', () => {
2669
+ locusInfo.getLocusPartner = sinon.stub().returns({state: MEETING_STATE.STATES.LEFT});
2670
+ locusInfo.parsedLocus = {
2671
+ fullState: {
2672
+ type: type,
2673
+ },
2674
+ self: {
2675
+ state: MEETING_STATE.STATES.DECLINED,
2676
+ },
2677
+ };
2678
+ locusInfo.isMeetingActive();
2679
+
2680
+ assert.calledWith(webex.internal.newMetrics.submitClientEvent, {
2681
+ name: 'client.call.remote-ended',
2682
+ options: {
2683
+ meetingId: locusInfo.meetingId,
2684
+ },
2685
+ });
2686
+ });
2470
2687
 
2471
- assert.calledWith(webex.internal.newMetrics.submitClientEvent, {
2472
- name: 'client.call.remote-ended',
2473
- options: {
2474
- meetingId: locusInfo.meetingId,
2475
- },
2688
+ it('sends client event correctly for state = SELF_LEFT', () => {
2689
+ locusInfo.getLocusPartner = sinon.stub().returns({state: MEETING_STATE.STATES.LEFT});
2690
+ locusInfo.parsedLocus = {
2691
+ fullState: {
2692
+ type: type,
2693
+ },
2694
+ self: {
2695
+ state: MEETING_STATE.STATES.LEFT,
2696
+ },
2697
+ };
2698
+
2699
+ locusInfo.isMeetingActive();
2700
+
2701
+ assert.calledWith(webex.internal.newMetrics.submitClientEvent, {
2702
+ name: 'client.call.remote-ended',
2703
+ options: {
2704
+ meetingId: locusInfo.meetingId,
2705
+ },
2706
+ });
2707
+ });
2476
2708
  });
2477
2709
  });
2478
2710
 
@@ -304,6 +304,13 @@ export const selfWithInactivity = {
304
304
  localRecord: {
305
305
  recording: false,
306
306
  },
307
+ brb: {
308
+ enabled: true,
309
+ meta: {
310
+ lastModified: '2024-10-24T14:05:58.526Z',
311
+ modifiedBy: '70978427-8238-4ffc-9227-8baf4b80b831',
312
+ },
313
+ },
307
314
  layouts: [
308
315
  {
309
316
  type: 'activePresence',
@@ -1,9 +1,10 @@
1
1
  import {assert} from '@webex/test-helper-chai';
2
2
  import Sinon from 'sinon';
3
- import {cloneDeep} from 'lodash';
3
+ import {cloneDeep, defaultsDeep} from 'lodash';
4
4
  import SelfUtils from '@webex/plugin-meetings/src/locus-info/selfUtils';
5
5
 
6
6
  import {self} from './selfConstant';
7
+ import {_IDLE_, _WAIT_} from '@webex/plugin-meetings/src/constants';
7
8
 
8
9
  describe('plugin-meetings', () => {
9
10
  describe('selfUtils', () => {
@@ -60,6 +61,14 @@ describe('plugin-meetings', () => {
60
61
  assert.calledWith(spy, self);
61
62
  assert.deepEqual(parsedSelf.layout, self.controls.layouts[0].type);
62
63
  });
64
+
65
+ it('calls getBrb and returns the resulting brb value', () => {
66
+ const spy = Sinon.spy(SelfUtils, 'getBrb');
67
+ const parsedSelf = SelfUtils.parse(self);
68
+
69
+ assert.calledWith(spy, self);
70
+ assert.deepEqual(parsedSelf.brb, self.controls.brb);
71
+ });
63
72
  });
64
73
 
65
74
  describe('getLayout', () => {
@@ -170,6 +179,37 @@ describe('plugin-meetings', () => {
170
179
  });
171
180
  });
172
181
 
182
+ describe('brbChanged', () => {
183
+ it('should return true if brb have changed', () => {
184
+ const current = {
185
+ brb: {enabled: true},
186
+ };
187
+ const previous = {
188
+ brb: {enabled: false},
189
+ };
190
+
191
+ assert.isTrue(SelfUtils.brbChanged(previous, current));
192
+ });
193
+
194
+ it('should return false if brb have not changed', () => {
195
+ const current = {
196
+ brb: {enabled: true},
197
+ };
198
+ const previous = {
199
+ brb: {enabled: true},
200
+ };
201
+
202
+ assert.isFalse(SelfUtils.brbChanged(previous, current));
203
+ });
204
+
205
+ it('should return false if brb in current is undefined', () => {
206
+ const current = {};
207
+ const previous = {brb: {enabled: true}};
208
+
209
+ assert.isFalse(SelfUtils.brbChanged(previous, current));
210
+ });
211
+ });
212
+
173
213
  describe('canNotViewTheParticipantList', () => {
174
214
  it('should return the correct value', () => {
175
215
  assert.equal(
@@ -299,6 +339,56 @@ describe('plugin-meetings', () => {
299
339
  assert.equal(updates.localAudioUnmuteRequestedByServer, false);
300
340
  });
301
341
  });
342
+
343
+ describe('updates.isUserUnadmitted', () => {
344
+ const testIsUserUnadmitted = (previousObjectDelta, currentObjectDelta, expected) => function () {
345
+ const previous =
346
+ previousObjectDelta === undefined ? undefined : defaultsDeep(previousObjectDelta, self);
347
+ const current = defaultsDeep(currentObjectDelta, self);
348
+
349
+ const {updates} = SelfUtils.getSelves(previous, current, self.devices[0].url);
350
+
351
+ assert.equal(updates.isUserUnadmitted, expected);
352
+ };
353
+
354
+ it(
355
+ 'should return true when previous is undefined and current is in lobby',
356
+ testIsUserUnadmitted(
357
+ undefined,
358
+ {devices: [{intent: {type: _WAIT_}}], state: _IDLE_},
359
+ true
360
+ )
361
+ );
362
+
363
+ it(
364
+ 'should return false when previous is undefined and user is not in meeting',
365
+ testIsUserUnadmitted(undefined, {devices: [], state: _IDLE_}, false)
366
+ );
367
+
368
+ it(
369
+ 'should return false when previous is undefined and current is in meeting',
370
+ testIsUserUnadmitted(undefined, {}, false)
371
+ );
372
+
373
+ it(
374
+ 'should return false when previous is in lobby and current is in lobby',
375
+ testIsUserUnadmitted(
376
+ {devices: [{intent: {type: _WAIT_}}], state: _IDLE_},
377
+ {devices: [{intent: {type: _WAIT_}}], state: _IDLE_},
378
+ false
379
+ )
380
+ );
381
+
382
+ it(
383
+ 'should return false when previous is in lobby and current is in meeting',
384
+ testIsUserUnadmitted({devices: [{intent: {type: _WAIT_}}], state: _IDLE_}, {}, false)
385
+ );
386
+
387
+ it(
388
+ 'should return true when previous is in meeting and current is in lobby',
389
+ testIsUserUnadmitted({}, {devices: [{intent: {type: _WAIT_}}], state: _IDLE_}, true)
390
+ );
391
+ });
302
392
  });
303
393
 
304
394
  describe('isSharingBlocked', () => {
@@ -42,6 +42,7 @@ describe('plugin-meetings', () => {
42
42
  waitingForOthersToJoin: null,
43
43
  canSendReactions: null,
44
44
  canManageBreakout: null,
45
+ canStartBreakout: null,
45
46
  canBroadcastMessageToBreakout: null,
46
47
  canAdmitLobbyToBreakout: null,
47
48
  canUserAskForHelp: null,
@@ -141,6 +142,7 @@ describe('plugin-meetings', () => {
141
142
  'waitingForOthersToJoin',
142
143
  'canSendReactions',
143
144
  'canManageBreakout',
145
+ 'canStartBreakout',
144
146
  'canBroadcastMessageToBreakout',
145
147
  'canAdmitLobbyToBreakout',
146
148
  'canUserAskForHelp',