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