Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/sessions/contrib/copilotChatSessions/test/browser/claudePermissionModePicker.test.ts
13406 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 assert from 'assert';
7
import { Event } from '../../../../../base/common/event.js';
8
import { DisposableStore } from '../../../../../base/common/lifecycle.js';
9
import { observableValue } from '../../../../../base/common/observable.js';
10
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js';
11
import { IActionWidgetService } from '../../../../../platform/actionWidget/browser/actionWidget.js';
12
import { IActionListItem } from '../../../../../platform/actionWidget/browser/actionList.js';
13
import { TestInstantiationService } from '../../../../../platform/instantiation/test/common/instantiationServiceMock.js';
14
import { ISessionsProvidersService } from '../../../../services/sessions/browser/sessionsProvidersService.js';
15
import { IActiveSession, ISessionsManagementService } from '../../../../services/sessions/common/sessionsManagement.js';
16
import { CopilotChatSessionsProvider, ICopilotChatSession } from '../../browser/copilotChatSessionsProvider.js';
17
import { ClaudePermissionModePicker } from '../../browser/claudePermissionModePicker.js';
18
19
interface IPermissionModeItem {
20
readonly id: string;
21
readonly label: string;
22
}
23
24
function showPicker(container: HTMLElement): void {
25
const trigger = container.querySelector<HTMLElement>('a.action-label');
26
assert.ok(trigger);
27
trigger.click();
28
}
29
30
function createPicker(
31
disposables: DisposableStore,
32
opts?: {
33
setOptionSpy?: (optionId: string, value: { id: string; name: string }) => void;
34
hasActiveSession?: boolean;
35
},
36
): { picker: ClaudePermissionModePicker; actionWidgetItems: IActionListItem<IPermissionModeItem>[]; onSelect: (item: IPermissionModeItem) => void } {
37
const instantiationService = disposables.add(new TestInstantiationService());
38
const actionWidgetItems: IActionListItem<IPermissionModeItem>[] = [];
39
let capturedOnSelect: ((item: IPermissionModeItem) => void) | undefined;
40
41
const setOptionSpy = opts?.setOptionSpy ?? (() => { });
42
const hasActiveSession = opts?.hasActiveSession ?? true;
43
44
const activeSession = hasActiveSession ? {
45
providerId: 'default-copilot',
46
sessionId: 'session-id',
47
loading: observableValue('loading', false),
48
} as unknown as IActiveSession : undefined;
49
50
const mockSession: Partial<ICopilotChatSession> = {
51
setOption: setOptionSpy as ICopilotChatSession['setOption'],
52
};
53
54
const provider = Object.assign(Object.create(CopilotChatSessionsProvider.prototype), {
55
getSession: () => mockSession,
56
});
57
58
instantiationService.stub(IActionWidgetService, {
59
isVisible: false,
60
hide: () => { },
61
show: <T>(_id: string, _supportsPreview: boolean, items: IActionListItem<T>[], delegate: { onSelect: (item: T) => void }) => {
62
actionWidgetItems.splice(0, actionWidgetItems.length, ...(items as IActionListItem<IPermissionModeItem>[]));
63
capturedOnSelect = delegate.onSelect as (item: IPermissionModeItem) => void;
64
},
65
});
66
instantiationService.stub(ISessionsManagementService, {
67
activeSession: observableValue<IActiveSession | undefined>('activeSession', activeSession),
68
} as unknown as ISessionsManagementService);
69
instantiationService.stub(ISessionsProvidersService, {
70
onDidChangeProviders: Event.None,
71
getProviders: () => [],
72
getProvider: () => provider,
73
} as unknown as ISessionsProvidersService);
74
75
const picker = disposables.add(instantiationService.createInstance(ClaudePermissionModePicker));
76
77
return {
78
picker,
79
actionWidgetItems,
80
get onSelect() { return capturedOnSelect!; },
81
};
82
}
83
84
suite('ClaudePermissionModePicker', () => {
85
const disposables = new DisposableStore();
86
87
teardown(() => {
88
disposables.clear();
89
});
90
91
ensureNoDisposablesAreLeakedInTestSuite();
92
93
test('shows all three permission modes', () => {
94
const { picker, actionWidgetItems } = createPicker(disposables);
95
const container = document.createElement('div');
96
picker.render(container);
97
showPicker(container);
98
99
assert.deepStrictEqual(
100
actionWidgetItems.map(item => ({ id: item.item?.id, label: item.label })),
101
[
102
{ id: 'default', label: 'Ask Before Edits' },
103
{ id: 'acceptEdits', label: 'Edit Automatically' },
104
{ id: 'plan', label: 'Plan Mode' },
105
],
106
);
107
});
108
109
test('selecting a mode updates the trigger label', () => {
110
const result = createPicker(disposables);
111
const container = document.createElement('div');
112
result.picker.render(container);
113
showPicker(container);
114
115
result.onSelect({ id: 'plan', label: 'Plan Mode' } as IPermissionModeItem);
116
117
const labelSpan = container.querySelector<HTMLElement>('span.sessions-chat-dropdown-label');
118
assert.ok(labelSpan);
119
assert.strictEqual(labelSpan.textContent, 'Plan Mode');
120
});
121
122
test('selecting a mode calls setOption on the session', () => {
123
const calls: { optionId: string; value: { id: string; name: string } }[] = [];
124
const result = createPicker(disposables, {
125
setOptionSpy: (optionId, value) => calls.push({ optionId, value }),
126
});
127
const container = document.createElement('div');
128
result.picker.render(container);
129
showPicker(container);
130
131
result.onSelect({ id: 'default', label: 'Ask Before Edits' } as IPermissionModeItem);
132
133
assert.deepStrictEqual(calls, [{
134
optionId: 'permissionMode',
135
value: { id: 'default', name: 'Ask Before Edits' },
136
}]);
137
});
138
139
test('selecting a mode does not throw when no active session', () => {
140
const result = createPicker(disposables, { hasActiveSession: false });
141
const container = document.createElement('div');
142
result.picker.render(container);
143
showPicker(container);
144
145
assert.doesNotThrow(() => result.onSelect({ id: 'plan', label: 'Plan Mode' } as IPermissionModeItem));
146
});
147
148
test('trigger has correct aria label', () => {
149
const { picker } = createPicker(disposables);
150
const container = document.createElement('div');
151
picker.render(container);
152
153
const trigger = container.querySelector<HTMLElement>('a.action-label');
154
assert.ok(trigger);
155
// Default mode is 'acceptEdits' → "Edit Automatically"
156
assert.ok(trigger.ariaLabel?.includes('Edit Automatically'));
157
});
158
});
159
160