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/diagnosticsChanged.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, createMockDiagnostic, createMockUri } from './testHelpers';
10
11
const { mockOnDidChangeDiagnostics, mockGetDiagnostics } = vi.hoisted(() => ({
12
mockOnDidChangeDiagnostics: vi.fn(),
13
mockGetDiagnostics: vi.fn(),
14
}));
15
16
vi.mock('vscode', () => {
17
const DiagnosticSeverity = {
18
Error: 0,
19
Warning: 1,
20
Information: 2,
21
Hint: 3,
22
};
23
24
return {
25
languages: {
26
getDiagnostics: mockGetDiagnostics,
27
onDidChangeDiagnostics: mockOnDidChangeDiagnostics,
28
},
29
DiagnosticSeverity,
30
Disposable: class Disposable {
31
constructor(private readonly callOnDispose: () => void) { }
32
dispose() { this.callOnDispose(); }
33
},
34
};
35
});
36
37
import { registerDiagnosticsChangedNotification } from '../tools/push/diagnosticsChanged';
38
39
interface DiagnosticNotificationParams {
40
uris: Array<{
41
uri: string;
42
diagnostics: Array<{
43
message: string;
44
severity: string;
45
source?: string;
46
code?: string | number;
47
range: {
48
start: { line: number; character: number };
49
end: { line: number; character: number };
50
};
51
}>;
52
}>;
53
}
54
55
describe('diagnosticsChanged push notification', () => {
56
const logger = new TestLogService();
57
let httpServer: MockHttpServer;
58
let registeredCallback: ((event: { uris: unknown[] }) => void) | null;
59
60
beforeEach(() => {
61
vi.clearAllMocks();
62
vi.useFakeTimers();
63
httpServer = new MockHttpServer();
64
registeredCallback = null;
65
66
mockOnDidChangeDiagnostics.mockImplementation((callback: (event: { uris: unknown[] }) => void) => {
67
registeredCallback = callback;
68
return { dispose: () => { } };
69
});
70
});
71
72
afterEach(() => {
73
vi.useRealTimers();
74
});
75
76
it('should register a diagnostics change listener', () => {
77
const disposables = registerDiagnosticsChangedNotification(logger, httpServer as unknown as InProcHttpServer);
78
79
expect(mockOnDidChangeDiagnostics).toHaveBeenCalled();
80
expect(disposables.length).toBeGreaterThan(0);
81
});
82
83
it('should broadcast diagnostics_changed notification when diagnostics change', async () => {
84
registerDiagnosticsChangedNotification(logger, httpServer as unknown as InProcHttpServer);
85
86
const uri = createMockUri('/test/file.ts');
87
const diag = createMockDiagnostic('Test error', 0, 0, 0, 0, 10, 'test-source');
88
89
mockGetDiagnostics.mockReturnValue([diag]);
90
91
registeredCallback!({ uris: [uri] });
92
await vi.advanceTimersByTimeAsync(250);
93
94
expect(httpServer.broadcastNotification).toHaveBeenCalledWith(
95
'diagnostics_changed',
96
expect.objectContaining({
97
uris: expect.arrayContaining([
98
expect.objectContaining({
99
uri: 'file:///test/file.ts',
100
diagnostics: expect.arrayContaining([
101
expect.objectContaining({
102
message: 'Test error',
103
severity: 'error',
104
}),
105
]),
106
}),
107
]),
108
}),
109
);
110
});
111
112
it('should debounce rapid diagnostic changes', async () => {
113
registerDiagnosticsChangedNotification(logger, httpServer as unknown as InProcHttpServer);
114
115
const uri1 = createMockUri('/file1.ts');
116
const uri2 = createMockUri('/file2.ts');
117
118
mockGetDiagnostics.mockReturnValue([]);
119
120
registeredCallback!({ uris: [uri1] });
121
await vi.advanceTimersByTimeAsync(100);
122
registeredCallback!({ uris: [uri2] });
123
await vi.advanceTimersByTimeAsync(250);
124
125
// Only the last event fires (debounced)
126
expect(httpServer.broadcastNotification).toHaveBeenCalledTimes(1);
127
});
128
129
it('should broadcast notification when diagnostics are cleared', async () => {
130
registerDiagnosticsChangedNotification(logger, httpServer as unknown as InProcHttpServer);
131
132
const uri = createMockUri('/test/file.ts');
133
mockGetDiagnostics.mockReturnValue([]);
134
135
registeredCallback!({ uris: [uri] });
136
await vi.advanceTimersByTimeAsync(250);
137
138
expect(httpServer.broadcastNotification).toHaveBeenCalledWith(
139
'diagnostics_changed',
140
expect.objectContaining({
141
uris: expect.arrayContaining([
142
expect.objectContaining({
143
uri: 'file:///test/file.ts',
144
diagnostics: [],
145
}),
146
]),
147
}),
148
);
149
});
150
151
it('should map severity levels correctly', async () => {
152
registerDiagnosticsChangedNotification(logger, httpServer as unknown as InProcHttpServer);
153
154
const uri = createMockUri('/test/file.ts');
155
const diagnostics = [
156
createMockDiagnostic('Error', 0, 0, 0, 0, 1),
157
createMockDiagnostic('Warning', 1, 1, 0, 1, 1),
158
createMockDiagnostic('Info', 2, 2, 0, 2, 1),
159
createMockDiagnostic('Hint', 3, 3, 0, 3, 1),
160
];
161
162
mockGetDiagnostics.mockReturnValue(diagnostics);
163
164
registeredCallback!({ uris: [uri] });
165
await vi.advanceTimersByTimeAsync(250);
166
167
const params = httpServer.broadcastNotification.mock.calls[0][1] as unknown as DiagnosticNotificationParams;
168
const severities = params.uris[0].diagnostics.map(d => d.severity);
169
170
expect(severities).toContain('error');
171
expect(severities).toContain('warning');
172
expect(severities).toContain('information');
173
expect(severities).toContain('hint');
174
});
175
176
it('should include diagnostic range, source, and code in notification', async () => {
177
registerDiagnosticsChangedNotification(logger, httpServer as unknown as InProcHttpServer);
178
179
const uri = createMockUri('/test/file.ts');
180
const diag = createMockDiagnostic('Structured message', 1, 5, 10, 5, 20, 'test-linter', 'WARN001');
181
182
mockGetDiagnostics.mockReturnValue([diag]);
183
184
registeredCallback!({ uris: [uri] });
185
await vi.advanceTimersByTimeAsync(250);
186
187
const params = httpServer.broadcastNotification.mock.calls[0][1] as unknown as DiagnosticNotificationParams;
188
const notifiedDiag = params.uris[0].diagnostics[0];
189
190
expect(notifiedDiag.message).toBe('Structured message');
191
expect(notifiedDiag.severity).toBe('warning');
192
expect(notifiedDiag.source).toBe('test-linter');
193
expect(notifiedDiag.code).toBe('WARN001');
194
expect(notifiedDiag.range.start.line).toBe(5);
195
expect(notifiedDiag.range.start.character).toBe(10);
196
expect(notifiedDiag.range.end.line).toBe(5);
197
expect(notifiedDiag.range.end.character).toBe(20);
198
});
199
200
it('should handle multiple URIs in a single change event', async () => {
201
registerDiagnosticsChangedNotification(logger, httpServer as unknown as InProcHttpServer);
202
203
const uri1 = createMockUri('/file1.ts');
204
const uri2 = createMockUri('/file2.ts');
205
const diag1 = createMockDiagnostic('Error 1', 0, 0, 0, 0, 5);
206
const diag2 = createMockDiagnostic('Error 2', 0, 1, 0, 1, 5);
207
208
mockGetDiagnostics.mockImplementation((uri: unknown) => {
209
const uriStr = (uri as { toString: () => string }).toString();
210
if (uriStr.includes('file1')) {
211
return [diag1];
212
}
213
if (uriStr.includes('file2')) {
214
return [diag2];
215
}
216
return [];
217
});
218
219
registeredCallback!({ uris: [uri1, uri2] });
220
await vi.advanceTimersByTimeAsync(250);
221
222
const params = httpServer.broadcastNotification.mock.calls[0][1] as unknown as DiagnosticNotificationParams;
223
224
expect(params.uris).toHaveLength(2);
225
expect(params.uris.some(u => u.uri === 'file:///file1.ts')).toBe(true);
226
expect(params.uris.some(u => u.uri === 'file:///file2.ts')).toBe(true);
227
});
228
});
229
230