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);