Path: blob/main/src/vs/sessions/contrib/agentFeedback/test/browser/agentFeedbackService.test.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 assert from 'assert';6import { URI } from '../../../../../base/common/uri.js';7import { Range } from '../../../../../editor/common/core/range.js';8import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js';9import { TestInstantiationService } from '../../../../../platform/instantiation/test/common/instantiationServiceMock.js';10import { mock } from '../../../../../base/test/common/mock.js';11import { AgentFeedbackService, IAgentFeedbackService } from '../../browser/agentFeedbackService.js';12import { IChatEditingService } from '../../../../../workbench/contrib/chat/common/editing/chatEditingService.js';13import { IAgentSessionsService } from '../../../../../workbench/contrib/chat/browser/agentSessions/agentSessionsService.js';14import { DisposableStore } from '../../../../../base/common/lifecycle.js';15import { NullTelemetryService } from '../../../../../platform/telemetry/common/telemetryUtils.js';16import { ITelemetryService } from '../../../../../platform/telemetry/common/telemetry.js';1718function r(startLine: number, endLine: number = startLine): Range {19return new Range(startLine, 1, endLine, 1);20}2122function feedbackSummary(items: readonly { resourceUri: URI; range: { startLineNumber: number } }[]): string[] {23return items.map(f => `${f.resourceUri.path}:${f.range.startLineNumber}`);24}2526suite('AgentFeedbackService - Ordering', () => {2728const store = new DisposableStore();29let service: IAgentFeedbackService;30let session: URI;31let fileA: URI;32let fileB: URI;33let fileC: URI;3435setup(() => {36const instantiationService = store.add(new TestInstantiationService());3738instantiationService.stub(IChatEditingService, new class extends mock<IChatEditingService>() { });39instantiationService.stub(IAgentSessionsService, new class extends mock<IAgentSessionsService>() { });40instantiationService.stub(ITelemetryService, NullTelemetryService);4142service = store.add(instantiationService.createInstance(AgentFeedbackService));43session = URI.parse('test://session/1');44fileA = URI.parse('file:///a.ts');45fileB = URI.parse('file:///b.ts');46fileC = URI.parse('file:///c.ts');47});4849teardown(() => {50store.clear();51});5253ensureNoDisposablesAreLeakedInTestSuite();5455test('single file - items sorted by line number', () => {56service.addFeedback(session, fileA, r(20), 'line 20');57service.addFeedback(session, fileA, r(5), 'line 5');58service.addFeedback(session, fileA, r(10), 'line 10');5960assert.deepStrictEqual(feedbackSummary(service.getFeedback(session)), [61'/a.ts:5',62'/a.ts:10',63'/a.ts:20',64]);65});6667test('multiple files - files ordered by recency, items within file sorted by line', () => {68service.addFeedback(session, fileA, r(10), 'A:10');69service.addFeedback(session, fileA, r(5), 'A:5');70service.addFeedback(session, fileB, r(20), 'B:20');71service.addFeedback(session, fileB, r(3), 'B:3');7273assert.deepStrictEqual(feedbackSummary(service.getFeedback(session)), [74'/a.ts:5',75'/a.ts:10',76'/b.ts:3',77'/b.ts:20',78]);79});8081test('new file appended to end', () => {82service.addFeedback(session, fileA, r(1), 'A:1');83service.addFeedback(session, fileB, r(1), 'B:1');84service.addFeedback(session, fileC, r(1), 'C:1');8586assert.deepStrictEqual(feedbackSummary(service.getFeedback(session)), [87'/a.ts:1',88'/b.ts:1',89'/c.ts:1',90]);91});9293test('adding to existing file does not change file ordering', () => {94service.addFeedback(session, fileA, r(10), 'A:10');95service.addFeedback(session, fileB, r(10), 'B:10');96// Add more feedback to fileA — should stay before fileB97service.addFeedback(session, fileA, r(5), 'A:5');98service.addFeedback(session, fileA, r(20), 'A:20');99100assert.deepStrictEqual(feedbackSummary(service.getFeedback(session)), [101'/a.ts:5',102'/a.ts:10',103'/a.ts:20',104'/b.ts:10',105]);106});107108test('interleaved adds across files maintain file recency and line sort', () => {109service.addFeedback(session, fileA, r(30), 'A:30');110service.addFeedback(session, fileB, r(50), 'B:50');111service.addFeedback(session, fileA, r(10), 'A:10');112service.addFeedback(session, fileC, r(1), 'C:1');113service.addFeedback(session, fileB, r(5), 'B:5');114service.addFeedback(session, fileA, r(20), 'A:20');115116assert.deepStrictEqual(feedbackSummary(service.getFeedback(session)), [117'/a.ts:10',118'/a.ts:20',119'/a.ts:30',120'/b.ts:5',121'/b.ts:50',122'/c.ts:1',123]);124});125126test('navigation follows sorted order', () => {127service.addFeedback(session, fileA, r(20), 'A:20');128service.addFeedback(session, fileB, r(10), 'B:10');129service.addFeedback(session, fileA, r(5), 'A:5');130131// Expected order: A:5, A:20, B:10132const first = service.getNextFeedback(session, true)!;133assert.strictEqual(first.resourceUri.path, '/a.ts');134assert.strictEqual(first.range.startLineNumber, 5);135136const second = service.getNextFeedback(session, true)!;137assert.strictEqual(second.resourceUri.path, '/a.ts');138assert.strictEqual(second.range.startLineNumber, 20);139140const third = service.getNextFeedback(session, true)!;141assert.strictEqual(third.resourceUri.path, '/b.ts');142assert.strictEqual(third.range.startLineNumber, 10);143144// Wraps around145const fourth = service.getNextFeedback(session, true)!;146assert.strictEqual(fourth.resourceUri.path, '/a.ts');147assert.strictEqual(fourth.range.startLineNumber, 5);148});149150test('navigation bearings reflect sorted position', () => {151service.addFeedback(session, fileA, r(20), 'A:20');152service.addFeedback(session, fileA, r(5), 'A:5');153service.addFeedback(session, fileB, r(1), 'B:1');154155// Before navigation, no anchor156let bearing = service.getNavigationBearing(session);157assert.strictEqual(bearing.activeIdx, -1);158assert.strictEqual(bearing.totalCount, 3);159160// Navigate to first (A:5)161service.getNextFeedback(session, true);162bearing = service.getNavigationBearing(session);163assert.strictEqual(bearing.activeIdx, 0);164165// Navigate to second (A:20)166service.getNextFeedback(session, true);167bearing = service.getNavigationBearing(session);168assert.strictEqual(bearing.activeIdx, 1);169170// Navigate to third (B:1)171service.getNextFeedback(session, true);172bearing = service.getNavigationBearing(session);173assert.strictEqual(bearing.activeIdx, 2);174});175176test('removing feedback preserves ordering', () => {177const f1 = service.addFeedback(session, fileA, r(30), 'A:30');178service.addFeedback(session, fileA, r(10), 'A:10');179service.addFeedback(session, fileA, r(20), 'A:20');180181assert.deepStrictEqual(feedbackSummary(service.getFeedback(session)), [182'/a.ts:10',183'/a.ts:20',184'/a.ts:30',185]);186187service.removeFeedback(session, f1.id);188assert.deepStrictEqual(feedbackSummary(service.getFeedback(session)), [189'/a.ts:10',190'/a.ts:20',191]);192});193194test('same line number items are stable', () => {195const f1 = service.addFeedback(session, fileA, r(10), 'first');196const f2 = service.addFeedback(session, fileA, r(10), 'second');197198const items = service.getFeedback(session);199assert.strictEqual(items[0].id, f1.id);200assert.strictEqual(items[1].id, f2.id);201});202203test('preserves optional feedback context fields', () => {204const feedback = service.addFeedback(session, fileA, r(10), 'with context', undefined, {205codeSelection: 'const value = 1;',206diffHunks: '@@ -1,1 +1,1 @@\n-const value = 0;\n+const value = 1;',207});208209assert.strictEqual(feedback.codeSelection, 'const value = 1;');210assert.strictEqual(feedback.diffHunks, '@@ -1,1 +1,1 @@\n-const value = 0;\n+const value = 1;');211});212});213214215