Skip to content

Commit

Permalink
chore(content-explorer): Migrate Create New Folder (#3896)
Browse files Browse the repository at this point in the history
  • Loading branch information
greg-in-a-box authored Feb 7, 2025
1 parent ec98688 commit 4e0712b
Show file tree
Hide file tree
Showing 8 changed files with 242 additions and 11 deletions.
Original file line number Diff line number Diff line change
@@ -1,9 +1,3 @@
/**
* @flow
* @file Content Explorer Create Folder Dialog
* @author Box
*/

import * as React from 'react';
import Modal from 'react-modal';
import { injectIntl, FormattedMessage } from 'react-intl';
Expand All @@ -25,9 +19,9 @@ type Props = {
intl: IntlShape,
isLoading: boolean,
isOpen: boolean,
onCancel: Function,
onCreate: Function,
parentElement: HTMLElement,
onCancel: any,
onCreate: any,
parentElement: HTMLElement
};

/* eslint-disable jsx-a11y/label-has-for */
Expand Down
109 changes: 109 additions & 0 deletions src/elements/common/create-folder-dialog/CreateFolderDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import React, { useState } from 'react';
import Modal from 'react-modal';
import { useIntl } from 'react-intl';
import { Modal as BlueprintModal, TextInput } from '@box/blueprint-web';
import {
CLASS_MODAL_CONTENT,
CLASS_MODAL_OVERLAY,
CLASS_MODAL,
ERROR_CODE_ITEM_NAME_TOO_LONG,
ERROR_CODE_ITEM_NAME_IN_USE,
} from '../../../constants';

import messages from '../messages';

export interface CreateFolderDialogProps {
appElement: HTMLElement;
errorCode: string;
isLoading: boolean;
isOpen: boolean;
onCancel: () => void;
onCreate: (value: string) => void;
parentElement: HTMLElement;
}

const CreateFolderDialog = ({
appElement,
errorCode,
isOpen,
isLoading,
onCancel,
onCreate,
parentElement,
}: CreateFolderDialogProps) => {
const { formatMessage } = useIntl();
const [value, setValue] = useState('');
let error;

const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setValue(e.target.value);
};

const handleCreate = () => {
if (value) {
onCreate(value);
}
};

const handleKeyDown = ({ key }) => {
switch (key) {
case 'Enter':
handleCreate();
break;
default:
break;
}
};

switch (errorCode) {
case ERROR_CODE_ITEM_NAME_IN_USE:
error = formatMessage(messages.createDialogErrorInUse);
break;
case ERROR_CODE_ITEM_NAME_TOO_LONG:
error = formatMessage(messages.createDialogErrorTooLong);
break;
default:
error = errorCode ? formatMessage(messages.createDialogErrorInvalid) : null;
break;
}

return (
<Modal
appElement={appElement}
className={CLASS_MODAL_CONTENT}
contentLabel={formatMessage(messages.createDialogLabel)}
isOpen={isOpen}
onRequestClose={onCancel}
overlayClassName={CLASS_MODAL_OVERLAY}
parentSelector={() => parentElement}
portalClassName={`${CLASS_MODAL} be-modal-create-folder`}
>
<BlueprintModal.Body>
<TextInput
autoFocus
error={error}
label={formatMessage(messages.createDialogText)}
onChange={handleChange}
onKeyDown={handleKeyDown}
required
value={value}
/>
</BlueprintModal.Body>
<BlueprintModal.Footer>
<BlueprintModal.Footer.SecondaryButton disabled={isLoading} onClick={onCancel} size="large">
{formatMessage(messages.cancel)}
</BlueprintModal.Footer.SecondaryButton>
<BlueprintModal.Footer.PrimaryButton
loading={isLoading}
loadingAriaLabel={formatMessage(messages.loading)}
onClick={handleCreate}
size="large"
>
{formatMessage(messages.create)}
</BlueprintModal.Footer.PrimaryButton>
</BlueprintModal.Footer>
</Modal>
);
};

export default CreateFolderDialog;
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import * as React from 'react';
import userEvent from '@testing-library/user-event';

import { render, screen } from '../../../../test-utils/testing-library';
import CreateFolderDialog, { CreateFolderDialogProps } from '../CreateFolderDialog';
import { ERROR_CODE_ITEM_NAME_TOO_LONG, ERROR_CODE_ITEM_NAME_IN_USE } from '../../../../constants';

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

const defaultProps = {
appElement: document.createElement('div'),
errorCode: '',
isLoading: false,
isOpen: true,
onCancel: jest.fn(),
onCreate: jest.fn(),
parentElement: document.createElement('div'),
};

describe('elements/common/create-folder-dialog/CreateFolderDialog', () => {
const renderComponent = (props: Partial<CreateFolderDialogProps> = {}) =>
render(<CreateFolderDialog {...defaultProps} {...props} />);

test('renders the dialog with the correct initial state', () => {
renderComponent();

expect(screen.getByText('Please enter a name.')).toBeInTheDocument();
expect(screen.getByRole('textbox')).toBeEmptyDOMElement();
expect(screen.getByRole('button', { name: 'Create' })).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Cancel' })).toBeInTheDocument();
});

test('calls onCancel when cancel button is clicked', async () => {
const onCancel = jest.fn();

renderComponent({ onCancel });
await userEvent.click(screen.getByRole('button', { name: 'Cancel' }));
expect(onCancel).toHaveBeenCalled();
});

test('calls onCreate with the correct values when create button is clicked', async () => {
const onCreate = jest.fn();

renderComponent({ onCreate });
const input = screen.getByRole('textbox');
await userEvent.clear(input);
await userEvent.type(input, 'newname');
await userEvent.click(screen.getByRole('button', { name: 'Create' }));
expect(onCreate).toHaveBeenCalledWith('newname');
});

test('displays an error message when errorCode is neither ERROR_CODE_ITEM_NAME_IN_USE nor ERROR_CODE_ITEM_NAME_TOO_LONG', () => {
renderComponent({ errorCode: 'bad' });
expect(screen.getByText('This is an invalid folder name.')).toBeInTheDocument();
});

test('displays an error message when errorCode is ERROR_CODE_ITEM_NAME_IN_USE', () => {
renderComponent({ errorCode: ERROR_CODE_ITEM_NAME_IN_USE });
expect(screen.getByText('A folder with the same name already exists.')).toBeInTheDocument();
});

test('displays an error message when errorCode is ERROR_CODE_ITEM_NAME_TOO_LONG', () => {
renderComponent({ errorCode: ERROR_CODE_ITEM_NAME_TOO_LONG });
expect(screen.getByText('This folder name is too long.')).toBeInTheDocument();
});

test('does not call onCreate if the name has not changed', async () => {
const onCancel = jest.fn();
const onCreate = jest.fn();

renderComponent({ onCancel, onCreate });
await userEvent.click(screen.getByText('Create'));
expect(onCreate).not.toHaveBeenCalled();
});

test('calls handleOnCreate on Enter key press', async () => {
const onCreate = jest.fn();

renderComponent({ onCreate });
const input = screen.getByRole('textbox');
await userEvent.clear(input);
await userEvent.type(input, 'newname');
await userEvent.type(input, '{enter}');
expect(onCreate).toHaveBeenCalledWith('newname');
});
});
1 change: 1 addition & 0 deletions src/elements/common/create-folder-dialog/index.js.flow
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export {default} from './CreateFolderDialog';
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
// @flow
export { default } from './CreateFolderDialog';
1 change: 1 addition & 0 deletions src/elements/common/modal.scss
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
background-color: $darker-black;
}

.be-modal-create-folder .be-modal-dialog-content,
.be-modal-rename .be-modal-dialog-content,
.be-modal-share .be-modal-dialog-content,
.be-modal-delete .be-modal-dialog-content {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ const mockRootFolder = {
parent: null,
permissions: {
can_download: true,
can_upload: false,
can_upload: true,
can_rename: false,
can_delete: false,
can_share: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,45 @@ export const openExistingFolder = {
},
};

export const openCreateFolderDialog = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);

const addButton = await canvas.findByRole('button', { name: 'Add' });
await userEvent.click(addButton);

const dropdown = await screen.findByRole('menu');
const newFolderButton = within(dropdown).getByText('New Folder');
expect(newFolderButton).toBeInTheDocument();
await userEvent.click(newFolderButton);

expect(await screen.findByText('Please enter a name.')).toBeInTheDocument();
},
};

export const closeCreateFolderDialog = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);

const addButton = await canvas.findByRole('button', { name: 'Add' });
await userEvent.click(addButton);

const dropdown = await screen.findByRole('menu');
const newFolderButton = within(dropdown).getByText('New Folder');
expect(newFolderButton).toBeInTheDocument();
await userEvent.click(newFolderButton);

expect(await screen.findByText('Please enter a name.')).toBeInTheDocument();

const cancelButton = screen.getByText('Cancel');
await userEvent.click(cancelButton);

await waitFor(() => {
expect(screen.queryByText('Please enter a name.')).not.toBeInTheDocument();
});
},
};

export const openDeleteConfirmationDialog = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
Expand Down

0 comments on commit 4e0712b

Please sign in to comment.