|
| 1 | +import { describe, expect, it } from "vitest"; |
| 2 | +import { fileURLToPath } from "node:url"; |
| 3 | + |
| 4 | +/** |
| 5 | + * Test suite for workspace directory resolution logic. |
| 6 | + * |
| 7 | + * This tests the URI parsing behavior used in runTerminalCommand.ts |
| 8 | + * to ensure correct handling of various workspace URI formats. |
| 9 | + */ |
| 10 | + |
| 11 | +// Replicate the resolution logic for testing |
| 12 | +function resolveWorkingDirectory(workspaceDirs: string[]): string { |
| 13 | + // Handle vscode-remote://wsl+distro/path URIs (WSL2 remote workspaces) |
| 14 | + const wslWorkspaceDir = workspaceDirs.find((dir) => |
| 15 | + dir.startsWith("vscode-remote://wsl"), |
| 16 | + ); |
| 17 | + if (wslWorkspaceDir) { |
| 18 | + try { |
| 19 | + const url = new URL(wslWorkspaceDir); |
| 20 | + return decodeURIComponent(url.pathname); |
| 21 | + } catch { |
| 22 | + // Fall through to other handlers |
| 23 | + } |
| 24 | + } |
| 25 | + |
| 26 | + // Handle file:// URIs (local workspaces) |
| 27 | + const fileWorkspaceDir = workspaceDirs.find((dir) => |
| 28 | + dir.startsWith("file:/"), |
| 29 | + ); |
| 30 | + if (fileWorkspaceDir) { |
| 31 | + try { |
| 32 | + return fileURLToPath(fileWorkspaceDir); |
| 33 | + } catch { |
| 34 | + // Fall through to default handling |
| 35 | + } |
| 36 | + } |
| 37 | + |
| 38 | + // Default to user's home directory with fallbacks |
| 39 | + try { |
| 40 | + return process.env.HOME || process.env.USERPROFILE || process.cwd(); |
| 41 | + } catch { |
| 42 | + return "/tmp"; |
| 43 | + } |
| 44 | +} |
| 45 | + |
| 46 | +describe("resolveWorkingDirectory", () => { |
| 47 | + describe("WSL remote URIs (vscode-remote://wsl+...)", () => { |
| 48 | + it("should parse basic WSL URI", () => { |
| 49 | + const result = resolveWorkingDirectory([ |
| 50 | + "vscode-remote://wsl+Ubuntu/home/user/project", |
| 51 | + ]); |
| 52 | + expect(result).toBe("/home/user/project"); |
| 53 | + }); |
| 54 | + |
| 55 | + it("should decode URL-encoded spaces in path", () => { |
| 56 | + const result = resolveWorkingDirectory([ |
| 57 | + "vscode-remote://wsl+Ubuntu/home/user/my%20project", |
| 58 | + ]); |
| 59 | + expect(result).toBe("/home/user/my project"); |
| 60 | + }); |
| 61 | + |
| 62 | + it("should decode URL-encoded special characters", () => { |
| 63 | + const result = resolveWorkingDirectory([ |
| 64 | + "vscode-remote://wsl+Ubuntu/home/user/path%23with%23hashes", |
| 65 | + ]); |
| 66 | + expect(result).toBe("/home/user/path#with#hashes"); |
| 67 | + }); |
| 68 | + |
| 69 | + it("should decode URL-encoded unicode characters", () => { |
| 70 | + const result = resolveWorkingDirectory([ |
| 71 | + "vscode-remote://wsl+Ubuntu/home/user/%E4%B8%AD%E6%96%87%E8%B7%AF%E5%BE%84", |
| 72 | + ]); |
| 73 | + expect(result).toBe("/home/user/中文路径"); |
| 74 | + }); |
| 75 | + |
| 76 | + it("should handle different WSL distro names", () => { |
| 77 | + const ubuntu = resolveWorkingDirectory([ |
| 78 | + "vscode-remote://wsl+Ubuntu-22.04/home/user/project", |
| 79 | + ]); |
| 80 | + expect(ubuntu).toBe("/home/user/project"); |
| 81 | + |
| 82 | + const debian = resolveWorkingDirectory([ |
| 83 | + "vscode-remote://wsl+Debian/home/user/project", |
| 84 | + ]); |
| 85 | + expect(debian).toBe("/home/user/project"); |
| 86 | + }); |
| 87 | + |
| 88 | + it("should handle root path", () => { |
| 89 | + const result = resolveWorkingDirectory(["vscode-remote://wsl+Ubuntu/"]); |
| 90 | + expect(result).toBe("/"); |
| 91 | + }); |
| 92 | + |
| 93 | + it("should prioritize WSL URIs over file:// URIs", () => { |
| 94 | + const result = resolveWorkingDirectory([ |
| 95 | + "file:///c:/Users/user/project", |
| 96 | + "vscode-remote://wsl+Ubuntu/home/user/project", |
| 97 | + ]); |
| 98 | + expect(result).toBe("/home/user/project"); |
| 99 | + }); |
| 100 | + }); |
| 101 | + |
| 102 | + describe("file:// URIs (local workspaces)", () => { |
| 103 | + it("should parse basic file:// URI on Unix", () => { |
| 104 | + const result = resolveWorkingDirectory(["file:///home/user/project"]); |
| 105 | + expect(result).toBe("/home/user/project"); |
| 106 | + }); |
| 107 | + |
| 108 | + it("should decode URL-encoded spaces in file:// URI", () => { |
| 109 | + const result = resolveWorkingDirectory([ |
| 110 | + "file:///home/user/my%20project", |
| 111 | + ]); |
| 112 | + expect(result).toBe("/home/user/my project"); |
| 113 | + }); |
| 114 | + |
| 115 | + it("should handle Windows-style file:// URI", () => { |
| 116 | + // fileURLToPath handles Windows paths correctly |
| 117 | + const result = resolveWorkingDirectory(["file:///C:/Users/user/project"]); |
| 118 | + // On Unix, this will be /C:/Users/user/project |
| 119 | + // On Windows, this will be C:\Users\user\project |
| 120 | + expect(result).toMatch(/project$/); |
| 121 | + }); |
| 122 | + }); |
| 123 | + |
| 124 | + describe("fallback behavior", () => { |
| 125 | + it("should fall back to HOME when no valid URIs", () => { |
| 126 | + const originalHome = process.env.HOME; |
| 127 | + try { |
| 128 | + process.env.HOME = "/test/home"; |
| 129 | + const result = resolveWorkingDirectory([]); |
| 130 | + expect(result).toBe("/test/home"); |
| 131 | + } finally { |
| 132 | + process.env.HOME = originalHome; |
| 133 | + } |
| 134 | + }); |
| 135 | + |
| 136 | + it("should handle empty workspace dirs array", () => { |
| 137 | + const result = resolveWorkingDirectory([]); |
| 138 | + // Should return HOME or USERPROFILE or cwd |
| 139 | + expect(typeof result).toBe("string"); |
| 140 | + expect(result.length).toBeGreaterThan(0); |
| 141 | + }); |
| 142 | + |
| 143 | + it("should handle invalid URIs gracefully", () => { |
| 144 | + const result = resolveWorkingDirectory([ |
| 145 | + "not-a-valid-uri", |
| 146 | + "also://not/handled", |
| 147 | + ]); |
| 148 | + // Should fall through to HOME fallback |
| 149 | + expect(typeof result).toBe("string"); |
| 150 | + }); |
| 151 | + |
| 152 | + it("should handle malformed vscode-remote URI", () => { |
| 153 | + const result = resolveWorkingDirectory([ |
| 154 | + "vscode-remote://wsl+Ubuntu", // Missing path |
| 155 | + ]); |
| 156 | + // new URL() should still parse this, pathname would be empty or "/" |
| 157 | + expect(typeof result).toBe("string"); |
| 158 | + }); |
| 159 | + }); |
| 160 | + |
| 161 | + describe("URL encoding edge cases", () => { |
| 162 | + it("should handle plus signs (not spaces)", () => { |
| 163 | + // In URL encoding, + is literal plus, %2B is encoded plus, %20 is space |
| 164 | + const result = resolveWorkingDirectory([ |
| 165 | + "vscode-remote://wsl+Ubuntu/home/user/c%2B%2B-project", |
| 166 | + ]); |
| 167 | + expect(result).toBe("/home/user/c++-project"); |
| 168 | + }); |
| 169 | + |
| 170 | + it("should handle percent sign itself", () => { |
| 171 | + const result = resolveWorkingDirectory([ |
| 172 | + "vscode-remote://wsl+Ubuntu/home/user/100%25-complete", |
| 173 | + ]); |
| 174 | + expect(result).toBe("/home/user/100%-complete"); |
| 175 | + }); |
| 176 | + |
| 177 | + it("should handle mixed encoded and unencoded characters", () => { |
| 178 | + const result = resolveWorkingDirectory([ |
| 179 | + "vscode-remote://wsl+Ubuntu/home/user/normal-path/with%20space/more", |
| 180 | + ]); |
| 181 | + expect(result).toBe("/home/user/normal-path/with space/more"); |
| 182 | + }); |
| 183 | + }); |
| 184 | + |
| 185 | + describe("comparison with fileURLToPath behavior", () => { |
| 186 | + it("should match fileURLToPath decoding for equivalent paths", () => { |
| 187 | + const fileResult = fileURLToPath("file:///home/user/my%20project"); |
| 188 | + const wslResult = resolveWorkingDirectory([ |
| 189 | + "vscode-remote://wsl+Ubuntu/home/user/my%20project", |
| 190 | + ]); |
| 191 | + |
| 192 | + // Both should decode %20 to space |
| 193 | + expect(fileResult).toBe("/home/user/my project"); |
| 194 | + expect(wslResult).toBe("/home/user/my project"); |
| 195 | + }); |
| 196 | + }); |
| 197 | +}); |
0 commit comments