Skip to content

Commit

Permalink
chore(content-explorer): Migrate deleteConfirmationDialog
Browse files Browse the repository at this point in the history
  • Loading branch information
greg-in-a-box committed Nov 5, 2024
1 parent a426f57 commit 4598961
Show file tree
Hide file tree
Showing 9 changed files with 188 additions and 80 deletions.
6 changes: 3 additions & 3 deletions i18n/en-US.properties
Original file line number Diff line number Diff line change
Expand Up @@ -519,7 +519,7 @@ be.messageCenter.events = Events
# Displayed when there are no posts to display
be.messageCenter.noPosts = There are no posts for this category at the moment.
# Error message for preview not loading an image
be.messageCenter.previewError = Sorry, we're having trouble showing this image.
be.messageCenter.previewError = Sorry, we're having trouble showing this image.
# Title for product category
be.messageCenter.product = Product
# Title for the message center modal
Expand Down Expand Up @@ -999,9 +999,9 @@ boxui.contentExplorer.name = Name
# Text shown on button used to create a new folder
boxui.contentExplorer.newFolder = New Folder
# Text shown to indicate the number of folders selected
boxui.contentExplorer.numFoldersSelected = {numSelected, plural, =0 {0 folders selected} one {1 folder selected} other {# folders selected} }
boxui.contentExplorer.numFoldersSelected = {numSelected, plural, =0 {0 folders selected} one {1 folder selected} other {# folders selected} }
# Text shown to indicate the number of items selected with Include Subfolders feature
boxui.contentExplorer.numItemsSelected = {numSelected, plural, =0 {0 items selected} one {1 item selected} other {# items selected} }
boxui.contentExplorer.numItemsSelected = {numSelected, plural, =0 {0 items selected} one {1 item selected} other {# items selected} }
# Text shown to indicate the number of items selected
boxui.contentExplorer.numSelected = {numSelected} Selected
# Results label for number of items on list when it's just 1
Expand Down
6 changes: 6 additions & 0 deletions src/elements/common/modal.scss
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
@use '@box/blueprint-web-assets/tokens/tokens.scss';

@import './variables';

.be-modal {
Expand All @@ -6,6 +8,10 @@
justify-content: center;
padding: 15px 0 0;

[class^='bp_base_button_module_button']:last-child {
margin-inline-start: tokens.$space-3;
}

.btn {
@include bdl-Button--large;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,48 +1,40 @@
/**
* @flow
* @file Content Explorer Delete Confirmation Dialog
* @author Box
*/

// @flow
import * as React from 'react';
import { FormattedMessage, useIntl } from 'react-intl';
import Modal from 'react-modal';
import { injectIntl, FormattedMessage } from 'react-intl';
import type { IntlShape } from 'react-intl';
import PrimaryButton from '../../components/primary-button/PrimaryButton';
import Button from '../../components/button/Button';
import messages from '../common/messages';
import { CLASS_MODAL_CONTENT, CLASS_MODAL_OVERLAY, CLASS_MODAL, TYPE_FOLDER } from '../../constants';
import { Button } from '@box/blueprint-web';
import type { BoxItem } from '../../common/types/core';

import './DeleteConfirmationDialog.scss';
import { CLASS_MODAL_CONTENT, CLASS_MODAL_OVERLAY, CLASS_MODAL, TYPE_FOLDER } from '../../constants';

import messages from '../common/messages';

type Props = {
appElement: HTMLElement,
intl: IntlShape,
isLoading: boolean,
isOpen: boolean,
item: BoxItem,
onCancel: Function,
onDelete: Function,
onCancel: any,
onDelete: any,
parentElement: HTMLElement,
};

const DeleteConfirmationDialog = ({
isOpen,
onDelete,
onCancel,
item,
isLoading,
parentElement,
appElement,
intl,
appElement,
isLoading,
isOpen,
item,
onCancel,
onDelete,
parentElement,
}: Props) => {
const { formatMessage } = useIntl();
const message = item.type === TYPE_FOLDER ? messages.deleteDialogFolderText : messages.deleteDialogFileText;
return (
<Modal
appElement={appElement}
className={CLASS_MODAL_CONTENT}
contentLabel={intl.formatMessage(messages.deleteDialogLabel)}
contentLabel={formatMessage(messages.deleteDialogLabel)}
isOpen={isOpen}
onRequestClose={onCancel}
overlayClassName={CLASS_MODAL_OVERLAY}
Expand All @@ -51,15 +43,21 @@ const DeleteConfirmationDialog = ({
>
<FormattedMessage {...message} values={{ name: item.name }} />
<div className="be-modal-btns">
<PrimaryButton isLoading={isLoading} onClick={onDelete} type="button">
<FormattedMessage {...messages.delete} />
</PrimaryButton>
<Button autoFocus isDisabled={isLoading} onClick={onCancel} type="button">
<FormattedMessage {...messages.cancel} />
<Button
loading={isLoading}
loadingAriaLabel={formatMessage(messages.loading)}
onClick={onDelete}
size="large"
variant="primary"
>
{formatMessage(messages.delete)}
</Button>
<Button autoFocus disabled={isLoading} onClick={onCancel} size="large" variant="secondary">
{formatMessage(messages.cancel)}
</Button>
</div>
</Modal>
);
};

export default injectIntl(DeleteConfirmationDialog);
export default DeleteConfirmationDialog;
5 changes: 0 additions & 5 deletions src/elements/content-explorer/DeleteConfirmationDialog.scss

This file was deleted.

62 changes: 62 additions & 0 deletions src/elements/content-explorer/DeleteConfirmationDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import * as React from 'react';
import { FormattedMessage, useIntl } from 'react-intl';
import Modal from 'react-modal';
import { Button } from '@box/blueprint-web';
import type { BoxItem } from '../../common/types/core';

import { CLASS_MODAL_CONTENT, CLASS_MODAL_OVERLAY, CLASS_MODAL, TYPE_FOLDER } from '../../constants';

import messages from '../common/messages';

export interface DeleteConfirmationDialogProps {
appElement: HTMLElement;
isLoading: boolean;
isOpen: boolean;
item: BoxItem;
onCancel: () => void;
onDelete: () => void;
parentElement: HTMLElement;
}

const DeleteConfirmationDialog = ({
appElement,
isLoading,
isOpen,
item,
onCancel,
onDelete,
parentElement,
}: DeleteConfirmationDialogProps) => {
const { formatMessage } = useIntl();
const message = item.type === TYPE_FOLDER ? messages.deleteDialogFolderText : messages.deleteDialogFileText;
return (
<Modal
appElement={appElement}
className={CLASS_MODAL_CONTENT}
contentLabel={formatMessage(messages.deleteDialogLabel)}
isOpen={isOpen}
onRequestClose={onCancel}
overlayClassName={CLASS_MODAL_OVERLAY}
parentSelector={() => parentElement}
portalClassName={`${CLASS_MODAL} be-modal-delete`}
>
<FormattedMessage {...message} values={{ name: item.name }} />
<div className="be-modal-btns">
<Button
loading={isLoading}
loadingAriaLabel={formatMessage(messages.loading)}
onClick={onDelete}
size="large"
variant="primary"
>
{formatMessage(messages.delete)}
</Button>
<Button autoFocus disabled={isLoading} onClick={onCancel} size="large" variant="secondary">
{formatMessage(messages.cancel)}
</Button>
</div>
</Modal>
);
};

export default DeleteConfirmationDialog;
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import * as React from 'react';
import userEvent from '@testing-library/user-event';
import { render, screen } from '../../../test-utils/testing-library';
import DeleteConfirmationDialog, { DeleteConfirmationDialogProps } from '../DeleteConfirmationDialog';

// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
import { TYPE_FOLDER } from '../../../constants';

const mockItem = { type: 'pdf', name: 'Test File' };
const mockFolderItem = { type: TYPE_FOLDER, name: 'Test Folder' };

jest.mock('react-modal', () => {
return jest.fn(({ children }) => <div aria-label="Preview">{children}</div>);
});

describe('elements/content-explorer/DeleteConfirmationDialog', () => {
const defaultProps = {
appElement: document.body,
errorCode: '',
isLoading: false,
isOpen: false,
item: mockItem,
onCancel: jest.fn(),
onDelete: jest.fn(),
parentElement: document.createElement('div'),
};

const renderComponent = (props: Partial<DeleteConfirmationDialogProps>) =>
render(<DeleteConfirmationDialog {...defaultProps} {...props} />);

test('should render the dialog with file message', async () => {
renderComponent({ isOpen: true });

expect(await screen.findByText('Are you sure you want to delete Test File?')).toBeInTheDocument();
});

test('should render the dialog with folder message', async () => {
renderComponent({ isOpen: true, item: mockFolderItem });

expect(
await screen.findByText('Are you sure you want to delete Test Folder and all its contents?'),
).toBeInTheDocument();
});

test('should call onCancel when cancel button is clicked', async () => {
const mockOnCancel = jest.fn();
renderComponent({ isOpen: true, onCancel: mockOnCancel });

const cancelButton = screen.getByRole('button', { name: 'Cancel' });
await userEvent.click(cancelButton);
expect(mockOnCancel).toBeCalledTimes(1);
});

test('should call onDelete when delete button is clicked', async () => {
const mockOnDelete = jest.fn();
renderComponent({ isOpen: true, onDelete: mockOnDelete });

const deleteButton = screen.getByRole('button', { name: 'Delete' });
await userEvent.click(deleteButton);
expect(mockOnDelete).toBeCalledTimes(1);
});

test('should disable buttons when isLoading is true', () => {
renderComponent({ isOpen: true, isLoading: true });

const cancelButton = screen.getByRole('button', { name: 'Cancel' });
expect(cancelButton).toBeDisabled();
const loadingIndicator = screen.getByRole('status', { name: 'Loading' });
expect(loadingIndicator).toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
// @flow
import * as React from 'react';
import { useArgs } from '@storybook/preview-api';

import PrimaryButton from '../../../components/primary-button/PrimaryButton';
import { Button } from '@box/blueprint-web';
import { addRootElement } from '../../../utils/storybook';

import DeleteConfirmationDialog from '../DeleteConfirmationDialog';

// need to import this into the story because it's usually in ContentExplorer
import '../../common/modal.scss';

export const deleteDialog = {
// eslint-disable-next-line react/prop-types
// eslint-disable-next-line @typescript-eslint/no-explicit-any
render: (args: any) => {
// eslint-disable-next-line react-hooks/rules-of-hooks
const [, setArgs] = useArgs();
Expand All @@ -22,27 +18,26 @@ export const deleteDialog = {
setArgs({ isOpen: false });
};

const { appElement, rootElement } = addRootElement();
const { rootElement } = addRootElement();

return (
<div>
<DeleteConfirmationDialog
appElement={appElement}
item={{
id: '123456',
name:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Eget nulla facilisi etiam dignissim diam quis enim lobortis scelerisque. Aliquam faucibus purus in massa tempor nec. Ut consequat semper viverra nam libero justo laoreet sit amet. Purus gravida quis blandit turpis cursus in hac. Dui ut ornare lectus sit amet est. Nisl condimentum id venenatis a condimentum vitae sapien.',
name: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Eget nulla facilisi etiam dignissim diam quis enim lobortis scelerisque. Aliquam faucibus purus in massa tempor nec. Ut consequat semper viverra nam libero justo laoreet sit amet. Purus gravida quis blandit turpis cursus in hac. Dui ut ornare lectus sit amet est. Nisl condimentum id venenatis a condimentum vitae sapien.',
}}
onCancel={handleCloseModal}
parentElement={rootElement}
{...args}
/>

<PrimaryButton onClick={handleOpenModal}>Launch DeleteConfirmationDialog</PrimaryButton>
<Button onClick={handleOpenModal} variant="primary">
Launch DeleteConfirmationDialog
</Button>
</div>
);
},
};
} as const;

export default {
title: 'Elements/ContentExplorer',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export const openDeleteConfirmationDialog = {
await userEvent.click(moreOptionsButton);

const dropdown = await screen.findByRole('menu');
const deleteButton = within(dropdown).getByText('Delete');
const deleteButton = within(dropdown).findByText('Delete');
expect(deleteButton).toBeInTheDocument();
await userEvent.click(deleteButton);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,44 +4,24 @@ import { addRootElement, defaultVisualConfig } from '../../../../utils/storybook

import DeleteConfirmationDialog from '../../DeleteConfirmationDialog';

// need to import this into the story because it's usually in ContentExplorer
import '../../../common/modal.scss';

const item = {
id: '123456',
name:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Eget nulla facilisi etiam dignissim diam quis enim lobortis scelerisque. Aliquam faucibus purus in massa tempor nec. Ut consequat semper viverra nam libero justo laoreet sit amet. Purus gravida quis blandit turpis cursus in hac. Dui ut ornare lectus sit amet est. Nisl condimentum id venenatis a condimentum vitae sapien ',
name: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Eget nulla facilisi etiam dignissim diam quis enim lobortis scelerisque. Aliquam faucibus purus in massa tempor nec. Ut consequat semper viverra nam libero justo laoreet sit amet. Purus gravida quis blandit turpis cursus in hac. Dui ut ornare lectus sit amet est. Nisl condimentum id venenatis a condimentum vitae sapien ',
};

export const deleteDialogNotLoading = {
render: () => {
const { appElement, rootElement } = addRootElement();

return (
<DeleteConfirmationDialog
appElement={appElement}
isLoading={false}
isOpen
item={item}
parentElement={rootElement}
/>
);
const { rootElement } = addRootElement();

return <DeleteConfirmationDialog isLoading={false} isOpen item={item} parentElement={rootElement} />;
},
};

export const deleteDialogIsLoading = {
render: () => {
const { appElement, rootElement } = addRootElement();

return (
<DeleteConfirmationDialog
appElement={appElement}
isLoading
isOpen
item={item}
parentElement={rootElement}
/>
);
const { rootElement } = addRootElement();

return <DeleteConfirmationDialog isLoading isOpen item={item} parentElement={rootElement} />;
},
};

Expand Down

0 comments on commit 4598961

Please sign in to comment.