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/addFileReference.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 { beforeEach, describe, expect, it, vi } from 'vitest';
7
import { TestLogService } from '../../../../../platform/testing/common/testLogService';
8
import type { InProcHttpServer } from '../inProcHttpServer';
9
import { MockHttpServer, MockSessionTracker, createMockEditor, createMockEditorWithScheme } from './testHelpers';
10
11
const { mockRegisterCommand, mockActiveTextEditor, mockShowQuickPick } = vi.hoisted(() => ({
12
mockRegisterCommand: vi.fn(),
13
mockActiveTextEditor: { value: null as unknown },
14
mockShowQuickPick: vi.fn(),
15
}));
16
17
vi.mock('vscode', () => ({
18
window: {
19
get activeTextEditor() { return mockActiveTextEditor.value; },
20
showWarningMessage: vi.fn(),
21
showQuickPick: (...args: unknown[]) => mockShowQuickPick(...args),
22
},
23
commands: {
24
registerCommand: (...args: unknown[]) => mockRegisterCommand(...args),
25
},
26
}));
27
28
import * as vscode from 'vscode';
29
import { ADD_FILE_REFERENCE_COMMAND, registerAddFileReferenceCommand } from '../commands/addFileReference';
30
import { ADD_FILE_REFERENCE_NOTIFICATION } from '../commands/sendContext';
31
32
describe('addFileReference command', () => {
33
const logger = new TestLogService();
34
let httpServer: MockHttpServer;
35
let sessionTracker: MockSessionTracker;
36
let registeredCommands: Map<string, (...args: unknown[]) => unknown>;
37
38
beforeEach(() => {
39
vi.clearAllMocks();
40
httpServer = new MockHttpServer();
41
sessionTracker = new MockSessionTracker();
42
registeredCommands = new Map();
43
mockActiveTextEditor.value = null;
44
45
// Default: one connected session
46
httpServer.setConnectedSessionIds(['session-1']);
47
48
mockRegisterCommand.mockImplementation((name: string, callback: (...args: unknown[]) => unknown) => {
49
registeredCommands.set(name, callback);
50
return { dispose: () => { } };
51
});
52
});
53
54
it('should register the command', () => {
55
registerAddFileReferenceCommand(logger, httpServer as unknown as InProcHttpServer, sessionTracker.asTracker());
56
expect(registeredCommands.has(ADD_FILE_REFERENCE_COMMAND)).toBe(true);
57
});
58
59
it('should send file reference from URI (explorer context menu)', async () => {
60
registerAddFileReferenceCommand(logger, httpServer as unknown as InProcHttpServer, sessionTracker.asTracker());
61
62
const uri = {
63
fsPath: '/test/explorer-file.ts',
64
scheme: 'file',
65
toString: () => 'file:///test/explorer-file.ts',
66
};
67
68
await registeredCommands.get(ADD_FILE_REFERENCE_COMMAND)!(uri);
69
70
expect(httpServer.sendNotification).toHaveBeenCalledWith(
71
'session-1',
72
ADD_FILE_REFERENCE_NOTIFICATION,
73
expect.objectContaining({
74
filePath: '/test/explorer-file.ts',
75
fileUrl: 'file:///test/explorer-file.ts',
76
selection: null,
77
selectedText: null,
78
}),
79
);
80
});
81
82
it('should send file reference from active editor with no selection', async () => {
83
mockActiveTextEditor.value = createMockEditor('/test/active-file.ts', 'Hello World', 0, 0, 0, 0);
84
85
registerAddFileReferenceCommand(logger, httpServer as unknown as InProcHttpServer, sessionTracker.asTracker());
86
await registeredCommands.get(ADD_FILE_REFERENCE_COMMAND)!();
87
88
expect(httpServer.sendNotification).toHaveBeenCalledWith(
89
'session-1',
90
ADD_FILE_REFERENCE_NOTIFICATION,
91
expect.objectContaining({
92
filePath: '/test/active-file.ts',
93
selection: null,
94
selectedText: null,
95
}),
96
);
97
});
98
99
it('should include selection info when text is selected', async () => {
100
mockActiveTextEditor.value = createMockEditor('/test/file.ts', 'line 0\nline 1\nline 2', 1, 0, 1, 6);
101
102
registerAddFileReferenceCommand(logger, httpServer as unknown as InProcHttpServer, sessionTracker.asTracker());
103
await registeredCommands.get(ADD_FILE_REFERENCE_COMMAND)!();
104
105
expect(httpServer.sendNotification).toHaveBeenCalledWith(
106
'session-1',
107
ADD_FILE_REFERENCE_NOTIFICATION,
108
expect.objectContaining({
109
filePath: '/test/file.ts',
110
selection: {
111
start: { line: 1, character: 0 },
112
end: { line: 1, character: 6 },
113
},
114
selectedText: 'line 1',
115
}),
116
);
117
});
118
119
it('should include multi-line selection info', async () => {
120
mockActiveTextEditor.value = createMockEditor('/test/file.ts', 'line 0\nline 1\nline 2', 0, 0, 2, 6);
121
122
registerAddFileReferenceCommand(logger, httpServer as unknown as InProcHttpServer, sessionTracker.asTracker());
123
await registeredCommands.get(ADD_FILE_REFERENCE_COMMAND)!();
124
125
expect(httpServer.sendNotification).toHaveBeenCalledWith(
126
'session-1',
127
ADD_FILE_REFERENCE_NOTIFICATION,
128
expect.objectContaining({
129
selection: {
130
start: { line: 0, character: 0 },
131
end: { line: 2, character: 6 },
132
},
133
selectedText: 'line 0\nline 1\nline 2',
134
}),
135
);
136
});
137
138
it('should show warning when no active editor and no URI', async () => {
139
registerAddFileReferenceCommand(logger, httpServer as unknown as InProcHttpServer, sessionTracker.asTracker());
140
await registeredCommands.get(ADD_FILE_REFERENCE_COMMAND)!();
141
142
expect(httpServer.sendNotification).not.toHaveBeenCalled();
143
expect(vscode.window.showWarningMessage).toHaveBeenCalledWith(
144
'No active editor. Open a file to add a reference.',
145
);
146
});
147
148
it('should prefer provided URI over active editor', async () => {
149
mockActiveTextEditor.value = createMockEditor('/test/active-file.ts', 'Active content', 0, 0, 0, 6);
150
151
registerAddFileReferenceCommand(logger, httpServer as unknown as InProcHttpServer, sessionTracker.asTracker());
152
153
const explorerUri = {
154
fsPath: '/test/explorer-file.ts',
155
scheme: 'file',
156
toString: () => 'file:///test/explorer-file.ts',
157
};
158
await registeredCommands.get(ADD_FILE_REFERENCE_COMMAND)!(explorerUri);
159
160
expect(httpServer.sendNotification).toHaveBeenCalledWith(
161
'session-1',
162
ADD_FILE_REFERENCE_NOTIFICATION,
163
expect.objectContaining({
164
filePath: '/test/explorer-file.ts',
165
selection: null,
166
selectedText: null,
167
}),
168
);
169
});
170
171
it('should show warning when no sessions are connected', async () => {
172
httpServer.setConnectedSessionIds([]);
173
mockActiveTextEditor.value = createMockEditor('/test/file.ts', 'content', 0, 0, 0, 0);
174
175
registerAddFileReferenceCommand(logger, httpServer as unknown as InProcHttpServer, sessionTracker.asTracker());
176
await registeredCommands.get(ADD_FILE_REFERENCE_COMMAND)!();
177
178
expect(httpServer.sendNotification).not.toHaveBeenCalled();
179
expect(vscode.window.showWarningMessage).toHaveBeenCalledWith(
180
'No Copilot CLI sessions are connected.',
181
);
182
});
183
184
it('should show picker when multiple sessions are connected', async () => {
185
httpServer.setConnectedSessionIds(['session-1', 'session-2']);
186
mockShowQuickPick.mockResolvedValue({ sessionId: 'session-2', label: 'session-2' });
187
188
registerAddFileReferenceCommand(logger, httpServer as unknown as InProcHttpServer, sessionTracker.asTracker());
189
190
const uri = {
191
fsPath: '/test/file.ts',
192
scheme: 'file',
193
toString: () => 'file:///test/file.ts',
194
};
195
await registeredCommands.get(ADD_FILE_REFERENCE_COMMAND)!(uri);
196
197
expect(mockShowQuickPick).toHaveBeenCalled();
198
expect(httpServer.sendNotification).toHaveBeenCalledWith(
199
'session-2',
200
ADD_FILE_REFERENCE_NOTIFICATION,
201
expect.objectContaining({ filePath: '/test/file.ts' }),
202
);
203
});
204
205
it('should use session name as picker label when available', async () => {
206
httpServer.setConnectedSessionIds(['session-1', 'session-2']);
207
sessionTracker.setSessionName('session-1', 'My CLI');
208
sessionTracker.setSessionName('session-2', 'session-2');
209
mockShowQuickPick.mockResolvedValue({ sessionId: 'session-1', label: 'My CLI' });
210
mockActiveTextEditor.value = createMockEditor('/test/file.ts', 'content', 0, 0, 0, 0);
211
212
registerAddFileReferenceCommand(logger, httpServer as unknown as InProcHttpServer, sessionTracker.asTracker());
213
await registeredCommands.get(ADD_FILE_REFERENCE_COMMAND)!();
214
215
const items = mockShowQuickPick.mock.calls[0][0] as Array<{ label: string; description?: string; sessionId: string }>;
216
expect(items[0].label).toBe('My CLI');
217
expect(items[0].description).toBe('session-1');
218
expect(items[1].label).toBe('session-2');
219
expect(items[1].description).toBeUndefined();
220
});
221
222
it('should do nothing when picker is dismissed', async () => {
223
httpServer.setConnectedSessionIds(['session-1', 'session-2']);
224
mockShowQuickPick.mockResolvedValue(undefined);
225
226
registerAddFileReferenceCommand(logger, httpServer as unknown as InProcHttpServer, sessionTracker.asTracker());
227
await registeredCommands.get(ADD_FILE_REFERENCE_COMMAND)!();
228
229
expect(httpServer.sendNotification).not.toHaveBeenCalled();
230
});
231
232
describe('URI scheme validation', () => {
233
it('should reject output scheme from editor with warning', async () => {
234
mockActiveTextEditor.value = createMockEditorWithScheme('/Output', 'content', 0, 0, 0, 7, 'output');
235
236
registerAddFileReferenceCommand(logger, httpServer as unknown as InProcHttpServer, sessionTracker.asTracker());
237
await registeredCommands.get(ADD_FILE_REFERENCE_COMMAND)!();
238
239
expect(httpServer.sendNotification).not.toHaveBeenCalled();
240
expect(vscode.window.showWarningMessage).toHaveBeenCalledWith(
241
'Cannot send virtual files to Copilot CLI.',
242
);
243
});
244
245
it('should reject virtual scheme from explorer URI with warning', async () => {
246
registerAddFileReferenceCommand(logger, httpServer as unknown as InProcHttpServer, sessionTracker.asTracker());
247
248
const uri = {
249
fsPath: '/block',
250
scheme: 'vscode-chat-code-block',
251
toString: () => 'vscode-chat-code-block:///block',
252
};
253
await registeredCommands.get(ADD_FILE_REFERENCE_COMMAND)!(uri);
254
255
expect(httpServer.sendNotification).not.toHaveBeenCalled();
256
expect(vscode.window.showWarningMessage).toHaveBeenCalledWith(
257
'Cannot send virtual files to Copilot CLI.',
258
);
259
});
260
261
it('should reject vscode-remote scheme from explorer URI with warning', async () => {
262
registerAddFileReferenceCommand(logger, httpServer as unknown as InProcHttpServer, sessionTracker.asTracker());
263
264
const uri = {
265
fsPath: '/remote/file.ts',
266
scheme: 'vscode-remote',
267
toString: () => 'vscode-remote:///remote/file.ts',
268
};
269
await registeredCommands.get(ADD_FILE_REFERENCE_COMMAND)!(uri);
270
271
expect(httpServer.sendNotification).not.toHaveBeenCalled();
272
expect(vscode.window.showWarningMessage).toHaveBeenCalledWith(
273
'Cannot send virtual files to Copilot CLI.',
274
);
275
});
276
});
277
});
278
279