@webex/contact-center 3.12.0-next.9 → 3.12.0-task-refactor.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 (200) hide show
  1. package/AGENTS.md +438 -0
  2. package/ai-docs/README.md +131 -0
  3. package/ai-docs/RULES.md +455 -0
  4. package/ai-docs/patterns/event-driven-patterns.md +485 -0
  5. package/ai-docs/patterns/testing-patterns.md +480 -0
  6. package/ai-docs/patterns/typescript-patterns.md +365 -0
  7. package/ai-docs/templates/README.md +102 -0
  8. package/ai-docs/templates/documentation/create-agents-md.md +240 -0
  9. package/ai-docs/templates/documentation/create-architecture-md.md +295 -0
  10. package/ai-docs/templates/existing-service/bug-fix.md +254 -0
  11. package/ai-docs/templates/existing-service/feature-enhancement.md +450 -0
  12. package/ai-docs/templates/new-method/00-master.md +80 -0
  13. package/ai-docs/templates/new-method/01-requirements.md +232 -0
  14. package/ai-docs/templates/new-method/02-implementation.md +295 -0
  15. package/ai-docs/templates/new-method/03-tests.md +201 -0
  16. package/ai-docs/templates/new-method/04-validation.md +141 -0
  17. package/ai-docs/templates/new-service/00-master.md +109 -0
  18. package/ai-docs/templates/new-service/01-pre-questions.md +159 -0
  19. package/ai-docs/templates/new-service/02-code-generation.md +346 -0
  20. package/ai-docs/templates/new-service/03-integration.md +178 -0
  21. package/ai-docs/templates/new-service/04-test-generation.md +205 -0
  22. package/ai-docs/templates/new-service/05-validation.md +145 -0
  23. package/dist/cc.js +65 -123
  24. package/dist/cc.js.map +1 -1
  25. package/dist/constants.js +13 -2
  26. package/dist/constants.js.map +1 -1
  27. package/dist/index.js +13 -5
  28. package/dist/index.js.map +1 -1
  29. package/dist/metrics/behavioral-events.js +26 -13
  30. package/dist/metrics/behavioral-events.js.map +1 -1
  31. package/dist/metrics/constants.js +7 -6
  32. package/dist/metrics/constants.js.map +1 -1
  33. package/dist/services/ApiAiAssistant.js +0 -3
  34. package/dist/services/ApiAiAssistant.js.map +1 -1
  35. package/dist/services/config/Util.js +2 -3
  36. package/dist/services/config/Util.js.map +1 -1
  37. package/dist/services/config/types.js +16 -14
  38. package/dist/services/config/types.js.map +1 -1
  39. package/dist/services/constants.js +0 -1
  40. package/dist/services/constants.js.map +1 -1
  41. package/dist/services/core/Err.js.map +1 -1
  42. package/dist/services/core/Utils.js +79 -55
  43. package/dist/services/core/Utils.js.map +1 -1
  44. package/dist/services/core/aqm-reqs.js +17 -92
  45. package/dist/services/core/aqm-reqs.js.map +1 -1
  46. package/dist/services/core/websocket/WebSocketManager.js +5 -25
  47. package/dist/services/core/websocket/WebSocketManager.js.map +1 -1
  48. package/dist/services/core/websocket/types.js.map +1 -1
  49. package/dist/services/index.js +1 -2
  50. package/dist/services/index.js.map +1 -1
  51. package/dist/services/task/Task.js +644 -0
  52. package/dist/services/task/Task.js.map +1 -0
  53. package/dist/services/task/TaskFactory.js +45 -0
  54. package/dist/services/task/TaskFactory.js.map +1 -0
  55. package/dist/services/task/TaskManager.js +556 -532
  56. package/dist/services/task/TaskManager.js.map +1 -1
  57. package/dist/services/task/TaskUtils.js +132 -28
  58. package/dist/services/task/TaskUtils.js.map +1 -1
  59. package/dist/services/task/constants.js +7 -6
  60. package/dist/services/task/constants.js.map +1 -1
  61. package/dist/services/task/dialer.js +0 -51
  62. package/dist/services/task/dialer.js.map +1 -1
  63. package/dist/services/task/digital/Digital.js +77 -0
  64. package/dist/services/task/digital/Digital.js.map +1 -0
  65. package/dist/services/task/state-machine/TaskStateMachine.js +634 -0
  66. package/dist/services/task/state-machine/TaskStateMachine.js.map +1 -0
  67. package/dist/services/task/state-machine/actions.js +366 -0
  68. package/dist/services/task/state-machine/actions.js.map +1 -0
  69. package/dist/services/task/state-machine/constants.js +139 -0
  70. package/dist/services/task/state-machine/constants.js.map +1 -0
  71. package/dist/services/task/state-machine/guards.js +256 -0
  72. package/dist/services/task/state-machine/guards.js.map +1 -0
  73. package/dist/services/task/state-machine/index.js +53 -0
  74. package/dist/services/task/state-machine/index.js.map +1 -0
  75. package/dist/services/task/state-machine/types.js +54 -0
  76. package/dist/services/task/state-machine/types.js.map +1 -0
  77. package/dist/services/task/state-machine/uiControlsComputer.js +369 -0
  78. package/dist/services/task/state-machine/uiControlsComputer.js.map +1 -0
  79. package/dist/services/task/taskDataNormalizer.js +99 -0
  80. package/dist/services/task/taskDataNormalizer.js.map +1 -0
  81. package/dist/services/task/types.js +157 -18
  82. package/dist/services/task/types.js.map +1 -1
  83. package/dist/services/task/voice/Voice.js +1031 -0
  84. package/dist/services/task/voice/Voice.js.map +1 -0
  85. package/dist/services/task/voice/WebRTC.js +149 -0
  86. package/dist/services/task/voice/WebRTC.js.map +1 -0
  87. package/dist/types/cc.d.ts +4 -33
  88. package/dist/types/constants.d.ts +13 -2
  89. package/dist/types/index.d.ts +11 -5
  90. package/dist/types/metrics/constants.d.ts +5 -3
  91. package/dist/types/services/ApiAiAssistant.d.ts +1 -1
  92. package/dist/types/services/config/types.d.ts +97 -25
  93. package/dist/types/services/core/Err.d.ts +0 -2
  94. package/dist/types/services/core/Utils.d.ts +25 -23
  95. package/dist/types/services/core/aqm-reqs.d.ts +0 -49
  96. package/dist/types/services/core/websocket/WebSocketManager.d.ts +1 -1
  97. package/dist/types/services/core/websocket/connection-service.d.ts +0 -1
  98. package/dist/types/services/core/websocket/types.d.ts +1 -1
  99. package/dist/types/services/index.d.ts +1 -1
  100. package/dist/types/services/task/Task.d.ts +146 -0
  101. package/dist/types/services/task/TaskFactory.d.ts +12 -0
  102. package/dist/types/services/task/TaskUtils.d.ts +39 -8
  103. package/dist/types/services/task/constants.d.ts +5 -4
  104. package/dist/types/services/task/dialer.d.ts +0 -15
  105. package/dist/types/services/task/digital/Digital.d.ts +22 -0
  106. package/dist/types/services/task/state-machine/TaskStateMachine.d.ts +906 -0
  107. package/dist/types/services/task/state-machine/actions.d.ts +8 -0
  108. package/dist/types/services/task/state-machine/constants.d.ts +91 -0
  109. package/dist/types/services/task/state-machine/guards.d.ts +78 -0
  110. package/dist/types/services/task/state-machine/index.d.ts +13 -0
  111. package/dist/types/services/task/state-machine/types.d.ts +256 -0
  112. package/dist/types/services/task/state-machine/uiControlsComputer.d.ts +9 -0
  113. package/dist/types/services/task/taskDataNormalizer.d.ts +10 -0
  114. package/dist/types/services/task/types.d.ts +539 -88
  115. package/dist/types/services/task/voice/Voice.d.ts +183 -0
  116. package/dist/types/services/task/voice/WebRTC.d.ts +53 -0
  117. package/dist/types/types.d.ts +68 -0
  118. package/dist/types/webex.d.ts +1 -0
  119. package/dist/types.js +70 -0
  120. package/dist/types.js.map +1 -1
  121. package/dist/webex.js +14 -2
  122. package/dist/webex.js.map +1 -1
  123. package/package.json +14 -11
  124. package/src/cc.ts +91 -177
  125. package/src/constants.ts +13 -2
  126. package/src/index.ts +14 -5
  127. package/src/metrics/ai-docs/AGENTS.md +348 -0
  128. package/src/metrics/ai-docs/ARCHITECTURE.md +336 -0
  129. package/src/metrics/behavioral-events.ts +28 -14
  130. package/src/metrics/constants.ts +7 -8
  131. package/src/services/ApiAiAssistant.ts +2 -4
  132. package/src/services/agent/ai-docs/AGENTS.md +238 -0
  133. package/src/services/agent/ai-docs/ARCHITECTURE.md +302 -0
  134. package/src/services/ai-docs/AGENTS.md +384 -0
  135. package/src/services/config/Util.ts +2 -3
  136. package/src/services/config/ai-docs/AGENTS.md +253 -0
  137. package/src/services/config/ai-docs/ARCHITECTURE.md +424 -0
  138. package/src/services/config/types.ts +108 -20
  139. package/src/services/constants.ts +0 -1
  140. package/src/services/core/Err.ts +0 -1
  141. package/src/services/core/Utils.ts +90 -67
  142. package/src/services/core/ai-docs/AGENTS.md +379 -0
  143. package/src/services/core/ai-docs/ARCHITECTURE.md +696 -0
  144. package/src/services/core/aqm-reqs.ts +22 -100
  145. package/src/services/core/websocket/WebSocketManager.ts +4 -23
  146. package/src/services/core/websocket/types.ts +1 -1
  147. package/src/services/index.ts +1 -2
  148. package/src/services/task/Task.ts +785 -0
  149. package/src/services/task/TaskFactory.ts +55 -0
  150. package/src/services/task/TaskManager.ts +567 -633
  151. package/src/services/task/TaskUtils.ts +175 -31
  152. package/src/services/task/ai-docs/AGENTS.md +448 -0
  153. package/src/services/task/ai-docs/ARCHITECTURE.md +573 -0
  154. package/src/services/task/constants.ts +5 -4
  155. package/src/services/task/dialer.ts +1 -56
  156. package/src/services/task/digital/Digital.ts +95 -0
  157. package/src/services/task/state-machine/TaskStateMachine.ts +793 -0
  158. package/src/services/task/state-machine/actions.ts +409 -0
  159. package/src/services/task/state-machine/ai-docs/AGENTS.md +495 -0
  160. package/src/services/task/state-machine/ai-docs/ARCHITECTURE.md +1135 -0
  161. package/src/services/task/state-machine/constants.ts +150 -0
  162. package/src/services/task/state-machine/guards.ts +295 -0
  163. package/src/services/task/state-machine/index.ts +28 -0
  164. package/src/services/task/state-machine/types.ts +228 -0
  165. package/src/services/task/state-machine/uiControlsComputer.ts +529 -0
  166. package/src/services/task/taskDataNormalizer.ts +137 -0
  167. package/src/services/task/types.ts +641 -95
  168. package/src/services/task/voice/Voice.ts +1255 -0
  169. package/src/services/task/voice/WebRTC.ts +187 -0
  170. package/src/types.ts +88 -5
  171. package/src/utils/AGENTS.md +276 -0
  172. package/src/webex.js +2 -0
  173. package/test/unit/spec/cc.ts +59 -142
  174. package/test/unit/spec/logger-proxy.ts +70 -0
  175. package/test/unit/spec/services/ApiAiAssistant.ts +17 -0
  176. package/test/unit/spec/services/config/index.ts +26 -55
  177. package/test/unit/spec/services/core/Utils.ts +103 -52
  178. package/test/unit/spec/services/core/websocket/WebSocketManager.ts +48 -112
  179. package/test/unit/spec/services/core/websocket/connection-service.ts +5 -4
  180. package/test/unit/spec/services/task/AutoWrapup.ts +63 -0
  181. package/test/unit/spec/services/task/Task.ts +416 -0
  182. package/test/unit/spec/services/task/TaskFactory.ts +62 -0
  183. package/test/unit/spec/services/task/TaskManager.ts +781 -1735
  184. package/test/unit/spec/services/task/TaskUtils.ts +125 -0
  185. package/test/unit/spec/services/task/dialer.ts +112 -198
  186. package/test/unit/spec/services/task/digital/Digital.ts +105 -0
  187. package/test/unit/spec/services/task/state-machine/TaskStateMachine.ts +473 -0
  188. package/test/unit/spec/services/task/state-machine/guards.ts +288 -0
  189. package/test/unit/spec/services/task/state-machine/types.ts +18 -0
  190. package/test/unit/spec/services/task/state-machine/uiControlsComputer.ts +147 -0
  191. package/test/unit/spec/services/task/taskTestUtils.ts +87 -0
  192. package/test/unit/spec/services/task/voice/Voice.ts +587 -0
  193. package/test/unit/spec/services/task/voice/WebRTC.ts +242 -0
  194. package/umd/contact-center.min.js +2 -2
  195. package/umd/contact-center.min.js.map +1 -1
  196. package/dist/services/task/index.js +0 -1525
  197. package/dist/services/task/index.js.map +0 -1
  198. package/dist/types/services/task/index.d.ts +0 -650
  199. package/src/services/task/index.ts +0 -1801
  200. package/test/unit/spec/services/task/index.ts +0 -2184
@@ -0,0 +1,480 @@
1
+ # Testing Patterns - Contact Center SDK
2
+
3
+ > **Purpose**: Jest testing patterns and conventions for the Contact Center SDK.
4
+
5
+ ---
6
+
7
+ ## Test Structure
8
+
9
+ ### File Location
10
+
11
+ ```
12
+ packages/@webex/contact-center/
13
+ ├── src/
14
+ │ ├── cc.ts
15
+ │ └── services/
16
+ │ ├── agent/
17
+ │ │ └── index.ts
18
+ │ ├── task/
19
+ │ │ └── TaskManager.ts
20
+ │ └── core/
21
+ │ └── Utils.ts
22
+ ├── test/
23
+ │ └── unit/
24
+ │ └── spec/
25
+ │ ├── cc.ts # Tests for src/cc.ts
26
+ │ └── services/
27
+ │ ├── agent/
28
+ │ │ └── index.ts # Tests for src/services/agent/index.ts
29
+ │ ├── task/
30
+ │ │ └── TaskManager.ts # Tests for src/services/task/TaskManager.ts
31
+ │ └── core/
32
+ │ └── Utils.ts # Tests for src/services/core/Utils.ts
33
+ ```
34
+
35
+ ### Test File Rule
36
+
37
+ **Every new source file MUST have a corresponding test file.** The test file location mirrors the source file path:
38
+
39
+ - Source: `src/services/{service}/{FileName}.ts`
40
+ - Test: `test/unit/spec/services/{service}/{FileName}.ts`
41
+
42
+ When creating a new source file, always create the corresponding test file in the matching directory structure under `test/unit/spec/`.
43
+
44
+ ### Test File Template
45
+
46
+ ```typescript
47
+ import 'jsdom-global/register';
48
+ import MockWebex from '@webex/test-helper-mock-webex';
49
+ import ContactCenter from '../../../src/cc';
50
+ import {WebexSDK} from '../../../src/types';
51
+ import config from '../../../src/config';
52
+
53
+ // Mock dependencies
54
+ jest.mock('../../../src/logger-proxy', () => ({
55
+ __esModule: true,
56
+ default: {
57
+ log: jest.fn(),
58
+ error: jest.fn(),
59
+ info: jest.fn(),
60
+ warn: jest.fn(),
61
+ trace: jest.fn(),
62
+ initialize: jest.fn(),
63
+ },
64
+ }));
65
+
66
+ describe('FeatureName', () => {
67
+ let webex: WebexSDK;
68
+
69
+ beforeEach(() => {
70
+ webex = MockWebex({
71
+ children: {
72
+ cc: ContactCenter,
73
+ },
74
+ logger: {
75
+ log: jest.fn(),
76
+ error: jest.fn(),
77
+ info: jest.fn(),
78
+ },
79
+ credentials: {
80
+ getOrgId: jest.fn(() => 'mockOrgId'),
81
+ },
82
+ config: config,
83
+ }) as unknown as WebexSDK; // MockWebex requires double-cast — do NOT use this pattern elsewhere
84
+ });
85
+
86
+ afterEach(() => {
87
+ jest.clearAllMocks();
88
+ });
89
+
90
+ describe('methodName', () => {
91
+ it('should do something specific', async () => {
92
+ // Arrange
93
+ const input = { /* test data */ };
94
+
95
+ // Act
96
+ const result = await webex.cc.methodName(input);
97
+
98
+ // Assert
99
+ expect(result).toBeDefined();
100
+ });
101
+ });
102
+ });
103
+ ```
104
+
105
+ ---
106
+
107
+ ## MockWebex Setup
108
+
109
+ ### Basic Setup
110
+
111
+ ```typescript
112
+ import MockWebex from '@webex/test-helper-mock-webex';
113
+ import ContactCenter from '../../../src/cc';
114
+ import Mercury from '@webex/internal-plugin-mercury';
115
+
116
+ beforeEach(() => {
117
+ webex = MockWebex({
118
+ children: {
119
+ cc: ContactCenter,
120
+ mercury: Mercury,
121
+ },
122
+ logger: {
123
+ log: jest.fn(),
124
+ error: jest.fn(),
125
+ info: jest.fn(),
126
+ },
127
+ credentials: {
128
+ getOrgId: jest.fn(() => 'mockOrgId'),
129
+ },
130
+ config: config,
131
+ once: jest.fn((event, callback) => callback()),
132
+ }) as unknown as WebexSDK;
133
+ });
134
+ ```
135
+
136
+ ### With Internal Plugins
137
+
138
+ ```typescript
139
+ webex = MockWebex({
140
+ children: {
141
+ cc: ContactCenter,
142
+ mercury: Mercury,
143
+ },
144
+ internal: {
145
+ mercury: {
146
+ connected: false,
147
+ connect: jest.fn().mockResolvedValue(undefined),
148
+ disconnect: jest.fn().mockResolvedValue(undefined),
149
+ on: jest.fn(),
150
+ off: jest.fn(),
151
+ },
152
+ device: {
153
+ unregister: jest.fn().mockResolvedValue(undefined),
154
+ },
155
+ },
156
+ }) as unknown as WebexSDK;
157
+ ```
158
+
159
+ ---
160
+
161
+ ## Mocking Singletons
162
+
163
+ ### Services Singleton
164
+
165
+ ```typescript
166
+ import Services from '../../../src/services';
167
+
168
+ const mockServicesInstance = {
169
+ agent: {
170
+ stationLogin: jest.fn(),
171
+ logout: jest.fn(),
172
+ reload: jest.fn(),
173
+ stateChange: jest.fn(),
174
+ buddyAgents: jest.fn(),
175
+ },
176
+ config: {
177
+ getAgentConfig: jest.fn(),
178
+ getOutdialAniEntries: jest.fn(),
179
+ },
180
+ webSocketManager: {
181
+ initWebSocket: jest.fn(),
182
+ on: jest.fn(),
183
+ off: jest.fn(),
184
+ close: jest.fn(),
185
+ isSocketClosed: false,
186
+ },
187
+ connectionService: {
188
+ on: jest.fn(),
189
+ off: jest.fn(),
190
+ },
191
+ contact: {
192
+ accept: jest.fn(),
193
+ hold: jest.fn(),
194
+ transfer: jest.fn(),
195
+ },
196
+ dialer: {
197
+ startOutdial: jest.fn(),
198
+ },
199
+ };
200
+
201
+ jest.spyOn(Services, 'getInstance').mockReturnValue(mockServicesInstance as any);
202
+ ```
203
+
204
+ ### TaskManager Singleton
205
+
206
+ ```typescript
207
+ import TaskManager from '../../../src/services/task/TaskManager';
208
+
209
+ const mockTaskManager = {
210
+ taskCollection: {},
211
+ setWrapupData: jest.fn(),
212
+ setAgentId: jest.fn(),
213
+ registerIncomingCallEvent: jest.fn(),
214
+ registerTaskListeners: jest.fn(),
215
+ getTask: jest.fn(),
216
+ getAllTasks: jest.fn(),
217
+ on: jest.fn(),
218
+ off: jest.fn(),
219
+ emit: jest.fn(),
220
+ unregisterIncomingCallEvent: jest.fn(),
221
+ };
222
+
223
+ jest.spyOn(TaskManager, 'getTaskManager').mockReturnValue(mockTaskManager);
224
+ ```
225
+
226
+ ### MetricsManager Singleton
227
+
228
+ ```typescript
229
+ import MetricsManager from '../../../src/metrics/MetricsManager';
230
+
231
+ const mockMetricsManager = {
232
+ trackEvent: jest.fn(),
233
+ timeEvent: jest.fn(),
234
+ };
235
+
236
+ jest.spyOn(MetricsManager, 'getInstance').mockReturnValue(mockMetricsManager);
237
+ ```
238
+
239
+ ---
240
+
241
+ ## Async Testing
242
+
243
+ ### Promise Resolution
244
+
245
+ ```typescript
246
+ it('should resolve with data on success', async () => {
247
+ // Arrange
248
+ mockServicesInstance.agent.stationLogin.mockResolvedValue({
249
+ data: { agentId: '123', status: 'LoggedIn' },
250
+ trackingId: 'track-123',
251
+ });
252
+
253
+ // Act
254
+ const result = await webex.cc.stationLogin({
255
+ teamId: 'team-1',
256
+ loginOption: 'BROWSER',
257
+ });
258
+
259
+ // Assert — always use exact matches, avoid expect.objectContaining
260
+ expect(result).toEqual({
261
+ agentId: '123',
262
+ status: 'LoggedIn',
263
+ trackingId: 'track-123',
264
+ });
265
+ });
266
+ ```
267
+
268
+ ### Promise Rejection
269
+
270
+ ```typescript
271
+ it('should throw error on failure', async () => {
272
+ // Arrange
273
+ const mockError = new Error('Login failed');
274
+ mockError.details = {
275
+ type: 'LoginFailed',
276
+ data: { reason: 'INVALID_CREDENTIALS' },
277
+ };
278
+ mockServicesInstance.agent.stationLogin.mockRejectedValue(mockError);
279
+
280
+ // Act & Assert
281
+ await expect(
282
+ webex.cc.stationLogin({ teamId: 'team-1', loginOption: 'BROWSER' })
283
+ ).rejects.toThrow('INVALID_CREDENTIALS');
284
+ });
285
+ ```
286
+
287
+ ---
288
+
289
+ ## Event Testing
290
+
291
+ Event listeners and their callbacks are tested by spying on the registration, extracting the callback via `mock.calls`, and invoking it directly.
292
+
293
+ ### Testing Event Listener Registration
294
+
295
+ ```typescript
296
+ it('should register event listeners on init', () => {
297
+ // Verify the listener was registered
298
+ expect(mockTaskManager.on).toHaveBeenCalledWith(
299
+ 'task:incoming',
300
+ expect.any(Function)
301
+ );
302
+ });
303
+ ```
304
+
305
+ ### Testing Event Callbacks via mock.calls
306
+
307
+ ```typescript
308
+ it('should handle websocket message and emit event', () => {
309
+ // Step 1: Find the registered callback via mock.calls
310
+ const onCalls = mockServicesInstance.webSocketManager.on.mock.calls;
311
+ const messageCall = onCalls.find(([event]) => event === 'message');
312
+ const wsHandler = messageCall[1];
313
+
314
+ // Step 2: Spy on the emit
315
+ const emitSpy = jest.spyOn(webex.cc, 'emit');
316
+
317
+ // Step 3: Invoke the callback directly with test data
318
+ wsHandler(JSON.stringify({
319
+ type: 'AgentStateChange',
320
+ data: { type: 'AgentStateChangeSuccess', agentId: 'agent-123', state: 'Available' },
321
+ }));
322
+
323
+ // Step 4: Assert exact emit arguments
324
+ expect(emitSpy).toHaveBeenCalledWith('agent:stateChange', {
325
+ type: 'AgentStateChangeSuccess',
326
+ agentId: 'agent-123',
327
+ state: 'Available',
328
+ });
329
+ });
330
+ ```
331
+
332
+ ### Testing TaskManager Event Callbacks
333
+
334
+ ```typescript
335
+ it('should trigger task:incoming when TaskManager emits', () => {
336
+ // Extract the registered callback
337
+ const taskIncomingCall = mockTaskManager.on.mock.calls
338
+ .find(([event]) => event === 'task:incoming');
339
+ const taskHandler = taskIncomingCall[1];
340
+
341
+ const triggerSpy = jest.spyOn(webex.cc, 'trigger');
342
+
343
+ // Invoke the callback
344
+ const mockTask = { interactionId: 'int-123', taskId: 'task-456' };
345
+ taskHandler(mockTask);
346
+
347
+ // Assert
348
+ expect(triggerSpy).toHaveBeenCalledWith('task:incoming', mockTask);
349
+ });
350
+ ```
351
+
352
+ ---
353
+
354
+ ## Mocking External APIs
355
+
356
+ ### Worker Mock
357
+
358
+ ```typescript
359
+ // __mocks__/workerMock.ts
360
+ class Worker {
361
+ onmessage: ((msg: any) => void) | null = null;
362
+
363
+ postMessage(msg: any) {
364
+ if (this.onmessage) {
365
+ this.onmessage({ data: msg });
366
+ }
367
+ }
368
+
369
+ terminate() {}
370
+ }
371
+
372
+ global.Worker = Worker as any;
373
+ ```
374
+
375
+ ### URL Mock
376
+
377
+ ```typescript
378
+ global.URL.createObjectURL = jest.fn(() => 'blob:http://localhost:3000/12345');
379
+ ```
380
+
381
+ ### UUID Mock
382
+
383
+ ```typescript
384
+ jest.mock('uuid', () => ({
385
+ v4: () => 'mock-tracking-uuid',
386
+ }));
387
+ ```
388
+
389
+ ---
390
+
391
+ ## Test Utilities
392
+
393
+ ### Spy on Utility Functions
394
+
395
+ ```typescript
396
+ import * as Utils from '../../../src/services/core/Utils';
397
+
398
+ let getErrorDetailsSpy: jest.SpyInstance;
399
+
400
+ beforeEach(() => {
401
+ getErrorDetailsSpy = jest.spyOn(Utils, 'getErrorDetails');
402
+ });
403
+
404
+ it('should call getErrorDetails on failure', async () => {
405
+ mockServicesInstance.agent.stationLogin.mockRejectedValue(mockError);
406
+
407
+ await expect(webex.cc.stationLogin(data)).rejects.toThrow();
408
+
409
+ expect(getErrorDetailsSpy).toHaveBeenCalledWith(
410
+ expect.any(Error),
411
+ 'stationLogin',
412
+ 'ContactCenter'
413
+ );
414
+ });
415
+ ```
416
+
417
+ ---
418
+
419
+ ## Common Assertions
420
+
421
+ **Prefer exact matches over `expect.objectContaining` in new tests.** Exact matches catch unexpected field changes and keep tests rigorous. Existing tests may use `expect.objectContaining` for complex objects — this is acceptable but not preferred for new code.
422
+
423
+ ### Structure Assertions
424
+
425
+ ```typescript
426
+ // Exact match on result — preferred
427
+ expect(result).toEqual({
428
+ agentId: 'agent-123',
429
+ status: 'LoggedIn',
430
+ trackingId: 'track-456',
431
+ });
432
+
433
+ // Array exact match
434
+ expect(result.teams).toEqual([
435
+ { teamId: 'team-1', teamName: 'Support' },
436
+ { teamId: 'team-2', teamName: 'Sales' },
437
+ ]);
438
+ ```
439
+
440
+ ### Call Assertions
441
+
442
+ ```typescript
443
+ // Check mock was called with exact args
444
+ expect(mockServicesInstance.agent.stationLogin).toHaveBeenCalledWith({
445
+ data: {
446
+ teamId: 'team-1',
447
+ deviceType: 'BROWSER',
448
+ },
449
+ });
450
+
451
+ // Check call count
452
+ expect(mockMetricsManager.trackEvent).toHaveBeenCalledTimes(1);
453
+
454
+ // Check specific call with exact values
455
+ expect(mockMetricsManager.timeEvent).toHaveBeenCalledWith([
456
+ 'STATION_LOGIN_SUCCESS',
457
+ 'STATION_LOGIN_FAILED',
458
+ ]);
459
+ ```
460
+
461
+ ---
462
+
463
+ ## Test Coverage Goals
464
+
465
+ Target: **85% coverage**
466
+
467
+ ```bash
468
+ # Run tests (coverage is collected automatically via jest.config.js)
469
+ yarn workspace @webex/contact-center test:unit
470
+
471
+ # Coverage thresholds (jest.config.js)
472
+ coverageThreshold: {
473
+ global: {
474
+ branches: 85,
475
+ functions: 85,
476
+ lines: 85,
477
+ statements: 85,
478
+ },
479
+ }
480
+ ```