From 77407eb5b9b48ffa77fcdc54d44368a2ba58e706 Mon Sep 17 00:00:00 2001 From: Kesava Krishnan Madavan Date: Thu, 5 Dec 2024 14:22:36 +0530 Subject: [PATCH 01/12] feat(user-state): load and change state, client timer --- docs/react-samples/src/App.tsx | 8 +- docs/web-component-samples/app.js | 6 +- docs/web-component-samples/index.html | 6 +- .../station-login/src/helper.ts | 8 +- packages/contact-center/store/src/store.ts | 6 +- .../contact-center/store/src/store.types.ts | 9 ++ .../contact-center/user-state/src/helper.ts | 43 ++++++- .../user-state/src/user-state/index.tsx | 12 +- .../src/user-state/use-state.types.ts | 20 +-- .../user-state/user-state.presentational.tsx | 121 +++++++++++++++++- 10 files changed, 205 insertions(+), 34 deletions(-) diff --git a/docs/react-samples/src/App.tsx b/docs/react-samples/src/App.tsx index fbdc81389..416865daa 100644 --- a/docs/react-samples/src/App.tsx +++ b/docs/react-samples/src/App.tsx @@ -6,6 +6,7 @@ import {UserState} from '@webex/cc-user-state'; function App() { const [isSdkReady, setIsSdkReady] = useState(false); const [accessToken, setAccessToken] = useState(""); + const [isLoggedIn, setIsLoggedIn] = useState(false); const webexConfig = { fedramp: false, @@ -16,10 +17,12 @@ function App() { const onLogin = () => { console.log('Agent login has been succesful'); + setIsLoggedIn(true); } const onLogout = () => { console.log('Agent logout has been succesful'); + setIsLoggedIn(false); } return ( @@ -39,12 +42,13 @@ function App() { }); }} >Init Widgets - {/* write code to check if sdk is ready and load components */} { isSdkReady && ( <> - + { + isLoggedIn && + } ) } diff --git a/docs/web-component-samples/app.js b/docs/web-component-samples/app.js index 66bbd47fa..4330b4a9c 100644 --- a/docs/web-component-samples/app.js +++ b/docs/web-component-samples/app.js @@ -1,6 +1,6 @@ -const widgetsContainer = document.getElementById('widgets-container'); const accessTokenElem = document.getElementById('access_token_elem'); const ccStationLogin = document.getElementById('cc-station-login'); +const ccUserState = document.getElementById('cc-user-state'); function switchButtonState(){ const buttonElem = document.querySelector('button'); @@ -20,7 +20,7 @@ function initWidgets(){ }).then(() => { ccStationLogin.onLogin = loginSuccess; ccStationLogin.onLogout = logoutSuccess; - widgetsContainer.classList.remove('disabled'); + ccStationLogin.classList.remove('disabled'); }).catch((error) => { console.error('Failed to initialize widgets:', error); }); @@ -28,8 +28,10 @@ function initWidgets(){ function loginSuccess(){ console.log('Agent login has been succesful'); + ccUserState.classList.remove('disabled'); } function logoutSuccess(){ console.log('Agent logout has been succesful'); + ccUserState.classList.add('disabled'); } \ No newline at end of file diff --git a/docs/web-component-samples/index.html b/docs/web-component-samples/index.html index fcc56a2a5..48fb12181 100644 --- a/docs/web-component-samples/index.html +++ b/docs/web-component-samples/index.html @@ -22,9 +22,9 @@

Contact Center widgets as web-component

autocapitalize="off" /> -
- - +
+ +
diff --git a/packages/contact-center/station-login/src/helper.ts b/packages/contact-center/station-login/src/helper.ts index b5af9fddf..1b9cdad02 100644 --- a/packages/contact-center/station-login/src/helper.ts +++ b/packages/contact-center/station-login/src/helper.ts @@ -17,7 +17,9 @@ export const useStationLogin = (props: UseStationLoginProps) => { cc.stationLogin({teamId: team, loginOption: deviceType, dialNumber: dialNumber}) .then((res: StationLoginSuccess) => { setLoginSuccess(res); - loginCb(); + if(loginCb){ + loginCb(); + } }).catch((error: Error) => { console.error(error); setLoginFailure(error); @@ -28,7 +30,9 @@ export const useStationLogin = (props: UseStationLoginProps) => { cc.stationLogout({logoutReason: 'User requested logout'}) .then((res: StationLogoutSuccess) => { setLogoutSuccess(res); - logoutCb(); + if(logoutCb){ + logoutCb(); + } }).catch((error: any) => { console.error(error); }); diff --git a/packages/contact-center/store/src/store.ts b/packages/contact-center/store/src/store.ts index 0a514cd77..f654a122b 100644 --- a/packages/contact-center/store/src/store.ts +++ b/packages/contact-center/store/src/store.ts @@ -1,11 +1,11 @@ import {makeAutoObservable, observable} from 'mobx'; import Webex from 'webex'; import { - AgentLogin, IContactCenter, Profile, Team, WithWebex, + IdleCode, InitParams, IStore } from './store.types'; @@ -14,6 +14,8 @@ class Store implements IStore { teams: Team[] = []; loginOptions: string[] = []; cc: IContactCenter; + idleCodes: IdleCode[] = []; + agentId: string = ''; constructor() { makeAutoObservable(this, {cc: observable.ref}); @@ -24,6 +26,8 @@ class Store implements IStore { 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); 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 bb5770845..bed35a23b 100644 --- a/packages/contact-center/store/src/store.types.ts +++ b/packages/contact-center/store/src/store.types.ts @@ -11,10 +11,18 @@ interface WithWebexConfig { type InitParams = WithWebex | WithWebexConfig; +type IdleCode = { + name: string; + id: string; + isSystem: boolean; + isDefault: boolean; +} + interface IStore { teams: Team[]; loginOptions: string[]; cc: IContactCenter; + idleCodes: IdleCode[]; registerCC(webex: WithWebex['webex']): Promise; init(params: InitParams): Promise; @@ -26,6 +34,7 @@ export type { Team, AgentLogin, WithWebex, + IdleCode, InitParams, IStore } \ 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 32d6b89c7..b08ea5066 100644 --- a/packages/contact-center/user-state/src/helper.ts +++ b/packages/contact-center/user-state/src/helper.ts @@ -1,11 +1,44 @@ -export const useUserState = () => { +import {useState, useEffect} from "react"; +export const useUserState = ({idleCodes, agentId, cc}) => { - const handleAgentStatus = (event: { target: { value: string; }; }) => { - }; + const [isSettingAgentStatus, setIsSettingAgentStatus] = useState(false); + const [errorMessage, setErrorMessage] = useState(''); + + const [elapsedTime, setElapsedTime] = useState(0); + + useEffect(() => { + // Reset the timer whenever the component mounts or the state changes + setElapsedTime(0); + const timer = setInterval(() => { + setElapsedTime(prevTime => prevTime + 1); + }, 1000); + + // Cleanup the timer on component unmount + return () => clearInterval(timer); + }, []); - const setAgentStatus = () => { + const setAgentStatus = ({ + auxCodeId, + state + }) => { + setIsSettingAgentStatus(true); + const chosenState = state === 'Available' ? 'Available' : 'Idle'; + cc.setAgentState({state: chosenState, auxCodeId, agentId, lastStateChangeReason: state}).then((response) => { + setIsSettingAgentStatus(false); + setErrorMessage(''); + setElapsedTime(0); + }).catch(error => { + setErrorMessage(error.toString()); + setIsSettingAgentStatus(false); + }); }; - return {name: 'UserState', handleAgentStatus, setAgentStatus}; + return { + idleCodes, + setAgentStatus, + isSettingAgentStatus, + errorMessage, + elapsedTime + } }; 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 e5d3300f3..704680f5f 100644 --- a/packages/contact-center/user-state/src/user-state/index.tsx +++ b/packages/contact-center/user-state/src/user-state/index.tsx @@ -5,13 +5,15 @@ import r2wc from '@r2wc/react-to-web-component'; import {useUserState} from '../helper'; import UserStatePresentational from './user-state.presentational'; +import {IUserState} from './use-state.types'; const UserState: React.FunctionComponent = observer(() => { - const {} = store; - const result = useUserState(); - const props = { - ...result, - }; + const {cc, idleCodes, agentId} = store; + const props: IUserState = useUserState({ + idleCodes, + agentId, + cc + }); return ; }); diff --git a/packages/contact-center/user-state/src/user-state/use-state.types.ts b/packages/contact-center/user-state/src/user-state/use-state.types.ts index fd5f8a7fc..a198e6628 100644 --- a/packages/contact-center/user-state/src/user-state/use-state.types.ts +++ b/packages/contact-center/user-state/src/user-state/use-state.types.ts @@ -1,19 +1,19 @@ +import { IdleCode } from '@webex/cc-store'; + /** * Interface representing the state of a user. */ export interface IUserState { /** - * The name of the user. + * The list of idle codes. */ - name: string; + idleCodes: IdleCode[]; - /** - * Handler for agent state changes - */ - handleAgentStatus: (event) => void; + setAgentStatus: (status: { auxCodeId: string; state: string }) => void; - /** - * Setter for agent state - */ - setAgentStatus: () => void + isSettingAgentStatus: boolean; + + errorMessage: string; + + elapsedTime: number; } diff --git a/packages/contact-center/user-state/src/user-state/user-state.presentational.tsx b/packages/contact-center/user-state/src/user-state/user-state.presentational.tsx index 915f1b930..d8c6a6714 100644 --- a/packages/contact-center/user-state/src/user-state/user-state.presentational.tsx +++ b/packages/contact-center/user-state/src/user-state/user-state.presentational.tsx @@ -1,14 +1,127 @@ -import React from 'react'; +import React, {CSSProperties, useEffect} from 'react'; import {IUserState} from './use-state.types'; const UserStatePresentational: React.FunctionComponent = (props) => { - const {handleAgentStatus, setAgentStatus} = props; + const {idleCodes,setAgentStatus,isSettingAgentStatus, errorMessage, elapsedTime} = props; + + const styles = { + box: { + backgroundColor: '#ffffff', + borderRadius: '8px', + boxShadow: '0 2px 4px rgba(0, 0, 0, 0.1)', + padding: '20px', + maxWidth: '800px', + margin: '0 auto' + }, + + sectionBox: { + padding: '10px', + border: '1px solid #ddd', + borderRadius: '8px' + }, + + fieldset: { + border: '1px solid #ccc', + borderRadius: '5px', + padding: '10px', + marginBottom: '20px', + position: 'relative' + } as CSSProperties, + + legendBox: { + fontWeight: 'bold', + color: '#0052bf' + }, + + btn: { + padding: '10px 20px', + backgroundColor: '#0052bf', + color: 'white', + border: 'none', + borderRadius: '4px', + cursor: 'pointer', + transition: 'background-color 0.3s', + marginRight: '8px' + }, + + select: { + width: '100%', + padding: '8px', + marginTop: '8px', + marginBottom: '12px', + border: '1px solid #ccc', + borderRadius: '4px' + }, + + input: { + width: '97%', + padding: '8px', + marginTop: '8px', + marginBottom: '12px', + border: '1px solid #ccc', + borderRadius: '4px' + }, + + elapsedTime: { + position: 'absolute', + right: '30px', + top: '25px', + color: isSettingAgentStatus ? 'grey' : 'black' + } as CSSProperties + }; + + const formatTime = (time) => { + const hours = Math.floor(time / 3600); + const minutes = Math.floor((time % 3600) / 60); + const seconds = time % 60; + return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`; + }; return ( <> -

{props.name}

-

User State: {}

+
+
+
+ Agent State +
+ +
{formatTime(elapsedTime)}
+ { + errorMessage &&
{errorMessage}
+ } +
+
+
); }; From 5433cbc9d5fb970d5259515bc5ad8e0dd0d387fa Mon Sep 17 00:00:00 2001 From: Kesava Krishnan Madavan Date: Thu, 5 Dec 2024 14:58:09 +0530 Subject: [PATCH 02/12] test(station-login): make logout login callbacks only if present --- .../station-login/tests/helper.ts | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/packages/contact-center/station-login/tests/helper.ts b/packages/contact-center/station-login/tests/helper.ts index 5bf46173e..63127b6fa 100644 --- a/packages/contact-center/station-login/tests/helper.ts +++ b/packages/contact-center/station-login/tests/helper.ts @@ -18,6 +18,10 @@ const loginCb = jest.fn(); const logoutCb = jest.fn(); describe('useStationLogin Hook', () => { + + afterEach(() => { + jest.clearAllMocks(); + }); it('should set loginSuccess on successful login', async () => { const successResponse = { @@ -79,6 +83,24 @@ describe('useStationLogin Hook', () => { }); }); + it('should not call login callback if not present', async () => { + + ccMock.stationLogin.mockResolvedValue({}); + + const { result, waitForNextUpdate } = renderHook(() => + //@ts-ignore + useStationLogin({cc: ccMock, onLogin: undefined, onLogout: logoutCb}) + ); + + act(() => { + result.current.login(); + }); + + await waitForNextUpdate(); + + expect(loginCb).not.toHaveBeenCalled(); + }); + it('should set loginFailure on failed login', async () => { const errorResponse = new Error('Login failed'); ccMock.stationLogin.mockRejectedValue(errorResponse); @@ -163,4 +185,21 @@ describe('useStationLogin Hook', () => { logoutSuccess: successResponse }); }); + + it('should not call logout callback if not present', async () => { + ccMock.stationLogout.mockResolvedValue({}); + + const {result, waitForNextUpdate} = renderHook(() => + //@ts-ignore + useStationLogin({cc: ccMock, onLogin: loginCb}) + ); + + act(() => { + result.current.logout(); + }); + + await waitForNextUpdate(); + + expect(logoutCb).not.toHaveBeenCalled(); + }); }) From 9e8d9e762f95bc3ce7a47fe2d0230373cb1e51e1 Mon Sep 17 00:00:00 2001 From: Kesava Krishnan Madavan Date: Thu, 5 Dec 2024 15:07:48 +0530 Subject: [PATCH 03/12] test(store): add check for agentId and idleCodes --- packages/contact-center/store/tests/store.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/contact-center/store/tests/store.ts b/packages/contact-center/store/tests/store.ts index f853c0f1f..843cd7443 100644 --- a/packages/contact-center/store/tests/store.ts +++ b/packages/contact-center/store/tests/store.ts @@ -47,7 +47,9 @@ describe('Store', () => { it('should set teams and loginOptions on successful register', async () => { const mockResponse = { teams: [{ id: 'team1', name: 'Team 1' }], - loginVoiceOptions: ['option1', 'option2'] + loginVoiceOptions: ['option1', 'option2'], + idleCodes: [{ id: 'code1', name: 'Code 1', isSystem: false, isDefault: false }], + agentId: 'agent1' }; mockWebex.cc.register.mockResolvedValue(mockResponse); @@ -55,6 +57,8 @@ describe('Store', () => { expect(store.teams).toEqual(mockResponse.teams); expect(store.loginOptions).toEqual(mockResponse.loginVoiceOptions); + expect(store.idleCodes).toEqual(mockResponse.idleCodes); + expect(store.agentId).toEqual(mockResponse.agentId); }); it('should log an error on failed register', async () => { From d8db76896ba0b5ee81c28864e03f46aa7c3f1089 Mon Sep 17 00:00:00 2001 From: Kesava Krishnan Madavan Date: Thu, 5 Dec 2024 15:18:23 +0530 Subject: [PATCH 04/12] test(user-state): timer and setAgentStatus tests --- packages/contact-center/store/package.json | 2 - .../contact-center/user-state/package.json | 6 +- .../contact-center/user-state/tests/helper.ts | 69 +++++++++++++++++++ 3 files changed, 73 insertions(+), 4 deletions(-) create mode 100644 packages/contact-center/user-state/tests/helper.ts diff --git a/packages/contact-center/store/package.json b/packages/contact-center/store/package.json index 7114c201f..2927ab43c 100644 --- a/packages/contact-center/store/package.json +++ b/packages/contact-center/store/package.json @@ -46,8 +46,6 @@ }, "jest": { "testEnvironment": "jsdom", - "//": "We can remove this when we have tests", - "passWithNoTests": true, "testMatch": [ "**/tests/**/*.ts", "**/tests/**/*.tsx" diff --git a/packages/contact-center/user-state/package.json b/packages/contact-center/user-state/package.json index 786a0e740..9e39ed609 100644 --- a/packages/contact-center/user-state/package.json +++ b/packages/contact-center/user-state/package.json @@ -46,8 +46,10 @@ }, "jest": { "testEnvironment": "jsdom", - "//": "We can remove this when we have tests", - "passWithNoTests": true + "testMatch": [ + "**/tests/**/*.ts", + "**/tests/**/*.tsx" + ] }, "stableVersion": "1.28.0-ccwidgets.1" } diff --git a/packages/contact-center/user-state/tests/helper.ts b/packages/contact-center/user-state/tests/helper.ts new file mode 100644 index 000000000..35824b44a --- /dev/null +++ b/packages/contact-center/user-state/tests/helper.ts @@ -0,0 +1,69 @@ +import { renderHook, act } from '@testing-library/react-hooks'; +import { useUserState } from '../src/helper'; // adjust the path accordingly + +jest.useFakeTimers(); + +describe('useUserState', () => { + let ccMock; + + beforeEach(() => { + ccMock = { + setAgentState: jest.fn(() => Promise.resolve()), + }; + }); + + test('should initialize with correct default values', () => { + const { result } = renderHook(() => useUserState({ idleCodes: [], agentId: '123', cc: ccMock })); + + expect(result.current.isSettingAgentStatus).toBe(false); + expect(result.current.errorMessage).toBe(''); + expect(result.current.elapsedTime).toBe(0); + }); + + test('should update elapsedTime every second', () => { + const { result } = renderHook(() => useUserState({ idleCodes: [], agentId: '123', cc: ccMock })); + + act(() => { + jest.advanceTimersByTime(3000); + }); + + expect(result.current.elapsedTime).toBe(3); + }); + + test('setAgentStatus should handle success', async () => { + const { result, waitForNextUpdate } = renderHook(() => useUserState({ idleCodes: [], agentId: '123', cc: ccMock })); + + act(() => { + result.current.setAgentStatus({ auxCodeId: '001', state: 'Available' }); + }); + + expect(result.current.isSettingAgentStatus).toBe(true); + + await waitForNextUpdate(); + + expect(ccMock.setAgentState).toHaveBeenCalledWith(expect.objectContaining({ + state: 'Available', + auxCodeId: '001', + agentId: '123' + })); + expect(result.current.isSettingAgentStatus).toBe(false); + expect(result.current.errorMessage).toBe(''); + expect(result.current.elapsedTime).toBe(0); + }); + + test('setAgentStatus should handle error', async () => { + ccMock.setAgentState.mockRejectedValueOnce(new Error('Network error')); + const { result, waitForNextUpdate } = renderHook(() => useUserState({ idleCodes: [], agentId: '123', cc: ccMock })); + + act(() => { + result.current.setAgentStatus({ auxCodeId: '001', state: 'Available' }); + }); + + expect(result.current.isSettingAgentStatus).toBe(true); + + await waitForNextUpdate(); + + expect(result.current.isSettingAgentStatus).toBe(false); + expect(result.current.errorMessage).toBe('Error: Network error'); + }); +}); \ No newline at end of file From 6d3ae5208953ce5160cef44a7abd4a3a62d6333f Mon Sep 17 00:00:00 2001 From: Kesava Krishnan Madavan Date: Thu, 5 Dec 2024 16:48:16 +0530 Subject: [PATCH 05/12] test(user-state): presentational layer --- package.json | 1 + .../contact-center/user-state/package.json | 3 +- .../src/user-state/styles-module.scss | 0 .../user-state/user-state.presentational.tsx | 7 ++-- .../contact-center/user-state/tests/helper.ts | 13 +++--- .../user-state/user-state.presentational.tsx | 40 +++++++++++++++++++ yarn.lock | 16 ++++++++ 7 files changed, 69 insertions(+), 11 deletions(-) delete mode 100644 packages/contact-center/user-state/src/user-state/styles-module.scss create mode 100644 packages/contact-center/user-state/tests/user-state/user-state.presentational.tsx diff --git a/package.json b/package.json index 0a42cf80a..bc7706fbf 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "@semantic-release/exec": "^6.0.3", "@semantic-release/git": "^10.0.1", "@semantic-release/github": "^11.0.1", + "@testing-library/jest-dom": "^6.6.3", "@testing-library/react-hooks": "^8.0.1", "crypto-browserify": "^3.12.1", "html-webpack-plugin": "^5.6.3", diff --git a/packages/contact-center/user-state/package.json b/packages/contact-center/user-state/package.json index 9e39ed609..0ba2617ba 100644 --- a/packages/contact-center/user-state/package.json +++ b/packages/contact-center/user-state/package.json @@ -49,7 +49,8 @@ "testMatch": [ "**/tests/**/*.ts", "**/tests/**/*.tsx" - ] + ], + "verbose": true }, "stableVersion": "1.28.0-ccwidgets.1" } diff --git a/packages/contact-center/user-state/src/user-state/styles-module.scss b/packages/contact-center/user-state/src/user-state/styles-module.scss deleted file mode 100644 index e69de29bb..000000000 diff --git a/packages/contact-center/user-state/src/user-state/user-state.presentational.tsx b/packages/contact-center/user-state/src/user-state/user-state.presentational.tsx index d8c6a6714..d521b88c4 100644 --- a/packages/contact-center/user-state/src/user-state/user-state.presentational.tsx +++ b/packages/contact-center/user-state/src/user-state/user-state.presentational.tsx @@ -1,4 +1,4 @@ -import React, {CSSProperties, useEffect} from 'react'; +import React, {CSSProperties} from 'react'; import {IUserState} from './use-state.types'; @@ -101,12 +101,11 @@ const UserStatePresentational: React.FunctionComponent = (props) => } disabled={isSettingAgentStatus} > - {idleCodes && idleCodes.map((code) => { + {idleCodes && idleCodes.map((code, index) => { return !code.isSystem ? ( diff --git a/packages/contact-center/user-state/tests/helper.ts b/packages/contact-center/user-state/tests/helper.ts index 35824b44a..5054e7553 100644 --- a/packages/contact-center/user-state/tests/helper.ts +++ b/packages/contact-center/user-state/tests/helper.ts @@ -1,4 +1,5 @@ -import { renderHook, act } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react-hooks'; +import { act } from 'react'; // Import act from react import { useUserState } from '../src/helper'; // adjust the path accordingly jest.useFakeTimers(); @@ -12,7 +13,7 @@ describe('useUserState', () => { }; }); - test('should initialize with correct default values', () => { + it('should initialize with correct default values', () => { const { result } = renderHook(() => useUserState({ idleCodes: [], agentId: '123', cc: ccMock })); expect(result.current.isSettingAgentStatus).toBe(false); @@ -20,7 +21,7 @@ describe('useUserState', () => { expect(result.current.elapsedTime).toBe(0); }); - test('should update elapsedTime every second', () => { + it('should update elapsedTime every second', () => { const { result } = renderHook(() => useUserState({ idleCodes: [], agentId: '123', cc: ccMock })); act(() => { @@ -30,7 +31,7 @@ describe('useUserState', () => { expect(result.current.elapsedTime).toBe(3); }); - test('setAgentStatus should handle success', async () => { + it('setAgentStatus should handle success', async () => { const { result, waitForNextUpdate } = renderHook(() => useUserState({ idleCodes: [], agentId: '123', cc: ccMock })); act(() => { @@ -51,7 +52,7 @@ describe('useUserState', () => { expect(result.current.elapsedTime).toBe(0); }); - test('setAgentStatus should handle error', async () => { + it('setAgentStatus should handle error', async () => { ccMock.setAgentState.mockRejectedValueOnce(new Error('Network error')); const { result, waitForNextUpdate } = renderHook(() => useUserState({ idleCodes: [], agentId: '123', cc: ccMock })); @@ -66,4 +67,4 @@ describe('useUserState', () => { expect(result.current.isSettingAgentStatus).toBe(false); expect(result.current.errorMessage).toBe('Error: Network error'); }); -}); \ No newline at end of file +}); diff --git a/packages/contact-center/user-state/tests/user-state/user-state.presentational.tsx b/packages/contact-center/user-state/tests/user-state/user-state.presentational.tsx new file mode 100644 index 000000000..b7d910e39 --- /dev/null +++ b/packages/contact-center/user-state/tests/user-state/user-state.presentational.tsx @@ -0,0 +1,40 @@ +import React from 'react'; +import { render, screen, fireEvent } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import UserStatePresentational from '../../src/user-state/user-state.presentational'; + +describe('UserStatePresentational Component', () => { + const mockSetAgentStatus = jest.fn(); + const mockIdleCodes = [ + { id: '1', name: 'Available', isSystem: false, isDefault: true }, + { id: '2', name: 'On Break', isSystem: false, isDefault: false }, + ]; + + it('renders without crashing', () => { + render(); + expect(screen.getByText('Agent State')).toBeInTheDocument(); + }); + + it('displays the correct default option', () => { + render(); + const selectElement = screen.getByRole('combobox'); + expect(selectElement.value).toBe('1'); + }); + + it('calls setAgentStatus with the correct parameters', () => { + render(); + const selectElement = screen.getByRole('combobox'); + fireEvent.change(selectElement, { target: { value: '2' } }); + expect(mockSetAgentStatus).toHaveBeenCalledWith({ auxCodeId: '2', state: 'On Break' }); + }); + + it('displays an error message when provided', () => { + render(); + expect(screen.getByText('Error occurred')).toBeInTheDocument(); + }); + + it('formats elapsed time correctly', () => { + render(); + expect(screen.getByText('01:01:01')).toBeInTheDocument(); + }); +}); \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index d68c83bdd..82ba6e28b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3977,6 +3977,21 @@ __metadata: languageName: node linkType: hard +"@testing-library/jest-dom@npm:^6.6.3": + version: 6.6.3 + resolution: "@testing-library/jest-dom@npm:6.6.3" + dependencies: + "@adobe/css-tools": "npm:^4.4.0" + aria-query: "npm:^5.0.0" + chalk: "npm:^3.0.0" + css.escape: "npm:^1.5.1" + dom-accessibility-api: "npm:^0.6.3" + lodash: "npm:^4.17.21" + redent: "npm:^3.0.0" + checksum: 10c0/5566b6c0b7b0709bc244aec3aa3dc9e5f4663e8fb2b99d8cd456fc07279e59db6076cbf798f9d3099a98fca7ef4cd50e4e1f4c4dec5a60a8fad8d24a638a5bf6 + languageName: node + linkType: hard + "@testing-library/react-hooks@npm:^8.0.1": version: 8.0.1 resolution: "@testing-library/react-hooks@npm:8.0.1" @@ -25823,6 +25838,7 @@ __metadata: "@semantic-release/exec": "npm:^6.0.3" "@semantic-release/git": "npm:^10.0.1" "@semantic-release/github": "npm:^11.0.1" + "@testing-library/jest-dom": "npm:^6.6.3" "@testing-library/react-hooks": "npm:^8.0.1" crypto-browserify: "npm:^3.12.1" html-webpack-plugin: "npm:^5.6.3" From 29bbc9588e99ce1479b7fb674fd3c3ab6dbc72e4 Mon Sep 17 00:00:00 2001 From: Kesava Krishnan Madavan Date: Thu, 5 Dec 2024 16:49:35 +0530 Subject: [PATCH 06/12] chore: remove unwanted jest-dom test lib --- package.json | 1 - yarn.lock | 16 ---------------- 2 files changed, 17 deletions(-) diff --git a/package.json b/package.json index bc7706fbf..0a42cf80a 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,6 @@ "@semantic-release/exec": "^6.0.3", "@semantic-release/git": "^10.0.1", "@semantic-release/github": "^11.0.1", - "@testing-library/jest-dom": "^6.6.3", "@testing-library/react-hooks": "^8.0.1", "crypto-browserify": "^3.12.1", "html-webpack-plugin": "^5.6.3", diff --git a/yarn.lock b/yarn.lock index 82ba6e28b..d68c83bdd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3977,21 +3977,6 @@ __metadata: languageName: node linkType: hard -"@testing-library/jest-dom@npm:^6.6.3": - version: 6.6.3 - resolution: "@testing-library/jest-dom@npm:6.6.3" - dependencies: - "@adobe/css-tools": "npm:^4.4.0" - aria-query: "npm:^5.0.0" - chalk: "npm:^3.0.0" - css.escape: "npm:^1.5.1" - dom-accessibility-api: "npm:^0.6.3" - lodash: "npm:^4.17.21" - redent: "npm:^3.0.0" - checksum: 10c0/5566b6c0b7b0709bc244aec3aa3dc9e5f4663e8fb2b99d8cd456fc07279e59db6076cbf798f9d3099a98fca7ef4cd50e4e1f4c4dec5a60a8fad8d24a638a5bf6 - languageName: node - linkType: hard - "@testing-library/react-hooks@npm:^8.0.1": version: 8.0.1 resolution: "@testing-library/react-hooks@npm:8.0.1" @@ -25838,7 +25823,6 @@ __metadata: "@semantic-release/exec": "npm:^6.0.3" "@semantic-release/git": "npm:^10.0.1" "@semantic-release/github": "npm:^11.0.1" - "@testing-library/jest-dom": "npm:^6.6.3" "@testing-library/react-hooks": "npm:^8.0.1" crypto-browserify: "npm:^3.12.1" html-webpack-plugin: "npm:^5.6.3" From a040792815ea315aec69c73cf1e728120e6b5da2 Mon Sep 17 00:00:00 2001 From: Kesava Krishnan Madavan Date: Thu, 5 Dec 2024 20:14:41 +0530 Subject: [PATCH 07/12] test(user-state): index file test --- .../user-state/user-state.presentational.tsx | 2 +- .../user-state/tests/user-state/index.tsx | 22 +++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 packages/contact-center/user-state/tests/user-state/index.tsx diff --git a/packages/contact-center/user-state/src/user-state/user-state.presentational.tsx b/packages/contact-center/user-state/src/user-state/user-state.presentational.tsx index d521b88c4..395006476 100644 --- a/packages/contact-center/user-state/src/user-state/user-state.presentational.tsx +++ b/packages/contact-center/user-state/src/user-state/user-state.presentational.tsx @@ -83,7 +83,7 @@ const UserStatePresentational: React.FunctionComponent = (props) =>
- Agent State + Agent State
{formatTime(elapsedTime)}
{ diff --git a/packages/contact-center/user-state/tests/helper.ts b/packages/contact-center/user-state/tests/helper.ts index 5054e7553..c4f8f9767 100644 --- a/packages/contact-center/user-state/tests/helper.ts +++ b/packages/contact-center/user-state/tests/helper.ts @@ -1,5 +1,4 @@ -import { renderHook } from '@testing-library/react-hooks'; -import { act } from 'react'; // Import act from react +import { renderHook, act, waitFor } from '@testing-library/react'; // Import act from react import { useUserState } from '../src/helper'; // adjust the path accordingly jest.useFakeTimers(); @@ -13,6 +12,10 @@ describe('useUserState', () => { }; }); + afterEach(() => { + jest.clearAllTimers(); + }); + it('should initialize with correct default values', () => { const { result } = renderHook(() => useUserState({ idleCodes: [], agentId: '123', cc: ccMock })); @@ -32,7 +35,7 @@ describe('useUserState', () => { }); it('setAgentStatus should handle success', async () => { - const { result, waitForNextUpdate } = renderHook(() => useUserState({ idleCodes: [], agentId: '123', cc: ccMock })); + const { result } = renderHook(() => useUserState({ idleCodes: [], agentId: '123', cc: ccMock })); act(() => { result.current.setAgentStatus({ auxCodeId: '001', state: 'Available' }); @@ -40,21 +43,21 @@ describe('useUserState', () => { expect(result.current.isSettingAgentStatus).toBe(true); - await waitForNextUpdate(); - - expect(ccMock.setAgentState).toHaveBeenCalledWith(expect.objectContaining({ - state: 'Available', - auxCodeId: '001', - agentId: '123' - })); - expect(result.current.isSettingAgentStatus).toBe(false); - expect(result.current.errorMessage).toBe(''); - expect(result.current.elapsedTime).toBe(0); + waitFor(() => { + expect(ccMock.setAgentState).toHaveBeenCalledWith(expect.objectContaining({ + state: 'Available', + auxCodeId: '001', + agentId: '123' + })); + expect(result.current.isSettingAgentStatus).toBe(false); + expect(result.current.errorMessage).toBe(''); + expect(result.current.elapsedTime).toBe(0); + }); }); it('setAgentStatus should handle error', async () => { ccMock.setAgentState.mockRejectedValueOnce(new Error('Network error')); - const { result, waitForNextUpdate } = renderHook(() => useUserState({ idleCodes: [], agentId: '123', cc: ccMock })); + const { result } = renderHook(() => useUserState({ idleCodes: [], agentId: '123', cc: ccMock })); act(() => { result.current.setAgentStatus({ auxCodeId: '001', state: 'Available' }); @@ -62,9 +65,9 @@ describe('useUserState', () => { expect(result.current.isSettingAgentStatus).toBe(true); - await waitForNextUpdate(); - - expect(result.current.isSettingAgentStatus).toBe(false); - expect(result.current.errorMessage).toBe('Error: Network error'); + waitFor(() => { + expect(result.current.isSettingAgentStatus).toBe(false); + expect(result.current.errorMessage).toBe('Error: Network error'); + }); }); }); 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 1e1f50a62..9eb323152 100644 --- a/packages/contact-center/user-state/tests/user-state/index.tsx +++ b/packages/contact-center/user-state/tests/user-state/index.tsx @@ -6,7 +6,9 @@ import '@testing-library/jest-dom'; // Mock the store import jest.mock('@webex/cc-store', () => {return { - cc: {} + cc: {}, + idleCodes: [], + agentId: 'testAgentId' }}); describe('UserState Component', () => { @@ -15,7 +17,7 @@ describe('UserState Component', () => { render(); - expect(useUserStateSpy).toHaveBeenCalledWith({cc: {}}); + expect(useUserStateSpy).toHaveBeenCalledWith({cc: {}, idleCodes: [], agentId: 'testAgentId'}); const heading = screen.getByTestId('user-state-title'); expect(heading).toHaveTextContent('Agent State'); }); From 7724850805f077b8dc3aed775834989ea09fa314 Mon Sep 17 00:00:00 2001 From: Kesava Krishnan Madavan Date: Mon, 16 Dec 2024 08:28:12 +0530 Subject: [PATCH 09/12] docs(samples): inject wc user state on login --- docs/web-component-samples/app.js | 4 +++- docs/web-component-samples/index.html | 1 - 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/web-component-samples/app.js b/docs/web-component-samples/app.js index cb4394a97..24ff1caca 100644 --- a/docs/web-component-samples/app.js +++ b/docs/web-component-samples/app.js @@ -1,6 +1,7 @@ const accessTokenElem = document.getElementById('access_token_elem'); +const widgetsContainer = document.getElementById('widgets-container'); const ccStationLogin = document.getElementById('cc-station-login'); -const ccUserState = document.getElementById('cc-user-state'); +const ccUserState = document.createElement('widget-cc-user-state'); if (!ccStationLogin && !ccUserState) { console.error('Failed to find the required elements'); @@ -33,6 +34,7 @@ function initWidgets(){ function loginSuccess(){ console.log('Agent login has been succesful'); ccUserState.classList.remove('disabled'); + widgetsContainer.appendChild(ccUserState); } function logoutSuccess(){ diff --git a/docs/web-component-samples/index.html b/docs/web-component-samples/index.html index 48fb12181..137c03ed3 100644 --- a/docs/web-component-samples/index.html +++ b/docs/web-component-samples/index.html @@ -24,7 +24,6 @@

Contact Center widgets as web-component

-
From cb299879812c1f549645ab47003ec3517651b7aa Mon Sep 17 00:00:00 2001 From: Kesava Krishnan Madavan Date: Mon, 16 Dec 2024 08:31:18 +0530 Subject: [PATCH 10/12] fix(user-state): reset state on setAgetnStatus failure --- .../contact-center/user-state/src/helper.ts | 22 ++++++++++--- .../src/user-state/use-state.types.ts | 30 +++++++++++++++++ .../user-state/user-state.presentational.tsx | 33 +++++++++---------- 3 files changed, 63 insertions(+), 22 deletions(-) diff --git a/packages/contact-center/user-state/src/helper.ts b/packages/contact-center/user-state/src/helper.ts index b08ea5066..af5f1e119 100644 --- a/packages/contact-center/user-state/src/helper.ts +++ b/packages/contact-center/user-state/src/helper.ts @@ -6,6 +6,7 @@ export const useUserState = ({idleCodes, agentId, cc}) => { const [errorMessage, setErrorMessage] = useState(''); const [elapsedTime, setElapsedTime] = useState(0); + const [currentState, setCurrentState] = useState({}); useEffect(() => { // Reset the timer whenever the component mounts or the state changes @@ -18,17 +19,26 @@ export const useUserState = ({idleCodes, agentId, cc}) => { return () => clearInterval(timer); }, []); - const setAgentStatus = ({ - auxCodeId, - state - }) => { + const setAgentStatus = (selectedCode) => { + const { + auxCodeId, + state + } = { + auxCodeId: selectedCode.id, + state: selectedCode.name + } setIsSettingAgentStatus(true); + let oldState = { + ...currentState + }; + setCurrentState(selectedCode); const chosenState = state === 'Available' ? 'Available' : 'Idle'; cc.setAgentState({state: chosenState, auxCodeId, agentId, lastStateChangeReason: state}).then((response) => { setIsSettingAgentStatus(false); setErrorMessage(''); setElapsedTime(0); }).catch(error => { + setCurrentState(oldState); setErrorMessage(error.toString()); setIsSettingAgentStatus(false); }); @@ -39,6 +49,8 @@ export const useUserState = ({idleCodes, agentId, cc}) => { setAgentStatus, isSettingAgentStatus, errorMessage, - elapsedTime + elapsedTime, + currentState, + setCurrentState } }; diff --git a/packages/contact-center/user-state/src/user-state/use-state.types.ts b/packages/contact-center/user-state/src/user-state/use-state.types.ts index a198e6628..1d9708ae9 100644 --- a/packages/contact-center/user-state/src/user-state/use-state.types.ts +++ b/packages/contact-center/user-state/src/user-state/use-state.types.ts @@ -9,11 +9,41 @@ export interface IUserState { */ idleCodes: IdleCode[]; + /** + * Function to set the agent + * status. + * @param status The status to set. + * @param status.auxCodeId The aux code id. + * @param status.state The state to set. + * @returns void + */ setAgentStatus: (status: { auxCodeId: string; state: string }) => void; + /** + * Boolean indicating if the agent status is being set. + */ isSettingAgentStatus: boolean; + /** + * The error message to display + */ errorMessage: string; + /** + * The duration of the current user state + */ elapsedTime: number; + + /** + * The idle code of the current user state + */ + currentState: IdleCode; + + /** + * Function to set the current state + * of the user. + * @param state The state to set. + * @returns void + */ + setCurrentState: (state: IdleCode) => void; } diff --git a/packages/contact-center/user-state/src/user-state/user-state.presentational.tsx b/packages/contact-center/user-state/src/user-state/user-state.presentational.tsx index 1b32312d1..1c7174ed7 100644 --- a/packages/contact-center/user-state/src/user-state/user-state.presentational.tsx +++ b/packages/contact-center/user-state/src/user-state/user-state.presentational.tsx @@ -1,4 +1,4 @@ -import React, {CSSProperties, useMemo} from 'react'; +import React, {CSSProperties, useMemo, useRef} from 'react'; import {IUserState} from './use-state.types'; @@ -69,9 +69,10 @@ const getStyles = (isSettingAgentStatus: boolean): Record }); const UserStatePresentational: React.FunctionComponent = (props) => { - const {idleCodes,setAgentStatus,isSettingAgentStatus, errorMessage, elapsedTime} = props; + const {idleCodes,setAgentStatus,isSettingAgentStatus, errorMessage, elapsedTime, currentState} = props; const styles = useMemo(() => getStyles(isSettingAgentStatus), [isSettingAgentStatus]); + const selectRef = useRef(null); const formatTime = (time: number): string => { const hours = Math.floor(time / 3600); @@ -89,28 +90,26 @@ const UserStatePresentational: React.FunctionComponent = (props) =>
{formatTime(elapsedTime)}
{ From 8336229a3d714ae49971e1111c8acc892e0e2464 Mon Sep 17 00:00:00 2001 From: Kesava Krishnan Madavan Date: Mon, 16 Dec 2024 08:31:40 +0530 Subject: [PATCH 11/12] test(user-state): update tests to reflect changes --- .../contact-center/user-state/tests/helper.ts | 81 +++++++++++-------- .../user-state/user-state.presentational.tsx | 70 ++++++++++------ 2 files changed, 95 insertions(+), 56 deletions(-) diff --git a/packages/contact-center/user-state/tests/helper.ts b/packages/contact-center/user-state/tests/helper.ts index c4f8f9767..75543f2bd 100644 --- a/packages/contact-center/user-state/tests/helper.ts +++ b/packages/contact-center/user-state/tests/helper.ts @@ -1,32 +1,39 @@ -import { renderHook, act, waitFor } from '@testing-library/react'; // Import act from react -import { useUserState } from '../src/helper'; // adjust the path accordingly +import { renderHook, act, waitFor } from '@testing-library/react'; +import { useUserState } from '../src/helper'; -jest.useFakeTimers(); +describe('useUserState Hook', () => { + const mockCC = { + setAgentState: jest.fn() + }; -describe('useUserState', () => { - let ccMock; + const idleCodes = [ + { id: '1', name: 'Idle Code 1', isSystem: false }, + { id: '2', name: 'Available', isSystem: false } + ]; + + const agentId = 'agent123'; beforeEach(() => { - ccMock = { - setAgentState: jest.fn(() => Promise.resolve()), - }; + jest.useFakeTimers(); + mockCC.setAgentState.mockReset(); }); afterEach(() => { - jest.clearAllTimers(); + jest.useRealTimers(); }); - it('should initialize with correct default values', () => { - const { result } = renderHook(() => useUserState({ idleCodes: [], agentId: '123', cc: ccMock })); - + it('should initialize with default values', () => { + const { result } = renderHook(() => useUserState({ idleCodes, agentId, cc: mockCC })); + expect(result.current.isSettingAgentStatus).toBe(false); expect(result.current.errorMessage).toBe(''); expect(result.current.elapsedTime).toBe(0); + expect(result.current.currentState).toEqual({}); }); - it('should update elapsedTime every second', () => { - const { result } = renderHook(() => useUserState({ idleCodes: [], agentId: '123', cc: ccMock })); - + it('should increment elapsedTime every second', () => { + const { result } = renderHook(() => useUserState({ idleCodes, agentId, cc: mockCC })); + act(() => { jest.advanceTimersByTime(3000); }); @@ -34,40 +41,50 @@ describe('useUserState', () => { expect(result.current.elapsedTime).toBe(3); }); - it('setAgentStatus should handle success', async () => { - const { result } = renderHook(() => useUserState({ idleCodes: [], agentId: '123', cc: ccMock })); - + it('should reset elapsedTime when agent status is set', async () => { + mockCC.setAgentState.mockResolvedValueOnce({}); + const { result } = renderHook(() => useUserState({ idleCodes, agentId, cc: mockCC })); + + act(() => { + result.current.setAgentStatus(idleCodes[1]); + jest.advanceTimersByTime(1000); + }); + + waitFor(() => { + expect(result.current.elapsedTime).toBe(0); + }); + }); + + it('should handle setAgentStatus correctly and update current state', async () => { + mockCC.setAgentState.mockResolvedValueOnce({}); + const { result } = renderHook(() => useUserState({ idleCodes, agentId, cc: mockCC })); + act(() => { - result.current.setAgentStatus({ auxCodeId: '001', state: 'Available' }); + result.current.setAgentStatus(idleCodes[1]); }); expect(result.current.isSettingAgentStatus).toBe(true); waitFor(() => { - expect(ccMock.setAgentState).toHaveBeenCalledWith(expect.objectContaining({ - state: 'Available', - auxCodeId: '001', - agentId: '123' - })); expect(result.current.isSettingAgentStatus).toBe(false); expect(result.current.errorMessage).toBe(''); - expect(result.current.elapsedTime).toBe(0); + expect(result.current.currentState).toEqual(idleCodes[1]); }); }); - it('setAgentStatus should handle error', async () => { - ccMock.setAgentState.mockRejectedValueOnce(new Error('Network error')); - const { result } = renderHook(() => useUserState({ idleCodes: [], agentId: '123', cc: ccMock })); + it('should handle errors from setAgentStatus and revert state', async () => { + const errorMsg = 'Error setting agent status'; + mockCC.setAgentState.mockRejectedValueOnce(new Error(errorMsg)); + const { result } = renderHook(() => useUserState({ idleCodes, agentId, cc: mockCC })); act(() => { - result.current.setAgentStatus({ auxCodeId: '001', state: 'Available' }); + result.current.setAgentStatus(idleCodes[1]); }); - expect(result.current.isSettingAgentStatus).toBe(true); - waitFor(() => { expect(result.current.isSettingAgentStatus).toBe(false); - expect(result.current.errorMessage).toBe('Error: Network error'); + expect(result.current.errorMessage).toBe(`Error: ${errorMsg}`); + expect(result.current.currentState).toEqual({}); }); }); }); diff --git a/packages/contact-center/user-state/tests/user-state/user-state.presentational.tsx b/packages/contact-center/user-state/tests/user-state/user-state.presentational.tsx index b7d910e39..c17517aa6 100644 --- a/packages/contact-center/user-state/tests/user-state/user-state.presentational.tsx +++ b/packages/contact-center/user-state/tests/user-state/user-state.presentational.tsx @@ -5,36 +5,58 @@ import UserStatePresentational from '../../src/user-state/user-state.presentatio describe('UserStatePresentational Component', () => { const mockSetAgentStatus = jest.fn(); - const mockIdleCodes = [ - { id: '1', name: 'Available', isSystem: false, isDefault: true }, - { id: '2', name: 'On Break', isSystem: false, isDefault: false }, - ]; - - it('renders without crashing', () => { - render(); - expect(screen.getByText('Agent State')).toBeInTheDocument(); + const mockSetCurrentState = jest.fn(); + const defaultProps = { + idleCodes: [ + { id: '1', name: 'Idle Code 1', isSystem: false }, + { id: '2', name: 'Idle Code 2', isSystem: true }, + { id: '3', name: 'Idle Code 3', isSystem: false } + ], + setAgentStatus: mockSetAgentStatus, + isSettingAgentStatus: false, + errorMessage: '', + elapsedTime: 3661, // 1 hour, 1 minute, 1 second + currentState: { id: '1' }, + setCurrentState: mockSetCurrentState + }; + + it('should render the component with correct elements', () => { + render(); + expect(screen.getByTestId('user-state-title')).toHaveTextContent('Agent State'); + expect(screen.getByRole('combobox')).toBeInTheDocument(); + expect(screen.getByText('01:01:01')).toBeInTheDocument(); }); - it('displays the correct default option', () => { - render(); - const selectElement = screen.getByRole('combobox'); - expect(selectElement.value).toBe('1'); + it('should render only non-system idle codes in the dropdown', () => { + render(); + const options = screen.getAllByRole('option'); + expect(options).toHaveLength(2); + expect(options[0]).toHaveTextContent('Idle Code 1'); + expect(options[1]).toHaveTextContent('Idle Code 3'); }); - it('calls setAgentStatus with the correct parameters', () => { - render(); - const selectElement = screen.getByRole('combobox'); - fireEvent.change(selectElement, { target: { value: '2' } }); - expect(mockSetAgentStatus).toHaveBeenCalledWith({ auxCodeId: '2', state: 'On Break' }); + it('should call setAgentStatus with correct code when an idle code is selected', () => { + render(); + fireEvent.change(screen.getByRole('combobox'), { target: { value: '3' } }); + expect(mockSetAgentStatus).toHaveBeenCalledWith({ id: '3', name: 'Idle Code 3', isSystem: false }); }); - it('displays an error message when provided', () => { - render(); - expect(screen.getByText('Error occurred')).toBeInTheDocument(); + it('should display an error message if provided', () => { + render(); + expect(screen.getByText('Error message')).toBeInTheDocument(); + expect(screen.getByText('Error message')).toHaveStyle('color: red'); }); - it('formats elapsed time correctly', () => { - render(); - expect(screen.getByText('01:01:01')).toBeInTheDocument(); + it('should disable the select box when isSettingAgentStatus is true', () => { + render(); + expect(screen.getByRole('combobox')).toBeDisabled(); + }); + + it('should render elapsed time in correct color based on isSettingAgentStatus', () => { + const { rerender } = render(); + expect(screen.getByText('01:01:01')).toHaveStyle('color: black'); + + rerender(); + expect(screen.getByText('01:01:01')).toHaveStyle('color: grey'); }); -}); \ No newline at end of file +}); From 74d9057dc9b434d3668a5c4e88526c4a56310352 Mon Sep 17 00:00:00 2001 From: Kesava Krishnan Madavan Date: Mon, 16 Dec 2024 16:47:02 +0530 Subject: [PATCH 12/12] fix: resolve reveiw comments --- .../contact-center/store/src/store.types.ts | 1 + packages/contact-center/store/tests/store.ts | 2 +- .../contact-center/user-state/src/helper.ts | 2 +- .../contact-center/user-state/tests/helper.ts | 36 +++++++++++-------- 4 files changed, 24 insertions(+), 17 deletions(-) diff --git a/packages/contact-center/store/src/store.types.ts b/packages/contact-center/store/src/store.types.ts index bed35a23b..f5f3d31e9 100644 --- a/packages/contact-center/store/src/store.types.ts +++ b/packages/contact-center/store/src/store.types.ts @@ -23,6 +23,7 @@ interface IStore { loginOptions: string[]; cc: IContactCenter; idleCodes: IdleCode[]; + agentId: string; registerCC(webex: WithWebex['webex']): Promise; init(params: InitParams): Promise; diff --git a/packages/contact-center/store/tests/store.ts b/packages/contact-center/store/tests/store.ts index 843cd7443..d0bf75dbf 100644 --- a/packages/contact-center/store/tests/store.ts +++ b/packages/contact-center/store/tests/store.ts @@ -44,7 +44,7 @@ describe('Store', () => { }); describe('registerCC', () => { - it('should set teams and loginOptions on successful register', async () => { + it('should initialise store values on successful register', async () => { const mockResponse = { teams: [{ id: 'team1', name: 'Team 1' }], loginVoiceOptions: ['option1', 'option2'], diff --git a/packages/contact-center/user-state/src/helper.ts b/packages/contact-center/user-state/src/helper.ts index af5f1e119..1b5e9fd83 100644 --- a/packages/contact-center/user-state/src/helper.ts +++ b/packages/contact-center/user-state/src/helper.ts @@ -34,12 +34,12 @@ export const useUserState = ({idleCodes, agentId, cc}) => { setCurrentState(selectedCode); const chosenState = state === 'Available' ? 'Available' : 'Idle'; cc.setAgentState({state: chosenState, auxCodeId, agentId, lastStateChangeReason: state}).then((response) => { - setIsSettingAgentStatus(false); setErrorMessage(''); setElapsedTime(0); }).catch(error => { setCurrentState(oldState); setErrorMessage(error.toString()); + }).finally(() => { setIsSettingAgentStatus(false); }); }; diff --git a/packages/contact-center/user-state/tests/helper.ts b/packages/contact-center/user-state/tests/helper.ts index 75543f2bd..69e4209fa 100644 --- a/packages/contact-center/user-state/tests/helper.ts +++ b/packages/contact-center/user-state/tests/helper.ts @@ -25,10 +25,12 @@ describe('useUserState Hook', () => { it('should initialize with default values', () => { const { result } = renderHook(() => useUserState({ idleCodes, agentId, cc: mockCC })); - expect(result.current.isSettingAgentStatus).toBe(false); - expect(result.current.errorMessage).toBe(''); - expect(result.current.elapsedTime).toBe(0); - expect(result.current.currentState).toEqual({}); + expect(result.current).toMatchObject({ + isSettingAgentStatus: false, + errorMessage: '', + elapsedTime: 0, + currentState: {} + }); }); it('should increment elapsedTime every second', () => { @@ -47,11 +49,11 @@ describe('useUserState Hook', () => { act(() => { result.current.setAgentStatus(idleCodes[1]); - jest.advanceTimersByTime(1000); + jest.advanceTimersByTime(3000); }); - waitFor(() => { - expect(result.current.elapsedTime).toBe(0); + await waitFor(() => { + expect(result.current.elapsedTime).toBe(3); }); }); @@ -65,10 +67,12 @@ describe('useUserState Hook', () => { expect(result.current.isSettingAgentStatus).toBe(true); - waitFor(() => { - expect(result.current.isSettingAgentStatus).toBe(false); - expect(result.current.errorMessage).toBe(''); - expect(result.current.currentState).toEqual(idleCodes[1]); + await waitFor(() => { + expect(result.current).toMatchObject({ + isSettingAgentStatus: false, + errorMessage: '', + currentState: idleCodes[1] + }); }); }); @@ -81,10 +85,12 @@ describe('useUserState Hook', () => { result.current.setAgentStatus(idleCodes[1]); }); - waitFor(() => { - expect(result.current.isSettingAgentStatus).toBe(false); - expect(result.current.errorMessage).toBe(`Error: ${errorMsg}`); - expect(result.current.currentState).toEqual({}); + await waitFor(() => { + expect(result.current).toMatchObject({ + isSettingAgentStatus: false, + errorMessage: `Error: ${errorMsg}`, + currentState: {} + }); }); }); });