Skip to content

Commit

Permalink
tech: refactor delete modal (#908)
Browse files Browse the repository at this point in the history
  • Loading branch information
haneabogdan authored Oct 17, 2023
1 parent 68ad197 commit aa18049
Show file tree
Hide file tree
Showing 17 changed files with 441 additions and 101 deletions.
16 changes: 14 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@
"react-use-websocket": "^4.2.0",
"reactflow": "^11.7.4",
"semver": "^7.5.4",
"strip-ansi": "^7.0.1",
"strip-ansi": "^6.0.1",
"styled-components": "5.3.5",
"zustand": "^4.4.1"
},
Expand Down
18 changes: 4 additions & 14 deletions packages/web/src/components/molecules/CommonSettings/Delete.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import {FC} from 'react';

import {UseMutation} from '@reduxjs/toolkit/dist/query/react/buildHooks';
import {MutationDefinition} from '@reduxjs/toolkit/query';

import {useModal} from '@modal/hooks';

import {DeleteEntityModal} from '@molecules';
Expand All @@ -14,21 +11,14 @@ interface DeleteProps {
description: string;
label: string;
redirectUrl: string;
useDeleteMutation: UseMutation<MutationDefinition<string, any, any, void>>;
onDelete: (id: string) => Promise<any>;
}

const Delete: FC<DeleteProps> = ({name, description, label, redirectUrl, useDeleteMutation}) => {
const {open} = useModal({
const Delete: FC<DeleteProps> = ({name, description, label, redirectUrl, onDelete}) => {
const {open, close} = useModal({
title: `Delete this ${label}`,
width: 600,
content: (
<DeleteEntityModal
defaultStackRoute={redirectUrl}
useDeleteMutation={useDeleteMutation}
name={name}
entityLabel={label}
/>
),
content: <DeleteEntityModal defaultStackRoute={redirectUrl} onDelete={onDelete} name={name} entityLabel={label} />,
});

return (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import React, {FC} from 'react';

import {fireEvent, render, waitFor} from '@testing-library/react';

import DashboardContext from '@contexts/DashboardContext';

import ModalContext from '@modal/context';

import {mockDashboardContextValue, mockModalContextValue} from '@utils/mocks';

import DeleteEntityModal, {DeleteEntityModalProps} from './DeleteEntityModal';

describe('DeleteEntityModal', () => {
let onDelete: jest.Mock;
const onConfirm = jest.fn();
const entityLabel = 'item';
const defaultStackRoute = '/items';
const name = 'Test Item';
const idToDelete = 'test-item-id';

beforeEach(() => {
onDelete = jest.fn().mockImplementation(() => Promise.resolve());
});

const DeleteEntityModalWrapper: FC<DeleteEntityModalProps> = props => (
<DashboardContext.Provider value={mockDashboardContextValue}>
<ModalContext.Provider value={mockModalContextValue}>
<DeleteEntityModal {...props} />
</ModalContext.Provider>
</DashboardContext.Provider>
);

afterEach(() => {
jest.clearAllMocks();
});

it('should render the modal with the correct content', async () => {
const {getByTestId} = render(
<DeleteEntityModalWrapper
onDelete={onDelete}
onConfirm={onConfirm}
entityLabel={entityLabel}
defaultStackRoute={defaultStackRoute}
name={name}
idToDelete={idToDelete}
/>
);

expect(getByTestId('delete-entity-title')).toBeInTheDocument();
expect(getByTestId('delete-entity-subtitle')).toBeInTheDocument();
expect(getByTestId('delete-entity-input')).toBeInTheDocument();
});

it('should disable the delete button if the entered name does not match the entity name', async () => {
const {getByTestId} = render(
<DeleteEntityModalWrapper
onDelete={onDelete}
onConfirm={onConfirm}
entityLabel={entityLabel}
defaultStackRoute={defaultStackRoute}
name={name}
idToDelete={idToDelete}
/>
);

const input = getByTestId(`delete-entity-input`);
const deleteButton = getByTestId('delete-action-btn');

fireEvent.change(input, {target: {value: 'Wrong Name'}});
expect(deleteButton).toBeDisabled();

fireEvent.change(input, {target: {value: name}});
expect(deleteButton).not.toBeDisabled();
});

it('should call onDelete when the delete button is clicked with the correct id', async () => {
const {getByTestId} = render(
<DeleteEntityModalWrapper
onDelete={onDelete}
onConfirm={onConfirm}
entityLabel={entityLabel}
defaultStackRoute={defaultStackRoute}
name={name}
idToDelete={idToDelete}
/>
);

const input = getByTestId(`delete-entity-input`);
const deleteButton = getByTestId('delete-action-btn');

fireEvent.change(input, {target: {value: name}});
fireEvent.click(deleteButton);

await waitFor(() => {
expect(onDelete).toHaveBeenCalledWith(idToDelete);
});
});

it('should call onConfirm when the delete is successful', async () => {
const {getByTestId} = render(
<DeleteEntityModalWrapper
onDelete={onDelete}
onConfirm={onConfirm}
entityLabel={entityLabel}
defaultStackRoute={defaultStackRoute}
name={name}
idToDelete={idToDelete}
/>
);

const input = getByTestId(`delete-entity-input`);
const deleteButton = getByTestId('delete-action-btn');

fireEvent.change(input, {target: {value: name}});
fireEvent.click(deleteButton);

await waitFor(() => {
expect(onConfirm).toHaveBeenCalled();
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -2,101 +2,62 @@ import {useState} from 'react';

import {Input} from 'antd';

import {MutationDefinition} from '@reduxjs/toolkit/dist/query';
import {UseMutation} from '@reduxjs/toolkit/dist/query/react/buildHooks';

import {capitalize} from 'lodash';

import {Button, FullWidthSpace, Text} from '@custom-antd';

import {useDashboardNavigate} from '@hooks/useDashboardNavigate';
import usePressEnter from '@hooks/usePressEnter';
import {FullWidthSpace, Text} from '@custom-antd';

import {useModal} from '@modal/hooks';

import {notificationCall} from '@molecules';
import {DeleteModal} from '@molecules';

import Colors from '@styles/Colors';

import {displayDefaultNotificationFlow} from '@utils/notification';

import {FooterSpace} from './DeleteEntityModal.styled';

// TODO: refactor using DeleteModal
const DeleteEntityModal: React.FC<{
// onCancel is passed from parent component <Modal />.
// Do not pass it directly
onCancel?: any;
useDeleteMutation: UseMutation<MutationDefinition<string, any, any, void>>;
export interface DeleteEntityModalProps {
onDelete: (id: string) => Promise<any>;
name: string;
entityLabel: string;
defaultStackRoute: string;
idToDelete?: string;
onConfirm?: any;
}> = props => {
const {onCancel, useDeleteMutation, name, onConfirm, entityLabel, defaultStackRoute, idToDelete} = props;

const {close} = useModal();
const back = useDashboardNavigate(defaultStackRoute);

const onEvent = usePressEnter();
onConfirm?: () => void;
}

const [deleteEntity] = useDeleteMutation();
const DeleteEntityModal: React.FC<DeleteEntityModalProps> = props => {
const {onDelete, name, onConfirm, entityLabel, defaultStackRoute, idToDelete} = props;

const {close} = useModal();
const [checkName, setName] = useState('');

const onDelete = () => {
deleteEntity(idToDelete || name)
.then(displayDefaultNotificationFlow)
.then(() => {
notificationCall('passed', `${capitalize(entityLabel)} was successfully deleted.`);
close();

if (onConfirm) {
onConfirm();
} else {
back();
}
})
.catch(error => {
notificationCall('failed', error.title, error.message);
});
};

return (
<FullWidthSpace
size={24}
direction="vertical"
onKeyPress={event => {
if (name === checkName) {
onEvent(event, onDelete);
}
}}
>
<Text className="regular middle" color={Colors.slate400}>
Do you really want to delete this {entityLabel}?
<br />
All your historical and analytical data will also be removed.
</Text>
<Text className="regular middle" color={Colors.slate400}>
Please enter the name of this {entityLabel} (<span style={{color: Colors.whitePure}}>{name}</span>) to delete it
forever.
</Text>
<Input
placeholder={`${capitalize(entityLabel)} name`}
onChange={e => {
setName(e.target.value);
}}
/>
<FooterSpace size={10}>
<Button $customType="secondary" onClick={onCancel}>
Cancel
</Button>
<Button $customType="warning" disabled={name !== checkName} onClick={onDelete}>
Delete
</Button>
</FooterSpace>
</FullWidthSpace>
<DeleteModal
onDelete={onDelete}
onClose={close}
onConfirm={onConfirm}
successMessage={`${capitalize(entityLabel)} was successfully deleted.`}
defaultStackRoute={defaultStackRoute}
idToDelete={idToDelete || name}
actionDisabled={checkName !== name}
deleteOnEnter
content={
<FullWidthSpace size={24} direction="vertical">
<Text data-testid="delete-entity-title" className="regular middle" color={Colors.slate400}>
Do you really want to delete this {entityLabel}?
<br />
All your historical and analytical data will also be removed.
</Text>
<Text data-testid="delete-entity-subtitle" className="regular middle" color={Colors.slate400}>
Please enter the name of this {entityLabel} (<span style={{color: Colors.whitePure}}>{name}</span>) to
delete it forever.
</Text>
<Input
data-testid="delete-entity-input"
placeholder={`${capitalize(entityLabel)} name`}
onChange={e => {
setName(e.target.value);
}}
/>
</FullWidthSpace>
}
/>
);
};

Expand Down
Loading

0 comments on commit aa18049

Please sign in to comment.