Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/extension/chatSessions/copilotcli/vscode-node/test/selectionChanged.spec.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 { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
7
import { TestLogService } from '../../../../../platform/testing/common/testLogService';
8
import type { InProcHttpServer } from '../inProcHttpServer';
9
import { MockHttpServer, createMockEditor } from './testHelpers';
10
11
const { mockOnDidChangeTextEditorSelection, mockActiveTextEditor } = vi.hoisted(() => ({
12
mockOnDidChangeTextEditorSelection: vi.fn(),
13
mockActiveTextEditor: { value: null as unknown },
14
}));
15
16
vi.mock('vscode', () => ({
17
window: {
18
get activeTextEditor() { return mockActiveTextEditor.value; },
19
onDidChangeTextEditorSelection: mockOnDidChangeTextEditorSelection,
20
},
21
Disposable: class Disposable {
22
constructor(private readonly callOnDispose: () => void) { }
23
dispose() { this.callOnDispose(); }
24
},
25
}));
26
27
import { SelectionState } from '../tools/getSelection';
28
import { registerSelectionChangedNotification } from '../tools/push/selectionChanged';
29
30
describe('selectionChanged push notification', () => {
31
const logger = new TestLogService();
32
let selectionState: SelectionState;
33
let httpServer: MockHttpServer;
34
let registeredCallback: ((event: unknown) => void) | null;
35
36
beforeEach(() => {
37
vi.clearAllMocks();
38
vi.useFakeTimers();
39
selectionState = new SelectionState();
40
httpServer = new MockHttpServer();
41
registeredCallback = null;
42
mockActiveTextEditor.value = null;
43
44
mockOnDidChangeTextEditorSelection.mockImplementation((callback: (event: unknown) => void) => {
45
registeredCallback = callback;
46
return { dispose: () => { } };
47
});
48
});
49
50
afterEach(() => {
51
vi.useRealTimers();
52
});
53
54
it('should register a selection change listener', () => {
55
const disposables = registerSelectionChangedNotification(logger, httpServer as unknown as InProcHttpServer, selectionState);
56
57
expect(mockOnDidChangeTextEditorSelection).toHaveBeenCalled();
58
expect(disposables.length).toBeGreaterThan(0);
59
});
60
61
it('should broadcast selection_changed notification on selection change', async () => {
62
registerSelectionChangedNotification(logger, httpServer as unknown as InProcHttpServer, selectionState);
63
64
const mockEditor = createMockEditor('/test/file.ts', 'Hello World', 0, 0, 0, 5);
65
registeredCallback!({ textEditor: mockEditor });
66
67
await vi.advanceTimersByTimeAsync(250);
68
69
expect(httpServer.broadcastNotification).toHaveBeenCalledWith(
70
'selection_changed',
71
expect.objectContaining({
72
text: 'Hello',
73
filePath: '/test/file.ts',
74
}),
75
);
76
});
77
78
it('should debounce rapid selection changes', async () => {
79
registerSelectionChangedNotification(logger, httpServer as unknown as InProcHttpServer, selectionState);
80
81
const editor1 = createMockEditor('/test/file.ts', 'Hello World', 0, 0, 0, 3);
82
const editor2 = createMockEditor('/test/file.ts', 'Hello World', 0, 0, 0, 5);
83
84
registeredCallback!({ textEditor: editor1 });
85
await vi.advanceTimersByTimeAsync(100);
86
registeredCallback!({ textEditor: editor2 });
87
await vi.advanceTimersByTimeAsync(250);
88
89
// Only the last change should be broadcast
90
expect(httpServer.broadcastNotification).toHaveBeenCalledTimes(1);
91
expect(httpServer.broadcastNotification).toHaveBeenCalledWith(
92
'selection_changed',
93
expect.objectContaining({ text: 'Hello' }),
94
);
95
});
96
97
it('should update selection state on change', async () => {
98
registerSelectionChangedNotification(logger, httpServer as unknown as InProcHttpServer, selectionState);
99
100
const mockEditor = createMockEditor('/test/file.ts', 'Hello World', 0, 6, 0, 11);
101
registeredCallback!({ textEditor: mockEditor });
102
103
await vi.advanceTimersByTimeAsync(250);
104
105
expect(selectionState.latest).not.toBe(null);
106
expect(selectionState.latest!.text).toBe('World');
107
expect(selectionState.latest!.filePath).toBe('/test/file.ts');
108
});
109
110
it('should handle empty selection (cursor position)', async () => {
111
registerSelectionChangedNotification(logger, httpServer as unknown as InProcHttpServer, selectionState);
112
113
const mockEditor = createMockEditor('/test/file.ts', 'Hello World', 0, 5, 0, 5);
114
registeredCallback!({ textEditor: mockEditor });
115
116
await vi.advanceTimersByTimeAsync(250);
117
118
expect(httpServer.broadcastNotification).toHaveBeenCalledWith(
119
'selection_changed',
120
expect.objectContaining({
121
text: '',
122
selection: expect.objectContaining({ isEmpty: true }),
123
}),
124
);
125
});
126
127
it('should include file path information in notification', async () => {
128
registerSelectionChangedNotification(logger, httpServer as unknown as InProcHttpServer, selectionState);
129
130
const mockEditor = createMockEditor('/src/main.ts', 'const x = 1;', 0, 0, 0, 5);
131
registeredCallback!({ textEditor: mockEditor });
132
133
await vi.advanceTimersByTimeAsync(250);
134
135
expect(httpServer.broadcastNotification).toHaveBeenCalledWith(
136
'selection_changed',
137
expect.objectContaining({
138
filePath: '/src/main.ts',
139
fileUrl: 'file:///src/main.ts',
140
}),
141
);
142
});
143
144
it('should initialize with current selection if active editor exists', () => {
145
mockActiveTextEditor.value = createMockEditor('/test/initial.ts', 'Initial content', 0, 0, 0, 7);
146
147
registerSelectionChangedNotification(logger, httpServer as unknown as InProcHttpServer, selectionState);
148
149
expect(selectionState.latest).not.toBe(null);
150
expect(selectionState.latest!.text).toBe('Initial');
151
});
152
});
153
154