Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import React, {useEffect, useRef} from 'react';
import {Button, Text} from '@momentum-design/components/dist/react';
import {CampaignErrorDialogProps, ERROR_TITLES, ERROR_MESSAGE} from './campaign-error-dialog.types';
import {withMetrics} from '@webex/cc-ui-logging';
import './campaign-error-dialog.style.scss';

const CampaignErrorDialog: React.FunctionComponent<CampaignErrorDialogProps> = ({errorType, isOpen, onClose}) => {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

doesnt momentum have a dialog you can use? and can you make a generic dialog that accepts props and then use that here? That way if we need another one we don't have to do this again

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does! However the momentum-design package has not been updated in 11 months and the changes necessary to use the newer version are large enough that they should be their own story.
I'd be happy to take that story as well.

const dialogRef = useRef<HTMLDialogElement>(null);

useEffect(() => {
const dialog = dialogRef.current;
if (!dialog) return;

if (isOpen && !dialog.open) {
dialog.showModal();
} else if (!isOpen && dialog.open) {
dialog.close();
}
}, [isOpen]);

const handleKeyDown = (event: React.KeyboardEvent<HTMLDialogElement>) => {
if (event.key === 'Escape') {
onClose();
}
};

const handleClose = () => {
onClose();
};

return (
<dialog
ref={dialogRef}
className="campaign-error-dialog"
data-testid="campaign-error-dialog"
onKeyDown={handleKeyDown}
>
<Text
tagname="h2"
type="body-large-bold"
className="campaign-error-dialog-title"
data-testid="campaign-error-dialog-title"
>
{ERROR_TITLES[errorType]}
</Text>
<Text
tagname="p"
type="body-midsize-regular"
className="campaign-error-dialog-message"
data-testid="campaign-error-dialog-message"
>
{ERROR_MESSAGE}
</Text>
<div className="campaign-error-dialog-actions">
<Button onClick={handleClose} data-testid="campaign-error-dialog-ok-button">
OK
</Button>
</div>
</dialog>
);
};

const CampaignErrorDialogWithMetrics = withMetrics(CampaignErrorDialog, 'CampaignErrorDialog');
export default CampaignErrorDialogWithMetrics;
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
.campaign-error-dialog {
width: 25rem;
border-radius: 0.5rem;
border-color: transparent;
padding: 1rem;
box-shadow: 0rem 0.25rem 0.5rem 0rem rgba(0, 0, 0, 0.16), 0rem 0rem 0.0625rem 0rem rgba(0, 0, 0, 0.16);

&::backdrop {
background-color: rgba(0, 0, 0, 0.5);
}

.campaign-error-dialog-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 1rem;
}

.campaign-error-dialog-title {
margin: 0;
flex: 1;
}

.campaign-error-dialog-close {
margin-left: 0.5rem;
}

.campaign-error-dialog-message {
margin-bottom: 1.5rem;
}

.campaign-error-dialog-actions {
display: flex;
justify-content: flex-end;
gap: 0.5rem;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export type CampaignErrorType = 'ACCEPT_FAILED' | 'SKIP_FAILED' | 'REMOVE_FAILED';

export interface CampaignErrorDialogProps {
errorType: CampaignErrorType;
isOpen: boolean;
onClose: () => void;
}

export const ERROR_TITLES: Record<CampaignErrorType, string> = {
ACCEPT_FAILED: "Can't accept contact",
SKIP_FAILED: "Can't skip contact",
REMOVE_FAILED: "Can't remove contact",
};

export const ERROR_MESSAGE =
'We ran into an issue connecting you with this contact. Check your network connection and try again.';
3 changes: 3 additions & 0 deletions packages/contact-center/cc-components/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import CallControlCADComponent from './components/task/CallControlCAD/call-contr
import IncomingTaskComponent from './components/task/IncomingTask/incoming-task';
import TaskListComponent from './components/task/TaskList/task-list';
import OutdialCallComponent from './components/task/OutdialCall/outdial-call';
import CampaignErrorDialogComponent from './components/task/CampaignErrorDialog/CampaignErrorDialog';

export {
UserStateComponent,
Expand All @@ -14,8 +15,10 @@ export {
IncomingTaskComponent,
TaskListComponent,
OutdialCallComponent,
CampaignErrorDialogComponent,
};
export * from './components/StationLogin/constants';
export * from './components/StationLogin/station-login.types';
export * from './components/UserState/user-state.types';
export * from './components/task/task.types';
export * from './components/task/CampaignErrorDialog/campaign-error-dialog.types';
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import React from 'react';
import {render, fireEvent, screen} from '@testing-library/react';
import '@testing-library/jest-dom';
import CampaignErrorDialogComponent from '../../../../src/components/task/CampaignErrorDialog/CampaignErrorDialog';
import {
CampaignErrorDialogProps,
CampaignErrorType,
ERROR_TITLES,
ERROR_MESSAGE,
} from '../../../../src/components/task/CampaignErrorDialog/campaign-error-dialog.types';

// Mock HTMLDialogElement methods
HTMLDialogElement.prototype.showModal = jest.fn();
HTMLDialogElement.prototype.close = jest.fn();

describe('CampaignErrorDialogComponent', () => {
const mockOnClose = jest.fn();

const defaultProps: CampaignErrorDialogProps = {
errorType: 'ACCEPT_FAILED',
isOpen: false,
onClose: mockOnClose,
};

beforeEach(() => {
jest.clearAllMocks();
(HTMLDialogElement.prototype.showModal as jest.Mock).mockClear();
(HTMLDialogElement.prototype.close as jest.Mock).mockClear();
});

describe('Rendering', () => {
it('should render the dialog element', () => {
render(<CampaignErrorDialogComponent {...defaultProps} />);

const dialog = screen.getByTestId('campaign-error-dialog');
expect(dialog).toBeInTheDocument();
expect(dialog).toHaveClass('campaign-error-dialog');
});

it('should render the correct title for ACCEPT_FAILED error type', () => {
render(<CampaignErrorDialogComponent {...defaultProps} errorType="ACCEPT_FAILED" isOpen={true} />);

const title = screen.getByTestId('campaign-error-dialog-title');
expect(title).toHaveTextContent(ERROR_TITLES.ACCEPT_FAILED);
expect(title).toHaveTextContent("Can't accept contact");
});

it('should render the correct title for SKIP_FAILED error type', () => {
render(<CampaignErrorDialogComponent {...defaultProps} errorType="SKIP_FAILED" isOpen={true} />);

const title = screen.getByTestId('campaign-error-dialog-title');
expect(title).toHaveTextContent(ERROR_TITLES.SKIP_FAILED);
expect(title).toHaveTextContent("Can't skip contact");
});

it('should render the correct title for REMOVE_FAILED error type', () => {
render(<CampaignErrorDialogComponent {...defaultProps} errorType="REMOVE_FAILED" isOpen={true} />);

const title = screen.getByTestId('campaign-error-dialog-title');
expect(title).toHaveTextContent(ERROR_TITLES.REMOVE_FAILED);
expect(title).toHaveTextContent("Can't remove contact");
});

it('should render the error message', () => {
render(<CampaignErrorDialogComponent {...defaultProps} isOpen={true} />);

const message = screen.getByTestId('campaign-error-dialog-message');
expect(message).toHaveTextContent(ERROR_MESSAGE);
expect(message).toHaveTextContent(
'We ran into an issue connecting you with this contact. Check your network connection and try again.'
);
});

it('should render the OK button', () => {
render(<CampaignErrorDialogComponent {...defaultProps} isOpen={true} />);

const okButton = screen.getByTestId('campaign-error-dialog-ok-button');
expect(okButton).toBeInTheDocument();
expect(okButton).toHaveTextContent('OK');
});
});

describe('Dialog Open/Close Behavior', () => {
it('should call showModal when isOpen changes to true', () => {
const {rerender} = render(<CampaignErrorDialogComponent {...defaultProps} isOpen={false} />);

expect(HTMLDialogElement.prototype.showModal).not.toHaveBeenCalled();

rerender(<CampaignErrorDialogComponent {...defaultProps} isOpen={true} />);

expect(HTMLDialogElement.prototype.showModal).toHaveBeenCalledTimes(1);
});

it('should call close when isOpen changes to false', () => {
const {rerender} = render(<CampaignErrorDialogComponent {...defaultProps} isOpen={true} />);

// Simulate dialog being open
const dialog = screen.getByTestId('campaign-error-dialog') as HTMLDialogElement;
Object.defineProperty(dialog, 'open', {value: true, writable: true});

rerender(<CampaignErrorDialogComponent {...defaultProps} isOpen={false} />);

expect(HTMLDialogElement.prototype.close).toHaveBeenCalledTimes(1);
});
});

describe('User Interactions', () => {
it('should call onClose when OK button is clicked', () => {
render(<CampaignErrorDialogComponent {...defaultProps} isOpen={true} />);

const okButton = screen.getByTestId('campaign-error-dialog-ok-button');
fireEvent.click(okButton);

expect(mockOnClose).toHaveBeenCalledTimes(1);
});

it('should call onClose when Escape key is pressed', () => {
render(<CampaignErrorDialogComponent {...defaultProps} isOpen={true} />);

const dialog = screen.getByTestId('campaign-error-dialog');
fireEvent.keyDown(dialog, {key: 'Escape'});

expect(mockOnClose).toHaveBeenCalledTimes(1);
});

it('should not call onClose when other keys are pressed', () => {
render(<CampaignErrorDialogComponent {...defaultProps} isOpen={true} />);

const dialog = screen.getByTestId('campaign-error-dialog');
fireEvent.keyDown(dialog, {key: 'Enter'});
fireEvent.keyDown(dialog, {key: 'Tab'});
fireEvent.keyDown(dialog, {key: 'Space'});

expect(mockOnClose).not.toHaveBeenCalled();
});
});

describe('Error Type Mapping', () => {
const errorTypes: CampaignErrorType[] = ['ACCEPT_FAILED', 'SKIP_FAILED', 'REMOVE_FAILED'];

errorTypes.forEach((errorType) => {
it(`should display correct title for ${errorType}`, () => {
render(<CampaignErrorDialogComponent {...defaultProps} errorType={errorType} isOpen={true} />);

const title = screen.getByTestId('campaign-error-dialog-title');
expect(title).toHaveTextContent(ERROR_TITLES[errorType]);
});
});
});

describe('Accessibility', () => {
it('should have proper dialog structure', () => {
render(<CampaignErrorDialogComponent {...defaultProps} isOpen={true} />);

const dialog = screen.getByTestId('campaign-error-dialog');
expect(dialog.tagName).toBe('DIALOG');
});

it('should have heading element for title', () => {
render(<CampaignErrorDialogComponent {...defaultProps} isOpen={true} />);

const title = screen.getByTestId('campaign-error-dialog-title');
expect(title).toBeInTheDocument();
});
});
});
Loading