@webex/plugin-meetings 3.0.0-beta.57 → 3.0.0-beta.59

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 (59) hide show
  1. package/dist/breakouts/breakout.js +1 -1
  2. package/dist/breakouts/index.js +1 -1
  3. package/dist/constants.js +2 -0
  4. package/dist/constants.js.map +1 -1
  5. package/dist/meeting/in-meeting-actions.js +6 -0
  6. package/dist/meeting/in-meeting-actions.js.map +1 -1
  7. package/dist/meeting/index.js +1 -0
  8. package/dist/meeting/index.js.map +1 -1
  9. package/dist/meeting/util.js +3 -0
  10. package/dist/meeting/util.js.map +1 -1
  11. package/dist/meetings/index.js +3 -0
  12. package/dist/meetings/index.js.map +1 -1
  13. package/dist/meetings/request.js +2 -0
  14. package/dist/meetings/request.js.map +1 -1
  15. package/dist/member/index.js +23 -0
  16. package/dist/member/index.js.map +1 -1
  17. package/dist/member/types.js +15 -0
  18. package/dist/member/types.js.map +1 -0
  19. package/dist/member/util.js +61 -2
  20. package/dist/member/util.js.map +1 -1
  21. package/dist/members/index.js +21 -0
  22. package/dist/members/index.js.map +1 -1
  23. package/dist/members/request.js +19 -0
  24. package/dist/members/request.js.map +1 -1
  25. package/dist/members/types.js +15 -0
  26. package/dist/members/types.js.map +1 -0
  27. package/dist/members/util.js +38 -0
  28. package/dist/members/util.js.map +1 -1
  29. package/dist/reconnection-manager/index.js +2 -2
  30. package/dist/reconnection-manager/index.js.map +1 -1
  31. package/dist/types/constants.d.ts +2 -0
  32. package/dist/types/meeting/in-meeting-actions.d.ts +6 -0
  33. package/dist/types/member/index.d.ts +10 -0
  34. package/dist/types/member/types.d.ts +21 -0
  35. package/dist/types/members/index.d.ts +10 -0
  36. package/dist/types/members/request.d.ts +8 -0
  37. package/dist/types/members/types.d.ts +24 -0
  38. package/package.json +18 -18
  39. package/src/constants.ts +2 -0
  40. package/src/meeting/in-meeting-actions.ts +12 -0
  41. package/src/meeting/index.ts +3 -0
  42. package/src/meeting/util.ts +3 -0
  43. package/src/meetings/index.ts +31 -23
  44. package/src/meetings/request.ts +2 -0
  45. package/src/member/index.ts +22 -0
  46. package/src/member/types.ts +24 -0
  47. package/src/member/util.ts +53 -0
  48. package/src/members/index.ts +27 -0
  49. package/src/members/request.ts +20 -0
  50. package/src/members/types.ts +28 -0
  51. package/src/members/util.ts +39 -1
  52. package/src/reconnection-manager/index.ts +2 -2
  53. package/test/unit/spec/meeting/in-meeting-actions.ts +6 -0
  54. package/test/unit/spec/meeting/utils.js +1 -0
  55. package/test/unit/spec/member/index.js +24 -0
  56. package/test/unit/spec/member/util.js +336 -33
  57. package/test/unit/spec/members/index.js +77 -0
  58. package/test/unit/spec/members/request.js +21 -0
  59. package/test/unit/spec/members/utils.js +28 -0
@@ -20,6 +20,8 @@ interface IInMeetingActions {
20
20
  canSetMuted?: boolean;
21
21
  canUnsetMuted?: boolean;
22
22
  canAssignHost?: boolean;
23
+ canSetPresenter?: boolean;
24
+ canUnsetPresenter?: boolean;
23
25
  canStartRecording?: boolean;
24
26
  canPauseRecording?: boolean;
25
27
  canResumeRecording?: boolean;
@@ -32,6 +34,7 @@ interface IInMeetingActions {
32
34
  canStartTranscribing?: boolean;
33
35
  canStopTranscribing?: boolean;
34
36
  isClosedCaptionActive?: boolean;
37
+ isSaveTranscriptsEnabled?: boolean;
35
38
  isWebexAssistantActive?: boolean;
36
39
  canViewCaptionPanel?: boolean;
37
40
  isRealTimeTranslationEnabled?: boolean;
@@ -71,6 +74,10 @@ export default class InMeetingActions implements IInMeetingActions {
71
74
 
72
75
  canSetMuteOnEntry = null;
73
76
 
77
+ canSetPresenter = null;
78
+
79
+ canUnsetPresenter = null;
80
+
74
81
  canUnsetMuteOnEntry = null;
75
82
 
76
83
  canSetDisallowUnmute = null;
@@ -97,6 +104,8 @@ export default class InMeetingActions implements IInMeetingActions {
97
104
 
98
105
  isClosedCaptionActive = null;
99
106
 
107
+ isSaveTranscriptsEnabled = null;
108
+
100
109
  isWebexAssistantActive = null;
101
110
 
102
111
  canViewCaptionPanel = null;
@@ -134,6 +143,8 @@ export default class InMeetingActions implements IInMeetingActions {
134
143
  canSetDisallowUnmute: this.canSetDisallowUnmute,
135
144
  canSetMuted: this.canSetMuted,
136
145
  canUnsetMuted: this.canUnsetMuted,
146
+ canSetPresenter: this.canSetPresenter,
147
+ canUnsetPresenter: this.canUnsetPresenter,
137
148
  canUnsetDisallowUnmute: this.canUnsetDisallowUnmute,
138
149
  canStartRecording: this.canStartRecording,
139
150
  canPauseRecording: this.canPauseRecording,
@@ -147,6 +158,7 @@ export default class InMeetingActions implements IInMeetingActions {
147
158
  canStartTranscribing: this.canStartTranscribing,
148
159
  canStopTranscribing: this.canStopTranscribing,
149
160
  isClosedCaptionActive: this.isClosedCaptionActive,
161
+ isSaveTranscriptsEnabled: this.isSaveTranscriptsEnabled,
150
162
  isWebexAssistantActive: this.isWebexAssistantActive,
151
163
  canViewCaptionPanel: this.canViewCaptionPanel,
152
164
  isRealTimeTranslationEnabled: this.isRealTimeTranslationEnabled,
@@ -2307,6 +2307,9 @@ export default class Meeting extends StatelessWebexPlugin {
2307
2307
  canStartTranscribing: MeetingUtil.canStartTranscribing(payload.info.userDisplayHints),
2308
2308
  canStopTranscribing: MeetingUtil.canStopTranscribing(payload.info.userDisplayHints),
2309
2309
  isClosedCaptionActive: MeetingUtil.isClosedCaptionActive(payload.info.userDisplayHints),
2310
+ isSaveTranscriptsEnabled: MeetingUtil.isSaveTranscriptsEnabled(
2311
+ payload.info.userDisplayHints
2312
+ ),
2310
2313
  isWebexAssistantActive: MeetingUtil.isWebexAssistantActive(payload.info.userDisplayHints),
2311
2314
  canViewCaptionPanel: MeetingUtil.canViewCaptionPanel(payload.info.userDisplayHints),
2312
2315
  isRealTimeTranslationEnabled: MeetingUtil.isRealTimeTranslationEnabled(
@@ -470,6 +470,9 @@ MeetingUtil.endMeetingForAll = (meeting) => {
470
470
  MeetingUtil.canEnableClosedCaption = (displayHints) =>
471
471
  displayHints.includes(DISPLAY_HINTS.CAPTION_START);
472
472
 
473
+ MeetingUtil.isSaveTranscriptsEnabled = (displayHints) =>
474
+ displayHints.includes(DISPLAY_HINTS.SAVE_TRANSCRIPTS_ENABLED);
475
+
473
476
  MeetingUtil.canStartTranscribing = (displayHints) =>
474
477
  displayHints.includes(DISPLAY_HINTS.TRANSCRIPTION_CONTROL_START);
475
478
 
@@ -1108,33 +1108,41 @@ export default class Meetings extends WebexPlugin {
1108
1108
  * @memberof Meetings
1109
1109
  */
1110
1110
  public syncMeetings() {
1111
- return this.request.getActiveMeetings().then((locusArray) => {
1112
- const activeLocusUrl = [];
1113
-
1114
- if (locusArray?.loci && locusArray.loci.length > 0) {
1115
- locusArray.loci.forEach((locus) => {
1116
- activeLocusUrl.push(locus.url);
1117
- this.handleLocusEvent({
1118
- locus,
1119
- locusUrl: locus.url,
1111
+ return this.request
1112
+ .getActiveMeetings()
1113
+ .then((locusArray) => {
1114
+ const activeLocusUrl = [];
1115
+
1116
+ if (locusArray?.loci && locusArray.loci.length > 0) {
1117
+ locusArray.loci.forEach((locus) => {
1118
+ activeLocusUrl.push(locus.url);
1119
+ this.handleLocusEvent({
1120
+ locus,
1121
+ locusUrl: locus.url,
1122
+ });
1120
1123
  });
1121
- });
1122
- }
1123
- const meetingsCollection = this.meetingCollection.getAll();
1124
-
1125
- if (Object.keys(meetingsCollection).length > 0) {
1126
- // Some time the mercury event is missed after mercury reconnect
1127
- // if sync returns no locus then clear all the meetings
1128
- for (const meeting of Object.values(meetingsCollection)) {
1129
- // @ts-ignore
1130
- if (!activeLocusUrl.includes(meeting.locusUrl)) {
1131
- // destroy function also uploads logs
1124
+ }
1125
+ const meetingsCollection = this.meetingCollection.getAll();
1126
+
1127
+ if (Object.keys(meetingsCollection).length > 0) {
1128
+ // Some time the mercury event is missed after mercury reconnect
1129
+ // if sync returns no locus then clear all the meetings
1130
+ for (const meeting of Object.values(meetingsCollection)) {
1132
1131
  // @ts-ignore
1133
- this.destroy(meeting, MEETING_REMOVED_REASON.NO_MEETINGS_TO_SYNC);
1132
+ if (!activeLocusUrl.includes(meeting.locusUrl)) {
1133
+ // destroy function also uploads logs
1134
+ // @ts-ignore
1135
+ this.destroy(meeting, MEETING_REMOVED_REASON.NO_MEETINGS_TO_SYNC);
1136
+ }
1134
1137
  }
1135
1138
  }
1136
- }
1137
- });
1139
+ })
1140
+ .catch((error) => {
1141
+ LoggerProxy.logger.error(
1142
+ `Meetings:index#syncMeetings --> failed to sync meetings, ${error}`
1143
+ );
1144
+ throw new Error(error);
1145
+ });
1138
1146
  }
1139
1147
 
1140
1148
  /**
@@ -23,6 +23,7 @@ export default class MeetingRequest extends StatelessWebexPlugin {
23
23
  LoggerProxy.logger.error(
24
24
  `Meetings:request#getActiveMeetings --> failed to get locus details, ${error}`
25
25
  );
26
+ throw new Error(error);
26
27
  });
27
28
  }
28
29
 
@@ -71,6 +72,7 @@ export default class MeetingRequest extends StatelessWebexPlugin {
71
72
  LoggerProxy.logger.error(
72
73
  `Meetings:request#determineRedirections --> failed to get locus details from url: ${url}, reason: ${error}`
73
74
  );
75
+ throw new Error(error);
74
76
  })
75
77
  )
76
78
  ).then(() => Promise.resolve(responseBody));
@@ -2,6 +2,7 @@
2
2
  * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
3
3
  */
4
4
  import {MEETINGS, _IN_LOBBY_, _NOT_IN_MEETING_, _IN_MEETING_} from '../constants';
5
+ import {IExternalRoles, ParticipantWithRoles} from './types';
5
6
 
6
7
  import MemberUtil from './util';
7
8
 
@@ -28,6 +29,7 @@ export default class Member {
28
29
  isSelf: any;
29
30
  isUser: any;
30
31
  isVideoMuted: any;
32
+ roles: IExternalRoles;
31
33
  name: any;
32
34
  participant: any;
33
35
  status: any;
@@ -228,6 +230,14 @@ export default class Member {
228
230
  * @memberof Member
229
231
  */
230
232
  this.isModeratorAssignmentProhibited = null;
233
+
234
+ /**
235
+ * @instance
236
+ * @type {IExternalRoles}
237
+ * @public
238
+ * @memberof Member
239
+ */
240
+ this.roles = null;
231
241
  // TODO: more participant types
232
242
  // such as native client, web client, is a device, what type of phone, etc
233
243
  this.processParticipant(participant);
@@ -258,6 +268,7 @@ export default class Member {
258
268
  this.isModeratorAssignmentProhibited =
259
269
  MemberUtil.isModeratorAssignmentProhibited(participant);
260
270
  this.processStatus(participant);
271
+ this.processRoles(participant as ParticipantWithRoles);
261
272
  // must be done last
262
273
  this.isNotAdmitted = MemberUtil.isNotAdmitted(participant, this.isGuest, this.status);
263
274
  }
@@ -427,6 +438,17 @@ export default class Member {
427
438
  }
428
439
  }
429
440
 
441
+ /**
442
+ * process the roles that have been applied to this member
443
+ * @param {Object} participant
444
+ * @returns {undefined}
445
+ * @private
446
+ * @memberof Member
447
+ */
448
+ private processRoles(participant: ParticipantWithRoles) {
449
+ this.roles = MemberUtil.extractControlRoles(participant);
450
+ }
451
+
430
452
  /**
431
453
  * set the type for the member, could be MEETING or CALL
432
454
  * @param {String} type
@@ -0,0 +1,24 @@
1
+ export interface IExternalRoles {
2
+ cohost: boolean;
3
+ moderator: boolean;
4
+ presenter: boolean;
5
+ }
6
+
7
+ export enum ServerRoles {
8
+ Cohost = 'COHOST',
9
+ Moderator = 'MODERATOR',
10
+ Presenter = 'PRESENTER',
11
+ }
12
+
13
+ export type ServerRoleShape = {
14
+ type: ServerRoles;
15
+ hasRole: boolean;
16
+ };
17
+
18
+ export type ParticipantWithRoles = {
19
+ controls: {
20
+ role: {
21
+ roles: Array<ServerRoleShape>;
22
+ };
23
+ };
24
+ };
@@ -1,3 +1,4 @@
1
+ import {IExternalRoles, ParticipantWithRoles, ServerRoles, ServerRoleShape} from './types';
1
2
  import {
2
3
  _USER_,
3
4
  _RESOURCE_ROOM_,
@@ -19,6 +20,58 @@ import ParameterError from '../common/errors/parameter';
19
20
 
20
21
  const MemberUtil: any = {};
21
22
 
23
+ /**
24
+ * @param {Object} participant the locus participant
25
+ * @returns {[ServerRoleShape]}
26
+ */
27
+ MemberUtil.getControlsRoles = (participant: ParticipantWithRoles): Array<ServerRoleShape> =>
28
+ participant?.controls?.role?.roles;
29
+
30
+ /**
31
+ * @param {Object} participant the locus participant
32
+ * @param {ServerRoles} controlRole the search role
33
+ * @returns {Boolean}
34
+ */
35
+ MemberUtil.hasRole = (participant: any, controlRole: ServerRoles): boolean =>
36
+ MemberUtil.getControlsRoles(participant)?.some(
37
+ (role) => role.type === controlRole && role.hasRole
38
+ );
39
+
40
+ /**
41
+ * @param {Object} participant the locus participant
42
+ * @returns {Boolean}
43
+ */
44
+ MemberUtil.hasCohost = (participant: ParticipantWithRoles): boolean =>
45
+ MemberUtil.hasRole(participant, ServerRoles.Cohost) || false;
46
+
47
+ /**
48
+ * @param {Object} participant the locus participant
49
+ * @returns {Boolean}
50
+ */
51
+ MemberUtil.hasModerator = (participant: ParticipantWithRoles): boolean =>
52
+ MemberUtil.hasRole(participant, ServerRoles.Moderator) || false;
53
+
54
+ /**
55
+ * @param {Object} participant the locus participant
56
+ * @returns {Boolean}
57
+ */
58
+ MemberUtil.hasPresenter = (participant: ParticipantWithRoles): boolean =>
59
+ MemberUtil.hasRole(participant, ServerRoles.Presenter) || false;
60
+
61
+ /**
62
+ * @param {Object} participant the locus participant
63
+ * @returns {IExternalRoles}
64
+ */
65
+ MemberUtil.extractControlRoles = (participant: ParticipantWithRoles): IExternalRoles => {
66
+ const roles = {
67
+ cohost: MemberUtil.hasCohost(participant),
68
+ moderator: MemberUtil.hasModerator(participant),
69
+ presenter: MemberUtil.hasPresenter(participant),
70
+ };
71
+
72
+ return roles;
73
+ };
74
+
22
75
  /**
23
76
  * @param {Object} participant the locus participant
24
77
  * @returns {Boolean}
@@ -16,6 +16,7 @@ import MembersRequest from './request';
16
16
  import MembersUtil from './util';
17
17
  import {ReceiveSlotManager} from '../multistream/receiveSlotManager';
18
18
  import {MediaRequestManager} from '../multistream/mediaRequestManager';
19
+ import {ServerRoleShape} from './types';
19
20
 
20
21
  /**
21
22
  * Members Update Event
@@ -803,6 +804,32 @@ export default class Members extends StatelessWebexPlugin {
803
804
  return this.membersRequest.muteMember(options);
804
805
  }
805
806
 
807
+ /**
808
+ * Assign role(s) to a member in the meeting
809
+ * @param {String} memberId
810
+ * @param {[ServerRoleShape]} roles - to assign an array of roles
811
+ * @returns {Promise}
812
+ * @public
813
+ * @memberof Members
814
+ */
815
+ public assignRoles(memberId: string, roles: Array<ServerRoleShape>) {
816
+ if (!this.locusUrl) {
817
+ return Promise.reject(
818
+ new ParameterError(
819
+ 'The associated locus url for this meetings members object must be defined.'
820
+ )
821
+ );
822
+ }
823
+ if (!memberId) {
824
+ return Promise.reject(
825
+ new ParameterError('The member id must be defined to assign the roles to a member.')
826
+ );
827
+ }
828
+ const options = MembersUtil.generateRoleAssignmentMemberOptions(memberId, roles, this.locusUrl);
829
+
830
+ return this.membersRequest.assignRolesMember(options);
831
+ }
832
+
806
833
  /**
807
834
  * Raise or lower the hand of a member in a meeting
808
835
  * @param {String} memberId
@@ -85,6 +85,26 @@ export default class MembersRequest extends StatelessWebexPlugin {
85
85
  return this.request(requestParams);
86
86
  }
87
87
 
88
+ /**
89
+ * Sends a request to the DTMF endpoint to send tones
90
+ * @param {Object} options
91
+ * @param {String} options.locusUrl
92
+ * @param {String} options.memberId ID of PSTN user
93
+ * @returns {Promise}
94
+ */
95
+ assignRolesMember(options: any) {
96
+ if (!options || !options.locusUrl || !options.memberId) {
97
+ throw new ParameterError(
98
+ 'memberId must be defined, and the associated locus url for this meeting object must be defined.'
99
+ );
100
+ }
101
+
102
+ const requestParams = MembersUtil.getRoleAssignmentMemberRequestParams(options);
103
+
104
+ // @ts-ignore
105
+ return this.request(requestParams);
106
+ }
107
+
88
108
  raiseOrLowerHandMember(options) {
89
109
  if (!options || !options.locusUrl || !options.memberId) {
90
110
  throw new ParameterError(
@@ -0,0 +1,28 @@
1
+ export enum ServerRoles {
2
+ Cohost = 'COHOST',
3
+ Moderator = 'MODERATOR',
4
+ Presenter = 'PRESENTER',
5
+ }
6
+
7
+ export type ServerRoleShape = {
8
+ type: ServerRoles;
9
+ hasRole: boolean;
10
+ };
11
+
12
+ export type RoleAssignmentOptions = {
13
+ roles: Array<ServerRoleShape>;
14
+ locusUrl: string;
15
+ memberId: string;
16
+ };
17
+
18
+ export type RoleAssignmentBody = {
19
+ role: {
20
+ roles: Array<ServerRoleShape>;
21
+ };
22
+ };
23
+
24
+ export type RoleAssignmentRequest = {
25
+ method: string;
26
+ uri: string;
27
+ body: RoleAssignmentBody;
28
+ };
@@ -1,5 +1,4 @@
1
1
  import uuid from 'uuid';
2
-
3
2
  import {
4
3
  HTTP_VERBS,
5
4
  CONTROLS,
@@ -12,6 +11,8 @@ import {
12
11
  _REMOVE_,
13
12
  } from '../constants';
14
13
 
14
+ import {RoleAssignmentOptions, RoleAssignmentRequest, ServerRoleShape} from './types';
15
+
15
16
  const MembersUtil: any = {};
16
17
 
17
18
  /**
@@ -152,6 +153,22 @@ MembersUtil.generateRaiseHandMemberOptions = (memberId, status, locusUrl) => ({
152
153
  locusUrl,
153
154
  });
154
155
 
156
+ /**
157
+ * @param {String} memberId
158
+ * @param {[ServerRoleShape]} roles
159
+ * @param {String} locusUrl
160
+ * @returns {RoleAssignmentOptions}
161
+ */
162
+ MembersUtil.generateRoleAssignmentMemberOptions = (
163
+ memberId: string,
164
+ roles: Array<ServerRoleShape>,
165
+ locusUrl: string
166
+ ): RoleAssignmentOptions => ({
167
+ memberId,
168
+ roles,
169
+ locusUrl,
170
+ });
171
+
155
172
  MembersUtil.generateLowerAllHandsMemberOptions = (requestingParticipantId, locusUrl) => ({
156
173
  requestingParticipantId,
157
174
  locusUrl,
@@ -173,6 +190,27 @@ MembersUtil.getMuteMemberRequestParams = (options) => {
173
190
  };
174
191
  };
175
192
 
193
+ /**
194
+ * @param {RoleAssignmentOptions} options
195
+ * @returns {RoleAssignmentRequest} the request parameters (method, uri, body) needed to make a addMember request
196
+ */
197
+ MembersUtil.getRoleAssignmentMemberRequestParams = (
198
+ options: RoleAssignmentOptions
199
+ ): RoleAssignmentRequest => {
200
+ const body = {role: {roles: []}};
201
+ options.roles.forEach((role) => {
202
+ body.role.roles.push({type: role.type, hasRole: role.hasRole});
203
+ });
204
+
205
+ const uri = `${options.locusUrl}/${PARTICIPANT}/${options.memberId}/${CONTROLS}`;
206
+
207
+ return {
208
+ method: HTTP_VERBS.PATCH,
209
+ uri,
210
+ body,
211
+ };
212
+ };
213
+
176
214
  MembersUtil.getRaiseHandMemberRequestParams = (options) => {
177
215
  const body = {
178
216
  hand: {
@@ -452,10 +452,10 @@ export default class ReconnectionManager {
452
452
  // So that on rejoin it known what parametrs it was using
453
453
  if (!this.meeting || !this.webex.meetings.getMeetingByType(_ID_, this.meeting.id)) {
454
454
  LoggerProxy.logger.info(
455
- 'ReconnectionManager:index#executeReconnection --> Meeting got deleted due to inactivity or ended remotely '
455
+ 'ReconnectionManager:index#executeReconnection --> Meeting got deleted due to inactivity or ended remotely.'
456
456
  );
457
457
 
458
- throw new Error('Unable to rejoin a meeting already ended or inactive .');
458
+ throw new Error('Unable to rejoin a meeting already ended or inactive.');
459
459
  }
460
460
 
461
461
  LoggerProxy.logger.info(
@@ -10,6 +10,8 @@ describe('plugin-meetings', () => {
10
10
  canLock: null,
11
11
  canUnlock: null,
12
12
  canAssignHost: null,
13
+ canSetPresenter: null,
14
+ canUnsetPresenter: null,
13
15
  canStartRecording: null,
14
16
  canPauseRecording: null,
15
17
  canResumeRecording: null,
@@ -28,6 +30,7 @@ describe('plugin-meetings', () => {
28
30
  canStartTranscribing: null,
29
31
  canStopTranscribing: null,
30
32
  isClosedCaptionActive: null,
33
+ isSaveTranscriptsEnabled: null,
31
34
  isWebexAssistantActive: null,
32
35
  canViewCaptionPanel: null,
33
36
  isRealTimeTranslationEnabled: null,
@@ -57,6 +60,8 @@ describe('plugin-meetings', () => {
57
60
  'canLock',
58
61
  'canUnlock',
59
62
  'canAssignHost',
63
+ 'canSetPresenter',
64
+ 'canUnsetPresenter',
60
65
  'canStartRecording',
61
66
  'canPauseRecording',
62
67
  'canResumeRecording',
@@ -72,6 +77,7 @@ describe('plugin-meetings', () => {
72
77
  'canEnableClosedCaption',
73
78
  'canStopTranscribing',
74
79
  'isClosedCaptionActive',
80
+ 'isSaveTranscriptsEnabled',
75
81
  'isWebexAssistantActive',
76
82
  'canViewCaptionPanel',
77
83
  'isRealTimeTranslationEnabled',
@@ -389,6 +389,7 @@ describe('plugin-meetings', () => {
389
389
  });
390
390
 
391
391
  [
392
+ {functionName: 'isSaveTranscriptsEnabled', displayHint: 'SAVE_TRANSCRIPTS_ENABLED'},
392
393
  {functionName: 'canEnableClosedCaption', displayHint: 'CAPTION_START'},
393
394
  {functionName: 'canStartTranscribing', displayHint: 'TRANSCRIPTION_CONTROL_START'},
394
395
  {functionName: 'canStopTranscribing', displayHint: 'TRANSCRIPTION_CONTROL_STOP'},
@@ -19,4 +19,28 @@ describe('member', () => {
19
19
 
20
20
  assert.calledOnceWithExactly(MemberUtil.isHandRaised, participant);
21
21
  });
22
+
23
+ describe('roles', () => {
24
+ it('checks that processParticipant calls processRoles', () => {
25
+ const participant = {};
26
+
27
+ const member = new Member({});
28
+
29
+ sinon.spy(member, 'processRoles');
30
+ member.processParticipant(participant);
31
+
32
+ assert.calledOnceWithExactly(member.processRoles, participant);
33
+ });
34
+
35
+ it('checks that processRoles calls extractControlRoles', () => {
36
+ const participant = {};
37
+
38
+ const member = new Member({});
39
+
40
+ sinon.spy(MemberUtil, 'extractControlRoles');
41
+ member.processParticipant(participant);
42
+
43
+ assert.calledOnceWithExactly(MemberUtil.extractControlRoles, participant);
44
+ });
45
+ })
22
46
  });