diff --git a/.gitignore b/.gitignore index 0b38ee592..d63696183 100644 --- a/.gitignore +++ b/.gitignore @@ -45,6 +45,7 @@ reports/ !package.json !package-lock.json !tsconfig.json +!tsconfig.test.json node_modules/ .yarn/* !.yarn/releases diff --git a/packages/contact-center/cc-components/package.json b/packages/contact-center/cc-components/package.json index 19410bd54..c4f0f5702 100644 --- a/packages/contact-center/cc-components/package.json +++ b/packages/contact-center/cc-components/package.json @@ -3,6 +3,7 @@ "description": "Webex Contact Center UI Components Library for your custom contact center solutions", "version": "1.28.0-ccwidgets.91", "main": "dist/index.js", + "types": "dist/types/index.d.ts", "publishConfig": { "access": "public" }, @@ -20,7 +21,7 @@ "build": "yarn run -T tsc", "build:src": "yarn run clean:dist && webpack", "build:watch": "webpack --watch", - "test:unit": "jest --coverage", + "test:unit": "tsc --project tsconfig.test.json && jest --coverage", "test:styles": "eslint" }, "dependencies": { @@ -39,6 +40,7 @@ "@testing-library/react": "16.0.1", "@types/jest": "29.5.14", "@types/react-test-renderer": "18", + "@webex/test-fixtures": "workspace:*", "babel-loader": "9.2.1", "eslint": "^9.20.1", "eslint-config-prettier": "^10.0.1", @@ -65,4 +67,4 @@ "react": ">=18.3.1", "react-dom": ">=18.3.1" } -} \ No newline at end of file +} diff --git a/packages/contact-center/cc-components/src/components/StationLogin/constants.ts b/packages/contact-center/cc-components/src/components/StationLogin/constants.ts index 80979167b..9f4a4cb9b 100644 --- a/packages/contact-center/cc-components/src/components/StationLogin/constants.ts +++ b/packages/contact-center/cc-components/src/components/StationLogin/constants.ts @@ -27,16 +27,3 @@ export const StationLoginLabels = { export const SignInErrors = { ['DUPLICATE_LOCATION']: 'This extension is already in use.', }; - -// Utility consts -const DIALNUMBER: string = 'AGENT_DN'; -const EXTENSION: string = 'EXTENSION'; -const DESKTOP: string = 'BROWSER'; - -const LoginOptions: {[key: string]: string} = { - [DIALNUMBER]: 'Dial Number', - [EXTENSION]: 'Extension', - [DESKTOP]: 'Desktop', -}; - -export {DIALNUMBER, EXTENSION, DESKTOP, LoginOptions}; diff --git a/packages/contact-center/cc-components/src/components/StationLogin/station-login.tsx b/packages/contact-center/cc-components/src/components/StationLogin/station-login.tsx index e0ff392fe..37633544c 100644 --- a/packages/contact-center/cc-components/src/components/StationLogin/station-login.tsx +++ b/packages/contact-center/cc-components/src/components/StationLogin/station-login.tsx @@ -1,7 +1,8 @@ import React, {useEffect, useState} from 'react'; import {StationLoginComponentProps} from './station-login.types'; import './station-login.style.scss'; -import {DESKTOP, LoginOptions, SignInErrors, StationLoginLabels} from './constants'; +import {SignInErrors, StationLoginLabels} from './constants'; +import {LoginOptions, DESKTOP} from '@webex/cc-store'; import {Button, Icon, Select, Option, Text, Tooltip, Input} from '@momentum-design/components/dist/react'; import { ccCancelButtonClicked, diff --git a/packages/contact-center/cc-components/src/components/StationLogin/station-login.types.ts b/packages/contact-center/cc-components/src/components/StationLogin/station-login.types.ts index f2907d7d4..09772521d 100644 --- a/packages/contact-center/cc-components/src/components/StationLogin/station-login.types.ts +++ b/packages/contact-center/cc-components/src/components/StationLogin/station-login.types.ts @@ -1,5 +1,6 @@ -import {IContactCenter, StationLoginSuccess, StationLogoutSuccess, Team} from '@webex/plugin-cc'; -import {ILogger} from '@webex/cc-store'; +import {StationLoginSuccessResponse, LogoutSuccess} from '@webex/plugin-cc'; +import {IContactCenter, ILogger} from '@webex/cc-store'; +import {Team} from '@webex/plugin-cc/dist/types/types'; /** * Interface representing the properties for the Station Login component. */ @@ -34,7 +35,7 @@ export interface IStationLoginProps { /** * Response data received on agent login success */ - loginSuccess?: StationLoginSuccess; + loginSuccess?: StationLoginSuccessResponse; /** * Error received on agent login failure @@ -44,7 +45,7 @@ export interface IStationLoginProps { /** * Response data received on agent login success */ - logoutSuccess?: StationLogoutSuccess; + logoutSuccess?: LogoutSuccess; /** * Flag to indicate if the agent is logged in @@ -199,11 +200,6 @@ export interface IStationLoginProps { * The selected team ID for login */ selectedTeamId: string; - - /** - * The selected option for login type (e.g., 'Extension', 'Agent DN', etc.) - */ - selectedOption: string; } export interface LoginOptionsState { @@ -245,5 +241,4 @@ export type StationLoginComponentProps = Pick< | 'setDialNumberValue' | 'setSelectedTeamId' | 'selectedTeamId' - | 'selectedOption' >; diff --git a/packages/contact-center/cc-components/src/components/StationLogin/station-login.utils.tsx b/packages/contact-center/cc-components/src/components/StationLogin/station-login.utils.tsx index 132b25284..58d740f24 100644 --- a/packages/contact-center/cc-components/src/components/StationLogin/station-login.utils.tsx +++ b/packages/contact-center/cc-components/src/components/StationLogin/station-login.utils.tsx @@ -1,5 +1,6 @@ import {useRef} from 'react'; -import {DESKTOP, DIALNUMBER, LoginOptions, StationLoginLabels} from './constants'; +import {StationLoginLabels} from './constants'; +import {LoginOptions, DESKTOP, DIALNUMBER} from '@webex/cc-store'; const handleModals = ( modalRef, diff --git a/packages/contact-center/cc-components/src/components/UserState/user-state.tsx b/packages/contact-center/cc-components/src/components/UserState/user-state.tsx index 83ce3d9b3..6375a9825 100644 --- a/packages/contact-center/cc-components/src/components/UserState/user-state.tsx +++ b/packages/contact-center/cc-components/src/components/UserState/user-state.tsx @@ -1,6 +1,6 @@ import React, {useMemo} from 'react'; -import {IUserState, AgentUserState} from './user-state.types'; +import {AgentUserState, UserStateComponentsProps} from './user-state.types'; import {formatTime} from '../../utils'; import './user-state.scss'; @@ -18,7 +18,7 @@ import { buildDropdownItems, } from './user-state.utils'; -const UserStateComponent: React.FunctionComponent = (props) => { +const UserStateComponent: React.FunctionComponent = (props) => { const { idleCodes, setAgentStatus, @@ -51,6 +51,7 @@ const UserStateComponent: React.FunctionComponent = (props) => { > {(item) => { const isRonaOrEngaged = [AgentUserState.RONA, AgentUserState.Engaged].includes( + //@ts-expect-error To be fixed in SDK - https://jira-eng-sjc12.cisco.com/jira/browse/CAI-6762 idleCodes.find((code) => code.id === currentState)?.name || '' ); const shouldHighlight = currentState === item.id || (isRonaOrEngaged && item.id === previousSelectableState); diff --git a/packages/contact-center/cc-components/src/components/UserState/user-state.types.ts b/packages/contact-center/cc-components/src/components/UserState/user-state.types.ts index baffa0ae9..f22496e3d 100644 --- a/packages/contact-center/cc-components/src/components/UserState/user-state.types.ts +++ b/packages/contact-center/cc-components/src/components/UserState/user-state.types.ts @@ -1,4 +1,4 @@ -import {IdleCode, ICustomState, ILogger} from '@webex/cc-store'; +import {IdleCode, ICustomState, ILogger, IContactCenter} from '@webex/cc-store'; /** * Interface representing the state of a user. @@ -24,11 +24,6 @@ export interface IUserState { */ isSettingAgentStatus: boolean; - /** - * The error message to display - */ - errorMessage: string; - /** * The duration of the current user state */ @@ -55,18 +50,49 @@ export interface IUserState { currentTheme: string; /** - * Function to handle state change - * @param state The state to change to - * @returns void + * Logger instance */ - onStateChange: (state: string) => void; + logger: ILogger; /** - * Logger instance + * Callback function to be called when the state changes. + * @param state The new state. */ - logger: ILogger; + onStateChange?: (arg: IdleCode | ICustomState) => void; + + /** + * The agent ID. + */ + agentId: string; + + /** + * CC SDK Instance. + */ + cc: IContactCenter; + + /** + * The timestamp of the last state change. + */ + lastStateChangeTimestamp?: number; + + /** + * The timestamp of the last idle code change. + */ + lastIdleCodeChangeTimestamp?: number; } +export type UserStateComponentsProps = Pick< + IUserState, + | 'idleCodes' + | 'setAgentStatus' + | 'isSettingAgentStatus' + | 'elapsedTime' + | 'lastIdleStateChangeElapsedTime' + | 'currentState' + | 'customState' + | 'logger' +>; + export enum AgentUserState { Available = 'Available', RONA = 'RONA', diff --git a/packages/contact-center/cc-components/src/components/UserState/user-state.utils.ts b/packages/contact-center/cc-components/src/components/UserState/user-state.utils.ts index 8fa1f4d19..0b54b0443 100644 --- a/packages/contact-center/cc-components/src/components/UserState/user-state.utils.ts +++ b/packages/contact-center/cc-components/src/components/UserState/user-state.utils.ts @@ -91,7 +91,7 @@ export const getPreviousSelectableState = (idleCodes: IdleCode[]): string => { * Gets the selected key for the dropdown */ export const getSelectedKey = (customState: ICustomState, currentState: string, idleCodes: IdleCode[]): string => { - if (customState) { + if (customState && 'developerName' in customState) { return `custom-${customState.developerName}`; } @@ -121,7 +121,7 @@ export const buildDropdownItems = ( name: code.name, })); - if (customState) { + if (customState && 'developerName' in customState) { items.push({ id: `custom-${customState.developerName}`, name: customState.name, diff --git a/packages/contact-center/cc-components/src/components/task/CallControl/call-control.tsx b/packages/contact-center/cc-components/src/components/task/CallControl/call-control.tsx index b8afefe27..641763620 100644 --- a/packages/contact-center/cc-components/src/components/task/CallControl/call-control.tsx +++ b/packages/contact-center/cc-components/src/components/task/CallControl/call-control.tsx @@ -1,6 +1,6 @@ import React, {useEffect, useState} from 'react'; -import {CallControlComponentProps, DestinationType, CallControlMenuType} from '../task.types'; +import {CallControlComponentProps, CallControlMenuType} from '../task.types'; import './call-control.styles.scss'; import {PopoverNext, TooltipNext, Text, ButtonCircle} from '@momentum-ui/react-collaboration'; import {Icon, Button, Select, Option} from '@momentum-design/components/dist/react'; @@ -8,6 +8,7 @@ import ConsultTransferPopoverComponent from './CallControlCustom/consult-transfe import AutoWrapupTimer from '../AutoWrapupTimer/AutoWrapupTimer'; import type {MEDIA_CHANNEL as MediaChannelType} from '../task.types'; import {getMediaTypeInfo} from '../../../utils'; +import {DestinationType} from '@webex/cc-store'; import { RESUME_CALL, HOLD_CALL, diff --git a/packages/contact-center/cc-components/src/components/task/CallControlCAD/call-control-cad.tsx b/packages/contact-center/cc-components/src/components/task/CallControlCAD/call-control-cad.tsx index fdacfd384..0e4f83354 100644 --- a/packages/contact-center/cc-components/src/components/task/CallControlCAD/call-control-cad.tsx +++ b/packages/contact-center/cc-components/src/components/task/CallControlCAD/call-control-cad.tsx @@ -57,7 +57,11 @@ const CallControlCADComponent: React.FC = (props) => const mediaChannel = currentTask.data.interaction.mediaType as MediaChannelType; const isSocial = mediaChannel === MediaChannelType.SOCIAL; const isTelephony = mediaChannel === MediaChannelType.TELEPHONY; + + //@ts-expect-error To be fixed in SDK - https://jira-eng-sjc12.cisco.com/jira/browse/CAI-6762 const customerName = currentTask?.data?.interaction?.callAssociatedDetails?.customerName; + + //@ts-expect-error To be fixed in SDK - https://jira-eng-sjc12.cisco.com/jira/browse/CAI-6762 const ani = currentTask?.data?.interaction?.callAssociatedDetails?.ani; // Create unique IDs for tooltips @@ -187,12 +191,24 @@ const CallControlCADComponent: React.FC = (props) =>
{QUEUE}{' '} - {currentTask?.data?.interaction?.callAssociatedDetails?.virtualTeamName || NO_TEAM_NAME} + + { + //@ts-expect-error To be fixed in SDK - https://jira-eng-sjc12.cisco.com/jira/browse/CAI-6762 + + currentTask?.data?.interaction?.callAssociatedDetails?.virtualTeamName || NO_TEAM_NAME + } + {renderPhoneNumber()} {RONA}{' '} - {currentTask?.data?.interaction?.callAssociatedDetails?.ronaTimeout || NO_RONA} + + { + //@ts-expect-error To be fixed in SDK - https://jira-eng-sjc12.cisco.com/jira/browse/CAI-6762 + + currentTask?.data?.interaction?.callAssociatedDetails?.ronaTimeout || NO_RONA + } +
diff --git a/packages/contact-center/cc-components/src/components/task/IncomingTask/incoming-task.tsx b/packages/contact-center/cc-components/src/components/task/IncomingTask/incoming-task.tsx index c71e36f60..87fe6a0f7 100644 --- a/packages/contact-center/cc-components/src/components/task/IncomingTask/incoming-task.tsx +++ b/packages/contact-center/cc-components/src/components/task/IncomingTask/incoming-task.tsx @@ -8,6 +8,7 @@ const IncomingTaskComponent: React.FunctionComponent return <>; // hidden component } + //@ts-expect-error To be fixed in SDK - https://jira-eng-sjc12.cisco.com/jira/browse/CAI-6762 const callAssociationDetails = incomingTask?.data?.interaction?.callAssociatedDetails; const ani = callAssociationDetails?.ani; const customerName = callAssociationDetails?.customerName; diff --git a/packages/contact-center/cc-components/src/components/task/TaskList/task-list.tsx b/packages/contact-center/cc-components/src/components/task/TaskList/task-list.tsx index da77d11a2..37c6e675e 100644 --- a/packages/contact-center/cc-components/src/components/task/TaskList/task-list.tsx +++ b/packages/contact-center/cc-components/src/components/task/TaskList/task-list.tsx @@ -12,6 +12,7 @@ const TaskListComponent: React.FunctionComponent = (prop return (
    {Object.values(taskList)?.map((task, index) => { + //@ts-expect-error To be fixed in SDK - https://jira-eng-sjc12.cisco.com/jira/browse/CAI-6762IT const callAssociationDetails = task?.data?.interaction?.callAssociatedDetails; const ani = callAssociationDetails?.ani; const customerName = callAssociationDetails?.customerName; diff --git a/packages/contact-center/cc-components/src/components/task/task.types.ts b/packages/contact-center/cc-components/src/components/task/task.types.ts index fc11255c8..ccf23f055 100644 --- a/packages/contact-center/cc-components/src/components/task/task.types.ts +++ b/packages/contact-center/cc-components/src/components/task/task.types.ts @@ -2,7 +2,7 @@ import { ILogger, ITask, IContactCenter, - WrapupCodes, + IWrapupCode, BuddyDetails, DestinationType, ContactServiceQueue, @@ -173,7 +173,7 @@ export interface ControlProps { * Array of wrap-up codes. * TODO: Expose this type from SDK. */ - wrapupCodes: WrapupCodes[]; + wrapupCodes: IWrapupCode[]; /** * Indicates if wrap-up is required. @@ -503,8 +503,6 @@ export interface CallControlConsultComponentsProps { */ export type CallControlMenuType = 'Consult' | 'Transfer'; -export {DestinationType}; - export const MEDIA_CHANNEL = { EMAIL: 'email', CHAT: 'chat', diff --git a/packages/contact-center/cc-components/src/wc.ts b/packages/contact-center/cc-components/src/wc.ts index 575f66a07..eb786815c 100644 --- a/packages/contact-center/cc-components/src/wc.ts +++ b/packages/contact-center/cc-components/src/wc.ts @@ -13,13 +13,10 @@ const WebUserState = r2wc(UserStateComponent, { idleCodes: 'json', setAgentStatus: 'function', isSettingAgentStatus: 'boolean', - errorMessage: 'string', elapsedTime: 'number', lastIdleStateChangeElapsedTime: 'number', currentState: 'string', customState: 'json', - currentTheme: 'string', - onStateChange: 'function', logger: 'function', }, }); diff --git a/packages/contact-center/cc-components/tests/components/StationLogin/station-login.snapshot.tsx b/packages/contact-center/cc-components/tests/components/StationLogin/station-login.snapshot.tsx index 3733a8035..353fdb1fd 100644 --- a/packages/contact-center/cc-components/tests/components/StationLogin/station-login.snapshot.tsx +++ b/packages/contact-center/cc-components/tests/components/StationLogin/station-login.snapshot.tsx @@ -11,8 +11,43 @@ describe('Station Login Component', () => { info: jest.fn(), warn: jest.fn(), error: jest.fn(), + debug: jest.fn(), + trace: jest.fn(), }; + const mockTeams = [ + { + id: 'team123', + name: 'Team A', + teamType: 'AGENT_BASED', + teamStatus: 'ACTIVE', + active: true, + siteId: 'site123', + siteName: 'Test Site A', + multiMediaProfileId: 'profile123', + userIds: ['user1', 'user2'], + rankQueuesForTeam: false, + queueRankings: [], + dbId: 'db123', + desktopLayoutId: 'layout123', + }, + { + id: 'team456', + name: 'Team B', + teamType: 'AGENT_BASED', + teamStatus: 'ACTIVE', + active: true, + siteId: 'site456', + siteName: 'Test Site B', + multiMediaProfileId: 'profile456', + userIds: ['user3', 'user4'], + rankQueuesForTeam: true, + queueRankings: ['queue1', 'queue2'], + dbId: 'db456', + desktopLayoutId: 'layout456', + }, + ]; + const props: StationLoginComponentProps = { loginOptions: ['AGENT_DN', 'EXTENSION', 'BROWSER'], login: jest.fn(), @@ -20,10 +55,7 @@ describe('Station Login Component', () => { loginSuccess: undefined, loginFailure: undefined, logoutSuccess: undefined, - teams: [ - {id: 'team123', name: 'Team A'}, - {id: 'team456', name: 'Team B'}, - ], + teams: mockTeams, setDeviceType: jest.fn(), setDialNumber: jest.fn(), setTeam: jest.fn(), @@ -41,7 +73,6 @@ describe('Station Login Component', () => { dialNumberValue: '', setDialNumberValue: jest.fn(), setSelectedDeviceType: jest.fn(), - selectedOption: 'EXTENSION', setCurrentLoginOptions: jest.fn(), currentLoginOptions: { deviceType: 'EXTENSION', diff --git a/packages/contact-center/cc-components/tests/components/StationLogin/station-login.tsx b/packages/contact-center/cc-components/tests/components/StationLogin/station-login.tsx index d1ff0c7f5..e99137e75 100644 --- a/packages/contact-center/cc-components/tests/components/StationLogin/station-login.tsx +++ b/packages/contact-center/cc-components/tests/components/StationLogin/station-login.tsx @@ -1,8 +1,9 @@ import React from 'react'; import '@testing-library/jest-dom'; import {fireEvent, render} from '@testing-library/react'; +import {LoginOptions} from '@webex/cc-store'; import StationLoginComponent from '../../../src/components/StationLogin/station-login'; -import {StationLoginLabels, LoginOptions, SignInErrors} from '../../../src/components/StationLogin/constants'; +import {StationLoginLabels, SignInErrors} from '../../../src/components/StationLogin/constants'; import {StationLoginComponentProps} from '../../../src/components/StationLogin/station-login.types'; import * as stationLoginUtils from '../../../src/components/StationLogin/station-login.utils'; @@ -12,8 +13,42 @@ describe('Station Login Component', () => { info: jest.fn(), warn: jest.fn(), error: jest.fn(), + trace: jest.fn(), }; + const mockTeams = [ + { + id: 'team123', + name: 'Team A', + teamType: 'AGENT_BASED', + teamStatus: 'ACTIVE', + active: true, + siteId: 'site123', + siteName: 'Test Site A', + multiMediaProfileId: 'profile123', + userIds: ['user1', 'user2'], + rankQueuesForTeam: false, + queueRankings: [], + dbId: 'db123', + desktopLayoutId: 'layout123', + }, + { + id: 'team456', + name: 'Team B', + teamType: 'AGENT_BASED', + teamStatus: 'ACTIVE', + active: true, + siteId: 'site456', + siteName: 'Test Site B', + multiMediaProfileId: 'profile456', + userIds: ['user3', 'user4'], + rankQueuesForTeam: true, + queueRankings: ['queue1', 'queue2'], + dbId: 'db456', + desktopLayoutId: 'layout456', + }, + ]; + const props: StationLoginComponentProps = { loginOptions: ['AGENT_DN', 'EXTENSION', 'BROWSER'], login: jest.fn(), @@ -21,10 +56,7 @@ describe('Station Login Component', () => { loginSuccess: undefined, loginFailure: undefined, logoutSuccess: undefined, - teams: [ - {id: 'team123', name: 'Team A'}, - {id: 'team456', name: 'Team B'}, - ], + teams: mockTeams, setDeviceType: jest.fn(), setDialNumber: jest.fn(), setTeam: jest.fn(), @@ -42,7 +74,6 @@ describe('Station Login Component', () => { dialNumberValue: '', setDialNumberValue: jest.fn(), setSelectedDeviceType: jest.fn(), - selectedOption: 'EXTENSION', setCurrentLoginOptions: jest.fn(), currentLoginOptions: { deviceType: 'EXTENSION', diff --git a/packages/contact-center/cc-components/tests/components/StationLogin/station-login.utils.tsx b/packages/contact-center/cc-components/tests/components/StationLogin/station-login.utils.tsx index 895503b45..53c546a1a 100644 --- a/packages/contact-center/cc-components/tests/components/StationLogin/station-login.utils.tsx +++ b/packages/contact-center/cc-components/tests/components/StationLogin/station-login.utils.tsx @@ -1,5 +1,6 @@ import React from 'react'; import '@testing-library/jest-dom'; +import {DESKTOP, DIALNUMBER, EXTENSION, LoginOptions} from '@webex/cc-store'; import { handleModals, continueClicked, @@ -15,13 +16,7 @@ import { handleOnCCSignOut, handleCCSignoutKeyDown, } from '../../../src/components/StationLogin/station-login.utils'; -import { - DESKTOP, - DIALNUMBER, - EXTENSION, - LoginOptions, - StationLoginLabels, -} from '../../../src/components/StationLogin/constants'; +import {StationLoginLabels} from '../../../src/components/StationLogin/constants'; const loggerMock = { info: jest.fn(), diff --git a/packages/contact-center/cc-components/tests/components/UserState/user-state.snapshot.tsx b/packages/contact-center/cc-components/tests/components/UserState/user-state.snapshot.tsx index 24e805010..82434fde3 100644 --- a/packages/contact-center/cc-components/tests/components/UserState/user-state.snapshot.tsx +++ b/packages/contact-center/cc-components/tests/components/UserState/user-state.snapshot.tsx @@ -5,6 +5,7 @@ import {IUserState} from '../../../src/components/UserState/user-state.types'; import UserStateComponent from '../../../src/components/UserState/user-state'; import * as userStateUtils from '../../../src/components/UserState/user-state.utils'; +import {mockCC} from '@webex/test-fixtures'; const mockUIDProps = (container) => { container @@ -24,22 +25,25 @@ describe('UserState Component Snapshots', () => { info: jest.fn(), warn: jest.fn(), error: jest.fn(), + debug: jest.fn(), + trace: jest.fn(), }; const mockSetAgentStatus = jest.fn(); const mockOnStateChange = jest.fn(); const defaultProps: IUserState = { + cc: mockCC, + agentId: 'agent123', idleCodes: [ - {id: '0', name: 'Available', isSystem: true}, - {id: '1', name: 'Break', isSystem: false}, - {id: '2', name: 'Training', isSystem: false}, - {id: '3', name: 'RONA', isSystem: true}, - {id: '4', name: 'ENGAGED', isSystem: true}, + {id: '0', name: 'Available', isSystem: true, isDefault: true}, + {id: '1', name: 'Break', isSystem: false, isDefault: false}, + {id: '2', name: 'Training', isSystem: false, isDefault: false}, + {id: '3', name: 'RONA', isSystem: true, isDefault: false}, + {id: '4', name: 'ENGAGED', isSystem: true, isDefault: false}, ], setAgentStatus: mockSetAgentStatus, isSettingAgentStatus: false, - errorMessage: '', elapsedTime: 3661, // 1 hour, 1 minute, 1 second lastIdleStateChangeElapsedTime: 1800, // 30 minutes currentState: '1', @@ -207,8 +211,8 @@ describe('UserState Component Snapshots', () => { ...defaultProps, currentState: 'nonexistent-id', // This ID doesn't exist in idleCodes idleCodes: [ - {id: '0', name: 'Available', isSystem: true}, - {id: '1', name: 'Break', isSystem: false}, + {id: '0', name: 'Available', isSystem: true, isDefault: true}, + {id: '1', name: 'Break', isSystem: false, isDefault: false}, ], }; diff --git a/packages/contact-center/cc-components/tests/components/UserState/user-state.tsx b/packages/contact-center/cc-components/tests/components/UserState/user-state.tsx index 15f601bc7..3a0a09604 100644 --- a/packages/contact-center/cc-components/tests/components/UserState/user-state.tsx +++ b/packages/contact-center/cc-components/tests/components/UserState/user-state.tsx @@ -4,6 +4,7 @@ import '@testing-library/jest-dom'; import UserStateComponent from '../../../src/components/UserState/user-state'; import {IUserState} from '../../../src/components/UserState/user-state.types'; import * as userStateUtils from '../../../src/components/UserState/user-state.utils'; +import {mockCC} from '@webex/test-fixtures'; describe('UserStateComponent', () => { const mockLogger = { @@ -11,22 +12,25 @@ describe('UserStateComponent', () => { info: jest.fn(), warn: jest.fn(), error: jest.fn(), + debug: jest.fn(), + trace: jest.fn(), }; const mockSetAgentStatus = jest.fn(); const mockOnStateChange = jest.fn(); const defaultProps: IUserState = { + cc: mockCC, + agentId: 'agent123', idleCodes: [ - {id: '0', name: 'Available', isSystem: true}, - {id: '1', name: 'Break', isSystem: false}, - {id: '2', name: 'Training', isSystem: false}, - {id: '3', name: 'RONA', isSystem: true}, - {id: '4', name: 'ENGAGED', isSystem: true}, + {id: '0', name: 'Available', isSystem: true, isDefault: false}, + {id: '1', name: 'Break', isSystem: false, isDefault: false}, + {id: '2', name: 'Training', isSystem: false, isDefault: false}, + {id: '3', name: 'RONA', isSystem: true, isDefault: false}, + {id: '4', name: 'ENGAGED', isSystem: true, isDefault: false}, ], setAgentStatus: mockSetAgentStatus, isSettingAgentStatus: false, - errorMessage: '', elapsedTime: 3661, // 1 hour, 1 minute, 1 second lastIdleStateChangeElapsedTime: 1800, // 30 minutes currentState: '1', @@ -294,8 +298,8 @@ describe('UserStateComponent', () => { ...defaultProps, currentState: 'nonexistent-id', // This ID doesn't exist in idleCodes idleCodes: [ - {id: '0', name: 'Available', isSystem: true}, - {id: '1', name: 'Break', isSystem: false}, + {id: '0', name: 'Available', isSystem: true, isDefault: true}, + {id: '1', name: 'Break', isSystem: false, isDefault: false}, ], }; diff --git a/packages/contact-center/cc-components/tests/components/UserState/user-state.utils.tsx b/packages/contact-center/cc-components/tests/components/UserState/user-state.utils.tsx index d92f40c71..ca00b4b7a 100644 --- a/packages/contact-center/cc-components/tests/components/UserState/user-state.utils.tsx +++ b/packages/contact-center/cc-components/tests/components/UserState/user-state.utils.tsx @@ -24,11 +24,11 @@ describe('UserState Utils', () => { }); const mockIdleCodes = [ - {id: '0', name: 'Available', isSystem: true}, - {id: '1', name: 'Break', isSystem: false}, - {id: '2', name: 'Training', isSystem: false}, - {id: '3', name: 'RONA', isSystem: true}, - {id: '4', name: 'ENGAGED', isSystem: true}, + {id: '0', name: 'Available', isSystem: true, isDefault: true}, + {id: '1', name: 'Break', isSystem: false, isDefault: false}, + {id: '2', name: 'Training', isSystem: false, isDefault: false}, + {id: '3', name: 'RONA', isSystem: true, isDefault: false}, + {id: '4', name: 'ENGAGED', isSystem: true, isDefault: false}, ]; describe('getDropdownClass', () => { @@ -206,8 +206,8 @@ describe('UserState Utils', () => { it('should return empty string when no selectable states', () => { const nonSelectableStates = [ - {id: '3', name: 'RONA', isSystem: true}, - {id: '4', name: 'ENGAGED', isSystem: true}, + {id: '3', name: 'RONA', isSystem: true, isDefault: false}, + {id: '4', name: 'ENGAGED', isSystem: true, isDefault: false}, ]; const result = getPreviousSelectableState(nonSelectableStates); @@ -285,8 +285,8 @@ describe('UserState Utils', () => { it('should handle idleCodes with only RONA and ENGAGED', () => { const ronaEngagedOnly = [ - {id: '3', name: 'RONA', isSystem: true}, - {id: '4', name: 'ENGAGED', isSystem: true}, + {id: '3', name: 'RONA', isSystem: true, isDefault: false}, + {id: '4', name: 'ENGAGED', isSystem: true, isDefault: false}, ]; const result = buildDropdownItems(null, ronaEngagedOnly); diff --git a/packages/contact-center/cc-components/tests/components/task/CallControl/call-control-consult.tsx b/packages/contact-center/cc-components/tests/components/task/CallControl/call-control-consult.tsx index 49e0395d2..7a3beb4dc 100644 --- a/packages/contact-center/cc-components/tests/components/task/CallControl/call-control-consult.tsx +++ b/packages/contact-center/cc-components/tests/components/task/CallControl/call-control-consult.tsx @@ -34,6 +34,7 @@ describe.skip('CallControlConsultComponent', () => { endConsultCall: mockEndConsultCall, consultCompleted: true, isAgentBeingConsulted: true, + isEndConsultEnabled: true, logger: loggerMock, }; diff --git a/packages/contact-center/cc-components/tests/components/task/CallControl/call-control.tsx b/packages/contact-center/cc-components/tests/components/task/CallControl/call-control.tsx index e2ba8928e..439d23291 100644 --- a/packages/contact-center/cc-components/tests/components/task/CallControl/call-control.tsx +++ b/packages/contact-center/cc-components/tests/components/task/CallControl/call-control.tsx @@ -3,6 +3,7 @@ import React from 'react'; import {render, screen, fireEvent} from '@testing-library/react'; import '@testing-library/jest-dom'; import CallControlComponent from '../../../../src/components/task/CallControl/call-control'; +import {mockTask} from '@webex/test-fixtures'; jest.mock('@webex/cc-store', () => ({ DestinationType: { @@ -68,54 +69,57 @@ describe.skip('CallControlPresentational', () => { const setIsHeld = jest.fn(); const defaultProps = { - currentTask: { - data: { - interaction: { - mediaResourceId: '1', - media: { - '1': {isHold: false}, - }, - callProcessingDetails: {isPaused: false}, - mediaType: 'telephony', - }, - }, - }, - audioRef: React.createRef(), + currentTask: mockTask, toggleHold: mockToggleHold, toggleRecording: mockToggleRecording, endCall: mockEndCall, wrapupCall: mockWrapupCall, wrapupCodes: mockWrapupCodes, - setIsHeld: setIsHeld, isHeld: false, + setIsHeld: setIsHeld, + isRecording: false, + setIsRecording: jest.fn(), buddyAgents: [], loadBuddyAgents: mockLoadBuddyAgents, - loadQueues: mockLoadQueues, transferCall: mockTransferCall, consultCall: mockConsultCall, - setIsRecording: jest.fn(), - deviceType: 'BROWSER', - featureFlags: { - isEndCallEnabled: true, - isEndConsultEnabled: true, - webRtcEnabled: true, - }, - queues: [], - setConsultAgentId: mockSetConsultAgentId, - setConsultAgentName: mockSetConsultAgentName, - consultAgentId: null, - consultAgentName: null, endConsultCall: jest.fn(), + consultInitiated: false, consultTransfer: jest.fn(), - logger: loggerMock, + consultCompleted: false, + consultAccepted: false, + consultStartTimeStamp: 0, + callControlAudio: null, + consultAgentName: '', + setConsultAgentName: mockSetConsultAgentName, + consultAgentId: '', + setConsultAgentId: mockSetConsultAgentId, + holdTime: 0, + callControlClassName: '', + callControlConsultClassName: '', + startTimestamp: 0, + queues: [], + loadQueues: mockLoadQueues, + isEndConsultEnabled: true, + allowConsultToQueue: true, + lastTargetType: 'agent' as 'agent' | 'queue', + setLastTargetType: jest.fn(), controlVisibility: { + accept: false, + decline: false, + end: true, + muteUnmute: false, holdResume: true, consult: true, transfer: true, - pauseResumeRecording: true, - end: true, + conference: false, wrapup: false, + pauseResumeRecording: true, + endConsult: false, + recordingIndicator: false, }, + logger: loggerMock, + cancelAutoWrapup: jest.fn(), }; beforeEach(() => { diff --git a/packages/contact-center/cc-components/tests/components/task/CallControl/consult-transfer-popover.tsx b/packages/contact-center/cc-components/tests/components/task/CallControl/consult-transfer-popover.tsx index c7d25fa58..5661d89f4 100644 --- a/packages/contact-center/cc-components/tests/components/task/CallControl/consult-transfer-popover.tsx +++ b/packages/contact-center/cc-components/tests/components/task/CallControl/consult-transfer-popover.tsx @@ -4,6 +4,7 @@ import {render, screen, fireEvent} from '@testing-library/react'; import '@testing-library/jest-dom'; import ConsultTransferPopoverComponent from '../../../../src/components/task/CallControl/CallControlCustom/consult-transfer-popover'; import ConsultTransferEmptyState from '../../../../src/components/task/CallControl/CallControlCustom/consult-transfer-empty-state'; +import {ContactServiceQueue} from '@webex/cc-store'; const loggerMock = { log: jest.fn(), @@ -40,15 +41,30 @@ describe.skip('ConsultTransferPopoverComponent', () => { heading: 'Select an Agent', buttonIcon: 'agent-icon', buddyAgents: [ - {agentId: 'agent1', agentName: 'Agent One', dn: '1001'}, - {agentId: 'agent2', agentName: 'Agent Two', dn: '1002'}, + { + agentId: 'agent1', + agentName: 'Agent One', + dn: '1001', + state: 'Available', + teamId: 'team1', + siteId: 'site1', + }, + { + agentId: 'agent2', + agentName: 'Agent Two', + dn: '1002', + state: 'Available', + teamId: 'team1', + siteId: 'site1', + }, ], queues: [ - {id: 'queue1', name: 'Queue One'}, - {id: 'queue2', name: 'Queue Two'}, + {id: 'queue1', name: 'Queue One'} as ContactServiceQueue, + {id: 'queue2', name: 'Queue Two'} as ContactServiceQueue, ], onAgentSelect: mockOnAgentSelect, onQueueSelect: mockOnQueueSelect, + allowConsultToQueue: true, logger: loggerMock, showTabs: true, emptyMessage: 'No agents or queues available', @@ -128,8 +144,8 @@ describe.skip('ConsultTransferPopoverComponent', () => { allowConsultToQueue={true} buddyAgents={[]} // So Queues tab is selected by default if you want queues={[ - {id: 'queue1', name: 'Queue One'}, - {id: 'queue2', name: 'Queue Two'}, + {id: 'queue1', name: 'Queue One'} as ContactServiceQueue, + {id: 'queue2', name: 'Queue Two'} as ContactServiceQueue, ]} /> ); diff --git a/packages/contact-center/cc-components/tests/components/task/IncomingTask/incoming-task.tsx b/packages/contact-center/cc-components/tests/components/task/IncomingTask/incoming-task.tsx index e058fb2d8..ecbd64984 100644 --- a/packages/contact-center/cc-components/tests/components/task/IncomingTask/incoming-task.tsx +++ b/packages/contact-center/cc-components/tests/components/task/IncomingTask/incoming-task.tsx @@ -2,6 +2,7 @@ import React from 'react'; import {render, screen, cleanup} from '@testing-library/react'; import '@testing-library/jest-dom'; import IncomingTaskComponent from '../../../../src/components/task/IncomingTask/incoming-task'; +import {mockTask} from '@webex/test-fixtures'; // This test suite is skipped because we have removed the :broken from the command // line in the package.json scripts to run these tests in pipeline @@ -9,26 +10,21 @@ describe.skip('IncomingTaskComponent', () => { afterEach(cleanup); it('renders incoming call for browser option', () => { - const mockTask = { - data: { - interaction: { - callAssociatedDetails: { - ani: '1234567890', - dn: '987654321', - virtualTeamName: 'Sales Team', - }, - }, - }, - }; - const props = { incomingTask: mockTask, accept: jest.fn(), - decline: jest.fn(), + reject: jest.fn(), isBrowser: true, isAnswered: false, isEnded: false, isMissed: false, + logger: { + log: jest.fn(), + info: jest.fn(), + warn: jest.fn(), + error: jest.fn(), + trace: jest.fn(), + }, }; render(); diff --git a/packages/contact-center/cc-components/tests/components/task/TaskList/task-list.tsx b/packages/contact-center/cc-components/tests/components/task/TaskList/task-list.tsx index 9e9362c70..3b7117e17 100644 --- a/packages/contact-center/cc-components/tests/components/task/TaskList/task-list.tsx +++ b/packages/contact-center/cc-components/tests/components/task/TaskList/task-list.tsx @@ -3,6 +3,7 @@ import {render, screen, cleanup} from '@testing-library/react'; import '@testing-library/jest-dom'; import TaskListComponent from '../../../../src/components/task/TaskList/task-list'; import {TaskListComponentProps} from '../../../../src/components/task/task.types'; +import {mockTask} from '@webex/test-fixtures'; // This test suite is skipped because we have removed the :broken from the command // line in the package.json scripts to run these tests in pipeline @@ -19,11 +20,14 @@ describe.skip('TaskListPresentational Component', () => { }; const props: TaskListComponentProps = { - taskList: [ - { + currentTask: mockTask, + taskList: { + '1': { + ...mockTask, id: '1', data: { interaction: { + //@ts-expect-error To be fixed in SDK - https://jira-eng-sjc12.cisco.com/jira/browse/CAI-6762 callAssociatedDetails: { ani: '1234567890', dn: '9876543210', @@ -32,10 +36,12 @@ describe.skip('TaskListPresentational Component', () => { }, }, }, - { + '2': { + ...mockTask, id: '2', data: { interaction: { + //@ts-expect-error To be fixed in SDK - https://jira-eng-sjc12.cisco.com/jira/browse/CAI-6762 callAssociatedDetails: { ani: '0987654321', dn: '8765432109', @@ -44,7 +50,11 @@ describe.skip('TaskListPresentational Component', () => { }, }, }, - ], + }, + isBrowser: true, + acceptTask: jest.fn(), + declineTask: jest.fn(), + onTaskSelect: jest.fn(), logger: loggerMock, }; diff --git a/packages/contact-center/cc-components/tsconfig.json b/packages/contact-center/cc-components/tsconfig.json index 50b68ce6a..3bcd18911 100644 --- a/packages/contact-center/cc-components/tsconfig.json +++ b/packages/contact-center/cc-components/tsconfig.json @@ -1,7 +1,7 @@ { "extends": "../../../tsconfig.json", "include": [ - "./src" + "./src", ], "compilerOptions": { "outDir": "./dist", diff --git a/packages/contact-center/cc-components/tsconfig.test.json b/packages/contact-center/cc-components/tsconfig.test.json new file mode 100644 index 000000000..40bd84f5a --- /dev/null +++ b/packages/contact-center/cc-components/tsconfig.test.json @@ -0,0 +1,9 @@ +// This config is to do type checking in our files while running tests. +{ + "extends": "./tsconfig.json", + "include": ["./tests"], + "exclude": ["node_modules"], + "compilerOptions": { + "noEmit": true // Don't output any files + } +} \ No newline at end of file diff --git a/packages/contact-center/cc-widgets/package.json b/packages/contact-center/cc-widgets/package.json index cb87130b7..ffb93e7ab 100644 --- a/packages/contact-center/cc-widgets/package.json +++ b/packages/contact-center/cc-widgets/package.json @@ -3,6 +3,7 @@ "description": "Webex Contact Center Widgets", "version": "1.28.0-ccwidgets.91", "main": "dist/index.js", + "types": "dist/types/index.d.ts", "publishConfig": { "access": "public" }, @@ -11,7 +12,10 @@ "package.json" ], "exports": { - ".": "./dist/index.js", + ".": { + "import": "./dist/index.js", + "types": "./dist/types/index.d.ts" + }, "./wc": "./dist/wc.js" }, "scripts": { @@ -86,4 +90,4 @@ "^.+\\.(css|less|scss)$": "babel-jest" } } -} \ No newline at end of file +} diff --git a/packages/contact-center/cc-widgets/src/wc.ts b/packages/contact-center/cc-widgets/src/wc.ts index 23f040660..4dee47a50 100644 --- a/packages/contact-center/cc-widgets/src/wc.ts +++ b/packages/contact-center/cc-widgets/src/wc.ts @@ -37,7 +37,7 @@ const WebCallControl = r2wc(CallControl, { props: { onHoldResume: 'function', onEnd: 'function', - onWrapup: 'function', + onWrapUp: 'function', onRecordingToggle: 'function', }, }); @@ -46,7 +46,7 @@ const WebCallControlCAD = r2wc(CallControlCAD, { props: { onHoldResume: 'function', onEnd: 'function', - onWrapup: 'function', + onWrapUp: 'function', onRecordingToggle: 'function', }, }); diff --git a/packages/contact-center/station-login/package.json b/packages/contact-center/station-login/package.json index 22cb58359..75c23d651 100644 --- a/packages/contact-center/station-login/package.json +++ b/packages/contact-center/station-login/package.json @@ -3,6 +3,7 @@ "description": "Webex Contact Center Widgets: Station Login", "version": "1.28.0-ccwidgets.91", "main": "dist/index.js", + "types": "dist/types/index.d.ts", "publishConfig": { "access": "public" }, @@ -16,7 +17,7 @@ "build": "yarn run -T tsc", "build:src": "yarn run clean:dist && webpack", "build:watch": "webpack --watch", - "test:unit": "jest --coverage", + "test:unit": "tsc --project tsconfig.test.json && jest --coverage", "test:styles": "eslint" }, "dependencies": { @@ -36,6 +37,7 @@ "@types/jest": "29.5.14", "@types/node": "^22.13.13", "@types/react-test-renderer": "18", + "@webex/test-fixtures": "workspace:*", "babel-jest": "29.7.0", "babel-loader": "9.2.1", "eslint": "^9.20.1", @@ -63,4 +65,4 @@ "react": ">=18.3.1", "react-dom": ">=18.3.1" } -} \ No newline at end of file +} diff --git a/packages/contact-center/station-login/package.tgz b/packages/contact-center/station-login/package.tgz new file mode 100644 index 000000000..2f31fed74 Binary files /dev/null and b/packages/contact-center/station-login/package.tgz differ diff --git a/packages/contact-center/station-login/src/helper.ts b/packages/contact-center/station-login/src/helper.ts index 39870bd91..806fe8fcc 100644 --- a/packages/contact-center/station-login/src/helper.ts +++ b/packages/contact-center/station-login/src/helper.ts @@ -1,5 +1,5 @@ import {useEffect, useState} from 'react'; -import {StationLoginSuccess, StationLogoutSuccess, AgentProfileUpdate, LoginOption} from '@webex/plugin-cc'; +import {LogoutSuccess, AgentProfileUpdate, LoginOption, StationLoginSuccessResponse} from '@webex/plugin-cc'; import {UseStationLoginProps} from './station-login/station-login.types'; import store, {CC_EVENTS} from '@webex/cc-store'; // we need to import as we are losing the context of this in store import {LoginOptionsState} from '@webex/cc-components'; @@ -17,9 +17,9 @@ export const useStationLogin = (props: UseStationLoginProps) => { const doStationLogout = props.doStationLogout === undefined || props.doStationLogout === null ? true : props.doStationLogout; const [team, setTeam] = useState(''); - const [loginSuccess, setLoginSuccess] = useState(); + const [loginSuccess, setLoginSuccess] = useState(); const [loginFailure, setLoginFailure] = useState(); - const [logoutSuccess, setLogoutSuccess] = useState(); + const [logoutSuccess, setLogoutSuccess] = useState(); const [dialNumberValue, setDialNumberValue] = useState(dialNumber || ''); const [selectedTeamId, setSelectedTeamId] = useState(teamId || ''); @@ -81,12 +81,6 @@ export const useStationLogin = (props: UseStationLoginProps) => { if (props.onSaveEnd) props.onSaveEnd(false); return; } - logger.log('Saving login options:', { - module: 'widget-station-login#helper.ts', - method: 'saveLoginOptions', - original: originalLoginOptions, - updated: currentLoginOptions, - }); if (props.onSaveStart) props.onSaveStart(); @@ -99,6 +93,11 @@ export const useStationLogin = (props: UseStationLoginProps) => { payload.dialNumber = currentLoginOptions.dialNumber; } + logger.log('Saving login options:', { + module: 'widget-station-login#helper.ts', + method: 'saveLoginOptions', + }); + cc.updateAgentProfile(payload) .then(() => { setOriginalLoginOptions({...currentLoginOptions}); @@ -110,7 +109,7 @@ export const useStationLogin = (props: UseStationLoginProps) => { if (props.onSaveEnd) props.onSaveEnd(true); }) .catch((error: Error) => { - logger.error('Failed to update agent device type', error, { + logger.error('Failed to update agent device type', { module: 'widget-station-login#helper.ts', method: 'saveLoginOptions', }); @@ -189,7 +188,7 @@ export const useStationLogin = (props: UseStationLoginProps) => { const login = () => { cc.stationLogin({teamId: team, loginOption: deviceType, dialNumber}) - .then((res: StationLoginSuccess) => { + .then((res: StationLoginSuccessResponse) => { logger.log('CC-Widgets: useStationLogin login(): stationLogin success', { module: 'widget-station-login#helper.ts', method: 'login', @@ -213,7 +212,7 @@ export const useStationLogin = (props: UseStationLoginProps) => { method: 'logout', }); cc.stationLogout({logoutReason: 'User requested logout'}) - .then((res: StationLogoutSuccess) => { + .then((res: LogoutSuccess) => { logger.log('CC-Widgets: useStationLogin logout(): stationLogout success', { module: 'widget-station-login#helper.ts', method: 'logout', diff --git a/packages/contact-center/station-login/src/station-login/index.tsx b/packages/contact-center/station-login/src/station-login/index.tsx index 98293bef4..7ad23401e 100644 --- a/packages/contact-center/station-login/src/station-login/index.tsx +++ b/packages/contact-center/station-login/src/station-login/index.tsx @@ -2,7 +2,7 @@ import React from 'react'; import store from '@webex/cc-store'; import {observer} from 'mobx-react-lite'; -import {StationLoginComponent} from '@webex/cc-components'; +import {StationLoginComponent, StationLoginComponentProps} from '@webex/cc-components'; import {useStationLogin} from '../helper'; import {StationLoginProps} from './station-login.types'; @@ -38,18 +38,16 @@ const StationLogin: React.FunctionComponent = observer( }); const dialNumberRegex = cc?.agentConfig?.regexUS; - const props = { + const props: StationLoginComponentProps = { ...result, setDeviceType, setDialNumber, teams, loginOptions, deviceType, - dialNumber, dialNumberRegex, isAgentLoggedIn, showMultipleLoginAlert, - teamId, setTeamId, logger, profileMode, diff --git a/packages/contact-center/station-login/src/station-login/station-login.types.ts b/packages/contact-center/station-login/src/station-login/station-login.types.ts index cc872e5bd..582996d5b 100644 --- a/packages/contact-center/station-login/src/station-login/station-login.types.ts +++ b/packages/contact-center/station-login/src/station-login/station-login.types.ts @@ -16,7 +16,10 @@ export type UseStationLoginProps = Pick< | 'doStationLogout' >; -export type StationLoginProps = Pick< - IStationLoginProps, - 'onLogin' | 'onLogout' | 'onCCSignOut' | 'profileMode' | 'onSaveStart' | 'onSaveEnd' | 'teamId' | 'doStationLogout' ->; +export type StationLoginProps = Pick & + Partial< + Pick< + IStationLoginProps, + 'onLogin' | 'onLogout' | 'onCCSignOut' | 'onSaveStart' | 'onSaveEnd' | 'teamId' | 'doStationLogout' + > + >; diff --git a/packages/contact-center/station-login/tests/helper.ts b/packages/contact-center/station-login/tests/helper.ts index 917293569..a67aed0a3 100644 --- a/packages/contact-center/station-login/tests/helper.ts +++ b/packages/contact-center/station-login/tests/helper.ts @@ -1,6 +1,8 @@ import {renderHook, act, waitFor} from '@testing-library/react'; import {useStationLogin} from '../src/helper'; -import * as store from '@webex/cc-store'; +import store, {CC_EVENTS} from '@webex/cc-store'; +import {mockCC} from '@webex/test-fixtures'; +import {LogoutSuccess, StationLoginSuccessResponse} from '@webex/plugin-cc'; jest.mock('@webex/cc-store', () => { let isAgentLoggedIn = false; @@ -32,11 +34,30 @@ jest.mock('@webex/cc-store', () => { // Mock webex instance const ccMock = { + ...mockCC, stationLogin: jest.fn(), stationLogout: jest.fn(), deregister: jest.fn(), on: jest.fn(), off: jest.fn(), + updateAgentProfile: jest.fn(), + LoggerProxy: { + log: jest.fn(), + info: jest.fn(), + warn: jest.fn(), + trace: jest.fn(), + error: jest.fn(), + }, + register: jest.fn(), + taskManager: { + getAllTasks: jest.fn(() => ({})), + }, + getBuddyAgents: jest.fn(), + getQueues: jest.fn(), + agentConfig: { + regexUS: /^\d{10}$/, + agentId: 'mockAgentId', + }, }; // Sample login parameters @@ -52,6 +73,8 @@ const logger = { log: jest.fn(), info: jest.fn(), error: jest.fn(), + warn: jest.fn(), + trace: jest.fn(), }; // Base props for useStationLogin hook @@ -68,6 +91,27 @@ const baseStationLoginProps = { isAgentLoggedIn: false, }; +const successResponse: StationLoginSuccessResponse = { + agentId: '6b310dff-569e-4ac7-b064-70f834ea56d8', + agentSessionId: 'c9c24ace-5170-4a9f-8bc2-2eeeff9d7c11', + auxCodeId: '00b4e8df-f7b0-460f-aacf-f1e635c87d4d', + eventType: 'AgentDesktopMessage', + interactionIds: [], + lastIdleCodeChangeTimestamp: 1731997914706, + lastStateChangeTimestamp: 1731997914706, + orgId: '6ecef209-9a34-4ed1-a07a-7ddd1dbe925a', + profileType: 'BLENDED', + roles: ['agent'], + siteId: 'd64e19c0-53a2-4ae0-ab7e-3ebc778b3dcd', + status: 'LoggedIn', + subStatus: 'Idle', + teamId: 'c789288e-39e3-40c9-8e66-62c6276f73de', + trackingId: 'f40915b9-07ed-4b6c-832d-e7f5e7af3b72', + type: 'AgentStationLoginSuccess', + mmProfile: {chat: 1, email: 1, social: 1, telephony: 1}, + notifsTrackingId: 'f40915b9-07ed-4b6c-832d-e7f5e7af3b72', +}; + describe('useStationLogin Hook', () => { afterEach(() => { jest.clearAllMocks(); @@ -78,31 +122,6 @@ describe('useStationLogin Hook', () => { }); it('should set loginSuccess on successful login and set loginFailure to undefined', async () => { - const successResponse = { - data: { - agentId: '6b310dff-569e-4ac7-b064-70f834ea56d8', - agentSessionId: 'c9c24ace-5170-4a9f-8bc2-2eeeff9d7c11', - auxCodeId: '00b4e8df-f7b0-460f-aacf-f1e635c87d4d', - deviceId: '1001', - deviceType: 'EXTENSION', - dn: '1001', - eventType: 'AgentDesktopMessage', - interactionIds: [], - lastIdleCodeChangeTimestamp: 1731997914706, - lastStateChangeTimestamp: 1731997914706, - orgId: '6ecef209-9a34-4ed1-a07a-7ddd1dbe925a', - profileType: 'BLENDED', - roles: ['agent'], - siteId: 'd64e19c0-53a2-4ae0-ab7e-3ebc778b3dcd', - status: 'LoggedIn', - subStatus: 'Idle', - teamId: 'c789288e-39e3-40c9-8e66-62c6276f73de', - trackingId: 'f40915b9-07ed-4b6c-832d-e7f5e7af3b72', - type: 'AgentStationLoginSuccess', - voiceCount: 1, - }, - }; - ccMock.stationLogin.mockResolvedValue(successResponse); const {result} = renderHook(() => useStationLogin({ @@ -152,15 +171,6 @@ describe('useStationLogin Hook', () => { }); it('should set loginSuccess on successful login without auxCode and last state timestamp', async () => { - const successResponse = { - data: { - agentId: '6b310dff-569e-4ac7-b064-70f834ea56d8', - agentSessionId: 'c9c24ace-5170-4a9f-8bc2-2eeeff9d7c11', - lastStateChangeTimestamp: '1234', - lastIdleCodeChangeTimestamp: '2345', - }, - }; - ccMock.stationLogin.mockResolvedValue(successResponse); const setSetCurrentStateSpy = jest.spyOn(store, 'setCurrentState'); const setSetLastStateChangeTimestampSpy = jest.spyOn(store, 'setLastStateChangeTimestamp'); @@ -182,25 +192,17 @@ describe('useStationLogin Hook', () => { }); await waitFor(async () => { - // @ts-expect-error only for testing purposes - expect(setSetCurrentStateSpy).not.toHaveBeenCalledWith(successResponse.data.auxCodeId); + expect(setSetCurrentStateSpy).not.toHaveBeenCalledWith(successResponse.auxCodeId); expect(setSetLastStateChangeTimestampSpy).not.toHaveBeenCalledWith( - new Date(successResponse.data.lastStateChangeTimestamp) + new Date(successResponse.lastStateChangeTimestamp) ); expect(setSetLastIdleCodeChangeTimestampSpy).not.toHaveBeenCalledWith( - new Date(successResponse.data.lastIdleCodeChangeTimestamp) + new Date(successResponse.lastIdleCodeChangeTimestamp) ); }); }); it('should set loginSuccess on successful login without onLogin callback', async () => { - const successResponse = { - data: { - agentId: '6b310dff-569e-4ac7-b064-70f834ea56d8', - agentSessionId: 'c9c24ace-5170-4a9f-8bc2-2eeeff9d7c11', - }, - }; - ccMock.stationLogin.mockResolvedValue(successResponse); const {result} = renderHook(() => useStationLogin({ @@ -295,7 +297,6 @@ describe('useStationLogin Hook', () => { it('should set loginFailure on failed login', async () => { const errorResponse = new Error('Login failed'); ccMock.stationLogin.mockRejectedValue(errorResponse); - loginCb.mockClear(); const {result} = renderHook(() => useStationLogin(baseStationLoginProps)); @@ -336,21 +337,23 @@ describe('useStationLogin Hook', () => { }); it('should set logoutSuccess on successful logout', async () => { - const successResponse = { - agentId: '6b310dff-569e-4ac7-b064-70f834ea56d8', - agentSessionId: '701ba0dc-2075-4867-a753-226ad8e2197a', - eventTime: 1731998475193, - eventType: 'AgentDesktopMessage', - loggedOutBy: 'SELF', - logoutReason: 'Agent Logged Out', + const successResponse: LogoutSuccess = { + data: { + agentId: '6b310dff-569e-4ac7-b064-70f834ea56d8', + agentSessionId: '701ba0dc-2075-4867-a753-226ad8e2197a', + eventType: 'AgentDesktopMessage', + loggedOutBy: 'SELF', + orgId: '6ecef209-9a34-4ed1-a07a-7ddd1dbe925a', + roles: ['agent'], + status: 'LoggedOut', + subStatus: 'Idle', + trackingId: '77170ae4-fd8d-4bf5-bfaa-5f9d8975265c', + type: 'AgentLogoutSuccess', + }, + type: 'AgentLogoutSuccess', orgId: '6ecef209-9a34-4ed1-a07a-7ddd1dbe925a', - roles: ['agent'], - status: 'LoggedOut', - subStatus: 'Idle', trackingId: '77170ae4-fd8d-4bf5-bfaa-5f9d8975265c', - type: 'AgentLogoutSuccess', }; - ccMock.stationLogout.mockResolvedValue(successResponse); const {result} = renderHook(() => @@ -396,7 +399,6 @@ describe('useStationLogin Hook', () => { ccMock.stationLogout.mockRejectedValue(new Error('Logout failed')); const {result} = renderHook(() => useStationLogin(baseStationLoginProps)); - await act(async () => { await result.current.logout(); }); @@ -410,7 +412,7 @@ describe('useStationLogin Hook', () => { }); it('should not call logout callback if not present', async () => { - ccMock.stationLogout.mockResolvedValue({}); + ccMock.stationLogout.mockResolvedValue({} as LogoutSuccess); const {result} = renderHook(() => useStationLogin({ @@ -499,10 +501,11 @@ describe('useStationLogin Hook', () => { jest.spyOn(store, 'setCCCallback').mockImplementation((event, cb) => { ccMock.on(event, cb); }); + const onSpy = jest.spyOn(ccMock, 'on'); renderHook(() => useStationLogin(baseStationLoginProps)); - expect(ccMock.on).toHaveBeenCalledWith(store.CC_EVENTS.AGENT_STATION_LOGIN_SUCCESS, expect.any(Function)); + expect(ccMock.on).toHaveBeenCalledWith(CC_EVENTS.AGENT_STATION_LOGIN_SUCCESS, expect.any(Function)); const mockPayload = { deviceType: 'EXTENSION', @@ -510,17 +513,17 @@ describe('useStationLogin Hook', () => { }; act(() => { - ccMock.on.mock.calls[0][1](mockPayload); + onSpy.mock.calls[0][1](mockPayload); }); await waitFor(() => { expect(loginCb).toHaveBeenCalled(); }); - expect(ccMock.on).toHaveBeenCalledWith(store.CC_EVENTS.AGENT_LOGOUT_SUCCESS, expect.any(Function)); + expect(ccMock.on).toHaveBeenCalledWith(CC_EVENTS.AGENT_LOGOUT_SUCCESS, expect.any(Function)); act(() => { - ccMock.on.mock.calls[1][1](); + onSpy.mock.calls[1][1]({}); }); await waitFor(() => { @@ -532,6 +535,7 @@ describe('useStationLogin Hook', () => { jest.spyOn(store, 'setCCCallback').mockImplementation((event, cb) => { ccMock.on(event, cb); }); + const onSpy = jest.spyOn(ccMock, 'on'); renderHook(() => useStationLogin({ @@ -540,7 +544,7 @@ describe('useStationLogin Hook', () => { }) ); - expect(ccMock.on).toHaveBeenCalledWith(store.CC_EVENTS.AGENT_STATION_LOGIN_SUCCESS, expect.any(Function)); + expect(ccMock.on).toHaveBeenCalledWith(CC_EVENTS.AGENT_STATION_LOGIN_SUCCESS, expect.any(Function)); const mockPayload = { deviceType: 'EXTENSION', @@ -548,7 +552,7 @@ describe('useStationLogin Hook', () => { }; act(() => { - ccMock.on.mock.calls[0][1](mockPayload); + onSpy.mock.calls[0][1](mockPayload); }); await waitFor(() => { @@ -557,7 +561,7 @@ describe('useStationLogin Hook', () => { }); it('should not save if isLoginOptionsChanged is false', () => { - const cc = {updateAgentProfile: jest.fn()}; + const cc = {...ccMock, updateAgentProfile: jest.fn()}; const {result} = renderHook(() => useStationLogin({ ...baseStationLoginProps, @@ -585,7 +589,7 @@ describe('useStationLogin Hook', () => { }); it('should call updateAgentProfile and update originalLoginOptions on save when changed', async () => { - const cc = {updateAgentProfile: jest.fn().mockResolvedValue({})}; + const cc = {...ccMock, updateAgentProfile: jest.fn().mockResolvedValue({})}; const {result} = renderHook(() => useStationLogin({ ...baseStationLoginProps, @@ -601,6 +605,7 @@ describe('useStationLogin Hook', () => { result.current.setCurrentLoginOptions({ deviceType: 'DIALNUMBER', dialNumber: '2002', + teamId: 'team123', }); }); @@ -612,7 +617,7 @@ describe('useStationLogin Hook', () => { expect(cc.updateAgentProfile).toHaveBeenCalledWith({ loginOption: 'DIALNUMBER', - teamId: undefined, + teamId: 'team123', dialNumber: '2002', }); expect(logger.log).toHaveBeenCalledWith( @@ -620,8 +625,6 @@ describe('useStationLogin Hook', () => { expect.objectContaining({ module: 'widget-station-login#helper.ts', method: 'saveLoginOptions', - original: expect.any(Object), - updated: expect.any(Object), }) ); expect(result.current.saveError).toBe(''); @@ -631,7 +634,7 @@ describe('useStationLogin Hook', () => { }); it('should handle updateAgentProfile errors', async () => { - const cc = {updateAgentProfile: jest.fn().mockRejectedValue(new Error('fail'))}; + const cc = {...ccMock, updateAgentProfile: jest.fn().mockRejectedValue(new Error('fail'))}; const {result} = renderHook(() => useStationLogin({ ...baseStationLoginProps, @@ -647,6 +650,7 @@ describe('useStationLogin Hook', () => { result.current.setCurrentLoginOptions({ deviceType: 'DIALNUMBER', dialNumber: '2002', + teamId: 'team123', }); }); @@ -657,7 +661,6 @@ describe('useStationLogin Hook', () => { expect(result.current.saveError).toBe('fail'); expect(logger.error).toHaveBeenCalledWith( 'Failed to update agent device type', - expect.any(Error), expect.objectContaining({ module: 'widget-station-login#helper.ts', method: 'saveLoginOptions', @@ -666,7 +669,7 @@ describe('useStationLogin Hook', () => { }); it('should call updateAgentProfile with no dialNumber when deviceType is BROWSER', async () => { - const cc = {updateAgentProfile: jest.fn().mockResolvedValue({})}; + const cc = {...ccMock, updateAgentProfile: jest.fn().mockResolvedValue({})}; const {result} = renderHook(() => useStationLogin({ ...baseStationLoginProps, @@ -682,6 +685,7 @@ describe('useStationLogin Hook', () => { result.current.setCurrentLoginOptions({ deviceType: 'BROWSER', dialNumber: 'shouldNotBeSent', + teamId: 'team123', }); }); @@ -691,7 +695,7 @@ describe('useStationLogin Hook', () => { expect(cc.updateAgentProfile).toHaveBeenCalledWith({ loginOption: 'BROWSER', - teamId: undefined, + teamId: 'team123', // dialNumber should NOT be present }); @@ -700,8 +704,6 @@ describe('useStationLogin Hook', () => { expect.objectContaining({ module: 'widget-station-login#helper.ts', method: 'saveLoginOptions', - original: expect.any(Object), - updated: expect.any(Object), }) ); }); @@ -716,7 +718,8 @@ describe('useStationLogin Hook', () => { it('should call stationLogout when doStationLogout is not passed', async () => { const onCCSignOut = jest.fn(); store.setIsAgentLoggedIn(true); - ccMock.stationLogout.mockResolvedValue({}); + ccMock.stationLogout.mockResolvedValue({} as LogoutSuccess); + const {result} = renderHook(() => useStationLogin({ ...baseStationLoginProps, @@ -798,7 +801,7 @@ describe('useStationLogin Hook', () => { it('should handle error if deregister fails in onCCSignOut', async () => { const onCCSignOut = jest.fn(); store.setIsAgentLoggedIn(true); - ccMock.stationLogout.mockResolvedValue({}); + ccMock.stationLogout.mockResolvedValue({} as LogoutSuccess); const error = new Error('Deregister failed'); ccMock.deregister.mockRejectedValue(error); const {result} = renderHook(() => diff --git a/packages/contact-center/station-login/tsconfig.json b/packages/contact-center/station-login/tsconfig.json index 50b68ce6a..3bcd18911 100644 --- a/packages/contact-center/station-login/tsconfig.json +++ b/packages/contact-center/station-login/tsconfig.json @@ -1,7 +1,7 @@ { "extends": "../../../tsconfig.json", "include": [ - "./src" + "./src", ], "compilerOptions": { "outDir": "./dist", diff --git a/packages/contact-center/station-login/tsconfig.test.json b/packages/contact-center/station-login/tsconfig.test.json new file mode 100644 index 000000000..1b8766912 --- /dev/null +++ b/packages/contact-center/station-login/tsconfig.test.json @@ -0,0 +1,9 @@ +// This config is to do type checking in our files while running tests. +{ + "extends": "./tsconfig.json", + "include": ["./tests"], + "exclude": ["**/node_modules/**"], + "compilerOptions": { + "noEmit": true // Don't output any files + } +} \ No newline at end of file diff --git a/packages/contact-center/store/package.json b/packages/contact-center/store/package.json index 3f3e62ab8..332d3f552 100644 --- a/packages/contact-center/store/package.json +++ b/packages/contact-center/store/package.json @@ -3,6 +3,7 @@ "description": "Webex Contact Center Widgets: Widgets Store", "version": "1.28.0-ccwidgets.91", "main": "dist/index.js", + "types": "dist/types/index.d.ts", "license": "MIT", "publishConfig": { "access": "public" @@ -17,11 +18,11 @@ "build": "yarn run -T tsc", "build:src": "yarn run clean:dist && webpack", "build:watch": "webpack --watch", - "test:unit": "jest --coverage", + "test:unit": "tsc --project tsconfig.test.json && jest --coverage", "test:styles": "eslint" }, "dependencies": { - "@webex/plugin-cc": "3.8.1-next.6", + "@webex/plugin-cc": "3.8.1-next.12", "mobx": "6.13.5", "typescript": "5.6.3" }, @@ -36,6 +37,7 @@ "@testing-library/react": "16.0.1", "@types/jest": "29.5.14", "@types/react-test-renderer": "18", + "@webex/test-fixtures": "workspace:*", "babel-jest": "29.7.0", "babel-loader": "9.2.1", "eslint": "^9.20.1", @@ -57,4 +59,4 @@ "webpack-cli": "5.1.4", "webpack-merge": "6.0.1" } -} \ No newline at end of file +} diff --git a/packages/contact-center/store/src/store.ts b/packages/contact-center/store/src/store.ts index 453833e37..64e7da31b 100644 --- a/packages/contact-center/store/src/store.ts +++ b/packages/contact-center/store/src/store.ts @@ -1,10 +1,9 @@ import {makeAutoObservable, observable} from 'mobx'; -import Webex from '@webex/plugin-cc'; +import Webex, {ITask} from '@webex/plugin-cc'; import { IContactCenter, Profile, Team, - WithWebex, IdleCode, InitParams, IStore, @@ -12,11 +11,11 @@ import { IWrapupCode, ICustomState, AgentLoginProfile, + LoginOptions, + WithWebex, } from './store.types'; -import {ITask} from '@webex/plugin-cc'; import {getFeatureFlags} from './util'; -import {LoginOptions} from '../../cc-components/src/components/StationLogin/constants'; class Store implements IStore { private static instance: Store; @@ -67,6 +66,7 @@ class Store implements IStore { console.log('Returning store instance'); return Store.instance; } + registerCC(webex?: WithWebex['webex']): Promise { if (webex) { this.cc = webex.cc; @@ -91,6 +91,7 @@ class Store implements IStore { }); // wire up logger into feature‐flag extraction this.featureFlags = getFeatureFlags(response); + //@ts-expect-error To be fixed in SDK - https://jira-eng-sjc12.cisco.com/jira/browse/CAI-6762 this.teams = response.teams; this.loginOptions = response.webRtcEnabled ? response.loginVoiceOptions @@ -131,6 +132,7 @@ class Store implements IStore { reject(new Error('Webex SDK failed to initialize')); }, 6000); + //@ts-expect-error To be fixed in SDK - https://jira-eng-sjc12.cisco.com/jira/browse/CAI-6762 const webex = Webex.init({ config: options.webexConfig, credentials: { diff --git a/packages/contact-center/store/src/store.types.ts b/packages/contact-center/store/src/store.types.ts index 40cec0c03..f4dee98e3 100644 --- a/packages/contact-center/store/src/store.types.ts +++ b/packages/contact-center/store/src/store.types.ts @@ -1,14 +1,54 @@ import { AgentLogin, - IContactCenter, Profile, - Team, - LogContext, BuddyDetails, - DestinationType, ContactServiceQueue, ITask, + BuddyAgents, + BuddyAgentsResponse, + StateChange, + Logout, } from '@webex/plugin-cc'; +import {DestinationType} from 'node_modules/@webex/plugin-cc/dist/types/services/task/types'; +import { + AgentProfileUpdate, + LogContext, + SetStateResponse, + StationLoginResponse, + StationLogoutResponse, + Team, + UpdateDeviceTypeResponse, +} from 'node_modules/@webex/plugin-cc/dist/types/types'; + +// To be fixed in SDK - https://jira-eng-sjc12.cisco.com/jira/browse/CAI-6762 +interface IContactCenter { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + on: (event: string, callback: (data: any) => void) => void; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + off: (event: string, callback?: (data: any) => void) => void; + updateAgentProfile(data: AgentProfileUpdate): Promise; + stationLogin(data: AgentLogin): Promise; + deregister(): Promise; + stationLogout(data: Logout): Promise; + LoggerProxy: ILogger; + register(): Promise; + taskManager: { + getAllTasks: () => Record; + }; + getBuddyAgents(data: BuddyAgents): Promise; + getQueues(search?: string, filter?: string, page?: number, pageSize?: number): Promise; + agentConfig?: { + regexUS: RegExp | string; + agentId: string; + }; + setAgentState(data: StateChange): Promise; +} +// To be fixed in SDK - https://jira-eng-sjc12.cisco.com/jira/browse/CAI-6762 +type IWebex = { + cc: IContactCenter; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + once: (event: string, callback: (data: any) => void) => void; +}; type ILogger = { log: (message: string, context?: LogContext) => void; @@ -18,15 +58,15 @@ type ILogger = { error: (message: string, context?: LogContext) => void; }; -interface WithWebex { +type WithWebex = { webex: {cc: IContactCenter; logger: ILogger}; -} +}; -interface WithWebexConfig { +type WithWebexConfig = { // eslint-disable-next-line @typescript-eslint/no-explicit-any - webexConfig: any; // TODO: Replace 'any' with the actual type of webexConfig + webexConfig: any; access_token: string; -} +}; type InitParams = WithWebex | WithWebexConfig; @@ -166,6 +206,17 @@ type AgentLoginProfile = { }; }; +// Utility consts +const DIALNUMBER: string = 'AGENT_DN'; +const EXTENSION: string = 'EXTENSION'; +const DESKTOP: string = 'BROWSER'; + +const LoginOptions: {[key: string]: string} = { + [DIALNUMBER]: 'Dial Number', + [EXTENSION]: 'Extension', + [DESKTOP]: 'Desktop', +}; + export type { IContactCenter, ITask, @@ -184,6 +235,7 @@ export type { BuddyDetails, ContactServiceQueue, AgentLoginProfile, + IWebex, }; -export {CC_EVENTS, TASK_EVENTS, ENGAGED_LABEL, ENGAGED_USERNAME}; +export {CC_EVENTS, TASK_EVENTS, ENGAGED_LABEL, ENGAGED_USERNAME, DIALNUMBER, EXTENSION, DESKTOP, LoginOptions}; diff --git a/packages/contact-center/store/src/storeEventsWrapper.ts b/packages/contact-center/store/src/storeEventsWrapper.ts index 0e0a0ca6d..e0b40682f 100644 --- a/packages/contact-center/store/src/storeEventsWrapper.ts +++ b/packages/contact-center/store/src/storeEventsWrapper.ts @@ -191,7 +191,7 @@ class StoreWrapper implements IStoreWrapper { this.store.isAgentLoggedIn = value; }; - setCurrentTask = (task: ITask, isClicked: boolean = false): void => { + setCurrentTask = (task: ITask | null, isClicked: boolean = false): void => { runInAction(() => { // Determine if the new task is the same as the current task let isSameTask = false; @@ -213,8 +213,8 @@ class StoreWrapper implements IStoreWrapper { this.store.taskList = this.store.cc.taskManager.getAllTasks(); }); if (this.currentTask) { - this.setCurrentTask(this.taskList[this.currentTask?.data?.interactionId]); - } else if (this.store.taskList.length > 0) { + this.setCurrentTask(this.store.taskList[this.currentTask?.data?.interactionId]); + } else if (Object.keys(this.store.taskList).length > 0) { this.setCurrentTask(this.store.taskList[Object.keys(this.store.taskList)[0]]); } }; @@ -277,7 +277,7 @@ class StoreWrapper implements IStoreWrapper { } }; - setIncomingTaskCb = (callback: (task: ITask) => void): void => { + setIncomingTaskCb = (callback: ({task}: {task: ITask}) => void): void => { this.onIncomingTask = callback; }; @@ -289,7 +289,7 @@ class StoreWrapper implements IStoreWrapper { this.onTaskAssigned = callback; }; - setTaskSelected = (callback: ((task: ITask) => void) | undefined): void => { + setTaskSelected = (callback: ((task: ITask, isClicked?: boolean) => void) | undefined): void => { if (callback && this.currentTask) { callback(this.currentTask); } @@ -612,10 +612,11 @@ class StoreWrapper implements IStoreWrapper { ): Promise> => { try { const response = await this.store.cc.getBuddyAgents({ + //@ts-expect-error To be fixed in SDK - https://jira-eng-sjc12.cisco.com/jira/browse/CAI-6762 mediaType: mediaType ?? 'telephony', state: 'Available', }); - return response.data.agentList; + return 'data' in response ? response.data.agentList : []; } catch (error) { return Promise.reject(error); } @@ -704,9 +705,11 @@ class StoreWrapper implements IStoreWrapper { this.setIsAgentLoggedIn(true); this.setDeviceType(payload.deviceType); this.setDialNumber(payload.dn); + // @ts-expect-error To be fixed in SDK - https://jira-eng-sjc12.cisco.com/jira/browse/CAI-6762 this.setCurrentState(payload.auxCodeId?.trim() !== '' ? payload.auxCodeId : '0'); this.setLastStateChangeTimestamp(payload.lastStateChangeTimestamp); this.setLastIdleCodeChangeTimestamp(payload.lastIdleCodeChangeTimestamp); + // @ts-expect-error To be fixed in SDK - https://jira-eng-sjc12.cisco.com/jira/browse/CAI-6762 this.setTeamId(payload.teamId); }); }; diff --git a/packages/contact-center/store/tests/store.ts b/packages/contact-center/store/tests/store.ts index 0b778f812..cf771f912 100644 --- a/packages/contact-center/store/tests/store.ts +++ b/packages/contact-center/store/tests/store.ts @@ -1,8 +1,11 @@ import {makeAutoObservable} from 'mobx'; import Webex from '@webex/plugin-cc'; import store from '../src/store'; // Adjust the import path as necessary +import {IStore} from '../src/store.types'; +import {mockProfile} from '@webex/test-fixtures'; let mockShouldCallback = true; +let webexInitSpy; console.log = jest.fn(); // Mock console.log jest.mock('mobx', () => ({ @@ -18,7 +21,7 @@ jest.mock('@webex/plugin-cc', () => ({ } }), cc: { - register: jest.fn(), + register: jest.fn().mockResolvedValue(mockProfile), LoggerProxy: { error: jest.fn(), log: jest.fn(), @@ -32,12 +35,21 @@ jest.mock('@webex/plugin-cc', () => ({ describe('Store', () => { let mockWebex; - let storeInstance; + let storeInstance: IStore; beforeEach(() => { // Reset store values before each test since store is a singleton storeInstance = store.getInstance(); - mockWebex = Webex.init(); + //@ts-expect-error To be fixed in SDK - https://jira-eng-sjc12.cisco.com/jira/browse/CAI-6762 + mockWebex = Webex.init({ + config: {anyConfig: true}, + credentials: { + access_token: 'fake_token', + }, + }); + + //@ts-expect-error To be fixed in SDK - https://jira-eng-sjc12.cisco.com/jira/browse/CAI-6762 + webexInitSpy = jest.spyOn(Webex, 'init'); jest.useFakeTimers(); // Use fake timers for testing setTimeout }); @@ -65,7 +77,7 @@ describe('Store', () => { describe('registerCC', () => { it('should initialise store values on successful register', async () => { const mockAgentName = 'John Doe'; - const date = new Date(); + const date = new Date().getTime(); const mockResponse = { teams: [{id: 'team1', name: 'Team 1'}], loginVoiceOptions: ['option1', 'option2'], @@ -127,40 +139,44 @@ describe('Store', () => { const initParams = {webex: mockWebex}; jest.spyOn(storeInstance, 'registerCC').mockResolvedValue(); - Webex.init.mockClear(); + webexInitSpy.mockClear(); await storeInstance.init(initParams, eventListenerCallback); expect(eventListenerCallback).toHaveBeenCalled(); expect(storeInstance.registerCC).toHaveBeenCalledWith(mockWebex); - expect(Webex.init).not.toHaveBeenCalled(); + expect(webexInitSpy).not.toHaveBeenCalled(); }); it('should call registerCC if webex is in options', async () => { const initParams = {webex: mockWebex}; jest.spyOn(storeInstance, 'registerCC').mockResolvedValue(); - Webex.init.mockClear(); + webexInitSpy.mockClear(); await storeInstance.init(initParams, jest.fn()); expect(storeInstance.registerCC).toHaveBeenCalledWith(mockWebex); - expect(Webex.init).not.toHaveBeenCalled(); + expect(webexInitSpy).not.toHaveBeenCalled(); }); it('should initialize webex and call registerCC on ready event', async () => { + //@ts-expect-error To be fixed in SDK - https://jira-eng-sjc12.cisco.com/jira/browse/CAI-6762 + webexInitSpy = jest.spyOn(Webex, 'init').mockReturnValue(mockWebex); + jest.spyOn(storeInstance, 'registerCC').mockClear(); + const initParams = { webexConfig: {anyConfig: true}, access_token: 'fake_token', }; - jest.spyOn(storeInstance, 'registerCC').mockResolvedValue(); await storeInstance.init(initParams, jest.fn()); - expect(Webex.init).toHaveBeenCalledWith({ + expect(webexInitSpy).toHaveBeenCalledWith({ config: initParams.webexConfig, credentials: {access_token: initParams.access_token}, }); - expect(storeInstance.registerCC).toHaveBeenCalledWith(expect.any(Object)); + + expect(storeInstance.registerCC).toHaveBeenCalledWith(mockWebex); }); it('should reject the promise if registerCC fails in init method', async () => { @@ -184,7 +200,7 @@ describe('Store', () => { jest.spyOn(storeInstance, 'registerCC').mockResolvedValue(); - const initPromise = storeInstance.init(initParams); + const initPromise = storeInstance.init(initParams, jest.fn()); jest.runAllTimers(); // Fast-forward the timers to simulate timeout diff --git a/packages/contact-center/store/tests/storeEventsWrapper.ts b/packages/contact-center/store/tests/storeEventsWrapper.ts index 343d7d2e7..81ce10e39 100644 --- a/packages/contact-center/store/tests/storeEventsWrapper.ts +++ b/packages/contact-center/store/tests/storeEventsWrapper.ts @@ -1,15 +1,19 @@ import {act, waitFor} from '@testing-library/react'; // Add a global mock for MediaStreamTrack to avoid undefined errors +// @ts-expect-error: Avoiding to mock the whole MediaStreamTrack class global.MediaStreamTrack = class MediaStreamTrackMock { constructor() { + // @ts-expect-error: Avoiding to mock the whole MediaStreamTrack class this.kind = 'audio'; } }; // Add a global mock for MediaStream to avoid undefined errors +// @ts-expect-error: Avoiding to mock the whole MediaStream class global.MediaStream = class MediaStreamMock { constructor(tracks) { + // @ts-expect-error: Avoiding to mock the whole MediaStream class this.tracks = tracks; } }; @@ -21,6 +25,7 @@ console.log = jest.fn(); import {CC_EVENTS, TASK_EVENTS} from '../src/store.types'; import storeWrapper from '../src/storeEventsWrapper'; import {ITask} from '@webex/plugin-cc'; +import {mockCC, mockTask as mockTaskFixture} from '@webex/test-fixtures'; jest.mock('../src/store', () => ({ getInstance: jest.fn().mockReturnValue({ @@ -30,7 +35,13 @@ jest.mock('../src/store', () => ({ on: jest.fn(), off: jest.fn(), taskManager: { - getAllTasks: jest.fn(), + getAllTasks: jest.fn().mockReturnValue({ + interaction1: { + data: { + interactionId: 'interaction1', + }, + }, + }), }, }, logger: { @@ -61,14 +72,17 @@ jest.mock('../src/store', () => ({ }, ], agentId: 'mockAgentId', - wrapupCodes: 'mockWrapupCodes', - currentTask: 'mockCurrentTask', + wrapupCodes: [], + currentTask: { + data: { + interactionId: 'mockInteractionId', + }, + }, isAgentLoggedIn: false, deviceType: 'BROWSER', dialNumber: '12345', itemId: '1234', - taskList: 'mockTaskList', - taskMetaData: {}, + taskList: {}, incomingTask: 'mockIncomingTask', currentState: 'mockCurrentState', lastStateChangeTimestamp: 'mockLastStateChangeTimestamp', @@ -148,11 +162,17 @@ describe('storeEventsWrapper', () => { }); it('should proxy wrapupCodes', () => { - expect(storeWrapper.wrapupCodes).toBe('mockWrapupCodes'); + expect(storeWrapper.wrapupCodes).toEqual([]); }); it('should proxy currentTask', () => { - expect(storeWrapper.currentTask).toBe('mockCurrentTask'); + const mockCurrentTask = { + data: { + interactionId: 'mockInteractionId', + }, + } as ITask; + storeWrapper.setCurrentTask(mockCurrentTask); + expect(storeWrapper.currentTask).toEqual(mockCurrentTask); }); it('should proxy isAgentLoggedIn', () => { @@ -164,7 +184,7 @@ describe('storeEventsWrapper', () => { }); it('should proxy taskList', () => { - expect(storeWrapper.taskList).toBe('mockTaskList'); + expect(storeWrapper.taskList).toEqual({}); }); it('should proxy currentState', () => { @@ -232,10 +252,6 @@ describe('storeEventsWrapper', () => { expect(storeWrapper.allowConsultToQueue).toBe(storeWrapper['store'].allowConsultToQueue); }); - it('should proxy taskData', () => { - expect(storeWrapper.taskData).toBe(storeWrapper['store'].taskData); - }); - it('should proxy consultStartTimeStamp', () => { expect(storeWrapper.consultStartTimeStamp).toBe(storeWrapper['store'].consultStartTimeStamp); }); @@ -294,16 +310,16 @@ describe('storeEventsWrapper', () => { const mockLogger = {log: jest.fn(), warn: jest.fn(), error: jest.fn(), info: jest.fn(), trace: jest.fn()}; storeWrapper.registerCC({ - cc: {}, + cc: mockCC, logger: mockLogger, }); - expect(mockRegisterCC).toHaveBeenCalledWith({cc: {}, logger: mockLogger}); + expect(mockRegisterCC).toHaveBeenCalledWith({cc: mockCC, logger: mockLogger}); }); it('should setLastStateChangeTimestamp', () => { expect(storeWrapper.setLastStateChangeTimestamp).toBeInstanceOf(Function); - const timestamp = new Date(); + const timestamp = new Date().getTime(); storeWrapper.setLastStateChangeTimestamp(timestamp); expect(storeWrapper['store'].lastStateChangeTimestamp).toBe(timestamp); }); @@ -311,7 +327,7 @@ describe('storeEventsWrapper', () => { it('should setLastIdleCodeChangeTimestamp', () => { expect(storeWrapper.setLastIdleCodeChangeTimestamp).toBeInstanceOf(Function); - const timestamp = new Date(); + const timestamp = new Date().getTime(); storeWrapper.setLastIdleCodeChangeTimestamp(timestamp); expect(storeWrapper.lastIdleCodeChangeTimestamp).toBe(timestamp); }); @@ -391,13 +407,7 @@ describe('storeEventsWrapper', () => { describe('setTaskCallback/removeTaskCallback', () => { let mockTask: ITask; beforeEach(() => { - mockTask = { - data: { - interactionId: 'mockTaskId', - }, - on: jest.fn(), - off: jest.fn(), - }; + mockTask = mockTaskFixture; // mock return the task list from cc.taskManager storeWrapper['store'].cc.taskManager.getAllTasks = jest .fn() @@ -408,6 +418,9 @@ describe('storeEventsWrapper', () => { it('should set task callback', () => { const mockCb = jest.fn(); expect(storeWrapper.setTaskCallback).toBeInstanceOf(Function); + storeWrapper['store'].taskList = { + mockTaskId: mockTask, + }; storeWrapper.setTaskCallback(TASK_EVENTS.TASK_ASSIGNED, mockCb, 'mockTaskId'); expect(mockTask.on).toHaveBeenCalledWith(TASK_EVENTS.TASK_ASSIGNED, mockCb); @@ -426,10 +439,13 @@ describe('storeEventsWrapper', () => { it('should remove task callback', () => { const mockCb = jest.fn(); + storeWrapper['store'].taskList = { + mockTaskId: mockTask, + }; expect(storeWrapper.removeTaskCallback).toBeInstanceOf(Function); - storeWrapper.removeTaskCallback('event', mockCb, 'mockTaskId'); - expect(mockTask.off).toHaveBeenCalledWith('event', mockCb); + storeWrapper.removeTaskCallback(TASK_EVENTS.AGENT_WRAPPEDUP, mockCb, 'mockTaskId'); + expect(mockTask.off).toHaveBeenCalledWith(TASK_EVENTS.AGENT_WRAPPEDUP, mockCb); }); it('should return and not remove callback if callback is not present or task is not found', () => { @@ -463,7 +479,18 @@ describe('storeEventsWrapper', () => { }); it('should initialize the store and set up incoming task handler', async () => { - const options = {someOption: 'value'}; + const options = { + webex: { + cc: mockCC, + logger: { + log: jest.fn(), + info: jest.fn(), + warn: jest.fn(), + trace: jest.fn(), + error: jest.fn(), + }, + }, + }; await storeWrapper.init(options); expect(storeWrapper['store'].init).toHaveBeenCalledWith(options, expect.any(Function)); @@ -473,7 +500,7 @@ describe('storeEventsWrapper', () => { const mockIncomingTaskCallback = jest.fn(); storeWrapper.setIncomingTaskCb(mockIncomingTaskCallback); // Ensure mockTask is properly set up - const mockTask: ITask = { + const mockTask2: ITask = { data: { interactionId: 'interaction1', interaction: { @@ -484,24 +511,25 @@ describe('storeEventsWrapper', () => { off: jest.fn(), } as unknown as ITask; - // Add the mock task to the task list + storeWrapper['store'].taskList = {interaction2: mockTask}; + storeWrapper.setCurrentTask(mockTask); // Call the method under test - storeWrapper.handleIncomingTask(mockTask); - expect(mockIncomingTaskCallback).toHaveBeenCalledWith({task: mockTask}); + storeWrapper.handleIncomingTask(mockTask2); + expect(mockIncomingTaskCallback).toHaveBeenCalledWith({task: mockTask2}); // Verify that the correct event handlers were registered - expect(mockTask.on).toHaveBeenCalledWith(TASK_EVENTS.TASK_END, expect.any(Function)); - expect(mockTask.on).toHaveBeenCalledWith(TASK_EVENTS.TASK_ASSIGNED, expect.any(Function)); - expect(mockTask.on).toHaveBeenCalledWith(TASK_EVENTS.AGENT_CONSULT_CREATED, expect.any(Function)); - expect(mockTask.on).toHaveBeenCalledWith(TASK_EVENTS.TASK_MEDIA, expect.any(Function)); - expect(mockTask.on).toHaveBeenCalledWith(TASK_EVENTS.TASK_CONSULTING, expect.any(Function)); - expect(mockTask.on).toHaveBeenCalledWith(TASK_EVENTS.TASK_CONSULT_ACCEPTED, expect.any(Function)); - expect(mockTask.on).toHaveBeenCalledWith(TASK_EVENTS.TASK_CONSULT_END, expect.any(Function)); - expect(mockTask.on).toHaveBeenCalledWith(TASK_EVENTS.TASK_CONSULT_QUEUE_CANCELLED, expect.any(Function)); - expect(mockTask.on).toHaveBeenCalledWith(TASK_EVENTS.AGENT_WRAPPEDUP, expect.any(Function)); - expect(mockTask.on).toHaveBeenCalledWith(TASK_EVENTS.TASK_HOLD, storeWrapper.refreshTaskList); - expect(mockTask.on).toHaveBeenCalledWith(TASK_EVENTS.TASK_UNHOLD, storeWrapper.refreshTaskList); + expect(mockTask2.on).toHaveBeenCalledWith(TASK_EVENTS.TASK_END, expect.any(Function)); + expect(mockTask2.on).toHaveBeenCalledWith(TASK_EVENTS.TASK_ASSIGNED, expect.any(Function)); + expect(mockTask2.on).toHaveBeenCalledWith(TASK_EVENTS.AGENT_CONSULT_CREATED, expect.any(Function)); + expect(mockTask2.on).toHaveBeenCalledWith(TASK_EVENTS.TASK_MEDIA, expect.any(Function)); + expect(mockTask2.on).toHaveBeenCalledWith(TASK_EVENTS.TASK_CONSULTING, expect.any(Function)); + expect(mockTask2.on).toHaveBeenCalledWith(TASK_EVENTS.TASK_CONSULT_ACCEPTED, expect.any(Function)); + expect(mockTask2.on).toHaveBeenCalledWith(TASK_EVENTS.TASK_CONSULT_END, expect.any(Function)); + expect(mockTask2.on).toHaveBeenCalledWith(TASK_EVENTS.TASK_CONSULT_QUEUE_CANCELLED, expect.any(Function)); + expect(mockTask2.on).toHaveBeenCalledWith(TASK_EVENTS.AGENT_WRAPPEDUP, expect.any(Function)); + expect(mockTask2.on).toHaveBeenCalledWith(TASK_EVENTS.TASK_HOLD, storeWrapper.refreshTaskList); + expect(mockTask2.on).toHaveBeenCalledWith(TASK_EVENTS.TASK_UNHOLD, storeWrapper.refreshTaskList); }); it('should handle consulting i.e handleIncomingTask with the task already present in the taskList', () => { @@ -689,14 +717,14 @@ describe('storeEventsWrapper', () => { it('should handle task removal when no task is present', () => { storeWrapper['store'].taskList = {}; storeWrapper['store'].currentTask = null; - storeWrapper.handleTaskRemove(mockTask.data.interactionId); + storeWrapper.handleTaskRemove('someRandomId'); expect(mockTask.off).not.toHaveBeenCalledWith(TASK_EVENTS.TASK_ASSIGNED, expect.any(Function)); expect(mockTask.off).not.toHaveBeenCalledWith(TASK_EVENTS.TASK_END, expect.any(Function)); const refreshTaskListSpy = jest.spyOn(storeWrapper, 'refreshTaskList'); const setCurrentTaskSpy = jest.spyOn(storeWrapper, 'setCurrentTask'); - storeWrapper.handleTaskRemove(mockTask.data.interactionId); + storeWrapper.handleTaskRemove('someRandomId'); expect(refreshTaskListSpy).toHaveBeenCalledWith(); expect(setCurrentTaskSpy).not.toHaveBeenCalledWith(null); @@ -782,7 +810,18 @@ describe('storeEventsWrapper', () => { off: jest.fn(), } as unknown as ITask; - const options = {someOption: 'value'}; + const options = { + webex: { + cc: mockCC, + logger: { + log: jest.fn(), + info: jest.fn(), + warn: jest.fn(), + trace: jest.fn(), + error: jest.fn(), + }, + }, + }; beforeEach(async () => { jest.clearAllMocks(); @@ -802,21 +841,20 @@ describe('storeEventsWrapper', () => { it('should set agentProfile on Relogin and Login', async () => { const cc = storeWrapper['store'].cc; + const onSpy = jest.spyOn(storeWrapper['cc'], 'on'); const setAgentProfileSpy = jest.spyOn(storeWrapper, 'setAgentProfile'); storeWrapper['store'].init = jest.fn().mockReturnValue(storeWrapper.setupIncomingTaskHandler(cc)); await storeWrapper.init(options); - const loginCb = storeWrapper['cc'].on.mock.calls.find( - (call) => call[0] === CC_EVENTS.AGENT_STATION_LOGIN_SUCCESS - )[1]; + const loginCb = onSpy.mock.calls.find((call) => call[0] === CC_EVENTS.AGENT_STATION_LOGIN_SUCCESS)[1]; act(() => { loginCb(mockAgentProfilePayload); }); expect(setAgentProfileSpy).toHaveBeenCalledWith(mockAgentProfilePayload); - const reloginCb = storeWrapper['cc'].on.mock.calls.find((call) => call[0] === CC_EVENTS.AGENT_RELOGIN_SUCCESS)[1]; + const reloginCb = onSpy.mock.calls.find((call) => call[0] === CC_EVENTS.AGENT_RELOGIN_SUCCESS)[1]; act(() => { reloginCb(mockAgentProfilePayload); @@ -827,12 +865,13 @@ describe('storeEventsWrapper', () => { it('should handle task:incoming event ', async () => { const cc = storeWrapper['store'].cc; + const onSpy = jest.spyOn(storeWrapper['cc'], 'on'); storeWrapper['store'].init = jest.fn().mockReturnValue(storeWrapper.setupIncomingTaskHandler(cc)); await storeWrapper.init(options); act(() => { - storeWrapper['store'].cc.on.mock.calls[0][1](mockTask); + onSpy.mock.calls[0][1](mockTask); }); waitFor(() => { @@ -844,22 +883,25 @@ describe('storeEventsWrapper', () => { it('should handle task:end event with wrapupRequired', async () => { const cc = storeWrapper['store'].cc; + const onSpy = jest.spyOn(storeWrapper['cc'], 'on'); storeWrapper['store'].init = jest.fn().mockReturnValue(storeWrapper.setupIncomingTaskHandler(cc)); await storeWrapper.init(options); // Login event stag: the agent is logged in act(() => { - storeWrapper['cc'].on.mock.calls[1][1](); + onSpy.mock.calls[1][1]({}); }); - expect(storeWrapper['cc'].on).toHaveBeenCalledWith(TASK_EVENTS.TASK_HYDRATE, expect.any(Function)); - expect(storeWrapper['cc'].on).toHaveBeenCalledWith(TASK_EVENTS.TASK_INCOMING, expect.any(Function)); - expect(storeWrapper['cc'].on).toHaveBeenCalledWith(CC_EVENTS.AGENT_STATE_CHANGE, expect.any(Function)); - expect(storeWrapper['cc'].on).toHaveBeenCalledWith(CC_EVENTS.AGENT_MULTI_LOGIN, expect.any(Function)); + expect(onSpy).toHaveBeenCalledWith(TASK_EVENTS.TASK_HYDRATE, expect.any(Function)); + expect(onSpy).toHaveBeenCalledWith(TASK_EVENTS.TASK_INCOMING, expect.any(Function)); + expect(onSpy).toHaveBeenCalledWith(CC_EVENTS.AGENT_STATE_CHANGE, expect.any(Function)); + expect(onSpy).toHaveBeenCalledWith(CC_EVENTS.AGENT_MULTI_LOGIN, expect.any(Function)); - const incomingTaskCb = storeWrapper['cc'].on.mock.calls.find((call) => call[0] === TASK_EVENTS.TASK_INCOMING)[1]; + const incomingTaskCb = onSpy.mock.calls.find((call) => call[0] === TASK_EVENTS.TASK_INCOMING)[1]; // Incoming task stage: a task has just reached the agent + + const mockTaskOnSpy = jest.spyOn(mockTask, 'on'); act(() => { incomingTaskCb(mockTask); }); @@ -872,7 +914,7 @@ describe('storeEventsWrapper', () => { // The call is answered and the task is assigned to the agent act(() => { - mockTask.on.mock.calls[1][1](); + mockTaskOnSpy.mock.calls[1][1](); }); waitFor(() => { @@ -882,7 +924,7 @@ describe('storeEventsWrapper', () => { // Task end stage: the task is completed act(() => { - mockTask.on.mock.calls[0][1]({wrapupRequired: true}); + mockTaskOnSpy.mock.calls[0][1]({wrapupRequired: true}); }); waitFor(() => { @@ -893,24 +935,27 @@ describe('storeEventsWrapper', () => { it('should handle AgentWrappedUp event ', async () => { const cc = storeWrapper['store'].cc; + const onSpy = jest.spyOn(storeWrapper['cc'], 'on'); storeWrapper['store'].init = jest.fn().mockReturnValue(storeWrapper.setupIncomingTaskHandler(cc)); await storeWrapper.init(options); // Login event stag: the agent is logged in act(() => { - storeWrapper['cc'].on.mock.calls[1][1](); + onSpy.mock.calls[1][1]({}); }); - const incomingTaskCb = storeWrapper['cc'].on.mock.calls.find((call) => call[0] === TASK_EVENTS.TASK_INCOMING)[1]; + const incomingTaskCb = onSpy.mock.calls.find((call) => call[0] === TASK_EVENTS.TASK_INCOMING)[1]; // Incoming task stage: a task has just reached the agent + const mockTaskOnSpy = jest.spyOn(mockTask, 'on'); + act(() => { incomingTaskCb(mockTask); }); // AgentWrappedUp event stage: the agent has wrapped up the task act(() => { - const mockTaskWrappedUpCb = mockTask.on.mock.calls.find((call) => call[0] === TASK_EVENTS.AGENT_WRAPPEDUP)[1]; + const mockTaskWrappedUpCb = mockTaskOnSpy.mock.calls.find((call) => call[0] === TASK_EVENTS.AGENT_WRAPPEDUP)[1]; mockTaskWrappedUpCb(mockTask.data); }); @@ -946,18 +991,6 @@ describe('storeEventsWrapper', () => { expect(setCurrentTaskSpy).toHaveBeenCalledWith(null); }); - it('should handle task end when call is not connected', () => { - const taskNotConnectedNoWrapup = { - data: { - interactionId: 'task4', - interaction: { - state: 'new', - }, - }, - }; - storeWrapper.handleTaskEnd(taskNotConnectedNoWrapup); - }); - it('should set selected login option', () => { jest.spyOn(storeWrapper, 'setDeviceType'); const option = 'newLoginOption'; @@ -967,15 +1000,14 @@ describe('storeEventsWrapper', () => { }); it('should handle multilogin session modal with in correct data', async () => { + const onSpy = jest.spyOn(storeWrapper['cc'], 'on'); jest.spyOn(storeWrapper, 'setShowMultipleLoginAlert'); const cc = storeWrapper['store'].cc; storeWrapper['store'].init = jest.fn().mockReturnValue(storeWrapper.setupIncomingTaskHandler(cc)); - - const options = {someOption: 'value'}; await storeWrapper.init(options); act(() => { - cc.on.mock.calls[1][1]({}); + onSpy.mock.calls[1][1]({}); }); expect(storeWrapper.setShowMultipleLoginAlert).not.toHaveBeenCalledWith(true); @@ -983,21 +1015,19 @@ describe('storeEventsWrapper', () => { it('should handle multilogin session modal with correct data', async () => { const cc = storeWrapper['store'].cc; + const onSpy = jest.spyOn(cc, 'on'); storeWrapper['store'].init = jest.fn().mockReturnValue(storeWrapper.setupIncomingTaskHandler(cc)); jest.spyOn(storeWrapper, 'setShowMultipleLoginAlert'); - const options = {someOption: 'value'}; await storeWrapper.init(options); // Login event stag: the agent is logged in act(() => { - storeWrapper['cc'].on.mock.calls[1][1](); + onSpy.mock.calls[1][1]({}); }); act(() => { - const multiLoginCb = storeWrapper['cc'].on.mock.calls.find( - (call) => call[0] === CC_EVENTS.AGENT_MULTI_LOGIN - )[1]; + const multiLoginCb = onSpy.mock.calls.find((call) => call[0] === CC_EVENTS.AGENT_MULTI_LOGIN)[1]; multiLoginCb({type: 'AgentMultiLoginCloseSession'}); }); @@ -1014,13 +1044,13 @@ describe('storeEventsWrapper', () => { it('should handle state change event with incorrect data', async () => { const cc = storeWrapper['store'].cc; + const onSpy = jest.spyOn(cc, 'on'); storeWrapper['store'].init = jest.fn().mockReturnValue(storeWrapper.setupIncomingTaskHandler(cc)); jest.spyOn(storeWrapper, 'setCurrentState'); - const options = {someOption: 'value'}; await storeWrapper.init(options); act(() => { - cc.on.mock.calls[1][1]({}); + onSpy.mock.calls[1][1]({}); }); expect(storeWrapper.setCurrentState).not.toHaveBeenCalledWith(); @@ -1028,20 +1058,18 @@ describe('storeEventsWrapper', () => { it('should handle state change event with correct data and emplty auxcodeId', async () => { const cc = storeWrapper['store'].cc; + const onSpy = jest.spyOn(cc, 'on'); storeWrapper['store'].init = jest.fn().mockReturnValue(storeWrapper.setupIncomingTaskHandler(cc)); jest.spyOn(storeWrapper, 'setCurrentState'); - const options = {someOption: 'value'}; await storeWrapper.init(options); act(() => { - storeWrapper['cc'].on.mock.calls[1][1](); + onSpy.mock.calls[1][1]({}); }); act(() => { - const stateChangeCb = storeWrapper['cc'].on.mock.calls.find( - (call) => call[0] === CC_EVENTS.AGENT_STATE_CHANGE - )[1]; + const stateChangeCb = onSpy.mock.calls.find((call) => call[0] === CC_EVENTS.AGENT_STATE_CHANGE)[1]; stateChangeCb({type: 'AgentStateChangeSuccess', auxCodeId: ''}); }); @@ -1050,20 +1078,18 @@ describe('storeEventsWrapper', () => { it('should handle state change event with correct data', async () => { const cc = storeWrapper['store'].cc; + const onSpy = jest.spyOn(cc, 'on'); storeWrapper['store'].init = jest.fn().mockReturnValue(storeWrapper.setupIncomingTaskHandler(cc)); jest.spyOn(storeWrapper, 'setCurrentState'); - const options = {someOption: 'value'}; await storeWrapper.init(options); act(() => { - storeWrapper['cc'].on.mock.calls[1][1](); + onSpy.mock.calls[1][1]({}); }); act(() => { - const stateChangeCb = storeWrapper['cc'].on.mock.calls.find( - (call) => call[0] === CC_EVENTS.AGENT_STATE_CHANGE - )[1]; + const stateChangeCb = onSpy.mock.calls.find((call) => call[0] === CC_EVENTS.AGENT_STATE_CHANGE)[1]; stateChangeCb({type: 'AgentStateChangeSuccess', auxCodeId: 'available'}); }); @@ -1071,6 +1097,7 @@ describe('storeEventsWrapper', () => { }); it('should handle hydrating the store with correct data', async () => { + const onSpy = jest.spyOn(storeWrapper['cc'], 'on'); const setCurrentTaskSpy = jest.spyOn(storeWrapper, 'setCurrentTask'); const refreshTaskListSpy = jest.spyOn(storeWrapper, 'refreshTaskList'); const handleTaskRemoveSpy = jest.spyOn(storeWrapper, 'handleTaskRemove'); @@ -1078,7 +1105,6 @@ describe('storeEventsWrapper', () => { const cc = storeWrapper['store'].cc; storeWrapper['store'].init = jest.fn().mockReturnValue(storeWrapper.setupIncomingTaskHandler(cc)); - const options = {someOption: 'value'}; await storeWrapper.init(options); storeWrapper['store'].taskList = {}; @@ -1101,11 +1127,11 @@ describe('storeEventsWrapper', () => { }; act(() => { - storeWrapper['cc'].on.mock.calls[1][1](); + onSpy.mock.calls[1][1]({}); }); act(() => { - const hydrateTaskCb = storeWrapper['cc'].on.mock.calls.find((call) => call[0] === TASK_EVENTS.TASK_HYDRATE)[1]; + const hydrateTaskCb = onSpy.mock.calls.find((call) => call[0] === TASK_EVENTS.TASK_HYDRATE)[1]; hydrateTaskCb(mockTask); }); @@ -1132,11 +1158,11 @@ describe('storeEventsWrapper', () => { const setStateSpy = jest.spyOn(storeWrapper, 'setState'); const cc = storeWrapper['store'].cc; + const onSpy = jest.spyOn(cc, 'on'); storeWrapper['store'].init = jest.fn().mockReturnValue(storeWrapper.setupIncomingTaskHandler(cc)); - const options = {someOption: 'value'}; await storeWrapper.init(options); - storeWrapper['store'].taskList = []; + storeWrapper['store'].taskList = {}; const mockTask = { data: { @@ -1156,13 +1182,11 @@ describe('storeEventsWrapper', () => { }; act(() => { - storeWrapper['cc'].on.mock.calls[1][1](); + onSpy.mock.calls[1][1]({}); }); act(() => { - const hydrateTaskCb = storeWrapper['cc'].on.mock.calls.find( - (call) => call[0] === TASK_EVENTS.TASK_HYDRATE - )[1]; + const hydrateTaskCb = onSpy.mock.calls.find((call) => call[0] === TASK_EVENTS.TASK_HYDRATE)[1]; hydrateTaskCb(mockTask); }); @@ -1174,13 +1198,11 @@ describe('storeEventsWrapper', () => { it('should handle custom state correctly when wrapup is not required', async () => { const setStateSpy = jest.spyOn(storeWrapper, 'setState'); - const cc = storeWrapper['store'].cc; - storeWrapper['store'].init = jest.fn().mockReturnValue(storeWrapper.setupIncomingTaskHandler(cc)); + const onSpy = jest.spyOn(cc, 'on'); - const options = {someOption: 'value'}; - await storeWrapper.init(options); - storeWrapper['store'].taskList = []; + storeWrapper['store'].init = jest.fn().mockReturnValue(storeWrapper.setupIncomingTaskHandler(cc)); + storeWrapper['store'].taskList = {}; const mockTask = { data: { @@ -1200,13 +1222,11 @@ describe('storeEventsWrapper', () => { }; act(() => { - storeWrapper['cc'].on.mock.calls[1][1](); + onSpy.mock.calls[1][1]({}); }); act(() => { - const hydrateTaskCb = storeWrapper['cc'].on.mock.calls.find( - (call) => call[0] === TASK_EVENTS.TASK_HYDRATE - )[1]; + const hydrateTaskCb = onSpy.mock.calls.find((call) => call[0] === TASK_EVENTS.TASK_HYDRATE)[1]; hydrateTaskCb(mockTask); }); @@ -1223,11 +1243,11 @@ describe('storeEventsWrapper', () => { const refreshTaskListSpy = jest.spyOn(storeWrapper, 'refreshTaskList'); const cc = storeWrapper['store'].cc; + const onSpy = jest.spyOn(cc, 'on'); storeWrapper['store'].init = jest.fn().mockReturnValue(storeWrapper.setupIncomingTaskHandler(cc)); - const options = {someOption: 'value'}; await storeWrapper.init(options); - storeWrapper['store'].taskList = []; + storeWrapper['store'].taskList = {}; const mockTask = { data: { @@ -1246,11 +1266,11 @@ describe('storeEventsWrapper', () => { }; act(() => { - storeWrapper['cc'].on.mock.calls[1][1](); + onSpy.mock.calls[1][1]({}); }); act(() => { - const hydrateTaskCb = storeWrapper['cc'].on.mock.calls.find((call) => call[0] === TASK_EVENTS.TASK_HYDRATE)[1]; + const hydrateTaskCb = onSpy.mock.calls.find((call) => call[0] === TASK_EVENTS.TASK_HYDRATE)[1]; hydrateTaskCb(mockTask); }); @@ -1260,19 +1280,19 @@ describe('storeEventsWrapper', () => { it('should remove event listeners on successful logout and clear agentProfile', async () => { const cc = storeWrapper['store'].cc; + const onSpy = jest.spyOn(cc, 'on'); storeWrapper['store'].init = jest.fn().mockReturnValue(storeWrapper.setupIncomingTaskHandler(cc)); const setAgentProfileSpy = jest.spyOn(storeWrapper, 'setAgentProfile'); - const options = {someOption: 'value'}; await storeWrapper.init(options); act(() => { - storeWrapper['cc'].on.mock.calls[1][1](); + onSpy.mock.calls[1][1]({}); }); act(() => { - const logOutCb = storeWrapper['cc'].on.mock.calls.find((call) => call[0] === CC_EVENTS.AGENT_LOGOUT_SUCCESS)[1]; - logOutCb(); + const logOutCb = onSpy.mock.calls.find((call) => call[0] === CC_EVENTS.AGENT_LOGOUT_SUCCESS)[1]; + logOutCb({}); }); expect(storeWrapper['cc'].off).toHaveBeenCalledWith(TASK_EVENTS.TASK_HYDRATE, expect.any(Function)); @@ -1290,20 +1310,24 @@ describe('storeEventsWrapper', () => { off: jest.fn(), } as unknown as ITask; + const rejectTaskOnSpy = jest.spyOn(rejectTask, 'on'); const onTaskRejectedMock = jest.fn(); - storeWrapper.setTaskRejected(onTaskRejectedMock); const removeSpy = jest.spyOn(storeWrapper, 'handleTaskRemove'); + + storeWrapper.setTaskRejected(onTaskRejectedMock); storeWrapper['store'].cc.taskManager.getAllTasks = jest .fn() .mockReturnValue({[rejectTask.data.interactionId]: rejectTask}); storeWrapper.refreshTaskList(); storeWrapper.handleIncomingTask(rejectTask); - const taskRejectCall = rejectTask.on.mock.calls.find((call) => call[0] === TASK_EVENTS.TASK_REJECT); + const taskRejectCall = rejectTaskOnSpy.mock.calls.find((call) => call[0] === TASK_EVENTS.TASK_REJECT); + expect(taskRejectCall).toBeDefined(); - const rejectCallback = taskRejectCall[1]; + const rejectCallback = taskRejectCall[1]; const reason = 'Task Rejected Reason'; + rejectCallback(reason); // Ensure the correct arguments are passed to onTaskRejectedMock @@ -1319,6 +1343,7 @@ describe('storeEventsWrapper', () => { on: jest.fn(), off: jest.fn(), } as unknown as ITask; + const rejectTaskOnSpy = jest.spyOn(rejectTask, 'on'); const onTaskRejectedMock = jest.fn(); storeWrapper.setTaskRejected(onTaskRejectedMock); @@ -1329,7 +1354,7 @@ describe('storeEventsWrapper', () => { storeWrapper.refreshTaskList(); storeWrapper.handleIncomingTask(rejectTask); - const taskRejectCall = rejectTask.on.mock.calls.find((call) => call[0] === TASK_EVENTS.TASK_REJECT); + const taskRejectCall = rejectTaskOnSpy.mock.calls.find((call) => call[0] === TASK_EVENTS.TASK_REJECT); expect(taskRejectCall).toBeDefined(); const rejectCallback = taskRejectCall[1]; @@ -1387,7 +1412,7 @@ describe('storeEventsWrapper', () => { off: jest.fn(), } as unknown as ITask; - storeWrapper['store'].taskList = []; + storeWrapper['store'].taskList = {}; storeWrapper.handleIncomingTask(mockTask); // Verify the TASK_CONSULT_QUEUE_CANCELLED handler was registered @@ -1413,7 +1438,6 @@ describe('storeEventsWrapper', () => { .fn() .mockReturnValue({[mockTask.data.interactionId]: mockTask}); storeWrapper.refreshTaskList(); - storeWrapper['store'].taskMetaData = {}; }); it('should attach TASK_MEDIA handler when deviceType is BROWSER', () => { @@ -1495,6 +1519,7 @@ describe('storeEventsWrapper', () => { }); it('should not attach TASK_MEDIA handler when deviceType is not BROWSER', () => { + const mockTaskOnSpy = jest.spyOn(mockTask, 'on'); // Set deviceType to something other than BROWSER storeWrapper['store'].deviceType = 'DESKTOP'; @@ -1502,11 +1527,12 @@ describe('storeEventsWrapper', () => { storeWrapper.handleIncomingTask(mockTask); // Verify TASK_MEDIA handler was not attached - const taskMediaCall = mockTask.on.mock.calls.find((call) => call[0] === TASK_EVENTS.TASK_MEDIA); + const taskMediaCall = mockTaskOnSpy.mock.calls.find((call) => call[0] === TASK_EVENTS.TASK_MEDIA); expect(taskMediaCall).toBeUndefined(); }); it('should not attach TASK_MEDIA handler in handleTaskHydrate when deviceType is not BROWSER', () => { + const mockTaskOnSpy = jest.spyOn(mockTask, 'on'); // Set deviceType to something other than BROWSER storeWrapper['store'].deviceType = 'DESKTOP'; @@ -1514,7 +1540,7 @@ describe('storeEventsWrapper', () => { storeWrapper.handleTaskHydrate(mockTask); // Verify TASK_MEDIA handler was not attached - const taskMediaCall = mockTask.on.mock.calls.find((call) => call[0] === TASK_EVENTS.TASK_MEDIA); + const taskMediaCall = mockTaskOnSpy.mock.calls.find((call) => call[0] === TASK_EVENTS.TASK_MEDIA); expect(taskMediaCall).toBeUndefined(); }); @@ -1531,17 +1557,18 @@ describe('storeEventsWrapper', () => { }); it('should not try to remove TASK_MEDIA handler on task removal when deviceType is not BROWSER', () => { + const mockTaskOffSpy = jest.spyOn(mockTask, 'off'); // Set deviceType to something other than BROWSER storeWrapper['store'].deviceType = 'DESKTOP'; // Add the task to taskList - storeWrapper['store'].taskList = [mockTask]; + storeWrapper['store'].taskList = {mockTask: mockTask}; // Call handleTaskRemove storeWrapper.handleTaskRemove(mockTask.data.interactionId); // Verify TASK_MEDIA handler was not removed - const taskMediaOffCall = mockTask.off.mock.calls.find((call) => call[0] === TASK_EVENTS.TASK_MEDIA); + const taskMediaOffCall = mockTaskOffSpy.mock.calls.find((call) => call[0] === TASK_EVENTS.TASK_MEDIA); expect(taskMediaOffCall).toBeUndefined(); expect(storeWrapper.setCallControlAudio).not.toHaveBeenCalled(); }); @@ -1558,6 +1585,7 @@ describe('storeEventsWrapper', () => { }); it('should not attach TASK_MEDIA handler in handleConsultAccepted when deviceType is not BROWSER', () => { + const mockTaskOnSpy = jest.spyOn(mockTask, 'on'); // Set deviceType to something other than BROWSER storeWrapper['store'].deviceType = 'DESKTOP'; @@ -1565,7 +1593,7 @@ describe('storeEventsWrapper', () => { storeWrapper.handleConsultAccepted(mockTask); // Verify TASK_MEDIA handler was not attached - const taskMediaCall = mockTask.on.mock.calls.find((call) => call[0] === TASK_EVENTS.TASK_MEDIA); + const taskMediaCall = mockTaskOnSpy.mock.calls.find((call) => call[0] === TASK_EVENTS.TASK_MEDIA); expect(taskMediaCall).toBeUndefined(); }); }); @@ -1619,7 +1647,6 @@ describe('storeEventsWrapper', () => { mockTaskB = { data: {interactionId: 'taskB'}, } as unknown as ITask; - storeWrapper['store'].taskMetaData = {}; storeWrapper['store'].consultCompleted = true; storeWrapper['store'].consultInitiated = true; storeWrapper['store'].consultAccepted = true; diff --git a/packages/contact-center/store/tests/util.ts b/packages/contact-center/store/tests/util.ts index 4a9e328bf..2a6aaf11b 100644 --- a/packages/contact-center/store/tests/util.ts +++ b/packages/contact-center/store/tests/util.ts @@ -1,37 +1,22 @@ import {getFeatureFlags} from '../src/util'; +import {mockProfile} from '@webex/test-fixtures'; describe('getFeatureFlags', () => { it('should return an object with feature flags from agent profile with corresponding values', () => { const featureFlags = { - isOutboundEnabledForTenant: true, - isOutboundEnabledForAgent: true, isAdhocDialingEnabled: true, isCampaignManagementEnabled: true, isEndCallEnabled: true, - isEndConsultEnabled: false, agentPersonalStatsEnabled: true, - isCallMonitoringEnabled: true, - isMidCallMonitoringEnabled: false, - isBargeInEnabled: true, - isManagedTeamsEnabled: true, - isManagedQueuesEnabled: false, - isSendMessageEnabled: true, - isAgentStateChangeEnabled: true, - isSignOutAgentsEnabled: true, - isTimeoutDesktopInactivityEnabled: true, - isAnalyzerEnabled: false, webRtcEnabled: true, - isRecordingManagementEnabled: true, allowConsultToQueue: true, + isEndConsultEnabled: true, + isOutboundEnabledForAgent: false, + isOutboundEnabledForTenant: false, + isTimeoutDesktopInactivityEnabled: false, }; - const agentProfile = { - randomKey1: 'randomValue1', - randomKey2: 'randomValue2', - ...featureFlags, - }; - - const result = getFeatureFlags(agentProfile); + const result = getFeatureFlags(mockProfile); expect(result).toEqual(featureFlags); }); diff --git a/packages/contact-center/store/tsconfig.json b/packages/contact-center/store/tsconfig.json index 0c449ca5e..32cd1eda3 100644 --- a/packages/contact-center/store/tsconfig.json +++ b/packages/contact-center/store/tsconfig.json @@ -1,7 +1,7 @@ { "extends": "../../../tsconfig.json", "include": [ - "./src" + "./src", ], "compilerOptions": { "outDir": "./dist", diff --git a/packages/contact-center/store/tsconfig.test.json b/packages/contact-center/store/tsconfig.test.json new file mode 100644 index 000000000..a2290801c --- /dev/null +++ b/packages/contact-center/store/tsconfig.test.json @@ -0,0 +1,10 @@ +// This config is to do type checking in our files while running tests. +{ + "extends": "./tsconfig.json", + "include": ["./tests"], + "exclude": ["node_modules"], + "compilerOptions": { + "noEmit": true ,// Don't output any files + "types": ["jest"], + } +} \ No newline at end of file diff --git a/packages/contact-center/task/package.json b/packages/contact-center/task/package.json index 3d8e7ea9c..6e08993c2 100644 --- a/packages/contact-center/task/package.json +++ b/packages/contact-center/task/package.json @@ -3,6 +3,7 @@ "description": "Webex Contact Center Widgets: Task", "version": "1.28.0-ccwidgets.91", "main": "dist/index.js", + "types": "dist/types/index.d.ts", "publishConfig": { "access": "public" }, @@ -16,7 +17,7 @@ "build": "yarn run -T tsc", "build:src": "yarn run clean:dist && webpack", "build:watch": "webpack --watch", - "test:unit": "jest --coverage", + "test:unit": "tsc --project tsconfig.test.json && jest --coverage", "test:styles": "eslint" }, "dependencies": { @@ -35,6 +36,7 @@ "@testing-library/react": "16.0.1", "@types/jest": "29.5.14", "@types/react-test-renderer": "18", + "@webex/test-fixtures": "workspace:*", "babel-jest": "29.7.0", "babel-loader": "9.2.1", "eslint": "^9.20.1", @@ -62,4 +64,4 @@ "react": ">=18.3.1", "react-dom": ">=18.3.1" } -} \ No newline at end of file +} diff --git a/packages/contact-center/task/src/Utils/task-util.ts b/packages/contact-center/task/src/Utils/task-util.ts index 3c33514f8..568dd354c 100644 --- a/packages/contact-center/task/src/Utils/task-util.ts +++ b/packages/contact-center/task/src/Utils/task-util.ts @@ -1,4 +1,4 @@ -import {ITask, Interaction} from '@webex/plugin-cc'; +import {ITask} from '@webex/plugin-cc'; /** * This function determines the visibility of various controls based on the task's data. @@ -41,6 +41,7 @@ export function getControlsVisibility(deviceType: string, featureFlags: {[key: s return controls; } +//@ts-expect-error To be fixed in SDK - https://jira-eng-sjc12.cisco.com/jira/browse/CAI-6762 export function findHoldTimestamp(interaction: Interaction, mType = 'mainCall'): number | null { if (!interaction?.media) return null; for (const key in interaction.media) { diff --git a/packages/contact-center/task/src/helper.ts b/packages/contact-center/task/src/helper.ts index e84ca82b4..f354e37ad 100644 --- a/packages/contact-center/task/src/helper.ts +++ b/packages/contact-center/task/src/helper.ts @@ -1,7 +1,6 @@ import {useEffect, useCallback, useState, useRef, useMemo} from 'react'; import {ITask} from '@webex/plugin-cc'; -import {useCallControlProps, UseTaskListProps, UseTaskProps, Participant} from './task.types'; -import {useOutdialCallProps} from '@webex/cc-components'; +import {useCallControlProps, UseTaskListProps, UseTaskProps, Participant, useOutdialCallProps} from './task.types'; import store, {TASK_EVENTS, BuddyDetails, DestinationType, ContactServiceQueue} from '@webex/cc-store'; import {findHoldTimestamp, getControlsVisibility} from './Utils/task-util'; @@ -42,7 +41,7 @@ export const useTaskList = (props: UseTaskListProps) => { } if (onTaskSelected) { - store.setTaskSelected(function (task, isClicked) { + store.setTaskSelected(function (task: ITask, isClicked: boolean) { onTaskSelected({task, isClicked}); }); } @@ -246,13 +245,13 @@ export const useCallControl = (props: useCallControlProps) => { }, [currentTask?.data?.interaction]); // Function to extract consulting agent information const extractConsultingAgent = useCallback(() => { - if (!currentTask || !currentTask.data || !currentTask.data.interaction) return; + if (!currentTask?.data?.interaction?.participants) return; const {interaction} = currentTask.data; const myAgentId = store.cc.agentConfig?.agentId; // Find all agent participants except the current agent - const otherAgents = Object.values(interaction.participants).filter( + const otherAgents = Object.values(interaction.participants || {}).filter( (participant): participant is Participant => (participant as Participant).pType === 'Agent' && (participant as Participant).id !== myAgentId ); @@ -420,7 +419,7 @@ export const useCallControl = (props: useCallControlProps) => { logError(`Error pausing recording: ${error}`, 'toggleRecording'); }); } else { - currentTask.resumeRecording().catch((error: Error) => { + currentTask.resumeRecording({autoResumed: false}).catch((error: Error) => { logError(`Error resuming recording: ${error}`, 'toggleRecording'); }); } @@ -451,6 +450,7 @@ export const useCallControl = (props: useCallControlProps) => { const transferCall = async (to: string, type: DestinationType) => { try { + //@ts-expect-error To be fixed in SDK - https://jira-eng-sjc12.cisco.com/jira/browse/CAI-6762 await currentTask.transfer({to, destinationType: type}); logger.info('transferCall success', {module: 'useCallControl', method: 'transferCall'}); } catch (error) { @@ -472,6 +472,7 @@ export const useCallControl = (props: useCallControlProps) => { } try { + //@ts-expect-error To be fixed in SDK - https://jira-eng-sjc12.cisco.com/jira/browse/CAI-6762 await currentTask.consult(consultPayload); store.setIsQueueConsultInProgress(false); if (destinationType === 'queue') { @@ -498,6 +499,7 @@ export const useCallControl = (props: useCallControlProps) => { }; try { + //@ts-expect-error To be fixed in SDK - https://jira-eng-sjc12.cisco.com/jira/browse/CAI-6762 await currentTask.endConsult(consultEndPayload); } catch (error) { logError(`Error ending consult call: ${error}`, 'endConsultCall'); @@ -511,6 +513,7 @@ export const useCallControl = (props: useCallControlProps) => { destinationType: destinationType, }; try { + //@ts-expect-error To be fixed in SDK - https://jira-eng-sjc12.cisco.com/jira/browse/CAI-6762 await currentTask.consultTransfer(consultTransferPayload); store.setConsultInitiated(true); } catch (error) { @@ -555,6 +558,7 @@ export const useCallControl = (props: useCallControlProps) => { logger.error('CC-Widgets: CallControl: Error initializing auto wrap-up timer', { module: 'widget-cc-task#helper.ts', method: 'useCallControl#autoWrapupTimer', + //@ts-expect-error To be fixed in SDK - https://jira-eng-sjc12.cisco.com/jira/browse/CAI-6762 error, }); } @@ -609,7 +613,7 @@ export const useOutdialCall = (props: useOutdialCallProps) => { alert('Destination number is required, it cannot be empty'); return; } - + //@ts-expect-error To be fixed in SDK - https://jira-eng-sjc12.cisco.com/jira/browse/CAI-6762 cc.startOutdial(destination) .then((response) => { logger.info('Outdial call started', response); diff --git a/packages/contact-center/task/src/task.types.ts b/packages/contact-center/task/src/task.types.ts index f9491e514..745e51a48 100644 --- a/packages/contact-center/task/src/task.types.ts +++ b/packages/contact-center/task/src/task.types.ts @@ -1,30 +1,27 @@ import {TaskProps, ControlProps, OutdialCallProps} from '@webex/cc-components'; -export type UseTaskProps = Pick; -export type UseTaskListProps = Pick< - TaskProps, - 'cc' | 'taskList' | 'deviceType' | 'onTaskAccepted' | 'onTaskDeclined' | 'onTaskSelected' | 'logger' ->; -export type IncomingTaskProps = Pick; -export type TaskListProps = Pick; +export type UseTaskProps = Pick & + Partial>; -export type CallControlProps = Pick< - ControlProps, - 'onHoldResume' | 'onEnd' | 'onWrapUp' | 'onRecordingToggle' | 'callControlClassName' | 'callControlConsultClassName' +export type UseTaskListProps = Pick & + Partial>; + +export type IncomingTaskProps = Pick & Partial>; + +export type TaskListProps = Partial>; + +export type CallControlProps = Partial< + Pick< + ControlProps, + 'onHoldResume' | 'onEnd' | 'onWrapUp' | 'onRecordingToggle' | 'callControlClassName' | 'callControlConsultClassName' + > >; export type useCallControlProps = Pick< ControlProps, - | 'currentTask' - | 'onHoldResume' - | 'onEnd' - | 'onWrapUp' - | 'onRecordingToggle' - | 'logger' - | 'consultInitiated' - | 'deviceType' - | 'featureFlags' ->; + 'currentTask' | 'logger' | 'consultInitiated' | 'deviceType' | 'featureFlags' +> & + Partial>; export type Participant = { id: string; diff --git a/packages/contact-center/task/tests/CallControl/index.tsx b/packages/contact-center/task/tests/CallControl/index.tsx index b7b605aa6..7cd334206 100644 --- a/packages/contact-center/task/tests/CallControl/index.tsx +++ b/packages/contact-center/task/tests/CallControl/index.tsx @@ -3,56 +3,18 @@ import {render} from '@testing-library/react'; import * as helper from '../../src/helper'; import {CallControl} from '../../src'; import store from '@webex/cc-store'; +import {mockTask} from '@webex/test-fixtures'; import '@testing-library/jest-dom'; -// Mock the store -jest.mock('@webex/cc-store', () => ({ - cc: {}, - deviceType: 'BROWSER', - dialNumber: '12345', - wrapupCodes: [], - logger: { - log: jest.fn(), - error: jest.fn(), - warn: jest.fn(), - info: jest.fn(), - }, - currentTask: { - data: {interactionId: 'mockInteractionId'}, - on: jest.fn(), - off: jest.fn(), - hold: jest.fn(() => Promise.resolve()), - resume: jest.fn(() => Promise.resolve()), - pauseRecording: jest.fn(() => Promise.resolve()), - resumeRecording: jest.fn(() => Promise.resolve()), - end: jest.fn(() => Promise.resolve()), - wrapup: jest.fn(() => Promise.resolve()), - }, - setTaskCallback: jest.fn(), - removeTaskCallback: jest.fn(), - TASK_EVENTS: { - TASK_MEDIA: 'task:media', - }, -})); const onHoldResumeCb = jest.fn(); const onEndCb = jest.fn(); const onWrapUpCb = jest.fn(); +const onRecordingToggleCb = jest.fn(); describe('CallControl Component', () => { it('renders CallControlPresentational with correct props', () => { const useCallControlSpy = jest.spyOn(helper, 'useCallControl').mockReturnValue({ - currentTask: { - data: {interactionId: 'mockInteractionId', interaction: {mediaType: 'voice'}}, - on: jest.fn(), - off: jest.fn(), - hold: jest.fn(), - resume: jest.fn(), - pauseRecording: jest.fn(), - resumeRecording: jest.fn(), - end: jest.fn(), - wrapup: jest.fn(), - }, - audioRef: {current: null}, + currentTask: mockTask, endCall: jest.fn(), toggleHold: jest.fn(), toggleRecording: jest.fn(), @@ -63,12 +25,20 @@ describe('CallControl Component', () => { setIsRecording: jest.fn(), buddyAgents: [], loadBuddyAgents: jest.fn(), + queues: [], + loadQueues: jest.fn(), transferCall: jest.fn(), consultCall: jest.fn(), + endConsultCall: jest.fn(), + consultTransfer: jest.fn(), + consultAgentName: 'Consult Agent', + setConsultAgentName: jest.fn(), + consultAgentId: 'mockConsultAgentId', + setConsultAgentId: jest.fn(), holdTime: 0, - consultInitiated: undefined, - featureFlags: undefined, - deviceType: 'BROWSER', + startTimestamp: 0, + lastTargetType: 'agent' as const, + setLastTargetType: jest.fn(), controlVisibility: { accept: false, decline: false, @@ -83,30 +53,30 @@ describe('CallControl Component', () => { endConsult: false, recordingIndicator: false, }, + secondsUntilAutoWrapup: 0, + cancelAutoWrapup: jest.fn(), }); - render(); + render( + + ); // Assert that the useCallControl hook is called with the correct arguments expect(useCallControlSpy).toHaveBeenCalledWith({ - currentTask: { - data: {interactionId: 'mockInteractionId'}, - on: expect.any(Function), - off: expect.any(Function), - hold: expect.any(Function), - resume: expect.any(Function), - pauseRecording: expect.any(Function), - resumeRecording: expect.any(Function), - end: expect.any(Function), - wrapup: expect.any(Function), - }, - onHoldResume: expect.any(Function), - onEnd: expect.any(Function), - onWrapUp: expect.any(Function), + currentTask: null, + onHoldResume: onHoldResumeCb, + onEnd: onEndCb, + onWrapUp: onWrapUpCb, + onRecordingToggle: onRecordingToggleCb, logger: store.logger, - consultInitiated: undefined, - featureFlags: undefined, - deviceType: 'BROWSER', + consultInitiated: false, + featureFlags: store.featureFlags, + deviceType: '', }); }); }); diff --git a/packages/contact-center/task/tests/IncomingTask/index.tsx b/packages/contact-center/task/tests/IncomingTask/index.tsx index 70cbc7ddb..7b0fdc862 100644 --- a/packages/contact-center/task/tests/IncomingTask/index.tsx +++ b/packages/contact-center/task/tests/IncomingTask/index.tsx @@ -3,6 +3,7 @@ import {render} from '@testing-library/react'; import * as helper from '../../src/helper'; import {IncomingTask} from '../../src'; import store from '@webex/cc-store'; +import {mockTask} from '@webex/test-fixtures'; import '@testing-library/jest-dom'; // Mock the store @@ -19,10 +20,6 @@ describe('IncomingTask Component', () => { it('renders IncomingTaskPresentational with correct props', () => { const useIncomingTaskSpy = jest.spyOn(helper, 'useIncomingTask'); - const mockTask = { - data: {interactionId: '12345', interaction: {mediaType: 'voice'}}, - }; - // Mock the return value of the useIncomingTask hook useIncomingTaskSpy.mockReturnValue({ incomingTask: mockTask, diff --git a/packages/contact-center/task/tests/helper.ts b/packages/contact-center/task/tests/helper.ts index f1accc31d..178e46be4 100644 --- a/packages/contact-center/task/tests/helper.ts +++ b/packages/contact-center/task/tests/helper.ts @@ -1,17 +1,14 @@ import {renderHook, act, waitFor} from '@testing-library/react'; import {useIncomingTask, useTaskList, useCallControl, useOutdialCall} from '../src/helper'; import {TASK_EVENTS} from '@webex/cc-store'; +import {mockAgents, mockCC, mockQueueDetails, mockTask} from '@webex/test-fixtures'; import store from '@webex/cc-store'; import React from 'react'; -// Mock webex instance and task -const ccMock = { - on: jest.fn(), - off: jest.fn(), -}; - const taskMock = { + ...mockTask, data: { + ...mockTask.data, interactionId: 'interaction1', }, accept: jest.fn().mockResolvedValue('Accepted'), @@ -31,6 +28,7 @@ const logger = { log: jest.fn(), warn: jest.fn(), info: jest.fn(), + trace: jest.fn(), }; // Override the wrapupCodes property before your tests run @@ -192,7 +190,11 @@ describe('useIncomingTask Hook', () => { onTaskDeclined.mockClear(); const noIdTask = { - data: {}, + ...taskMock, + data: { + ...taskMock.data, + interactionId: undefined, // Simulate no taskId + }, accept: jest.fn(), reject: jest.fn(), on: jest.fn(), @@ -321,7 +323,10 @@ describe('useIncomingTask Hook', () => { }); describe('useTaskList Hook', () => { - const mockTaskList = [taskMock, taskMock]; + const mockTaskList = { + mockId1: taskMock, + mockId2: taskMock, + }; afterEach(() => { jest.clearAllMocks(); logger.error.mockRestore(); @@ -337,7 +342,7 @@ describe('useTaskList Hook', () => { store.onTaskAssigned = callback; }); - renderHook(() => useTaskList({cc: ccMock, deviceType: '', onTaskAccepted, logger, taskList: mockTaskList})); + renderHook(() => useTaskList({cc: mockCC, deviceType: '', onTaskAccepted, logger, taskList: mockTaskList})); // Manually trigger the stored callback with the task act(() => { @@ -353,7 +358,7 @@ describe('useTaskList Hook', () => { it('should return if not task is passed while calling acceptTask', async () => { // This test is purely to improve the coverage report, as the acceptTask function cannot be called without a task const {result} = renderHook(() => - useTaskList({cc: ccMock, deviceType: '', onTaskAccepted, logger, taskList: mockTaskList}) + useTaskList({cc: mockCC, deviceType: '', onTaskAccepted, logger, taskList: mockTaskList}) ); act(() => { @@ -368,7 +373,7 @@ describe('useTaskList Hook', () => { it('should return if not task is passed while calling acceptTask', async () => { // This test is purely to improve the coverage report, as the acceptTask function cannot be called without a task const {result} = renderHook(() => - useTaskList({cc: ccMock, deviceType: '', onTaskDeclined, logger, taskList: mockTaskList}) + useTaskList({cc: mockCC, deviceType: '', onTaskDeclined, logger, taskList: mockTaskList}) ); act(() => { @@ -390,7 +395,7 @@ describe('useTaskList Hook', () => { store.onTaskRejected = callback; }); - renderHook(() => useTaskList({cc: ccMock, deviceType: '', onTaskDeclined, logger, taskList: mockTaskList})); + renderHook(() => useTaskList({cc: mockCC, deviceType: '', onTaskDeclined, logger, taskList: mockTaskList})); // Manually trigger the stored callback with the task act(() => { @@ -413,7 +418,7 @@ describe('useTaskList Hook', () => { store.onTaskSelected = callback; }); - renderHook(() => useTaskList({cc: ccMock, deviceType: '', onTaskSelected, logger, taskList: mockTaskList})); + renderHook(() => useTaskList({cc: mockCC, deviceType: '', onTaskSelected, logger, taskList: mockTaskList})); // Manually trigger the stored callback with the task act(() => { @@ -434,7 +439,7 @@ describe('useTaskList Hook', () => { }; const {result} = renderHook(() => - useTaskList({cc: ccMock, onTaskAccepted, deviceType: 'BROWSER', logger, taskList: mockTaskList}) + useTaskList({cc: mockCC, onTaskAccepted, deviceType: 'BROWSER', logger, taskList: mockTaskList}) ); act(() => { @@ -461,7 +466,7 @@ describe('useTaskList Hook', () => { }; const {result} = renderHook(() => - useTaskList({cc: ccMock, onTaskDeclined, deviceType: 'BROWSER', logger, taskList: mockTaskList}) + useTaskList({cc: mockCC, onTaskDeclined, deviceType: 'BROWSER', logger, taskList: mockTaskList}) ); act(() => { @@ -483,7 +488,7 @@ describe('useTaskList Hook', () => { it('should not call onTaskAccepted if it is not provided', async () => { const {result} = renderHook(() => useTaskList({ - cc: ccMock, + cc: mockCC, logger, deviceType: 'BROWSER', taskList: mockTaskList, @@ -505,7 +510,7 @@ describe('useTaskList Hook', () => { it('should not call onTaskDeclined if it is not provided', async () => { const {result} = renderHook(() => useTaskList({ - cc: ccMock, + cc: mockCC, logger, deviceType: '', taskList: mockTaskList, @@ -529,7 +534,9 @@ describe('useCallControl', () => { let originalWorker: typeof Worker; const mockCurrentTask = { + ...mockTask, data: { + ...mockTask.data, interactionId: 'someMockInteractionId', }, on: jest.fn(), @@ -541,6 +548,9 @@ describe('useCallControl', () => { end: jest.fn(() => Promise.resolve()), wrapup: jest.fn(() => Promise.resolve()), cancelAutoWrapupTimer: jest.fn(), + consultTransfer: jest.fn(() => Promise.resolve()), + consult: jest.fn(() => Promise.resolve()), + endConsult: jest.fn(() => Promise.resolve()), }; const mockLogger = { @@ -548,6 +558,7 @@ describe('useCallControl', () => { info: jest.fn(), log: jest.fn(), warn: jest.fn(), + trace: jest.fn(), }; const mockOnHoldResume = jest.fn(); @@ -608,6 +619,7 @@ describe('useCallControl', () => { logger: mockLogger, featureFlags: store.featureFlags, deviceType: store.deviceType, + consultInitiated: false, }) ); @@ -632,6 +644,7 @@ describe('useCallControl', () => { onWrapUp: jest.fn(), featureFlags: store.featureFlags, deviceType: store.deviceType, + consultInitiated: false, }) ); @@ -666,6 +679,7 @@ describe('useCallControl', () => { logger: mockLogger, featureFlags: store.featureFlags, deviceType: store.deviceType, + consultInitiated: false, }) ); @@ -688,6 +702,7 @@ describe('useCallControl', () => { logger: mockLogger, featureFlags: store.featureFlags, deviceType: store.deviceType, + consultInitiated: false, }) ); @@ -712,6 +727,7 @@ describe('useCallControl', () => { logger: mockLogger, featureFlags: store.featureFlags, deviceType: store.deviceType, + consultInitiated: false, }) ); @@ -735,6 +751,7 @@ describe('useCallControl', () => { logger: mockLogger, featureFlags: store.featureFlags, deviceType: store.deviceType, + consultInitiated: false, }) ); @@ -756,6 +773,7 @@ describe('useCallControl', () => { logger: mockLogger, featureFlags: store.featureFlags, deviceType: store.deviceType, + consultInitiated: false, }) ); @@ -779,6 +797,7 @@ describe('useCallControl', () => { logger: mockLogger, featureFlags: store.featureFlags, deviceType: store.deviceType, + consultInitiated: false, }) ); @@ -809,6 +828,7 @@ describe('useCallControl', () => { logger: mockLogger, featureFlags: store.featureFlags, deviceType: store.deviceType, + consultInitiated: false, }) ); @@ -843,6 +863,7 @@ describe('useCallControl', () => { logger: mockLogger, featureFlags: store.featureFlags, deviceType: store.deviceType, + consultInitiated: false, }) ); @@ -863,6 +884,7 @@ describe('useCallControl', () => { logger: mockLogger, featureFlags: store.featureFlags, deviceType: store.deviceType, + consultInitiated: false, }) ); await waitFor(() => { @@ -887,6 +909,7 @@ describe('useCallControl', () => { logger: mockLogger, featureFlags: store.featureFlags, deviceType: store.deviceType, + consultInitiated: false, }) ); @@ -912,6 +935,7 @@ describe('useCallControl', () => { logger: mockLogger, featureFlags: store.featureFlags, deviceType: store.deviceType, + consultInitiated: false, }) ); @@ -924,7 +948,7 @@ describe('useCallControl', () => { mockCurrentTask.on.mock.calls.find((call) => call[0] === TASK_EVENTS.CONTACT_RECORDING_RESUMED)?.[1](); }); - expect(mockCurrentTask.resumeRecording).toHaveBeenCalledWith(); + expect(mockCurrentTask.resumeRecording).toHaveBeenCalledWith({autoResumed: false}); }); it('should fail and log if resume failed', async () => { @@ -938,6 +962,7 @@ describe('useCallControl', () => { logger: mockLogger, featureFlags: store.featureFlags, deviceType: store.deviceType, + consultInitiated: false, }) ); await waitFor(() => { @@ -948,7 +973,7 @@ describe('useCallControl', () => { await result.current.toggleRecording(); }); - expect(mockCurrentTask.resumeRecording).toHaveBeenCalledWith(); + expect(mockCurrentTask.resumeRecording).toHaveBeenCalledWith({autoResumed: false}); expect(mockLogger.error).toHaveBeenCalledWith('Error resuming recording: Error: Resume error', expect.any(Object)); }); @@ -962,6 +987,7 @@ describe('useCallControl', () => { logger: mockLogger, featureFlags: store.featureFlags, deviceType: store.deviceType, + consultInitiated: false, }) ); // Ensure no event handler is set @@ -981,6 +1007,7 @@ describe('useCallControl', () => { logger: mockLogger, featureFlags: store.featureFlags, deviceType: store.deviceType, + consultInitiated: false, }) ); // Ensure no event handler is set @@ -988,11 +1015,7 @@ describe('useCallControl', () => { }); it('should load buddy agents successfully', async () => { - const dummyAgents = [ - {id: 'a1', name: 'Agent1'}, - {id: 'a2', name: 'Agent2'}, - ]; - const getBuddyAgentsSpy = jest.spyOn(store, 'getBuddyAgents').mockResolvedValue(dummyAgents); + const getBuddyAgentsSpy = jest.spyOn(store, 'getBuddyAgents').mockResolvedValue(mockAgents); const {result} = renderHook(() => useCallControl({ currentTask: mockCurrentTask, @@ -1002,12 +1025,13 @@ describe('useCallControl', () => { logger: mockLogger, featureFlags: store.featureFlags, deviceType: store.deviceType, + consultInitiated: false, }) ); await act(async () => { await result.current.loadBuddyAgents(); }); - expect(result.current.buddyAgents).toEqual(dummyAgents); + expect(result.current.buddyAgents).toEqual(mockAgents); getBuddyAgentsSpy.mockRestore(); }); @@ -1023,6 +1047,7 @@ describe('useCallControl', () => { logger: mockLogger, featureFlags: store.featureFlags, deviceType: store.deviceType, + consultInitiated: false, }) ); await act(async () => { @@ -1047,6 +1072,7 @@ describe('useCallControl', () => { logger: mockLogger, featureFlags: store.featureFlags, deviceType: store.deviceType, + consultInitiated: false, }) ); await act(async () => { @@ -1073,6 +1099,7 @@ describe('useCallControl', () => { logger: mockLogger, featureFlags: store.featureFlags, deviceType: store.deviceType, + consultInitiated: false, }) ); @@ -1096,6 +1123,7 @@ describe('useCallControl', () => { logger: mockLogger, featureFlags: store.featureFlags, deviceType: store.deviceType, + consultInitiated: false, }) ); await act(async () => { @@ -1118,6 +1146,7 @@ describe('useCallControl', () => { logger: mockLogger, featureFlags: store.featureFlags, deviceType: store.deviceType, + consultInitiated: false, }) ); @@ -1140,6 +1169,7 @@ describe('useCallControl', () => { logger: mockLogger, featureFlags: store.featureFlags, deviceType: store.deviceType, + consultInitiated: true, }) ); await act(async () => { @@ -1162,6 +1192,7 @@ describe('useCallControl', () => { logger: mockLogger, featureFlags: store.featureFlags, deviceType: store.deviceType, + consultInitiated: false, }) ); @@ -1180,6 +1211,7 @@ describe('useCallControl', () => { logger: mockLogger, featureFlags: store.featureFlags, deviceType: store.deviceType, + consultInitiated: true, }) ); @@ -1206,6 +1238,7 @@ describe('useCallControl', () => { logger: mockLogger, featureFlags: store.featureFlags, deviceType: store.deviceType, + consultInitiated: true, }) ); await act(async () => { @@ -1231,6 +1264,7 @@ describe('useCallControl', () => { logger: mockLogger, featureFlags: store.featureFlags, deviceType: store.deviceType, + consultInitiated: false, }) ); @@ -1248,7 +1282,9 @@ describe('useCallControl', () => { it('should extract consulting agent information correctly when initiating consult', async () => { // Mock store.cc.agentConfig.agentId for comparison const mockStoreCC = { + ...mockCC, agentConfig: { + ...mockCC.agentConfig, agentId: 'currentAgentId', }, }; @@ -1258,8 +1294,10 @@ describe('useCallControl', () => { const taskWithParticipants = { ...mockCurrentTask, data: { + ...mockCurrentTask.data, interactionId: 'someMockInteractionId', interaction: { + ...mockCurrentTask.data.interaction, participants: { currentAgentId: { id: 'currentAgentId', @@ -1311,7 +1349,9 @@ describe('useCallControl', () => { it('should extract consulting agent information correctly when receiving consult', async () => { // Mock store.cc.agentConfig.agentId for comparison const mockStoreCC = { + ...mockCC, agentConfig: { + ...mockCC.agentConfig, agentId: 'currentAgentId', }, }; @@ -1321,8 +1361,10 @@ describe('useCallControl', () => { const taskWithParticipants = { ...mockCurrentTask, data: { + ...mockCurrentTask.data, interactionId: 'someMockInteractionId', interaction: { + ...mockCurrentTask.data.interaction, participants: { currentAgentId: { id: 'currentAgentId', @@ -1374,7 +1416,9 @@ describe('useCallControl', () => { it('should not update consultAgentName when no consulting agent is found', async () => { // Mock store.cc.agentConfig.agentId for comparison const mockStoreCC = { + ...mockCC, agentConfig: { + ...mockCC.agentConfig, agentId: 'currentAgentId', }, }; @@ -1384,8 +1428,10 @@ describe('useCallControl', () => { const taskWithoutConsultAgent = { ...mockCurrentTask, data: { + ...mockTask.data, interactionId: 'someMockInteractionId', interaction: { + ...mockTask.data.interaction, participants: { currentAgentId: { id: 'currentAgentId', @@ -1431,6 +1477,7 @@ describe('useCallControl', () => { const taskWithNoInteraction = { ...mockCurrentTask, data: { + ...mockCurrentTask.data, interactionId: 'someMockInteractionId', // No interaction property }, @@ -1445,6 +1492,7 @@ describe('useCallControl', () => { logger: mockLogger, featureFlags: store.featureFlags, deviceType: store.deviceType, + consultInitiated: false, }); // Set initial value return hook; @@ -1464,6 +1512,7 @@ describe('useCallControl', () => { logger: mockLogger, featureFlags: store.featureFlags, deviceType: store.deviceType, + consultInitiated: false, }) ); @@ -1480,13 +1529,18 @@ describe('useCallControl', () => { data: { ...mockCurrentTask.data, interaction: { + ...mockCurrentTask.data.interaction, media: { someMockInteractionId: { mType: 'mainCall', - holdTimestamp, + mediaResourceId: 'some-resource-id', + mediaType: 'telephony', // or one of: email, chat, telephony, social, sms, facebook, whatsapp + mediaMgr: 'some-media-manager', + participants: [], + isHold: false, + holdTimestamp: holdTimestamp, }, }, - participants: {}, // <-- Add this line }, }, }; @@ -1507,6 +1561,7 @@ describe('useCallControl', () => { logger: mockLogger, featureFlags: store.featureFlags, deviceType: store.deviceType, + consultInitiated: false, }) ); @@ -1523,13 +1578,19 @@ describe('useCallControl', () => { data: { ...mockCurrentTask.data, interaction: { + ...mockCurrentTask.data.interaction, media: { - main: { - mType: 'mainCall', + someMockInteractionId: { + mType: 'call', + mediaResourceId: 'some-resource-id', + mediaType: 'telephony', // or one of: email, chat, telephony, social, sms, facebook, whatsapp + mediaMgr: 'some-media-manager', + participants: [], + isHold: false, // No holdTimestamp + holdTimestamp: undefined, }, }, - participants: {}, }, }, }; @@ -1550,6 +1611,7 @@ describe('useCallControl', () => { logger: mockLogger, featureFlags: store.featureFlags, deviceType: store.deviceType, + consultInitiated: false, }) ); @@ -1566,10 +1628,16 @@ describe('useCallControl', () => { data: { ...mockCurrentTask.data, interaction: { + ...mockCurrentTask.data.interaction, media: { main: { mType: 'mainCall', holdTimestamp, + mediaResourceId: 'some-resource-id', + mediaMgr: 'some-media-manager', + participants: [], + isHold: false, + mediaType: 'telephony', // or one of: email, chat, telephony, social, sms, facebook, whatsapp }, }, participants: {}, @@ -1587,6 +1655,7 @@ describe('useCallControl', () => { logger: mockLogger, featureFlags: store.featureFlags, deviceType: store.deviceType, + consultInitiated: false, }), {initialProps: {task: mockTaskWithHold}} ); @@ -1597,10 +1666,17 @@ describe('useCallControl', () => { data: { ...mockCurrentTask.data, interaction: { + ...mockCurrentTask.data.interaction, media: { main: { - mType: 'mainCall', - // holdTimestamp removed + mType: 'call', + mediaResourceId: 'some-resource-id', + mediaType: 'telephony', // or one of: email, chat, telephony, social, sms, facebook, whatsapp + mediaMgr: 'some-media-manager', + participants: [], + isHold: false, + // holdTimestamp undefined + holdTimestamp: undefined, }, }, participants: {}, @@ -1621,8 +1697,14 @@ describe('useCallControl', () => { data: { ...mockCurrentTask.data, interaction: { + ...mockCurrentTask.data.interaction, media: { main: { + mediaResourceId: 'some-resource-id', + mediaType: 'telephony', // or one of: email, chat, telephony, social, sms, facebook, whatsapp + mediaMgr: 'some-media-manager', + participants: [], + isHold: false, mType: 'mainCall', holdTimestamp, }, @@ -1633,6 +1715,7 @@ describe('useCallControl', () => { }; let setHoldTimeValue = 0; + // @ts-expect-error Mock useState to capture the holdTime value jest.spyOn(React, 'useState').mockImplementation((init) => [init, (v) => (setHoldTimeValue = v)]); renderHook(() => @@ -1644,6 +1727,7 @@ describe('useCallControl', () => { logger: mockLogger, featureFlags: store.featureFlags, deviceType: store.deviceType, + consultInitiated: false, }) ); @@ -1666,8 +1750,14 @@ describe('useCallControl', () => { data: { ...mockCurrentTask.data, interaction: { + ...mockCurrentTask.data.interaction, media: { someMockInteractionId: { + mediaResourceId: 'some-resource-id', + mediaType: 'telephony', // or one of: email, chat, telephony, social, sms, facebook, whatsapp + mediaMgr: 'some-media-manager', + participants: [], + isHold: false, mType: 'mainCall', holdTimestamp, }, @@ -1694,6 +1784,7 @@ describe('useCallControl', () => { logger: mockLogger, featureFlags: store.featureFlags, deviceType: store.deviceType, + consultInitiated: false, }) ); @@ -1721,10 +1812,16 @@ describe('useCallControl', () => { data: { ...mockCurrentTask.data, interaction: { + ...mockCurrentTask.data.interaction, media: { someMockInteractionId: { mType: 'mainCall', holdTimestamp, + isHold: false, + mediaResourceId: 'some-resource-id', + mediaType: 'telephony', // or one of: email, chat, telephony, social, sms, facebook, whatsapp + mediaMgr: 'some-media-manager', + participants: [], }, }, participants: {}, @@ -1747,6 +1844,7 @@ describe('useCallControl', () => { logger: mockLogger, featureFlags: store.featureFlags, deviceType: store.deviceType, + consultInitiated: false, }) ); @@ -1771,6 +1869,7 @@ describe('useCallControl', () => { logger: mockLogger, featureFlags: store.featureFlags, deviceType: store.deviceType, + consultInitiated: false, }) ); @@ -1800,7 +1899,7 @@ describe('useCallControl', () => { logger: mockLogger, featureFlags: store.featureFlags, deviceType: store.deviceType, - logger: logger, + consultInitiated: true, }) ); @@ -1816,11 +1915,7 @@ describe('useCallControl', () => { }); it('should load queues successfully', async () => { - const dummyQueues = [ - {id: 'q1', name: 'Queue1'}, - {id: 'q2', name: 'Queue2'}, - ]; - const getQueuesSpy = jest.spyOn(store, 'getQueues').mockResolvedValue(dummyQueues); + const getQueuesSpy = jest.spyOn(store, 'getQueues').mockResolvedValue(mockQueueDetails); const {result} = renderHook(() => useCallControl({ @@ -1828,6 +1923,7 @@ describe('useCallControl', () => { logger: mockLogger, featureFlags: store.featureFlags, deviceType: store.deviceType, + consultInitiated: false, }) ); @@ -1835,7 +1931,7 @@ describe('useCallControl', () => { await result.current.loadQueues(); }); - expect(result.current.queues).toEqual(dummyQueues); + expect(result.current.queues).toEqual(mockQueueDetails); getQueuesSpy.mockRestore(); }); @@ -1870,12 +1966,17 @@ describe('useCallControl', () => { describe('useOutdialCall', () => { const ccMock = { + ...mockCC, startOutdial: jest.fn().mockResolvedValue('Success'), }; const logger = { info: jest.fn(), error: jest.fn(), + trace: jest.fn(), + debug: jest.fn(), + warn: jest.fn(), + log: jest.fn(), }; const destination = '123456789'; @@ -1923,13 +2024,14 @@ describe('useOutdialCall', () => { }); it('should handle errors when starting outdial call fails', async () => { - const errorCcMock = { + const errormockCC = { + ...mockCC, startOutdial: jest.fn().mockRejectedValue(new Error('Outdial call failed')), }; const {result} = renderHook(() => useOutdialCall({ - cc: errorCcMock, + cc: errormockCC, logger, }) ); @@ -1938,7 +2040,7 @@ describe('useOutdialCall', () => { await result.current.startOutdial(destination); }); - expect(errorCcMock.startOutdial).toHaveBeenCalledWith(destination); + expect(errormockCC.startOutdial).toHaveBeenCalledWith(destination); expect(logger.error).toHaveBeenCalledWith('Error: Outdial call failed', { module: 'widget-OutdialCall#helper.ts', method: 'startOutdial', diff --git a/packages/contact-center/task/tests/utils/task-util.ts b/packages/contact-center/task/tests/utils/task-util.ts index dd58967ed..ac4b9375c 100644 --- a/packages/contact-center/task/tests/utils/task-util.ts +++ b/packages/contact-center/task/tests/utils/task-util.ts @@ -1,3 +1,4 @@ +import {mockTask} from '@webex/test-fixtures'; import {findHoldTimestamp, getControlsVisibility} from '../../src/Utils/task-util'; describe('getControlsVisibility', () => { it('should show correct controls when station logis is BROWSER, all flags are enabled and media type is telehphony', () => { @@ -7,15 +8,7 @@ describe('getControlsVisibility', () => { isEndConsultEnabled: true, webRtcEnabled: true, }; - const task = { - data: { - interaction: { - mediaType: 'telephony', - }, - wrapUpRequired: true, - }, - }; - + // Updating const expectedControls = { accept: true, decline: true, @@ -25,13 +18,13 @@ describe('getControlsVisibility', () => { consult: true, transfer: true, conference: true, - wrapup: true, + wrapup: false, pauseResumeRecording: true, endConsult: true, recordingIndicator: true, }; - expect(getControlsVisibility(deviceType, featureFlags, task)).toEqual(expectedControls); + expect(getControlsVisibility(deviceType, featureFlags, mockTask)).toEqual(expectedControls); }); it('should show correct controls when station logis is BROWSER, webRtcEnabled is disbaled and media type is telehphony', () => { @@ -41,14 +34,6 @@ describe('getControlsVisibility', () => { isEndConsultEnabled: true, webRtcEnabled: false, }; - const task = { - data: { - interaction: { - mediaType: 'telephony', - }, - wrapUpRequired: true, - }, - }; const expectedControls = { accept: false, @@ -59,13 +44,13 @@ describe('getControlsVisibility', () => { consult: false, transfer: false, conference: false, - wrapup: true, + wrapup: false, pauseResumeRecording: false, endConsult: false, recordingIndicator: true, }; - expect(getControlsVisibility(deviceType, featureFlags, task)).toEqual(expectedControls); + expect(getControlsVisibility(deviceType, featureFlags, mockTask)).toEqual(expectedControls); }); it('should show correct controls when station logis is BROWSER, isEndCallEnabled is disbaled and media type is telehphony', () => { @@ -75,14 +60,6 @@ describe('getControlsVisibility', () => { isEndConsultEnabled: true, webRtcEnabled: true, }; - const task = { - data: { - interaction: { - mediaType: 'telephony', - }, - wrapUpRequired: true, - }, - }; const expectedControls = { accept: true, @@ -93,13 +70,13 @@ describe('getControlsVisibility', () => { consult: true, transfer: true, conference: true, - wrapup: true, + wrapup: false, pauseResumeRecording: true, endConsult: true, recordingIndicator: true, }; - expect(getControlsVisibility(deviceType, featureFlags, task)).toEqual(expectedControls); + expect(getControlsVisibility(deviceType, featureFlags, mockTask)).toEqual(expectedControls); }); it('should show correct controls when station logis is BROWSER, isEndConsultEnabled is disbaled and media type is telehphony', () => { @@ -109,13 +86,11 @@ describe('getControlsVisibility', () => { isEndConsultEnabled: false, webRtcEnabled: true, }; - const task = { - data: { - interaction: { - mediaType: 'telephony', - }, - wrapUpRequired: true, - }, + + const task = mockTask; + task.data.interaction = { + ...task.data.interaction, + mediaType: 'telephony', }; const expectedControls = { @@ -127,7 +102,7 @@ describe('getControlsVisibility', () => { consult: true, transfer: true, conference: true, - wrapup: true, + wrapup: false, pauseResumeRecording: true, endConsult: false, recordingIndicator: true, @@ -143,14 +118,6 @@ describe('getControlsVisibility', () => { isEndConsultEnabled: true, webRtcEnabled: true, }; - const task = { - data: { - interaction: { - mediaType: 'telephony', - }, - wrapUpRequired: true, - }, - }; const expectedControls = { accept: false, @@ -161,13 +128,13 @@ describe('getControlsVisibility', () => { consult: true, transfer: true, conference: false, - wrapup: true, + wrapup: false, pauseResumeRecording: true, endConsult: true, recordingIndicator: true, }; - expect(getControlsVisibility(deviceType, featureFlags, task)).toEqual(expectedControls); + expect(getControlsVisibility(deviceType, featureFlags, mockTask)).toEqual(expectedControls); }); it('should show correct controls when station logis is EXTENSION, all flags are enabled and media type is telehphony', () => { @@ -177,14 +144,9 @@ describe('getControlsVisibility', () => { isEndConsultEnabled: true, webRtcEnabled: true, }; - const task = { - data: { - interaction: { - mediaType: 'telephony', - }, - wrapUpRequired: true, - }, - }; + + const task = mockTask; + task.data.interaction.mediaType = 'telephony'; const expectedControls = { accept: false, @@ -195,7 +157,7 @@ describe('getControlsVisibility', () => { consult: true, transfer: true, conference: false, - wrapup: true, + wrapup: false, pauseResumeRecording: true, endConsult: true, recordingIndicator: true, @@ -211,14 +173,9 @@ describe('getControlsVisibility', () => { isEndConsultEnabled: true, webRtcEnabled: true, }; - const task = { - data: { - interaction: { - mediaType: 'chat', - }, - wrapUpRequired: true, - }, - }; + + const task = mockTask; + task.data.interaction.mediaType = 'chat'; const expectedControls = { accept: true, @@ -229,7 +186,7 @@ describe('getControlsVisibility', () => { consult: false, transfer: true, conference: true, - wrapup: true, + wrapup: false, pauseResumeRecording: false, endConsult: false, recordingIndicator: false, @@ -245,13 +202,9 @@ describe('getControlsVisibility', () => { isEndConsultEnabled: true, webRtcEnabled: true, }; - const task = { - data: { - interaction: { - mediaType: 'email', - }, - }, - }; + + const task = mockTask; + task.data.interaction.mediaType = 'email'; const expectedControls = { accept: true, diff --git a/packages/contact-center/task/tsconfig.json b/packages/contact-center/task/tsconfig.json index 3b81144dc..725e78fea 100644 --- a/packages/contact-center/task/tsconfig.json +++ b/packages/contact-center/task/tsconfig.json @@ -1,7 +1,7 @@ { "extends": "../../../tsconfig.json", "include": [ - "./src" + "./src", ], "compilerOptions": { "outDir": "./dist", diff --git a/packages/contact-center/task/tsconfig.test.json b/packages/contact-center/task/tsconfig.test.json new file mode 100644 index 000000000..40bd84f5a --- /dev/null +++ b/packages/contact-center/task/tsconfig.test.json @@ -0,0 +1,9 @@ +// This config is to do type checking in our files while running tests. +{ + "extends": "./tsconfig.json", + "include": ["./tests"], + "exclude": ["node_modules"], + "compilerOptions": { + "noEmit": true // Don't output any files + } +} \ No newline at end of file diff --git a/packages/contact-center/test-fixtures/bable.config.js b/packages/contact-center/test-fixtures/bable.config.js new file mode 100644 index 000000000..0eaef236c --- /dev/null +++ b/packages/contact-center/test-fixtures/bable.config.js @@ -0,0 +1,3 @@ +const baseConfig = require('../../../babel.config.js'); + +module.exports = baseConfig; diff --git a/packages/contact-center/test-fixtures/eslint.config.mjs b/packages/contact-center/test-fixtures/eslint.config.mjs new file mode 100644 index 000000000..ec394673b --- /dev/null +++ b/packages/contact-center/test-fixtures/eslint.config.mjs @@ -0,0 +1,20 @@ +import globals from 'globals'; +import pluginJs from '@eslint/js'; +import tseslint from 'typescript-eslint'; +import pluginReact from 'eslint-plugin-react'; +import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'; +import eslintConfigPrettier from 'eslint-config-prettier'; + +export default [ + {files: ['**/src/**/*.{js,mjs,cjs,ts,jsx,tsx}']}, + {ignores: ['.babelrc.js', '*config.{js,ts}', 'dist', 'node_modules', 'coverage']}, + {languageOptions: {globals: globals.browser}}, + pluginJs.configs.recommended, + ...tseslint.configs.recommended, + { + ...pluginReact.configs.flat.recommended, + settings: {react: {version: 'detect'}}, + }, + eslintPluginPrettierRecommended, + eslintConfigPrettier, +]; diff --git a/packages/contact-center/test-fixtures/package.json b/packages/contact-center/test-fixtures/package.json new file mode 100644 index 000000000..f1bac5717 --- /dev/null +++ b/packages/contact-center/test-fixtures/package.json @@ -0,0 +1,50 @@ +{ + "name": "@webex/test-fixtures", + "description": "Webex Contact Center Widgets: Widgets Store", + "main": "dist/index.js", + "types": "dist/types/index.d.ts", + "license": "MIT", + "publishConfig": { + "access": "public" + }, + "files": [ + "dist/", + "package.json" + ], + "scripts": { + "clean": "rm -rf dist && rm -rf node_modules", + "clean:dist": "rm -rf dist", + "build": "yarn run -T tsc", + "build:src": "yarn run clean:dist && webpack", + "build:watch": "webpack --watch", + "test:styles": "eslint" + }, + "dependencies": { + "@webex/cc-store": "workspace:*", + "typescript": "5.6.3" + }, + "devDependencies": { + "@babel/core": "7.25.2", + "@babel/preset-env": "7.25.4", + "@babel/preset-react": "7.24.7", + "@babel/preset-typescript": "7.25.9", + "@eslint/js": "^9.20.0", + "babel-loader": "9.2.1", + "eslint": "^9.20.1", + "eslint-config-prettier": "^10.0.1", + "eslint-config-standard": "^17.1.0", + "eslint-plugin-import": "^2.25.2", + "eslint-plugin-n": "^15.0.0 || ^16.0.0 ", + "eslint-plugin-prettier": "^5.2.3", + "eslint-plugin-promise": "^6.0.0", + "eslint-plugin-react": "^7.37.4", + "file-loader": "6.2.0", + "globals": "^16.0.0", + "prettier": "^3.5.1", + "ts-loader": "9.5.1", + "typescript-eslint": "^8.24.1", + "webpack": "5.94.0", + "webpack-cli": "5.1.4", + "webpack-merge": "6.0.1" + } +} diff --git a/packages/contact-center/test-fixtures/src/fixtures.ts b/packages/contact-center/test-fixtures/src/fixtures.ts new file mode 100644 index 000000000..9c39f6531 --- /dev/null +++ b/packages/contact-center/test-fixtures/src/fixtures.ts @@ -0,0 +1,373 @@ +import {ITask, Profile, TaskData, TaskResponse} from '@webex/plugin-cc'; +import {IContactCenter} from '@webex/cc-store'; + +const mockProfile: Profile = { + teams: [{teamId: 'team1', teamName: 'Team 1'}], + defaultDn: '12345', + dn: '12345', + forceDefaultDn: false, + forceDefaultDnForAgent: false, + regexUS: '', + regexOther: '', + agentId: 'agent1', + agentName: 'John Doe', + agentMailId: 'agent@example.com', + agentProfileID: 'profile123', + dialPlan: { + type: 'adhocDial', + dialPlanEntity: [], + }, + multimediaProfileId: 'mm123', + skillProfileId: 'skill123', + siteId: 'site123', + enterpriseId: 'enterprise123', + privacyShieldVisible: false, + idleCodes: [{id: 'code1', name: 'Code 1', isSystem: false, isDefault: false}], + idleCodesList: [], + idleCodesAccess: 'ALL', + wrapupCodes: [{id: 'wrap1', name: 'Wrap Code 1', isSystem: false, isDefault: false}], + defaultWrapupCode: 'wrap1', + wrapUpData: { + wrapUpProps: { + autoWrapup: false, + autoWrapupInterval: 0, + lastAgentRoute: false, + wrapUpReasonList: [], + wrapUpCodesList: [], + idleCodesAccess: 'ALL', + allowCancelAutoWrapup: false, + }, + }, + orgId: 'org1', + isOutboundEnabledForTenant: false, + isOutboundEnabledForAgent: false, + isAdhocDialingEnabled: true, + isAgentAvailableAfterOutdial: false, + isCampaignManagementEnabled: true, + outDialEp: '', + isEndCallEnabled: true, + isEndConsultEnabled: true, + agentDbId: 'agentDb123', + allowConsultToQueue: true, + agentPersonalStatsEnabled: true, + isTimeoutDesktopInactivityEnabled: false, + timeoutDesktopInactivityMins: 30, + loginVoiceOptions: ['BROWSER'], + deviceType: 'BROWSER', + currentTeamId: 'team1', + webRtcEnabled: true, + lostConnectionRecoveryTimeout: 30000, + maskSensitiveData: false, + isAgentLoggedIn: true, + lastStateAuxCodeId: 'auxCodeId', + lastStateChangeTimestamp: 123456789, + lastIdleCodeChangeTimestamp: 123456789, +}; + +const mockCC: IContactCenter = { + on: jest.fn(), + off: jest.fn(), + updateAgentProfile: jest.fn(), + stationLogin: jest.fn(), + deregister: jest.fn(), + stationLogout: jest.fn(), + LoggerProxy: { + log: jest.fn(), + error: jest.fn(), + warn: jest.fn(), + info: jest.fn(), + trace: jest.fn(), + }, + register: jest.fn(), + taskManager: { + getAllTasks: jest.fn().mockReturnValue({}), + }, + getBuddyAgents: jest.fn().mockResolvedValue([]), + getQueues: jest.fn().mockResolvedValue([]), + setAgentState: jest.fn().mockResolvedValue({}), +}; + +const mockTask: ITask = { + data: {interaction: {mediaType: 'telephony'}} as TaskData, + webCallMap: {}, + autoWrapup: undefined, + on: jest.fn(), + off: jest.fn(), + emit: jest.fn(), + addListener: jest.fn(), + once: jest.fn(), + removeListener: jest.fn(), + removeAllListeners: jest.fn(), + setMaxListeners: jest.fn(), + getMaxListeners: jest.fn().mockReturnValue(10), + listeners: jest.fn().mockReturnValue([]), + rawListeners: jest.fn().mockReturnValue([]), + listenerCount: jest.fn().mockReturnValue(0), + prependListener: jest.fn(), + prependOnceListener: jest.fn(), + eventNames: jest.fn().mockReturnValue([]), + cancelAutoWrapupTimer: jest.fn(), + unregisterWebCallListeners: jest.fn(), + updateTaskData: jest.fn().mockReturnValue({} as ITask), + accept: jest.fn().mockResolvedValue({} as TaskResponse), + decline: jest.fn().mockResolvedValue({} as TaskResponse), + hold: jest.fn().mockResolvedValue({} as TaskResponse), + resume: jest.fn().mockResolvedValue({} as TaskResponse), + end: jest.fn().mockResolvedValue({} as TaskResponse), + wrapup: jest.fn().mockResolvedValue({} as TaskResponse), + pauseRecording: jest.fn().mockResolvedValue({} as TaskResponse), + resumeRecording: jest.fn().mockResolvedValue({} as TaskResponse), + //@ts-expect-error To be fixed in SDK - https://jira-eng-sjc12.cisco.com/jira/browse/CAI-6762 + consult: jest.fn().mockResolvedValue({} as TaskResponse), + transfer: jest.fn().mockResolvedValue({} as TaskResponse), + consultTransfer: jest.fn().mockResolvedValue({} as TaskResponse), + endConsult: jest.fn().mockResolvedValue({} as TaskResponse), +}; + +const mockQueueDetails = [ + { + id: 'q1', + name: 'Queue1', + description: 'Test Queue 1', + queueType: 'inbound', + checkAgentAvailability: true, + channelType: 'telephony', + skillProfileId: 'skill1', + siteId: 'site1', + version: 1, + isActive: true, + maxWaitTime: 300, + serviceLevelThreshold: 20, + serviceLevelType: 'percentage', + maxHandlingTime: 600, + abandonedThreshold: 30, + distributionPolicy: 'longest_idle', + routingPolicy: 'skill_based', + wrapUpTime: 60, + autoAnswer: false, + recordingPolicy: 'always', + monitoringPolicy: 'supervisor', + scriptId: 'script1', + surveyId: 'survey1', + outdialANI: '1234567890', + outdialMethod: 'progressive', + dialMode: 'preview', + maxAttempts: 3, + dialingOrder: 'priority', + timeZoneHandling: 'agent', + callbackOption: true, + priority: 1, + teamId: 'team1', + maxActiveContacts: 10, + maxTimeInQueue: 600, + defaultMusicInQueueMediaFileId: 'music1', + timezone: 'UTC', + allowedWaitTimeMusicFiles: ['music1'], + musicInQueueFileType: 'mp3', + outdialRetryInterval: 300, + maxQueueSize: 100, + enableCallback: true, + enableBusyRedial: false, + busyRedialDelay: 60, + skillMatchingEnabled: true, + roundRobinRotation: true, + preserveAgentOnTransfer: false, + enableAgentToAgentTransfer: true, + enableQueueToQueueTransfer: true, + enableConferencing: true, + maxConferenceParticipants: 6, + enableWhisper: false, + enableBarge: false, + emergencyContactNumber: '911', + active: true, + outdialCampaignEnabled: false, + monitoringPermitted: true, + parkingPermitted: true, + conferencePermitted: true, + consultPermitted: true, + transferPermitted: true, + holdPermitted: true, + breakoutPermitted: false, + superQueueEnabled: false, + workflowId: 'workflow1', + businessRuleId: 'rule1', + outboundAni: '1234567890', + ringingTimeoutInterval: 20, + bargePermitted: true, + whisperPermitted: true, + organizationId: 'org1', + enterpriseId: 'enterprise1', + recordingPermitted: true, + recordingAllCallsPermitted: true, + pauseRecordingPermitted: true, + recordingPauseDuration: 30, + callRecordingEnabled: true, + screenRecordingEnabled: false, + mediaStreamingEnabled: false, + transcriptionEnabled: false, + realTimeTranscriptionEnabled: false, + queueStatisticsEnabled: true, + controlFlowScriptUrl: 'https://example.com/script1.js', + ivrRequeueUrl: 'https://example.com/requeue1', + routingType: 'skill_based', + queueRoutingType: 'priority', + callParkingUrl: 'https://example.com/parking1', + overflowQueueId: 'overflow1', + queueContactLimit: 100, + queueAgentLimit: 50, + enableVoicemail: true, + voicemailGreeting: 'Hello, please leave a message', + queueSkillRequirements: [{id: 'skill1', name: 'Skill 1'}], + agents: [ + {id: 'agent1', name: 'Agent 1'}, + {id: 'agent2', name: 'Agent 2'}, + ], + callDistributionGroups: [{id: 'group1', name: 'Group 1', agentGroups: [], order: 1, duration: 30}], + links: [ + 'https://api.example.com/queues/q1', + 'https://api.example.com/queues/q1/agents', + 'https://api.example.com/queues/q1/stats', + ], + createdBy: 'admin', + lastModified: '2024-01-01T00:00:00Z', + createdTime: '2024-01-01T00:00:00Z', + lastUpdatedTime: '2024-01-01T00:00:00Z', + }, + { + id: 'q2', + name: 'Queue2', + description: 'Test Queue 2', + queueType: 'inbound', + checkAgentAvailability: true, + channelType: 'telephony', + skillProfileId: 'skill2', + siteId: 'site1', + version: 1, + isActive: true, + maxWaitTime: 300, + serviceLevelThreshold: 20, + serviceLevelType: 'percentage', + maxHandlingTime: 600, + abandonedThreshold: 30, + distributionPolicy: 'longest_idle', + routingPolicy: 'skill_based', + wrapUpTime: 60, + autoAnswer: false, + recordingPolicy: 'always', + monitoringPolicy: 'supervisor', + scriptId: 'script2', + surveyId: 'survey2', + outdialANI: '1234567890', + outdialMethod: 'progressive', + dialMode: 'preview', + maxAttempts: 3, + dialingOrder: 'priority', + timeZoneHandling: 'agent', + callbackOption: true, + priority: 2, + teamId: 'team1', + maxActiveContacts: 10, + maxTimeInQueue: 600, + defaultMusicInQueueMediaFileId: 'music2', + timezone: 'UTC', + allowedWaitTimeMusicFiles: ['music2'], + musicInQueueFileType: 'mp3', + outdialRetryInterval: 300, + maxQueueSize: 100, + enableCallback: true, + enableBusyRedial: false, + busyRedialDelay: 60, + skillMatchingEnabled: true, + roundRobinRotation: true, + preserveAgentOnTransfer: false, + enableAgentToAgentTransfer: true, + enableQueueToQueueTransfer: true, + enableConferencing: true, + maxConferenceParticipants: 6, + enableWhisper: false, + enableBarge: false, + emergencyContactNumber: '911', + active: true, + outdialCampaignEnabled: false, + monitoringPermitted: true, + parkingPermitted: true, + conferencePermitted: true, + consultPermitted: true, + transferPermitted: true, + holdPermitted: true, + breakoutPermitted: false, + superQueueEnabled: false, + workflowId: 'workflow2', + businessRuleId: 'rule2', + outboundAni: '1234567890', + ringingTimeoutInterval: 20, + bargePermitted: true, + whisperPermitted: true, + organizationId: 'org1', + enterpriseId: 'enterprise1', + recordingPermitted: true, + recordingAllCallsPermitted: true, + pauseRecordingPermitted: true, + recordingPauseDuration: 30, + callRecordingEnabled: true, + screenRecordingEnabled: false, + mediaStreamingEnabled: false, + transcriptionEnabled: false, + realTimeTranscriptionEnabled: false, + queueStatisticsEnabled: true, + controlFlowScriptUrl: 'https://example.com/script2.js', + ivrRequeueUrl: 'https://example.com/requeue2', + routingType: 'skill_based', + queueRoutingType: 'priority', + callParkingUrl: 'https://example.com/parking2', + overflowQueueId: 'overflow2', + queueContactLimit: 100, + queueAgentLimit: 50, + enableVoicemail: true, + voicemailGreeting: 'Hello, please leave a message', + queueSkillRequirements: [{id: 'skill2', name: 'Skill 2'}], + agents: [ + {id: 'agent3', name: 'Agent 3'}, + {id: 'agent4', name: 'Agent 4'}, + ], + callDistributionGroups: [{id: 'group2', name: 'Group 2', agentGroups: [], order: 1, duration: 30}], + links: [ + 'https://api.example.com/queues/q2', + 'https://api.example.com/queues/q2/agents', + 'https://api.example.com/queues/q2/stats', + ], + createdBy: 'admin', + lastModified: '2024-01-01T00:00:00Z', + createdTime: '2024-01-01T00:00:00Z', + lastUpdatedTime: '2024-01-01T00:00:00Z', + }, +]; + +const mockAgents = [ + { + id: 'a1', + name: 'Agent1', + agentId: 'a1', + agentName: 'Agent1', + state: 'Available', + teamId: 'team1', + dn: '1001', + email: 'agent1@example.com', + extension: '1001', + siteId: 'site1', + }, + { + id: 'a2', + name: 'Agent2', + agentId: 'a2', + agentName: 'Agent2', + state: 'Available', + teamId: 'team1', + dn: '1002', + email: 'agent2@example.com', + extension: '1002', + siteId: 'site1', + }, +]; + +export {mockProfile, mockCC, mockTask, mockQueueDetails, mockAgents}; diff --git a/packages/contact-center/test-fixtures/src/index.ts b/packages/contact-center/test-fixtures/src/index.ts new file mode 100644 index 000000000..995b5bc6e --- /dev/null +++ b/packages/contact-center/test-fixtures/src/index.ts @@ -0,0 +1 @@ +export * from './fixtures'; diff --git a/packages/contact-center/test-fixtures/tsconfig.json b/packages/contact-center/test-fixtures/tsconfig.json new file mode 100644 index 000000000..32cd1eda3 --- /dev/null +++ b/packages/contact-center/test-fixtures/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../../../tsconfig.json", + "include": [ + "./src", + ], + "compilerOptions": { + "outDir": "./dist", + "declaration": true, + "declarationDir": "./dist/types", + "moduleResolution": "NodeNext", + "module": "NodeNext" + }, +} \ No newline at end of file diff --git a/packages/contact-center/test-fixtures/webpack.config.js b/packages/contact-center/test-fixtures/webpack.config.js new file mode 100644 index 000000000..247cf2783 --- /dev/null +++ b/packages/contact-center/test-fixtures/webpack.config.js @@ -0,0 +1,12 @@ +const {merge} = require('webpack-merge'); +const path = require('path'); + +const baseConfig = require('../../../webpack.config'); + +module.exports = merge(baseConfig, { + output: { + path: path.resolve(__dirname, 'dist'), + filename: 'index.js', // Set the output filename to index.js + libraryTarget: 'commonjs2', + }, +}); diff --git a/packages/contact-center/user-state/package.json b/packages/contact-center/user-state/package.json index b4f53bc49..5f4a95f6d 100644 --- a/packages/contact-center/user-state/package.json +++ b/packages/contact-center/user-state/package.json @@ -3,6 +3,7 @@ "description": "Webex Contact Center Widgets: User State", "version": "1.28.0-ccwidgets.91", "main": "dist/index.js", + "types": "dist/types/index.d.ts", "publishConfig": { "access": "public" }, @@ -16,7 +17,7 @@ "build": "yarn run -T tsc", "build:src": "yarn run clean:dist && webpack", "build:watch": "webpack --watch", - "test:unit": "jest --coverage", + "test:unit": "tsc --project tsconfig.test.json && jest --coverage", "test:styles": "eslint" }, "dependencies": { @@ -61,4 +62,4 @@ "react": ">=18.3.1", "react-dom": ">=18.3.1" } -} \ No newline at end of file +} diff --git a/packages/contact-center/user-state/src/helper.ts b/packages/contact-center/user-state/src/helper.ts index a126f583e..13041579a 100644 --- a/packages/contact-center/user-state/src/helper.ts +++ b/packages/contact-center/user-state/src/helper.ts @@ -1,5 +1,6 @@ import {useState, useEffect, useRef} from 'react'; import store from '@webex/cc-store'; +import {UseUserStateProps} from './user-state.types'; export const useUserState = ({ idleCodes, @@ -11,7 +12,7 @@ export const useUserState = ({ logger, onStateChange, lastIdleCodeChangeTimestamp, -}) => { +}: UseUserStateProps) => { const [isSettingAgentStatus, setIsSettingAgentStatus] = useState(false); const [elapsedTime, setElapsedTime] = useState(0); const [lastIdleStateChangeElapsedTime, setLastIdleStateChangeElapsedTime] = useState(0); @@ -25,7 +26,7 @@ export const useUserState = ({ method: 'callOnStateChange', }); if (onStateChange) { - if (customState?.developerName) { + if (customState && 'developerName' in customState && customState?.developerName) { onStateChange(customState); return; } @@ -199,9 +200,10 @@ export const useUserState = ({ module: 'useUserState', method: 'updateAgentState', }); - - store.setLastStateChangeTimestamp(response.data.lastStateChangeTimestamp); - store.setLastIdleCodeChangeTimestamp(response.data.lastIdleCodeChangeTimestamp); + if ('data' in response) { + store.setLastStateChangeTimestamp(response.data.lastStateChangeTimestamp); + store.setLastIdleCodeChangeTimestamp(response.data.lastIdleCodeChangeTimestamp); + } }) .catch((error) => { logger.error(`Error setting agent state: ${error.toString()}`, { @@ -223,5 +225,6 @@ export const useUserState = ({ elapsedTime, lastIdleStateChangeElapsedTime, currentState, + onStateChange, }; }; diff --git a/packages/contact-center/user-state/src/user-state.types.ts b/packages/contact-center/user-state/src/user-state.types.ts index a2e45ddbd..56b97aa61 100644 --- a/packages/contact-center/user-state/src/user-state.types.ts +++ b/packages/contact-center/user-state/src/user-state.types.ts @@ -1,3 +1,16 @@ -export interface IUserStateProps { - onStateChange: (state: string) => void; -} +import {IUserState} from '@webex/cc-components'; + +export type IUserStateProps = Pick; + +export type UseUserStateProps = Pick< + IUserState, + | 'idleCodes' + | 'agentId' + | 'cc' + | 'currentState' + | 'customState' + | 'lastStateChangeTimestamp' + | 'logger' + | 'onStateChange' + | 'lastIdleCodeChangeTimestamp' +>; diff --git a/packages/contact-center/user-state/src/user-state/index.tsx b/packages/contact-center/user-state/src/user-state/index.tsx index 6e03ad675..5348e89c9 100644 --- a/packages/contact-center/user-state/src/user-state/index.tsx +++ b/packages/contact-center/user-state/src/user-state/index.tsx @@ -3,7 +3,7 @@ import store from '@webex/cc-store'; import {observer} from 'mobx-react-lite'; import {useUserState} from '../helper'; -import {UserStateComponent, IUserState} from '@webex/cc-components'; +import {UserStateComponent, UserStateComponentsProps} from '@webex/cc-components'; import {IUserStateProps} from '../user-state.types'; const UserState: React.FunctionComponent = observer(({onStateChange}) => { @@ -11,14 +11,13 @@ const UserState: React.FunctionComponent = observer(({onStateCh cc, idleCodes, agentId, - currentTheme, currentState, lastStateChangeTimestamp, lastIdleCodeChangeTimestamp, customState, logger, } = store; - const props: IUserState = { + const props: UserStateComponentsProps = { ...useUserState({ idleCodes, agentId, @@ -30,7 +29,6 @@ const UserState: React.FunctionComponent = observer(({onStateCh onStateChange, lastIdleCodeChangeTimestamp, }), - currentTheme, customState, logger, }; diff --git a/packages/contact-center/user-state/tests/helper.ts b/packages/contact-center/user-state/tests/helper.ts index dde398b1d..2f67cab75 100644 --- a/packages/contact-center/user-state/tests/helper.ts +++ b/packages/contact-center/user-state/tests/helper.ts @@ -1,17 +1,12 @@ import {renderHook, act, waitFor} from '@testing-library/react'; import {useUserState} from '../src/helper'; import store from '@webex/cc-store'; +import {mockCC} from '@webex/test-fixtures'; describe('useUserState Hook', () => { - const mockCC = { - setAgentState: jest.fn(), - on: jest.fn(), - off: jest.fn(), - }; - const idleCodes = [ - {id: '1', name: 'Idle Code 1', isSystem: false}, - {id: '2', name: 'Available', isSystem: false}, + {id: '1', name: 'Idle Code 1', isSystem: false, isDefault: false}, + {id: '2', name: 'Available', isSystem: false, isDefault: false}, ]; const agentId = 'agent123'; @@ -21,13 +16,89 @@ describe('useUserState Hook', () => { log: jest.fn(), info: jest.fn(), error: jest.fn(), + warn: jest.fn(), + trace: jest.fn(), + }; + + // Reusable mock response objects + const mockAvailableStateResponse = { + type: 'AgentStateChangeSuccess' as const, + orgId: 'org123', + trackingId: 'track456', + data: { + eventType: 'AgentDesktopMessage' as const, + agentId: 'agent123', + trackingId: 'track456', + auxCodeId: '2', + agentSessionId: 'session123', + orgId: 'org123', + status: 'Available', + subStatus: 'Available' as const, + lastIdleCodeChangeTimestamp: new Date().getTime(), + lastStateChangeTimestamp: new Date().getTime(), + lastStateChangeReason: 'Available', + type: 'AgentStateChangeSuccess' as const, + changedBy: 'AGENT', + changedById: 'agent123', + changedByName: 'Test Agent', + }, + }; + + const mockAvailableStateResponseWithTimestamp = { + type: 'AgentStateChangeSuccess' as const, + orgId: 'org123', + trackingId: 'track456', + data: { + eventType: 'AgentDesktopMessage' as const, + agentId: 'agent123', + trackingId: 'track456', + auxCodeId: '2', + agentSessionId: 'session123', + orgId: 'org123', + status: 'Available', + subStatus: 'Available' as const, + lastIdleCodeChangeTimestamp: 1740748111287, + lastStateChangeTimestamp: 1740748111287, + lastStateChangeReason: 'Available', + type: 'AgentStateChangeSuccess' as const, + changedBy: 'AGENT', + changedById: 'agent123', + changedByName: 'Test Agent', + }, + }; + + const mockIdleStateResponse = { + type: 'AgentStateChangeSuccess' as const, + orgId: 'org123', + trackingId: 'track456', + data: { + eventType: 'AgentDesktopMessage' as const, + agentId: 'agent123', + trackingId: 'track456', + auxCodeId: '1', + agentSessionId: 'session123', + orgId: 'org123', + status: 'Idle', + subStatus: 'Idle' as const, + lastIdleCodeChangeTimestamp: 1740748111287, + lastStateChangeTimestamp: 1740748111287, + lastStateChangeReason: 'Idle Code 1', + type: 'AgentStateChangeSuccess' as const, + changedBy: 'AGENT', + changedById: 'agent123', + changedByName: 'Test Agent', + }, }; + const ccOnSpy = jest.spyOn(mockCC, 'on'); + const ccOffSpy = jest.spyOn(mockCC, 'off'); + const ccSetAgentStateSpy = jest.spyOn(mockCC, 'setAgentState'); + beforeEach(() => { jest.useFakeTimers(); - mockCC.setAgentState.mockReset(); - mockCC.on.mockReset(); - mockCC.off.mockReset(); + ccSetAgentStateSpy.mockReset(); + ccOnSpy.mockReset(); + ccOffSpy.mockReset(); workerMock = { postMessage: jest.fn(), @@ -71,14 +142,12 @@ describe('useUserState Hook', () => { }); it('should clean up on unmount', () => { - mockCC.setAgentState.mockResolvedValueOnce({ - data: {auxCodeId: '2', lastStateChangeTimestamp: new Date().getTime()}, - }); + ccSetAgentStateSpy.mockResolvedValueOnce(mockAvailableStateResponse); const {unmount} = renderHook(() => useUserState({ idleCodes: [], agentId: 'agent123', - cc: {}, + cc: mockCC, currentState: '0', customState: null, logger: logger, @@ -151,9 +220,7 @@ describe('useUserState Hook', () => { }); it('should handle setAgentStatus correctly and update state', async () => { - mockCC.setAgentState.mockResolvedValueOnce({ - data: {auxCodeId: '2', lastStateChangeTimestamp: new Date().getTime()}, - }); + ccSetAgentStateSpy.mockResolvedValueOnce(mockAvailableStateResponse); const {result} = renderHook(() => useUserState({ idleCodes, @@ -178,8 +245,7 @@ describe('useUserState Hook', () => { }); it('should update last state change timestamp from setAgentState', async () => { - const resolvingValue = {data: {auxCodeId: '2', lastStateChangeTimestamp: 1740748111287}}; - mockCC.setAgentState.mockResolvedValueOnce(resolvingValue); + ccSetAgentStateSpy.mockResolvedValueOnce(mockAvailableStateResponseWithTimestamp); const {rerender} = renderHook( ({currentState}) => useUserState({ @@ -208,13 +274,14 @@ describe('useUserState Hook', () => { agentId, lastStateChangeReason: 'Available', }); - expect(store.lastStateChangeTimestamp).toEqual(resolvingValue.data.lastStateChangeTimestamp); + expect(store.lastStateChangeTimestamp).toEqual( + mockAvailableStateResponseWithTimestamp.data.lastStateChangeTimestamp + ); }); }); it('should set idle status if name does not match: Available', async () => { - const resolvingValue = {data: {auxCodeId: '1', lastStateChangeTimestamp: 1740748111287}}; - mockCC.setAgentState.mockResolvedValueOnce(resolvingValue); + ccSetAgentStateSpy.mockResolvedValueOnce(mockIdleStateResponse); const {rerender} = renderHook( ({currentState}) => useUserState({ @@ -243,12 +310,12 @@ describe('useUserState Hook', () => { agentId, lastStateChangeReason: 'Idle Code 1', }); - expect(store.lastStateChangeTimestamp).toEqual(resolvingValue.data.lastStateChangeTimestamp); + expect(store.lastStateChangeTimestamp).toEqual(mockIdleStateResponse.data.lastStateChangeTimestamp); }); }); it('should handle errors from setAgentState and revert state', async () => { - mockCC.setAgentState.mockRejectedValueOnce(new Error('Error setting agent status')); + ccSetAgentStateSpy.mockRejectedValueOnce(new Error('Error setting agent status')); const {rerender} = renderHook( ({currentState}) => useUserState({ @@ -257,7 +324,7 @@ describe('useUserState Hook', () => { cc: mockCC, currentState, customState: null, - lastStateChangeTimestamp: new Date(), + lastStateChangeTimestamp: new Date().getTime(), lastIdleCodeChangeTimestamp: undefined, logger, onStateChange, @@ -325,7 +392,7 @@ describe('useUserState Hook', () => { }); it('should not call onStateChange if not available', () => { - const customState = {developerName: 'Custom State'}; + const customState = {developerName: 'Custom State', name: 'Custom State'}; renderHook(() => useUserState({ idleCodes, @@ -344,7 +411,7 @@ describe('useUserState Hook', () => { }); it('should call onStateChange with customState if provided', () => { - const customState = {developerName: 'Custom State'}; + const customState = {developerName: 'Custom State', name: 'Custom State'}; renderHook(() => useUserState({ idleCodes, @@ -352,7 +419,7 @@ describe('useUserState Hook', () => { cc: mockCC, currentState: '0', customState, - lastStateChangeTimestamp: new Date(), + lastStateChangeTimestamp: new Date().getTime(), lastIdleCodeChangeTimestamp: undefined, logger, onStateChange, @@ -370,7 +437,7 @@ describe('useUserState Hook', () => { cc: mockCC, currentState: '1', customState: null, - lastStateChangeTimestamp: new Date(), + lastStateChangeTimestamp: new Date().getTime(), lastIdleCodeChangeTimestamp: undefined, logger, onStateChange, diff --git a/packages/contact-center/user-state/tests/user-state/index.tsx b/packages/contact-center/user-state/tests/user-state/index.tsx index d2c6546cf..aad1270c3 100644 --- a/packages/contact-center/user-state/tests/user-state/index.tsx +++ b/packages/contact-center/user-state/tests/user-state/index.tsx @@ -17,7 +17,7 @@ jest.mock('@webex/cc-store', () => { log: jest.fn(), info: jest.fn(), }, - lastStateChangeTimestamp: new Date(), + lastStateChangeTimestamp: new Date().getTime(), customState: null, currentState: '0', }; @@ -33,6 +33,7 @@ describe('UserState Component', () => { terminate: jest.fn(), onmessage: null, }; + global.Worker = jest.fn(() => workerMock); global.URL.createObjectURL = jest.fn(() => 'blob:http://localhost:3000/12345'); @@ -55,7 +56,7 @@ describe('UserState Component', () => { agentId: 'testAgentId', currentState: '0', customState: null, - lastStateChangeTimestamp: expect.any(Date), + lastStateChangeTimestamp: expect.any(Number), logger: { log: expect.any(Function), info: expect.any(Function), diff --git a/packages/contact-center/user-state/tsconfig.json b/packages/contact-center/user-state/tsconfig.json index 3b81144dc..725e78fea 100644 --- a/packages/contact-center/user-state/tsconfig.json +++ b/packages/contact-center/user-state/tsconfig.json @@ -1,7 +1,7 @@ { "extends": "../../../tsconfig.json", "include": [ - "./src" + "./src", ], "compilerOptions": { "outDir": "./dist", diff --git a/packages/contact-center/user-state/tsconfig.test.json b/packages/contact-center/user-state/tsconfig.test.json new file mode 100644 index 000000000..40bd84f5a --- /dev/null +++ b/packages/contact-center/user-state/tsconfig.test.json @@ -0,0 +1,9 @@ +// This config is to do type checking in our files while running tests. +{ + "extends": "./tsconfig.json", + "include": ["./tests"], + "exclude": ["node_modules"], + "compilerOptions": { + "noEmit": true // Don't output any files + } +} \ No newline at end of file diff --git a/tooling/src/publish.js b/tooling/src/publish.js index 4566021cc..afa8fb1b7 100644 --- a/tooling/src/publish.js +++ b/tooling/src/publish.js @@ -67,7 +67,13 @@ function versionAndPublish() { execSync(`yarn workspace ${workspace} npm publish --tag ${branchName}`, {stdio: 'inherit'}); }; + const denyList = ['@webex/test-fixtures']; // Add workspace names to exclude from publishing + for (const workspace of workspaceData) { + if (denyList.includes(workspace)) { + console.log(`Skipping ${workspace} - workspace is in deny list`); + continue; + } publishWorkspace(workspace); } } catch (error) { diff --git a/tooling/tests/publish.js b/tooling/tests/publish.js index 850602b01..4c8cdb367 100644 --- a/tooling/tests/publish.js +++ b/tooling/tests/publish.js @@ -203,6 +203,54 @@ describe('versionAndPublish', () => { }); }); + it('should not publish packages in deny list P.S we only have test-fixtures in deny list right now', () => { + const packageJsonContent = JSON.stringify({ + name: '@webex/test-fixtures', + version: '1.0.0', + }); + const packageJsonContent2 = JSON.stringify({ + name: '@webex/cc-station-login', + version: '1.0.0', + }); + + mockFs.readdirSync.mockReturnValue([ + {name: 'test-fixtures', isDirectory: () => true}, + {name: 'station-login', isDirectory: () => true}, + ]); + + mockFs.readFileSync.mockReturnValueOnce(packageJsonContent).mockReturnValueOnce(packageJsonContent2); + mockFs.writeFileSync.mockImplementation(() => {}); + + const mockExecSync = require('child_process').execSync; + mockExecSync.mockImplementation(() => {}); + + const processArgvMock = ['node', 'script.js', 'main', '1.0.1']; + process.argv = processArgvMock; + + versionAndPublish(); + + // Ensures that first 2 calls were to update version and next 2 calls were to publish the package. + expect(mockFs.writeFileSync).toHaveBeenNthCalledWith( + 1, + 'packages/contact-center/test-fixtures/package.json', + expect.any(String), + 'utf-8' + ); + expect(mockFs.writeFileSync).toHaveBeenNthCalledWith( + 2, + 'packages/contact-center/station-login/package.json', + expect.any(String), + 'utf-8' + ); + + expect(mockExecSync).not.toHaveBeenCalledWith('yarn workspace @webex/test-fixtures npm publish --tag main', { + stdio: 'inherit', + }); + expect(mockExecSync).toHaveBeenNthCalledWith(1, 'yarn workspace @webex/cc-station-login npm publish --tag main', { + stdio: 'inherit', + }); + }); + it('error occurred while reading package.json data', () => { mockFs.readdirSync.mockReturnValue([ {name: 'store', isDirectory: () => true}, diff --git a/tsconfig.json b/tsconfig.json index 145075caa..95c23fab5 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -23,6 +23,7 @@ "ESNext", "DOM" ], - "jsx": "react" + "jsx": "react", + "types": ["jest"], }, } \ No newline at end of file diff --git a/widgets-samples/cc/samples-cc-react-app/src/App.tsx b/widgets-samples/cc/samples-cc-react-app/src/App.tsx index 5d0ea298d..cfef2a2ed 100644 --- a/widgets-samples/cc/samples-cc-react-app/src/App.tsx +++ b/widgets-samples/cc/samples-cc-react-app/src/App.tsx @@ -236,10 +236,12 @@ const onTaskDeclined = (task,reason) => { lastStateChangeReason: newState, }) .then((response) => { - store.setCurrentState(response.data.auxCodeId); - store.setLastStateChangeTimestamp(response.data.lastStateChangeTimestamp); - store.setLastIdleCodeChangeTimestamp(response.data.lastIdleCodeChangeTimestamp); - console.log('Agent state updated to', newState); + if('data'in response){ + store.setCurrentState(response.data.auxCodeId); + store.setLastStateChangeTimestamp(response.data.lastStateChangeTimestamp); + store.setLastIdleCodeChangeTimestamp(response.data.lastIdleCodeChangeTimestamp); + console.log('Agent state updated to', newState); + } }) .catch((error) => { console.error('Error updating agent state:', error); diff --git a/yarn.lock b/yarn.lock index 2458c41d2..93685b010 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9367,21 +9367,21 @@ __metadata: languageName: node linkType: hard -"@webex/calling@npm:3.8.1-next.2": - version: 3.8.1-next.2 - resolution: "@webex/calling@npm:3.8.1-next.2" +"@webex/calling@npm:3.8.1-next.6": + version: 3.8.1-next.6 + resolution: "@webex/calling@npm:3.8.1-next.6" dependencies: "@types/platform": "npm:1.3.4" - "@webex/internal-media-core": "npm:2.17.1" - "@webex/internal-plugin-metrics": "npm:3.8.1-next.1" - "@webex/media-helpers": "npm:3.8.1-next.1" + "@webex/internal-media-core": "npm:2.18.1" + "@webex/internal-plugin-metrics": "npm:3.8.1-next.4" + "@webex/media-helpers": "npm:3.8.1-next.5" async-mutex: "npm:0.4.0" buffer: "npm:6.0.3" jest-html-reporters: "npm:3.0.11" platform: "npm:1.3.6" uuid: "npm:8.3.2" xstate: "npm:4.30.6" - checksum: 10c0/7749be1d384de81f51ac0c93cc93ae95bd2c2661dc7a322cea49549b2eef50ff49efcc46d50de85b78f933c1b19da3fe3620026090a7b6994125e0a29fc78ff3 + checksum: 10c0/73b3688c0f7eba293590a63e6bda99115f696410b394198cb8f7b71782dd4756f5496de57539bfbf63ad07d7a725b15a06383d2b8f2d0362dab6272d786f4aa9 languageName: node linkType: hard @@ -9402,6 +9402,7 @@ __metadata: "@types/jest": "npm:29.5.14" "@types/react-test-renderer": "npm:18" "@webex/cc-store": "workspace:*" + "@webex/test-fixtures": "workspace:*" babel-loader: "npm:9.2.1" eslint: "npm:^9.20.1" eslint-config-prettier: "npm:^10.0.1" @@ -9446,6 +9447,7 @@ __metadata: "@types/react-test-renderer": "npm:18" "@webex/cc-components": "workspace:*" "@webex/cc-store": "workspace:*" + "@webex/test-fixtures": "workspace:*" babel-jest: "npm:29.7.0" babel-loader: "npm:9.2.1" eslint: "npm:^9.20.1" @@ -9489,7 +9491,8 @@ __metadata: "@testing-library/react": "npm:16.0.1" "@types/jest": "npm:29.5.14" "@types/react-test-renderer": "npm:18" - "@webex/plugin-cc": "npm:3.8.1-next.6" + "@webex/plugin-cc": "npm:3.8.1-next.12" + "@webex/test-fixtures": "workspace:*" babel-jest: "npm:29.7.0" babel-loader: "npm:9.2.1" eslint: "npm:^9.20.1" @@ -9531,6 +9534,7 @@ __metadata: "@types/react-test-renderer": "npm:18" "@webex/cc-components": "workspace:*" "@webex/cc-store": "workspace:*" + "@webex/test-fixtures": "workspace:*" babel-jest: "npm:29.7.0" babel-loader: "npm:9.2.1" eslint: "npm:^9.20.1" @@ -9678,10 +9682,10 @@ __metadata: languageName: node linkType: hard -"@webex/common-timers@npm:3.8.1-next.1": - version: 3.8.1-next.1 - resolution: "@webex/common-timers@npm:3.8.1-next.1" - checksum: 10c0/9aae6c1e4b0575c4e9ed39b32ed90184045ff68ab735ca2f54ff55a86790b0a8784ba0afe0fe77397a385689558922a16f8be6c4c686e80a4ca44bc079014907 +"@webex/common-timers@npm:3.8.1-next.4": + version: 3.8.1-next.4 + resolution: "@webex/common-timers@npm:3.8.1-next.4" + checksum: 10c0/5ff68c32c802f1195a41560ac4608fb2ecaa19c76a93a1ff83e8fcf44a28cbafb6af1cbe4e15e50357219267025e385eeaf72c3bc3e100324e822869c8bfc1c5 languageName: node linkType: hard @@ -9748,9 +9752,9 @@ __metadata: languageName: node linkType: hard -"@webex/common@npm:3.8.1-next.1": - version: 3.8.1-next.1 - resolution: "@webex/common@npm:3.8.1-next.1" +"@webex/common@npm:3.8.1-next.4": + version: 3.8.1-next.4 + resolution: "@webex/common@npm:3.8.1-next.4" dependencies: backoff: "npm:^2.5.0" bowser: "npm:^2.11.0" @@ -9759,7 +9763,7 @@ __metadata: lodash: "npm:^4.17.21" safe-buffer: "npm:^5.2.0" urlsafe-base64: "npm:^1.0.0" - checksum: 10c0/59078f230b0b8b5dfa58e9afbcd728574cf7b7035c0250736106c478612579cfe74e0bbfa92505db745e0ee70cf9ef7042629800730e7f531f8d42643d9f4c04 + checksum: 10c0/a97e896d445968bfb528117dff4f8d495e586727318c58047c4caa881d31713ffeda6831b6ea41a1c3c21b455bc7fa3ff9a735d2877309a4a5db1b4cfa36ea22 languageName: node linkType: hard @@ -9812,6 +9816,19 @@ __metadata: languageName: node linkType: hard +"@webex/event-dictionary-ts@npm:^1.0.1819": + version: 1.0.1838 + resolution: "@webex/event-dictionary-ts@npm:1.0.1838" + dependencies: + amf-client-js: "npm:^5.2.6" + json-schema-to-typescript: "npm:^12.0.0" + minimist: "npm:^1.2.8" + shelljs: "npm:^0.8.5" + webapi-parser: "npm:^0.5.0" + checksum: 10c0/7a0cac58c0abbc937ddee6ffc0b5cfbc14c554101acfab8ff329e262662b01684e4d601da5fea27cca0af3be671bd692e1fcd8543688bdf3ed65ac6c864c8308 + languageName: node + linkType: hard + "@webex/helper-html@npm:2.60.2": version: 2.60.2 resolution: "@webex/helper-html@npm:2.60.2" @@ -9839,12 +9856,12 @@ __metadata: languageName: node linkType: hard -"@webex/helper-html@npm:3.8.1-next.1": - version: 3.8.1-next.1 - resolution: "@webex/helper-html@npm:3.8.1-next.1" +"@webex/helper-html@npm:3.8.1-next.4": + version: 3.8.1-next.4 + resolution: "@webex/helper-html@npm:3.8.1-next.4" dependencies: lodash: "npm:^4.17.21" - checksum: 10c0/a8f0c79bb36cf51ec33db7f4925f0d14425477d5fd81230803e6290ec3eeb89ac9270a2d0174b9875fb21604941d24326efb6ad45300ea431102d42b773d30ca + checksum: 10c0/7c41ccb2728bb170c13a79978aa53f23b5a0ff092d082379c7ba33bf53da6151e4fa134f7e2512cfbba35461d3055ad23aaee0f1747a9fe3c6919978fb1a516f languageName: node linkType: hard @@ -9899,20 +9916,20 @@ __metadata: languageName: node linkType: hard -"@webex/helper-image@npm:3.8.1-next.1": - version: 3.8.1-next.1 - resolution: "@webex/helper-image@npm:3.8.1-next.1" +"@webex/helper-image@npm:3.8.1-next.4": + version: 3.8.1-next.4 + resolution: "@webex/helper-image@npm:3.8.1-next.4" dependencies: - "@webex/http-core": "npm:3.8.1-next.1" - "@webex/test-helper-chai": "npm:3.8.1-next.1" - "@webex/test-helper-file": "npm:3.8.1-next.1" - "@webex/test-helper-mocha": "npm:3.8.1-next.1" + "@webex/http-core": "npm:3.8.1-next.4" + "@webex/test-helper-chai": "npm:3.8.1-next.4" + "@webex/test-helper-file": "npm:3.8.1-next.4" + "@webex/test-helper-mocha": "npm:3.8.1-next.4" exifr: "npm:^5.0.3" gm: "npm:^1.23.1" lodash: "npm:^4.17.21" mime: "npm:^2.4.4" safe-buffer: "npm:^5.2.0" - checksum: 10c0/8095eb404c5107a60a9929f19732144ed1595e77830ea0c51d00f54c9aae84af1116dcb16fd0a7291ec3376b8b8f3e2b13f69117087782a2b0b565f23a69d54b + checksum: 10c0/e3600336dbf4da2bc7a8ce6c759c24113cb554c95ba54dd858e8ca22925cc70e9035aac36c753b338cd6c4a928db98d4a0c03e3fda32d92bcbb54da92234cb24 languageName: node linkType: hard @@ -9999,14 +10016,14 @@ __metadata: languageName: node linkType: hard -"@webex/http-core@npm:3.8.1-next.1": - version: 3.8.1-next.1 - resolution: "@webex/http-core@npm:3.8.1-next.1" +"@webex/http-core@npm:3.8.1-next.4": + version: 3.8.1-next.4 + resolution: "@webex/http-core@npm:3.8.1-next.4" dependencies: - "@webex/common": "npm:3.8.1-next.1" - "@webex/internal-plugin-device": "npm:3.8.1-next.1" - "@webex/test-helper-test-users": "npm:3.8.1-next.1" - "@webex/webex-core": "npm:3.8.1-next.1" + "@webex/common": "npm:3.8.1-next.4" + "@webex/internal-plugin-device": "npm:3.8.1-next.4" + "@webex/test-helper-test-users": "npm:3.8.1-next.4" + "@webex/webex-core": "npm:3.8.1-next.4" file-type: "npm:^16.0.1" global: "npm:^4.4.0" is-function: "npm:^1.0.1" @@ -10016,7 +10033,7 @@ __metadata: request: "npm:^2.88.0" safe-buffer: "npm:^5.2.0" xtend: "npm:^4.0.2" - checksum: 10c0/457d34113151fbc337d4dd34de2377d88dcf1e05326bc9bd08a8e373aebbc0cba32c992f7c371b33f6e41945b4f3b3e96ae8266723f2da49605d9940b97efe99 + checksum: 10c0/7736998a3aaad6295e2b16cdda92ba5323964e2722053a8ea1e29ff8850b7da451be9a4c5c4a14b53750e40fdc11c1b9bb73665e35c07fa43de6f9edc00c5b3b languageName: node linkType: hard @@ -10057,6 +10074,26 @@ __metadata: languageName: node linkType: hard +"@webex/internal-media-core@npm:2.18.1": + version: 2.18.1 + resolution: "@webex/internal-media-core@npm:2.18.1" + dependencies: + "@babel/runtime": "npm:^7.18.9" + "@babel/runtime-corejs2": "npm:^7.25.0" + "@webex/rtcstats": "npm:^1.5.0" + "@webex/ts-sdp": "npm:1.7.0" + "@webex/web-capabilities": "npm:^1.4.1" + "@webex/web-client-media-engine": "npm:3.33.1" + events: "npm:^3.3.0" + ip-anonymize: "npm:^0.1.0" + typed-emitter: "npm:^2.1.0" + uuid: "npm:^8.3.2" + webrtc-adapter: "npm:^8.1.2" + xstate: "npm:^4.30.6" + checksum: 10c0/60a221c9bccbea3da44bcd5d03198a358cf801173e20fb61dfb567a08c885b6d0785a6fb7136341b21262cd4631a5215b281f275b69dc7a5d948a79c06616293 + languageName: node + linkType: hard + "@webex/internal-plugin-calendar@npm:2.60.2": version: 2.60.2 resolution: "@webex/internal-plugin-calendar@npm:2.60.2" @@ -10153,21 +10190,21 @@ __metadata: languageName: node linkType: hard -"@webex/internal-plugin-conversation@npm:3.8.1-next.1": - version: 3.8.1-next.1 - resolution: "@webex/internal-plugin-conversation@npm:3.8.1-next.1" +"@webex/internal-plugin-conversation@npm:3.8.1-next.4": + version: 3.8.1-next.4 + resolution: "@webex/internal-plugin-conversation@npm:3.8.1-next.4" dependencies: - "@webex/common": "npm:3.8.1-next.1" - "@webex/helper-html": "npm:3.8.1-next.1" - "@webex/helper-image": "npm:3.8.1-next.1" - "@webex/internal-plugin-encryption": "npm:3.8.1-next.1" - "@webex/internal-plugin-user": "npm:3.8.1-next.1" - "@webex/webex-core": "npm:3.8.1-next.1" + "@webex/common": "npm:3.8.1-next.4" + "@webex/helper-html": "npm:3.8.1-next.4" + "@webex/helper-image": "npm:3.8.1-next.4" + "@webex/internal-plugin-encryption": "npm:3.8.1-next.4" + "@webex/internal-plugin-user": "npm:3.8.1-next.4" + "@webex/webex-core": "npm:3.8.1-next.4" crypto-js: "npm:^4.1.1" lodash: "npm:^4.17.21" node-scr: "npm:^0.3.0" uuid: "npm:^3.3.2" - checksum: 10c0/bc780e153e93e01158f1fcff664c261ab10e29870f23040f68afeb07b1ee32668b79450278a65443d05246f4cd522fa23804ec9c4d112c7e905232620f251994 + checksum: 10c0/2e932d0e1b83a0b408f198a0c1ca4fa98232b882e75980f34c35ae1fde72f30750756d3ed97fa5030cb43cb523db5bddd01eb93b5d38961d03f71a6905d49e17 languageName: node linkType: hard @@ -10220,20 +10257,20 @@ __metadata: languageName: node linkType: hard -"@webex/internal-plugin-device@npm:3.8.1-next.1": - version: 3.8.1-next.1 - resolution: "@webex/internal-plugin-device@npm:3.8.1-next.1" +"@webex/internal-plugin-device@npm:3.8.1-next.4": + version: 3.8.1-next.4 + resolution: "@webex/internal-plugin-device@npm:3.8.1-next.4" dependencies: - "@webex/common": "npm:3.8.1-next.1" - "@webex/common-timers": "npm:3.8.1-next.1" - "@webex/http-core": "npm:3.8.1-next.1" - "@webex/internal-plugin-metrics": "npm:3.8.1-next.1" - "@webex/webex-core": "npm:3.8.1-next.1" + "@webex/common": "npm:3.8.1-next.4" + "@webex/common-timers": "npm:3.8.1-next.4" + "@webex/http-core": "npm:3.8.1-next.4" + "@webex/internal-plugin-metrics": "npm:3.8.1-next.4" + "@webex/webex-core": "npm:3.8.1-next.4" ampersand-collection: "npm:^2.0.2" ampersand-state: "npm:^5.0.3" lodash: "npm:^4.17.21" uuid: "npm:^3.3.2" - checksum: 10c0/f7967226027bec3bb4507f9faa87646d3cece63fdd2500e80a5d89f6204b801123c2217cf69a1b69c3086f49dd0d52e3dcf7921fbf45abd456679dcf2a898980 + checksum: 10c0/1ed5eb8bc966dbfcf3d1786f6007ec0024a486b70747ca33bc8506f374ab8a4da2f1d5011eae69ca2732b6280361c8627d99a087404598c0aff941d0974ebd09 languageName: node linkType: hard @@ -10315,17 +10352,17 @@ __metadata: languageName: node linkType: hard -"@webex/internal-plugin-encryption@npm:3.8.1-next.1": - version: 3.8.1-next.1 - resolution: "@webex/internal-plugin-encryption@npm:3.8.1-next.1" +"@webex/internal-plugin-encryption@npm:3.8.1-next.4": + version: 3.8.1-next.4 + resolution: "@webex/internal-plugin-encryption@npm:3.8.1-next.4" dependencies: - "@webex/common": "npm:3.8.1-next.1" - "@webex/common-timers": "npm:3.8.1-next.1" - "@webex/http-core": "npm:3.8.1-next.1" - "@webex/internal-plugin-device": "npm:3.8.1-next.1" - "@webex/internal-plugin-mercury": "npm:3.8.1-next.1" - "@webex/test-helper-file": "npm:3.8.1-next.1" - "@webex/webex-core": "npm:3.8.1-next.1" + "@webex/common": "npm:3.8.1-next.4" + "@webex/common-timers": "npm:3.8.1-next.4" + "@webex/http-core": "npm:3.8.1-next.4" + "@webex/internal-plugin-device": "npm:3.8.1-next.4" + "@webex/internal-plugin-mercury": "npm:3.8.1-next.4" + "@webex/test-helper-file": "npm:3.8.1-next.4" + "@webex/webex-core": "npm:3.8.1-next.4" asn1js: "npm:^2.0.26" debug: "npm:^4.3.4" isomorphic-webcrypto: "npm:^2.3.8" @@ -10337,7 +10374,7 @@ __metadata: safe-buffer: "npm:^5.2.0" uuid: "npm:^3.3.2" valid-url: "npm:^1.0.9" - checksum: 10c0/a09ad5833c336e4939fb3520ba84c5e0d153170b4a64472e74efd5c513d5f2bb28c217ef79e3284134345463a3739351a099c8033f9e88800ff8c9c77a810abf + checksum: 10c0/2e84fbf962667ea352dd083b3ef42ea42cbed9cb64459dc46516173c732da2a9d77facbbf94958f3182b203115c5d600ac6ca1bd8e45e00eb585842ccbc7b970 languageName: node linkType: hard @@ -10376,14 +10413,14 @@ __metadata: languageName: node linkType: hard -"@webex/internal-plugin-feature@npm:3.8.1-next.1": - version: 3.8.1-next.1 - resolution: "@webex/internal-plugin-feature@npm:3.8.1-next.1" +"@webex/internal-plugin-feature@npm:3.8.1-next.4": + version: 3.8.1-next.4 + resolution: "@webex/internal-plugin-feature@npm:3.8.1-next.4" dependencies: - "@webex/internal-plugin-device": "npm:3.8.1-next.1" - "@webex/webex-core": "npm:3.8.1-next.1" + "@webex/internal-plugin-device": "npm:3.8.1-next.4" + "@webex/webex-core": "npm:3.8.1-next.4" lodash: "npm:^4.17.21" - checksum: 10c0/bc3b935100bfd0b0764fc2267bb008eff0abdbf2b413ce9365741b339951d445b55eed3e2a596d206513d8bdc6f4ffff81c282d3c17ba831d032e90e4f0233a9 + checksum: 10c0/38bc94408f2d543d2b05ada91ac7fbd9bc7a7f5dbc11b928174a8d42b40aa36640b04b89db26c7bdf6ffc263e82bfd95b2d0d30dec4467820f4d04e9ca918d81 languageName: node linkType: hard @@ -10561,27 +10598,27 @@ __metadata: languageName: node linkType: hard -"@webex/internal-plugin-mercury@npm:3.8.1-next.1": - version: 3.8.1-next.1 - resolution: "@webex/internal-plugin-mercury@npm:3.8.1-next.1" +"@webex/internal-plugin-mercury@npm:3.8.1-next.4": + version: 3.8.1-next.4 + resolution: "@webex/internal-plugin-mercury@npm:3.8.1-next.4" dependencies: - "@webex/common": "npm:3.8.1-next.1" - "@webex/common-timers": "npm:3.8.1-next.1" - "@webex/internal-plugin-device": "npm:3.8.1-next.1" - "@webex/internal-plugin-feature": "npm:3.8.1-next.1" - "@webex/internal-plugin-metrics": "npm:3.8.1-next.1" - "@webex/test-helper-chai": "npm:3.8.1-next.1" - "@webex/test-helper-mocha": "npm:3.8.1-next.1" - "@webex/test-helper-mock-web-socket": "npm:3.8.1-next.1" - "@webex/test-helper-mock-webex": "npm:3.8.1-next.1" - "@webex/test-helper-refresh-callback": "npm:3.8.1-next.1" - "@webex/test-helper-test-users": "npm:3.8.1-next.1" - "@webex/webex-core": "npm:3.8.1-next.1" + "@webex/common": "npm:3.8.1-next.4" + "@webex/common-timers": "npm:3.8.1-next.4" + "@webex/internal-plugin-device": "npm:3.8.1-next.4" + "@webex/internal-plugin-feature": "npm:3.8.1-next.4" + "@webex/internal-plugin-metrics": "npm:3.8.1-next.4" + "@webex/test-helper-chai": "npm:3.8.1-next.4" + "@webex/test-helper-mocha": "npm:3.8.1-next.4" + "@webex/test-helper-mock-web-socket": "npm:3.8.1-next.4" + "@webex/test-helper-mock-webex": "npm:3.8.1-next.4" + "@webex/test-helper-refresh-callback": "npm:3.8.1-next.4" + "@webex/test-helper-test-users": "npm:3.8.1-next.4" + "@webex/webex-core": "npm:3.8.1-next.4" backoff: "npm:^2.5.0" lodash: "npm:^4.17.21" uuid: "npm:^3.3.2" ws: "npm:^8.17.1" - checksum: 10c0/d48469249ed90d2f6f1848836d18b566a317ee455f209e9474fab2ffcf1f34044c8863dddf5d3d1c443b88aa34910bc9a60d1a552bee753b1fa92e437f9378c1 + checksum: 10c0/87cc4384519bf71e2eec4434be8c2c1607d12f60f9a627066a5b4c49b01b6ce1ed6c1782d5711567f3c6d1eee930c76c94ee34dfdae298b8705179e4a51443e9 languageName: node linkType: hard @@ -10629,21 +10666,21 @@ __metadata: languageName: node linkType: hard -"@webex/internal-plugin-metrics@npm:3.8.1-next.1": - version: 3.8.1-next.1 - resolution: "@webex/internal-plugin-metrics@npm:3.8.1-next.1" +"@webex/internal-plugin-metrics@npm:3.8.1-next.4": + version: 3.8.1-next.4 + resolution: "@webex/internal-plugin-metrics@npm:3.8.1-next.4" dependencies: - "@webex/common": "npm:3.8.1-next.1" - "@webex/common-timers": "npm:3.8.1-next.1" - "@webex/event-dictionary-ts": "npm:^1.0.1753" - "@webex/internal-plugin-metrics": "npm:3.8.1-next.1" - "@webex/test-helper-chai": "npm:3.8.1-next.1" - "@webex/test-helper-mock-webex": "npm:3.8.1-next.1" - "@webex/webex-core": "npm:3.8.1-next.1" + "@webex/common": "npm:3.8.1-next.4" + "@webex/common-timers": "npm:3.8.1-next.4" + "@webex/event-dictionary-ts": "npm:^1.0.1819" + "@webex/internal-plugin-metrics": "npm:3.8.1-next.4" + "@webex/test-helper-chai": "npm:3.8.1-next.4" + "@webex/test-helper-mock-webex": "npm:3.8.1-next.4" + "@webex/webex-core": "npm:3.8.1-next.4" ip-anonymize: "npm:^0.1.0" lodash: "npm:^4.17.21" uuid: "npm:^3.3.2" - checksum: 10c0/32d4e481345bd4e7735312107896f6e139c15133353e1312fa17dee1f7cc3355a3a988f8794f7c0847b16c008741a867b47b486eb3e5c1a0b6917518faaddb4f + checksum: 10c0/66751ad22eec8ee82c808e91778222f34d93b33eaf119aa56b21171352adaa120de9dafab983e2d376a871c272b36eafe018c954e86bcf852c498bbc12486c1d languageName: node linkType: hard @@ -10740,18 +10777,18 @@ __metadata: languageName: node linkType: hard -"@webex/internal-plugin-search@npm:3.8.1-next.1": - version: 3.8.1-next.1 - resolution: "@webex/internal-plugin-search@npm:3.8.1-next.1" +"@webex/internal-plugin-search@npm:3.8.1-next.4": + version: 3.8.1-next.4 + resolution: "@webex/internal-plugin-search@npm:3.8.1-next.4" dependencies: - "@webex/common": "npm:3.8.1-next.1" - "@webex/internal-plugin-conversation": "npm:3.8.1-next.1" - "@webex/internal-plugin-device": "npm:3.8.1-next.1" - "@webex/internal-plugin-encryption": "npm:3.8.1-next.1" - "@webex/webex-core": "npm:3.8.1-next.1" + "@webex/common": "npm:3.8.1-next.4" + "@webex/internal-plugin-conversation": "npm:3.8.1-next.4" + "@webex/internal-plugin-device": "npm:3.8.1-next.4" + "@webex/internal-plugin-encryption": "npm:3.8.1-next.4" + "@webex/webex-core": "npm:3.8.1-next.4" lodash: "npm:^4.17.21" uuid: "npm:^3.3.2" - checksum: 10c0/d2b3f6004cc8346df83cfa126d361689ff63f0197184457d25422a8d3ee852025eada074ca155f3769c328f1f2ba83d2d8a0d4b49630d0775a84ada647161f3e + checksum: 10c0/00470f649b26dc4ebb2c2fca21137b0109efb3effea4c3f0a85ef5d690e8f4d359f7b3bf6a63fed2ecce30955bc19c6e9d77a5ce01d08fdf33ba3a4544e9b121 languageName: node linkType: hard @@ -10806,20 +10843,20 @@ __metadata: languageName: node linkType: hard -"@webex/internal-plugin-support@npm:3.8.1-next.1": - version: 3.8.1-next.1 - resolution: "@webex/internal-plugin-support@npm:3.8.1-next.1" +"@webex/internal-plugin-support@npm:3.8.1-next.4": + version: 3.8.1-next.4 + resolution: "@webex/internal-plugin-support@npm:3.8.1-next.4" dependencies: - "@webex/internal-plugin-device": "npm:3.8.1-next.1" - "@webex/internal-plugin-search": "npm:3.8.1-next.1" - "@webex/test-helper-chai": "npm:3.8.1-next.1" - "@webex/test-helper-file": "npm:3.8.1-next.1" - "@webex/test-helper-mock-webex": "npm:3.8.1-next.1" - "@webex/test-helper-test-users": "npm:3.8.1-next.1" - "@webex/webex-core": "npm:3.8.1-next.1" + "@webex/internal-plugin-device": "npm:3.8.1-next.4" + "@webex/internal-plugin-search": "npm:3.8.1-next.4" + "@webex/test-helper-chai": "npm:3.8.1-next.4" + "@webex/test-helper-file": "npm:3.8.1-next.4" + "@webex/test-helper-mock-webex": "npm:3.8.1-next.4" + "@webex/test-helper-test-users": "npm:3.8.1-next.4" + "@webex/webex-core": "npm:3.8.1-next.4" lodash: "npm:^4.17.21" uuid: "npm:^3.3.2" - checksum: 10c0/c7a24bdf21dc83dc31a8a162dcf3b7c3ea324de8c9f5668811058fb1a8c8729c5e6bb7803548c19d1b17eb3abab004e4835d1833a4b11cb626461bff3de83ece + checksum: 10c0/1d69c5d4a77ff59db9b5a09fc9a25e6a2e8e884a2c38ae617ca96562ad581b93ff3a221b2fb92bb48869a4594f9dcf96e652eb8ab0bcde7f05656824fb494207 languageName: node linkType: hard @@ -10871,19 +10908,19 @@ __metadata: languageName: node linkType: hard -"@webex/internal-plugin-user@npm:3.8.1-next.1": - version: 3.8.1-next.1 - resolution: "@webex/internal-plugin-user@npm:3.8.1-next.1" +"@webex/internal-plugin-user@npm:3.8.1-next.4": + version: 3.8.1-next.4 + resolution: "@webex/internal-plugin-user@npm:3.8.1-next.4" dependencies: - "@webex/common": "npm:3.8.1-next.1" - "@webex/internal-plugin-device": "npm:3.8.1-next.1" - "@webex/test-helper-chai": "npm:3.8.1-next.1" - "@webex/test-helper-mock-webex": "npm:3.8.1-next.1" - "@webex/test-helper-test-users": "npm:3.8.1-next.1" - "@webex/webex-core": "npm:3.8.1-next.1" + "@webex/common": "npm:3.8.1-next.4" + "@webex/internal-plugin-device": "npm:3.8.1-next.4" + "@webex/test-helper-chai": "npm:3.8.1-next.4" + "@webex/test-helper-mock-webex": "npm:3.8.1-next.4" + "@webex/test-helper-test-users": "npm:3.8.1-next.4" + "@webex/webex-core": "npm:3.8.1-next.4" lodash: "npm:^4.17.21" uuid: "npm:^3.3.2" - checksum: 10c0/92362bc507f6b3828af4bb0b09e6f533b760440fa5de4b01f5c77b559d345caa91e6ad74cc7c4b7c4f6432622a502508682d7a9944888edb64428c73a85a6a5e + checksum: 10c0/6759b203bd791fe9a0601e22e20b701edef4e3d5aaefc74bdeff580fa5c0286ed01e9701e60bc66fb2287d5d80d77c647a55755a1f77debc19b2a7f75b28fdfe languageName: node linkType: hard @@ -10939,14 +10976,14 @@ __metadata: languageName: node linkType: hard -"@webex/media-helpers@npm:3.8.1-next.1": - version: 3.8.1-next.1 - resolution: "@webex/media-helpers@npm:3.8.1-next.1" +"@webex/media-helpers@npm:3.8.1-next.5": + version: 3.8.1-next.5 + resolution: "@webex/media-helpers@npm:3.8.1-next.5" dependencies: - "@webex/internal-media-core": "npm:2.17.1" + "@webex/internal-media-core": "npm:2.18.1" "@webex/ts-events": "npm:^1.1.0" "@webex/web-media-effects": "npm:2.27.1" - checksum: 10c0/d07dd651fc08d3f8ac88eb0af0b445a977a6b95f5ed4ea081df9aa2508c4896e19c24a6497e905afd89d08eb1b16b36e1f422fcaab5ea98fa4c2bcf886c2e81c + checksum: 10c0/de70a02ad8898601045a18935f736e56ebc8b50b6e7d6b35f67f486807e3cb03374aed9906ebb870e3cda7342d78b19c71a9b0c099cea1842e31b9e40fb69f70 languageName: node linkType: hard @@ -11058,20 +11095,20 @@ __metadata: languageName: node linkType: hard -"@webex/plugin-authorization-browser@npm:3.8.1-next.1": - version: 3.8.1-next.1 - resolution: "@webex/plugin-authorization-browser@npm:3.8.1-next.1" +"@webex/plugin-authorization-browser@npm:3.8.1-next.4": + version: 3.8.1-next.4 + resolution: "@webex/plugin-authorization-browser@npm:3.8.1-next.4" dependencies: - "@webex/common": "npm:3.8.1-next.1" - "@webex/internal-plugin-device": "npm:3.8.1-next.1" - "@webex/plugin-authorization-node": "npm:3.8.1-next.1" - "@webex/storage-adapter-local-storage": "npm:3.8.1-next.1" - "@webex/storage-adapter-spec": "npm:3.8.1-next.1" - "@webex/webex-core": "npm:3.8.1-next.1" + "@webex/common": "npm:3.8.1-next.4" + "@webex/internal-plugin-device": "npm:3.8.1-next.4" + "@webex/plugin-authorization-node": "npm:3.8.1-next.4" + "@webex/storage-adapter-local-storage": "npm:3.8.1-next.4" + "@webex/storage-adapter-spec": "npm:3.8.1-next.4" + "@webex/webex-core": "npm:3.8.1-next.4" jsonwebtoken: "npm:^9.0.2" lodash: "npm:^4.17.21" uuid: "npm:^3.3.2" - checksum: 10c0/b965268cc9b85dd067a636148f3031d568aba86ab07fd6eca919a4ec6e43131d17fdcbec0946b48b3d211f55d65f0125573687c2cd000875859ca8bf370b410c + checksum: 10c0/f8dd9481773cb5699920454bf0143ee6713c738aeb42350816a851811e76dc58cb57f60a2e28fc609e392652f348f11bc73bef54234eb11f2040bb7d988f2ac5 languageName: node linkType: hard @@ -11114,16 +11151,16 @@ __metadata: languageName: node linkType: hard -"@webex/plugin-authorization-node@npm:3.8.1-next.1": - version: 3.8.1-next.1 - resolution: "@webex/plugin-authorization-node@npm:3.8.1-next.1" +"@webex/plugin-authorization-node@npm:3.8.1-next.4": + version: 3.8.1-next.4 + resolution: "@webex/plugin-authorization-node@npm:3.8.1-next.4" dependencies: - "@webex/common": "npm:3.8.1-next.1" - "@webex/internal-plugin-device": "npm:3.8.1-next.1" - "@webex/webex-core": "npm:3.8.1-next.1" + "@webex/common": "npm:3.8.1-next.4" + "@webex/internal-plugin-device": "npm:3.8.1-next.4" + "@webex/webex-core": "npm:3.8.1-next.4" jsonwebtoken: "npm:^9.0.0" uuid: "npm:^3.3.2" - checksum: 10c0/e140ef79680940544e20f242acea7069633da8fc5d45b52af251f29d96511ed14ba7e119efd3da17291f2948db383597b946624a7735063cfee7eb063aa22c24 + checksum: 10c0/10cb903ce0f8fee6f18b8806d8c5d35749f35ccc74ff4ccc3c07c2194ab395513fb09e6896ba5e3bf9e4ecde47ea633e4ef855ec8449077ba48168e6c5e249a9 languageName: node linkType: hard @@ -11157,13 +11194,13 @@ __metadata: languageName: node linkType: hard -"@webex/plugin-authorization@npm:3.8.1-next.1": - version: 3.8.1-next.1 - resolution: "@webex/plugin-authorization@npm:3.8.1-next.1" +"@webex/plugin-authorization@npm:3.8.1-next.4": + version: 3.8.1-next.4 + resolution: "@webex/plugin-authorization@npm:3.8.1-next.4" dependencies: - "@webex/plugin-authorization-browser": "npm:3.8.1-next.1" - "@webex/plugin-authorization-node": "npm:3.8.1-next.1" - checksum: 10c0/2b55b95713b758745afa5cfc9ffec1d248a9694847cc4e0c07ab5bb69a427c45a33a80dda8b5fed78a2f40444b54e00d7a239d75a2600b9c24e6bcd53a803fb2 + "@webex/plugin-authorization-browser": "npm:3.8.1-next.4" + "@webex/plugin-authorization-node": "npm:3.8.1-next.4" + checksum: 10c0/0ac645d9ce7ec019cfff659b6fd637107fa63a3c68b2d09907257532312b9a08ca0e0cd8e9d729fb6ddd89cdaed5b767ece5f9830fb2831799823583b4006a1d languageName: node linkType: hard @@ -11185,21 +11222,21 @@ __metadata: languageName: node linkType: hard -"@webex/plugin-cc@npm:3.8.1-next.6": - version: 3.8.1-next.6 - resolution: "@webex/plugin-cc@npm:3.8.1-next.6" +"@webex/plugin-cc@npm:3.8.1-next.12": + version: 3.8.1-next.12 + resolution: "@webex/plugin-cc@npm:3.8.1-next.12" dependencies: "@types/platform": "npm:1.3.4" - "@webex/calling": "npm:3.8.1-next.2" - "@webex/internal-plugin-mercury": "npm:3.8.1-next.1" - "@webex/internal-plugin-metrics": "npm:3.8.1-next.1" - "@webex/internal-plugin-support": "npm:3.8.1-next.1" - "@webex/plugin-authorization": "npm:3.8.1-next.1" - "@webex/plugin-logger": "npm:3.8.1-next.1" - "@webex/webex-core": "npm:3.8.1-next.1" + "@webex/calling": "npm:3.8.1-next.6" + "@webex/internal-plugin-mercury": "npm:3.8.1-next.4" + "@webex/internal-plugin-metrics": "npm:3.8.1-next.4" + "@webex/internal-plugin-support": "npm:3.8.1-next.4" + "@webex/plugin-authorization": "npm:3.8.1-next.4" + "@webex/plugin-logger": "npm:3.8.1-next.4" + "@webex/webex-core": "npm:3.8.1-next.4" jest-html-reporters: "npm:3.0.11" lodash: "npm:^4.17.21" - checksum: 10c0/975cfa38010796c03d20e6b8af9e9ee7f717bee3a46bb9333a77349ec825c235b6494a65752ca73c70c4e34f57e59670358b77ab73480870c363ef7948ab193d + checksum: 10c0/add57a8c72d9b8cfcd5d3bd6ab383d9eb6caa9ca6e1d5dc3b2ef1f0e98227d198f9bc73ceb098a0d0e137eaffaa7a7e21091447f5fb14865bd5734246b644f20 languageName: node linkType: hard @@ -11309,17 +11346,17 @@ __metadata: languageName: node linkType: hard -"@webex/plugin-logger@npm:3.8.1-next.1": - version: 3.8.1-next.1 - resolution: "@webex/plugin-logger@npm:3.8.1-next.1" +"@webex/plugin-logger@npm:3.8.1-next.4": + version: 3.8.1-next.4 + resolution: "@webex/plugin-logger@npm:3.8.1-next.4" dependencies: - "@webex/common": "npm:3.8.1-next.1" - "@webex/test-helper-chai": "npm:3.8.1-next.1" - "@webex/test-helper-mocha": "npm:3.8.1-next.1" - "@webex/test-helper-mock-webex": "npm:3.8.1-next.1" - "@webex/webex-core": "npm:3.8.1-next.1" + "@webex/common": "npm:3.8.1-next.4" + "@webex/test-helper-chai": "npm:3.8.1-next.4" + "@webex/test-helper-mocha": "npm:3.8.1-next.4" + "@webex/test-helper-mock-webex": "npm:3.8.1-next.4" + "@webex/webex-core": "npm:3.8.1-next.4" lodash: "npm:^4.17.21" - checksum: 10c0/06fba86d69dc7a159dcad19f79ecf77a8daa31f3432e5245a1d1d7c3521564436d707221b70428443f89de163791aafbc3d079b4332441a43c0428968c2283e2 + checksum: 10c0/d7b984c4578a5b87d5d28c133e5cfcc21c4ce1694eb6b1490d4e8e247843cb0a535cb87f2f4dfe7e4411857be769807f91cc1af05a4c62c83a7cf1cce18afe44 languageName: node linkType: hard @@ -11790,14 +11827,14 @@ __metadata: languageName: node linkType: hard -"@webex/storage-adapter-local-storage@npm:3.8.1-next.1": - version: 3.8.1-next.1 - resolution: "@webex/storage-adapter-local-storage@npm:3.8.1-next.1" +"@webex/storage-adapter-local-storage@npm:3.8.1-next.4": + version: 3.8.1-next.4 + resolution: "@webex/storage-adapter-local-storage@npm:3.8.1-next.4" dependencies: - "@webex/storage-adapter-spec": "npm:3.8.1-next.1" - "@webex/test-helper-mocha": "npm:3.8.1-next.1" - "@webex/webex-core": "npm:3.8.1-next.1" - checksum: 10c0/97b5f6ae23f1e4d6ed97c92705f1275d3490e60e73158b89840e015b307f318da2222c86e917dfc7bb8977777bc1d7186b67fef51b19f6eaafd07ee8f1c10bf9 + "@webex/storage-adapter-spec": "npm:3.8.1-next.4" + "@webex/test-helper-mocha": "npm:3.8.1-next.4" + "@webex/webex-core": "npm:3.8.1-next.4" + checksum: 10c0/beb0eddfa992d8da64c16ef7ebf87864eddc17e9adeb9835538180b0d9e479e8fb68cc770fb6d06146a9a1fe76ff3a0529206a6aa93f59c4ce6d994ed1029281 languageName: node linkType: hard @@ -11828,15 +11865,46 @@ __metadata: languageName: node linkType: hard -"@webex/storage-adapter-spec@npm:3.8.1-next.1": - version: 3.8.1-next.1 - resolution: "@webex/storage-adapter-spec@npm:3.8.1-next.1" +"@webex/storage-adapter-spec@npm:3.8.1-next.4": + version: 3.8.1-next.4 + resolution: "@webex/storage-adapter-spec@npm:3.8.1-next.4" dependencies: - "@webex/test-helper-chai": "npm:3.8.1-next.1" - checksum: 10c0/d95acb80b5e2bc477ffbfeb7dc4b47d3b62855c65f4f5fcff103dfdba90e047f283095f968362f8d5f03aa7c01dff83542e488111c789ce61974ef818fd8af40 + "@webex/test-helper-chai": "npm:3.8.1-next.4" + checksum: 10c0/74bd9539a229aaf2cd52701fd234f6da7b2de69c6a0be086a277ed37b9b7e7a5719d8e54537290bb2d99d5f93bd3624028bd7804798606da6a04c777b71d1ee0 languageName: node linkType: hard +"@webex/test-fixtures@workspace:*, @webex/test-fixtures@workspace:packages/contact-center/test-fixtures": + version: 0.0.0-use.local + resolution: "@webex/test-fixtures@workspace:packages/contact-center/test-fixtures" + dependencies: + "@babel/core": "npm:7.25.2" + "@babel/preset-env": "npm:7.25.4" + "@babel/preset-react": "npm:7.24.7" + "@babel/preset-typescript": "npm:7.25.9" + "@eslint/js": "npm:^9.20.0" + "@webex/cc-store": "workspace:*" + babel-loader: "npm:9.2.1" + eslint: "npm:^9.20.1" + eslint-config-prettier: "npm:^10.0.1" + eslint-config-standard: "npm:^17.1.0" + eslint-plugin-import: "npm:^2.25.2" + eslint-plugin-n: "npm:^15.0.0 || ^16.0.0 " + eslint-plugin-prettier: "npm:^5.2.3" + eslint-plugin-promise: "npm:^6.0.0" + eslint-plugin-react: "npm:^7.37.4" + file-loader: "npm:6.2.0" + globals: "npm:^16.0.0" + prettier: "npm:^3.5.1" + ts-loader: "npm:9.5.1" + typescript: "npm:5.6.3" + typescript-eslint: "npm:^8.24.1" + webpack: "npm:5.94.0" + webpack-cli: "npm:5.1.4" + webpack-merge: "npm:6.0.1" + languageName: unknown + linkType: soft + "@webex/test-helper-chai@npm:2.60.2": version: 2.60.2 resolution: "@webex/test-helper-chai@npm:2.60.2" @@ -11870,14 +11938,14 @@ __metadata: languageName: node linkType: hard -"@webex/test-helper-chai@npm:3.8.1-next.1": - version: 3.8.1-next.1 - resolution: "@webex/test-helper-chai@npm:3.8.1-next.1" +"@webex/test-helper-chai@npm:3.8.1-next.4": + version: 3.8.1-next.4 + resolution: "@webex/test-helper-chai@npm:3.8.1-next.4" dependencies: - "@webex/test-helper-file": "npm:3.8.1-next.1" + "@webex/test-helper-file": "npm:3.8.1-next.4" check-error: "npm:^1.0.2" lodash: "npm:^4.17.21" - checksum: 10c0/74d4de27edbb4f847977c8a4d95aafd8f2668a69f95c49c925f5bcbf8a02572a09f545a344f26fef0da4b61bc6e565d30916bb60a191779ec3a16b84d814549e + checksum: 10c0/47828f5026ef042098b3fd589c32a92447f7a840930c1f14d179608c70355d0086b4234a174abbb960847e366200c421cde4ed443cac4e682f46185c71d14b82 languageName: node linkType: hard @@ -11920,16 +11988,16 @@ __metadata: languageName: node linkType: hard -"@webex/test-helper-file@npm:3.8.1-next.1": - version: 3.8.1-next.1 - resolution: "@webex/test-helper-file@npm:3.8.1-next.1" +"@webex/test-helper-file@npm:3.8.1-next.4": + version: 3.8.1-next.4 + resolution: "@webex/test-helper-file@npm:3.8.1-next.4" dependencies: - "@webex/common": "npm:3.8.1-next.1" - "@webex/test-helper-make-local-url": "npm:3.8.1-next.1" + "@webex/common": "npm:3.8.1-next.4" + "@webex/test-helper-make-local-url": "npm:3.8.1-next.4" es6-promise: "npm:^4.2.8" file-type: "npm:^16.0.1" xhr: "npm:^2.5.0" - checksum: 10c0/0916498ea6749bab8a1e1dfce2837f3caf0cfa4b0f8620a9a876f01f03e0720ece7641e6acc9c1b812f491e3479919e466cd9c3717a363ec9456a5ac04cb694d + checksum: 10c0/8e5c875a5f938fdebc769b3acfc232937e383bb8798c5af3eb4c252531a37845b639cad61bc4bf1a6177b6dfbade9d1a3621721754625f7cb6d1b5cad93cac7f languageName: node linkType: hard @@ -11954,10 +12022,10 @@ __metadata: languageName: node linkType: hard -"@webex/test-helper-make-local-url@npm:3.8.1-next.1": - version: 3.8.1-next.1 - resolution: "@webex/test-helper-make-local-url@npm:3.8.1-next.1" - checksum: 10c0/0bccc08f171cb41c329e59e3d0a34a8715de79ac19e63d4258e5068936db25948e801fac96ac55528f9f11e0bb72d65fbe49e1e71527c4721784f8042daeb8eb +"@webex/test-helper-make-local-url@npm:3.8.1-next.4": + version: 3.8.1-next.4 + resolution: "@webex/test-helper-make-local-url@npm:3.8.1-next.4" + checksum: 10c0/7456f09f6e62ff9f021d7ef38e41a2d5137892b26d8586ab7c85d90eca125b44192971f32fce11bb1522d960a7eb6e5ad7fa149d215fba007127109634a00f98 languageName: node linkType: hard @@ -11988,12 +12056,12 @@ __metadata: languageName: node linkType: hard -"@webex/test-helper-mocha@npm:3.8.1-next.1": - version: 3.8.1-next.1 - resolution: "@webex/test-helper-mocha@npm:3.8.1-next.1" +"@webex/test-helper-mocha@npm:3.8.1-next.4": + version: 3.8.1-next.4 + resolution: "@webex/test-helper-mocha@npm:3.8.1-next.4" dependencies: bowser: "npm:^2.11.0" - checksum: 10c0/55286e8d5334858174c32ee13c5fd439fe003689c2c37f9c5bb252f5ed94e8e6feca86b755b2269ba35bfd608c0548051761e39dd49fa4fcf908a3ca97663f12 + checksum: 10c0/b8c7194839db850fd26547e6ec27a8f25b675e8e416c7ec1e51d30d0edca33fe2da555b3dbc28fedcafd308b4404f247703f501cd59d68cd1b9226f210e71b59 languageName: node linkType: hard @@ -12018,10 +12086,10 @@ __metadata: languageName: node linkType: hard -"@webex/test-helper-mock-web-socket@npm:3.8.1-next.1": - version: 3.8.1-next.1 - resolution: "@webex/test-helper-mock-web-socket@npm:3.8.1-next.1" - checksum: 10c0/ff6eabbaafd34ee7fe4bc815c28b1c2c1fcc1e90971871545d3d4a705f8598c416019b83b99c16c33d94e99d23d533cf8ac23a9af5759b34df6b9638f8130f9e +"@webex/test-helper-mock-web-socket@npm:3.8.1-next.4": + version: 3.8.1-next.4 + resolution: "@webex/test-helper-mock-web-socket@npm:3.8.1-next.4" + checksum: 10c0/3c66571214b90c07327ce2182bfc62da3081b81856b1ea1ee80f9ed520beba128e1d96e709aec9dfaae262b73b66d2b9dc3ce4e597f6b0a43f196f4499675a24 languageName: node linkType: hard @@ -12058,14 +12126,14 @@ __metadata: languageName: node linkType: hard -"@webex/test-helper-mock-webex@npm:3.8.1-next.1": - version: 3.8.1-next.1 - resolution: "@webex/test-helper-mock-webex@npm:3.8.1-next.1" +"@webex/test-helper-mock-webex@npm:3.8.1-next.4": + version: 3.8.1-next.4 + resolution: "@webex/test-helper-mock-webex@npm:3.8.1-next.4" dependencies: ampersand-state: "npm:^5.0.3" es6-promise: "npm:^4.2.8" lodash: "npm:^4.17.21" - checksum: 10c0/6df4cfae1b92d7f20df034eda6b036f15314890f3ae204ea1e6eb166ab04fd3eebcb59d9902853ffb96f85dbd9c14d0ea1b03980d8a39f627ccb5a09801f9870 + checksum: 10c0/2e130bfb81830ea4de20a74d985042aaa952dd048911a82faf0148ed471b94e6a1dbc91bc9abea21e647d81319eb15be177ef5ee50d7b96c1fa9d525aec873e3 languageName: node linkType: hard @@ -12090,10 +12158,10 @@ __metadata: languageName: node linkType: hard -"@webex/test-helper-refresh-callback@npm:3.8.1-next.1": - version: 3.8.1-next.1 - resolution: "@webex/test-helper-refresh-callback@npm:3.8.1-next.1" - checksum: 10c0/d6a40b46c230ed3d34722ded09beb4ccf4290285e1cbb7d9fbe09e3db11bfdbbf87c24e8c49b3081d641d5316ac5d9e8861e3dbf748aff33a0230ac8762506b8 +"@webex/test-helper-refresh-callback@npm:3.8.1-next.4": + version: 3.8.1-next.4 + resolution: "@webex/test-helper-refresh-callback@npm:3.8.1-next.4" + checksum: 10c0/3a48fb3e9f64501c8281571225b65d2685aada01cd7f6158e828fe6bb985bda6276e6afe48d63401590124f73e2abf8fe8c384a80a4351f595ef0b0e212ad3ef languageName: node linkType: hard @@ -12124,12 +12192,12 @@ __metadata: languageName: node linkType: hard -"@webex/test-helper-retry@npm:3.8.1-next.1": - version: 3.8.1-next.1 - resolution: "@webex/test-helper-retry@npm:3.8.1-next.1" +"@webex/test-helper-retry@npm:3.8.1-next.4": + version: 3.8.1-next.4 + resolution: "@webex/test-helper-retry@npm:3.8.1-next.4" dependencies: es6-promise: "npm:^4.2.8" - checksum: 10c0/378ed0935abc4513a40174972970af4873a1e468fd58091a0832735f9eb5f9e320fc2d04e84486a8e9fab5851f1592a977465acb2fc665cda67e5b7f65dd4b84 + checksum: 10c0/4be623decdbeac2ef4e1afc06bf1138f9f33d07cd3e366a052b9d4dcb5453f717a8e16cf3dd62b8296b5e325b9c04bc112b9f1cba55dad9ba3eace48c63bb18e languageName: node linkType: hard @@ -12178,18 +12246,18 @@ __metadata: languageName: node linkType: hard -"@webex/test-helper-test-users@npm:3.8.1-next.1": - version: 3.8.1-next.1 - resolution: "@webex/test-helper-test-users@npm:3.8.1-next.1" +"@webex/test-helper-test-users@npm:3.8.1-next.4": + version: 3.8.1-next.4 + resolution: "@webex/test-helper-test-users@npm:3.8.1-next.4" dependencies: "@ciscospark/test-users-legacy": "npm:^1.0.2" - "@webex/test-helper-retry": "npm:3.8.1-next.1" - "@webex/test-users": "npm:3.8.1-next.1" + "@webex/test-helper-retry": "npm:3.8.1-next.4" + "@webex/test-users": "npm:3.8.1-next.4" lodash: "npm:^4.17.21" dependenciesMeta: "@ciscospark/test-users-legacy": optional: true - checksum: 10c0/6f1135aea10a73f704d5e3267caec7b2dc0c1215cb89585db50454c7834eccdbb06d73716cb147dba68535a6587625f45f7958560dfd69bf1d078a068bae1117 + checksum: 10c0/24eaaf6e298b5250a3628d3476818e482525bf6f1ccae6d50284579fd2559582be5de54233192e1f8b2d7f2154c5e0ae0f3de29f4e2b672bda5964d66e222554 languageName: node linkType: hard @@ -12235,17 +12303,17 @@ __metadata: languageName: node linkType: hard -"@webex/test-users@npm:3.8.1-next.1": - version: 3.8.1-next.1 - resolution: "@webex/test-users@npm:3.8.1-next.1" +"@webex/test-users@npm:3.8.1-next.4": + version: 3.8.1-next.4 + resolution: "@webex/test-users@npm:3.8.1-next.4" dependencies: - "@webex/http-core": "npm:3.8.1-next.1" - "@webex/test-helper-mocha": "npm:3.8.1-next.1" + "@webex/http-core": "npm:3.8.1-next.4" + "@webex/test-helper-mocha": "npm:3.8.1-next.4" btoa: "npm:^1.2.1" lodash: "npm:^4.17.21" node-random-name: "npm:^1.0.1" uuid: "npm:^3.3.2" - checksum: 10c0/56e7f8271034d59a4760ff2e373170e32f66748e98865a3d2ac9531bb2708c4fa36af77851185bcce641b2555a3f1a5d18f645b38640d6ee9d57705b55adf83d + checksum: 10c0/3dceb67e2ffb7bce951bfb0b7a646a084eb86b3bb3594add40c9abbe8c21d6b22e12aa556a82474594f12a77f48b60c29f41e9399e6eacec8709d4658fb2fc67 languageName: node linkType: hard @@ -12323,6 +12391,25 @@ __metadata: languageName: node linkType: hard +"@webex/web-client-media-engine@npm:3.33.1": + version: 3.33.1 + resolution: "@webex/web-client-media-engine@npm:3.33.1" + dependencies: + "@webex/json-multistream": "npm:^2.2.1" + "@webex/rtcstats": "npm:^1.5.0" + "@webex/ts-events": "npm:^1.0.1" + "@webex/ts-sdp": "npm:1.8.1" + "@webex/web-capabilities": "npm:^1.4.0" + "@webex/web-media-effects": "npm:2.18.1" + "@webex/webrtc-core": "npm:2.13.0" + async: "npm:^3.2.4" + js-logger: "npm:^1.6.1" + typed-emitter: "npm:^2.1.0" + uuid: "npm:^8.3.2" + checksum: 10c0/5f8be3be08837222536a8fdfb29a183391d14b82b7df91b8c710eed4a5d020783755a6056e13c6e42fb841664f92bd4f3c8d767b2036306348466fa75944c39f + languageName: node + linkType: hard + "@webex/web-media-effects@npm:2.18.1": version: 2.18.1 resolution: "@webex/web-media-effects@npm:2.18.1" @@ -12417,16 +12504,16 @@ __metadata: languageName: node linkType: hard -"@webex/webex-core@npm:3.8.1-next.1": - version: 3.8.1-next.1 - resolution: "@webex/webex-core@npm:3.8.1-next.1" +"@webex/webex-core@npm:3.8.1-next.4": + version: 3.8.1-next.4 + resolution: "@webex/webex-core@npm:3.8.1-next.4" dependencies: - "@webex/common": "npm:3.8.1-next.1" - "@webex/common-timers": "npm:3.8.1-next.1" - "@webex/http-core": "npm:3.8.1-next.1" - "@webex/internal-plugin-device": "npm:3.8.1-next.1" - "@webex/plugin-logger": "npm:3.8.1-next.1" - "@webex/storage-adapter-spec": "npm:3.8.1-next.1" + "@webex/common": "npm:3.8.1-next.4" + "@webex/common-timers": "npm:3.8.1-next.4" + "@webex/http-core": "npm:3.8.1-next.4" + "@webex/internal-plugin-device": "npm:3.8.1-next.4" + "@webex/plugin-logger": "npm:3.8.1-next.4" + "@webex/storage-adapter-spec": "npm:3.8.1-next.4" ampersand-collection: "npm:^2.0.2" ampersand-events: "npm:^2.0.2" ampersand-state: "npm:^5.0.3" @@ -12435,7 +12522,7 @@ __metadata: jsonwebtoken: "npm:^9.0.0" lodash: "npm:^4.17.21" uuid: "npm:^3.3.2" - checksum: 10c0/1e7dcb23cdb75c1221e9f9e9b8de21e820956395563a8d7f47f1e1ab372f32b15b12b725fe3db9cf0f550ae3b9eb7c5dc97c4254389f142badaabc51bac74ddd + checksum: 10c0/975f2d65eba07162d7ec4d2f7a24798050bc2b6cf3cdb652e8fd9c95966b4f0dd2669ddeb154a0618c0265ea1d19562f450e5ec000d76a9a6ef57a789f47f61e languageName: node linkType: hard