7.7 KB241 lines
Blame
1/**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7
8import {Set as ImSet} from 'immutable';
9import type {Repository} from 'isl-server/src/Repository';
10import {repositoryCache} from 'isl-server/src/RepositoryCache';
11import fs from 'node:fs';
12import {ComparisonType, type Comparison} from 'shared/Comparison';
13import * as vscode from 'vscode';
14import {vscodeCommands} from '../commands';
15import {shouldOpenBeside} from '../config';
16import {encodeDeletedFileUri} from '../DeletedFileContentProvider';
17import {encodeSaplingDiffUri} from '../DiffContentProvider';
18
19// Mock vscode command
20jest.mock('vscode', () => {
21 const actualVscode = jest.requireActual('../../__mocks__/vscode');
22 return {
23 ...actualVscode,
24 commands: {
25 executeCommand: jest.fn(),
26 },
27 };
28});
29const mockExecuteVSCodeCommand = vscode.commands.executeCommand as jest.MockedFunction<
30 typeof vscode.commands.executeCommand
31>;
32
33// Mock fs access
34jest.mock('node:fs', () => ({
35 promises: {
36 access: jest.fn(),
37 },
38}));
39const mockFsAccess = fs.promises.access as jest.MockedFunction<typeof fs.promises.access>;
40
41// Mock global config
42jest.mock('../config', () => ({
43 shouldOpenBeside: jest.fn(),
44}));
45const mockShouldOpenBeside = shouldOpenBeside as jest.MockedFunction<typeof shouldOpenBeside>;
46
47describe('open-file-diff', () => {
48 const openDiffView = vscodeCommands['sapling.open-file-diff'];
49
50 const repoRoot = '/repo/root';
51 const filePath = 'path/to/file';
52 const submodulePath = 'path/to/submodule';
53 const fileUri = vscode.Uri.file(`${repoRoot}/${filePath}`);
54 const submoduleUri = vscode.Uri.file(`${repoRoot}/${submodulePath}`);
55
56 // Create a proper mock repository
57 const mockRepo = {
58 info: {
59 repoRoot,
60 },
61 getSubmodulePathCache: jest.fn(),
62 } as unknown as jest.Mocked<Repository>;
63
64 beforeEach(() => {
65 jest.clearAllMocks();
66
67 jest.spyOn(repositoryCache, 'cachedRepositoryForPath').mockReturnValue(mockRepo);
68 mockRepo.getSubmodulePathCache.mockReturnValue(ImSet([submodulePath]));
69 mockShouldOpenBeside.mockReturnValue(false);
70 });
71
72 it('uncommitted changes, regular file', async () => {
73 mockFsAccess.mockResolvedValue(undefined); // File exists
74
75 const comparison: Comparison = {type: ComparisonType.UncommittedChanges};
76 await openDiffView(fileUri, comparison);
77
78 const expectedLeftRev = '.';
79 const expectedLeftUri = encodeSaplingDiffUri(fileUri, expectedLeftRev);
80
81 expect(mockExecuteVSCodeCommand).toHaveBeenCalledWith(
82 'vscode.diff',
83 expectedLeftUri,
84 fileUri,
85 'file (Uncommitted Changes)',
86 {viewColumn: undefined},
87 );
88 });
89
90 it('uncommitted changes, submodule', async () => {
91 mockFsAccess.mockRejectedValue(undefined); // Path exists
92
93 const comparison: Comparison = {type: ComparisonType.UncommittedChanges};
94 await openDiffView(submoduleUri, comparison);
95
96 const expectedLeftRev = '.';
97 const expectedLeftUri = encodeSaplingDiffUri(submoduleUri, expectedLeftRev);
98 const expectedRightRev = 'wdir()';
99 const expectedRightUri = encodeSaplingDiffUri(submoduleUri, expectedRightRev);
100
101 expect(mockExecuteVSCodeCommand).toHaveBeenCalledWith(
102 'vscode.diff',
103 expectedLeftUri,
104 expectedRightUri,
105 'submodule (Uncommitted Changes)',
106 {viewColumn: undefined},
107 );
108 });
109
110 it('uncommitted changes, file deleted', async () => {
111 mockFsAccess.mockRejectedValue(new Error('File not found')); // File doesn't exist
112
113 const comparison: Comparison = {type: ComparisonType.UncommittedChanges};
114 await openDiffView(fileUri, comparison);
115
116 const expectedLeftRev = '.';
117 const expectedLeftUri = encodeSaplingDiffUri(fileUri, expectedLeftRev);
118 const expectedRightUri = encodeDeletedFileUri(fileUri);
119
120 expect(mockExecuteVSCodeCommand).toHaveBeenCalledWith(
121 'vscode.diff',
122 expectedLeftUri,
123 expectedRightUri,
124 'file (Uncommitted Changes)',
125 {viewColumn: undefined},
126 );
127 });
128
129 it('head changes, regular file', async () => {
130 mockFsAccess.mockResolvedValue(undefined); // File exists
131
132 const comparison: Comparison = {type: ComparisonType.HeadChanges};
133 await openDiffView(fileUri, comparison);
134
135 const expectedLeftRev = '.^';
136 const expectedLeftUri = encodeSaplingDiffUri(fileUri, expectedLeftRev);
137
138 expect(mockExecuteVSCodeCommand).toHaveBeenCalledWith(
139 'vscode.diff',
140 expectedLeftUri,
141 fileUri,
142 'file (Head Changes)',
143 {viewColumn: undefined},
144 );
145 });
146
147 it('head changes, submodule', async () => {
148 mockFsAccess.mockRejectedValue(undefined); // Path exists
149
150 const comparison: Comparison = {type: ComparisonType.HeadChanges};
151 await openDiffView(submoduleUri, comparison);
152
153 const expectedLeftRev = '.^';
154 const expectedLeftUri = encodeSaplingDiffUri(submoduleUri, expectedLeftRev);
155 const expectedRightRev = 'wdir()';
156 const expectedRightUri = encodeSaplingDiffUri(submoduleUri, expectedRightRev);
157
158 expect(mockExecuteVSCodeCommand).toHaveBeenCalledWith(
159 'vscode.diff',
160 expectedLeftUri,
161 expectedRightUri,
162 'submodule (Head Changes)',
163 {viewColumn: undefined},
164 );
165 });
166
167 it('stack changes, regular file', async () => {
168 mockFsAccess.mockResolvedValue(undefined); // File exists
169
170 const comparison: Comparison = {type: ComparisonType.StackChanges};
171 await openDiffView(fileUri, comparison);
172
173 const expectedLeftRev = 'ancestor(.,interestingmaster())';
174 const expectedLeftUri = encodeSaplingDiffUri(fileUri, expectedLeftRev);
175
176 expect(mockExecuteVSCodeCommand).toHaveBeenCalledWith(
177 'vscode.diff',
178 expectedLeftUri,
179 fileUri,
180 'file (Stack Changes)',
181 {viewColumn: undefined},
182 );
183 });
184
185 it('stack changes, submodule', async () => {
186 mockFsAccess.mockRejectedValue(undefined); // Path exists
187
188 const comparison: Comparison = {type: ComparisonType.StackChanges};
189 await openDiffView(submoduleUri, comparison);
190
191 const expectedLeftRev = 'ancestor(.,interestingmaster())';
192 const expectedLeftUri = encodeSaplingDiffUri(submoduleUri, expectedLeftRev);
193 const expectedRightRev = 'wdir()';
194 const expectedRightUri = encodeSaplingDiffUri(submoduleUri, expectedRightRev);
195
196 expect(mockExecuteVSCodeCommand).toHaveBeenCalledWith(
197 'vscode.diff',
198 expectedLeftUri,
199 expectedRightUri,
200 'submodule (Stack Changes)',
201 {viewColumn: undefined},
202 );
203 });
204
205 it('committed changes, regular file', async () => {
206 const comparison: Comparison = {type: ComparisonType.Committed, hash: 'abc123'};
207 await openDiffView(fileUri, comparison);
208
209 const expectedLeftRev = 'abc123^';
210 const expectedLeftUri = encodeSaplingDiffUri(fileUri, expectedLeftRev);
211 const expectedRightRev = 'abc123';
212 const expectedRightUri = encodeSaplingDiffUri(fileUri, expectedRightRev);
213
214 expect(mockExecuteVSCodeCommand).toHaveBeenCalledWith(
215 'vscode.diff',
216 expectedLeftUri,
217 expectedRightUri,
218 'file (In abc123)',
219 {viewColumn: undefined},
220 );
221 });
222
223 it('committed changes, submodule', async () => {
224 const comparison: Comparison = {type: ComparisonType.Committed, hash: 'abc123'};
225 await openDiffView(submoduleUri, comparison);
226
227 const expectedLeftRev = 'abc123^';
228 const expectedLeftUri = encodeSaplingDiffUri(submoduleUri, expectedLeftRev);
229 const expectedRightRev = 'abc123';
230 const expectedRightUri = encodeSaplingDiffUri(submoduleUri, expectedRightRev);
231
232 expect(mockExecuteVSCodeCommand).toHaveBeenCalledWith(
233 'vscode.diff',
234 expectedLeftUri,
235 expectedRightUri,
236 'submodule (In abc123)',
237 {viewColumn: undefined},
238 );
239 });
240});
241