-
Notifications
You must be signed in to change notification settings - Fork 313
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'master' into fix-archive-date-preview
- Loading branch information
Showing
7 changed files
with
345 additions
and
56 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
import * as React 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 type { BoxItem } from '../../common/types/core'; | ||
|
||
import messages from '../common/messages'; | ||
|
||
export interface RenameDialogProps { | ||
appElement: HTMLElement; | ||
errorCode: string; | ||
isLoading: boolean; | ||
isOpen: boolean; | ||
item: BoxItem; | ||
onCancel: () => void; | ||
onRename: (value: string, extension: string) => void; | ||
parentElement: HTMLElement; | ||
} | ||
|
||
const RenameDialog = ({ | ||
appElement, | ||
errorCode, | ||
isOpen, | ||
isLoading, | ||
item, | ||
onCancel, | ||
onRename, | ||
parentElement, | ||
}: RenameDialogProps) => { | ||
const { formatMessage } = useIntl(); | ||
let textInput = null; | ||
let error; | ||
|
||
const { name = '', extension } = item; | ||
const ext = extension ? `.${extension}` : ''; | ||
const nameWithoutExt = extension ? name.replace(ext, '') : name; | ||
|
||
/** | ||
* Appends the extension and calls rename function | ||
*/ | ||
const handleRename = () => { | ||
if (textInput && textInput.value) { | ||
if (textInput.value === nameWithoutExt) { | ||
onCancel(); | ||
} else { | ||
onRename(textInput.value, ext); | ||
} | ||
} | ||
}; | ||
|
||
/** | ||
* Grabs reference to the input element | ||
*/ | ||
const ref = input => { | ||
textInput = input; | ||
if (textInput instanceof HTMLInputElement) { | ||
textInput.focus(); | ||
textInput.select(); | ||
} | ||
}; | ||
|
||
/** | ||
* Handles enter key down | ||
*/ | ||
const onKeyDown = ({ key }) => { | ||
switch (key) { | ||
case 'Enter': | ||
handleRename(); | ||
break; | ||
default: | ||
break; | ||
} | ||
}; | ||
|
||
switch (errorCode) { | ||
case ERROR_CODE_ITEM_NAME_IN_USE: | ||
error = messages.renameDialogErrorInUse; | ||
break; | ||
case ERROR_CODE_ITEM_NAME_TOO_LONG: | ||
error = messages.renameDialogErrorTooLong; | ||
break; | ||
default: | ||
error = errorCode ? messages.renameDialogErrorInvalid : null; | ||
break; | ||
} | ||
|
||
return ( | ||
<Modal | ||
appElement={appElement} | ||
className={CLASS_MODAL_CONTENT} | ||
contentLabel={formatMessage(messages.renameDialogLabel)} | ||
isOpen={isOpen} | ||
onRequestClose={onCancel} | ||
overlayClassName={CLASS_MODAL_OVERLAY} | ||
parentSelector={() => parentElement} | ||
portalClassName={`${CLASS_MODAL} be-modal-rename`} | ||
> | ||
<BlueprintModal.Body> | ||
<TextInput | ||
defaultValue={nameWithoutExt} | ||
error={error && formatMessage(error, { name: nameWithoutExt })} | ||
label={formatMessage(messages.renameDialogText, { name: nameWithoutExt })} | ||
onKeyDown={onKeyDown} | ||
ref={ref} | ||
required | ||
/> | ||
</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={handleRename} | ||
size="large" | ||
> | ||
{formatMessage(messages.rename)} | ||
</BlueprintModal.Footer.PrimaryButton> | ||
</BlueprintModal.Footer> | ||
</Modal> | ||
); | ||
}; | ||
|
||
export default RenameDialog; |
90 changes: 90 additions & 0 deletions
90
src/elements/content-explorer/__tests__/RenameDialog.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
import * as React from 'react'; | ||
import userEvent from '@testing-library/user-event'; | ||
|
||
import { render, screen } from '../../../test-utils/testing-library'; | ||
import RenameDialog from '../RenameDialog'; | ||
import { ERROR_CODE_ITEM_NAME_TOO_LONG, ERROR_CODE_ITEM_NAME_IN_USE } from '../../../constants'; | ||
|
||
jest.mock('react-modal', () => { | ||
return jest.fn(({ children }) => <div aria-label="Rename">{children}</div>); | ||
}); | ||
|
||
const defaultProps = { | ||
appElement: document.createElement('div'), | ||
errorCode: '', | ||
isLoading: false, | ||
isOpen: true, | ||
item: { name: 'test.txt', extension: 'txt' }, | ||
onCancel: jest.fn(), | ||
onRename: jest.fn(), | ||
parentElement: document.createElement('div'), | ||
}; | ||
|
||
describe('elements/content-explorer/RenameDialog', () => { | ||
const renderComponent = (props = {}) => render(<RenameDialog {...defaultProps} {...props} />); | ||
|
||
test('renders the dialog with the correct initial state', () => { | ||
renderComponent(); | ||
|
||
expect(screen.getByText('Please enter a new name for test:')).toBeInTheDocument(); | ||
expect(screen.getByRole('textbox')).toHaveValue('test'); | ||
expect(screen.getByDisplayValue('test')).toBeInTheDocument(); | ||
expect(screen.getByRole('button', { name: 'Rename' })).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 onRename with the correct values when rename button is clicked', async () => { | ||
const onRename = jest.fn(); | ||
|
||
renderComponent({ onRename }); | ||
const input = screen.getByRole('textbox'); | ||
await userEvent.clear(input); | ||
await userEvent.type(input, 'newname'); | ||
await userEvent.click(screen.getByRole('button', { name: 'Rename' })); | ||
expect(onRename).toHaveBeenCalledWith('newname', '.txt'); | ||
}); | ||
|
||
test('displays an error message is neither ERROR_CODE_ITEM_NAME_IN_USE or ERROR_CODE_ITEM_NAME_TOO_LONG', () => { | ||
renderComponent({ errorCode: 'something else' }); | ||
expect(screen.getByText('This name is invalid.')).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('An item 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 name is too long.')).toBeInTheDocument(); | ||
}); | ||
|
||
test('does not call onRename if the name has not changed', async () => { | ||
const onCancel = jest.fn(); | ||
const onRename = jest.fn(); | ||
|
||
renderComponent({ onCancel, onRename }); | ||
await userEvent.click(screen.getByText('Rename')); | ||
expect(onRename).not.toHaveBeenCalled(); | ||
expect(onCancel).toHaveBeenCalled(); | ||
}); | ||
|
||
test('calls handleRename on Enter key press', async () => { | ||
const onRename = jest.fn(); | ||
|
||
renderComponent({ onRename }); | ||
const input = screen.getByRole('textbox'); | ||
await userEvent.clear(input); | ||
await userEvent.type(input, 'newname'); | ||
await userEvent.type(input, '{enter}'); | ||
expect(onRename).toHaveBeenCalledWith('newname', '.txt'); | ||
}); | ||
}); |
Oops, something went wrong.