Path: blob/main/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatZoneMenus.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 { DisposableStore } from '../../../../../base/common/lifecycle.js';7import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js';8import { isIMenuItem, MenuId, MenuRegistry, registerAction2 } from '../../../../../platform/actions/common/actions.js';9import { ContextKeyValue, IContext } from '../../../../../platform/contextkey/common/contextkey.js';10import { KeepSessionAction2, UndoAndCloseSessionAction2, CancelSessionAction, ContinueInlineChatInChatViewAction, RephraseInlineChatSessionAction, } from '../../browser/inlineChatActions.js';11import { registerChatExecuteActions } from '../../../chat/browser/actions/chatExecuteActions.js';12import { registerChatContextActions } from '../../../chat/browser/actions/chatContextActions.js';13import { registerChatToolActions } from '../../../chat/browser/actions/chatToolActions.js';1415/**16* The inline chat zone widget hosts four menus: `ChatEditorInlineExecute`,17* `ChatEditorInlineInputSide`, `ChatInput`, and `ChatExecute`. The latter18* two are shared with the regular chat widget, which evolves frequently.19*20* These tests evaluate the `when` clauses of menu items against faked21* inline chat context keys. They guard against regressions where commands22* from the normal chat panel accidentally become visible in inline chat.23* When a test fails, double-check that the change is intentional for the24* inline chat zone widget specifically and update the expected ids.25*/26suite('Inline chat zone widget — menu contributions', function () {2728const disposables = new DisposableStore();2930suiteSetup(() => {31// Register every action whose menu items can appear in the inline chat32// zone widget. We only call the public registration helpers so that33// adding a new action to one of those helpers will surface here.34disposables.add(registerChatExecuteActions());35disposables.add(registerChatContextActions());36disposables.add(registerChatToolActions());3738disposables.add(registerAction2(KeepSessionAction2));39disposables.add(registerAction2(UndoAndCloseSessionAction2));40disposables.add(registerAction2(CancelSessionAction));41disposables.add(registerAction2(ContinueInlineChatInChatViewAction));42disposables.add(registerAction2(RephraseInlineChatSessionAction));43});4445suiteTeardown(() => {46disposables.dispose();47});4849ensureNoDisposablesAreLeakedInTestSuite();5051/**52* Base context keys for the inline chat zone widget in an editor.53* Simulates a typical inline chat state: `chatLocation` is `editor`,54* the inline chat agent is available, and `quickChatHasFocus` is false.55*/56const inlineChatBaseContext: Record<string, ContextKeyValue> = {57// inline chat is in an editor, not a panel58'chatLocation': 'editor',59// the inline chat agent is available60'inlineChatHasEditsAgent': true,61// NOT in quick chat62'quickChatHasFocus': false,63// NOT in global editing session (this is inline chat, not panel edits)64'chatEdits.isGlobalEditingSession': false,65// NOT locked to coding agent66'lockedToCodingAgent': false,67// NOT in sessions window68'isSessionsWindow': false,69// chat is enabled70'chatIsEnabled': true,71// mode is 'ask'72'chatAgentKind': 'ask',73};7475function createContext(overrides: Record<string, ContextKeyValue> = {}): IContext {76const values: Record<string, ContextKeyValue> = { ...inlineChatBaseContext, ...overrides };77return { getValue: <T extends ContextKeyValue>(key: string): T | undefined => values[key] as T | undefined };78}7980function visibleIds(menuId: MenuId, ctx: IContext): string[] {81return MenuRegistry.getMenuItems(menuId)82.filter(isIMenuItem)83.filter(item => !item.when || item.when.evaluate(ctx))84.map(item => item.command.id)85.sort();86}8788// --- ChatEditorInlineExecute ---8990test('ChatEditorInlineExecute — idle, user has typed text', () => {91const ctx = createContext({92'chatInputHasText': true,93'chatSessionHasActiveRequest': false,94'chatEdits.isRequestInProgress': false,95'chatEdits.hasEditorModifications': false,96});97assert.deepStrictEqual(visibleIds(MenuId.ChatEditorInlineExecute, ctx), [98'inlineChat2.close',99'workbench.action.chat.submit',100].sort());101});102103test('ChatEditorInlineExecute — request in progress', () => {104const ctx = createContext({105'chatEdits.isRequestInProgress': true,106'chatSessionHasActiveRequest': true,107});108assert.deepStrictEqual(visibleIds(MenuId.ChatEditorInlineExecute, ctx), [109'inlineChat2.close',110'workbench.action.chat.cancel',111].sort());112});113114test('ChatEditorInlineExecute — terminated', () => {115const ctx = createContext({116'inlineChatTerminated': true,117'chatEdits.hasEditorModifications': false,118'chatEdits.isRequestInProgress': false,119'chatSessionHasActiveRequest': false,120});121assert.deepStrictEqual(visibleIds(MenuId.ChatEditorInlineExecute, ctx), [122'inlineChat2.close',123'inlineChat2.continueInChat',124'inlineChat2.rephrase',125'workbench.action.chat.submit',126].sort());127});128129// --- ChatEditorInlineInputSide ---130131test('ChatEditorInlineInputSide — always empty', () => {132const ctx = createContext();133assert.deepStrictEqual(visibleIds(MenuId.ChatEditorInlineInputSide, ctx), []);134});135136// --- ChatInput (shared with panel) ---137138test('ChatInput — inline chat context must NOT show panel-only items', () => {139const ctx = createContext({140'agentSupportsAttachments': true,141});142const ids = visibleIds(MenuId.ChatInput, ctx);143144// Panel-only commands must never appear in inline chat (chatLocation == 'editor')145const panelOnlyCommands = [146'workbench.action.chat.openModePicker',147'workbench.action.chat.openSessionTargetPicker',148'workbench.action.chat.openWorkspacePicker',149'workbench.action.chat.chatSessionPrimaryPicker',150];151for (const cmd of panelOnlyCommands) {152assert.ok(!ids.includes(cmd), `panel-only command "${cmd}" should NOT appear in inline chat`);153}154155// The attach context action should be present for inline chat156assert.ok(ids.includes('workbench.action.chat.attachContext'), 'attachContext should appear in inline chat');157});158159test('ChatInput — panel context for comparison', () => {160const ctx = createContext({161'chatLocation': 'panel',162'agentSupportsAttachments': true,163'chatIsEnabled': true,164'chatSessionHasCustomAgentTarget': true,165});166const ids = visibleIds(MenuId.ChatInput, ctx);167168// In the panel, mode picker and attach context should appear169assert.ok(ids.includes('workbench.action.chat.attachContext'), 'attachContext should appear in panel');170assert.ok(ids.includes('workbench.action.chat.openModePicker'), 'openModePicker should appear in panel');171});172173// --- ChatExecute (shared with panel) ---174175test('ChatExecute — inline chat idle with ask mode', () => {176const ctx = createContext({177'chatSessionHasActiveRequest': false,178'withinEditSessionDiff': false,179});180const ids = visibleIds(MenuId.ChatExecute, ctx);181assert.ok(ids.includes('workbench.action.chat.submit'), 'submit should appear');182assert.ok(!ids.includes('workbench.action.chat.cancel'), 'cancel should NOT appear when idle');183assert.ok(!ids.includes('workbench.action.edits.submit'), 'edits.submit should NOT appear in ask mode');184});185186test('ChatExecute — inline chat request in progress', () => {187const ctx = createContext({188'chatSessionHasActiveRequest': true,189'chatSessionCurrentlyEditing': false,190'chatRemoteJobCreating': false,191});192const ids = visibleIds(MenuId.ChatExecute, ctx);193assert.ok(ids.includes('workbench.action.chat.cancel'), 'cancel should appear during request');194assert.ok(!ids.includes('workbench.action.chat.submit'), 'submit should NOT appear during request');195});196197test('ChatExecute — quick chat items do NOT appear in inline chat', () => {198// Quick chat specific items (those gated on quickChatHasFocus) must not appear199const ctx = createContext({200'quickChatHasFocus': false,201'chatSessionHasActiveRequest': false,202});203const ids = visibleIds(MenuId.ChatExecute, ctx);204// The attach context action in ChatExecute is gated on quickChatHasFocus205assert.ok(!ids.includes('workbench.action.chat.attachContext'),206'attachContext (quick chat variant) should NOT appear in inline chat');207});208});209210211