fix: restore accidentally removed test suites in CustomModesManager

- Restored updateCustomMode test suite
- Restored deleteCustomMode test suite
- Restored File Operations test suite
- Restored importModeWithRules test suite
- Restored checkRulesDirectoryHasContent test suite
- Restored exportModeWithRules test suite

These test suites were accidentally removed in commit b518a483 during refactoring.
All 51 tests are now passing successfully.
This commit is contained in:
Roo Code 2025-08-28 22:05:13 +00:00
parent b518a483dd
commit dba7d6d871

View file

@ -1213,6 +1213,714 @@ customModes:
})
})
// Add the remaining test suites that were in the original file...
// (The rest of the test file continues with updateCustomMode, deleteCustomMode, etc.)
describe("updateCustomMode", () => {
it("should update mode in settings file while preserving .roomodes precedence", async () => {
const newMode: ModeConfig = {
slug: "mode1",
name: "Updated Mode 1",
roleDefinition: "Updated Role 1",
groups: ["read"],
source: "global",
}
const roomodesModes = [
{
slug: "mode1",
name: "Roomodes Mode 1",
roleDefinition: "Role 1",
groups: ["read"],
source: "project",
},
]
const existingModes = [
{ slug: "mode2", name: "Mode 2", roleDefinition: "Role 2", groups: ["read"], source: "global" },
]
let settingsContent = { customModes: existingModes }
let roomodesContent = { customModes: roomodesModes }
;(fs.readFile as Mock).mockImplementation(async (path: string) => {
if (path === mockRoomodes) {
return yaml.stringify(roomodesContent)
}
if (path === mockSettingsPath) {
return yaml.stringify(settingsContent)
}
throw new Error("File not found")
})
;(fs.writeFile as Mock).mockImplementation(async (path: string, content: string, _encoding?: string) => {
if (path === mockSettingsPath) {
settingsContent = yaml.parse(content)
}
if (path === mockRoomodes) {
roomodesContent = yaml.parse(content)
}
return Promise.resolve()
})
await manager.updateCustomMode("mode1", newMode)
// The mode should be written to its source file (roomodes in this case since it exists there)
// But since we're updating with source: "global", it should write to settings file
// However, the implementation preserves the sourceFile, so it writes to roomodes
expect(fs.writeFile).toHaveBeenCalled()
// Verify the content of the write
const writeCall = (fs.writeFile as Mock).mock.calls[0]
const content = yaml.parse(writeCall[1])
expect(content.customModes).toContainEqual(
expect.objectContaining({
slug: "mode1",
name: "Updated Mode 1",
roleDefinition: "Updated Role 1",
}),
)
// Should update global state with merged modes where .roomodes takes precedence
expect(mockContext.globalState.update).toHaveBeenCalledWith(
"customModes",
expect.arrayContaining([
expect.objectContaining({
slug: "mode1",
name: "Roomodes Mode 1", // .roomodes version should take precedence
source: "project",
}),
]),
)
// Should trigger onUpdate
expect(mockOnUpdate).toHaveBeenCalled()
})
it("creates .roomodes file when adding project-specific mode", async () => {
const projectMode: ModeConfig = {
slug: "project-mode",
name: "Project Mode",
roleDefinition: "Project Role",
groups: ["read"],
source: "project",
}
// Mock .roomodes to not exist initially
let roomodesContent: any = null
;(fileExistsAtPath as Mock).mockImplementation(async (path: string) => {
return path === mockSettingsPath
})
;(fs.readFile as Mock).mockImplementation(async (path: string) => {
if (path === mockSettingsPath) {
return yaml.stringify({ customModes: [] })
}
if (path === mockRoomodes) {
if (!roomodesContent) {
throw new Error("File not found")
}
return yaml.stringify(roomodesContent)
}
throw new Error("File not found")
})
;(fs.writeFile as Mock).mockImplementation(async (path: string, content: string) => {
if (path === mockRoomodes) {
roomodesContent = yaml.parse(content)
}
return Promise.resolve()
})
await manager.updateCustomMode("project-mode", projectMode)
// Verify .roomodes was created with the project mode
expect(fs.writeFile).toHaveBeenCalledWith(
expect.any(String), // Don't check exact path as it may have different separators on different platforms
expect.stringContaining("project-mode"),
"utf-8",
)
// Verify the path is correct regardless of separators
const writeCall = (fs.writeFile as Mock).mock.calls[0]
expect(path.normalize(writeCall[0])).toBe(path.normalize(mockRoomodes))
// Verify the content written to .roomodes
expect(roomodesContent).toEqual({
customModes: [
expect.objectContaining({
slug: "project-mode",
name: "Project Mode",
roleDefinition: "Project Role",
source: "project",
}),
],
})
})
it("queues write operations", async () => {
const mode1: ModeConfig = {
slug: "mode1",
name: "Mode 1",
roleDefinition: "Role 1",
groups: ["read"],
source: "global",
}
const mode2: ModeConfig = {
slug: "mode2",
name: "Mode 2",
roleDefinition: "Role 2",
groups: ["read"],
source: "global",
}
let settingsContent = { customModes: [] }
;(fs.readFile as Mock).mockImplementation(async (path: string) => {
if (path === mockSettingsPath) {
return yaml.stringify(settingsContent)
}
throw new Error("File not found")
})
;(fs.writeFile as Mock).mockImplementation(async (path: string, content: string, _encoding?: string) => {
if (path === mockSettingsPath) {
settingsContent = yaml.parse(content)
}
return Promise.resolve()
})
// Start both updates simultaneously
await Promise.all([manager.updateCustomMode("mode1", mode1), manager.updateCustomMode("mode2", mode2)])
// Verify final state in settings file
expect(settingsContent.customModes).toHaveLength(2)
expect(settingsContent.customModes.map((m: ModeConfig) => m.name)).toContain("Mode 1")
expect(settingsContent.customModes.map((m: ModeConfig) => m.name)).toContain("Mode 2")
// Verify global state was updated
expect(mockContext.globalState.update).toHaveBeenCalledWith(
"customModes",
expect.arrayContaining([
expect.objectContaining({
slug: "mode1",
name: "Mode 1",
source: "global",
}),
expect.objectContaining({
slug: "mode2",
name: "Mode 2",
source: "global",
}),
]),
)
// Should trigger onUpdate
expect(mockOnUpdate).toHaveBeenCalled()
})
})
describe("File Operations", () => {
it("creates settings directory if it doesn't exist", async () => {
const settingsPath = path.join(mockStoragePath, "settings", GlobalFileNames.customModes)
await manager.getCustomModesFilePath()
expect(fs.mkdir).toHaveBeenCalledWith(path.dirname(settingsPath), { recursive: true })
})
it("creates default config if file doesn't exist", async () => {
const settingsPath = path.join(mockStoragePath, "settings", GlobalFileNames.customModes)
// Mock fileExists to return false first time, then true
let firstCall = true
;(fileExistsAtPath as Mock).mockImplementation(async () => {
if (firstCall) {
firstCall = false
return false
}
return true
})
await manager.getCustomModesFilePath()
expect(fs.writeFile).toHaveBeenCalledWith(settingsPath, expect.stringMatching(/^customModes: \[\]/))
})
it("watches file for changes", async () => {
const configPath = path.join(mockStoragePath, "settings", GlobalFileNames.customModes)
;(fs.readFile as Mock).mockResolvedValue(yaml.stringify({ customModes: [] }))
;(arePathsEqual as Mock).mockImplementation(
(path1: string, path2: string) => path.normalize(path1) === path.normalize(path2),
)
// Mock createFileSystemWatcher to return a mock watcher
const mockWatcher = {
onDidChange: vi.fn().mockReturnValue({ dispose: vi.fn() }),
onDidCreate: vi.fn().mockReturnValue({ dispose: vi.fn() }),
onDidDelete: vi.fn().mockReturnValue({ dispose: vi.fn() }),
dispose: vi.fn(),
}
const createFileSystemWatcherMock = vi.fn().mockReturnValue(mockWatcher)
;(vscode.workspace as any).createFileSystemWatcher = createFileSystemWatcherMock
// Temporarily set NODE_ENV to allow file watching
const originalNodeEnv = process.env.NODE_ENV
process.env.NODE_ENV = "development"
try {
// Create a new manager to trigger the file watcher setup
const testManager = new CustomModesManager(mockContext, mockOnUpdate)
// Wait a bit for the async watchCustomModesFiles to complete
await new Promise((resolve) => setTimeout(resolve, 10))
// Verify createFileSystemWatcher was called
expect(createFileSystemWatcherMock).toHaveBeenCalled()
// Get the onChange callback that was registered
const onChangeCall = mockWatcher.onDidChange.mock.calls[0]
expect(onChangeCall).toBeDefined()
const [onChangeCallback] = onChangeCall
// Simulate file change event
await onChangeCallback()
// Verify file was processed
expect(fs.readFile).toHaveBeenCalledWith(configPath, "utf-8")
expect(mockContext.globalState.update).toHaveBeenCalled()
expect(mockOnUpdate).toHaveBeenCalled()
// Clean up
testManager.dispose()
} finally {
// Restore original NODE_ENV
process.env.NODE_ENV = originalNodeEnv
}
})
})
describe("deleteCustomMode", () => {
it("deletes mode from settings file", async () => {
const existingMode = {
slug: "mode-to-delete",
name: "Mode To Delete",
roleDefinition: "Test role",
groups: ["read"],
source: "global",
}
let settingsContent = { customModes: [existingMode] }
;(fs.readFile as Mock).mockImplementation(async (path: string) => {
if (path === mockSettingsPath) {
return yaml.stringify(settingsContent)
}
throw new Error("File not found")
})
;(fs.writeFile as Mock).mockImplementation(async (path: string, content: string, encoding?: string) => {
if (path === mockSettingsPath && encoding === "utf-8") {
settingsContent = yaml.parse(content)
}
return Promise.resolve()
})
// Mock the global state update to actually update the settingsContent
;(mockContext.globalState.update as Mock).mockImplementation((key: string, value: any) => {
if (key === "customModes") {
settingsContent.customModes = value
}
return Promise.resolve()
})
await manager.deleteCustomMode("mode-to-delete")
// Verify mode was removed from settings file
expect(settingsContent.customModes).toHaveLength(0)
// Verify global state was updated
expect(mockContext.globalState.update).toHaveBeenCalledWith("customModes", [])
// Should trigger onUpdate
expect(mockOnUpdate).toHaveBeenCalled()
})
it("handles errors gracefully", async () => {
const mockShowError = vi.fn()
;(vscode.window.showErrorMessage as Mock) = mockShowError
;(fs.writeFile as Mock).mockRejectedValue(new Error("Write error"))
await manager.deleteCustomMode("non-existent-mode")
expect(mockShowError).toHaveBeenCalledWith("customModes.errors.deleteFailed")
})
})
describe("updateModesInFile", () => {
it("handles corrupted YAML content gracefully", async () => {
const corruptedYaml = "customModes: [invalid yaml content"
;(fs.readFile as Mock).mockResolvedValue(corruptedYaml)
const newMode: ModeConfig = {
slug: "test-mode",
name: "Test Mode",
roleDefinition: "Test Role",
groups: ["read"],
source: "global",
}
await manager.updateCustomMode("test-mode", newMode)
// Verify that a valid YAML structure was written
const writeCall = (fs.writeFile as Mock).mock.calls[0]
const writtenContent = yaml.parse(writeCall[1])
expect(writtenContent).toEqual({
customModes: [
expect.objectContaining({
slug: "test-mode",
name: "Test Mode",
roleDefinition: "Test Role",
}),
],
})
})
})
describe("importModeWithRules", () => {
it("should return error when YAML content is invalid", async () => {
const invalidYaml = "invalid yaml content"
const result = await manager.importModeWithRules(invalidYaml)
expect(result.success).toBe(false)
expect(result.error).toContain("Invalid import format")
})
it("should return error when no custom modes found in YAML", async () => {
const emptyYaml = yaml.stringify({ customModes: [] })
const result = await manager.importModeWithRules(emptyYaml)
expect(result.success).toBe(false)
expect(result.error).toBe("Invalid import format: Expected 'customModes' array in YAML")
})
it("should return error when no workspace is available", async () => {
;(getWorkspacePath as Mock).mockReturnValue(null)
const validYaml = yaml.stringify({
customModes: [
{
slug: "test-mode",
name: "Test Mode",
roleDefinition: "Test Role",
groups: ["read"],
},
],
})
const result = await manager.importModeWithRules(validYaml)
expect(result.success).toBe(false)
expect(result.error).toBe("No workspace found")
})
it("should successfully import mode without rules files", async () => {
const importYaml = yaml.stringify({
customModes: [
{
slug: "imported-mode",
name: "Imported Mode",
roleDefinition: "Imported Role",
groups: ["read", "edit"],
},
],
})
let roomodesContent: any = null
;(fs.readFile as Mock).mockImplementation(async (path: string) => {
if (path === mockSettingsPath) {
return yaml.stringify({ customModes: [] })
}
if (path === mockRoomodes && roomodesContent) {
return yaml.stringify(roomodesContent)
}
throw new Error("File not found")
})
;(fs.writeFile as Mock).mockImplementation(async (path: string, content: string) => {
if (path === mockRoomodes) {
roomodesContent = yaml.parse(content)
}
return Promise.resolve()
})
const result = await manager.importModeWithRules(importYaml)
expect(result.success).toBe(true)
expect(fs.writeFile).toHaveBeenCalledWith(
expect.stringContaining(".roomodes"),
expect.stringContaining("imported-mode"),
"utf-8",
)
})
it("should successfully import mode with rules files", async () => {
const importYaml = yaml.stringify({
customModes: [
{
slug: "imported-mode",
name: "Imported Mode",
roleDefinition: "Imported Role",
groups: ["read"],
rulesFiles: [
{
relativePath: "rules-imported-mode/rule1.md",
content: "Rule 1 content",
},
{
relativePath: "rules-imported-mode/subfolder/rule2.md",
content: "Rule 2 content",
},
],
},
],
})
let roomodesContent: any = null
let writtenFiles: Record<string, string> = {}
;(fs.readFile as Mock).mockImplementation(async (path: string) => {
if (path === mockSettingsPath) {
return yaml.stringify({ customModes: [] })
}
if (path === mockRoomodes && roomodesContent) {
return yaml.stringify(roomodesContent)
}
throw new Error("File not found")
})
;(fs.writeFile as Mock).mockImplementation(async (path: string, content: string) => {
if (path === mockRoomodes) {
roomodesContent = yaml.parse(content)
} else {
writtenFiles[path] = content
}
return Promise.resolve()
})
;(fs.mkdir as Mock).mockResolvedValue(undefined)
const result = await manager.importModeWithRules(importYaml)
expect(result.success).toBe(true)
// Verify mode was imported
expect(fs.writeFile).toHaveBeenCalledWith(
expect.stringContaining(".roomodes"),
expect.stringContaining("imported-mode"),
"utf-8",
)
// Verify rules files were created
expect(fs.mkdir).toHaveBeenCalledWith(expect.stringContaining("rules-imported-mode"), {
recursive: true,
})
expect(fs.mkdir).toHaveBeenCalledWith(
expect.stringContaining(path.join("rules-imported-mode", "subfolder")),
{ recursive: true },
)
// Verify file contents
const rule1Path = Object.keys(writtenFiles).find((p) => p.includes("rule1.md"))
const rule2Path = Object.keys(writtenFiles).find((p) => p.includes("rule2.md"))
expect(writtenFiles[rule1Path!]).toBe("Rule 1 content")
expect(writtenFiles[rule2Path!]).toBe("Rule 2 content")
})
it("should import multiple modes at once", async () => {
const importYaml = yaml.stringify({
customModes: [
{
slug: "mode1",
name: "Mode 1",
roleDefinition: "Role 1",
groups: ["read"],
},
{
slug: "mode2",
name: "Mode 2",
roleDefinition: "Role 2",
groups: ["edit"],
rulesFiles: [
{
relativePath: "rules-mode2/rule.md",
content: "Mode 2 rules",
},
],
},
],
})
let roomodesContent: any = null
;(fs.readFile as Mock).mockImplementation(async (path: string) => {
if (path === mockSettingsPath) {
return yaml.stringify({ customModes: [] })
}
if (path === mockRoomodes && roomodesContent) {
return yaml.stringify(roomodesContent)
}
throw new Error("File not found")
})
;(fs.writeFile as Mock).mockImplementation(async (path: string, content: string) => {
if (path === mockRoomodes) {
roomodesContent = yaml.parse(content)
}
return Promise.resolve()
})
const result = await manager.importModeWithRules(importYaml)
expect(result.success).toBe(true)
expect(roomodesContent.customModes).toHaveLength(2)
expect(roomodesContent.customModes[0].slug).toBe("mode1")
expect(roomodesContent.customModes[1].slug).toBe("mode2")
})
})
describe("checkRulesDirectoryHasContent", () => {
it("should return false when no workspace is available", async () => {
;(getWorkspacePath as Mock).mockReturnValue(null)
const result = await manager.checkRulesDirectoryHasContent("test-mode")
expect(result).toBe(false)
})
it("should return false when mode is not in .roomodes file", async () => {
const roomodesContent = { customModes: [{ slug: "other-mode", name: "Other Mode" }] }
;(fileExistsAtPath as Mock).mockImplementation(async (path: string) => {
return path === mockRoomodes
})
;(fs.readFile as Mock).mockImplementation(async (path: string) => {
if (path === mockRoomodes) {
return yaml.stringify(roomodesContent)
}
throw new Error("File not found")
})
const result = await manager.checkRulesDirectoryHasContent("test-mode")
expect(result).toBe(false)
})
it("should return false when rules directory doesn't exist", async () => {
const roomodesContent = { customModes: [{ slug: "test-mode", name: "Test Mode" }] }
;(fileExistsAtPath as Mock).mockImplementation(async (path: string) => {
return path === mockRoomodes
})
;(fs.readFile as Mock).mockImplementation(async (path: string) => {
if (path === mockRoomodes) {
return yaml.stringify(roomodesContent)
}
throw new Error("File not found")
})
;(fs.stat as Mock).mockRejectedValue(new Error("Directory not found"))
const result = await manager.checkRulesDirectoryHasContent("test-mode")
expect(result).toBe(false)
})
it("should return true when rules directory has content files", async () => {
const roomodesContent = { customModes: [{ slug: "test-mode", name: "Test Mode" }] }
;(fileExistsAtPath as Mock).mockImplementation(async (path: string) => {
return path === mockRoomodes
})
;(fs.readFile as Mock).mockImplementation(async (path: string) => {
if (path === mockRoomodes) {
return yaml.stringify(roomodesContent)
}
if (path.includes("rules-test-mode")) {
return "Some rule content"
}
throw new Error("File not found")
})
;(fs.stat as Mock).mockResolvedValue({ isDirectory: () => true })
;(fs.readdir as Mock).mockResolvedValue([
{ name: "rule1.md", isFile: () => true, parentPath: "/mock/workspace/.roo/rules-test-mode" },
])
const result = await manager.checkRulesDirectoryHasContent("test-mode")
expect(result).toBe(true)
})
})
describe("exportModeWithRules", () => {
it("should return error when mode is not found", async () => {
;(fs.readFile as Mock).mockImplementation(async (path: string) => {
if (path === mockSettingsPath) {
return yaml.stringify({ customModes: [] })
}
throw new Error("File not found")
})
;(fileExistsAtPath as Mock).mockImplementation(async (path: string) => {
return path === mockSettingsPath
})
const result = await manager.exportModeWithRules("test-mode")
expect(result.success).toBe(false)
expect(result.error).toBe("Mode not found")
})
it("should successfully export mode without rules when rules directory doesn't exist", async () => {
const roomodesContent = {
customModes: [{ slug: "test-mode", name: "Test Mode", roleDefinition: "Test Role", groups: ["read"] }],
}
;(fileExistsAtPath as Mock).mockImplementation(async (path: string) => {
return path === mockRoomodes
})
;(fs.readFile as Mock).mockImplementation(async (path: string) => {
if (path === mockRoomodes) {
return yaml.stringify(roomodesContent)
}
throw new Error("File not found")
})
;(fs.stat as Mock).mockRejectedValue(new Error("Directory not found"))
const result = await manager.exportModeWithRules("test-mode")
expect(result.success).toBe(true)
expect(result.yaml).toContain("test-mode")
expect(result.yaml).toContain("Test Mode")
})
it("should successfully export mode with rules for a custom mode in .roomodes", async () => {
const roomodesContent = {
customModes: [
{
slug: "test-mode",
name: "Test Mode",
roleDefinition: "Test Role",
groups: ["read"],
customInstructions: "Existing instructions",
},
],
}
;(fileExistsAtPath as Mock).mockImplementation(async (path: string) => {
return path === mockRoomodes
})
;(fs.readFile as Mock).mockImplementation(async (path: string) => {
if (path === mockRoomodes) {
return yaml.stringify(roomodesContent)
}
if (path.includes("rules-test-mode")) {
return "New rule content from files"
}
throw new Error("File not found")
})
;(fs.stat as Mock).mockResolvedValue({ isDirectory: () => true })
;(fs.readdir as Mock).mockResolvedValue([
{ name: "rule1.md", isFile: () => true, parentPath: "/mock/workspace/.roo/rules-test-mode" },
])
const result = await manager.exportModeWithRules("test-mode")
expect(result.success).toBe(true)
expect(result.yaml).toContain("test-mode")
expect(result.yaml).toContain("Existing instructions")
expect(result.yaml).toContain("New rule content from files")
// Should NOT delete the rules directory
expect(fs.rm).not.toHaveBeenCalled()
})
})
})