diff --git a/client/modules/IDE/components/FileNode.jsx b/client/modules/IDE/components/FileNode.jsx
index 2a381624ef..2663eb70ee 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);
@@ -195,6 +198,13 @@ const FileNode = ({
const hasEmptyFilename = updatedName.trim() === '';
const hasOnlyExtension =
newFileExtension && updatedName.trim() === newFileExtension[0];
+
+ if (fileType === 'file' && !updatedName.match(CREATE_FILE_REGEX)) {
+ showToast(t('NewFileModal.InvalidType'));
+ setUpdatedName(currentName);
+ return;
+ }
+
if (
hasEmptyFilename ||
hasNoExtension ||
@@ -412,7 +422,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 +444,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..1060893433 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 = [
@@ -100,7 +101,7 @@ describe('', () => {
});
it('can change to a valid filename', async () => {
- const newName = 'newname.jsx';
+ const newName = 'newname.js';
const props = renderFileNode('file');
changeName(newName);
@@ -125,7 +126,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,8 +138,19 @@ 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);
@@ -171,7 +183,7 @@ describe('', () => {
});
it('cannot have a file extension', async () => {
- const newName = 'foldername.jsx';
+ const newName = 'foldername.js';
const props = renderFileNode('folder');
changeName(newName);