diff --git a/client/modules/IDE/components/FileNode.jsx b/client/modules/IDE/components/FileNode.jsx
index 2a381624ef..2a81545840 100644
--- a/client/modules/IDE/components/FileNode.jsx
+++ b/client/modules/IDE/components/FileNode.jsx
@@ -7,6 +7,8 @@ import { useSelector } from 'react-redux';
import * as IDEActions from '../actions/ide';
import * as FileActions from '../actions/files';
+import { showToast as showToastAction } from '../actions/toast';
+import { CREATE_FILE_REGEX } from '../../../../server/utils/fileUtils';
import DownArrowIcon from '../../../images/down-filled-triangle.svg';
import FolderRightIcon from '../../../images/triangle-arrow-right.svg';
import FolderDownIcon from '../../../images/triangle-arrow-down.svg';
@@ -82,7 +84,8 @@ const FileNode = ({
canEdit,
openUploadFileModal,
authenticated,
- onClickFile
+ onClickFile,
+ showToast
}) => {
const [isOptionsOpen, setIsOptionsOpen] = useState(false);
const [isEditingName, setIsEditingName] = useState(false);
@@ -186,23 +189,42 @@ const FileNode = ({
const oldFileExtension = currentName.match(/\.[0-9a-z]+$/i);
const newFileExtension = updatedName.match(/\.[0-9a-z]+$/i);
const hasPeriod = updatedName.match(/\.+/);
- const hasNoExtension = oldFileExtension && !newFileExtension;
- const hasExtensionIfFolder = fileType === 'folder' && hasPeriod;
const notSameExtension =
oldFileExtension &&
newFileExtension &&
oldFileExtension[0].toLowerCase() !== newFileExtension[0].toLowerCase();
const hasEmptyFilename = updatedName.trim() === '';
- const hasOnlyExtension =
- newFileExtension && updatedName.trim() === newFileExtension[0];
- if (
- hasEmptyFilename ||
- hasNoExtension ||
- hasOnlyExtension ||
- hasExtensionIfFolder
- ) {
+
+ // Check empty filename
+ if (hasEmptyFilename) {
+ if (fileType === 'folder') {
+ showToast(t('NewFolderModal.EnterName'));
+ } else {
+ showToast(t('NewFileModal.EnterName'));
+ }
setUpdatedName(currentName);
- } else if (notSameExtension) {
+ return;
+ }
+
+ // Check folder specific rules
+ if (fileType === 'folder') {
+ if (hasPeriod) {
+ showToast(t('NewFolderModal.InvalidExtension'));
+ setUpdatedName(currentName);
+ return;
+ }
+ }
+
+ // Check file specific rules
+ if (fileType === 'file') {
+ if (!updatedName.match(CREATE_FILE_REGEX)) {
+ showToast(t('NewFileModal.InvalidType'));
+ setUpdatedName(currentName);
+ return;
+ }
+ }
+
+ if (notSameExtension) {
const userResponse = window.confirm(
'Are you sure you want to change the file extension?'
);
@@ -412,7 +434,8 @@ FileNode.propTypes = {
canEdit: PropTypes.bool.isRequired,
openUploadFileModal: PropTypes.func.isRequired,
authenticated: PropTypes.bool.isRequired,
- onClickFile: PropTypes.func
+ onClickFile: PropTypes.func,
+ showToast: PropTypes.func.isRequired
};
FileNode.defaultProps = {
@@ -433,7 +456,11 @@ function mapStateToProps(state, ownProps) {
});
}
-const mapDispatchToProps = { ...FileActions, ...IDEActions };
+const mapDispatchToProps = {
+ ...FileActions,
+ ...IDEActions,
+ showToast: showToastAction
+};
const ConnectedFileNode = connect(
mapStateToProps,
diff --git a/client/modules/IDE/components/FileNode.unit.test.jsx b/client/modules/IDE/components/FileNode.unit.test.jsx
index 0c9fd6fb5a..9fec9bc43d 100644
--- a/client/modules/IDE/components/FileNode.unit.test.jsx
+++ b/client/modules/IDE/components/FileNode.unit.test.jsx
@@ -33,7 +33,7 @@ describe('', () => {
const props = {
...extraProps,
id: '0',
- name: fileType === 'folder' ? 'afolder' : 'test.jsx',
+ name: fileType === 'folder' ? 'afolder' : 'test.js',
fileType,
canEdit: true,
children: [],
@@ -48,7 +48,8 @@ describe('', () => {
showFolderChildren: jest.fn(),
hideFolderChildren: jest.fn(),
openUploadFileModal: jest.fn(),
- setProjectName: jest.fn()
+ setProjectName: jest.fn(),
+ showToast: jest.fn()
};
const mockFiles = [
@@ -96,11 +97,12 @@ describe('', () => {
changeName('');
await waitFor(() => expect(props.updateFileName).not.toHaveBeenCalled());
+ expect(props.showToast).toHaveBeenCalled();
await expectFileNameToBe(props.name);
});
it('can change to a valid filename', async () => {
- const newName = 'newname.jsx';
+ const newName = 'newname.js';
const props = renderFileNode('file');
changeName(newName);
@@ -118,6 +120,7 @@ describe('', () => {
changeName(newName);
await waitFor(() => expect(props.updateFileName).not.toHaveBeenCalled());
+ expect(props.showToast).toHaveBeenCalled();
await expectFileNameToBe(props.name);
});
@@ -125,7 +128,7 @@ describe('', () => {
const mockConfirm = jest.fn(() => true);
window.confirm = mockConfirm;
- const newName = 'newname.gif';
+ const newName = 'newname.json';
const props = renderFileNode('file');
changeName(newName);
@@ -137,13 +140,25 @@ describe('', () => {
await expectFileNameToBe(props.name);
});
+ it('cannot change to an invalid extension', async () => {
+ const newName = 'newname.obj';
+ const props = renderFileNode('file');
+
+ changeName(newName);
+
+ await waitFor(() => expect(props.updateFileName).not.toHaveBeenCalled());
+ expect(props.showToast).toHaveBeenCalled();
+ await expectFileNameToBe(props.name);
+ });
+
it('cannot be just an extension', async () => {
- const newName = '.jsx';
+ const newName = '.js';
const props = renderFileNode('file');
changeName(newName);
await waitFor(() => expect(props.updateFileName).not.toHaveBeenCalled());
+ expect(props.showToast).toHaveBeenCalled();
await expectFileNameToBe(props.name);
});
});
@@ -155,6 +170,7 @@ describe('', () => {
changeName('');
await waitFor(() => expect(props.updateFileName).not.toHaveBeenCalled());
+ expect(props.showToast).toHaveBeenCalled();
await expectFileNameToBe(props.name);
});
@@ -171,12 +187,13 @@ describe('', () => {
});
it('cannot have a file extension', async () => {
- const newName = 'foldername.jsx';
+ const newName = 'foldername.js';
const props = renderFileNode('folder');
changeName(newName);
await waitFor(() => expect(props.updateFileName).not.toHaveBeenCalled());
+ expect(props.showToast).toHaveBeenCalled();
await expectFileNameToBe(props.name);
});
});