@webex/contact-center 3.12.0-next.8 → 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.
- package/AGENTS.md +438 -0
- package/ai-docs/README.md +131 -0
- package/ai-docs/RULES.md +455 -0
- package/ai-docs/patterns/event-driven-patterns.md +485 -0
- package/ai-docs/patterns/testing-patterns.md +480 -0
- package/ai-docs/patterns/typescript-patterns.md +365 -0
- package/ai-docs/templates/README.md +102 -0
- package/ai-docs/templates/documentation/create-agents-md.md +240 -0
- package/ai-docs/templates/documentation/create-architecture-md.md +295 -0
- package/ai-docs/templates/existing-service/bug-fix.md +254 -0
- package/ai-docs/templates/existing-service/feature-enhancement.md +450 -0
- package/ai-docs/templates/new-method/00-master.md +80 -0
- package/ai-docs/templates/new-method/01-requirements.md +232 -0
- package/ai-docs/templates/new-method/02-implementation.md +295 -0
- package/ai-docs/templates/new-method/03-tests.md +201 -0
- package/ai-docs/templates/new-method/04-validation.md +141 -0
- package/ai-docs/templates/new-service/00-master.md +109 -0
- package/ai-docs/templates/new-service/01-pre-questions.md +159 -0
- package/ai-docs/templates/new-service/02-code-generation.md +346 -0
- package/ai-docs/templates/new-service/03-integration.md +178 -0
- package/ai-docs/templates/new-service/04-test-generation.md +205 -0
- package/ai-docs/templates/new-service/05-validation.md +145 -0
- package/dist/cc.js +65 -123
- package/dist/cc.js.map +1 -1
- package/dist/constants.js +13 -2
- package/dist/constants.js.map +1 -1
- package/dist/index.js +13 -5
- package/dist/index.js.map +1 -1
- package/dist/metrics/behavioral-events.js +26 -13
- package/dist/metrics/behavioral-events.js.map +1 -1
- package/dist/metrics/constants.js +7 -6
- package/dist/metrics/constants.js.map +1 -1
- package/dist/services/ApiAiAssistant.js +0 -3
- package/dist/services/ApiAiAssistant.js.map +1 -1
- package/dist/services/config/Util.js +2 -3
- package/dist/services/config/Util.js.map +1 -1
- package/dist/services/config/types.js +16 -14
- package/dist/services/config/types.js.map +1 -1
- package/dist/services/constants.js +0 -1
- package/dist/services/constants.js.map +1 -1
- package/dist/services/core/Err.js.map +1 -1
- package/dist/services/core/Utils.js +79 -55
- package/dist/services/core/Utils.js.map +1 -1
- package/dist/services/core/aqm-reqs.js +17 -92
- package/dist/services/core/aqm-reqs.js.map +1 -1
- package/dist/services/core/websocket/WebSocketManager.js +5 -25
- package/dist/services/core/websocket/WebSocketManager.js.map +1 -1
- package/dist/services/core/websocket/types.js.map +1 -1
- package/dist/services/index.js +1 -2
- package/dist/services/index.js.map +1 -1
- package/dist/services/task/Task.js +644 -0
- package/dist/services/task/Task.js.map +1 -0
- package/dist/services/task/TaskFactory.js +45 -0
- package/dist/services/task/TaskFactory.js.map +1 -0
- package/dist/services/task/TaskManager.js +556 -532
- package/dist/services/task/TaskManager.js.map +1 -1
- package/dist/services/task/TaskUtils.js +132 -28
- package/dist/services/task/TaskUtils.js.map +1 -1
- package/dist/services/task/constants.js +7 -6
- package/dist/services/task/constants.js.map +1 -1
- package/dist/services/task/dialer.js +0 -51
- package/dist/services/task/dialer.js.map +1 -1
- package/dist/services/task/digital/Digital.js +77 -0
- package/dist/services/task/digital/Digital.js.map +1 -0
- package/dist/services/task/state-machine/TaskStateMachine.js +634 -0
- package/dist/services/task/state-machine/TaskStateMachine.js.map +1 -0
- package/dist/services/task/state-machine/actions.js +366 -0
- package/dist/services/task/state-machine/actions.js.map +1 -0
- package/dist/services/task/state-machine/constants.js +139 -0
- package/dist/services/task/state-machine/constants.js.map +1 -0
- package/dist/services/task/state-machine/guards.js +256 -0
- package/dist/services/task/state-machine/guards.js.map +1 -0
- package/dist/services/task/state-machine/index.js +53 -0
- package/dist/services/task/state-machine/index.js.map +1 -0
- package/dist/services/task/state-machine/types.js +54 -0
- package/dist/services/task/state-machine/types.js.map +1 -0
- package/dist/services/task/state-machine/uiControlsComputer.js +369 -0
- package/dist/services/task/state-machine/uiControlsComputer.js.map +1 -0
- package/dist/services/task/taskDataNormalizer.js +99 -0
- package/dist/services/task/taskDataNormalizer.js.map +1 -0
- package/dist/services/task/types.js +157 -18
- package/dist/services/task/types.js.map +1 -1
- package/dist/services/task/voice/Voice.js +1031 -0
- package/dist/services/task/voice/Voice.js.map +1 -0
- package/dist/services/task/voice/WebRTC.js +149 -0
- package/dist/services/task/voice/WebRTC.js.map +1 -0
- package/dist/types/cc.d.ts +4 -33
- package/dist/types/constants.d.ts +13 -2
- package/dist/types/index.d.ts +11 -5
- package/dist/types/metrics/constants.d.ts +5 -3
- package/dist/types/services/ApiAiAssistant.d.ts +1 -1
- package/dist/types/services/config/types.d.ts +97 -25
- package/dist/types/services/core/Err.d.ts +0 -2
- package/dist/types/services/core/Utils.d.ts +25 -23
- package/dist/types/services/core/aqm-reqs.d.ts +0 -49
- package/dist/types/services/core/websocket/WebSocketManager.d.ts +1 -1
- package/dist/types/services/core/websocket/connection-service.d.ts +0 -1
- package/dist/types/services/core/websocket/types.d.ts +1 -1
- package/dist/types/services/index.d.ts +1 -1
- package/dist/types/services/task/Task.d.ts +146 -0
- package/dist/types/services/task/TaskFactory.d.ts +12 -0
- package/dist/types/services/task/TaskUtils.d.ts +39 -8
- package/dist/types/services/task/constants.d.ts +5 -4
- package/dist/types/services/task/dialer.d.ts +0 -15
- package/dist/types/services/task/digital/Digital.d.ts +22 -0
- package/dist/types/services/task/state-machine/TaskStateMachine.d.ts +906 -0
- package/dist/types/services/task/state-machine/actions.d.ts +8 -0
- package/dist/types/services/task/state-machine/constants.d.ts +91 -0
- package/dist/types/services/task/state-machine/guards.d.ts +78 -0
- package/dist/types/services/task/state-machine/index.d.ts +13 -0
- package/dist/types/services/task/state-machine/types.d.ts +256 -0
- package/dist/types/services/task/state-machine/uiControlsComputer.d.ts +9 -0
- package/dist/types/services/task/taskDataNormalizer.d.ts +10 -0
- package/dist/types/services/task/types.d.ts +539 -88
- package/dist/types/services/task/voice/Voice.d.ts +183 -0
- package/dist/types/services/task/voice/WebRTC.d.ts +53 -0
- package/dist/types/types.d.ts +68 -0
- package/dist/types/webex.d.ts +1 -0
- package/dist/types.js +70 -0
- package/dist/types.js.map +1 -1
- package/dist/webex.js +14 -2
- package/dist/webex.js.map +1 -1
- package/package.json +14 -11
- package/src/cc.ts +91 -177
- package/src/constants.ts +13 -2
- package/src/index.ts +14 -5
- package/src/metrics/ai-docs/AGENTS.md +348 -0
- package/src/metrics/ai-docs/ARCHITECTURE.md +336 -0
- package/src/metrics/behavioral-events.ts +28 -14
- package/src/metrics/constants.ts +7 -8
- package/src/services/ApiAiAssistant.ts +2 -4
- package/src/services/agent/ai-docs/AGENTS.md +238 -0
- package/src/services/agent/ai-docs/ARCHITECTURE.md +302 -0
- package/src/services/ai-docs/AGENTS.md +384 -0
- package/src/services/config/Util.ts +2 -3
- package/src/services/config/ai-docs/AGENTS.md +253 -0
- package/src/services/config/ai-docs/ARCHITECTURE.md +424 -0
- package/src/services/config/types.ts +108 -20
- package/src/services/constants.ts +0 -1
- package/src/services/core/Err.ts +0 -1
- package/src/services/core/Utils.ts +90 -67
- package/src/services/core/ai-docs/AGENTS.md +379 -0
- package/src/services/core/ai-docs/ARCHITECTURE.md +696 -0
- package/src/services/core/aqm-reqs.ts +22 -100
- package/src/services/core/websocket/WebSocketManager.ts +4 -23
- package/src/services/core/websocket/types.ts +1 -1
- package/src/services/index.ts +1 -2
- package/src/services/task/Task.ts +785 -0
- package/src/services/task/TaskFactory.ts +55 -0
- package/src/services/task/TaskManager.ts +567 -633
- package/src/services/task/TaskUtils.ts +175 -31
- package/src/services/task/ai-docs/AGENTS.md +448 -0
- package/src/services/task/ai-docs/ARCHITECTURE.md +573 -0
- package/src/services/task/constants.ts +5 -4
- package/src/services/task/dialer.ts +1 -56
- package/src/services/task/digital/Digital.ts +95 -0
- package/src/services/task/state-machine/TaskStateMachine.ts +793 -0
- package/src/services/task/state-machine/actions.ts +409 -0
- package/src/services/task/state-machine/ai-docs/AGENTS.md +495 -0
- package/src/services/task/state-machine/ai-docs/ARCHITECTURE.md +1135 -0
- package/src/services/task/state-machine/constants.ts +150 -0
- package/src/services/task/state-machine/guards.ts +295 -0
- package/src/services/task/state-machine/index.ts +28 -0
- package/src/services/task/state-machine/types.ts +228 -0
- package/src/services/task/state-machine/uiControlsComputer.ts +529 -0
- package/src/services/task/taskDataNormalizer.ts +137 -0
- package/src/services/task/types.ts +641 -95
- package/src/services/task/voice/Voice.ts +1255 -0
- package/src/services/task/voice/WebRTC.ts +187 -0
- package/src/types.ts +88 -5
- package/src/utils/AGENTS.md +276 -0
- package/src/webex.js +2 -0
- package/test/unit/spec/cc.ts +59 -142
- package/test/unit/spec/logger-proxy.ts +70 -0
- package/test/unit/spec/services/ApiAiAssistant.ts +17 -0
- package/test/unit/spec/services/config/index.ts +26 -55
- package/test/unit/spec/services/core/Utils.ts +103 -52
- package/test/unit/spec/services/core/websocket/WebSocketManager.ts +48 -112
- package/test/unit/spec/services/core/websocket/connection-service.ts +5 -4
- package/test/unit/spec/services/task/AutoWrapup.ts +63 -0
- package/test/unit/spec/services/task/Task.ts +416 -0
- package/test/unit/spec/services/task/TaskFactory.ts +62 -0
- package/test/unit/spec/services/task/TaskManager.ts +781 -1735
- package/test/unit/spec/services/task/TaskUtils.ts +125 -0
- package/test/unit/spec/services/task/dialer.ts +112 -198
- package/test/unit/spec/services/task/digital/Digital.ts +105 -0
- package/test/unit/spec/services/task/state-machine/TaskStateMachine.ts +473 -0
- package/test/unit/spec/services/task/state-machine/guards.ts +288 -0
- package/test/unit/spec/services/task/state-machine/types.ts +18 -0
- package/test/unit/spec/services/task/state-machine/uiControlsComputer.ts +147 -0
- package/test/unit/spec/services/task/taskTestUtils.ts +87 -0
- package/test/unit/spec/services/task/voice/Voice.ts +587 -0
- package/test/unit/spec/services/task/voice/WebRTC.ts +242 -0
- package/umd/contact-center.min.js +2 -2
- package/umd/contact-center.min.js.map +1 -1
- package/dist/services/task/index.js +0 -1525
- package/dist/services/task/index.js.map +0 -1
- package/dist/types/services/task/index.d.ts +0 -650
- package/src/services/task/index.ts +0 -1801
- 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
|
+
```
|