Path: blob/main/extensions/copilot/src/extension/chatSessions/copilotcli/vscode-node/test/closeDiff.spec.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 { beforeEach, describe, expect, it, vi } from 'vitest';6import { TestLogService } from '../../../../../platform/testing/common/testLogService';7import { MockMcpServer, parseToolResult } from './testHelpers';89vi.mock('vscode', () => ({10Uri: {11file: (path: string) => ({ fsPath: path, scheme: 'file' }),12},13window: {14tabGroups: {15activeTabGroup: {16activeTab: null,17},18all: [],19onDidChangeTabGroups: () => ({ dispose: () => { } }),20onDidChangeTabs: () => ({ dispose: () => { } }),21},22},23commands: {24executeCommand: vi.fn().mockResolvedValue(undefined),25},26TabInputTextDiff: class TabInputTextDiff {27constructor(public original: any, public modified: any) { }28},29}));3031interface CloseDiffResult {32success: boolean;33already_closed: boolean;34tab_name: string;35message: string;36}3738import {39DiffStateManager,40type ActiveDiff,41} from '../diffState';42import { registerCloseDiffTool } from '../tools/closeDiff';4344describe('closeDiff tool', () => {45const logger = new TestLogService();46let diffState: DiffStateManager;4748const createMockDiff = (tabName: string, diffIdSuffix?: string): ActiveDiff => ({49diffId: `/tmp/modified-${diffIdSuffix ?? tabName}.ts`,50tabName: tabName,51originalUri: { fsPath: `/path/to/original-${tabName}.ts`, scheme: 'file' } as any,52modifiedUri: { fsPath: `/tmp/modified-${diffIdSuffix ?? tabName}.ts`, scheme: 'file' } as any,53newContents: `// new contents for ${tabName}`,54cleanup: vi.fn(),55resolve: vi.fn(),56});5758beforeEach(() => {59diffState = new DiffStateManager(logger);60});6162it('should register the close_diff tool', () => {63const mockServer = new MockMcpServer();64registerCloseDiffTool(mockServer as any, logger, diffState);6566expect(mockServer.getToolHandler('close_diff')).toBeDefined();67});6869it('should close an active diff by tab name', async () => {70const mockServer = new MockMcpServer();71registerCloseDiffTool(mockServer as any, logger, diffState);7273const diff = createMockDiff('My Test Diff');74diffState.register(diff);7576const handler = mockServer.getToolHandler('close_diff')!;77const result = await handler({ tab_name: 'My Test Diff' });78const parsed = parseToolResult<CloseDiffResult>(result);7980expect(parsed.success).toBe(true);81expect(parsed.already_closed).toBe(false);82expect(parsed.tab_name).toBe('My Test Diff');83expect(parsed.message).toContain('closed successfully');8485expect(diff.resolve).toHaveBeenCalledWith({86status: 'REJECTED',87trigger: 'closed_via_tool',88});89});9091it('should return success with already_closed=true for non-existent tab', async () => {92const mockServer = new MockMcpServer();93registerCloseDiffTool(mockServer as any, logger, diffState);9495const handler = mockServer.getToolHandler('close_diff')!;96const result = await handler({ tab_name: 'Non-existent Tab' });97const parsed = parseToolResult<CloseDiffResult>(result);9899expect(parsed.success).toBe(true);100expect(parsed.already_closed).toBe(true);101expect(parsed.tab_name).toBe('Non-existent Tab');102expect(parsed.message).toContain('may already be closed');103});104105it('should be idempotent - closing same tab twice returns success', async () => {106const mockServer = new MockMcpServer();107registerCloseDiffTool(mockServer as any, logger, diffState);108109const diff = createMockDiff('Idempotent Test');110diffState.register(diff);111112const handler = mockServer.getToolHandler('close_diff')!;113114const result1 = await handler({ tab_name: 'Idempotent Test' });115const parsed1 = parseToolResult<CloseDiffResult>(result1);116expect(parsed1.success).toBe(true);117expect(parsed1.already_closed).toBe(false);118119diffState.unregister(diff.diffId);120121const result2 = await handler({ tab_name: 'Idempotent Test' });122const parsed2 = parseToolResult<CloseDiffResult>(result2);123expect(parsed2.success).toBe(true);124expect(parsed2.already_closed).toBe(true);125});126127it('should close the correct diff when multiple diffs are open', async () => {128const mockServer = new MockMcpServer();129registerCloseDiffTool(mockServer as any, logger, diffState);130131const diff1 = createMockDiff('First Diff');132const diff2 = createMockDiff('Second Diff');133const diff3 = createMockDiff('Third Diff');134diffState.register(diff1);135diffState.register(diff2);136diffState.register(diff3);137138const handler = mockServer.getToolHandler('close_diff')!;139140const result = await handler({ tab_name: 'Second Diff' });141const parsed = parseToolResult<CloseDiffResult>(result);142143expect(parsed.success).toBe(true);144expect(parsed.already_closed).toBe(false);145146expect(diff1.resolve).not.toHaveBeenCalled();147expect(diff2.resolve).toHaveBeenCalledWith({148status: 'REJECTED',149trigger: 'closed_via_tool',150});151expect(diff3.resolve).not.toHaveBeenCalled();152153expect(diffState.getByTabName('First Diff')).toBe(diff1);154expect(diffState.getByTabName('Third Diff')).toBe(diff3);155});156157describe('edge cases', () => {158it('should handle empty tab name', async () => {159const mockServer = new MockMcpServer();160registerCloseDiffTool(mockServer as any, logger, diffState);161162const handler = mockServer.getToolHandler('close_diff')!;163const result = await handler({ tab_name: '' });164const parsed = parseToolResult<CloseDiffResult>(result);165166expect(parsed.success).toBe(true);167expect(parsed.already_closed).toBe(true);168});169170it('should handle tab name with special characters', async () => {171const mockServer = new MockMcpServer();172registerCloseDiffTool(mockServer as any, logger, diffState);173174const diff = createMockDiff('Diff: src/file.ts → modified (2024-01-23)');175diffState.register(diff);176177const handler = mockServer.getToolHandler('close_diff')!;178const result = await handler({ tab_name: 'Diff: src/file.ts → modified (2024-01-23)' });179const parsed = parseToolResult<CloseDiffResult>(result);180181expect(parsed.success).toBe(true);182expect(parsed.already_closed).toBe(false);183expect(diff.resolve).toHaveBeenCalled();184});185186it('should handle closing tab that was never opened', async () => {187const mockServer = new MockMcpServer();188registerCloseDiffTool(mockServer as any, logger, diffState);189190const handler = mockServer.getToolHandler('close_diff')!;191const result = await handler({ tab_name: 'Never Existed Tab' });192const parsed = parseToolResult<CloseDiffResult>(result);193194expect(parsed.success).toBe(true);195expect(parsed.already_closed).toBe(true);196expect(parsed.message).toContain('may already be closed');197});198199it('should handle multiple diffs with same tab name but different diffIds', async () => {200const mockServer = new MockMcpServer();201registerCloseDiffTool(mockServer as any, logger, diffState);202203const diff1 = createMockDiff('Duplicate Name', 'diff1');204const diff2 = createMockDiff('Duplicate Name', 'diff2');205diffState.register(diff1);206diffState.register(diff2);207208const handler = mockServer.getToolHandler('close_diff')!;209const result = await handler({ tab_name: 'Duplicate Name' });210const parsed = parseToolResult<CloseDiffResult>(result);211212expect(parsed.success).toBe(true);213expect(parsed.already_closed).toBe(false);214215expect(diff1.resolve).toHaveBeenCalled();216expect(diff2.resolve).not.toHaveBeenCalled();217218diffState.unregister(diff1.diffId);219220expect(diffState.getByTabName('Duplicate Name')).toBe(diff2);221});222223it('should handle rapid successive closes of different tabs', async () => {224const mockServer = new MockMcpServer();225registerCloseDiffTool(mockServer as any, logger, diffState);226227const diff1 = createMockDiff('Tab A');228const diff2 = createMockDiff('Tab B');229const diff3 = createMockDiff('Tab C');230diffState.register(diff1);231diffState.register(diff2);232diffState.register(diff3);233234const handler = mockServer.getToolHandler('close_diff')!;235236const [result1, result2, result3] = await Promise.all([237handler({ tab_name: 'Tab A' }),238handler({ tab_name: 'Tab B' }),239handler({ tab_name: 'Tab C' }),240]);241242expect(parseToolResult<CloseDiffResult>(result1).success).toBe(true);243expect(parseToolResult<CloseDiffResult>(result2).success).toBe(true);244expect(parseToolResult<CloseDiffResult>(result3).success).toBe(true);245246expect(diff1.resolve).toHaveBeenCalled();247expect(diff2.resolve).toHaveBeenCalled();248expect(diff3.resolve).toHaveBeenCalled();249});250251it('should handle tab name that is very long', async () => {252const mockServer = new MockMcpServer();253registerCloseDiffTool(mockServer as any, logger, diffState);254255const longTabName = 'A'.repeat(1000);256const diff = createMockDiff(longTabName);257diffState.register(diff);258259const handler = mockServer.getToolHandler('close_diff')!;260const result = await handler({ tab_name: longTabName });261const parsed = parseToolResult<CloseDiffResult>(result);262263expect(parsed.success).toBe(true);264expect(parsed.already_closed).toBe(false);265expect(diff.resolve).toHaveBeenCalled();266});267268it('should handle whitespace-only tab name', async () => {269const mockServer = new MockMcpServer();270registerCloseDiffTool(mockServer as any, logger, diffState);271272const handler = mockServer.getToolHandler('close_diff')!;273const result = await handler({ tab_name: ' ' });274const parsed = parseToolResult<CloseDiffResult>(result);275276expect(parsed.success).toBe(true);277expect(parsed.already_closed).toBe(true);278});279280it('should handle tab name with unicode characters', async () => {281const mockServer = new MockMcpServer();282registerCloseDiffTool(mockServer as any, logger, diffState);283284const diff = createMockDiff('编辑文件 🔧 файл.ts');285diffState.register(diff);286287const handler = mockServer.getToolHandler('close_diff')!;288const result = await handler({ tab_name: '编辑文件 🔧 файл.ts' });289const parsed = parseToolResult<CloseDiffResult>(result);290291expect(parsed.success).toBe(true);292expect(parsed.already_closed).toBe(false);293expect(diff.resolve).toHaveBeenCalled();294});295});296});297298299