From 163c37ec3940fce6d33649d37943704f015bc8ed Mon Sep 17 00:00:00 2001 From: Kesava Krishnan Madavan Date: Tue, 7 Jan 2025 20:25:10 +0530 Subject: [PATCH 1/4] feat(cc-store): add logger from sdk --- .../station-login/src/helper.ts | 11 ++- .../station-login/src/station-login/index.tsx | 4 +- .../src/station-login/station-login.types.ts | 9 ++- .../station-login/tests/helper.ts | 81 +++++++++++++------ packages/contact-center/store/src/store.ts | 10 ++- .../contact-center/store/src/store.types.ts | 20 +++-- packages/contact-center/store/tests/store.ts | 12 ++- .../contact-center/user-state/tests/helper.ts | 16 ++-- 8 files changed, 114 insertions(+), 49 deletions(-) diff --git a/packages/contact-center/station-login/src/helper.ts b/packages/contact-center/station-login/src/helper.ts index 54572129b..201b75537 100644 --- a/packages/contact-center/station-login/src/helper.ts +++ b/packages/contact-center/station-login/src/helper.ts @@ -6,6 +6,7 @@ export const useStationLogin = (props: UseStationLoginProps) => { const cc = props.cc; const loginCb = props.onLogin; const logoutCb = props.onLogout; + const logger = props.logger; const [dialNumber, setDialNumber] = useState(''); const [deviceType, setDeviceType] = useState(''); const [team, setTeam] = useState(''); @@ -21,7 +22,10 @@ export const useStationLogin = (props: UseStationLoginProps) => { loginCb(); } }).catch((error: Error) => { - console.error(error); + logger.error(`Error logging in: ${error}`, { + module: 'widget-station-login#helper.ts', + method: 'login', + }); setLoginFailure(error); }); }; @@ -34,7 +38,10 @@ export const useStationLogin = (props: UseStationLoginProps) => { logoutCb(); } }).catch((error: Error) => { - console.error(error); + logger.error(`Error logging out: ${error}`, { + 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 9431768d5..c8c591979 100644 --- a/packages/contact-center/station-login/src/station-login/index.tsx +++ b/packages/contact-center/station-login/src/station-login/index.tsx @@ -7,8 +7,8 @@ import {useStationLogin} from '../helper'; import {StationLoginProps} from './station-login.types'; const StationLogin: React.FunctionComponent = observer(({onLogin, onLogout}) => { - const {cc, teams, loginOptions} = store; - const result = useStationLogin({cc, onLogin, onLogout}); + const {cc, teams, loginOptions, logger} = store; + const result = useStationLogin({cc, onLogin, onLogout, logger}); const props = { ...result, 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 94cb813f0..0aee51be7 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 @@ -1,4 +1,4 @@ -import {AgentLogin, IContactCenter, StationLoginSuccess, StationLogoutSuccess, Team} from '@webex/plugin-cc'; +import {AgentLogin, IContactCenter, StationLoginSuccess, StationLogoutSuccess, Team, ILogger} from '@webex/plugin-cc'; /** * Interface representing the properties for the Station Login component. */ @@ -72,10 +72,15 @@ export interface IStationLoginProps { * Handler to set the selected agent team */ setTeam: (team: string) => void; + + /** + * The logger instance from SDK + */ + logger: ILogger; } export type StationLoginPresentationalProps = Pick; -export type UseStationLoginProps = Pick; +export type UseStationLoginProps = Pick; export type StationLoginProps = Pick; \ No newline at end of file diff --git a/packages/contact-center/station-login/tests/helper.ts b/packages/contact-center/station-login/tests/helper.ts index 501b724b1..a2028e71b 100644 --- a/packages/contact-center/station-login/tests/helper.ts +++ b/packages/contact-center/station-login/tests/helper.ts @@ -16,11 +16,17 @@ const loginParams = { const loginCb = jest.fn(); const logoutCb = jest.fn(); +const logger = { + error: jest.fn() +}; describe('useStationLogin Hook', () => { afterEach(() => { jest.clearAllMocks(); + loginCb.mockClear(); + logoutCb.mockClear(); + logger.error.mockClear(); }); it('should set loginSuccess on successful login', async () => { @@ -50,18 +56,20 @@ describe('useStationLogin Hook', () => { ccMock.stationLogin.mockResolvedValue(successResponse); const { result } = renderHook(() => - useStationLogin({cc: ccMock, onLogin: loginCb, onLogout: logoutCb}) + useStationLogin({cc: ccMock, onLogin: loginCb, onLogout: logoutCb, logger}) ); - result.current.setDeviceType(loginParams.loginOption); - result.current.setDialNumber(loginParams.dialNumber); - result.current.setTeam(loginParams.teamId); - act(() => { - result.current.login(); + result.current.setDeviceType(loginParams.loginOption); + result.current.setDialNumber(loginParams.dialNumber); + result.current.setTeam(loginParams.teamId); + }); + + await act(async () => { + await result.current.login(); }); - waitFor(() => { + await waitFor(async () => { expect(ccMock.stationLogin).toHaveBeenCalledWith({ teamId: loginParams.teamId, loginOption: loginParams.loginOption, @@ -88,14 +96,14 @@ describe('useStationLogin Hook', () => { ccMock.stationLogin.mockResolvedValue({}); const { result } = renderHook(() => - useStationLogin({cc: ccMock, onLogout: logoutCb}) + useStationLogin({cc: ccMock, onLogout: logoutCb, logger}) ); - act(() => { - result.current.login(); + await act(async () => { + await result.current.login(); }); - waitFor(() => { + await waitFor(() => { expect(loginCb).not.toHaveBeenCalled(); }); }); @@ -106,18 +114,20 @@ describe('useStationLogin Hook', () => { loginCb.mockClear(); const { result } = renderHook(() => - useStationLogin({cc: ccMock, onLogin: loginCb, onLogout: logoutCb}) + useStationLogin({cc: ccMock, onLogin: loginCb, onLogout: logoutCb, logger}) ); - result.current.setDeviceType(loginParams.loginOption); - result.current.setDialNumber(loginParams.dialNumber); - result.current.setTeam(loginParams.teamId); - act(() => { - result.current.login(); + result.current.setDeviceType(loginParams.loginOption); + result.current.setDialNumber(loginParams.dialNumber); + result.current.setTeam(loginParams.teamId); + }); + + await act(async () => { + await result.current.login(); }); - waitFor(() => { + await waitFor(() => { expect(ccMock.stationLogin).toHaveBeenCalledWith({ teamId: loginParams.teamId, loginOption: loginParams.loginOption, @@ -159,14 +169,14 @@ describe('useStationLogin Hook', () => { ccMock.stationLogout.mockResolvedValue(successResponse); const {result} = renderHook(() => - useStationLogin({cc: ccMock, onLogin: loginCb, onLogout: logoutCb}) + useStationLogin({cc: ccMock, onLogin: loginCb, onLogout: logoutCb, logger}) ); - act(() => { - result.current.logout(); + await act(async () => { + await result.current.logout(); }); - waitFor(() => { + await waitFor(() => { expect(ccMock.stationLogout).toHaveBeenCalledWith({logoutReason: 'User requested logout'}); expect(logoutCb).toHaveBeenCalledWith(); @@ -185,18 +195,37 @@ describe('useStationLogin Hook', () => { }); }); + it('should log error on logout failure', async () => { + ccMock.stationLogout.mockRejectedValue(new Error('Logout failed')); + + const {result} = renderHook(() => + useStationLogin({cc: ccMock, onLogin: loginCb, onLogout: logoutCb, logger}) + ); + + await act(async () => { + await result.current.logout(); + }); + + await waitFor(() => { + expect(logger.error).toHaveBeenCalledWith('Error logging out: Error: Logout failed', { + module: 'widget-station-login#helper.ts', + method: 'logout', + }); + }); + }); + it('should not call logout callback if not present', async () => { ccMock.stationLogout.mockResolvedValue({}); const {result} = renderHook(() => - useStationLogin({cc: ccMock, onLogin: loginCb}) + useStationLogin({cc: ccMock, onLogin: loginCb, logger}) ); - act(() => { - result.current.logout(); + await act(async () => { + await result.current.logout(); }); - waitFor(() => { + await waitFor(() => { expect(logoutCb).not.toHaveBeenCalled(); }); }); diff --git a/packages/contact-center/store/src/store.ts b/packages/contact-center/store/src/store.ts index f654a122b..b236192b6 100644 --- a/packages/contact-center/store/src/store.ts +++ b/packages/contact-center/store/src/store.ts @@ -7,13 +7,15 @@ import { WithWebex, IdleCode, InitParams, - IStore + IStore, + ILogger } from './store.types'; class Store implements IStore { teams: Team[] = []; loginOptions: string[] = []; cc: IContactCenter; + logger: ILogger; idleCodes: IdleCode[] = []; agentId: string = ''; @@ -23,13 +25,17 @@ class Store implements IStore { registerCC(webex: WithWebex['webex']): Promise { this.cc = webex.cc; + this.logger = this.cc.LoggerProxy; return this.cc.register().then((response: Profile) => { this.teams = response.teams; this.loginOptions = response.loginVoiceOptions; this.idleCodes = response.idleCodes; this.agentId = response.agentId; }).catch((error) => { - console.error('Error registering contact center', error); + this.logger.error(`Error registering contact center: ${error}`, { + module: 'cc-store#store.ts', + method: 'registerCC', + }); return Promise.reject(error); }); } diff --git a/packages/contact-center/store/src/store.types.ts b/packages/contact-center/store/src/store.types.ts index f5f3d31e9..1e8b3c6ce 100644 --- a/packages/contact-center/store/src/store.types.ts +++ b/packages/contact-center/store/src/store.types.ts @@ -1,7 +1,15 @@ -import {AgentLogin, IContactCenter, Profile, Team} from '@webex/plugin-cc'; +import {AgentLogin, IContactCenter, Profile, Team, LogContext} from '@webex/plugin-cc'; + +type ILogger = { + log: (message: string, context?: LogContext) => void; + info: (message: string, context?: LogContext) => void; + warn: (message: string, context?: LogContext) => void; + trace: (message: string, context?: LogContext) => void; + error: (message: string, context?: LogContext) => void; +} interface WithWebex { - webex: { cc: IContactCenter }; + webex: { cc: IContactCenter, logger: ILogger }; } interface WithWebexConfig { @@ -24,11 +32,12 @@ interface IStore { cc: IContactCenter; idleCodes: IdleCode[]; agentId: string; - + logger: ILogger; registerCC(webex: WithWebex['webex']): Promise; init(params: InitParams): Promise; } + export type { IContactCenter, Profile, @@ -37,5 +46,6 @@ export type { WithWebex, IdleCode, InitParams, - IStore -} \ No newline at end of file + IStore, + ILogger, +} diff --git a/packages/contact-center/store/tests/store.ts b/packages/contact-center/store/tests/store.ts index d0bf75dbf..a0f6d6029 100644 --- a/packages/contact-center/store/tests/store.ts +++ b/packages/contact-center/store/tests/store.ts @@ -17,7 +17,10 @@ jest.mock('webex', () => ({ } }), cc: { - register: jest.fn() + register: jest.fn(), + LoggerProxy: { + error: jest.fn() + } } })) })); @@ -62,7 +65,6 @@ describe('Store', () => { }); it('should log an error on failed register', async () => { - const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(); const mockError = new Error('Register failed'); mockWebex.cc.register.mockRejectedValue(mockError); @@ -71,8 +73,10 @@ describe('Store', () => { } catch (error) { expect(error).toEqual(mockError); - expect(consoleErrorSpy).toHaveBeenCalledWith('Error registering contact center', mockError); - consoleErrorSpy.mockRestore(); + expect(store.logger.error).toHaveBeenCalledWith("Error registering contact center: Error: Register failed", { + "method": "registerCC", + "module": "cc-store#store.ts", + }); } }); }); diff --git a/packages/contact-center/user-state/tests/helper.ts b/packages/contact-center/user-state/tests/helper.ts index 27b574f77..ab2d47736 100644 --- a/packages/contact-center/user-state/tests/helper.ts +++ b/packages/contact-center/user-state/tests/helper.ts @@ -54,8 +54,8 @@ describe('useUserState Hook', () => { mockCC.setAgentState.mockResolvedValueOnce({}); const { result } = renderHook(() => useUserState({ idleCodes, agentId, cc: mockCC })); - act(() => { - result.current.setAgentStatus(idleCodes[1]); + await act(async () => { + await result.current.setAgentStatus(idleCodes[1]); jest.advanceTimersByTime(3000); }); @@ -67,12 +67,16 @@ describe('useUserState Hook', () => { it('should handle setAgentStatus correctly and update current state', async () => { mockCC.setAgentState.mockResolvedValueOnce({}); const { result } = renderHook(() => useUserState({ idleCodes, agentId, cc: mockCC })); + let agentStatusPromise; act(() => { - result.current.setAgentStatus(idleCodes[1]); + agentStatusPromise = result.current.setAgentStatus(idleCodes[1]); }); - expect(result.current.isSettingAgentStatus).toBe(true); + await act(async () => { + expect(result.current.isSettingAgentStatus).toBe(true); + await agentStatusPromise; + }); await waitFor(() => { expect(result.current).toMatchObject({ @@ -88,8 +92,8 @@ describe('useUserState Hook', () => { mockCC.setAgentState.mockRejectedValueOnce(new Error(errorMsg)); const { result } = renderHook(() => useUserState({ idleCodes, agentId, cc: mockCC })); - act(() => { - result.current.setAgentStatus(idleCodes[1]); + await act(async () => { + await result.current.setAgentStatus(idleCodes[1]); }); await waitFor(() => { From 44de0707f2025d9f155fb4654c4bb0d338f65d0e Mon Sep 17 00:00:00 2001 From: Kesava Krishnan Madavan Date: Wed, 8 Jan 2025 15:19:59 +0530 Subject: [PATCH 2/4] fix(cc-store): update sdk with logger proxy attachment --- packages/contact-center/store/package.json | 2 +- yarn.lock | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/contact-center/store/package.json b/packages/contact-center/store/package.json index b20af9c42..53d2ee89b 100644 --- a/packages/contact-center/store/package.json +++ b/packages/contact-center/store/package.json @@ -22,7 +22,7 @@ "react": "18.3.1", "react-dom": "18.3.1", "typescript": "5.6.3", - "webex": "3.7.0-wxcc.3" + "webex": "3.7.0-wxcc.6" }, "devDependencies": { "@babel/core": "7.25.2", diff --git a/yarn.lock b/yarn.lock index 88d26d11f..288a3ae99 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5435,7 +5435,7 @@ __metadata: react-dom: "npm:18.3.1" ts-loader: "npm:9.5.1" typescript: "npm:5.6.3" - webex: "npm:3.7.0-wxcc.3" + webex: "npm:3.7.0-wxcc.6" webpack: "npm:5.94.0" webpack-cli: "npm:5.1.4" webpack-merge: "npm:6.0.1" @@ -6382,9 +6382,9 @@ __metadata: languageName: node linkType: hard -"@webex/plugin-cc@npm:3.5.0-wxcc.9": - version: 3.5.0-wxcc.9 - resolution: "@webex/plugin-cc@npm:3.5.0-wxcc.9" +"@webex/plugin-cc@npm:3.5.0-wxcc.12": + version: 3.5.0-wxcc.12 + resolution: "@webex/plugin-cc@npm:3.5.0-wxcc.12" dependencies: "@types/platform": "npm:1.3.4" "@webex/calling": "npm:3.6.0-wxcc.1" @@ -6392,7 +6392,7 @@ __metadata: "@webex/webex-core": "npm:3.5.0-wxcc.1" buffer: "npm:6.0.3" jest-html-reporters: "npm:3.0.11" - checksum: 10c0/238585455185112cbe13460a4477a71346c1a33a1b083704eb45b3ed737a6001f8dd221c8914160adc42482bcbdfdbd53fed9ec5a81d83a354e6e9efd573f786 + checksum: 10c0/168fd6cd866d45ae03c21a70a2b2090d3cc23944f1dfed8716e29f81227ad417b977c7a988dfbd4ffadbb4ea1cc0fd2f8d9e1b33761a16a44fe537720339193b languageName: node linkType: hard @@ -25907,9 +25907,9 @@ __metadata: languageName: node linkType: hard -"webex@npm:3.7.0-wxcc.3": - version: 3.7.0-wxcc.3 - resolution: "webex@npm:3.7.0-wxcc.3" +"webex@npm:3.7.0-wxcc.6": + version: 3.7.0-wxcc.6 + resolution: "webex@npm:3.7.0-wxcc.6" dependencies: "@babel/polyfill": "npm:^7.12.1" "@babel/runtime-corejs2": "npm:^7.14.8" @@ -25923,7 +25923,7 @@ __metadata: "@webex/internal-plugin-voicea": "npm:3.5.0-wxcc.1" "@webex/plugin-attachment-actions": "npm:3.5.0-wxcc.1" "@webex/plugin-authorization": "npm:3.5.0-wxcc.1" - "@webex/plugin-cc": "npm:3.5.0-wxcc.9" + "@webex/plugin-cc": "npm:3.5.0-wxcc.12" "@webex/plugin-device-manager": "npm:3.5.0-wxcc.1" "@webex/plugin-logger": "npm:3.5.0-wxcc.1" "@webex/plugin-meetings": "npm:3.5.0-wxcc.1" @@ -25937,7 +25937,7 @@ __metadata: "@webex/storage-adapter-local-storage": "npm:3.5.0-wxcc.1" "@webex/webex-core": "npm:3.5.0-wxcc.1" lodash: "npm:^4.17.21" - checksum: 10c0/ee83226f0d884db685b86fadf514c2cddfcbf91f8329d691be4132828e29477a6551bfebfe82a510b24b223319d0eb6badb7322d10713fec1c302a879a253e9d + checksum: 10c0/9faf57582064e3466a138e8848fe818a41cebad17060dfc0cff43997c57a8b93f7a867a864759d029c2cbbfab99dad019a76a241b0fc2fc96f25e704c1b6685f languageName: node linkType: hard From 9bc21bb0af1ce4cd599b6e4735e99c43c66b6ed6 Mon Sep 17 00:00:00 2001 From: Kesava Krishnan Madavan Date: Fri, 10 Jan 2025 17:25:39 +0530 Subject: [PATCH 3/4] fix(task): change console statements to logger --- packages/contact-center/task/src/helper.ts | 24 ++- .../contact-center/task/src/task.types.ts | 10 +- packages/contact-center/task/tests/helper.ts | 138 +++++++++--------- 3 files changed, 92 insertions(+), 80 deletions(-) diff --git a/packages/contact-center/task/src/helper.ts b/packages/contact-center/task/src/helper.ts index 8a89e0305..c2ad49dd2 100644 --- a/packages/contact-center/task/src/helper.ts +++ b/packages/contact-center/task/src/helper.ts @@ -4,7 +4,7 @@ import {ITask} from '@webex/plugin-cc'; // Hook for managing the task list export const useTaskList = (props: UseTaskListProps) => { - const {cc, selectedLoginOption, onTaskAccepted, onTaskDeclined} = props; + const {cc, selectedLoginOption, onTaskAccepted, onTaskDeclined, logger} = props; const [taskList, setTaskList] = useState([]); const isBrowser = selectedLoginOption === 'BROWSER'; @@ -49,7 +49,10 @@ export const useTaskList = (props: UseTaskListProps) => { onTaskAccepted && onTaskAccepted(task); }) .catch((error: Error) => { - console.error(error); + logger.error(`Error accepting task: ${error}`, { + module: 'widget-cc-task#helper.ts', + method: 'useTaskList#acceptTask', + }); }); }; @@ -63,7 +66,10 @@ export const useTaskList = (props: UseTaskListProps) => { onTaskDeclined && onTaskDeclined(task); }) .catch((error: Error) => { - console.error(error); + logger.error(`Error declining task: ${error}`, { + module: 'widget-cc-task#helper.ts', + method: 'useTaskList#declineTask', + }); }); }; @@ -81,7 +87,7 @@ export const useTaskList = (props: UseTaskListProps) => { // Hook for managing the current task export const useIncomingTask = (props: UseTaskProps) => { - const {cc, onAccepted, onDeclined, selectedLoginOption} = props; + const {cc, onAccepted, onDeclined, selectedLoginOption, logger} = props; const [currentTask, setCurrentTask] = useState(null); const [isAnswered, setIsAnswered] = useState(false); const [isEnded, setIsEnded] = useState(false); @@ -143,7 +149,10 @@ export const useIncomingTask = (props: UseTaskProps) => { onAccepted && onAccepted(); }) .catch((error: Error) => { - console.error(error); + logger.error(`Error accepting incoming task: ${error}`, { + module: 'widget-cc-task#helper.ts', + method: 'useIncomingTask#accept', + }); }); }; @@ -158,7 +167,10 @@ export const useIncomingTask = (props: UseTaskProps) => { onDeclined && onDeclined(); }) .catch((error: Error) => { - console.error(error); + logger.error(`Error declining incoming task: ${error}`, { + module: 'widget-cc-task#helper.ts', + method: 'useIncomingTask#decline', + }); }); }; diff --git a/packages/contact-center/task/src/task.types.ts b/packages/contact-center/task/src/task.types.ts index 35844a343..b935ac4c4 100644 --- a/packages/contact-center/task/src/task.types.ts +++ b/packages/contact-center/task/src/task.types.ts @@ -1,4 +1,5 @@ import {ITask, IContactCenter} from '@webex/plugin-cc'; +import {ILogger} from '@webex/cc-store'; /** * Interface representing the TaskProps of a user. @@ -88,10 +89,15 @@ export interface TaskProps { * Audio reference */ audioRef: React.RefObject; + + /** + * The logger instance from SDK + */ + logger: ILogger; } -export type UseTaskProps = Pick; -export type UseTaskListProps = Pick; +export type UseTaskProps = Pick; +export type UseTaskListProps = Pick; export type IncomingTaskPresentationalProps = Pick< TaskProps, 'currentTask' | 'isBrowser' | 'isAnswered' | 'isEnded' | 'isMissed' | 'accept' | 'decline' | 'audioRef' diff --git a/packages/contact-center/task/tests/helper.ts b/packages/contact-center/task/tests/helper.ts index e4e5b8e4d..311c8eced 100644 --- a/packages/contact-center/task/tests/helper.ts +++ b/packages/contact-center/task/tests/helper.ts @@ -23,22 +23,20 @@ const onDeclined = jest.fn(); const onTaskAccepted = jest.fn(); const onTaskDeclined = jest.fn(); -describe('useIncomingTask Hook', () => { - let consoleErrorMock; +const logger = { + error: jest.fn() +}; - beforeEach(() => { - // Mock console.error to spy on errors - consoleErrorMock = jest.spyOn(console, 'error').mockImplementation(); - }); +describe('useIncomingTask Hook', () => { afterEach(() => { jest.clearAllMocks(); - consoleErrorMock.mockRestore(); + logger.error.mockRestore(); }); it('should register task events for the current task', async () => { const {result} = renderHook(() => - useIncomingTask({cc: ccMock, onAccepted, onDeclined, selectedLoginOption: 'BROWSER'}) + useIncomingTask({cc: ccMock, onAccepted, onDeclined, selectedLoginOption: 'BROWSER', logger}) ); act(() => { @@ -51,12 +49,12 @@ describe('useIncomingTask Hook', () => { }); // Ensure no errors are logged - expect(consoleErrorMock).not.toHaveBeenCalled(); + expect(logger.error).not.toHaveBeenCalled(); }); it('should not call onAccepted if it is not provided', async () => { const {result} = renderHook(() => - useIncomingTask({cc: ccMock, onAccepted: null, onDeclined: null, selectedLoginOption: 'BROWSER'}) + useIncomingTask({cc: ccMock, onAccepted: null, onDeclined: null, selectedLoginOption: 'BROWSER', logger}) ); act(() => { @@ -72,12 +70,12 @@ describe('useIncomingTask Hook', () => { }); // Ensure no errors are logged - expect(consoleErrorMock).not.toHaveBeenCalled(); + expect(logger.error).not.toHaveBeenCalled(); }); it('should not call onDeclined if it is not provided', async () => { const {result} = renderHook(() => - useIncomingTask({cc: ccMock, onAccepted: null, onDeclined: null, selectedLoginOption: 'BROWSER'}) + useIncomingTask({cc: ccMock, onAccepted: null, onDeclined: null, selectedLoginOption: 'BROWSER', logger}) ); act(() => { @@ -93,12 +91,12 @@ describe('useIncomingTask Hook', () => { }); // Ensure no errors are logged - expect(consoleErrorMock).not.toHaveBeenCalled(); + expect(logger.error).not.toHaveBeenCalled(); }); it('should clean up task events on task change or unmount', async () => { const {result, unmount} = renderHook(() => - useIncomingTask({cc: ccMock, onAccepted, onDeclined, selectedLoginOption: 'BROWSER'}) + useIncomingTask({cc: ccMock, onAccepted, onDeclined, selectedLoginOption: 'BROWSER', logger}) ); act(() => { @@ -113,7 +111,7 @@ describe('useIncomingTask Hook', () => { }); // Ensure no errors are logged - expect(consoleErrorMock).not.toHaveBeenCalled(); + expect(logger.error).not.toHaveBeenCalled(); }); it('should handle errors when accepting a task', async () => { @@ -123,7 +121,7 @@ describe('useIncomingTask Hook', () => { decline: jest.fn(), // No-op for decline in this test }; - const {result} = renderHook(() => useIncomingTask({cc: ccMock, onAccepted, selectedLoginOption: 'BROWSER'})); + const {result} = renderHook(() => useIncomingTask({cc: ccMock, onAccepted, selectedLoginOption: 'BROWSER', logger})); act(() => { ccMock.on.mock.calls[0][1](failingTask); @@ -138,8 +136,11 @@ describe('useIncomingTask Hook', () => { }); // Ensure errors are logged in the console - expect(consoleErrorMock).toHaveBeenCalled(); - expect(consoleErrorMock).toHaveBeenCalledWith('Error'); + expect(logger.error).toHaveBeenCalled(); + expect(logger.error).toHaveBeenCalledWith('Error accepting incoming task: Error', { + module: 'widget-cc-task#helper.ts', + method: 'useIncomingTask#accept', + }); }); it('should handle errors when declining a task', async () => { @@ -149,7 +150,7 @@ describe('useIncomingTask Hook', () => { decline: jest.fn().mockRejectedValue('Error'), }; - const {result} = renderHook(() => useIncomingTask({cc: ccMock, onDeclined, selectedLoginOption: 'BROWSER'})); + const {result} = renderHook(() => useIncomingTask({cc: ccMock, onDeclined, selectedLoginOption: 'BROWSER', logger})); act(() => { ccMock.on.mock.calls[0][1](failingTask); @@ -164,26 +165,23 @@ describe('useIncomingTask Hook', () => { }); // Ensure errors are logged in the console - expect(consoleErrorMock).toHaveBeenCalled(); - expect(consoleErrorMock).toHaveBeenCalledWith('Error'); + expect(logger.error).toHaveBeenCalled(); + expect(logger.error).toHaveBeenCalledWith('Error declining incoming task: Error', { + module: 'widget-cc-task#helper.ts', + method: 'useIncomingTask#decline', + }); }); }); describe('useTaskList Hook', () => { - let consoleErrorMock; - - beforeEach(() => { - // Mock console.error to spy on errors - consoleErrorMock = jest.spyOn(console, 'error').mockImplementation(); - }); afterEach(() => { jest.clearAllMocks(); - consoleErrorMock.mockRestore(); + logger.error.mockRestore(); }); it('should call onTaskAccepted callback when provided', async () => { - const {result} = renderHook(() => useTaskList({cc: ccMock, onTaskAccepted})); + const {result} = renderHook(() => useTaskList({cc: ccMock, selectedLoginOption:'', onTaskAccepted, logger})); act(() => { result.current.acceptTask(taskMock); @@ -194,11 +192,11 @@ describe('useTaskList Hook', () => { }); // Ensure no errors are logged - expect(consoleErrorMock).not.toHaveBeenCalled(); + expect(logger.error).not.toHaveBeenCalled(); }); it('should call onTaskDeclined callback when provided', async () => { - const {result} = renderHook(() => useTaskList({cc: ccMock, onTaskDeclined})); + const {result} = renderHook(() => useTaskList({cc: ccMock, selectedLoginOption:'', onTaskDeclined, logger})); act(() => { result.current.declineTask(taskMock); @@ -209,7 +207,7 @@ describe('useTaskList Hook', () => { }); // Ensure no errors are logged - expect(consoleErrorMock).not.toHaveBeenCalled(); + expect(logger.error).not.toHaveBeenCalled(); }); it('should handle errors when accepting a task', async () => { @@ -219,7 +217,7 @@ describe('useTaskList Hook', () => { decline: jest.fn(), // No-op for decline in this test }; - const {result} = renderHook(() => useTaskList({cc: ccMock, onTaskAccepted, selectedLoginOption: 'BROWSER'})); + const {result} = renderHook(() => useTaskList({cc: ccMock, onTaskAccepted, selectedLoginOption: 'BROWSER', logger})); act(() => { ccMock.on.mock.calls[0][1](failingTask); @@ -234,8 +232,11 @@ describe('useTaskList Hook', () => { }); // Ensure errors are logged in the console - expect(consoleErrorMock).toHaveBeenCalled(); - expect(consoleErrorMock).toHaveBeenCalledWith('Error'); + expect(logger.error).toHaveBeenCalled(); + expect(logger.error).toHaveBeenCalledWith('Error accepting task: Error', { + module: 'widget-cc-task#helper.ts', + method: 'useTaskList#acceptTask', + }); }); it('should handle errors when declining a task', async () => { @@ -245,7 +246,7 @@ describe('useTaskList Hook', () => { decline: jest.fn().mockRejectedValue('Error'), }; - const {result} = renderHook(() => useTaskList({cc: ccMock, onTaskDeclined, selectedLoginOption: 'BROWSER'})); + const {result} = renderHook(() => useTaskList({cc: ccMock, onTaskDeclined, selectedLoginOption: 'BROWSER', logger})); act(() => { ccMock.on.mock.calls[0][1](failingTask); @@ -260,12 +261,15 @@ describe('useTaskList Hook', () => { }); // Ensure errors are logged in the console - expect(consoleErrorMock).toHaveBeenCalled(); - expect(consoleErrorMock).toHaveBeenCalledWith('Error'); + expect(logger.error).toHaveBeenCalled(); + expect(logger.error).toHaveBeenCalledWith('Error declining task: Error', { + module: 'widget-cc-task#helper.ts', + method: 'useTaskList#declineTask', + }); }); it('should add tasks to the list on TASK_INCOMING event', async () => { - const {result} = renderHook(() => useTaskList({cc: ccMock})); + const {result} = renderHook(() => useTaskList({cc: ccMock, logger, selectedLoginOption:''})); act(() => { ccMock.on.mock.calls[0][1](taskMock); @@ -276,11 +280,11 @@ describe('useTaskList Hook', () => { }); // Ensure no errors are logged - expect(consoleErrorMock).not.toHaveBeenCalled(); + expect(logger.error).not.toHaveBeenCalled(); }); it('should not call onTaskAccepted if it is not provided', async () => { - const {result} = renderHook(() => useTaskList({cc: ccMock, onTaskAccepted: null, onTaskDeclined: null})); + const {result} = renderHook(() => useTaskList({cc: ccMock, onTaskAccepted: null, onTaskDeclined: null, logger, selectedLoginOption:''})); act(() => { result.current.acceptTask(taskMock); @@ -291,11 +295,11 @@ describe('useTaskList Hook', () => { }); // Ensure no errors are logged - expect(consoleErrorMock).not.toHaveBeenCalled(); + expect(logger.error).not.toHaveBeenCalled(); }); it('should not call onTaskDeclined if it is not provided', async () => { - const {result} = renderHook(() => useTaskList({cc: ccMock, onTaskAccepted: null, onTaskDeclined: null})); + const {result} = renderHook(() => useTaskList({cc: ccMock, onTaskAccepted: null, onTaskDeclined: null, logger, selectedLoginOption:''})); act(() => { result.current.declineTask(taskMock); @@ -306,11 +310,11 @@ describe('useTaskList Hook', () => { }); // Ensure no errors are logged - expect(consoleErrorMock).not.toHaveBeenCalled(); + expect(logger.error).not.toHaveBeenCalled(); }); it('should remove a task from the list when it ends', async () => { - const {result} = renderHook(() => useTaskList({cc: ccMock})); + const {result} = renderHook(() => useTaskList({cc: ccMock, logger, selectedLoginOption:''})); act(() => { ccMock.on.mock.calls[0][1](taskMock); @@ -325,11 +329,11 @@ describe('useTaskList Hook', () => { }); // Ensure no errors are logged - expect(consoleErrorMock).not.toHaveBeenCalled(); + expect(logger.error).not.toHaveBeenCalled(); }); it('should update an existing task in the list', async () => { - const {result} = renderHook(() => useTaskList({cc: ccMock})); + const {result} = renderHook(() => useTaskList({cc: ccMock, logger, selectedLoginOption:''})); act(() => { ccMock.on.mock.calls[0][1](taskMock); @@ -343,11 +347,11 @@ describe('useTaskList Hook', () => { await waitFor(() => {}); // Ensure no errors are logged - expect(consoleErrorMock).not.toHaveBeenCalled(); + expect(logger.error).not.toHaveBeenCalled(); }); it('should deduplicate tasks by interactionId', async () => { - const {result} = renderHook(() => useTaskList({cc: ccMock})); + const {result} = renderHook(() => useTaskList({cc: ccMock, logger, selectedLoginOption:''})); act(() => { ccMock.on.mock.calls[0][1](taskMock); @@ -359,25 +363,19 @@ describe('useTaskList Hook', () => { }); // Ensure no errors are logged - expect(consoleErrorMock).not.toHaveBeenCalled(); + expect(logger.error).not.toHaveBeenCalled(); }); describe('useIncomingTask Hook - Task Events', () => { - let consoleErrorMock; - - beforeEach(() => { - // Mock console.error to spy on errors - consoleErrorMock = jest.spyOn(console, 'error').mockImplementation(); - }); afterEach(() => { jest.clearAllMocks(); - consoleErrorMock.mockRestore(); + logger.error.mockRestore(); }); it('should set isAnswered to true when task is assigned', async () => { const {result} = renderHook(() => - useIncomingTask({cc: ccMock, onAccepted, onDeclined, selectedLoginOption: 'BROWSER'}) + useIncomingTask({cc: ccMock, onAccepted, onDeclined, selectedLoginOption: 'BROWSER', logger, selectedLoginOption:''}) ); // Simulate task being assigned @@ -394,12 +392,12 @@ describe('useTaskList Hook', () => { }); // Ensure no errors are logged - expect(consoleErrorMock).not.toHaveBeenCalled(); + expect(logger.error).not.toHaveBeenCalled(); }); it('should set isEnded to true and clear currentTask when task ends', async () => { const {result} = renderHook(() => - useIncomingTask({cc: ccMock, onAccepted, onDeclined, selectedLoginOption: 'BROWSER'}) + useIncomingTask({cc: ccMock, onAccepted, onDeclined, selectedLoginOption: 'BROWSER', logger, selectedLoginOption:''}) ); // Simulate task being assigned @@ -418,12 +416,12 @@ describe('useTaskList Hook', () => { }); // Ensure no errors are logged - expect(consoleErrorMock).not.toHaveBeenCalled(); + expect(logger.error).not.toHaveBeenCalled(); }); it('should set isMissed to true and clear currentTask when task is missed', async () => { const {result} = renderHook(() => - useIncomingTask({cc: ccMock, onAccepted, onDeclined, selectedLoginOption: 'BROWSER'}) + useIncomingTask({cc: ccMock, onAccepted, onDeclined, selectedLoginOption: 'BROWSER', logger, selectedLoginOption:''}) ); // Simulate task being assigned @@ -442,17 +440,13 @@ describe('useTaskList Hook', () => { }); // Ensure no errors are logged - expect(consoleErrorMock).not.toHaveBeenCalled(); + expect(logger.error).not.toHaveBeenCalled(); }); }); describe('useIncomingTask Hook - handleTaskMedia', () => { - let consoleErrorMock; beforeEach(() => { - // Mock console.error to spy on errors - consoleErrorMock = jest.spyOn(console, 'error').mockImplementation(); - // Mock the MediaStreamTrack and MediaStream classes for the test environment global.MediaStreamTrack = jest.fn().mockImplementation(() => ({ kind: 'audio', // Simulating an audio track @@ -467,7 +461,7 @@ describe('useTaskList Hook', () => { afterEach(() => { jest.clearAllMocks(); - consoleErrorMock.mockRestore(); + logger.error.mockRestore(); }); it('should assign track to audioRef.current.srcObject when handleTaskMedia is called', async () => { @@ -477,7 +471,7 @@ describe('useTaskList Hook', () => { }; const {result} = renderHook(() => - useIncomingTask({cc: ccMock, onAccepted, onDeclined, selectedLoginOption: 'BROWSER'}) + useIncomingTask({cc: ccMock, onAccepted, onDeclined, selectedLoginOption: 'BROWSER', logger, selectedLoginOption:''}) ); // Manually assign the mocked audio element to the ref @@ -503,13 +497,13 @@ describe('useTaskList Hook', () => { }); // Ensure no errors are logged - expect(consoleErrorMock).not.toHaveBeenCalled(); + expect(logger.error).not.toHaveBeenCalled(); }); it('should not set srcObject if audioRef.current is null', async () => { // Mock audioRef to simulate the absence of an audio element const {result} = renderHook(() => - useIncomingTask({cc: ccMock, onAccepted, onDeclined, selectedLoginOption: 'BROWSER'}) + useIncomingTask({cc: ccMock, onAccepted, onDeclined, selectedLoginOption: 'BROWSER', logger, }) ); result.current.audioRef.current = null; @@ -533,7 +527,7 @@ describe('useTaskList Hook', () => { }); // Ensure no errors are logged - expect(consoleErrorMock).not.toHaveBeenCalled(); + expect(logger.error).not.toHaveBeenCalled(); }); }); }); From ec1393d8270e7f00435a9ec1fcdb784db3dff8c3 Mon Sep 17 00:00:00 2001 From: Kesava Krishnan Madavan Date: Fri, 10 Jan 2025 17:46:41 +0530 Subject: [PATCH 4/4] fix(task): pass logger to hooks --- packages/contact-center/task/src/IncomingTask/index.tsx | 4 ++-- packages/contact-center/task/src/TaskList/index.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/contact-center/task/src/IncomingTask/index.tsx b/packages/contact-center/task/src/IncomingTask/index.tsx index 819c42470..06f03a932 100644 --- a/packages/contact-center/task/src/IncomingTask/index.tsx +++ b/packages/contact-center/task/src/IncomingTask/index.tsx @@ -7,9 +7,9 @@ import IncomingTaskPresentational from './incoming-task.presentational'; import {IncomingTaskProps} from '../task.types'; const IncomingTask: React.FunctionComponent = observer(({onAccepted, onDeclined}) => { - const {cc, selectedLoginOption} = store; + const {cc, selectedLoginOption, logger} = store; - const result = useIncomingTask({cc, onAccepted, onDeclined, selectedLoginOption}); + const result = useIncomingTask({cc, onAccepted, onDeclined, selectedLoginOption, logger}); const props = { ...result, diff --git a/packages/contact-center/task/src/TaskList/index.tsx b/packages/contact-center/task/src/TaskList/index.tsx index 7830393b3..c1eeda3fc 100644 --- a/packages/contact-center/task/src/TaskList/index.tsx +++ b/packages/contact-center/task/src/TaskList/index.tsx @@ -6,9 +6,9 @@ import TaskListPresentational from './task-list.presentational'; import {useTaskList} from '../helper'; const TaskList: React.FunctionComponent = observer(() => { - const {cc, selectedLoginOption} = store; + const {cc, selectedLoginOption, logger} = store; - const result = useTaskList({cc, selectedLoginOption}); + const result = useTaskList({cc, selectedLoginOption, logger}); return ; });