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