Path: blob/main/extensions/copilot/src/extension/chatSessions/copilotcli/vscode-node/test/diagnosticsChanged.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, createMockDiagnostic, createMockUri } from './testHelpers';910const { mockOnDidChangeDiagnostics, mockGetDiagnostics } = vi.hoisted(() => ({11mockOnDidChangeDiagnostics: vi.fn(),12mockGetDiagnostics: vi.fn(),13}));1415vi.mock('vscode', () => {16const DiagnosticSeverity = {17Error: 0,18Warning: 1,19Information: 2,20Hint: 3,21};2223return {24languages: {25getDiagnostics: mockGetDiagnostics,26onDidChangeDiagnostics: mockOnDidChangeDiagnostics,27},28DiagnosticSeverity,29Disposable: class Disposable {30constructor(private readonly callOnDispose: () => void) { }31dispose() { this.callOnDispose(); }32},33};34});3536import { registerDiagnosticsChangedNotification } from '../tools/push/diagnosticsChanged';3738interface DiagnosticNotificationParams {39uris: Array<{40uri: string;41diagnostics: Array<{42message: string;43severity: string;44source?: string;45code?: string | number;46range: {47start: { line: number; character: number };48end: { line: number; character: number };49};50}>;51}>;52}5354describe('diagnosticsChanged push notification', () => {55const logger = new TestLogService();56let httpServer: MockHttpServer;57let registeredCallback: ((event: { uris: unknown[] }) => void) | null;5859beforeEach(() => {60vi.clearAllMocks();61vi.useFakeTimers();62httpServer = new MockHttpServer();63registeredCallback = null;6465mockOnDidChangeDiagnostics.mockImplementation((callback: (event: { uris: unknown[] }) => void) => {66registeredCallback = callback;67return { dispose: () => { } };68});69});7071afterEach(() => {72vi.useRealTimers();73});7475it('should register a diagnostics change listener', () => {76const disposables = registerDiagnosticsChangedNotification(logger, httpServer as unknown as InProcHttpServer);7778expect(mockOnDidChangeDiagnostics).toHaveBeenCalled();79expect(disposables.length).toBeGreaterThan(0);80});8182it('should broadcast diagnostics_changed notification when diagnostics change', async () => {83registerDiagnosticsChangedNotification(logger, httpServer as unknown as InProcHttpServer);8485const uri = createMockUri('/test/file.ts');86const diag = createMockDiagnostic('Test error', 0, 0, 0, 0, 10, 'test-source');8788mockGetDiagnostics.mockReturnValue([diag]);8990registeredCallback!({ uris: [uri] });91await vi.advanceTimersByTimeAsync(250);9293expect(httpServer.broadcastNotification).toHaveBeenCalledWith(94'diagnostics_changed',95expect.objectContaining({96uris: expect.arrayContaining([97expect.objectContaining({98uri: 'file:///test/file.ts',99diagnostics: expect.arrayContaining([100expect.objectContaining({101message: 'Test error',102severity: 'error',103}),104]),105}),106]),107}),108);109});110111it('should debounce rapid diagnostic changes', async () => {112registerDiagnosticsChangedNotification(logger, httpServer as unknown as InProcHttpServer);113114const uri1 = createMockUri('/file1.ts');115const uri2 = createMockUri('/file2.ts');116117mockGetDiagnostics.mockReturnValue([]);118119registeredCallback!({ uris: [uri1] });120await vi.advanceTimersByTimeAsync(100);121registeredCallback!({ uris: [uri2] });122await vi.advanceTimersByTimeAsync(250);123124// Only the last event fires (debounced)125expect(httpServer.broadcastNotification).toHaveBeenCalledTimes(1);126});127128it('should broadcast notification when diagnostics are cleared', async () => {129registerDiagnosticsChangedNotification(logger, httpServer as unknown as InProcHttpServer);130131const uri = createMockUri('/test/file.ts');132mockGetDiagnostics.mockReturnValue([]);133134registeredCallback!({ uris: [uri] });135await vi.advanceTimersByTimeAsync(250);136137expect(httpServer.broadcastNotification).toHaveBeenCalledWith(138'diagnostics_changed',139expect.objectContaining({140uris: expect.arrayContaining([141expect.objectContaining({142uri: 'file:///test/file.ts',143diagnostics: [],144}),145]),146}),147);148});149150it('should map severity levels correctly', async () => {151registerDiagnosticsChangedNotification(logger, httpServer as unknown as InProcHttpServer);152153const uri = createMockUri('/test/file.ts');154const diagnostics = [155createMockDiagnostic('Error', 0, 0, 0, 0, 1),156createMockDiagnostic('Warning', 1, 1, 0, 1, 1),157createMockDiagnostic('Info', 2, 2, 0, 2, 1),158createMockDiagnostic('Hint', 3, 3, 0, 3, 1),159];160161mockGetDiagnostics.mockReturnValue(diagnostics);162163registeredCallback!({ uris: [uri] });164await vi.advanceTimersByTimeAsync(250);165166const params = httpServer.broadcastNotification.mock.calls[0][1] as unknown as DiagnosticNotificationParams;167const severities = params.uris[0].diagnostics.map(d => d.severity);168169expect(severities).toContain('error');170expect(severities).toContain('warning');171expect(severities).toContain('information');172expect(severities).toContain('hint');173});174175it('should include diagnostic range, source, and code in notification', async () => {176registerDiagnosticsChangedNotification(logger, httpServer as unknown as InProcHttpServer);177178const uri = createMockUri('/test/file.ts');179const diag = createMockDiagnostic('Structured message', 1, 5, 10, 5, 20, 'test-linter', 'WARN001');180181mockGetDiagnostics.mockReturnValue([diag]);182183registeredCallback!({ uris: [uri] });184await vi.advanceTimersByTimeAsync(250);185186const params = httpServer.broadcastNotification.mock.calls[0][1] as unknown as DiagnosticNotificationParams;187const notifiedDiag = params.uris[0].diagnostics[0];188189expect(notifiedDiag.message).toBe('Structured message');190expect(notifiedDiag.severity).toBe('warning');191expect(notifiedDiag.source).toBe('test-linter');192expect(notifiedDiag.code).toBe('WARN001');193expect(notifiedDiag.range.start.line).toBe(5);194expect(notifiedDiag.range.start.character).toBe(10);195expect(notifiedDiag.range.end.line).toBe(5);196expect(notifiedDiag.range.end.character).toBe(20);197});198199it('should handle multiple URIs in a single change event', async () => {200registerDiagnosticsChangedNotification(logger, httpServer as unknown as InProcHttpServer);201202const uri1 = createMockUri('/file1.ts');203const uri2 = createMockUri('/file2.ts');204const diag1 = createMockDiagnostic('Error 1', 0, 0, 0, 0, 5);205const diag2 = createMockDiagnostic('Error 2', 0, 1, 0, 1, 5);206207mockGetDiagnostics.mockImplementation((uri: unknown) => {208const uriStr = (uri as { toString: () => string }).toString();209if (uriStr.includes('file1')) {210return [diag1];211}212if (uriStr.includes('file2')) {213return [diag2];214}215return [];216});217218registeredCallback!({ uris: [uri1, uri2] });219await vi.advanceTimersByTimeAsync(250);220221const params = httpServer.broadcastNotification.mock.calls[0][1] as unknown as DiagnosticNotificationParams;222223expect(params.uris).toHaveLength(2);224expect(params.uris.some(u => u.uri === 'file:///file1.ts')).toBe(true);225expect(params.uris.some(u => u.uri === 'file:///file2.ts')).toBe(true);226});227});228229230