Path: blob/main/src/vs/sessions/contrib/agentFeedback/test/browser/agentFeedbackEditorWidget.fixture.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 { Event } from '../../../../../base/common/event.js';6import { Color } from '../../../../../base/common/color.js';7import { DisposableStore } from '../../../../../base/common/lifecycle.js';8import { IMarkdownRendererService, MarkdownRendererService } from '../../../../../platform/markdown/browser/markdownRenderer.js';9import { URI } from '../../../../../base/common/uri.js';10import { mock } from '../../../../../base/test/common/mock.js';11import { CodeEditorWidget, ICodeEditorWidgetOptions } from '../../../../../editor/browser/widget/codeEditor/codeEditorWidget.js';12import { IRange } from '../../../../../editor/common/core/range.js';13import { TokenizationRegistry } from '../../../../../editor/common/languages.js';14import { IAgentFeedback, IAgentFeedbackService } from '../../browser/agentFeedbackService.js';15import { AgentFeedbackEditorWidget } from '../../browser/agentFeedbackEditorWidgetContribution.js';16import { ComponentFixtureContext, createEditorServices, createTextModel, defineComponentFixture, defineThemedFixtureGroup } from '../../../../../workbench/test/browser/componentFixtures/fixtureUtils.js';17import { ICodeReviewService, ICodeReviewSuggestion } from '../../../codeReview/browser/codeReviewService.js';18import { createMockCodeReviewService } from '../../../../../workbench/test/browser/componentFixtures/sessions/mockCodeReviewService.js';19import { ISessionEditorComment, SessionEditorCommentSource } from '../../browser/sessionEditorComments.js';2021const sessionResource = URI.parse('vscode-agent-session://fixture/session-1');22const fileResource = URI.parse('inmemory://model/agent-feedback-widget.ts');2324const sampleCode = [25'function alpha() {',26'\tconst first = 1;',27'\treturn first;',28'}',29'',30'function beta() {',31'\tconst second = 2;',32'\tconst third = second + 1;',33'\treturn third;',34'}',35'',36'function gamma() {',37'\tconst done = true;',38'\treturn done;',39'}',40].join('\n');4142interface IFixtureOptions {43readonly expanded?: boolean;44readonly focusedCommentId?: string;45readonly hidden?: boolean;46readonly commentItems: readonly ISessionEditorComment[];47}4849function createRange(startLineNumber: number, endLineNumber: number = startLineNumber): IRange {50return {51startLineNumber,52startColumn: 1,53endLineNumber,54endColumn: 1,55};56}5758function createFeedbackComment(id: string, text: string, startLineNumber: number, endLineNumber: number = startLineNumber, suggestion?: ICodeReviewSuggestion): ISessionEditorComment {59return {60id: `agentFeedback:${id}`,61sourceId: id,62source: SessionEditorCommentSource.AgentFeedback,63sessionResource,64resourceUri: fileResource,65range: createRange(startLineNumber, endLineNumber),66text,67suggestion,68canConvertToAgentFeedback: false,69};70}7172function createReviewComment(id: string, text: string, startLineNumber: number, endLineNumber: number = startLineNumber, suggestion?: ICodeReviewSuggestion): ISessionEditorComment {73const range: IRange = {74startLineNumber,75startColumn: 1,76endLineNumber,77endColumn: 1,78};7980return {81id: `codeReview:${id}`,82sourceId: id,83source: SessionEditorCommentSource.CodeReview,84text,85resourceUri: fileResource,86range,87sessionResource,88suggestion,89severity: 'warning',90canConvertToAgentFeedback: true,91};92}9394function createPRReviewComment(id: string, text: string, startLineNumber: number, endLineNumber: number = startLineNumber): ISessionEditorComment {95return {96id: `prReview:${id}`,97sourceId: id,98source: SessionEditorCommentSource.PRReview,99text,100resourceUri: fileResource,101range: createRange(startLineNumber, endLineNumber),102sessionResource,103canConvertToAgentFeedback: true,104};105}106107function createMockAgentFeedbackService(): IAgentFeedbackService {108return new class extends mock<IAgentFeedbackService>() {109override readonly onDidChangeFeedback = Event.None;110override readonly onDidChangeNavigation = Event.None;111112override addFeedback(): IAgentFeedback {113throw new Error('Not implemented for fixture');114}115116override removeFeedback(): void { }117118override getFeedback(): readonly IAgentFeedback[] {119return [];120}121122override getMostRecentSessionForResource(): URI | undefined {123return undefined;124}125126override async revealFeedback(): Promise<void> { }127128override getNextFeedback(): IAgentFeedback | undefined {129return undefined;130}131132override getNavigationBearing() {133return { activeIdx: -1, totalCount: 0 };134}135136override getNextNavigableItem() {137return undefined;138}139140override setNavigationAnchor(): void { }141142override clearFeedback(): void { }143144override async addFeedbackAndSubmit(): Promise<void> { }145}();146}147148function ensureTokenColorMap(): void {149if (TokenizationRegistry.getColorMap()?.length) {150return;151}152153const colorMap = [154Color.fromHex('#000000'),155Color.fromHex('#d4d4d4'),156Color.fromHex('#9cdcfe'),157Color.fromHex('#ce9178'),158Color.fromHex('#b5cea8'),159Color.fromHex('#4fc1ff'),160Color.fromHex('#c586c0'),161Color.fromHex('#569cd6'),162Color.fromHex('#dcdcaa'),163Color.fromHex('#f44747'),164];165166TokenizationRegistry.setColorMap(colorMap);167}168169function renderWidget(context: ComponentFixtureContext, options: IFixtureOptions): void {170const scopedDisposables = context.disposableStore.add(new DisposableStore());171context.container.style.width = '760px';172context.container.style.height = '420px';173context.container.style.border = '1px solid var(--vscode-editorWidget-border)';174context.container.style.background = 'var(--vscode-editor-background)';175176ensureTokenColorMap();177178const agentFeedbackService = createMockAgentFeedbackService();179const codeReviewService = createMockCodeReviewService();180const instantiationService = createEditorServices(scopedDisposables, {181colorTheme: context.theme,182additionalServices: reg => {183reg.defineInstance(IAgentFeedbackService, agentFeedbackService);184reg.defineInstance(ICodeReviewService, codeReviewService);185reg.define(IMarkdownRendererService, MarkdownRendererService);186},187});188const model = scopedDisposables.add(createTextModel(instantiationService, sampleCode, fileResource, 'typescript'));189190const editorOptions: ICodeEditorWidgetOptions = {191contributions: [],192};193194const editor = scopedDisposables.add(instantiationService.createInstance(195CodeEditorWidget,196context.container,197{198automaticLayout: true,199lineNumbers: 'on',200minimap: { enabled: false },201scrollBeyondLastLine: false,202fontSize: 13,203lineHeight: 20,204},205editorOptions206));207208editor.setModel(model);209210const widget = scopedDisposables.add(instantiationService.createInstance(211AgentFeedbackEditorWidget,212editor,213options.commentItems,214sessionResource,215));216217widget.layout(options.commentItems[0].range.startLineNumber);218219if (options.expanded) {220widget.expand();221}222223if (options.focusedCommentId) {224widget.focusFeedback(options.focusedCommentId);225}226227if (options.hidden) {228const domNode = widget.getDomNode();229domNode.style.transition = 'none';230domNode.style.animation = 'none';231widget.toggle(false);232}233}234235const singleFeedback = [236createFeedbackComment('f-1', 'Prefer a clearer variable name on this line.', 2),237];238239const groupedFeedback = [240createFeedbackComment('f-1', 'Prefer a clearer variable name on this line.', 2),241createFeedbackComment('f-2', 'This return statement can be simplified.', 3),242createFeedbackComment('f-3', 'Consider documenting why this branch is needed.', 6, 8),243];244245const reviewOnly = [246createReviewComment('r-1', 'Handle the null case before returning here.', 7),247createReviewComment('r-2', 'This branch needs a stronger explanation.', 8),248];249250const mixedComments = [251createFeedbackComment('f-1', 'Prefer a clearer variable name on this line.', 2),252createReviewComment('r-1', 'This should be extracted into a helper.', 3),253createFeedbackComment('f-2', 'Consider renaming this for readability.', 4),254];255256const reviewSuggestion: ICodeReviewSuggestion = {257edits: [258{ range: createRange(8), oldText: '\tconst third = second + 1;', newText: '\tconst third = second + computeOffset();' },259],260};261262const suggestionMix = [263createReviewComment('r-3', 'Prefer using the helper so the intent is explicit.', 8, 8, reviewSuggestion),264createFeedbackComment('f-3', 'Keep the helper name aligned with the domain concept.', 9),265];266267const prReviewOnly = [268createPRReviewComment('pr-1', 'This variable should be renamed to match our naming conventions.', 2),269createPRReviewComment('pr-2', 'Please add error handling for the edge case when second is zero.', 7, 8),270];271272const allSourcesMixed = [273createFeedbackComment('f-1', 'Prefer a clearer variable name on this line.', 2),274createPRReviewComment('pr-1', 'Our style guide says to use descriptive names here.', 3),275createReviewComment('r-1', 'This should be extracted into a helper.', 6),276createPRReviewComment('pr-2', 'This logic duplicates what we have in utils.ts — consider reusing.', 8, 9),277];278279export default defineThemedFixtureGroup({ path: 'sessions/agentFeedback/' }, {280CollapsedSingleComment: defineComponentFixture({281labels: { kind: 'screenshot' },282render: context => renderWidget(context, {283commentItems: singleFeedback,284}),285}),286287ExpandedSingleComment: defineComponentFixture({288labels: { kind: 'screenshot' },289render: context => renderWidget(context, {290commentItems: singleFeedback,291expanded: true,292}),293}),294295CollapsedMultiComment: defineComponentFixture({296labels: { kind: 'screenshot' },297render: context => renderWidget(context, {298commentItems: groupedFeedback,299}),300}),301302ExpandedMultiComment: defineComponentFixture({303labels: { kind: 'screenshot' },304render: context => renderWidget(context, {305commentItems: groupedFeedback,306expanded: true,307}),308}),309310ExpandedFocusedFeedback: defineComponentFixture({311labels: { kind: 'screenshot' },312render: context => renderWidget(context, {313commentItems: groupedFeedback,314expanded: true,315focusedCommentId: 'agentFeedback:f-2',316}),317}),318319ExpandedReviewOnly: defineComponentFixture({320labels: { kind: 'screenshot' },321render: context => renderWidget(context, {322commentItems: reviewOnly,323expanded: true,324}),325}),326327ExpandedMixedComments: defineComponentFixture({328labels: { kind: 'screenshot' },329render: context => renderWidget(context, {330commentItems: mixedComments,331expanded: true,332}),333}),334335ExpandedFocusedReviewComment: defineComponentFixture({336labels: { kind: 'screenshot' },337render: context => renderWidget(context, {338commentItems: mixedComments,339expanded: true,340focusedCommentId: 'codeReview:r-1',341}),342}),343344ExpandedReviewSuggestion: defineComponentFixture({345labels: { kind: 'screenshot' },346render: context => renderWidget(context, {347commentItems: suggestionMix,348expanded: true,349}),350}),351352ExpandedPRReviewOnly: defineComponentFixture({353labels: { kind: 'screenshot' },354render: context => renderWidget(context, {355commentItems: prReviewOnly,356expanded: true,357}),358}),359360ExpandedAllSourcesMixed: defineComponentFixture({361labels: { kind: 'screenshot' },362render: context => renderWidget(context, {363commentItems: allSourcesMixed,364expanded: true,365}),366}),367368ExpandedFocusedPRReview: defineComponentFixture({369labels: { kind: 'screenshot' },370render: context => renderWidget(context, {371commentItems: allSourcesMixed,372expanded: true,373focusedCommentId: 'prReview:pr-2',374}),375}),376377HiddenWidget: defineComponentFixture({378labels: { kind: 'screenshot' },379render: context => renderWidget(context, {380commentItems: mixedComments,381hidden: true,382}),383}),384});385386387