Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/sessions/contrib/agentFeedback/test/browser/agentFeedbackEditorWidget.fixture.ts
13405 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { Event } from '../../../../../base/common/event.js';
7
import { Color } from '../../../../../base/common/color.js';
8
import { DisposableStore } from '../../../../../base/common/lifecycle.js';
9
import { IMarkdownRendererService, MarkdownRendererService } from '../../../../../platform/markdown/browser/markdownRenderer.js';
10
import { URI } from '../../../../../base/common/uri.js';
11
import { mock } from '../../../../../base/test/common/mock.js';
12
import { CodeEditorWidget, ICodeEditorWidgetOptions } from '../../../../../editor/browser/widget/codeEditor/codeEditorWidget.js';
13
import { IRange } from '../../../../../editor/common/core/range.js';
14
import { TokenizationRegistry } from '../../../../../editor/common/languages.js';
15
import { IAgentFeedback, IAgentFeedbackService } from '../../browser/agentFeedbackService.js';
16
import { AgentFeedbackEditorWidget } from '../../browser/agentFeedbackEditorWidgetContribution.js';
17
import { ComponentFixtureContext, createEditorServices, createTextModel, defineComponentFixture, defineThemedFixtureGroup } from '../../../../../workbench/test/browser/componentFixtures/fixtureUtils.js';
18
import { ICodeReviewService, ICodeReviewSuggestion } from '../../../codeReview/browser/codeReviewService.js';
19
import { createMockCodeReviewService } from '../../../../../workbench/test/browser/componentFixtures/sessions/mockCodeReviewService.js';
20
import { ISessionEditorComment, SessionEditorCommentSource } from '../../browser/sessionEditorComments.js';
21
22
const sessionResource = URI.parse('vscode-agent-session://fixture/session-1');
23
const fileResource = URI.parse('inmemory://model/agent-feedback-widget.ts');
24
25
const sampleCode = [
26
'function alpha() {',
27
'\tconst first = 1;',
28
'\treturn first;',
29
'}',
30
'',
31
'function beta() {',
32
'\tconst second = 2;',
33
'\tconst third = second + 1;',
34
'\treturn third;',
35
'}',
36
'',
37
'function gamma() {',
38
'\tconst done = true;',
39
'\treturn done;',
40
'}',
41
].join('\n');
42
43
interface IFixtureOptions {
44
readonly expanded?: boolean;
45
readonly focusedCommentId?: string;
46
readonly hidden?: boolean;
47
readonly commentItems: readonly ISessionEditorComment[];
48
}
49
50
function createRange(startLineNumber: number, endLineNumber: number = startLineNumber): IRange {
51
return {
52
startLineNumber,
53
startColumn: 1,
54
endLineNumber,
55
endColumn: 1,
56
};
57
}
58
59
function createFeedbackComment(id: string, text: string, startLineNumber: number, endLineNumber: number = startLineNumber, suggestion?: ICodeReviewSuggestion): ISessionEditorComment {
60
return {
61
id: `agentFeedback:${id}`,
62
sourceId: id,
63
source: SessionEditorCommentSource.AgentFeedback,
64
sessionResource,
65
resourceUri: fileResource,
66
range: createRange(startLineNumber, endLineNumber),
67
text,
68
suggestion,
69
canConvertToAgentFeedback: false,
70
};
71
}
72
73
function createReviewComment(id: string, text: string, startLineNumber: number, endLineNumber: number = startLineNumber, suggestion?: ICodeReviewSuggestion): ISessionEditorComment {
74
const range: IRange = {
75
startLineNumber,
76
startColumn: 1,
77
endLineNumber,
78
endColumn: 1,
79
};
80
81
return {
82
id: `codeReview:${id}`,
83
sourceId: id,
84
source: SessionEditorCommentSource.CodeReview,
85
text,
86
resourceUri: fileResource,
87
range,
88
sessionResource,
89
suggestion,
90
severity: 'warning',
91
canConvertToAgentFeedback: true,
92
};
93
}
94
95
function createPRReviewComment(id: string, text: string, startLineNumber: number, endLineNumber: number = startLineNumber): ISessionEditorComment {
96
return {
97
id: `prReview:${id}`,
98
sourceId: id,
99
source: SessionEditorCommentSource.PRReview,
100
text,
101
resourceUri: fileResource,
102
range: createRange(startLineNumber, endLineNumber),
103
sessionResource,
104
canConvertToAgentFeedback: true,
105
};
106
}
107
108
function createMockAgentFeedbackService(): IAgentFeedbackService {
109
return new class extends mock<IAgentFeedbackService>() {
110
override readonly onDidChangeFeedback = Event.None;
111
override readonly onDidChangeNavigation = Event.None;
112
113
override addFeedback(): IAgentFeedback {
114
throw new Error('Not implemented for fixture');
115
}
116
117
override removeFeedback(): void { }
118
119
override getFeedback(): readonly IAgentFeedback[] {
120
return [];
121
}
122
123
override getMostRecentSessionForResource(): URI | undefined {
124
return undefined;
125
}
126
127
override async revealFeedback(): Promise<void> { }
128
129
override getNextFeedback(): IAgentFeedback | undefined {
130
return undefined;
131
}
132
133
override getNavigationBearing() {
134
return { activeIdx: -1, totalCount: 0 };
135
}
136
137
override getNextNavigableItem() {
138
return undefined;
139
}
140
141
override setNavigationAnchor(): void { }
142
143
override clearFeedback(): void { }
144
145
override async addFeedbackAndSubmit(): Promise<void> { }
146
}();
147
}
148
149
function ensureTokenColorMap(): void {
150
if (TokenizationRegistry.getColorMap()?.length) {
151
return;
152
}
153
154
const colorMap = [
155
Color.fromHex('#000000'),
156
Color.fromHex('#d4d4d4'),
157
Color.fromHex('#9cdcfe'),
158
Color.fromHex('#ce9178'),
159
Color.fromHex('#b5cea8'),
160
Color.fromHex('#4fc1ff'),
161
Color.fromHex('#c586c0'),
162
Color.fromHex('#569cd6'),
163
Color.fromHex('#dcdcaa'),
164
Color.fromHex('#f44747'),
165
];
166
167
TokenizationRegistry.setColorMap(colorMap);
168
}
169
170
function renderWidget(context: ComponentFixtureContext, options: IFixtureOptions): void {
171
const scopedDisposables = context.disposableStore.add(new DisposableStore());
172
context.container.style.width = '760px';
173
context.container.style.height = '420px';
174
context.container.style.border = '1px solid var(--vscode-editorWidget-border)';
175
context.container.style.background = 'var(--vscode-editor-background)';
176
177
ensureTokenColorMap();
178
179
const agentFeedbackService = createMockAgentFeedbackService();
180
const codeReviewService = createMockCodeReviewService();
181
const instantiationService = createEditorServices(scopedDisposables, {
182
colorTheme: context.theme,
183
additionalServices: reg => {
184
reg.defineInstance(IAgentFeedbackService, agentFeedbackService);
185
reg.defineInstance(ICodeReviewService, codeReviewService);
186
reg.define(IMarkdownRendererService, MarkdownRendererService);
187
},
188
});
189
const model = scopedDisposables.add(createTextModel(instantiationService, sampleCode, fileResource, 'typescript'));
190
191
const editorOptions: ICodeEditorWidgetOptions = {
192
contributions: [],
193
};
194
195
const editor = scopedDisposables.add(instantiationService.createInstance(
196
CodeEditorWidget,
197
context.container,
198
{
199
automaticLayout: true,
200
lineNumbers: 'on',
201
minimap: { enabled: false },
202
scrollBeyondLastLine: false,
203
fontSize: 13,
204
lineHeight: 20,
205
},
206
editorOptions
207
));
208
209
editor.setModel(model);
210
211
const widget = scopedDisposables.add(instantiationService.createInstance(
212
AgentFeedbackEditorWidget,
213
editor,
214
options.commentItems,
215
sessionResource,
216
));
217
218
widget.layout(options.commentItems[0].range.startLineNumber);
219
220
if (options.expanded) {
221
widget.expand();
222
}
223
224
if (options.focusedCommentId) {
225
widget.focusFeedback(options.focusedCommentId);
226
}
227
228
if (options.hidden) {
229
const domNode = widget.getDomNode();
230
domNode.style.transition = 'none';
231
domNode.style.animation = 'none';
232
widget.toggle(false);
233
}
234
}
235
236
const singleFeedback = [
237
createFeedbackComment('f-1', 'Prefer a clearer variable name on this line.', 2),
238
];
239
240
const groupedFeedback = [
241
createFeedbackComment('f-1', 'Prefer a clearer variable name on this line.', 2),
242
createFeedbackComment('f-2', 'This return statement can be simplified.', 3),
243
createFeedbackComment('f-3', 'Consider documenting why this branch is needed.', 6, 8),
244
];
245
246
const reviewOnly = [
247
createReviewComment('r-1', 'Handle the null case before returning here.', 7),
248
createReviewComment('r-2', 'This branch needs a stronger explanation.', 8),
249
];
250
251
const mixedComments = [
252
createFeedbackComment('f-1', 'Prefer a clearer variable name on this line.', 2),
253
createReviewComment('r-1', 'This should be extracted into a helper.', 3),
254
createFeedbackComment('f-2', 'Consider renaming this for readability.', 4),
255
];
256
257
const reviewSuggestion: ICodeReviewSuggestion = {
258
edits: [
259
{ range: createRange(8), oldText: '\tconst third = second + 1;', newText: '\tconst third = second + computeOffset();' },
260
],
261
};
262
263
const suggestionMix = [
264
createReviewComment('r-3', 'Prefer using the helper so the intent is explicit.', 8, 8, reviewSuggestion),
265
createFeedbackComment('f-3', 'Keep the helper name aligned with the domain concept.', 9),
266
];
267
268
const prReviewOnly = [
269
createPRReviewComment('pr-1', 'This variable should be renamed to match our naming conventions.', 2),
270
createPRReviewComment('pr-2', 'Please add error handling for the edge case when second is zero.', 7, 8),
271
];
272
273
const allSourcesMixed = [
274
createFeedbackComment('f-1', 'Prefer a clearer variable name on this line.', 2),
275
createPRReviewComment('pr-1', 'Our style guide says to use descriptive names here.', 3),
276
createReviewComment('r-1', 'This should be extracted into a helper.', 6),
277
createPRReviewComment('pr-2', 'This logic duplicates what we have in utils.ts — consider reusing.', 8, 9),
278
];
279
280
export default defineThemedFixtureGroup({ path: 'sessions/agentFeedback/' }, {
281
CollapsedSingleComment: defineComponentFixture({
282
labels: { kind: 'screenshot' },
283
render: context => renderWidget(context, {
284
commentItems: singleFeedback,
285
}),
286
}),
287
288
ExpandedSingleComment: defineComponentFixture({
289
labels: { kind: 'screenshot' },
290
render: context => renderWidget(context, {
291
commentItems: singleFeedback,
292
expanded: true,
293
}),
294
}),
295
296
CollapsedMultiComment: defineComponentFixture({
297
labels: { kind: 'screenshot' },
298
render: context => renderWidget(context, {
299
commentItems: groupedFeedback,
300
}),
301
}),
302
303
ExpandedMultiComment: defineComponentFixture({
304
labels: { kind: 'screenshot' },
305
render: context => renderWidget(context, {
306
commentItems: groupedFeedback,
307
expanded: true,
308
}),
309
}),
310
311
ExpandedFocusedFeedback: defineComponentFixture({
312
labels: { kind: 'screenshot' },
313
render: context => renderWidget(context, {
314
commentItems: groupedFeedback,
315
expanded: true,
316
focusedCommentId: 'agentFeedback:f-2',
317
}),
318
}),
319
320
ExpandedReviewOnly: defineComponentFixture({
321
labels: { kind: 'screenshot' },
322
render: context => renderWidget(context, {
323
commentItems: reviewOnly,
324
expanded: true,
325
}),
326
}),
327
328
ExpandedMixedComments: defineComponentFixture({
329
labels: { kind: 'screenshot' },
330
render: context => renderWidget(context, {
331
commentItems: mixedComments,
332
expanded: true,
333
}),
334
}),
335
336
ExpandedFocusedReviewComment: defineComponentFixture({
337
labels: { kind: 'screenshot' },
338
render: context => renderWidget(context, {
339
commentItems: mixedComments,
340
expanded: true,
341
focusedCommentId: 'codeReview:r-1',
342
}),
343
}),
344
345
ExpandedReviewSuggestion: defineComponentFixture({
346
labels: { kind: 'screenshot' },
347
render: context => renderWidget(context, {
348
commentItems: suggestionMix,
349
expanded: true,
350
}),
351
}),
352
353
ExpandedPRReviewOnly: defineComponentFixture({
354
labels: { kind: 'screenshot' },
355
render: context => renderWidget(context, {
356
commentItems: prReviewOnly,
357
expanded: true,
358
}),
359
}),
360
361
ExpandedAllSourcesMixed: defineComponentFixture({
362
labels: { kind: 'screenshot' },
363
render: context => renderWidget(context, {
364
commentItems: allSourcesMixed,
365
expanded: true,
366
}),
367
}),
368
369
ExpandedFocusedPRReview: defineComponentFixture({
370
labels: { kind: 'screenshot' },
371
render: context => renderWidget(context, {
372
commentItems: allSourcesMixed,
373
expanded: true,
374
focusedCommentId: 'prReview:pr-2',
375
}),
376
}),
377
378
HiddenWidget: defineComponentFixture({
379
labels: { kind: 'screenshot' },
380
render: context => renderWidget(context, {
381
commentItems: mixedComments,
382
hidden: true,
383
}),
384
}),
385
});
386
387