@webex/contact-center 3.9.0-next.24 → 3.9.0-next.26

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.
@@ -0,0 +1,131 @@
1
+ import { checkParticipantNotInInteraction,
2
+ getIsConferenceInProgress,
3
+ isParticipantInMainInteraction,
4
+ isPrimary,} from '../../../../../src/services/task/TaskUtils';
5
+ import {ITask} from '../../../../../src/services/task/types';
6
+
7
+ describe('TaskUtils', () => {
8
+ let mockTask: ITask;
9
+ const mockAgentId = 'agent-123';
10
+ const mockOtherAgentId = 'agent-456';
11
+
12
+ beforeEach(() => {
13
+ mockTask = {
14
+ data: {
15
+ interactionId: 'interaction-123',
16
+ agentId: mockAgentId,
17
+ interaction: {
18
+ owner: mockAgentId,
19
+ participants: {
20
+ [mockAgentId]: { hasLeft: false },
21
+ [mockOtherAgentId]: { hasLeft: false },
22
+ },
23
+ media: {
24
+ 'media-1': {
25
+ mType: 'mainCall',
26
+ participants: [mockAgentId, mockOtherAgentId],
27
+ },
28
+ },
29
+ },
30
+ },
31
+ emit: jest.fn(),
32
+ updateTaskData: jest.fn(),
33
+ } as any;
34
+ });
35
+
36
+ describe('isPrimary', () => {
37
+ it('should return true when agent is the owner', () => {
38
+ expect(isPrimary(mockTask, mockAgentId)).toBe(true);
39
+ });
40
+
41
+ it('should return false when agent is not the owner', () => {
42
+ expect(isPrimary(mockTask, mockOtherAgentId)).toBe(false);
43
+ });
44
+
45
+ it('should fallback to data.agentId when owner is not set', () => {
46
+ mockTask.data.interaction.owner = undefined;
47
+ expect(isPrimary(mockTask, mockAgentId)).toBe(true);
48
+ expect(isPrimary(mockTask, mockOtherAgentId)).toBe(false);
49
+ });
50
+ });
51
+
52
+ describe('isParticipantInMainInteraction', () => {
53
+ it('should return true when agent is in mainCall media', () => {
54
+ expect(isParticipantInMainInteraction(mockTask, mockAgentId)).toBe(true);
55
+ });
56
+
57
+ it('should return false when agent is not in mainCall media', () => {
58
+ mockTask.data.interaction.media['media-1'].participants = [mockOtherAgentId];
59
+ expect(isParticipantInMainInteraction(mockTask, mockAgentId)).toBe(false);
60
+ });
61
+
62
+ it('should return false when no mainCall media exists', () => {
63
+ mockTask.data.interaction.media['media-1'].mType = 'consult';
64
+ expect(isParticipantInMainInteraction(mockTask, mockAgentId)).toBe(false);
65
+ });
66
+ });
67
+
68
+ describe('checkParticipantNotInInteraction', () => {
69
+ it('should return false when agent is active participant', () => {
70
+ expect(checkParticipantNotInInteraction(mockTask, mockAgentId)).toBe(false);
71
+ });
72
+
73
+ it('should return true when agent is not in participants', () => {
74
+ delete mockTask.data.interaction.participants[mockAgentId];
75
+ expect(checkParticipantNotInInteraction(mockTask, mockAgentId)).toBe(true);
76
+ });
77
+
78
+ it('should return true when agent has left', () => {
79
+ mockTask.data.interaction.participants[mockAgentId].hasLeft = true;
80
+ expect(checkParticipantNotInInteraction(mockTask, mockAgentId)).toBe(true);
81
+ });
82
+ });
83
+
84
+ describe('getIsConferenceInProgress', () => {
85
+ beforeEach(() => {
86
+ // Set up mock task with proper media structure for conference detection
87
+ mockTask.data.interaction.media = {
88
+ [mockTask.data.interactionId]: {
89
+ mType: 'mainCall',
90
+ participants: [mockAgentId, mockOtherAgentId, 'customer-123'],
91
+ },
92
+ };
93
+ mockTask.data.interaction.participants = {
94
+ [mockAgentId]: { pType: 'Agent', hasLeft: false },
95
+ [mockOtherAgentId]: { pType: 'Agent', hasLeft: false },
96
+ 'customer-123': { pType: 'Customer', hasLeft: false },
97
+ };
98
+ });
99
+
100
+ it('should return true when there are 2 or more active agents', () => {
101
+ expect(getIsConferenceInProgress(mockTask)).toBe(true);
102
+ });
103
+
104
+ it('should return false when there is only 1 active agent', () => {
105
+ mockTask.data.interaction.participants[mockOtherAgentId].hasLeft = true;
106
+ expect(getIsConferenceInProgress(mockTask)).toBe(false);
107
+ });
108
+
109
+ it('should exclude customers from agent count', () => {
110
+ // Remove one agent, should still be false with only 1 agent + customer
111
+ delete mockTask.data.interaction.participants[mockOtherAgentId];
112
+ mockTask.data.interaction.media[mockTask.data.interactionId].participants = [mockAgentId, 'customer-123'];
113
+ expect(getIsConferenceInProgress(mockTask)).toBe(false);
114
+ });
115
+
116
+ it('should exclude supervisors from agent count', () => {
117
+ mockTask.data.interaction.participants[mockOtherAgentId].pType = 'Supervisor';
118
+ expect(getIsConferenceInProgress(mockTask)).toBe(false);
119
+ });
120
+
121
+ it('should exclude VVA from agent count', () => {
122
+ mockTask.data.interaction.participants[mockOtherAgentId].pType = 'VVA';
123
+ expect(getIsConferenceInProgress(mockTask)).toBe(false);
124
+ });
125
+
126
+ it('should return false when no main call media exists', () => {
127
+ mockTask.data.interaction.media = {};
128
+ expect(getIsConferenceInProgress(mockTask)).toBe(false);
129
+ });
130
+ });
131
+ });
@@ -217,7 +217,7 @@ describe('Task', () => {
217
217
  });
218
218
 
219
219
  describe('updateTaskData cases', () => {
220
- it('test updating the task data by overwrite', async () => {
220
+ it('updates the task data by overwrite', async () => {
221
221
  const newData = {
222
222
  type: CC_EVENTS.AGENT_CONTACT_ASSIGNED,
223
223
  agentId: '723a8ffb-a26e-496d-b14a-ff44fb83b64f',
@@ -266,12 +266,12 @@ describe('Task', () => {
266
266
  expect(task.data).toEqual(newData);
267
267
  });
268
268
 
269
- it('test updating the task data by merging', async () => {
269
+ it('updates the task data by merging with key removal', async () => {
270
270
  const newData = {
271
- // ...taskDataMock, // Purposefully omit this to test scenario when other keys isn't present
271
+ // Purposefully omit other keys to test remove and merge behavior
272
272
  isConsulting: true, // Add a new custom key to test persistence
273
273
  interaction: {
274
- // ...taskDataMock.interaction, // Purposefully omit this to test scenario when a nested key isn't present
274
+ // Purposefully omit other interaction keys to test removal
275
275
  media: {
276
276
  '58a45567-4e61-4f4b-a580-5bc86357bef0': {
277
277
  holdTimestamp: null,
@@ -298,11 +298,12 @@ describe('Task', () => {
298
298
  },
299
299
  };
300
300
 
301
+ // The reconcileData method removes keys from oldData that are not in newData
302
+ // This means only keys present in newData will remain in the final result
301
303
  const expectedData: TaskData = {
302
- ...taskDataMock,
303
- isConsulting: true,
304
+ isConsulting: true, // New key is added
304
305
  interaction: {
305
- ...taskDataMock.interaction,
306
+ // Only the media key from newData.interaction remains
306
307
  media: {
307
308
  '58a45567-4e61-4f4b-a580-5bc86357bef0': {
308
309
  holdTimestamp: null,
@@ -335,6 +336,60 @@ describe('Task', () => {
335
336
 
336
337
  expect(task.data).toEqual(expectedData);
337
338
  });
339
+
340
+ it('updates the task data by merging and preserving existing keys', async () => {
341
+ const newData = {
342
+ ...taskDataMock, // Include all existing keys to test merge without removal
343
+ isConsulting: true, // Add a new custom key
344
+ interaction: {
345
+ ...taskDataMock.interaction, // Include existing interaction data
346
+ media: {
347
+ ...taskDataMock.interaction.media, // Include existing media
348
+ '58a45567-4e61-4f4b-a580-5bc86357bef0': {
349
+ holdTimestamp: null,
350
+ isHold: true,
351
+ mType: 'consult',
352
+ mediaMgr: 'callmm',
353
+ mediaResourceId: '58a45567-4e61-4f4b-a580-5bc86357bef0',
354
+ mediaType: 'telephony',
355
+ participants: [
356
+ 'f520d6b5-28ad-4f2f-b83e-781bb64af617',
357
+ '723a8ffb-a26e-496d-b14a-ff44fb83b64f',
358
+ ],
359
+ },
360
+ },
361
+ },
362
+ };
363
+
364
+ const expectedData: TaskData = {
365
+ ...taskDataMock,
366
+ isConsulting: true,
367
+ interaction: {
368
+ ...taskDataMock.interaction,
369
+ media: {
370
+ ...taskDataMock.interaction.media,
371
+ '58a45567-4e61-4f4b-a580-5bc86357bef0': {
372
+ holdTimestamp: null,
373
+ isHold: true,
374
+ mType: 'consult',
375
+ mediaMgr: 'callmm',
376
+ mediaResourceId: '58a45567-4e61-4f4b-a580-5bc86357bef0',
377
+ mediaType: 'telephony',
378
+ participants: [
379
+ 'f520d6b5-28ad-4f2f-b83e-781bb64af617',
380
+ '723a8ffb-a26e-496d-b14a-ff44fb83b64f',
381
+ ],
382
+ },
383
+ },
384
+ },
385
+ };
386
+
387
+ expect(task.data).toEqual(taskDataMock);
388
+ const shouldOverwrite = false;
389
+ task.updateTaskData(newData, shouldOverwrite);
390
+
391
+ expect(task.data).toEqual(expectedData);
392
+ });
338
393
  });
339
394
 
340
395
  it('should accept a task and answer call when using BROWSER login option', async () => {
@@ -1707,9 +1762,6 @@ describe('Task', () => {
1707
1762
  });
1708
1763
  });
1709
1764
 
1710
- // TODO: Uncomment this test section in future PR for Multi-Party Conference support (>3 participants)
1711
- // Conference transfer tests will be uncommented when implementing enhanced multi-party conference functionality
1712
- /*
1713
1765
  describe('transferConference', () => {
1714
1766
  it('should successfully transfer conference', async () => {
1715
1767
  const mockResponse = {
@@ -1756,6 +1808,5 @@ describe('Task', () => {
1756
1808
  });
1757
1809
  });
1758
1810
  });
1759
- */
1760
1811
  });
1761
1812
  });