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/claudeSessionOptionBuilder.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 { afterEach, beforeEach, describe, expect, it } from 'vitest';
7
import type * as vscode from 'vscode';
8
import { ConfigKey, IConfigurationService } from '../../../../platform/configuration/common/configurationService';
9
import { TestWorkspaceService } from '../../../../platform/test/node/testWorkspaceService';
10
import { IWorkspaceService } from '../../../../platform/workspace/common/workspaceService';
11
import { DisposableStore } from '../../../../util/vs/base/common/lifecycle';
12
import { URI } from '../../../../util/vs/base/common/uri';
13
import { createExtensionUnitTestingServices } from '../../../test/node/services';
14
import { FolderRepositoryMRUEntry, IChatFolderMruService } from '../../common/folderRepositoryManager';
15
import { ClaudeSessionOptionBuilder } from '../claudeSessionOptionBuilder';
16
17
class MockChatFolderMruService implements IChatFolderMruService {
18
declare _serviceBrand: undefined;
19
20
private _mruEntries: FolderRepositoryMRUEntry[] = [];
21
22
setMRUEntries(entries: FolderRepositoryMRUEntry[]): void {
23
this._mruEntries = entries;
24
}
25
26
async getRecentlyUsedFolders(): Promise<FolderRepositoryMRUEntry[]> {
27
return this._mruEntries;
28
}
29
30
async deleteRecentlyUsedFolder(): Promise<void> { }
31
}
32
33
describe('ClaudeSessionOptionBuilder', () => {
34
let builder: ClaudeSessionOptionBuilder;
35
let mockFolderMruService: MockChatFolderMruService;
36
const store = new DisposableStore();
37
38
afterEach(() => {
39
store.clear();
40
});
41
42
function createBuilder(workspaceFolders: URI[], configOverrides?: { bypassPermissions?: boolean }): ClaudeSessionOptionBuilder {
43
const workspaceService = new TestWorkspaceService(workspaceFolders);
44
mockFolderMruService = new MockChatFolderMruService();
45
const serviceCollection = store.add(createExtensionUnitTestingServices(store));
46
serviceCollection.set(IWorkspaceService, workspaceService);
47
const accessor = serviceCollection.createTestingAccessor();
48
const configService = accessor.get(IConfigurationService);
49
if (configOverrides?.bypassPermissions) {
50
configService.setConfig(ConfigKey.ClaudeAgentAllowDangerouslySkipPermissions, true);
51
}
52
return new ClaudeSessionOptionBuilder(configService, mockFolderMruService, workspaceService);
53
}
54
55
describe('buildPermissionModeGroup', () => {
56
it('includes default permission modes', () => {
57
builder = createBuilder([URI.file('/project')]);
58
59
const group = builder.buildPermissionModeGroup();
60
61
expect(group.id).toBe('permissionMode');
62
expect(group.items.map(i => i.id)).toEqual(['default', 'acceptEdits', 'plan']);
63
});
64
65
it('includes bypass when config enabled', () => {
66
builder = createBuilder([URI.file('/project')], { bypassPermissions: true });
67
68
const group = builder.buildPermissionModeGroup();
69
70
expect(group.items.map(i => i.id)).toContain('bypassPermissions');
71
});
72
});
73
74
describe('buildNewFolderGroup', () => {
75
it('returns undefined for single-root workspace', async () => {
76
builder = createBuilder([URI.file('/project')]);
77
78
const group = await builder.buildNewFolderGroup();
79
80
expect(group).toBeUndefined();
81
});
82
83
it('returns folder group for multi-root workspace', async () => {
84
const folderA = URI.file('/a');
85
const folderB = URI.file('/b');
86
builder = createBuilder([folderA, folderB]);
87
88
const group = await builder.buildNewFolderGroup();
89
90
expect(group).toBeDefined();
91
expect(group!.id).toBe('folder');
92
expect(group!.items.map(i => i.id)).toEqual([folderA.fsPath, folderB.fsPath]);
93
expect(group!.selected?.id).toBe(folderA.fsPath);
94
});
95
96
it('uses MRU entries for empty workspace', async () => {
97
builder = createBuilder([]);
98
const mruFolder = URI.file('/recent');
99
mockFolderMruService.setMRUEntries([
100
{ folder: mruFolder, repository: undefined, lastAccessed: Date.now() },
101
]);
102
103
const group = await builder.buildNewFolderGroup();
104
105
expect(group).toBeDefined();
106
expect(group!.items[0].id).toBe(mruFolder.fsPath);
107
});
108
});
109
110
describe('buildExistingFolderGroup', () => {
111
it('builds locked folder group', () => {
112
builder = createBuilder([URI.file('/a'), URI.file('/b')]);
113
const folderUri = URI.file('/a');
114
115
const group = builder.buildExistingFolderGroup(folderUri);
116
117
expect(group.id).toBe('folder');
118
expect(group.selected?.locked).toBe(true);
119
expect(group.items.every(i => i.locked)).toBe(true);
120
});
121
});
122
123
describe('buildNewSessionGroups', () => {
124
it('includes permission mode group with default selection', async () => {
125
builder = createBuilder([URI.file('/project')]);
126
127
const groups = await builder.buildNewSessionGroups();
128
129
const permGroup = groups.find(g => g.id === 'permissionMode');
130
expect(permGroup).toBeDefined();
131
expect(permGroup!.selected?.id).toBe('acceptEdits');
132
});
133
134
it('excludes folder group for single-root workspace', async () => {
135
builder = createBuilder([URI.file('/project')]);
136
137
const groups = await builder.buildNewSessionGroups();
138
139
expect(groups.find(g => g.id === 'folder')).toBeUndefined();
140
});
141
142
it('includes folder group for multi-root workspace', async () => {
143
builder = createBuilder([URI.file('/a'), URI.file('/b')]);
144
145
const groups = await builder.buildNewSessionGroups();
146
147
expect(groups.find(g => g.id === 'folder')).toBeDefined();
148
});
149
});
150
151
describe('buildExistingSessionGroups', () => {
152
it('does not lock permission mode items', async () => {
153
builder = createBuilder([URI.file('/project')]);
154
155
const groups = await builder.buildExistingSessionGroups('plan', undefined);
156
157
const permGroup = groups.find(g => g.id === 'permissionMode');
158
expect(permGroup!.selected?.id).toBe('plan');
159
expect(permGroup!.selected?.locked).toBeUndefined();
160
expect(permGroup!.items.every(i => !i.locked)).toBe(true);
161
});
162
163
it('includes locked folder group when folder URI provided', async () => {
164
builder = createBuilder([URI.file('/a'), URI.file('/b')]);
165
const folderUri = URI.file('/a');
166
167
const groups = await builder.buildExistingSessionGroups('acceptEdits', folderUri);
168
169
const folderGroup = groups.find(g => g.id === 'folder');
170
expect(folderGroup).toBeDefined();
171
expect(folderGroup!.selected?.locked).toBe(true);
172
});
173
174
it('excludes folder group when folder URI is undefined', async () => {
175
builder = createBuilder([URI.file('/project')]);
176
177
const groups = await builder.buildExistingSessionGroups('acceptEdits', undefined);
178
179
expect(groups.find(g => g.id === 'folder')).toBeUndefined();
180
});
181
});
182
183
describe('getSelections', () => {
184
beforeEach(() => {
185
builder = createBuilder([URI.file('/a'), URI.file('/b')]);
186
});
187
188
it('extracts permission mode from groups', () => {
189
const groups: vscode.ChatSessionProviderOptionGroup[] = [{
190
id: 'permissionMode',
191
name: 'Permission Mode',
192
description: '',
193
items: [{ id: 'plan', name: 'Plan mode' }],
194
selected: { id: 'plan', name: 'Plan mode' },
195
}];
196
197
const { permissionMode } = builder.getSelections(groups);
198
199
expect(permissionMode).toBe('plan');
200
});
201
202
it('extracts folder URI from groups', () => {
203
const groups: vscode.ChatSessionProviderOptionGroup[] = [{
204
id: 'folder',
205
name: 'Folder',
206
description: '',
207
items: [{ id: '/some/path', name: 'path' }],
208
selected: { id: '/some/path', name: 'path' },
209
}];
210
211
const { folderUri } = builder.getSelections(groups);
212
213
expect(folderUri?.fsPath).toBe(URI.file('/some/path').fsPath);
214
});
215
216
it('updates lastUsedPermissionMode as side effect', () => {
217
expect(builder.lastUsedPermissionMode).toBe('acceptEdits');
218
219
builder.getSelections([{
220
id: 'permissionMode',
221
name: 'Permission Mode',
222
description: '',
223
items: [{ id: 'plan', name: 'Plan mode' }],
224
selected: { id: 'plan', name: 'Plan mode' },
225
}]);
226
227
expect(builder.lastUsedPermissionMode).toBe('plan');
228
});
229
230
it('ignores invalid permission mode', () => {
231
const { permissionMode } = builder.getSelections([{
232
id: 'permissionMode',
233
name: 'Permission Mode',
234
description: '',
235
items: [{ id: 'garbage', name: 'Garbage' }],
236
selected: { id: 'garbage', name: 'Garbage' },
237
}]);
238
239
expect(permissionMode).toBeUndefined();
240
expect(builder.lastUsedPermissionMode).toBe('acceptEdits');
241
});
242
});
243
244
describe('getDefaultFolder', () => {
245
it('returns first workspace folder', async () => {
246
const folderA = URI.file('/a');
247
builder = createBuilder([folderA, URI.file('/b')]);
248
249
const result = await builder.getDefaultFolder();
250
251
expect(result?.fsPath).toBe(folderA.fsPath);
252
});
253
254
it('returns first MRU entry for empty workspace', async () => {
255
builder = createBuilder([]);
256
const mruFolder = URI.file('/recent');
257
mockFolderMruService.setMRUEntries([
258
{ folder: mruFolder, repository: undefined, lastAccessed: Date.now() },
259
]);
260
261
const result = await builder.getDefaultFolder();
262
263
expect(result?.fsPath).toBe(mruFolder.fsPath);
264
});
265
266
it('returns undefined when no folders available', async () => {
267
builder = createBuilder([]);
268
269
const result = await builder.getDefaultFolder();
270
271
expect(result).toBeUndefined();
272
});
273
});
274
});
275
276