Skip to content

Commit 586865b

Browse files
committed
update: rename runnging project
1 parent 6cba215 commit 586865b

File tree

3 files changed

+488
-29
lines changed

3 files changed

+488
-29
lines changed

app/project-manager-shim/src/projectService/__tests__/ProjectService.test.ts

Lines changed: 339 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -671,4 +671,343 @@ describe('ProjectService', () => {
671671
expect(duplicateMetadata.namespace).toBe(originalMetadata.namespace)
672672
})
673673
})
674+
675+
describe('renameProject', () => {
676+
test('should successfully rename a project when language server is not running', async () => {
677+
// Create a project
678+
const originalName = 'OriginalProjectName'
679+
const createResult = await projectService.createProject(originalName, projectsDirectory)
680+
681+
// Add some content to verify it's preserved after rename
682+
const testFilePath = path.join(createResult.projectPath, 'src', 'Main.enso')
683+
await fs.mkdir(path.dirname(testFilePath), { recursive: true })
684+
await fs.writeFile(testFilePath, 'main = "Hello from project"')
685+
686+
const newName = 'RenamedProjectName'
687+
688+
// Rename the project
689+
await projectService.renameProject(createResult.projectId, newName, projectsDirectory)
690+
691+
// Verify the directory was renamed
692+
const oldDirectoryPath = createResult.projectPath
693+
const newDirectoryPath = path.join(path.dirname(oldDirectoryPath), 'RenamedProjectName')
694+
695+
// Verify metadata is preserved
696+
const metadataPath = path.join(newDirectoryPath, '.enso', 'project.json')
697+
const metadataContent = await fs.readFile(metadataPath, 'utf-8')
698+
const metadata = JSON.parse(metadataContent)
699+
expect(metadata.id).toBe(createResult.projectId)
700+
701+
// Verify package name is updated
702+
const packagePath = path.join(newDirectoryPath, 'package.yaml')
703+
const packageContent = await fs.readFile(packagePath, 'utf-8')
704+
expect(packageContent).contain(newName)
705+
706+
const oldDirExists = await fs
707+
.access(oldDirectoryPath)
708+
.then(() => true)
709+
.catch(() => false)
710+
const newDirExists = await fs
711+
.access(newDirectoryPath)
712+
.then(() => true)
713+
.catch(() => false)
714+
715+
expect(oldDirExists).toBe(false)
716+
expect(newDirExists).toBe(true)
717+
718+
// Verify content was preserved
719+
const renamedTestFilePath = path.join(newDirectoryPath, 'src', 'Main.enso')
720+
const content = await fs.readFile(renamedTestFilePath, 'utf-8')
721+
expect(content).toBe('main = "Hello from project"')
722+
})
723+
724+
test(
725+
'should defer directory rename when language server is running',
726+
async () => {
727+
// Create a project
728+
const originalName = 'RunningProjectToRename'
729+
const createResult = await projectService.createProject(originalName, projectsDirectory)
730+
731+
// Open the project to start the language server
732+
await projectService.openProject(createResult.projectId, projectsDirectory)
733+
734+
// Rename the project while it's running
735+
const newName = 'RenamedRunningProject'
736+
try {
737+
await projectService.renameProject(createResult.projectId, newName, projectsDirectory)
738+
} catch {/* Expected error for uninitialized test project */}
739+
740+
const oldDirectoryPath = createResult.projectPath
741+
// Verify metadata is preserved
742+
const metadataPath = path.join(oldDirectoryPath, '.enso', 'project.json')
743+
const metadataContent = await fs.readFile(metadataPath, 'utf-8')
744+
const metadata = JSON.parse(metadataContent)
745+
expect(metadata.id).toBe(createResult.projectId)
746+
747+
// Verify package name is updated
748+
const packagePath = path.join(oldDirectoryPath, 'package.yaml')
749+
const packageContent = await fs.readFile(packagePath, 'utf-8')
750+
expect(packageContent).contain(newName)
751+
752+
// Verify directory has NOT been renamed yet (deferred)
753+
const oldDirExists = await fs
754+
.access(createResult.projectPath)
755+
.then(() => true)
756+
.catch(() => false)
757+
expect(oldDirExists).toBe(true)
758+
759+
// Close the project to trigger the deferred rename
760+
await projectService.closeProject(createResult.projectId)
761+
762+
// Now verify the directory was renamed after closing
763+
const newDirectoryPath = path.join(path.dirname(createResult.projectPath), newName)
764+
const oldDirExistsAfter = await fs
765+
.access(createResult.projectPath)
766+
.then(() => true)
767+
.catch(() => false)
768+
const newDirExistsAfter = await fs
769+
.access(newDirectoryPath)
770+
.then(() => true)
771+
.catch(() => false)
772+
773+
expect(oldDirExistsAfter).toBe(false)
774+
expect(newDirExistsAfter).toBe(true)
775+
},
776+
LANGUAGE_SERVER_TEST_TIMEOUT,
777+
)
778+
779+
test('should fail when renaming to an existing project name', async () => {
780+
// Create two projects
781+
const _project1 = await projectService.createProject('Project1', projectsDirectory)
782+
const project2 = await projectService.createProject('Project2', projectsDirectory)
783+
784+
// Try to rename project2 to project1's name
785+
await expect(
786+
projectService.renameProject(project2.projectId, 'Project1', projectsDirectory),
787+
).rejects.toThrow("Project with name 'Project1' already exists.")
788+
})
789+
790+
test('should fail when renaming non-existent project', async () => {
791+
const nonExistentId = crypto.randomUUID() as UUID
792+
793+
await expect(
794+
projectService.renameProject(nonExistentId, 'NewName', projectsDirectory),
795+
).rejects.toThrow(`Project not found: ${nonExistentId}`)
796+
})
797+
798+
test('should reject empty new name', async () => {
799+
// Create a project
800+
const createResult = await projectService.createProject('ProjectToRename', projectsDirectory)
801+
802+
// Try to rename with empty name
803+
await expect(
804+
projectService.renameProject(createResult.projectId, '', projectsDirectory),
805+
).rejects.toThrow('Project name cannot be empty')
806+
807+
await expect(
808+
projectService.renameProject(createResult.projectId, ' ', projectsDirectory),
809+
).rejects.toThrow('Project name cannot be empty')
810+
})
811+
812+
test('should handle special characters in new name', async () => {
813+
// Create a project
814+
const originalName = 'SimpleProject'
815+
const createResult = await projectService.createProject(originalName, projectsDirectory)
816+
817+
const newName = 'Project #1 & Special'
818+
819+
// Rename the project
820+
await projectService.renameProject(createResult.projectId, newName, projectsDirectory)
821+
822+
const newDirectoryPath = path.join(path.dirname(createResult.projectPath), 'Project1Special')
823+
// Verify metadata is preserved
824+
const metadataPath = path.join(newDirectoryPath, '.enso', 'project.json')
825+
const metadataContent = await fs.readFile(metadataPath, 'utf-8')
826+
const metadata = JSON.parse(metadataContent)
827+
expect(metadata.id).toBe(createResult.projectId)
828+
829+
// Verify package name is updated
830+
const packagePath = path.join(newDirectoryPath, 'package.yaml')
831+
const packageContent = await fs.readFile(packagePath, 'utf-8')
832+
expect(packageContent).contain(newName)
833+
834+
// Verify the directory was renamed with normalized name
835+
const newDirExists = await fs
836+
.access(newDirectoryPath)
837+
.then(() => true)
838+
.catch(() => false)
839+
expect(newDirExists).toBe(true)
840+
})
841+
842+
test('should preserve project content after rename', async () => {
843+
// Create a project with content
844+
const originalName = 'ProjectWithContent'
845+
const createResult = await projectService.createProject(originalName, projectsDirectory)
846+
847+
// Add various files and directories
848+
const srcDir = path.join(createResult.projectPath, 'src')
849+
const testDir = path.join(createResult.projectPath, 'test')
850+
await fs.mkdir(srcDir, { recursive: true })
851+
await fs.mkdir(testDir, { recursive: true })
852+
await fs.writeFile(path.join(srcDir, 'Main.enso'), 'main = "Main content"')
853+
await fs.writeFile(path.join(srcDir, 'Utils.enso'), 'utils = "Utils content"')
854+
await fs.writeFile(path.join(testDir, 'Test.enso'), 'test = "Test content"')
855+
856+
const newName = 'RenamedProjectWithContent'
857+
858+
// Rename the project
859+
await projectService.renameProject(createResult.projectId, newName, projectsDirectory)
860+
861+
// Verify all content is preserved
862+
const newDirectoryPath = path.join(
863+
path.dirname(createResult.projectPath),
864+
'RenamedProjectWithContent',
865+
)
866+
const mainContent = await fs.readFile(
867+
path.join(newDirectoryPath, 'src', 'Main.enso'),
868+
'utf-8',
869+
)
870+
const utilsContent = await fs.readFile(
871+
path.join(newDirectoryPath, 'src', 'Utils.enso'),
872+
'utf-8',
873+
)
874+
const testContent = await fs.readFile(
875+
path.join(newDirectoryPath, 'test', 'Test.enso'),
876+
'utf-8',
877+
)
878+
879+
expect(mainContent).toBe('main = "Main content"')
880+
expect(utilsContent).toBe('utils = "Utils content"')
881+
expect(testContent).toBe('test = "Test content"')
882+
})
883+
884+
test(
885+
'should allow renaming an opened project multiple times',
886+
async () => {
887+
// Create a project
888+
const originalName = 'ProjectToRenameMultipleTimes'
889+
const createResult = await projectService.createProject(originalName, projectsDirectory)
890+
891+
// Open the project to start the language server
892+
await projectService.openProject(createResult.projectId, projectsDirectory)
893+
894+
// First rename while it's running
895+
const firstName = 'FirstRename'
896+
try {
897+
await projectService.renameProject(createResult.projectId, firstName, projectsDirectory)
898+
} catch {/* Expected error for uninitialized test project */}
899+
900+
// Verify first rename was applied to package.yaml
901+
const packagePath1 = path.join(createResult.projectPath, 'package.yaml')
902+
const packageContent1 = await fs.readFile(packagePath1, 'utf-8')
903+
expect(packageContent1).contain(firstName)
904+
905+
// Second rename while still running
906+
const secondName = 'SecondRename'
907+
try {
908+
await projectService.renameProject(createResult.projectId, secondName, projectsDirectory)
909+
} catch {/* Expected error for uninitialized test project */}
910+
911+
// Verify second rename was applied
912+
const packageContent2 = await fs.readFile(packagePath1, 'utf-8')
913+
expect(packageContent2).contain(secondName)
914+
expect(packageContent2).not.contain(firstName)
915+
916+
// Third rename while still running
917+
const thirdName = 'ThirdRename'
918+
try {
919+
await projectService.renameProject(createResult.projectId, thirdName, projectsDirectory)
920+
} catch {/* Expected error for uninitialized test project */}
921+
922+
// Verify third rename was applied
923+
const packageContent3 = await fs.readFile(packagePath1, 'utf-8')
924+
expect(packageContent3).contain(thirdName)
925+
expect(packageContent3).not.contain(secondName)
926+
927+
// Close the project to trigger the final deferred rename
928+
await projectService.closeProject(createResult.projectId)
929+
930+
// Verify the directory was renamed to the final name
931+
const finalDirectoryPath = path.join(path.dirname(createResult.projectPath), thirdName)
932+
const finalDirExists = await fs
933+
.access(finalDirectoryPath)
934+
.then(() => true)
935+
.catch(() => false)
936+
expect(finalDirExists).toBe(true)
937+
938+
// Verify the original directory no longer exists
939+
const originalDirExists = await fs
940+
.access(createResult.projectPath)
941+
.then(() => true)
942+
.catch(() => false)
943+
expect(originalDirExists).toBe(false)
944+
945+
// Verify metadata is preserved with correct ID
946+
const metadataPath = path.join(finalDirectoryPath, '.enso', 'project.json')
947+
const metadataContent = await fs.readFile(metadataPath, 'utf-8')
948+
const metadata = JSON.parse(metadataContent)
949+
expect(metadata.id).toBe(createResult.projectId)
950+
},
951+
LANGUAGE_SERVER_TEST_TIMEOUT,
952+
)
953+
954+
test(
955+
'should handle renaming multiple running projects independently',
956+
async () => {
957+
// Create two projects
958+
const project1 = await projectService.createProject('RunningProject1', projectsDirectory)
959+
const project2 = await projectService.createProject('RunningProject2', projectsDirectory)
960+
961+
// Open both projects
962+
await projectService.openProject(project1.projectId, projectsDirectory)
963+
await projectService.openProject(project2.projectId, projectsDirectory)
964+
965+
// Rename both projects while they're running
966+
try {
967+
await projectService.renameProject(
968+
project1.projectId,
969+
'RenamedRunning1',
970+
projectsDirectory,
971+
)
972+
} catch {/* Expected error for uninitialized test project */}
973+
try {
974+
await projectService.renameProject(
975+
project2.projectId,
976+
'RenamedRunning2',
977+
projectsDirectory,
978+
)
979+
} catch {/* Expected error for uninitialized test project */}
980+
981+
// Verify package name is updated
982+
const package1Path = path.join(project1.projectPath, 'package.yaml')
983+
const package1Content = await fs.readFile(package1Path, 'utf-8')
984+
expect(package1Content).contain('RenamedRunning1')
985+
986+
const package2Path = path.join(project2.projectPath, 'package.yaml')
987+
const package2Content = await fs.readFile(package2Path, 'utf-8')
988+
expect(package2Content).contain('RenamedRunning2')
989+
990+
// Close both projects
991+
await projectService.closeProject(project1.projectId)
992+
await projectService.closeProject(project2.projectId)
993+
994+
// Verify both directories were renamed
995+
const newPath1 = path.join(path.dirname(project1.projectPath), 'RenamedRunning1')
996+
const newPath2 = path.join(path.dirname(project2.projectPath), 'RenamedRunning2')
997+
998+
const exists1 = await fs
999+
.access(newPath1)
1000+
.then(() => true)
1001+
.catch(() => false)
1002+
const exists2 = await fs
1003+
.access(newPath2)
1004+
.then(() => true)
1005+
.catch(() => false)
1006+
1007+
expect(exists1).toBe(true)
1008+
expect(exists2).toBe(true)
1009+
},
1010+
LANGUAGE_SERVER_TEST_TIMEOUT * 2,
1011+
)
1012+
})
6741013
})

0 commit comments

Comments
 (0)