From 1d689eae9ee9c29fdcbed4c4ab65e93ea12defa4 Mon Sep 17 00:00:00 2001 From: Rankush Kumar Date: Thu, 11 Dec 2025 17:01:57 +0530 Subject: [PATCH 1/3] docs(test-fixtures): add AI documentation - AGENTS.md and ARCHITECTURE.md --- .../test-fixtures/ai-docs/AGENTS.md | 340 ++++++++++ .../test-fixtures/ai-docs/ARCHITECTURE.md | 640 ++++++++++++++++++ 2 files changed, 980 insertions(+) create mode 100644 packages/contact-center/test-fixtures/ai-docs/AGENTS.md create mode 100644 packages/contact-center/test-fixtures/ai-docs/ARCHITECTURE.md diff --git a/packages/contact-center/test-fixtures/ai-docs/AGENTS.md b/packages/contact-center/test-fixtures/ai-docs/AGENTS.md new file mode 100644 index 000000000..5547f6180 --- /dev/null +++ b/packages/contact-center/test-fixtures/ai-docs/AGENTS.md @@ -0,0 +1,340 @@ +# Test Fixtures - Mock Data for Testing + +## Overview + +Test Fixtures is a utility package that provides comprehensive mock data for testing contact center widgets. It includes mock objects for the Contact Center SDK, tasks, profiles, agents, queues, and address books. These fixtures enable isolated unit testing without requiring actual SDK connections. + +**Package:** `@webex/test-fixtures` + +**Version:** See [package.json](../package.json) + +--- + +## Why and What is This Package Used For? + +### Purpose + +Test Fixtures provides realistic mock data for testing widgets and components. It: +- **Provides mock SDK instance** - IContactCenter mock with jest functions +- **Supplies mock data** - Tasks, profiles, agents, queues, address books +- **Enables isolated testing** - Test widgets without backend dependencies +- **Ensures consistency** - Same mock data across all tests +- **Supports customization** - Easy to extend or override fixtures + +### Key Capabilities + +- **Complete SDK Mock**: Mock `IContactCenter` with all methods +- **Task Fixtures**: Mock tasks with various states and media types +- **Profile Data**: Mock agent profiles with teams, dial plans, idle codes +- **Address Book**: Mock contact entries and search results +- **Queue Data**: Mock queue configurations and statistics +- **Agent Data**: Mock agent lists for buddy agents/transfers +- **Type-Safe**: All fixtures match actual SDK TypeScript types + +--- + +## Examples and Use Cases + +### Getting Started + +#### Basic Widget Test + +```typescript +import { render } from '@testing-library/react'; +import { StationLogin } from '@webex/cc-station-login'; +import { mockCC, mockProfile } from '@webex/test-fixtures'; +import store from '@webex/cc-store'; + +// Mock the store +jest.mock('@webex/cc-store', () => ({ + cc: mockCC, + teams: mockProfile.teams, + loginOptions: mockProfile.loginVoiceOptions, + logger: mockCC.LoggerProxy, + isAgentLoggedIn: false, +})); + +test('renders station login', () => { + const { getByText } = render(); + expect(getByText('Login')).toBeInTheDocument(); +}); +``` + +#### Using Mock Task + +```typescript +import { render } from '@testing-library/react'; +import { CallControl } from '@webex/cc-task'; +import { mockTask } from '@webex/test-fixtures'; + +test('renders call control for active task', () => { + const { getByRole } = render( + + ); + + expect(getByRole('button', { name: /hold/i })).toBeInTheDocument(); + expect(getByRole('button', { name: /end/i })).toBeInTheDocument(); +}); +``` + +### Common Use Cases + +#### 1. Mocking Contact Center SDK + +```typescript +import { mockCC } from '@webex/test-fixtures'; + +// Use in tests +test('calls SDK stationLogin method', async () => { + const loginSpy = jest.spyOn(mockCC, 'stationLogin') + .mockResolvedValue({ success: true }); + + await mockCC.stationLogin({ + teamId: 'team1', + loginOption: 'BROWSER', + dialNumber: '' + }); + + expect(loginSpy).toHaveBeenCalledWith({ + teamId: 'team1', + loginOption: 'BROWSER', + dialNumber: '' + }); +}); +``` + +#### 2. Customizing Mock Profile + +```typescript +import { mockProfile } from '@webex/test-fixtures'; + +test('handles agent with custom idle codes', () => { + // Customize fixture + const customProfile = { + ...mockProfile, + idleCodes: [ + { id: 'break', name: 'Break', isSystem: true, isDefault: false }, + { id: 'lunch', name: 'Lunch', isSystem: false, isDefault: false }, + { id: 'meeting', name: 'Meeting', isSystem: false, isDefault: true }, + ] + }; + + // Use in test + store.setIdleCodes(customProfile.idleCodes); + // ... test logic +}); +``` + +#### 3. Testing Task Operations + +```typescript +import { mockTask } from '@webex/test-fixtures'; + +test('can hold and resume task', async () => { + // Task has pre-configured jest mocks + await mockTask.hold(); + expect(mockTask.hold).toHaveBeenCalled(); + + await mockTask.resume(); + expect(mockTask.resume).toHaveBeenCalled(); +}); + +test('can end task with wrapup', async () => { + const wrapupSpy = jest.spyOn(mockTask, 'wrapup') + .mockResolvedValue({ success: true }); + + await mockTask.wrapup(); + expect(wrapupSpy).toHaveBeenCalled(); +}); +``` + +#### 4. Testing with Mock Agents + +```typescript +import { mockAgents } from '@webex/test-fixtures'; + +test('displays buddy agents for transfer', () => { + const { getByText } = render( + + ); + + expect(getByText('Agent1')).toBeInTheDocument(); + expect(getByText('Agent2')).toBeInTheDocument(); +}); +``` + +#### 5. Testing Queue Selection + +```typescript +import { mockQueueDetails } from '@webex/test-fixtures'; + +test('allows selecting transfer queue', () => { + const { getByRole } = render( + + ); + + const queue1 = getByRole('option', { name: /Queue1/i }); + expect(queue1).toBeInTheDocument(); +}); +``` + +#### 6. Custom Address Book Mock + +```typescript +import { makeMockAddressBook } from '@webex/test-fixtures'; + +test('searches address book entries', async () => { + const mockGetEntries = jest.fn().mockResolvedValue({ + data: [ + { id: 'c1', name: 'John', number: '123' }, + { id: 'c2', name: 'Jane', number: '456' }, + ], + meta: { page: 0, pageSize: 25, totalPages: 1 } + }); + + const addressBook = makeMockAddressBook(mockGetEntries); + + const result = await addressBook.getEntries({ search: 'John' }); + + expect(mockGetEntries).toHaveBeenCalledWith({ search: 'John' }); + expect(result.data).toHaveLength(2); +}); +``` + +### Integration Patterns + +#### Complete Widget Test Setup + +```typescript +import { render } from '@testing-library/react'; +import { UserState } from '@webex/cc-user-state'; +import { mockCC, mockProfile } from '@webex/test-fixtures'; +import store from '@webex/cc-store'; + +// Mock store module +jest.mock('@webex/cc-store', () => ({ + cc: mockCC, + idleCodes: mockProfile.idleCodes, + agentId: mockProfile.agentId, + currentState: 'Available', + lastStateChangeTimestamp: Date.now(), + customState: null, + logger: mockCC.LoggerProxy, + setCurrentState: jest.fn(), + setLastStateChangeTimestamp: jest.fn(), + setLastIdleCodeChangeTimestamp: jest.fn(), +})); + +test('user state widget', () => { + const onStateChange = jest.fn(); + + render(); + + // Test interactions + // ... +}); +``` + +#### Snapshot Testing + +```typescript +import { render } from '@testing-library/react'; +import { TaskList } from '@webex/cc-task'; +import { mockTask } from '@webex/test-fixtures'; + +test('task list matches snapshot', () => { + const { container } = render( + + ); + + expect(container.firstChild).toMatchSnapshot(); +}); +``` + +--- + +## Dependencies + +**Note:** For exact versions, see [package.json](../package.json) + +### Runtime Dependencies + +| Package | Purpose | +|---------|---------| +| `@webex/cc-store` | Store types and interfaces | +| `typescript` | TypeScript support | + +### Development Dependencies + +Key development tools (see [package.json](../package.json) for versions): +- TypeScript +- Webpack (bundling) +- Babel (transpilation) +- ESLint (linting) + +**Note:** This package has no peer dependencies since it's only used in tests. + +--- + +## Available Fixtures + +### Core Fixtures + +| Export | Type | Purpose | +|--------|------|---------| +| `mockCC` | `IContactCenter` | Complete SDK instance mock with jest functions | +| `mockProfile` | `Profile` | Agent profile with teams, idle codes, wrapup codes | +| `mockTask` | `ITask` | Active task with telephony interaction | +| `mockQueueDetails` | `Array` | Queue configurations | +| `mockAgents` | `Array` | Buddy agent list | +| `mockEntryPointsResponse` | `EntryPointListResponse` | Entry points for outdial | +| `mockAddressBookEntriesResponse` | `AddressBookEntriesResponse` | Address book contacts | +| `makeMockAddressBook` | `Function` | Factory for custom address book mock | + +### Importing Fixtures + +```typescript +// Import all fixtures +import { + mockCC, + mockProfile, + mockTask, + mockQueueDetails, + mockAgents, + mockEntryPointsResponse, + mockAddressBookEntriesResponse, + makeMockAddressBook, +} from '@webex/test-fixtures'; + +// Use in tests +test('example', () => { + expect(mockCC.stationLogin).toBeDefined(); + expect(mockProfile.teams).toHaveLength(1); + expect(mockTask.data.interactionId).toBe('interaction123'); +}); +``` + +--- + +## Installation + +```bash +# Install as dev dependency +yarn add -D @webex/test-fixtures + +# Usually already included in widget package devDependencies +``` + +--- + +## Additional Resources + +For detailed fixture structure, customization patterns, and testing strategies, see [architecture.md](./architecture.md). + +--- + +_Last Updated: 2025-11-26_ + diff --git a/packages/contact-center/test-fixtures/ai-docs/ARCHITECTURE.md b/packages/contact-center/test-fixtures/ai-docs/ARCHITECTURE.md new file mode 100644 index 000000000..fd30c3b3b --- /dev/null +++ b/packages/contact-center/test-fixtures/ai-docs/ARCHITECTURE.md @@ -0,0 +1,640 @@ +# Test Fixtures - Architecture + +## Component Overview + +Test Fixtures is a testing utility package that provides realistic mock data for all contact center SDK types and widgets. It follows a fixture pattern where each fixture is a pre-configured, reusable mock object that matches the actual SDK types. + +### Fixture Table + +| Fixture | Type | File | Key Properties | Customizable | +|---------|------|------|----------------|--------------| +| **mockCC** | `IContactCenter` | `src/fixtures.ts` | All SDK methods (stationLogin, stationLogout, setUserState, etc.) | Via jest mocking | +| **mockProfile** | `Profile` | `src/fixtures.ts` | teams, idleCodes, wrapupCodes, agentId, loginVoiceOptions | Via object spread | +| **mockTask** | `ITask` | `src/fixtures.ts` | data (interactionId, origin, destination), hold(), resume(), wrapup(), end() | Via jest mocking | +| **mockQueueDetails** | `QueueDetails[]` | `src/fixtures.ts` | Queue list for transfers | Via array modification | +| **mockAgents** | `Agent[]` | `src/fixtures.ts` | Buddy agent list | Via array modification | +| **mockEntryPointsResponse** | `EntryPointListResponse` | `src/fixtures.ts` | Entry points for outdial | Via object spread | +| **mockAddressBookEntriesResponse** | `AddressBookEntriesResponse` | `src/fixtures.ts` | Address book contacts | Via object spread | +| **makeMockAddressBook** | `Function` | `src/fixtures.ts` | Factory for custom address book | Via function parameter | + +### File Structure + +``` +test-fixtures/ +├── src/ +│ ├── index.ts # Package exports +│ └── fixtures.ts # All fixture definitions +├── dist/ +│ ├── index.js # Build output +│ └── types/ +│ ├── index.d.ts +│ └── fixtures.d.ts +├── package.json +├── tsconfig.json +└── webpack.config.js +``` + +--- + +## Fixture Structure + +### mockCC (IContactCenter) + +Complete SDK mock with all methods as jest functions: + +```typescript +const mockCC: IContactCenter = { + // Core methods + stationLogin: jest.fn(), + stationLogout: jest.fn(), + setUserState: jest.fn(), + + // Task methods + acceptTask: jest.fn(), + endTask: jest.fn(), + holdTask: jest.fn(), + resumeTask: jest.fn(), + wrapupTask: jest.fn(), + + // Transfer/Consult methods + consultTask: jest.fn(), + transferTask: jest.fn(), + cancelConsult: jest.fn(), + completeConsult: jest.fn(), + + // Recording methods + pauseRecording: jest.fn(), + resumeRecording: jest.fn(), + + // Outdial + outdial: jest.fn(), + + // Proxies + AgentProxy: { /* agent-related methods */ }, + DiagnosticsProxy: { /* diagnostics methods */ }, + LoggerProxy: { /* logger methods */ }, + ScreenRecordingProxy: { /* screen recording */ }, + TaskProxy: { /* task subscriptions */ }, + + // Properties + version: '1.0.0', + initialized: true, +}; +``` + +**Usage:** + +```typescript +// Basic usage +test('calls stationLogin', async () => { + await mockCC.stationLogin({ teamId: 'team1', loginOption: 'BROWSER', dialNumber: '' }); + expect(mockCC.stationLogin).toHaveBeenCalled(); +}); + +// Custom mock implementation +test('handles login error', async () => { + mockCC.stationLogin.mockRejectedValue(new Error('Login failed')); + + await expect(mockCC.stationLogin({})).rejects.toThrow('Login failed'); +}); +``` + +--- + +### mockProfile (Profile) + +Complete agent profile with teams, idle codes, wrapup codes: + +```typescript +const mockProfile: Profile = { + agentId: 'agent123', + teams: [ + { + id: 'team1', + name: 'Team One', + isDefault: true, + // ... other team properties + } + ], + idleCodes: [ + { id: 'idle1', name: 'Break', isSystem: true, isDefault: false }, + { id: 'idle2', name: 'Lunch', isSystem: false, isDefault: true }, + ], + wrapupCodes: [ + { id: 'wrap1', name: 'Resolved', isDefault: true }, + { id: 'wrap2', name: 'Escalated', isDefault: false }, + ], + loginVoiceOptions: [ + { label: 'Browser', value: 'BROWSER' }, + { label: 'Extension', value: 'EXTENSION' }, + ], + // ... other profile properties +}; +``` + +**Usage:** + +```typescript +// Use as-is +test('renders teams', () => { + render(); +}); + +// Customize +test('handles single team', () => { + const singleTeamProfile = { + ...mockProfile, + teams: [mockProfile.teams[0]] + }; + + render(); +}); +``` + +--- + +### mockTask (ITask) + +Active task with telephony interaction: + +```typescript +const mockTask: ITask = { + data: { + interactionId: 'interaction123', + taskId: 'task123', + origin: { type: 'INBOUND', number: '+1234567890', name: 'John Doe' }, + destination: { type: 'AGENT', number: '+0987654321' }, + status: 'CONNECTED', + mediaType: 'telephony', + queueId: 'queue1', + channelType: 'telephony', + createdTime: Date.now(), + // ... other task properties + }, + + // Methods (jest mocks) + hold: jest.fn(), + resume: jest.fn(), + wrapup: jest.fn(), + end: jest.fn(), + transfer: jest.fn(), + consult: jest.fn(), + cancelConsult: jest.fn(), + completeConsult: jest.fn(), + pauseRecording: jest.fn(), + resumeRecording: jest.fn(), +}; +``` + +**Usage:** + +```typescript +// Use task methods +test('can hold task', async () => { + mockTask.hold.mockResolvedValue({ success: true }); + + await mockTask.hold(); + expect(mockTask.hold).toHaveBeenCalled(); +}); + +// Customize task data +test('handles inbound call', () => { + const inboundTask = { + ...mockTask, + data: { + ...mockTask.data, + origin: { type: 'INBOUND', number: '+1111111111', name: 'Jane Smith' } + } + }; + + render(); +}); +``` + +--- + +### mockQueueDetails (QueueDetails[]) + +List of queue configurations: + +```typescript +const mockQueueDetails: QueueDetails[] = [ + { + id: 'queue1', + name: 'Queue1', + statistics: { + agentsAvailable: 5, + tasksWaiting: 2, + // ... other stats + }, + // ... other queue properties + }, + { + id: 'queue2', + name: 'Queue2', + statistics: { /* ... */ }, + }, +]; +``` + +--- + +### mockAgents (Agent[]) + +List of buddy agents: + +```typescript +const mockAgents: Agent[] = [ + { + id: 'agent1', + name: 'Agent One', + state: 'Available', + skills: ['Support', 'Sales'], + // ... other agent properties + }, + { + id: 'agent2', + name: 'Agent Two', + state: 'Idle', + skills: ['Technical'], + }, +]; +``` + +--- + +### makeMockAddressBook (Factory Function) + +Factory function to create custom address book mocks: + +```typescript +const makeMockAddressBook = ( + mockGetEntries: jest.Mock = jest.fn() +) => ({ + getEntries: mockGetEntries, + // ... other address book methods +}); +``` + +**Usage:** + +```typescript +test('searches address book', async () => { + const mockGetEntries = jest.fn().mockResolvedValue({ + data: [ + { id: 'c1', name: 'Contact1', number: '123' }, + ], + meta: { page: 0, pageSize: 25, totalPages: 1 } + }); + + const addressBook = makeMockAddressBook(mockGetEntries); + + const result = await addressBook.getEntries({ search: 'Contact1' }); + + expect(mockGetEntries).toHaveBeenCalledWith({ search: 'Contact1' }); + expect(result.data).toHaveLength(1); +}); +``` + +--- + +## Testing Patterns + +### Unit Testing Widgets + +```mermaid +graph TB + subgraph "Test Setup" + Fixtures[Import Fixtures] + Mock[Mock Store/SDK] + end + + subgraph "Test Execution" + Render[Render Component] + Interact[User Interactions] + Assert[Assertions] + end + + subgraph "Test Fixtures" + MockCC[mockCC] + MockProfile[mockProfile] + MockTask[mockTask] + end + + Fixtures --> MockCC + Fixtures --> MockProfile + Fixtures --> MockTask + + MockCC --> Mock + MockProfile --> Mock + MockTask --> Mock + + Mock --> Render + Render --> Interact + Interact --> Assert + + style Fixtures fill:#e1f5ff + style Mock fill:#ffe1e1 + style Assert fill:#e1ffe1 +``` + +### Store Mocking Pattern + +```typescript +// Mock store with fixtures +jest.mock('@webex/cc-store', () => { + const { mockCC, mockProfile } = require('@webex/test-fixtures'); + + return { + __esModule: true, + default: { + cc: mockCC, + teams: mockProfile.teams, + idleCodes: mockProfile.idleCodes, + logger: mockCC.LoggerProxy, + isAgentLoggedIn: false, + // Mock methods + setTeams: jest.fn(), + setIdleCodes: jest.fn(), + setIsAgentLoggedIn: jest.fn(), + } + }; +}); +``` + +### Customization Pattern + +```typescript +// Base fixture +import { mockTask } from '@webex/test-fixtures'; + +// Customize for specific test +const consultingTask = { + ...mockTask, + data: { + ...mockTask.data, + status: 'CONSULTING', + consultedAgentId: 'agent2' + } +}; + +// Use customized fixture +test('handles consulting state', () => { + render(); + expect(screen.getByText('Consulting...')).toBeInTheDocument(); +}); +``` + +--- + +## Fixture Coverage + +### SDK Coverage + +| SDK Feature | Mock Provided | Customizable | +|-------------|---------------|--------------| +| Station Login/Logout | ✅ `mockCC.stationLogin`, `mockCC.stationLogout` | ✅ Via jest mocking | +| User State | ✅ `mockCC.setUserState` | ✅ Via jest mocking | +| Task Accept/End | ✅ `mockCC.acceptTask`, `mockCC.endTask` | ✅ Via jest mocking | +| Task Hold/Resume | ✅ `mockTask.hold`, `mockTask.resume` | ✅ Via jest mocking | +| Transfer/Consult | ✅ `mockCC.transferTask`, `mockCC.consultTask` | ✅ Via jest mocking | +| Recording | ✅ `mockCC.pauseRecording`, `mockCC.resumeRecording` | ✅ Via jest mocking | +| Outdial | ✅ `mockCC.outdial`, `mockEntryPointsResponse` | ✅ Via jest mocking | +| Address Book | ✅ `makeMockAddressBook` | ✅ Via factory parameter | +| Agent Profile | ✅ `mockProfile` | ✅ Via object spread | +| Queues | ✅ `mockQueueDetails` | ✅ Via array modification | +| Agents | ✅ `mockAgents` | ✅ Via array modification | + +--- + +## Troubleshooting Guide + +### Common Issues + +#### 1. Type Errors with Fixtures + +**Symptoms:** +- TypeScript errors when using fixtures +- Type mismatch with actual SDK types + +**Possible Causes:** +- SDK types updated but fixtures not +- Missing required properties + +**Solutions:** + +```typescript +// Verify fixture type matches SDK type +import type { IContactCenter, Profile } from '@webex/contact-center'; +import { mockCC, mockProfile } from '@webex/test-fixtures'; + +// Type assertion if needed +const cc: IContactCenter = mockCC as IContactCenter; +const profile: Profile = mockProfile as Profile; + +// Add missing properties +const extendedProfile = { + ...mockProfile, + newProperty: 'value' // Add new required property +}; +``` + +#### 2. Jest Mock Not Working + +**Symptoms:** +- Mock functions not being called +- Assertions failing + +**Possible Causes:** +- Mock not reset between tests +- Wrong jest mock method + +**Solutions:** + +```typescript +// Reset mocks in beforeEach +beforeEach(() => { + jest.clearAllMocks(); + // or + mockCC.stationLogin.mockClear(); +}); + +// Use correct jest mock methods +mockCC.stationLogin.mockResolvedValue({ success: true }); // For promises +mockCC.stationLogin.mockReturnValue({ success: true }); // For sync +mockCC.stationLogin.mockImplementation(async () => ({ success: true })); +``` + +#### 3. Store Mock Not Working in Tests + +**Symptoms:** +- Widget uses actual store instead of mock +- Mock store data not used + +**Possible Causes:** +- Mock not hoisted before imports +- Store imported before mock + +**Solutions:** + +```typescript +// Place mock BEFORE imports +jest.mock('@webex/cc-store', () => { + const { mockCC, mockProfile } = require('@webex/test-fixtures'); + return { __esModule: true, default: { cc: mockCC, /* ... */ } }; +}); + +// Now import widget +import { StationLogin } from '@webex/cc-station-login'; + +// Or use jest.doMock for dynamic mocking +jest.doMock('@webex/cc-store', () => ({ /* mock */ })); +``` + +#### 4. Fixture Data Not Realistic + +**Symptoms:** +- Tests pass but widget fails in production +- Edge cases not covered + +**Possible Causes:** +- Fixture data too simplified +- Missing edge case scenarios + +**Solutions:** + +```typescript +// Create realistic fixtures +const realisticTask = { + ...mockTask, + data: { + ...mockTask.data, + // Add realistic data + createdTime: Date.now() - 60000, // 1 minute ago + queueTime: 30000, // 30 seconds in queue + origin: { + type: 'INBOUND', + number: '+12025551234', // Real format + name: 'John Smith' + }, + } +}; + +// Create edge case fixtures +const longWaitTask = { + ...mockTask, + data: { + ...mockTask.data, + queueTime: 600000, // 10 minutes (edge case) + } +}; +``` + +#### 5. Fixture Mutations Affect Other Tests + +**Symptoms:** +- Tests pass in isolation but fail together +- Flaky tests + +**Possible Causes:** +- Fixture objects mutated during tests +- Shared fixture reference + +**Solutions:** + +```typescript +// Create fresh copy for each test +import { mockTask } from '@webex/test-fixtures'; + +beforeEach(() => { + // Deep clone fixture + const freshTask = JSON.parse(JSON.stringify(mockTask)); + + // Or use object spread (shallow) + const freshTask = { ...mockTask, data: { ...mockTask.data } }; +}); + +// Or create fixture factory +const createMockTask = () => ({ + data: { /* ... */ }, + hold: jest.fn(), + // ... other properties +}); + +test('test 1', () => { + const task = createMockTask(); // Fresh instance +}); +``` + +--- + +## Best Practices + +### 1. Reset Mocks Between Tests + +```typescript +beforeEach(() => { + jest.clearAllMocks(); +}); +``` + +### 2. Use Factory Functions for Complex Scenarios + +```typescript +const createCustomTask = (overrides = {}) => ({ + ...mockTask, + data: { ...mockTask.data, ...overrides } +}); + +test('handles escalated task', () => { + const task = createCustomTask({ escalated: true }); + // ... test logic +}); +``` + +### 3. Create Reusable Test Utilities + +```typescript +// test-utils.ts +export const setupMockStore = (overrides = {}) => { + const mockStore = { + cc: mockCC, + teams: mockProfile.teams, + ...overrides + }; + + jest.mock('@webex/cc-store', () => ({ default: mockStore })); + + return mockStore; +}; +``` + +### 4. Document Custom Fixtures + +```typescript +/** + * Mock task in consulting state with second agent + * Use this for testing consult/transfer scenarios + */ +const consultingTask = { + ...mockTask, + data: { + ...mockTask.data, + status: 'CONSULTING', + consultedAgentId: 'agent2' + } +}; +``` + +--- + +## Related Documentation + +- [Agent Documentation](./agent.md) - Usage examples and fixtures +- [Testing Patterns](../../../../ai-docs/patterns/testing-patterns.md) - Testing strategies +- [CC Store Documentation](../../store/ai-docs/agent.md) - Store mocking patterns + +--- + +_Last Updated: 2025-11-26_ + From d870afd64b9518355516ec7c61c6507b8e1a0780 Mon Sep 17 00:00:00 2001 From: Shreyas Sharma Date: Tue, 10 Feb 2026 20:33:06 +0530 Subject: [PATCH 2/3] docs(fixtures): review --- .../test-fixtures/ai-docs/AGENTS.md | 33 +++++++--- .../test-fixtures/ai-docs/ARCHITECTURE.md | 66 ++++++++++--------- 2 files changed, 59 insertions(+), 40 deletions(-) diff --git a/packages/contact-center/test-fixtures/ai-docs/AGENTS.md b/packages/contact-center/test-fixtures/ai-docs/AGENTS.md index 5547f6180..faa42c825 100644 --- a/packages/contact-center/test-fixtures/ai-docs/AGENTS.md +++ b/packages/contact-center/test-fixtures/ai-docs/AGENTS.md @@ -23,7 +23,7 @@ Test Fixtures provides realistic mock data for testing widgets and components. I ### Key Capabilities -- **Complete SDK Mock**: Mock `IContactCenter` with all methods +- **SDK Mock**: Mock SDK methods to be used in testing - **Task Fixtures**: Mock tasks with various states and media types - **Profile Data**: Mock agent profiles with teams, dial plans, idle codes - **Address Book**: Mock contact entries and search results @@ -132,19 +132,26 @@ import { mockTask } from '@webex/test-fixtures'; test('can hold and resume task', async () => { // Task has pre-configured jest mocks - await mockTask.hold(); - expect(mockTask.hold).toHaveBeenCalled(); - await mockTask.resume(); - expect(mockTask.resume).toHaveBeenCalled(); + const holdSpy = jest.spyOn(mockTask,'hold') + const resumeSpy = jest.spyOn(mockTask,'resume') + + await mockTask.hold() + await mockTask.resume() + await mockTask.hold() + + expect(holdSpy).toHaveBeenCalledTimes(2) + expect(resumeSpy).toHaveBeenCalledTimes(1) }); test('can end task with wrapup', async () => { + const mockData = { success: true } const wrapupSpy = jest.spyOn(mockTask, 'wrapup') - .mockResolvedValue({ success: true }); + .mockResolvedValue(mockData); - await mockTask.wrapup(); + const res = await mockTask.wrapup(); expect(wrapupSpy).toHaveBeenCalled(); + expect(res).toEqual(mockData) }); ``` @@ -217,7 +224,7 @@ jest.mock('@webex/cc-store', () => ({ idleCodes: mockProfile.idleCodes, agentId: mockProfile.agentId, currentState: 'Available', - lastStateChangeTimestamp: Date.now(), + lastStateChangeTimestamp: 'mock-date', customState: null, logger: mockCC.LoggerProxy, setCurrentState: jest.fn(), @@ -242,8 +249,8 @@ import { render } from '@testing-library/react'; import { TaskList } from '@webex/cc-task'; import { mockTask } from '@webex/test-fixtures'; -test('task list matches snapshot', () => { - const { container } = render( +test('task list matches snapshot', async() => { + const { container } = await render( { |---------|---------| | `@webex/cc-store` | Store types and interfaces | | `typescript` | TypeScript support | +| `@webex/contact-center` | Types import from SDK | ### Development Dependencies @@ -293,6 +301,11 @@ Key development tools (see [package.json](../package.json) for versions): | `mockEntryPointsResponse` | `EntryPointListResponse` | Entry points for outdial | | `mockAddressBookEntriesResponse` | `AddressBookEntriesResponse` | Address book contacts | | `makeMockAddressBook` | `Function` | Factory for custom address book mock | +| `mockIncomingTaskData` | `Record` | Incoming task data variants for incoming task tests | +| `mockTaskData` | `Record>` | Task list item data variants for task list tests | +| `mockOutdialCallProps` | `OutdialCallComponentProps` | Outdial call component props mock with jest functions | +| `mockAniEntries` | `Array` | Outdial ANI entries list | +| `mockCCWithAni` | `IContactCenter` | CC mock with outdial ANI configured | ### Importing Fixtures diff --git a/packages/contact-center/test-fixtures/ai-docs/ARCHITECTURE.md b/packages/contact-center/test-fixtures/ai-docs/ARCHITECTURE.md index fd30c3b3b..3255fa38f 100644 --- a/packages/contact-center/test-fixtures/ai-docs/ARCHITECTURE.md +++ b/packages/contact-center/test-fixtures/ai-docs/ARCHITECTURE.md @@ -16,22 +16,38 @@ Test Fixtures is a testing utility package that provides realistic mock data for | **mockEntryPointsResponse** | `EntryPointListResponse` | `src/fixtures.ts` | Entry points for outdial | Via object spread | | **mockAddressBookEntriesResponse** | `AddressBookEntriesResponse` | `src/fixtures.ts` | Address book contacts | Via object spread | | **makeMockAddressBook** | `Function` | `src/fixtures.ts` | Factory for custom address book | Via function parameter | +| **mockIncomingTaskData** | `object` | `src/incomingTaskFixtures.ts` | webRTC, extension, social, chat (ani, customerName, mediaType, acceptText, etc.) | Via object key access | +| **mockTaskData** | `object` | `src/taskListFixtures.ts` | active, incoming, action, selection (task list UI data by scenario) | Via object key access | +| **mockOutdialCallProps** | `object` | `src/components/task/outdialCallFixtures.ts` | mockCC + startOutdial, getOutdialANIEntries | Via jest mocking | +| **mockAniEntries** | `object[]` | `src/components/task/outdialCallFixtures.ts` | ANI entries (organizationId, id, name, number) | Via array modification | +| **mockCCWithAni** | `IContactCenter` | `src/components/task/outdialCallFixtures.ts` | mockCC + agentConfig.outdialANIId, getOutdialAniEntries | Via object spread / jest | ### File Structure ``` test-fixtures/ ├── src/ -│ ├── index.ts # Package exports -│ └── fixtures.ts # All fixture definitions +│ ├── index.ts # Package exports (fixtures, incomingTaskFixtures, taskListFixtures, outdialCallFixtures) +│ ├── fixtures.ts # Core SDK fixtures (mockCC, mockProfile, mockTask, etc.) +│ ├── incomingTaskFixtures.ts # Incoming task UI data (mockIncomingTaskData) +│ ├── taskListFixtures.ts # Task list UI data by scenario (mockTaskData) +│ └── components/ +│ └── task/ +│ └── outdialCallFixtures.ts # Outdial call props and ANI fixtures (mockOutdialCallProps, mockAniEntries, mockCCWithAni) ├── dist/ -│ ├── index.js # Build output +│ ├── index.js # Build output │ └── types/ │ ├── index.d.ts -│ └── fixtures.d.ts +│ ├── fixtures.d.ts +│ ├── incomingTaskFixtures.d.ts +│ ├── taskListFixtures.d.ts +│ └── components/ +│ └── task/ +│ └── outdialCallFixtures.d.ts ├── package.json ├── tsconfig.json └── webpack.config.js +└── babel.config.js ``` --- @@ -50,15 +66,15 @@ const mockCC: IContactCenter = { setUserState: jest.fn(), // Task methods - acceptTask: jest.fn(), - endTask: jest.fn(), - holdTask: jest.fn(), - resumeTask: jest.fn(), - wrapupTask: jest.fn(), + accept: jest.fn(), + end: jest.fn(), + hold: jest.fn(), + resume: jest.fn(), + wrapup: jest.fn(), // Transfer/Consult methods - consultTask: jest.fn(), - transferTask: jest.fn(), + consult: jest.fn(), + transfer: jest.fn(), cancelConsult: jest.fn(), completeConsult: jest.fn(), @@ -85,9 +101,11 @@ const mockCC: IContactCenter = { **Usage:** ```typescript -// Basic usage +// Basic usage — teamId from mockProfile.teams (shape: { teamId, teamName }), loginOption from loginVoiceOptions (string[]) test('calls stationLogin', async () => { - await mockCC.stationLogin({ teamId: 'team1', loginOption: 'BROWSER', dialNumber: '' }); + const [team] = mockProfile.teams; + const loginOption = mockProfile.loginVoiceOptions[0]; + await mockCC.stationLogin({ teamId: team.teamId, loginOption, dialNumber: '' }); expect(mockCC.stationLogin).toHaveBeenCalled(); }); @@ -107,27 +125,15 @@ Complete agent profile with teams, idle codes, wrapup codes: ```typescript const mockProfile: Profile = { - agentId: 'agent123', - teams: [ - { - id: 'team1', - name: 'Team One', - isDefault: true, - // ... other team properties - } - ], + agentId: 'agent1', + teams: [{ teamId: 'team1', teamName: 'Team 1' }], idleCodes: [ - { id: 'idle1', name: 'Break', isSystem: true, isDefault: false }, - { id: 'idle2', name: 'Lunch', isSystem: false, isDefault: true }, + { id: 'code1', name: 'Code 1', isSystem: false, isDefault: false }, ], wrapupCodes: [ - { id: 'wrap1', name: 'Resolved', isDefault: true }, - { id: 'wrap2', name: 'Escalated', isDefault: false }, - ], - loginVoiceOptions: [ - { label: 'Browser', value: 'BROWSER' }, - { label: 'Extension', value: 'EXTENSION' }, + { id: 'wrap1', name: 'Wrap Code 1', isSystem: false, isDefault: false }, ], + loginVoiceOptions: ['BROWSER'], // ... other profile properties }; ``` From bcfaa5e7661c3251e04a344470db3d2ec8497d37 Mon Sep 17 00:00:00 2001 From: Shreyas Sharma Date: Thu, 12 Feb 2026 11:47:41 +0530 Subject: [PATCH 3/3] docs(fixtures): update from test to it in docs --- .../test-fixtures/ai-docs/AGENTS.md | 30 ++++++++++--------- .../test-fixtures/ai-docs/ARCHITECTURE.md | 20 ++++++------- 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/packages/contact-center/test-fixtures/ai-docs/AGENTS.md b/packages/contact-center/test-fixtures/ai-docs/AGENTS.md index faa42c825..061156ce0 100644 --- a/packages/contact-center/test-fixtures/ai-docs/AGENTS.md +++ b/packages/contact-center/test-fixtures/ai-docs/AGENTS.md @@ -54,9 +54,11 @@ jest.mock('@webex/cc-store', () => ({ isAgentLoggedIn: false, })); -test('renders station login', () => { - const { getByText } = render(); - expect(getByText('Login')).toBeInTheDocument(); +describe('station login', () => { + it('renders station login', () => { + const { getByText } = render(); + expect(getByText('Login')).toBeInTheDocument(); + }) }); ``` @@ -67,7 +69,7 @@ import { render } from '@testing-library/react'; import { CallControl } from '@webex/cc-task'; import { mockTask } from '@webex/test-fixtures'; -test('renders call control for active task', () => { +it('renders call control for active task', () => { const { getByRole } = render( ); @@ -85,7 +87,7 @@ test('renders call control for active task', () => { import { mockCC } from '@webex/test-fixtures'; // Use in tests -test('calls SDK stationLogin method', async () => { +it('calls SDK stationLogin method', async () => { const loginSpy = jest.spyOn(mockCC, 'stationLogin') .mockResolvedValue({ success: true }); @@ -108,7 +110,7 @@ test('calls SDK stationLogin method', async () => { ```typescript import { mockProfile } from '@webex/test-fixtures'; -test('handles agent with custom idle codes', () => { +it('handles agent with custom idle codes', () => { // Customize fixture const customProfile = { ...mockProfile, @@ -130,7 +132,7 @@ test('handles agent with custom idle codes', () => { ```typescript import { mockTask } from '@webex/test-fixtures'; -test('can hold and resume task', async () => { +it('can hold and resume task', async () => { // Task has pre-configured jest mocks const holdSpy = jest.spyOn(mockTask,'hold') @@ -144,7 +146,7 @@ test('can hold and resume task', async () => { expect(resumeSpy).toHaveBeenCalledTimes(1) }); -test('can end task with wrapup', async () => { +it('can end task with wrapup', async () => { const mockData = { success: true } const wrapupSpy = jest.spyOn(mockTask, 'wrapup') .mockResolvedValue(mockData); @@ -160,7 +162,7 @@ test('can end task with wrapup', async () => { ```typescript import { mockAgents } from '@webex/test-fixtures'; -test('displays buddy agents for transfer', () => { +it('displays buddy agents for transfer', () => { const { getByText } = render( ); @@ -175,7 +177,7 @@ test('displays buddy agents for transfer', () => { ```typescript import { mockQueueDetails } from '@webex/test-fixtures'; -test('allows selecting transfer queue', () => { +it('allows selecting transfer queue', () => { const { getByRole } = render( ); @@ -190,7 +192,7 @@ test('allows selecting transfer queue', () => { ```typescript import { makeMockAddressBook } from '@webex/test-fixtures'; -test('searches address book entries', async () => { +it('searches address book entries', async () => { const mockGetEntries = jest.fn().mockResolvedValue({ data: [ { id: 'c1', name: 'John', number: '123' }, @@ -232,7 +234,7 @@ jest.mock('@webex/cc-store', () => ({ setLastIdleCodeChangeTimestamp: jest.fn(), })); -test('user state widget', () => { +it('user state widget', () => { const onStateChange = jest.fn(); render(); @@ -249,7 +251,7 @@ import { render } from '@testing-library/react'; import { TaskList } from '@webex/cc-task'; import { mockTask } from '@webex/test-fixtures'; -test('task list matches snapshot', async() => { +it('task list matches snapshot', async() => { const { container } = await render( { +it('example', () => { expect(mockCC.stationLogin).toBeDefined(); expect(mockProfile.teams).toHaveLength(1); expect(mockTask.data.interactionId).toBe('interaction123'); diff --git a/packages/contact-center/test-fixtures/ai-docs/ARCHITECTURE.md b/packages/contact-center/test-fixtures/ai-docs/ARCHITECTURE.md index 3255fa38f..079ea6fe9 100644 --- a/packages/contact-center/test-fixtures/ai-docs/ARCHITECTURE.md +++ b/packages/contact-center/test-fixtures/ai-docs/ARCHITECTURE.md @@ -102,7 +102,7 @@ const mockCC: IContactCenter = { ```typescript // Basic usage — teamId from mockProfile.teams (shape: { teamId, teamName }), loginOption from loginVoiceOptions (string[]) -test('calls stationLogin', async () => { +it('calls stationLogin', async () => { const [team] = mockProfile.teams; const loginOption = mockProfile.loginVoiceOptions[0]; await mockCC.stationLogin({ teamId: team.teamId, loginOption, dialNumber: '' }); @@ -110,7 +110,7 @@ test('calls stationLogin', async () => { }); // Custom mock implementation -test('handles login error', async () => { +it('handles login error', async () => { mockCC.stationLogin.mockRejectedValue(new Error('Login failed')); await expect(mockCC.stationLogin({})).rejects.toThrow('Login failed'); @@ -142,12 +142,12 @@ const mockProfile: Profile = { ```typescript // Use as-is -test('renders teams', () => { +it('renders teams', () => { render(); }); // Customize -test('handles single team', () => { +it('handles single team', () => { const singleTeamProfile = { ...mockProfile, teams: [mockProfile.teams[0]] @@ -196,7 +196,7 @@ const mockTask: ITask = { ```typescript // Use task methods -test('can hold task', async () => { +it('can hold task', async () => { mockTask.hold.mockResolvedValue({ success: true }); await mockTask.hold(); @@ -204,7 +204,7 @@ test('can hold task', async () => { }); // Customize task data -test('handles inbound call', () => { +it('handles inbound call', () => { const inboundTask = { ...mockTask, data: { @@ -285,7 +285,7 @@ const makeMockAddressBook = ( **Usage:** ```typescript -test('searches address book', async () => { +it('searches address book', async () => { const mockGetEntries = jest.fn().mockResolvedValue({ data: [ { id: 'c1', name: 'Contact1', number: '123' }, @@ -385,7 +385,7 @@ const consultingTask = { }; // Use customized fixture -test('handles consulting state', () => { +it('handles consulting state', () => { render(); expect(screen.getByText('Consulting...')).toBeInTheDocument(); }); @@ -567,7 +567,7 @@ const createMockTask = () => ({ // ... other properties }); -test('test 1', () => { +it('test 1', () => { const task = createMockTask(); // Fresh instance }); ``` @@ -592,7 +592,7 @@ const createCustomTask = (overrides = {}) => ({ data: { ...mockTask.data, ...overrides } }); -test('handles escalated task', () => { +it('handles escalated task', () => { const task = createCustomTask({ escalated: true }); // ... test logic });