|
| 1 | +import * as vscode from "vscode" |
| 2 | +import * as path from "path" |
| 3 | +import * as fs from "fs/promises" |
| 4 | +import { fileExistsAtPath } from "../utils/fs" |
| 5 | +import { GlobalFileNames } from "../shared/globalFileNames" |
| 6 | +import { migrateSettings } from "../utils/migrateSettings" |
| 7 | + |
| 8 | +// Mock dependencies |
| 9 | +jest.mock("vscode") |
| 10 | +jest.mock("fs/promises") |
| 11 | +jest.mock("fs") |
| 12 | +jest.mock("../utils/fs") |
| 13 | +// We're testing the real migrateSettings function |
| 14 | + |
| 15 | +describe("Settings Migration", () => { |
| 16 | + let mockContext: vscode.ExtensionContext |
| 17 | + let mockOutputChannel: vscode.OutputChannel |
| 18 | + const mockStoragePath = "/mock/storage" |
| 19 | + const mockSettingsDir = path.join(mockStoragePath, "settings") |
| 20 | + |
| 21 | + // Legacy file names |
| 22 | + const legacyCustomModesPath = path.join(mockSettingsDir, "cline_custom_modes.json") |
| 23 | + const legacyMcpSettingsPath = path.join(mockSettingsDir, "cline_mcp_settings.json") |
| 24 | + |
| 25 | + // New file names |
| 26 | + const newCustomModesPath = path.join(mockSettingsDir, GlobalFileNames.customModes) |
| 27 | + const newMcpSettingsPath = path.join(mockSettingsDir, GlobalFileNames.mcpSettings) |
| 28 | + |
| 29 | + beforeEach(() => { |
| 30 | + jest.clearAllMocks() |
| 31 | + |
| 32 | + // Mock output channel |
| 33 | + mockOutputChannel = { |
| 34 | + appendLine: jest.fn(), |
| 35 | + append: jest.fn(), |
| 36 | + clear: jest.fn(), |
| 37 | + show: jest.fn(), |
| 38 | + hide: jest.fn(), |
| 39 | + dispose: jest.fn(), |
| 40 | + } as unknown as vscode.OutputChannel |
| 41 | + |
| 42 | + // Mock extension context |
| 43 | + mockContext = { |
| 44 | + globalStorageUri: { fsPath: mockStoragePath }, |
| 45 | + } as unknown as vscode.ExtensionContext |
| 46 | + |
| 47 | + // The fs/promises mock is already set up in src/__mocks__/fs/promises.ts |
| 48 | + // We don't need to manually mock these methods |
| 49 | + |
| 50 | + // Set global outputChannel for all tests |
| 51 | + ;(global as any).outputChannel = mockOutputChannel |
| 52 | + }) |
| 53 | + |
| 54 | + it("should migrate custom modes file if old file exists and new file doesn't", async () => { |
| 55 | + const mockCustomModesContent = '{"customModes":[{"slug":"test-mode"}]}' as string |
| 56 | + |
| 57 | + // Mock file existence checks |
| 58 | + ;(fileExistsAtPath as jest.Mock).mockImplementation(async (path: string) => { |
| 59 | + if (path === mockSettingsDir) return true |
| 60 | + if (path === legacyCustomModesPath) return true |
| 61 | + if (path === newCustomModesPath) return false |
| 62 | + return false |
| 63 | + }) |
| 64 | + |
| 65 | + await migrateSettings(mockContext, mockOutputChannel) |
| 66 | + |
| 67 | + // Verify file was renamed |
| 68 | + expect(fs.rename).toHaveBeenCalledWith(legacyCustomModesPath, newCustomModesPath) |
| 69 | + }) |
| 70 | + |
| 71 | + it("should migrate MCP settings file if old file exists and new file doesn't", async () => { |
| 72 | + const mockMcpSettingsContent = '{"mcpServers":{"test-server":{}}}' as string |
| 73 | + |
| 74 | + // Mock file existence checks |
| 75 | + ;(fileExistsAtPath as jest.Mock).mockImplementation(async (path: string) => { |
| 76 | + if (path === mockSettingsDir) return true |
| 77 | + if (path === legacyMcpSettingsPath) return true |
| 78 | + if (path === newMcpSettingsPath) return false |
| 79 | + return false |
| 80 | + }) |
| 81 | + |
| 82 | + await migrateSettings(mockContext, mockOutputChannel) |
| 83 | + |
| 84 | + // Verify file was renamed |
| 85 | + expect(fs.rename).toHaveBeenCalledWith(legacyMcpSettingsPath, newMcpSettingsPath) |
| 86 | + }) |
| 87 | + |
| 88 | + it("should not migrate if new file already exists", async () => { |
| 89 | + // Mock file existence checks |
| 90 | + ;(fileExistsAtPath as jest.Mock).mockImplementation(async (path: string) => { |
| 91 | + if (path === mockSettingsDir) return true |
| 92 | + if (path === legacyCustomModesPath) return true |
| 93 | + if (path === newCustomModesPath) return true |
| 94 | + if (path === legacyMcpSettingsPath) return true |
| 95 | + if (path === newMcpSettingsPath) return true |
| 96 | + return false |
| 97 | + }) |
| 98 | + |
| 99 | + await migrateSettings(mockContext, mockOutputChannel) |
| 100 | + |
| 101 | + // Verify no files were renamed |
| 102 | + expect(fs.rename).not.toHaveBeenCalled() |
| 103 | + }) |
| 104 | + |
| 105 | + it("should handle errors gracefully", async () => { |
| 106 | + // Mock file existence checks to throw an error |
| 107 | + ;(fileExistsAtPath as jest.Mock).mockRejectedValue(new Error("Test error")) |
| 108 | + |
| 109 | + // Set the global outputChannel for the test |
| 110 | + ;(global as any).outputChannel = mockOutputChannel |
| 111 | + |
| 112 | + await migrateSettings(mockContext, mockOutputChannel) |
| 113 | + |
| 114 | + // Verify error was logged |
| 115 | + expect(mockOutputChannel.appendLine).toHaveBeenCalledWith( |
| 116 | + expect.stringContaining("Error migrating settings files"), |
| 117 | + ) |
| 118 | + }) |
| 119 | +}) |
0 commit comments