Path: blob/main/extensions/copilot/src/extension/inlineEdits/test/vscode-node/diagnosticsCollection.spec.ts
13405 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 * as assert from 'assert';6import { suite, test } from 'vitest';7import { DiagnosticData } from '../../../../platform/inlineEdits/common/dataTypes/diagnosticData';8import { URI } from '../../../../util/vs/base/common/uri';9import { StringEdit } from '../../../../util/vs/editor/common/core/edits/stringEdit';10import { OffsetRange } from '../../../../util/vs/editor/common/core/ranges/offsetRange';11import { StringText } from '../../../../util/vs/editor/common/core/text/abstractText';12import { Diagnostic } from '../../vscode-node/features/diagnosticsBasedCompletions/diagnosticsCompletions';13import { DiagnosticsCollection } from '../../vscode-node/features/diagnosticsCompletionProcessor';1415// Helper function to create a Diagnostic from a mock VS Code diagnostic16function createDiagnostic(message: string, range: OffsetRange): Diagnostic {17return new Diagnostic(new DiagnosticData(18URI.parse('file:///test/document.ts'),19message,20'error',21range,22undefined,23undefined24));25}2627suite('DiagnosticsCollection', () => {28test('isEqualAndUpdate should return true for empty arrays', () => {29const collection = new DiagnosticsCollection();30const result = collection.isEqualAndUpdate([]);31assert.strictEqual(result, true);32});33test('isEqualAndUpdate should update diagnostics and return false when different', () => {34const collection = new DiagnosticsCollection();35const diagnostic = createDiagnostic(36'Test error',37new OffsetRange(0, 4)38);3940const result = collection.isEqualAndUpdate([diagnostic]);4142assert.strictEqual(result, false);43});44test('isEqualAndUpdate should return true when diagnostics are equal', () => {45const collection = new DiagnosticsCollection();46const diagnostic1 = createDiagnostic('Test error', new OffsetRange(0, 4));47const diagnostic2 = createDiagnostic('Test error', new OffsetRange(0, 4));4849collection.isEqualAndUpdate([diagnostic1]);50const result = collection.isEqualAndUpdate([diagnostic2]);5152assert.strictEqual(result, true);53});54test('isEqualAndUpdate should return false when a diagnostics is invalidated', () => {55const collection = new DiagnosticsCollection();56const diagnostic1 = createDiagnostic('Test error', new OffsetRange(0, 4));57const diagnostic2 = createDiagnostic('Test error', new OffsetRange(0, 4));5859collection.isEqualAndUpdate([diagnostic1]);6061diagnostic1.invalidate();6263const result = collection.isEqualAndUpdate([diagnostic2]);6465assert.strictEqual(result, false);66});6768suite('applyEdit', () => {69test('should invalidate when typing numbers at the end of a diagnostic range', () => {70const collection = new DiagnosticsCollection();71const diagnostic = createDiagnostic('Test error', new OffsetRange(12, 17)); // "test" = positions 12-15 (0-based)72collection.isEqualAndUpdate([diagnostic]);7374// Replace "test" with "test123"75const before = new StringText('hello world test');76const edit = StringEdit.replace(new OffsetRange(12, 17), 'test123'); // 0-based: 12-1677const after = edit.applyOnText(before);7879const hasInvalidated = collection.applyEdit(before, edit, after);80assert.strictEqual(hasInvalidated, true);81assert.strictEqual(diagnostic.isValid(), false);82});8384test('should invalidate diagnostic when range shrinks', () => {85const collection = new DiagnosticsCollection();86const diagnostic = createDiagnostic('Test error', new OffsetRange(6, 11)); // "world"87collection.isEqualAndUpdate([diagnostic]);8889// Create an edit that removes "w"90const before = new StringText('hello world test');91const edit = StringEdit.replace(new OffsetRange(6, 7), ''); // Remove "w"92const after = edit.applyOnText(before);9394const hasInvalidated = collection.applyEdit(before, edit, after);9596assert.strictEqual(hasInvalidated, true);97assert.strictEqual(diagnostic.isValid(), false);98});99100test('should update range when content stays the same and range length unchanged', () => {101const collection = new DiagnosticsCollection();102const diagnostic = createDiagnostic('Test error', new OffsetRange(12, 16));103collection.isEqualAndUpdate([diagnostic]);104105// Insert " big" without touching the diagnostic range106const before = new StringText('hello world test');107const edit = StringEdit.replace(new OffsetRange(6, 6), ' big');108const after = edit.applyOnText(before);109110const hasInvalidated = collection.applyEdit(before, edit, after);111112assert.strictEqual(hasInvalidated, false);113assert.strictEqual(diagnostic.isValid(), true);114});115116test('should invalidate diagnostic when content at range changes with same length', () => {117const collection = new DiagnosticsCollection();118const diagnostic = createDiagnostic('Test error', new OffsetRange(12, 16)); // "test"119collection.isEqualAndUpdate([diagnostic]);120121// Replace "test" with "best"122const before = new StringText('hello world test');123const edit = StringEdit.replace(new OffsetRange(12, 16), 'best');124const after = edit.applyOnText(before);125126const hasInvalidated = collection.applyEdit(before, edit, after);127128assert.strictEqual(hasInvalidated, true);129assert.strictEqual(diagnostic.isValid(), false);130});131test('should handle range growth with same prefix content', () => {132const collection = new DiagnosticsCollection();133const diagnostic = createDiagnostic('Test error', new OffsetRange(12, 16));134collection.isEqualAndUpdate([diagnostic]);135136// "test" becomes "test!" (non-alphanumeric edge)137const before = new StringText('hello world test');138const edit = StringEdit.replace(new OffsetRange(12, 16), 'test!');139const after = edit.applyOnText(before);140141const hasInvalidated = collection.applyEdit(before, edit, after);142143assert.strictEqual(hasInvalidated, false);144assert.strictEqual(diagnostic.isValid(), true);145146// Range should still point to the original "test" part147assert.strictEqual(diagnostic.range.start, 12);148assert.strictEqual(diagnostic.range.endExclusive, 16);149});150151test('should handle range growth with same suffix content', () => {152const collection = new DiagnosticsCollection();153const diagnostic = createDiagnostic('Test error', new OffsetRange(12, 16)); // "test"154collection.isEqualAndUpdate([diagnostic]);155156const before = new StringText('hello world test');157const edit = StringEdit.replace(new OffsetRange(12, 12), 'ab');158const after = edit.applyOnText(before);159160const hasInvalidated = collection.applyEdit(before, edit, after);161162assert.strictEqual(hasInvalidated, true);163assert.strictEqual(diagnostic.isValid(), false);164});165166test('should invalidate when edge character is alphanumeric with prefix match', () => {167const collection = new DiagnosticsCollection();168const diagnostic = createDiagnostic('Test error', new OffsetRange(12, 16)); // "test"169collection.isEqualAndUpdate([diagnostic]);170171const before = new StringText('hello world test');172const edit = StringEdit.replace(new OffsetRange(16, 16), 'A');173const after = edit.applyOnText(before);174175// Add A after "test"176177const hasInvalidated = collection.applyEdit(before, edit, after);178179assert.strictEqual(hasInvalidated, true);180assert.strictEqual(diagnostic.isValid(), false);181});182183test('should not invalidate when edge character is non-alphanumeric with prefix match', () => {184const collection = new DiagnosticsCollection();185const diagnostic = createDiagnostic('Test error', new OffsetRange(12, 16)); // "test" = positions 12-15 (0-based)186collection.isEqualAndUpdate([diagnostic]);187188// Replace "test" with "test!"189const before = new StringText('hello world test');190const edit = StringEdit.replace(new OffsetRange(12, 16), 'test!'); // 0-based: 12-15191const after = edit.applyOnText(before);192193const hasInvalidated = collection.applyEdit(before, edit, after);194195assert.strictEqual(hasInvalidated, false);196assert.strictEqual(diagnostic.isValid(), true);197});198199test('should handle multiple diagnostics correctly', () => {200const collection = new DiagnosticsCollection();201const diagnostic1 = createDiagnostic('Error 1', new OffsetRange(0, 5)); // "hello" = positions 0-4 (0-based)202const diagnostic2 = createDiagnostic('Error 2', new OffsetRange(12, 16)); // "test" = positions 12-15 (0-based)203collection.isEqualAndUpdate([diagnostic1, diagnostic2]);204205const before = new StringText('hello world test');206const edit = StringEdit.replace(new OffsetRange(6, 6), 'big ');207const after = edit.applyOnText(before);208209const hasInvalidated = collection.applyEdit(before, edit, after);210211assert.strictEqual(hasInvalidated, false);212assert.strictEqual(diagnostic1.isValid(), true);213assert.strictEqual(diagnostic2.isValid(), true);214215// First diagnostic range should be unchanged216assert.strictEqual(diagnostic1.range.start, 0);217assert.strictEqual(diagnostic1.range.endExclusive, 5);218219// Second diagnostic range should be shifted by 4 positions ("big ")220assert.strictEqual(diagnostic2.range.start, 16);221assert.strictEqual(diagnostic2.range.endExclusive, 20);222});223224test('should handle edge case with empty edge character', () => {225const collection = new DiagnosticsCollection();226const diagnostic = createDiagnostic('Test error', new OffsetRange(12, 16)); // "test" = positions 12-15 (0-based)227collection.isEqualAndUpdate([diagnostic]);228229const before = new StringText('hello world test');230const after = new StringText('hello world testx'); // Add 'x' at end231232// Replace "test" with "testx"233const edit = StringEdit.replace(new OffsetRange(12, 16), 'testx'); // 0-based: 12-15234235const hasInvalidated = collection.applyEdit(before, edit, after);236237// Since 'x' is alphanumeric, should invalidate238assert.strictEqual(hasInvalidated, true);239assert.strictEqual(diagnostic.isValid(), false);240});241242test('should handle suffix match with non-alphanumeric edge character', () => {243const collection = new DiagnosticsCollection();244const diagnostic = createDiagnostic('Test error', new OffsetRange(12, 16)); // "test" = positions 12-15 (0-based)245collection.isEqualAndUpdate([diagnostic]);246247const before = new StringText('hello world test');248const after = new StringText('hello world .test'); // "test" becomes ".test"249250// Replace "test" with ".test"251const edit = StringEdit.replace(new OffsetRange(12, 16), '.test'); // 0-based: 12-15252253const hasInvalidated = collection.applyEdit(before, edit, after);254255assert.strictEqual(hasInvalidated, false);256assert.strictEqual(diagnostic.isValid(), true);257// Range should point to the suffix "test" part258assert.strictEqual(diagnostic.range.start, 13);259assert.strictEqual(diagnostic.range.endExclusive, 17); // 17 + 1 (".")260});261262test('should handle case where newOffsetRange is null', () => {263const collection = new DiagnosticsCollection();264const diagnostic = createDiagnostic('Test error', new OffsetRange(12, 16)); // "test" = positions 12-15 (0-based)265collection.isEqualAndUpdate([diagnostic]);266267// Mock applyEditsToRanges to return null (would happen if range is completely removed)268const before = new StringText('hello world test');269const after = new StringText('hello world'); // "test" completely removed270271// Remove " test" completely (0-based: positions 11-15)272const edit = StringEdit.replace(new OffsetRange(11, 16), '');273274const hasInvalidated = collection.applyEdit(before, edit, after);275276assert.strictEqual(hasInvalidated, true);277assert.strictEqual(diagnostic.isValid(), false);278});279});280});281282283