Path: blob/main/extensions/copilot/src/extension/chatSessions/copilotcli/vscode-node/test/selectionChanged.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 { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';6import { TestLogService } from '../../../../../platform/testing/common/testLogService';7import type { InProcHttpServer } from '../inProcHttpServer';8import { MockHttpServer, createMockEditor } from './testHelpers';910const { mockOnDidChangeTextEditorSelection, mockActiveTextEditor } = vi.hoisted(() => ({11mockOnDidChangeTextEditorSelection: vi.fn(),12mockActiveTextEditor: { value: null as unknown },13}));1415vi.mock('vscode', () => ({16window: {17get activeTextEditor() { return mockActiveTextEditor.value; },18onDidChangeTextEditorSelection: mockOnDidChangeTextEditorSelection,19},20Disposable: class Disposable {21constructor(private readonly callOnDispose: () => void) { }22dispose() { this.callOnDispose(); }23},24}));2526import { SelectionState } from '../tools/getSelection';27import { registerSelectionChangedNotification } from '../tools/push/selectionChanged';2829describe('selectionChanged push notification', () => {30const logger = new TestLogService();31let selectionState: SelectionState;32let httpServer: MockHttpServer;33let registeredCallback: ((event: unknown) => void) | null;3435beforeEach(() => {36vi.clearAllMocks();37vi.useFakeTimers();38selectionState = new SelectionState();39httpServer = new MockHttpServer();40registeredCallback = null;41mockActiveTextEditor.value = null;4243mockOnDidChangeTextEditorSelection.mockImplementation((callback: (event: unknown) => void) => {44registeredCallback = callback;45return { dispose: () => { } };46});47});4849afterEach(() => {50vi.useRealTimers();51});5253it('should register a selection change listener', () => {54const disposables = registerSelectionChangedNotification(logger, httpServer as unknown as InProcHttpServer, selectionState);5556expect(mockOnDidChangeTextEditorSelection).toHaveBeenCalled();57expect(disposables.length).toBeGreaterThan(0);58});5960it('should broadcast selection_changed notification on selection change', async () => {61registerSelectionChangedNotification(logger, httpServer as unknown as InProcHttpServer, selectionState);6263const mockEditor = createMockEditor('/test/file.ts', 'Hello World', 0, 0, 0, 5);64registeredCallback!({ textEditor: mockEditor });6566await vi.advanceTimersByTimeAsync(250);6768expect(httpServer.broadcastNotification).toHaveBeenCalledWith(69'selection_changed',70expect.objectContaining({71text: 'Hello',72filePath: '/test/file.ts',73}),74);75});7677it('should debounce rapid selection changes', async () => {78registerSelectionChangedNotification(logger, httpServer as unknown as InProcHttpServer, selectionState);7980const editor1 = createMockEditor('/test/file.ts', 'Hello World', 0, 0, 0, 3);81const editor2 = createMockEditor('/test/file.ts', 'Hello World', 0, 0, 0, 5);8283registeredCallback!({ textEditor: editor1 });84await vi.advanceTimersByTimeAsync(100);85registeredCallback!({ textEditor: editor2 });86await vi.advanceTimersByTimeAsync(250);8788// Only the last change should be broadcast89expect(httpServer.broadcastNotification).toHaveBeenCalledTimes(1);90expect(httpServer.broadcastNotification).toHaveBeenCalledWith(91'selection_changed',92expect.objectContaining({ text: 'Hello' }),93);94});9596it('should update selection state on change', async () => {97registerSelectionChangedNotification(logger, httpServer as unknown as InProcHttpServer, selectionState);9899const mockEditor = createMockEditor('/test/file.ts', 'Hello World', 0, 6, 0, 11);100registeredCallback!({ textEditor: mockEditor });101102await vi.advanceTimersByTimeAsync(250);103104expect(selectionState.latest).not.toBe(null);105expect(selectionState.latest!.text).toBe('World');106expect(selectionState.latest!.filePath).toBe('/test/file.ts');107});108109it('should handle empty selection (cursor position)', async () => {110registerSelectionChangedNotification(logger, httpServer as unknown as InProcHttpServer, selectionState);111112const mockEditor = createMockEditor('/test/file.ts', 'Hello World', 0, 5, 0, 5);113registeredCallback!({ textEditor: mockEditor });114115await vi.advanceTimersByTimeAsync(250);116117expect(httpServer.broadcastNotification).toHaveBeenCalledWith(118'selection_changed',119expect.objectContaining({120text: '',121selection: expect.objectContaining({ isEmpty: true }),122}),123);124});125126it('should include file path information in notification', async () => {127registerSelectionChangedNotification(logger, httpServer as unknown as InProcHttpServer, selectionState);128129const mockEditor = createMockEditor('/src/main.ts', 'const x = 1;', 0, 0, 0, 5);130registeredCallback!({ textEditor: mockEditor });131132await vi.advanceTimersByTimeAsync(250);133134expect(httpServer.broadcastNotification).toHaveBeenCalledWith(135'selection_changed',136expect.objectContaining({137filePath: '/src/main.ts',138fileUrl: 'file:///src/main.ts',139}),140);141});142143it('should initialize with current selection if active editor exists', () => {144mockActiveTextEditor.value = createMockEditor('/test/initial.ts', 'Initial content', 0, 0, 0, 7);145146registerSelectionChangedNotification(logger, httpServer as unknown as InProcHttpServer, selectionState);147148expect(selectionState.latest).not.toBe(null);149expect(selectionState.latest!.text).toBe('Initial');150});151});152153154