Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatZoneMenus.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 { DisposableStore } from '../../../../../base/common/lifecycle.js';
8
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js';
9
import { isIMenuItem, MenuId, MenuRegistry, registerAction2 } from '../../../../../platform/actions/common/actions.js';
10
import { ContextKeyValue, IContext } from '../../../../../platform/contextkey/common/contextkey.js';
11
import { KeepSessionAction2, UndoAndCloseSessionAction2, CancelSessionAction, ContinueInlineChatInChatViewAction, RephraseInlineChatSessionAction, } from '../../browser/inlineChatActions.js';
12
import { registerChatExecuteActions } from '../../../chat/browser/actions/chatExecuteActions.js';
13
import { registerChatContextActions } from '../../../chat/browser/actions/chatContextActions.js';
14
import { registerChatToolActions } from '../../../chat/browser/actions/chatToolActions.js';
15
16
/**
17
* The inline chat zone widget hosts four menus: `ChatEditorInlineExecute`,
18
* `ChatEditorInlineInputSide`, `ChatInput`, and `ChatExecute`. The latter
19
* two are shared with the regular chat widget, which evolves frequently.
20
*
21
* These tests evaluate the `when` clauses of menu items against faked
22
* inline chat context keys. They guard against regressions where commands
23
* from the normal chat panel accidentally become visible in inline chat.
24
* When a test fails, double-check that the change is intentional for the
25
* inline chat zone widget specifically and update the expected ids.
26
*/
27
suite('Inline chat zone widget — menu contributions', function () {
28
29
const disposables = new DisposableStore();
30
31
suiteSetup(() => {
32
// Register every action whose menu items can appear in the inline chat
33
// zone widget. We only call the public registration helpers so that
34
// adding a new action to one of those helpers will surface here.
35
disposables.add(registerChatExecuteActions());
36
disposables.add(registerChatContextActions());
37
disposables.add(registerChatToolActions());
38
39
disposables.add(registerAction2(KeepSessionAction2));
40
disposables.add(registerAction2(UndoAndCloseSessionAction2));
41
disposables.add(registerAction2(CancelSessionAction));
42
disposables.add(registerAction2(ContinueInlineChatInChatViewAction));
43
disposables.add(registerAction2(RephraseInlineChatSessionAction));
44
});
45
46
suiteTeardown(() => {
47
disposables.dispose();
48
});
49
50
ensureNoDisposablesAreLeakedInTestSuite();
51
52
/**
53
* Base context keys for the inline chat zone widget in an editor.
54
* Simulates a typical inline chat state: `chatLocation` is `editor`,
55
* the inline chat agent is available, and `quickChatHasFocus` is false.
56
*/
57
const inlineChatBaseContext: Record<string, ContextKeyValue> = {
58
// inline chat is in an editor, not a panel
59
'chatLocation': 'editor',
60
// the inline chat agent is available
61
'inlineChatHasEditsAgent': true,
62
// NOT in quick chat
63
'quickChatHasFocus': false,
64
// NOT in global editing session (this is inline chat, not panel edits)
65
'chatEdits.isGlobalEditingSession': false,
66
// NOT locked to coding agent
67
'lockedToCodingAgent': false,
68
// NOT in sessions window
69
'isSessionsWindow': false,
70
// chat is enabled
71
'chatIsEnabled': true,
72
// mode is 'ask'
73
'chatAgentKind': 'ask',
74
};
75
76
function createContext(overrides: Record<string, ContextKeyValue> = {}): IContext {
77
const values: Record<string, ContextKeyValue> = { ...inlineChatBaseContext, ...overrides };
78
return { getValue: <T extends ContextKeyValue>(key: string): T | undefined => values[key] as T | undefined };
79
}
80
81
function visibleIds(menuId: MenuId, ctx: IContext): string[] {
82
return MenuRegistry.getMenuItems(menuId)
83
.filter(isIMenuItem)
84
.filter(item => !item.when || item.when.evaluate(ctx))
85
.map(item => item.command.id)
86
.sort();
87
}
88
89
// --- ChatEditorInlineExecute ---
90
91
test('ChatEditorInlineExecute — idle, user has typed text', () => {
92
const ctx = createContext({
93
'chatInputHasText': true,
94
'chatSessionHasActiveRequest': false,
95
'chatEdits.isRequestInProgress': false,
96
'chatEdits.hasEditorModifications': false,
97
});
98
assert.deepStrictEqual(visibleIds(MenuId.ChatEditorInlineExecute, ctx), [
99
'inlineChat2.close',
100
'workbench.action.chat.submit',
101
].sort());
102
});
103
104
test('ChatEditorInlineExecute — request in progress', () => {
105
const ctx = createContext({
106
'chatEdits.isRequestInProgress': true,
107
'chatSessionHasActiveRequest': true,
108
});
109
assert.deepStrictEqual(visibleIds(MenuId.ChatEditorInlineExecute, ctx), [
110
'inlineChat2.close',
111
'workbench.action.chat.cancel',
112
].sort());
113
});
114
115
test('ChatEditorInlineExecute — terminated', () => {
116
const ctx = createContext({
117
'inlineChatTerminated': true,
118
'chatEdits.hasEditorModifications': false,
119
'chatEdits.isRequestInProgress': false,
120
'chatSessionHasActiveRequest': false,
121
});
122
assert.deepStrictEqual(visibleIds(MenuId.ChatEditorInlineExecute, ctx), [
123
'inlineChat2.close',
124
'inlineChat2.continueInChat',
125
'inlineChat2.rephrase',
126
'workbench.action.chat.submit',
127
].sort());
128
});
129
130
// --- ChatEditorInlineInputSide ---
131
132
test('ChatEditorInlineInputSide — always empty', () => {
133
const ctx = createContext();
134
assert.deepStrictEqual(visibleIds(MenuId.ChatEditorInlineInputSide, ctx), []);
135
});
136
137
// --- ChatInput (shared with panel) ---
138
139
test('ChatInput — inline chat context must NOT show panel-only items', () => {
140
const ctx = createContext({
141
'agentSupportsAttachments': true,
142
});
143
const ids = visibleIds(MenuId.ChatInput, ctx);
144
145
// Panel-only commands must never appear in inline chat (chatLocation == 'editor')
146
const panelOnlyCommands = [
147
'workbench.action.chat.openModePicker',
148
'workbench.action.chat.openSessionTargetPicker',
149
'workbench.action.chat.openWorkspacePicker',
150
'workbench.action.chat.chatSessionPrimaryPicker',
151
];
152
for (const cmd of panelOnlyCommands) {
153
assert.ok(!ids.includes(cmd), `panel-only command "${cmd}" should NOT appear in inline chat`);
154
}
155
156
// The attach context action should be present for inline chat
157
assert.ok(ids.includes('workbench.action.chat.attachContext'), 'attachContext should appear in inline chat');
158
});
159
160
test('ChatInput — panel context for comparison', () => {
161
const ctx = createContext({
162
'chatLocation': 'panel',
163
'agentSupportsAttachments': true,
164
'chatIsEnabled': true,
165
'chatSessionHasCustomAgentTarget': true,
166
});
167
const ids = visibleIds(MenuId.ChatInput, ctx);
168
169
// In the panel, mode picker and attach context should appear
170
assert.ok(ids.includes('workbench.action.chat.attachContext'), 'attachContext should appear in panel');
171
assert.ok(ids.includes('workbench.action.chat.openModePicker'), 'openModePicker should appear in panel');
172
});
173
174
// --- ChatExecute (shared with panel) ---
175
176
test('ChatExecute — inline chat idle with ask mode', () => {
177
const ctx = createContext({
178
'chatSessionHasActiveRequest': false,
179
'withinEditSessionDiff': false,
180
});
181
const ids = visibleIds(MenuId.ChatExecute, ctx);
182
assert.ok(ids.includes('workbench.action.chat.submit'), 'submit should appear');
183
assert.ok(!ids.includes('workbench.action.chat.cancel'), 'cancel should NOT appear when idle');
184
assert.ok(!ids.includes('workbench.action.edits.submit'), 'edits.submit should NOT appear in ask mode');
185
});
186
187
test('ChatExecute — inline chat request in progress', () => {
188
const ctx = createContext({
189
'chatSessionHasActiveRequest': true,
190
'chatSessionCurrentlyEditing': false,
191
'chatRemoteJobCreating': false,
192
});
193
const ids = visibleIds(MenuId.ChatExecute, ctx);
194
assert.ok(ids.includes('workbench.action.chat.cancel'), 'cancel should appear during request');
195
assert.ok(!ids.includes('workbench.action.chat.submit'), 'submit should NOT appear during request');
196
});
197
198
test('ChatExecute — quick chat items do NOT appear in inline chat', () => {
199
// Quick chat specific items (those gated on quickChatHasFocus) must not appear
200
const ctx = createContext({
201
'quickChatHasFocus': false,
202
'chatSessionHasActiveRequest': false,
203
});
204
const ids = visibleIds(MenuId.ChatExecute, ctx);
205
// The attach context action in ChatExecute is gated on quickChatHasFocus
206
assert.ok(!ids.includes('workbench.action.chat.attachContext'),
207
'attachContext (quick chat variant) should NOT appear in inline chat');
208
});
209
});
210
211