Path: blob/main/extensions/copilot/src/extension/inlineEdits/test/node/rejectionCollector.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 { outdent } from 'outdent';6import { describe, expect, test } from 'vitest';7import { DocumentId } from '../../../../platform/inlineEdits/common/dataTypes/documentId';8import { IObservableDocument, MutableObservableWorkspace } from '../../../../platform/inlineEdits/common/observableWorkspace';9import { TestLogService } from '../../../../platform/testing/common/testLogService';10import { runOnChange } from '../../../../util/vs/base/common/observableInternal';11import { URI } from '../../../../util/vs/base/common/uri';12import { StringEdit, StringReplacement } from '../../../../util/vs/editor/common/core/edits/stringEdit';13import { OffsetRange } from '../../../../util/vs/editor/common/core/ranges/offsetRange';14import { IRecordingInformation } from '../../common/observableWorkspaceRecordingReplayer';15import { RejectionCollector } from '../../common/rejectionCollector';16import { loadJSON, relativeFile } from './fileLoading';17import { runRecording } from './runRecording';1819describe('RejectionCollector[visualizable]', () => {20test('test1', async () => {21const result = await runRecording(22await loadJSON<IRecordingInformation>({23filePath: relativeFile('recordings/RejectionCollector.test1.w.json'),24}),25ctx => {26const rejs: (boolean | string)[] = [];2728const rejectionCollector = ctx.store.add(new RejectionCollector(ctx.workspace, new TestLogService()));2930ctx.workspace.lastActiveDocument.recomputeInitiallyAndOnChange(ctx.store);3132const getEdit = (doc: IObservableDocument | undefined = undefined) => {33if (!doc) {34doc = ctx.workspace.lastActiveDocument.get();35}36if (!doc) {37return undefined;38}3940const edit = createEdit(41doc.value.get().value,42`items.push([[oldItem ? item.withIdentity(oldItem.identity) : item]]);`,43`OLDiTEM`,44);45if (!edit) {46return undefined;47}4849return { edit, doc };50};5152while (!getEdit()) {53if (!ctx.step()) {54return { rejs };55}56}57const { doc } = getEdit()!;5859ctx.store.add(runOnChange(ctx.workspace.onDidOpenDocumentChange, () => {60const e = getEdit(doc);61if (e) {62rejs.push(rejectionCollector.isRejected(doc.id, e.edit));63} else {64rejs.push('edit not found');65}66}));6768ctx.stepSkipNonContentChanges();6970const { edit } = getEdit()!;71rejectionCollector.reject(doc.id, edit);7273ctx.finishReplay();7475return { rejs };76}77);7879expect(result.rejs).toMatchInlineSnapshot(`80[81false,82true,83true,84true,85true,86true,87true,88true,89true,90true,91true,92true,93true,94true,95true,96true,97true,98true,99true,100true,101true,102true,103true,104true,105true,106true,107true,108true,109true,110true,111true,112true,113true,114true,115true,116"edit not found",117]118`);119});120test.skip('overlapping', () => {121const observableWorkspace = new MutableObservableWorkspace();122const doc = observableWorkspace.addDocument({123id: DocumentId.create(URI.file('/test/test.ts').toString()),124initialValue: outdent`125class Point {126constructor(127private readonly x: number,128private readonly y: number,129) { }130getDistance() {131return Math.sqrt(this.x ** 2 + this.y ** 2);132}133}134`.trim()135});136137const rejectionCollector = new RejectionCollector(observableWorkspace, new TestLogService());138try {139const edit1 = StringReplacement.replace(OffsetRange.fromTo(96, 107), 'fo');140expect(rejectionCollector.isRejected(doc.id, edit1)).toBe(false);141const rej1 = StringReplacement.replace(OffsetRange.fromTo(96, 107), 'foobar');142rejectionCollector.reject(doc.id, rej1);143expect(rejectionCollector.isRejected(doc.id, rej1)).toBe(true);144145expect(rejectionCollector.isRejected(doc.id, edit1)).toBe(false);146doc.applyEdit(StringEdit.single(edit1));147expect(rejectionCollector.isRejected(doc.id, StringReplacement.replace(OffsetRange.fromTo(98, 98), 'obar'))).toBe(true);148149const edit2 = StringReplacement.replace(OffsetRange.fromTo(98, 98), 'ob');150expect(rejectionCollector.isRejected(doc.id, edit2)).toBe(false);151doc.applyEdit(StringEdit.single(edit2));152expect(rejectionCollector.isRejected(doc.id, StringReplacement.replace(OffsetRange.fromTo(100, 100), 'ar'))).toBe(true);153154const edit3 = StringReplacement.replace(OffsetRange.fromTo(100, 100), 'A');155expect(rejectionCollector.isRejected(doc.id, edit3)).toBe(false);156doc.applyEdit(StringEdit.single(edit3));157// now evicted158expect(rejectionCollector.isRejected(doc.id, StringReplacement.replace(OffsetRange.fromTo(101, 101), 'r'))).toBe(false);159} finally {160rejectionCollector.dispose();161}162});163});164165166/**167* Match is context[[valueToReplace]]context168*/169function createEdit(base: string, match: string, newValue: string): StringReplacement | undefined {170let cleanedMatch: string;171const idxStart = match.indexOf('[[');172const idxEnd = match.indexOf(']]') - 2;173174let range: OffsetRange;175if (idxStart === -1 || idxEnd === -3) {176range = new OffsetRange(0, match.length);177cleanedMatch = match;178} else {179range = new OffsetRange(idxStart, idxEnd);180cleanedMatch = match.replace('[[', '').replace(']]', '');181}182183const idx = base.indexOf(cleanedMatch);184if (idx === -1) {185return undefined;186}187188const r = range.delta(idx);189return StringReplacement.replace(r, newValue);190}191192193