From 780e2240e63abc8a90da5e1e5b7b6fcbcbd6ebdd Mon Sep 17 00:00:00 2001 From: Greg Wong Date: Thu, 3 Oct 2024 21:23:15 -0400 Subject: [PATCH] chore(content-explorer): Migrate deleteConfirmationDialog --- i18n/en-US.properties | 6 +- src/elements/common/modal.scss | 6 ++ ...og.js => DeleteConfirmationDialog.js.flow} | 60 ++++++++-------- .../DeleteConfirmationDialog.scss | 5 -- .../DeleteConfirmationDialog.tsx | 62 ++++++++++++++++ .../__tests__/DeleteConfirmation.test.tsx | 72 +++++++++++++++++++ ...s => DeleteConfirmationDialog.stories.tsx} | 21 +++--- .../tests/ContentExplorer-visual.stories.js | 2 +- ...DeleteConfirmationDialog-visual.stories.js | 34 ++------- 9 files changed, 188 insertions(+), 80 deletions(-) rename src/elements/content-explorer/{DeleteConfirmationDialog.js => DeleteConfirmationDialog.js.flow} (52%) delete mode 100644 src/elements/content-explorer/DeleteConfirmationDialog.scss create mode 100644 src/elements/content-explorer/DeleteConfirmationDialog.tsx create mode 100644 src/elements/content-explorer/__tests__/DeleteConfirmation.test.tsx rename src/elements/content-explorer/stories/{DeleteConfirmationDialog.stories.js => DeleteConfirmationDialog.stories.tsx} (51%) diff --git a/i18n/en-US.properties b/i18n/en-US.properties index 597ad95e7b..46a572c6e2 100644 --- a/i18n/en-US.properties +++ b/i18n/en-US.properties @@ -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 @@ -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 diff --git a/src/elements/common/modal.scss b/src/elements/common/modal.scss index 0e5b89e967..b6616c7d68 100644 --- a/src/elements/common/modal.scss +++ b/src/elements/common/modal.scss @@ -1,3 +1,5 @@ +@use '@box/blueprint-web-assets/tokens/tokens.scss'; + @import './variables'; .be-modal { @@ -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; diff --git a/src/elements/content-explorer/DeleteConfirmationDialog.js b/src/elements/content-explorer/DeleteConfirmationDialog.js.flow similarity index 52% rename from src/elements/content-explorer/DeleteConfirmationDialog.js rename to src/elements/content-explorer/DeleteConfirmationDialog.js.flow index 2586d09c10..8b57732382 100644 --- a/src/elements/content-explorer/DeleteConfirmationDialog.js +++ b/src/elements/content-explorer/DeleteConfirmationDialog.js.flow @@ -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 (
- - - - +
); }; -export default injectIntl(DeleteConfirmationDialog); +export default DeleteConfirmationDialog; diff --git a/src/elements/content-explorer/DeleteConfirmationDialog.scss b/src/elements/content-explorer/DeleteConfirmationDialog.scss deleted file mode 100644 index 68317c27d5..0000000000 --- a/src/elements/content-explorer/DeleteConfirmationDialog.scss +++ /dev/null @@ -1,5 +0,0 @@ -.be-modal.be-modal-delete { - .be-modal-dialog-content { - overflow-wrap: break-word; - } -} diff --git a/src/elements/content-explorer/DeleteConfirmationDialog.tsx b/src/elements/content-explorer/DeleteConfirmationDialog.tsx new file mode 100644 index 0000000000..c76ece18f0 --- /dev/null +++ b/src/elements/content-explorer/DeleteConfirmationDialog.tsx @@ -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 ( + parentElement} + portalClassName={`${CLASS_MODAL} be-modal-delete`} + > + +
+ + +
+
+ ); +}; + +export default DeleteConfirmationDialog; diff --git a/src/elements/content-explorer/__tests__/DeleteConfirmation.test.tsx b/src/elements/content-explorer/__tests__/DeleteConfirmation.test.tsx new file mode 100644 index 0000000000..57e6a8c375 --- /dev/null +++ b/src/elements/content-explorer/__tests__/DeleteConfirmation.test.tsx @@ -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 }) =>
{children}
); +}); + +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) => + render(); + + 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(); + }); +}); diff --git a/src/elements/content-explorer/stories/DeleteConfirmationDialog.stories.js b/src/elements/content-explorer/stories/DeleteConfirmationDialog.stories.tsx similarity index 51% rename from src/elements/content-explorer/stories/DeleteConfirmationDialog.stories.js rename to src/elements/content-explorer/stories/DeleteConfirmationDialog.stories.tsx index 5d5bb24c68..eb0c2086de 100644 --- a/src/elements/content-explorer/stories/DeleteConfirmationDialog.stories.js +++ b/src/elements/content-explorer/stories/DeleteConfirmationDialog.stories.tsx @@ -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(); @@ -22,27 +18,26 @@ export const deleteDialog = { setArgs({ isOpen: false }); }; - const { appElement, rootElement } = addRootElement(); + const { rootElement } = addRootElement(); return (
- - Launch DeleteConfirmationDialog +
); }, -}; +} as const; export default { title: 'Elements/ContentExplorer', diff --git a/src/elements/content-explorer/stories/tests/ContentExplorer-visual.stories.js b/src/elements/content-explorer/stories/tests/ContentExplorer-visual.stories.js index 178b5d94cb..5a8d71a2d3 100644 --- a/src/elements/content-explorer/stories/tests/ContentExplorer-visual.stories.js +++ b/src/elements/content-explorer/stories/tests/ContentExplorer-visual.stories.js @@ -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); diff --git a/src/elements/content-explorer/stories/tests/DeleteConfirmationDialog-visual.stories.js b/src/elements/content-explorer/stories/tests/DeleteConfirmationDialog-visual.stories.js index 62361634f4..aa06d800c9 100644 --- a/src/elements/content-explorer/stories/tests/DeleteConfirmationDialog-visual.stories.js +++ b/src/elements/content-explorer/stories/tests/DeleteConfirmationDialog-visual.stories.js @@ -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 ( - - ); + const { rootElement } = addRootElement(); + + return ; }, }; export const deleteDialogIsLoading = { render: () => { - const { appElement, rootElement } = addRootElement(); - - return ( - - ); + const { rootElement } = addRootElement(); + + return ; }, };