diff --git a/client/modules/IDE/components/FileNode.jsx b/client/modules/IDE/components/FileNode.jsx index 3c84d31196..cb872eb982 100644 --- a/client/modules/IDE/components/FileNode.jsx +++ b/client/modules/IDE/components/FileNode.jsx @@ -3,6 +3,7 @@ import classNames from 'classnames'; import React, { useState, useRef } from 'react'; import { connect } from 'react-redux'; import { useTranslation } from 'react-i18next'; +import { useSelector } from 'react-redux'; import * as IDEActions from '../actions/ide'; import * as FileActions from '../actions/files'; @@ -88,6 +89,24 @@ const FileNode = ({ const [isDeleting, setIsDeleting] = useState(false); const [updatedName, setUpdatedName] = useState(name); + const files = useSelector((state) => state.files); + + const checkDuplicate = (newName) => { + const parentFolder = files.find((f) => f.id === parentId); + if (!parentFolder) return false; + + const siblingFiles = parentFolder.children + .map((childId) => files.find((f) => f.id === childId)) + .filter(Boolean) + .filter((file) => file.id !== id); + + const isDuplicate = siblingFiles.some( + (f) => f.name.trim().toLowerCase() === newName.trim().toLowerCase() + ); + + return isDuplicate; + }; + const { t } = useTranslation(); const fileNameInput = useRef(null); const fileOptionsRef = useRef(null); @@ -157,7 +176,7 @@ const FileNode = ({ }; const saveUpdatedFileName = () => { - if (updatedName !== name) { + if (!checkDuplicate(updatedName) && updatedName !== name) { updateFileName(id, updatedName); } }; @@ -198,8 +217,13 @@ const FileNode = ({ }; const handleFileNameBlur = () => { - validateFileName(); - hideEditFileName(); + if (!checkDuplicate(updatedName)) { + validateFileName(); + hideEditFileName(); + } else { + setUpdatedName(name); + hideEditFileName(); + } }; const toggleFileOptions = (event) => { @@ -271,6 +295,7 @@ const FileNode = ({ aria-label={updatedName} className="sidebar__file-item-name" onClick={handleFileClick} + onDoubleClick={handleClickRename} data-testid="file-name" > diff --git a/client/modules/IDE/components/FileNode.unit.test.jsx b/client/modules/IDE/components/FileNode.unit.test.jsx index 8676a817d8..0c9fd6fb5a 100644 --- a/client/modules/IDE/components/FileNode.unit.test.jsx +++ b/client/modules/IDE/components/FileNode.unit.test.jsx @@ -1,4 +1,7 @@ import React from 'react'; +import { Provider } from 'react-redux'; +import configureStore from 'redux-mock-store'; +import thunk from 'redux-thunk'; import { fireEvent, @@ -9,6 +12,8 @@ import { } from '../../../test-utils'; import { FileNode } from './FileNode'; +const mockStore = configureStore([thunk]); + describe('', () => { const changeName = (newFileName) => { const renameButton = screen.getByText(/Rename/i); @@ -32,6 +37,7 @@ describe('', () => { fileType, canEdit: true, children: [], + parentId: 'parent-folder-id', authenticated: false, setSelectedFile: jest.fn(), deleteFile: jest.fn(), @@ -45,7 +51,40 @@ describe('', () => { setProjectName: jest.fn() }; - render(); + const mockFiles = [ + { + id: '0', + name: props.name, + parentId: 'parent-folder-id' + }, + { + id: '1', + name: 'sketch.js', + parentId: '0', + isSelectedFile: true + }, + { + id: 'parent-folder-id', + name: 'parent', + parentId: null, + children: ['0', 'some-other-file-id'] + }, + { + id: 'some-other-file-id', + name: 'duplicate.js', + parentId: 'parent-folder-id' + } + ]; + + const store = mockStore({ + files: mockFiles + }); + + render( + + + + ); return props; };