Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/extension/chatSessions/vscode-node/test/claudeWorkspaceFolderService.spec.ts
13405 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { beforeEach, describe, expect, it, vi } from 'vitest';
7
import * as vscode from 'vscode';
8
import { IVSCodeExtensionContext } from '../../../../platform/extContext/common/extensionContext';
9
import { MockFileSystemService } from '../../../../platform/filesystem/node/test/mockFileSystemService';
10
import { RepoContext } from '../../../../platform/git/common/gitService';
11
import { MockGitService } from '../../../../platform/ignore/node/test/mockGitService';
12
import { ILogService } from '../../../../platform/log/common/logService';
13
import { mock } from '../../../../util/common/test/simpleMock';
14
import { constObservable } from '../../../../util/vs/base/common/observableInternal';
15
import { URI } from '../../../../util/vs/base/common/uri';
16
import { ClaudeWorkspaceFolderService } from '../claudeWorkspaceFolderServiceImpl';
17
18
class MockLogService extends mock<ILogService>() {
19
override trace = vi.fn();
20
override info = vi.fn();
21
override warn = vi.fn();
22
override error = vi.fn();
23
override debug = vi.fn();
24
}
25
26
class MockExtensionContext extends mock<IVSCodeExtensionContext>() {
27
override globalStorageUri = vscode.Uri.file('/mock/global/storage');
28
}
29
30
function createMockRepoContext(overrides?: Partial<RepoContext>): RepoContext {
31
return {
32
rootUri: URI.file('/mock/repo'),
33
kind: 0 as any,
34
isUsingVirtualFileSystem: false,
35
headIncomingChanges: undefined,
36
headOutgoingChanges: undefined,
37
headBranchName: 'feature-branch',
38
headCommitHash: 'abc123',
39
upstreamBranchName: undefined,
40
upstreamRemote: undefined,
41
isRebasing: false,
42
remotes: [],
43
worktrees: [],
44
changes: {
45
mergeChanges: [],
46
indexChanges: [],
47
workingTree: [],
48
untrackedChanges: [],
49
},
50
headBranchNameObs: constObservable('feature-branch'),
51
headCommitHashObs: constObservable('abc123'),
52
upstreamBranchNameObs: constObservable(undefined),
53
upstreamRemoteObs: constObservable(undefined),
54
isRebasingObs: constObservable(false),
55
isIgnored: vi.fn().mockResolvedValue(false),
56
...overrides,
57
};
58
}
59
60
describe('ClaudeWorkspaceFolderService', () => {
61
let gitService: MockGitService;
62
let logService: MockLogService;
63
let extensionContext: MockExtensionContext;
64
let fileSystemService: MockFileSystemService;
65
let service: ClaudeWorkspaceFolderService;
66
67
beforeEach(() => {
68
gitService = new MockGitService();
69
logService = new MockLogService();
70
extensionContext = new MockExtensionContext();
71
fileSystemService = new MockFileSystemService();
72
service = new ClaudeWorkspaceFolderService(gitService, logService, extensionContext, fileSystemService);
73
});
74
75
describe('getWorkspaceChanges', () => {
76
it('returns empty array when repository is not found', async () => {
77
gitService.getRepository = vi.fn().mockResolvedValue(undefined);
78
79
const result = await service.getWorkspaceChanges('/nonexistent', 'main', undefined);
80
81
expect(result).toEqual([]);
82
expect(logService.warn).toHaveBeenCalled();
83
});
84
85
it('returns empty array when repository has no changes object', async () => {
86
gitService.getRepository = vi.fn().mockResolvedValue(
87
createMockRepoContext({ changes: undefined }),
88
);
89
90
const result = await service.getWorkspaceChanges('/mock/repo', 'main', undefined);
91
92
expect(result).toEqual([]);
93
});
94
95
it('returns cached result on second call with same inputs', async () => {
96
const repo = createMockRepoContext();
97
gitService.getRepository = vi.fn().mockResolvedValue(repo);
98
gitService.exec = vi.fn().mockResolvedValue('');
99
100
const result1 = await service.getWorkspaceChanges('/mock/repo', 'feature-branch', undefined);
101
const result2 = await service.getWorkspaceChanges('/mock/repo', 'feature-branch', undefined);
102
103
expect(result1).toBe(result2);
104
expect(gitService.exec).toHaveBeenCalledTimes(1);
105
});
106
107
it('bypasses cache when forceRefresh is true', async () => {
108
const repo = createMockRepoContext();
109
gitService.getRepository = vi.fn().mockResolvedValue(repo);
110
gitService.exec = vi.fn().mockResolvedValue('');
111
112
await service.getWorkspaceChanges('/mock/repo', 'feature-branch', undefined);
113
await service.getWorkspaceChanges('/mock/repo', 'feature-branch', undefined, true);
114
115
expect(gitService.exec).toHaveBeenCalledTimes(2);
116
});
117
118
it('returns empty array on git exec error', async () => {
119
const repo = createMockRepoContext();
120
gitService.getRepository = vi.fn().mockResolvedValue(repo);
121
gitService.exec = vi.fn().mockRejectedValue(new Error('git failed'));
122
123
const result = await service.getWorkspaceChanges('/mock/repo', 'feature-branch', undefined);
124
125
expect(result).toEqual([]);
126
expect(logService.error).toHaveBeenCalled();
127
});
128
});
129
130
describe('base branch auto-resolution', () => {
131
it('calls getBranchBase when gitBaseBranch is undefined and gitBranch is provided', async () => {
132
const repo = createMockRepoContext();
133
gitService.getRepository = vi.fn().mockResolvedValue(repo);
134
gitService.getBranchBase = vi.fn().mockResolvedValue({ name: 'main', commit: 'def456', type: 0 });
135
gitService.exec = vi.fn().mockResolvedValue('');
136
gitService.getMergeBase = vi.fn().mockResolvedValue('def456');
137
138
await service.getWorkspaceChanges('/mock/repo', 'feature-branch', undefined);
139
140
expect(gitService.getBranchBase).toHaveBeenCalledWith(repo.rootUri, 'feature-branch');
141
expect(gitService.exec).toHaveBeenCalledWith(
142
repo.rootUri,
143
expect.arrayContaining(['--merge-base', 'main']),
144
);
145
});
146
147
it('does not call getBranchBase when gitBaseBranch is explicitly provided', async () => {
148
const repo = createMockRepoContext();
149
gitService.getRepository = vi.fn().mockResolvedValue(repo);
150
gitService.getBranchBase = vi.fn();
151
gitService.exec = vi.fn().mockResolvedValue('');
152
153
await service.getWorkspaceChanges('/mock/repo', 'feature-branch', 'develop');
154
155
expect(gitService.getBranchBase).not.toHaveBeenCalled();
156
expect(gitService.exec).toHaveBeenCalledWith(
157
repo.rootUri,
158
expect.arrayContaining(['--merge-base', 'develop']),
159
);
160
});
161
162
it('handles getBranchBase returning undefined gracefully', async () => {
163
const repo = createMockRepoContext();
164
gitService.getRepository = vi.fn().mockResolvedValue(repo);
165
gitService.getBranchBase = vi.fn().mockResolvedValue(undefined);
166
gitService.exec = vi.fn().mockResolvedValue('');
167
168
const result = await service.getWorkspaceChanges('/mock/repo', 'feature-branch', undefined);
169
170
expect(result).toEqual([]);
171
expect(gitService.exec).toHaveBeenCalledWith(
172
repo.rootUri,
173
expect.not.arrayContaining(['--merge-base']),
174
);
175
});
176
177
it('handles getBranchBase throwing an error gracefully', async () => {
178
const repo = createMockRepoContext();
179
gitService.getRepository = vi.fn().mockResolvedValue(repo);
180
gitService.getBranchBase = vi.fn().mockRejectedValue(new Error('branch not found'));
181
gitService.exec = vi.fn().mockResolvedValue('');
182
183
const result = await service.getWorkspaceChanges('/mock/repo', 'feature-branch', undefined);
184
185
expect(result).toEqual([]);
186
expect(logService.warn).toHaveBeenCalledWith(
187
expect.stringContaining('Failed to resolve base branch'),
188
);
189
});
190
191
it('does not call getBranchBase when headCommitHash is undefined', async () => {
192
const repo = createMockRepoContext({ headCommitHash: undefined });
193
gitService.getRepository = vi.fn().mockResolvedValue(repo);
194
gitService.getBranchBase = vi.fn();
195
gitService.exec = vi.fn().mockResolvedValue('');
196
197
await service.getWorkspaceChanges('/mock/repo', 'feature-branch', undefined);
198
199
expect(gitService.getBranchBase).not.toHaveBeenCalled();
200
});
201
});
202
203
describe('dispose', () => {
204
it('clears the cache on dispose', async () => {
205
const repo = createMockRepoContext();
206
gitService.getRepository = vi.fn().mockResolvedValue(repo);
207
gitService.exec = vi.fn().mockResolvedValue('');
208
209
await service.getWorkspaceChanges('/mock/repo', 'feature-branch', undefined);
210
211
service.dispose();
212
213
gitService.exec = vi.fn().mockResolvedValue('');
214
await service.getWorkspaceChanges('/mock/repo', 'feature-branch', undefined);
215
216
expect(gitService.exec).toHaveBeenCalledTimes(1);
217
});
218
});
219
});
220
221