Add ConfirmationModal common component (#383)

* Add ConfirmationModal common component

* Update specs
This commit is contained in:
Oleg Shur 2021-04-20 16:48:50 +03:00 committed by GitHub
parent ab57772329
commit 9d62670eef
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 236 additions and 76 deletions

View file

@ -37,9 +37,11 @@ const List: React.FC<ListProps> = ({
return (
<div className="section">
<Breadcrumb>All Connectors</Breadcrumb>
<div className="box has-background-danger has-text-centered has-text-light">
<article className="message is-warning">
<div className="message-body">
Kafka Connect section is under construction.
</div>
</article>
<MetricsWrapper>
<Indicator
className="level-left is-one-third"

View file

@ -9,6 +9,7 @@ import { deleteConnector } from 'redux/actions';
import Dropdown from 'components/common/Dropdown/Dropdown';
import DropdownDivider from 'components/common/Dropdown/DropdownDivider';
import DropdownItem from 'components/common/Dropdown/DropdownItem';
import ConfirmationModal from 'components/common/ConfirmationModal/ConfirmationModal';
import StatusTag from '../StatusTag';
export interface ListItemProps {
@ -30,11 +31,16 @@ const ListItem: React.FC<ListItemProps> = ({
},
}) => {
const dispatch = useDispatch();
const [
isDeleteConnectorConfirmationVisible,
setDeleteConnectorConfirmationVisible,
] = React.useState(false);
const handleDelete = React.useCallback(() => {
if (clusterName && connect && name) {
dispatch(deleteConnector(clusterName, connect, name));
}
setDeleteConnectorConfirmationVisible(false);
}, [clusterName, connect, name]);
const runningTasks = React.useMemo(() => {
@ -67,7 +73,8 @@ const ListItem: React.FC<ListItemProps> = ({
</span>
)}
</td>
<td className="has-text-right">
<td>
<div className="has-text-right">
<Dropdown
label={
<span className="icon">
@ -77,10 +84,20 @@ const ListItem: React.FC<ListItemProps> = ({
right
>
<DropdownDivider />
<DropdownItem onClick={handleDelete}>
<DropdownItem
onClick={() => setDeleteConnectorConfirmationVisible(true)}
>
<span className="has-text-danger">Remove Connector</span>
</DropdownItem>
</Dropdown>
</div>
<ConfirmationModal
isOpen={isDeleteConnectorConfirmationVisible}
onCancel={() => setDeleteConnectorConfirmationVisible(false)}
onConfirm={handleDelete}
>
Are you sure want to remove <b>{name}</b> connector?
</ConfirmationModal>
</td>
</tr>
);

View file

@ -8,6 +8,11 @@ import ListItem, { ListItemProps } from '../ListItem';
const store = configureStore();
jest.mock(
'components/common/ConfirmationModal/ConfirmationModal',
() => 'mock-ConfirmationModal'
);
describe('Connectors ListItem', () => {
const connector = connectorsPayload[0];
const setupWrapper = (props: Partial<ListItemProps> = {}) => (
@ -57,7 +62,12 @@ describe('Connectors ListItem', () => {
it('handles delete', () => {
const wrapper = mount(setupWrapper());
wrapper.find('DropdownItem a').last().simulate('click');
expect(wrapper.find('mock-ConfirmationModal').prop('isOpen')).toBeFalsy();
wrapper.find('DropdownItem').last().simulate('click');
const modal = wrapper.find('mock-ConfirmationModal');
expect(modal.prop('isOpen')).toBeTruthy();
modal.simulate('cancel');
expect(wrapper.find('mock-ConfirmationModal').prop('isOpen')).toBeFalsy();
});
it('matches snapshot', () => {

View file

@ -110,7 +110,8 @@ exports[`Connectors ListItem matches snapshot 1`] = `
2
</span>
</td>
<td
<td>
<div
className="has-text-right"
>
<Dropdown
@ -181,6 +182,18 @@ exports[`Connectors ListItem matches snapshot 1`] = `
</div>
</div>
</Dropdown>
</div>
<mock-ConfirmationModal
isOpen={false}
onCancel={[Function]}
onConfirm={[Function]}
>
Are you sure want to remove
<b>
hdfs-source-connector
</b>
connector?
</mock-ConfirmationModal>
</td>
</tr>
</ListItem>

View file

@ -0,0 +1,50 @@
import React from 'react';
export interface ConfirmationModalProps {
isOpen?: boolean;
title?: React.ReactNode;
onConfirm(): void;
onCancel(): void;
}
const ConfirmationModal: React.FC<ConfirmationModalProps> = ({
isOpen,
children,
title,
onCancel,
onConfirm,
}) => {
if (!isOpen) return null;
return (
<div className="modal is-active">
<div className="modal-background" onClick={onCancel} aria-hidden="true" />
<div className="modal-card">
<header className="modal-card-head">
<p className="modal-card-title">{title || 'Confirm the action'}</p>
<button
onClick={onCancel}
type="button"
className="delete"
aria-label="close"
/>
</header>
<section className="modal-card-body">{children}</section>
<footer className="modal-card-foot is-justify-content-flex-end">
<button
onClick={onConfirm}
type="button"
className="button is-danger"
>
Confirm
</button>
<button onClick={onCancel} type="button" className="button">
Cancel
</button>
</footer>
</div>
</div>
);
};
export default ConfirmationModal;

View file

@ -0,0 +1,68 @@
import { mount, ReactWrapper } from 'enzyme';
import React from 'react';
import ConfirmationModal, {
ConfirmationModalProps,
} from '../ConfirmationModal';
const confirmMock = jest.fn();
const cancelMock = jest.fn();
const body = 'Please Confirm the action!';
describe('ConfiramationModal', () => {
const setupWrapper = (props: Partial<ConfirmationModalProps> = {}) => (
<ConfirmationModal onCancel={cancelMock} onConfirm={confirmMock} {...props}>
{body}
</ConfirmationModal>
);
it('renders nothing', () => {
const wrapper = mount(setupWrapper({ isOpen: false }));
expect(wrapper.exists(ConfirmationModal)).toBeTruthy();
expect(wrapper.exists('.modal.is-active')).toBeFalsy();
});
it('renders modal', () => {
const wrapper = mount(setupWrapper({ isOpen: true }));
expect(wrapper.exists(ConfirmationModal)).toBeTruthy();
expect(wrapper.exists('.modal.is-active')).toBeTruthy();
expect(wrapper.find('.modal-card-body').text()).toEqual(body);
});
it('renders modal with default header', () => {
const wrapper = mount(setupWrapper({ isOpen: true }));
expect(wrapper.find('.modal-card-title').text()).toEqual(
'Confirm the action'
);
});
it('renders modal with custom header', () => {
const title = 'My Custom Header';
const wrapper = mount(setupWrapper({ isOpen: true, title }));
expect(wrapper.find('.modal-card-title').text()).toEqual(title);
});
it('handles onConfirm when user clicks confirm button', () => {
const wrapper = mount(setupWrapper({ isOpen: true }));
expect(wrapper.find('.modal-card-foot button').length).toEqual(2);
const cancelBtn = wrapper.find('.modal-card-foot button').at(0);
expect(cancelBtn.text()).toEqual('Confirm');
cancelBtn.simulate('click');
expect(cancelMock).toHaveBeenCalledTimes(0);
expect(confirmMock).toHaveBeenCalledTimes(1);
});
describe('cancellation', () => {
let wrapper: ReactWrapper;
beforeEach(() => {
wrapper = mount(setupWrapper({ isOpen: true }));
});
it('handles onCancel when user clicks on modal-background', () => {
wrapper.find('.modal-background').simulate('click');
expect(cancelMock).toHaveBeenCalledTimes(1);
expect(confirmMock).toHaveBeenCalledTimes(0);
});
it('handles onCancel when user clicks on Cancel button', () => {
expect(wrapper.find('.modal-card-foot button').length).toEqual(2);
const cancelBtn = wrapper.find('.modal-card-foot button').at(1);
expect(cancelBtn.text()).toEqual('Cancel');
cancelBtn.simulate('click');
expect(cancelMock).toHaveBeenCalledTimes(1);
expect(confirmMock).toHaveBeenCalledTimes(0);
});
});
});