Path: blob/main/src/vs/workbench/contrib/chat/test/common/chatModeService.test.ts
3296 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 { timeout } from '../../../../../base/common/async.js';7import { Emitter } from '../../../../../base/common/event.js';8import { URI } from '../../../../../base/common/uri.js';9import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js';10import { IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js';11import { TestInstantiationService } from '../../../../../platform/instantiation/test/common/instantiationServiceMock.js';12import { MockContextKeyService } from '../../../../../platform/keybinding/test/common/mockKeybindingService.js';13import { ILogService, NullLogService } from '../../../../../platform/log/common/log.js';14import { IStorageService } from '../../../../../platform/storage/common/storage.js';15import { TestStorageService } from '../../../../test/common/workbenchTestServices.js';16import { IChatAgentService } from '../../common/chatAgents.js';17import { ChatMode, ChatModeService } from '../../common/chatModes.js';18import { ChatModeKind } from '../../common/constants.js';19import { ICustomChatMode, IPromptsService } from '../../common/promptSyntax/service/promptsService.js';20import { MockPromptsService } from './mockPromptsService.js';2122class TestChatAgentService implements Partial<IChatAgentService> {23_serviceBrand: undefined;2425private _hasToolsAgent = true;26private readonly _onDidChangeAgents = new Emitter<any>();2728get hasToolsAgent(): boolean {29return this._hasToolsAgent;30}3132setHasToolsAgent(value: boolean): void {33this._hasToolsAgent = value;34this._onDidChangeAgents.fire(undefined);35}3637readonly onDidChangeAgents = this._onDidChangeAgents.event;38}3940suite('ChatModeService', () => {41const testDisposables = ensureNoDisposablesAreLeakedInTestSuite();4243let instantiationService: TestInstantiationService;44let promptsService: MockPromptsService;45let chatAgentService: TestChatAgentService;46let storageService: TestStorageService;47let chatModeService: ChatModeService;4849setup(async () => {50instantiationService = testDisposables.add(new TestInstantiationService());51promptsService = new MockPromptsService();52chatAgentService = new TestChatAgentService();53storageService = testDisposables.add(new TestStorageService());5455instantiationService.stub(IPromptsService, promptsService);56instantiationService.stub(IChatAgentService, chatAgentService);57instantiationService.stub(IStorageService, storageService);58instantiationService.stub(ILogService, new NullLogService());59instantiationService.stub(IContextKeyService, new MockContextKeyService());6061chatModeService = testDisposables.add(instantiationService.createInstance(ChatModeService));62});6364test('should return builtin modes', () => {65const modes = chatModeService.getModes();6667assert.strictEqual(modes.builtin.length, 3);68assert.strictEqual(modes.custom.length, 0);6970// Check that Ask mode is always present71const askMode = modes.builtin.find(mode => mode.id === ChatModeKind.Ask);72assert.ok(askMode);73assert.strictEqual(askMode.label, 'Ask');74assert.strictEqual(askMode.name, 'ask');75assert.strictEqual(askMode.kind, ChatModeKind.Ask);76});7778test('should adjust builtin modes based on tools agent availability', () => {79// With tools agent80chatAgentService.setHasToolsAgent(true);81let modes = chatModeService.getModes();82assert.ok(modes.builtin.find(mode => mode.id === ChatModeKind.Agent));8384// Without tools agent - Agent mode should not be present85chatAgentService.setHasToolsAgent(false);86modes = chatModeService.getModes();87assert.strictEqual(modes.builtin.find(mode => mode.id === ChatModeKind.Agent), undefined);8889// But Ask and Edit modes should always be present90assert.ok(modes.builtin.find(mode => mode.id === ChatModeKind.Ask));91assert.ok(modes.builtin.find(mode => mode.id === ChatModeKind.Edit));92});9394test('should find builtin modes by id', () => {95const agentMode = chatModeService.findModeById(ChatModeKind.Agent);96assert.ok(agentMode);97assert.strictEqual(agentMode.id, ChatMode.Agent.id);98assert.strictEqual(agentMode.kind, ChatModeKind.Agent);99});100101test('should return undefined for non-existent mode', () => {102const mode = chatModeService.findModeById('non-existent-mode');103assert.strictEqual(mode, undefined);104});105106test('should handle custom modes from prompts service', async () => {107const customMode: ICustomChatMode = {108uri: URI.parse('file:///test/custom-mode.md'),109name: 'Test Mode',110description: 'A test custom mode',111tools: ['tool1', 'tool2'],112body: 'Custom mode body',113variableReferences: []114};115116promptsService.setCustomModes([customMode]);117118// Wait for the service to refresh119await timeout(0);120121const modes = chatModeService.getModes();122assert.strictEqual(modes.custom.length, 1);123124const testMode = modes.custom[0];125assert.strictEqual(testMode.id, customMode.uri.toString());126assert.strictEqual(testMode.name, customMode.name);127assert.strictEqual(testMode.label, customMode.name);128assert.strictEqual(testMode.description.get(), customMode.description);129assert.strictEqual(testMode.kind, ChatModeKind.Agent);130assert.deepStrictEqual(testMode.customTools?.get(), customMode.tools);131assert.strictEqual(testMode.body?.get(), customMode.body);132assert.strictEqual(testMode.uri?.get().toString(), customMode.uri.toString());133});134135test('should fire change event when custom modes are updated', async () => {136let eventFired = false;137testDisposables.add(chatModeService.onDidChangeChatModes(() => {138eventFired = true;139}));140141const customMode: ICustomChatMode = {142uri: URI.parse('file:///test/custom-mode.md'),143name: 'Test Mode',144description: 'A test custom mode',145tools: [],146body: 'Custom mode body',147variableReferences: []148};149150promptsService.setCustomModes([customMode]);151152// Wait for the event to fire153await timeout(0);154155assert.ok(eventFired);156});157158test('should find custom modes by id', async () => {159const customMode: ICustomChatMode = {160uri: URI.parse('file:///test/findable-mode.md'),161name: 'Findable Mode',162description: 'A findable custom mode',163tools: [],164body: 'Findable mode body',165variableReferences: []166};167168promptsService.setCustomModes([customMode]);169170// Wait for the service to refresh171await timeout(0);172173const foundMode = chatModeService.findModeById(customMode.uri.toString());174assert.ok(foundMode);175assert.strictEqual(foundMode.id, customMode.uri.toString());176assert.strictEqual(foundMode.name, customMode.name);177assert.strictEqual(foundMode.label, customMode.name);178});179180test('should update existing custom mode instances when data changes', async () => {181const uri = URI.parse('file:///test/updateable-mode.md');182const initialMode: ICustomChatMode = {183uri,184name: 'Initial Mode',185description: 'Initial description',186tools: ['tool1'],187body: 'Initial body',188model: 'gpt-4',189variableReferences: []190};191192promptsService.setCustomModes([initialMode]);193await timeout(0);194195const initialModes = chatModeService.getModes();196const initialCustomMode = initialModes.custom[0];197assert.strictEqual(initialCustomMode.description.get(), 'Initial description');198199// Update the mode data200const updatedMode: ICustomChatMode = {201...initialMode,202description: 'Updated description',203tools: ['tool1', 'tool2'],204body: 'Updated body',205model: 'Updated model'206};207208promptsService.setCustomModes([updatedMode]);209await timeout(0);210211const updatedModes = chatModeService.getModes();212const updatedCustomMode = updatedModes.custom[0];213214// The instance should be the same (reused)215assert.strictEqual(initialCustomMode, updatedCustomMode);216217// But the observable properties should be updated218assert.strictEqual(updatedCustomMode.description.get(), 'Updated description');219assert.deepStrictEqual(updatedCustomMode.customTools?.get(), ['tool1', 'tool2']);220assert.strictEqual(updatedCustomMode.body?.get(), 'Updated body');221assert.strictEqual(updatedCustomMode.model?.get(), 'Updated model');222});223224test('should remove custom modes that no longer exist', async () => {225const mode1: ICustomChatMode = {226uri: URI.parse('file:///test/mode1.md'),227name: 'Mode 1',228description: 'First mode',229tools: [],230body: 'Mode 1 body',231variableReferences: []232};233234const mode2: ICustomChatMode = {235uri: URI.parse('file:///test/mode2.md'),236name: 'Mode 2',237description: 'Second mode',238tools: [],239body: 'Mode 2 body',240variableReferences: []241};242243// Add both modes244promptsService.setCustomModes([mode1, mode2]);245await timeout(0);246247let modes = chatModeService.getModes();248assert.strictEqual(modes.custom.length, 2);249250// Remove one mode251promptsService.setCustomModes([mode1]);252await timeout(0);253254modes = chatModeService.getModes();255assert.strictEqual(modes.custom.length, 1);256assert.strictEqual(modes.custom[0].id, mode1.uri.toString());257});258});259260261