|
1 | | -import { DiffViewProvider } from "../DiffViewProvider" |
| 1 | +import { DiffViewProvider, DIFF_VIEW_URI_SCHEME, DIFF_VIEW_LABEL_CHANGES } from "../DiffViewProvider" |
2 | 2 | import * as vscode from "vscode" |
| 3 | +import * as path from "path" |
| 4 | + |
| 5 | +// Mock fs/promises |
| 6 | +vi.mock("fs/promises", () => ({ |
| 7 | + readFile: vi.fn().mockResolvedValue("file content"), |
| 8 | + writeFile: vi.fn().mockResolvedValue(undefined), |
| 9 | +})) |
| 10 | + |
| 11 | +// Mock utils |
| 12 | +vi.mock("../../../utils/fs", () => ({ |
| 13 | + createDirectoriesForFile: vi.fn().mockResolvedValue([]), |
| 14 | +})) |
| 15 | + |
| 16 | +// Mock path |
| 17 | +vi.mock("path", () => ({ |
| 18 | + resolve: vi.fn((cwd, relPath) => `${cwd}/${relPath}`), |
| 19 | + basename: vi.fn((path) => path.split("/").pop()), |
| 20 | +})) |
3 | 21 |
|
4 | 22 | // Mock vscode |
5 | 23 | vi.mock("vscode", () => ({ |
6 | 24 | workspace: { |
7 | 25 | applyEdit: vi.fn(), |
| 26 | + onDidOpenTextDocument: vi.fn(() => ({ dispose: vi.fn() })), |
| 27 | + textDocuments: [], |
| 28 | + fs: { |
| 29 | + stat: vi.fn(), |
| 30 | + }, |
8 | 31 | }, |
9 | 32 | window: { |
10 | 33 | createTextEditorDecorationType: vi.fn(), |
| 34 | + showTextDocument: vi.fn(), |
| 35 | + onDidChangeVisibleTextEditors: vi.fn(() => ({ dispose: vi.fn() })), |
| 36 | + tabGroups: { |
| 37 | + all: [], |
| 38 | + close: vi.fn(), |
| 39 | + }, |
| 40 | + visibleTextEditors: [], |
| 41 | + }, |
| 42 | + commands: { |
| 43 | + executeCommand: vi.fn(), |
| 44 | + }, |
| 45 | + languages: { |
| 46 | + getDiagnostics: vi.fn(() => []), |
11 | 47 | }, |
12 | 48 | WorkspaceEdit: vi.fn().mockImplementation(() => ({ |
13 | 49 | replace: vi.fn(), |
14 | 50 | delete: vi.fn(), |
15 | 51 | })), |
| 52 | + ViewColumn: { |
| 53 | + Active: 1, |
| 54 | + Beside: 2, |
| 55 | + One: 1, |
| 56 | + Two: 2, |
| 57 | + Three: 3, |
| 58 | + Four: 4, |
| 59 | + Five: 5, |
| 60 | + Six: 6, |
| 61 | + Seven: 7, |
| 62 | + Eight: 8, |
| 63 | + Nine: 9, |
| 64 | + }, |
16 | 65 | Range: vi.fn(), |
17 | 66 | Position: vi.fn(), |
18 | 67 | Selection: vi.fn(), |
19 | 68 | TextEditorRevealType: { |
20 | 69 | InCenter: 2, |
21 | 70 | }, |
| 71 | + TabInputTextDiff: class TabInputTextDiff {}, |
| 72 | + Uri: { |
| 73 | + file: vi.fn((path) => ({ fsPath: path })), |
| 74 | + parse: vi.fn((uri) => ({ with: vi.fn(() => ({})) })), |
| 75 | + }, |
22 | 76 | })) |
23 | 77 |
|
24 | 78 | // Mock DecorationController |
25 | 79 | vi.mock("../DecorationController", () => ({ |
26 | 80 | DecorationController: vi.fn().mockImplementation(() => ({ |
27 | 81 | setActiveLine: vi.fn(), |
28 | 82 | updateOverlayAfterLine: vi.fn(), |
| 83 | + addLines: vi.fn(), |
29 | 84 | clear: vi.fn(), |
30 | 85 | })), |
31 | 86 | })) |
@@ -60,7 +115,11 @@ describe("DiffViewProvider", () => { |
60 | 115 | revealRange: vi.fn(), |
61 | 116 | } |
62 | 117 | ;(diffViewProvider as any).activeLineController = { setActiveLine: vi.fn(), clear: vi.fn() } |
63 | | - ;(diffViewProvider as any).fadedOverlayController = { updateOverlayAfterLine: vi.fn(), clear: vi.fn() } |
| 118 | + ;(diffViewProvider as any).fadedOverlayController = { |
| 119 | + updateOverlayAfterLine: vi.fn(), |
| 120 | + addLines: vi.fn(), |
| 121 | + clear: vi.fn(), |
| 122 | + } |
64 | 123 | }) |
65 | 124 |
|
66 | 125 | describe("update method", () => { |
@@ -93,4 +152,179 @@ describe("DiffViewProvider", () => { |
93 | 152 | expect(mockWorkspaceEdit.replace).toHaveBeenCalledWith(expect.anything(), expect.anything(), "New content") |
94 | 153 | }) |
95 | 154 | }) |
| 155 | + |
| 156 | + describe("open method", () => { |
| 157 | + it("should pre-open file as text document before executing diff command", async () => { |
| 158 | + // Setup |
| 159 | + const mockEditor = { |
| 160 | + document: { |
| 161 | + uri: { fsPath: `${mockCwd}/test.md` }, |
| 162 | + getText: vi.fn().mockReturnValue(""), |
| 163 | + lineCount: 0, |
| 164 | + }, |
| 165 | + selection: { |
| 166 | + active: { line: 0, character: 0 }, |
| 167 | + anchor: { line: 0, character: 0 }, |
| 168 | + }, |
| 169 | + edit: vi.fn().mockResolvedValue(true), |
| 170 | + revealRange: vi.fn(), |
| 171 | + } |
| 172 | + |
| 173 | + // Track the order of calls |
| 174 | + const callOrder: string[] = [] |
| 175 | + |
| 176 | + // Mock showTextDocument to track when it's called |
| 177 | + vi.mocked(vscode.window.showTextDocument).mockImplementation(async (uri, options) => { |
| 178 | + callOrder.push("showTextDocument") |
| 179 | + expect(options).toEqual({ preview: false, viewColumn: vscode.ViewColumn.Active }) |
| 180 | + return mockEditor as any |
| 181 | + }) |
| 182 | + |
| 183 | + // Mock executeCommand to track when it's called |
| 184 | + vi.mocked(vscode.commands.executeCommand).mockImplementation(async (command) => { |
| 185 | + callOrder.push("executeCommand") |
| 186 | + expect(command).toBe("vscode.diff") |
| 187 | + return undefined |
| 188 | + }) |
| 189 | + |
| 190 | + // Mock workspace.onDidOpenTextDocument to trigger immediately |
| 191 | + vi.mocked(vscode.workspace.onDidOpenTextDocument).mockImplementation((callback) => { |
| 192 | + // Trigger the callback immediately with the document |
| 193 | + setTimeout(() => { |
| 194 | + callback({ uri: { fsPath: `${mockCwd}/test.md` } } as any) |
| 195 | + }, 0) |
| 196 | + return { dispose: vi.fn() } |
| 197 | + }) |
| 198 | + |
| 199 | + // Mock window.visibleTextEditors to return our editor |
| 200 | + vi.mocked(vscode.window).visibleTextEditors = [mockEditor as any] |
| 201 | + |
| 202 | + // Set up for file |
| 203 | + ;(diffViewProvider as any).editType = "modify" |
| 204 | + |
| 205 | + // Execute open |
| 206 | + await diffViewProvider.open("test.md") |
| 207 | + |
| 208 | + // Verify that showTextDocument was called before executeCommand |
| 209 | + expect(callOrder).toEqual(["showTextDocument", "executeCommand"]) |
| 210 | + |
| 211 | + // Verify that showTextDocument was called with preview: false |
| 212 | + expect(vscode.window.showTextDocument).toHaveBeenCalledWith( |
| 213 | + expect.objectContaining({ fsPath: `${mockCwd}/test.md` }), |
| 214 | + { preview: false, viewColumn: vscode.ViewColumn.Active }, |
| 215 | + ) |
| 216 | + |
| 217 | + // Verify that the diff command was executed |
| 218 | + expect(vscode.commands.executeCommand).toHaveBeenCalledWith( |
| 219 | + "vscode.diff", |
| 220 | + expect.any(Object), |
| 221 | + expect.any(Object), |
| 222 | + `test.md: ${DIFF_VIEW_LABEL_CHANGES} (Editable)`, |
| 223 | + { preserveFocus: true }, |
| 224 | + ) |
| 225 | + }) |
| 226 | + |
| 227 | + it("should handle showTextDocument failure", async () => { |
| 228 | + // Mock showTextDocument to fail |
| 229 | + vi.mocked(vscode.window.showTextDocument).mockRejectedValue(new Error("Cannot open file")) |
| 230 | + |
| 231 | + // Mock workspace.onDidOpenTextDocument |
| 232 | + vi.mocked(vscode.workspace.onDidOpenTextDocument).mockReturnValue({ dispose: vi.fn() }) |
| 233 | + |
| 234 | + // Mock window.onDidChangeVisibleTextEditors |
| 235 | + vi.mocked(vscode.window.onDidChangeVisibleTextEditors).mockReturnValue({ dispose: vi.fn() }) |
| 236 | + |
| 237 | + // Set up for file |
| 238 | + ;(diffViewProvider as any).editType = "modify" |
| 239 | + |
| 240 | + // Try to open and expect rejection |
| 241 | + await expect(diffViewProvider.open("test.md")).rejects.toThrow( |
| 242 | + "Failed to execute diff command for /mock/cwd/test.md: Cannot open file", |
| 243 | + ) |
| 244 | + }) |
| 245 | + }) |
| 246 | + |
| 247 | + describe("closeAllDiffViews method", () => { |
| 248 | + it("should close diff views including those identified by label", async () => { |
| 249 | + // Mock tab groups with various types of tabs |
| 250 | + const mockTabs = [ |
| 251 | + // Normal diff view |
| 252 | + { |
| 253 | + input: { |
| 254 | + constructor: { name: "TabInputTextDiff" }, |
| 255 | + original: { scheme: DIFF_VIEW_URI_SCHEME }, |
| 256 | + modified: { fsPath: "/test/file1.ts" }, |
| 257 | + }, |
| 258 | + label: `file1.ts: ${DIFF_VIEW_LABEL_CHANGES} (Editable)`, |
| 259 | + isDirty: false, |
| 260 | + }, |
| 261 | + // Diff view identified by label (for pre-opened files) |
| 262 | + { |
| 263 | + input: { |
| 264 | + constructor: { name: "TabInputTextDiff" }, |
| 265 | + original: { scheme: "file" }, // Different scheme due to pre-opening |
| 266 | + modified: { fsPath: "/test/file2.md" }, |
| 267 | + }, |
| 268 | + label: `file2.md: ${DIFF_VIEW_LABEL_CHANGES} (Editable)`, |
| 269 | + isDirty: false, |
| 270 | + }, |
| 271 | + // Regular file tab (should not be closed) |
| 272 | + { |
| 273 | + input: { |
| 274 | + constructor: { name: "TabInputText" }, |
| 275 | + uri: { fsPath: "/test/file3.js" }, |
| 276 | + }, |
| 277 | + label: "file3.js", |
| 278 | + isDirty: false, |
| 279 | + }, |
| 280 | + // Dirty diff view (should not be closed) |
| 281 | + { |
| 282 | + input: { |
| 283 | + constructor: { name: "TabInputTextDiff" }, |
| 284 | + original: { scheme: DIFF_VIEW_URI_SCHEME }, |
| 285 | + modified: { fsPath: "/test/file4.ts" }, |
| 286 | + }, |
| 287 | + label: `file4.ts: ${DIFF_VIEW_LABEL_CHANGES} (Editable)`, |
| 288 | + isDirty: true, |
| 289 | + }, |
| 290 | + ] |
| 291 | + |
| 292 | + // Make tabs appear as TabInputTextDiff instances |
| 293 | + mockTabs.forEach((tab) => { |
| 294 | + if (tab.input.constructor.name === "TabInputTextDiff") { |
| 295 | + Object.setPrototypeOf(tab.input, vscode.TabInputTextDiff.prototype) |
| 296 | + } |
| 297 | + }) |
| 298 | + |
| 299 | + // Mock the tabGroups getter |
| 300 | + Object.defineProperty(vscode.window.tabGroups, "all", { |
| 301 | + get: () => [ |
| 302 | + { |
| 303 | + tabs: mockTabs as any, |
| 304 | + }, |
| 305 | + ], |
| 306 | + configurable: true, |
| 307 | + }) |
| 308 | + |
| 309 | + const closedTabs: any[] = [] |
| 310 | + vi.mocked(vscode.window.tabGroups.close).mockImplementation((tab) => { |
| 311 | + closedTabs.push(tab) |
| 312 | + return Promise.resolve(true) |
| 313 | + }) |
| 314 | + |
| 315 | + // Execute closeAllDiffViews |
| 316 | + await (diffViewProvider as any).closeAllDiffViews() |
| 317 | + |
| 318 | + // Verify that only the appropriate tabs were closed |
| 319 | + expect(closedTabs).toHaveLength(2) |
| 320 | + expect(closedTabs[0].label).toBe(`file1.ts: ${DIFF_VIEW_LABEL_CHANGES} (Editable)`) |
| 321 | + expect(closedTabs[1].label).toBe(`file2.md: ${DIFF_VIEW_LABEL_CHANGES} (Editable)`) |
| 322 | + |
| 323 | + // Verify that the regular file and dirty diff were not closed |
| 324 | + expect(closedTabs.find((t) => t.label === "file3.js")).toBeUndefined() |
| 325 | + expect( |
| 326 | + closedTabs.find((t) => t.label === `file4.ts: ${DIFF_VIEW_LABEL_CHANGES} (Editable)` && t.isDirty), |
| 327 | + ).toBeUndefined() |
| 328 | + }) |
| 329 | + }) |
96 | 330 | }) |
0 commit comments