Path: blob/main/extensions/copilot/src/extension/chatSessions/vscode-node/test/claudeSessionOptionBuilder.spec.ts
13405 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { afterEach, beforeEach, describe, expect, it } from 'vitest';6import type * as vscode from 'vscode';7import { ConfigKey, IConfigurationService } from '../../../../platform/configuration/common/configurationService';8import { TestWorkspaceService } from '../../../../platform/test/node/testWorkspaceService';9import { IWorkspaceService } from '../../../../platform/workspace/common/workspaceService';10import { DisposableStore } from '../../../../util/vs/base/common/lifecycle';11import { URI } from '../../../../util/vs/base/common/uri';12import { createExtensionUnitTestingServices } from '../../../test/node/services';13import { FolderRepositoryMRUEntry, IChatFolderMruService } from '../../common/folderRepositoryManager';14import { ClaudeSessionOptionBuilder } from '../claudeSessionOptionBuilder';1516class MockChatFolderMruService implements IChatFolderMruService {17declare _serviceBrand: undefined;1819private _mruEntries: FolderRepositoryMRUEntry[] = [];2021setMRUEntries(entries: FolderRepositoryMRUEntry[]): void {22this._mruEntries = entries;23}2425async getRecentlyUsedFolders(): Promise<FolderRepositoryMRUEntry[]> {26return this._mruEntries;27}2829async deleteRecentlyUsedFolder(): Promise<void> { }30}3132describe('ClaudeSessionOptionBuilder', () => {33let builder: ClaudeSessionOptionBuilder;34let mockFolderMruService: MockChatFolderMruService;35const store = new DisposableStore();3637afterEach(() => {38store.clear();39});4041function createBuilder(workspaceFolders: URI[], configOverrides?: { bypassPermissions?: boolean }): ClaudeSessionOptionBuilder {42const workspaceService = new TestWorkspaceService(workspaceFolders);43mockFolderMruService = new MockChatFolderMruService();44const serviceCollection = store.add(createExtensionUnitTestingServices(store));45serviceCollection.set(IWorkspaceService, workspaceService);46const accessor = serviceCollection.createTestingAccessor();47const configService = accessor.get(IConfigurationService);48if (configOverrides?.bypassPermissions) {49configService.setConfig(ConfigKey.ClaudeAgentAllowDangerouslySkipPermissions, true);50}51return new ClaudeSessionOptionBuilder(configService, mockFolderMruService, workspaceService);52}5354describe('buildPermissionModeGroup', () => {55it('includes default permission modes', () => {56builder = createBuilder([URI.file('/project')]);5758const group = builder.buildPermissionModeGroup();5960expect(group.id).toBe('permissionMode');61expect(group.items.map(i => i.id)).toEqual(['default', 'acceptEdits', 'plan']);62});6364it('includes bypass when config enabled', () => {65builder = createBuilder([URI.file('/project')], { bypassPermissions: true });6667const group = builder.buildPermissionModeGroup();6869expect(group.items.map(i => i.id)).toContain('bypassPermissions');70});71});7273describe('buildNewFolderGroup', () => {74it('returns undefined for single-root workspace', async () => {75builder = createBuilder([URI.file('/project')]);7677const group = await builder.buildNewFolderGroup();7879expect(group).toBeUndefined();80});8182it('returns folder group for multi-root workspace', async () => {83const folderA = URI.file('/a');84const folderB = URI.file('/b');85builder = createBuilder([folderA, folderB]);8687const group = await builder.buildNewFolderGroup();8889expect(group).toBeDefined();90expect(group!.id).toBe('folder');91expect(group!.items.map(i => i.id)).toEqual([folderA.fsPath, folderB.fsPath]);92expect(group!.selected?.id).toBe(folderA.fsPath);93});9495it('uses MRU entries for empty workspace', async () => {96builder = createBuilder([]);97const mruFolder = URI.file('/recent');98mockFolderMruService.setMRUEntries([99{ folder: mruFolder, repository: undefined, lastAccessed: Date.now() },100]);101102const group = await builder.buildNewFolderGroup();103104expect(group).toBeDefined();105expect(group!.items[0].id).toBe(mruFolder.fsPath);106});107});108109describe('buildExistingFolderGroup', () => {110it('builds locked folder group', () => {111builder = createBuilder([URI.file('/a'), URI.file('/b')]);112const folderUri = URI.file('/a');113114const group = builder.buildExistingFolderGroup(folderUri);115116expect(group.id).toBe('folder');117expect(group.selected?.locked).toBe(true);118expect(group.items.every(i => i.locked)).toBe(true);119});120});121122describe('buildNewSessionGroups', () => {123it('includes permission mode group with default selection', async () => {124builder = createBuilder([URI.file('/project')]);125126const groups = await builder.buildNewSessionGroups();127128const permGroup = groups.find(g => g.id === 'permissionMode');129expect(permGroup).toBeDefined();130expect(permGroup!.selected?.id).toBe('acceptEdits');131});132133it('excludes folder group for single-root workspace', async () => {134builder = createBuilder([URI.file('/project')]);135136const groups = await builder.buildNewSessionGroups();137138expect(groups.find(g => g.id === 'folder')).toBeUndefined();139});140141it('includes folder group for multi-root workspace', async () => {142builder = createBuilder([URI.file('/a'), URI.file('/b')]);143144const groups = await builder.buildNewSessionGroups();145146expect(groups.find(g => g.id === 'folder')).toBeDefined();147});148});149150describe('buildExistingSessionGroups', () => {151it('does not lock permission mode items', async () => {152builder = createBuilder([URI.file('/project')]);153154const groups = await builder.buildExistingSessionGroups('plan', undefined);155156const permGroup = groups.find(g => g.id === 'permissionMode');157expect(permGroup!.selected?.id).toBe('plan');158expect(permGroup!.selected?.locked).toBeUndefined();159expect(permGroup!.items.every(i => !i.locked)).toBe(true);160});161162it('includes locked folder group when folder URI provided', async () => {163builder = createBuilder([URI.file('/a'), URI.file('/b')]);164const folderUri = URI.file('/a');165166const groups = await builder.buildExistingSessionGroups('acceptEdits', folderUri);167168const folderGroup = groups.find(g => g.id === 'folder');169expect(folderGroup).toBeDefined();170expect(folderGroup!.selected?.locked).toBe(true);171});172173it('excludes folder group when folder URI is undefined', async () => {174builder = createBuilder([URI.file('/project')]);175176const groups = await builder.buildExistingSessionGroups('acceptEdits', undefined);177178expect(groups.find(g => g.id === 'folder')).toBeUndefined();179});180});181182describe('getSelections', () => {183beforeEach(() => {184builder = createBuilder([URI.file('/a'), URI.file('/b')]);185});186187it('extracts permission mode from groups', () => {188const groups: vscode.ChatSessionProviderOptionGroup[] = [{189id: 'permissionMode',190name: 'Permission Mode',191description: '',192items: [{ id: 'plan', name: 'Plan mode' }],193selected: { id: 'plan', name: 'Plan mode' },194}];195196const { permissionMode } = builder.getSelections(groups);197198expect(permissionMode).toBe('plan');199});200201it('extracts folder URI from groups', () => {202const groups: vscode.ChatSessionProviderOptionGroup[] = [{203id: 'folder',204name: 'Folder',205description: '',206items: [{ id: '/some/path', name: 'path' }],207selected: { id: '/some/path', name: 'path' },208}];209210const { folderUri } = builder.getSelections(groups);211212expect(folderUri?.fsPath).toBe(URI.file('/some/path').fsPath);213});214215it('updates lastUsedPermissionMode as side effect', () => {216expect(builder.lastUsedPermissionMode).toBe('acceptEdits');217218builder.getSelections([{219id: 'permissionMode',220name: 'Permission Mode',221description: '',222items: [{ id: 'plan', name: 'Plan mode' }],223selected: { id: 'plan', name: 'Plan mode' },224}]);225226expect(builder.lastUsedPermissionMode).toBe('plan');227});228229it('ignores invalid permission mode', () => {230const { permissionMode } = builder.getSelections([{231id: 'permissionMode',232name: 'Permission Mode',233description: '',234items: [{ id: 'garbage', name: 'Garbage' }],235selected: { id: 'garbage', name: 'Garbage' },236}]);237238expect(permissionMode).toBeUndefined();239expect(builder.lastUsedPermissionMode).toBe('acceptEdits');240});241});242243describe('getDefaultFolder', () => {244it('returns first workspace folder', async () => {245const folderA = URI.file('/a');246builder = createBuilder([folderA, URI.file('/b')]);247248const result = await builder.getDefaultFolder();249250expect(result?.fsPath).toBe(folderA.fsPath);251});252253it('returns first MRU entry for empty workspace', async () => {254builder = createBuilder([]);255const mruFolder = URI.file('/recent');256mockFolderMruService.setMRUEntries([257{ folder: mruFolder, repository: undefined, lastAccessed: Date.now() },258]);259260const result = await builder.getDefaultFolder();261262expect(result?.fsPath).toBe(mruFolder.fsPath);263});264265it('returns undefined when no folders available', async () => {266builder = createBuilder([]);267268const result = await builder.getDefaultFolder();269270expect(result).toBeUndefined();271});272});273});274275276