Path: blob/main/src/vs/sessions/contrib/copilotChatSessions/test/browser/claudePermissionModePicker.test.ts
13406 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 assert from 'assert';6import { Event } from '../../../../../base/common/event.js';7import { DisposableStore } from '../../../../../base/common/lifecycle.js';8import { observableValue } from '../../../../../base/common/observable.js';9import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js';10import { IActionWidgetService } from '../../../../../platform/actionWidget/browser/actionWidget.js';11import { IActionListItem } from '../../../../../platform/actionWidget/browser/actionList.js';12import { TestInstantiationService } from '../../../../../platform/instantiation/test/common/instantiationServiceMock.js';13import { ISessionsProvidersService } from '../../../../services/sessions/browser/sessionsProvidersService.js';14import { IActiveSession, ISessionsManagementService } from '../../../../services/sessions/common/sessionsManagement.js';15import { CopilotChatSessionsProvider, ICopilotChatSession } from '../../browser/copilotChatSessionsProvider.js';16import { ClaudePermissionModePicker } from '../../browser/claudePermissionModePicker.js';1718interface IPermissionModeItem {19readonly id: string;20readonly label: string;21}2223function showPicker(container: HTMLElement): void {24const trigger = container.querySelector<HTMLElement>('a.action-label');25assert.ok(trigger);26trigger.click();27}2829function createPicker(30disposables: DisposableStore,31opts?: {32setOptionSpy?: (optionId: string, value: { id: string; name: string }) => void;33hasActiveSession?: boolean;34},35): { picker: ClaudePermissionModePicker; actionWidgetItems: IActionListItem<IPermissionModeItem>[]; onSelect: (item: IPermissionModeItem) => void } {36const instantiationService = disposables.add(new TestInstantiationService());37const actionWidgetItems: IActionListItem<IPermissionModeItem>[] = [];38let capturedOnSelect: ((item: IPermissionModeItem) => void) | undefined;3940const setOptionSpy = opts?.setOptionSpy ?? (() => { });41const hasActiveSession = opts?.hasActiveSession ?? true;4243const activeSession = hasActiveSession ? {44providerId: 'default-copilot',45sessionId: 'session-id',46loading: observableValue('loading', false),47} as unknown as IActiveSession : undefined;4849const mockSession: Partial<ICopilotChatSession> = {50setOption: setOptionSpy as ICopilotChatSession['setOption'],51};5253const provider = Object.assign(Object.create(CopilotChatSessionsProvider.prototype), {54getSession: () => mockSession,55});5657instantiationService.stub(IActionWidgetService, {58isVisible: false,59hide: () => { },60show: <T>(_id: string, _supportsPreview: boolean, items: IActionListItem<T>[], delegate: { onSelect: (item: T) => void }) => {61actionWidgetItems.splice(0, actionWidgetItems.length, ...(items as IActionListItem<IPermissionModeItem>[]));62capturedOnSelect = delegate.onSelect as (item: IPermissionModeItem) => void;63},64});65instantiationService.stub(ISessionsManagementService, {66activeSession: observableValue<IActiveSession | undefined>('activeSession', activeSession),67} as unknown as ISessionsManagementService);68instantiationService.stub(ISessionsProvidersService, {69onDidChangeProviders: Event.None,70getProviders: () => [],71getProvider: () => provider,72} as unknown as ISessionsProvidersService);7374const picker = disposables.add(instantiationService.createInstance(ClaudePermissionModePicker));7576return {77picker,78actionWidgetItems,79get onSelect() { return capturedOnSelect!; },80};81}8283suite('ClaudePermissionModePicker', () => {84const disposables = new DisposableStore();8586teardown(() => {87disposables.clear();88});8990ensureNoDisposablesAreLeakedInTestSuite();9192test('shows all three permission modes', () => {93const { picker, actionWidgetItems } = createPicker(disposables);94const container = document.createElement('div');95picker.render(container);96showPicker(container);9798assert.deepStrictEqual(99actionWidgetItems.map(item => ({ id: item.item?.id, label: item.label })),100[101{ id: 'default', label: 'Ask Before Edits' },102{ id: 'acceptEdits', label: 'Edit Automatically' },103{ id: 'plan', label: 'Plan Mode' },104],105);106});107108test('selecting a mode updates the trigger label', () => {109const result = createPicker(disposables);110const container = document.createElement('div');111result.picker.render(container);112showPicker(container);113114result.onSelect({ id: 'plan', label: 'Plan Mode' } as IPermissionModeItem);115116const labelSpan = container.querySelector<HTMLElement>('span.sessions-chat-dropdown-label');117assert.ok(labelSpan);118assert.strictEqual(labelSpan.textContent, 'Plan Mode');119});120121test('selecting a mode calls setOption on the session', () => {122const calls: { optionId: string; value: { id: string; name: string } }[] = [];123const result = createPicker(disposables, {124setOptionSpy: (optionId, value) => calls.push({ optionId, value }),125});126const container = document.createElement('div');127result.picker.render(container);128showPicker(container);129130result.onSelect({ id: 'default', label: 'Ask Before Edits' } as IPermissionModeItem);131132assert.deepStrictEqual(calls, [{133optionId: 'permissionMode',134value: { id: 'default', name: 'Ask Before Edits' },135}]);136});137138test('selecting a mode does not throw when no active session', () => {139const result = createPicker(disposables, { hasActiveSession: false });140const container = document.createElement('div');141result.picker.render(container);142showPicker(container);143144assert.doesNotThrow(() => result.onSelect({ id: 'plan', label: 'Plan Mode' } as IPermissionModeItem));145});146147test('trigger has correct aria label', () => {148const { picker } = createPicker(disposables);149const container = document.createElement('div');150picker.render(container);151152const trigger = container.querySelector<HTMLElement>('a.action-label');153assert.ok(trigger);154// Default mode is 'acceptEdits' → "Edit Automatically"155assert.ok(trigger.ariaLabel?.includes('Edit Automatically'));156});157});158159160