@opensumi/ide-testing 2.21.13 → 2.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/browser/components/testing.explorer.tree.d.ts.map +1 -1
- package/lib/browser/components/testing.explorer.tree.js +7 -5
- package/lib/browser/components/testing.explorer.tree.js.map +1 -1
- package/lib/browser/components/testing.view.js +1 -1
- package/lib/browser/components/testing.view.js.map +1 -1
- package/lib/browser/icons/icons.d.ts +1 -0
- package/lib/browser/icons/icons.d.ts.map +1 -1
- package/lib/browser/icons/icons.js +15 -14
- package/lib/browser/icons/icons.js.map +1 -1
- package/lib/browser/index.js.map +1 -1
- package/lib/browser/outputPeek/test-output-peek.js +9 -9
- package/lib/browser/outputPeek/test-output-peek.js.map +1 -1
- package/lib/browser/outputPeek/test-peek-message.service.js.map +1 -1
- package/lib/browser/outputPeek/test-peek-opener.service.js +1 -1
- package/lib/browser/outputPeek/test-peek-opener.service.js.map +1 -1
- package/lib/browser/outputPeek/test-peek-widget.js +1 -1
- package/lib/browser/outputPeek/test-peek-widget.js.map +1 -1
- package/lib/browser/outputPeek/test-tree-container.js +2 -2
- package/lib/browser/outputPeek/test-tree-container.js.map +1 -1
- package/lib/browser/test-contextkey.service.js.map +1 -1
- package/lib/browser/test-decorations.js +19 -19
- package/lib/browser/test-decorations.js.map +1 -1
- package/lib/browser/test-profile.service.js.map +1 -1
- package/lib/browser/test-tree-view.model.d.ts +1 -0
- package/lib/browser/test-tree-view.model.d.ts.map +1 -1
- package/lib/browser/test-tree-view.model.js +13 -10
- package/lib/browser/test-tree-view.model.js.map +1 -1
- package/lib/browser/test.result.service.d.ts +1 -1
- package/lib/browser/test.result.service.d.ts.map +1 -1
- package/lib/browser/test.result.service.js +5 -5
- package/lib/browser/test.result.service.js.map +1 -1
- package/lib/browser/test.service.d.ts +4 -0
- package/lib/browser/test.service.d.ts.map +1 -1
- package/lib/browser/test.service.js +31 -4
- package/lib/browser/test.service.js.map +1 -1
- package/lib/browser/testing.contribution.d.ts.map +1 -1
- package/lib/browser/testing.contribution.js +21 -10
- package/lib/browser/testing.contribution.js.map +1 -1
- package/lib/common/commands.d.ts +1 -0
- package/lib/common/commands.d.ts.map +1 -1
- package/lib/common/commands.js +6 -1
- package/lib/common/commands.js.map +1 -1
- package/lib/common/constants.js +12 -12
- package/lib/common/constants.js.map +1 -1
- package/lib/common/getComputedState.d.ts +1 -1
- package/lib/common/getComputedState.d.ts.map +1 -1
- package/lib/common/getComputedState.js +1 -1
- package/lib/common/getComputedState.js.map +1 -1
- package/lib/common/index.d.ts +5 -0
- package/lib/common/index.d.ts.map +1 -1
- package/lib/common/index.js.map +1 -1
- package/lib/common/observableValue.d.ts +14 -0
- package/lib/common/observableValue.d.ts.map +1 -0
- package/lib/common/observableValue.js +23 -0
- package/lib/common/observableValue.js.map +1 -0
- package/lib/common/test-result.d.ts +2 -2
- package/lib/common/test-result.d.ts.map +1 -1
- package/lib/common/test-result.js +19 -19
- package/lib/common/test-result.js.map +1 -1
- package/lib/common/testCollection.d.ts +9 -8
- package/lib/common/testCollection.d.ts.map +1 -1
- package/lib/common/testCollection.js +27 -27
- package/lib/common/testCollection.js.map +1 -1
- package/lib/common/testId.js +21 -21
- package/lib/common/testId.js.map +1 -1
- package/lib/common/testingStates.js +11 -11
- package/lib/common/testingStates.js.map +1 -1
- package/lib/common/testingUri.d.ts +1 -1
- package/lib/common/testingUri.d.ts.map +1 -1
- package/lib/common/testingUri.js +16 -16
- package/lib/common/testingUri.js.map +1 -1
- package/package.json +14 -13
- package/src/browser/components/testing.explorer.tree.tsx +129 -0
- package/src/browser/components/testing.module.less +26 -0
- package/src/browser/components/testing.view.tsx +35 -0
- package/src/browser/icons/icons.less +31 -0
- package/src/browser/icons/icons.ts +31 -0
- package/src/browser/index.ts +51 -0
- package/src/browser/outputPeek/test-message-container.tsx +183 -0
- package/src/browser/outputPeek/test-output-peek.ts +282 -0
- package/src/browser/outputPeek/test-peek-message.service.ts +15 -0
- package/src/browser/outputPeek/test-peek-opener.service.ts +131 -0
- package/src/browser/outputPeek/test-peek-widget.less +86 -0
- package/src/browser/outputPeek/test-peek-widget.tsx +127 -0
- package/src/browser/outputPeek/test-tree-container.tsx +171 -0
- package/src/browser/test-contextkey.service.ts +24 -0
- package/src/browser/test-decorations.ts +567 -0
- package/src/browser/test-profile.service.ts +66 -0
- package/src/browser/test-tree-view.model.ts +304 -0
- package/src/browser/test.result.service.ts +193 -0
- package/src/browser/test.service.ts +190 -0
- package/src/browser/testing.contribution.ts +471 -0
- package/src/browser/theme.less +6 -0
- package/src/common/commands.ts +78 -0
- package/src/common/constants.ts +57 -0
- package/src/common/contextKeys.ts +0 -0
- package/src/common/getComputedState.ts +136 -0
- package/src/common/index.ts +55 -0
- package/src/common/observableValue.ts +27 -0
- package/src/common/test-profile.ts +25 -0
- package/src/common/test-result.ts +328 -0
- package/src/common/testCollection.ts +680 -0
- package/src/common/testId.ts +191 -0
- package/src/common/testing-view.ts +28 -0
- package/src/common/testingPeekOpener.ts +31 -0
- package/src/common/testingStates.ts +92 -0
- package/src/common/testingUri.ts +101 -0
- package/src/common/tree-view.model.ts +41 -0
- package/src/index.ts +1 -0
|
@@ -0,0 +1,567 @@
|
|
|
1
|
+
import { Injectable, Autowired, INJECTOR_TOKEN, Injector, Optional } from '@opensumi/di';
|
|
2
|
+
import { Event, IContextKeyService, MonacoOverrideServiceRegistry, ServiceNames } from '@opensumi/ide-core-browser';
|
|
3
|
+
import {
|
|
4
|
+
AbstractMenuService,
|
|
5
|
+
generateMergedCtxMenu,
|
|
6
|
+
ICtxMenuRenderer,
|
|
7
|
+
IMenu,
|
|
8
|
+
MenuId,
|
|
9
|
+
} from '@opensumi/ide-core-browser/lib/menu/next';
|
|
10
|
+
import { Disposable, IDisposable, IRange, URI, uuid, strings } from '@opensumi/ide-core-common';
|
|
11
|
+
import { IEditor, IEditorFeatureContribution } from '@opensumi/ide-editor/lib/browser';
|
|
12
|
+
import { MonacoCodeService } from '@opensumi/ide-editor/lib/browser/editor.override';
|
|
13
|
+
import { ICodeEditor } from '@opensumi/ide-monaco/lib/browser/monaco-api/types';
|
|
14
|
+
import { MarkdownString } from '@opensumi/monaco-editor-core/esm/vs/base/common/htmlContent';
|
|
15
|
+
import { MouseTargetType } from '@opensumi/monaco-editor-core/esm/vs/editor/browser/editorBrowser';
|
|
16
|
+
import { EditorOption } from '@opensumi/monaco-editor-core/esm/vs/editor/common/config/editorOptions';
|
|
17
|
+
import * as editorCommon from '@opensumi/monaco-editor-core/esm/vs/editor/common/editorCommon';
|
|
18
|
+
import { IModelDeltaDecoration } from '@opensumi/monaco-editor-core/esm/vs/editor/common/model';
|
|
19
|
+
import * as monaco from '@opensumi/monaco-editor-core/esm/vs/editor/editor.api';
|
|
20
|
+
|
|
21
|
+
import { TestServiceToken } from '../common';
|
|
22
|
+
import {
|
|
23
|
+
IncrementalTestCollectionItem,
|
|
24
|
+
IRichLocation,
|
|
25
|
+
ITestMessage,
|
|
26
|
+
TestMessageType,
|
|
27
|
+
TestResultItem,
|
|
28
|
+
TestResultState,
|
|
29
|
+
TestRunProfileBitset,
|
|
30
|
+
TestsDiff,
|
|
31
|
+
} from '../common/testCollection';
|
|
32
|
+
import { TestingPeekOpenerServiceToken } from '../common/testingPeekOpener';
|
|
33
|
+
import { buildTestUri, TestUriType } from '../common/testingUri';
|
|
34
|
+
|
|
35
|
+
import { labelForTestInState, testMessageSeverityColors } from './../common/constants';
|
|
36
|
+
import { TestResultImpl, TestResultServiceToken } from './../common/test-result';
|
|
37
|
+
import { maxPriority, parseMarkdownText } from './../common/testingStates';
|
|
38
|
+
import {
|
|
39
|
+
defaultIconColor,
|
|
40
|
+
testingRunAllIcon,
|
|
41
|
+
testingRunIcon,
|
|
42
|
+
testingStatesToIcons,
|
|
43
|
+
testStatesToIconColors,
|
|
44
|
+
} from './icons/icons';
|
|
45
|
+
import { TestingPeekOpenerServiceImpl } from './outputPeek/test-peek-opener.service';
|
|
46
|
+
import { ResultChangeEvent, TestResultServiceImpl } from './test.result.service';
|
|
47
|
+
import { TestServiceImpl } from './test.service';
|
|
48
|
+
|
|
49
|
+
const { removeAnsiEscapeCodes } = strings;
|
|
50
|
+
|
|
51
|
+
interface ITestDecoration extends IDisposable {
|
|
52
|
+
id: string;
|
|
53
|
+
readonly editorDecoration: IModelDeltaDecoration;
|
|
54
|
+
click(e: monaco.editor.IEditorMouseEvent): boolean;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const FONT_FAMILY_VAR = '--testMessageDecorationFontFamily';
|
|
58
|
+
|
|
59
|
+
const hasValidLocation = <T extends { location?: IRichLocation }>(
|
|
60
|
+
editorUri: URI,
|
|
61
|
+
t: T,
|
|
62
|
+
): t is T & { location: IRichLocation } => t.location?.uri.toString() === editorUri.toString();
|
|
63
|
+
|
|
64
|
+
const firstLineRange = (originalRange: IRange) => ({
|
|
65
|
+
startLineNumber: originalRange.startLineNumber,
|
|
66
|
+
endLineNumber: originalRange.startLineNumber,
|
|
67
|
+
startColumn: 0,
|
|
68
|
+
endColumn: 1,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const createRunTestDecoration = (
|
|
72
|
+
tests: readonly IncrementalTestCollectionItem[],
|
|
73
|
+
states: readonly (TestResultItem | undefined)[],
|
|
74
|
+
): IModelDeltaDecoration => {
|
|
75
|
+
const range = tests[0]?.item.range;
|
|
76
|
+
if (!range) {
|
|
77
|
+
throw new Error('Test decorations can only be created for tests with a range');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
let computedState = TestResultState.Unset;
|
|
81
|
+
const hoverMessageParts: string[] = [];
|
|
82
|
+
let testIdWithMessages: string | undefined;
|
|
83
|
+
let retired = false;
|
|
84
|
+
for (let i = 0; i < tests.length; i++) {
|
|
85
|
+
const test = tests[i];
|
|
86
|
+
const resultItem = states[i];
|
|
87
|
+
const state = resultItem?.computedState ?? TestResultState.Unset;
|
|
88
|
+
hoverMessageParts.push(labelForTestInState(test.item.label, state));
|
|
89
|
+
computedState = maxPriority(computedState, state);
|
|
90
|
+
retired = retired || !!resultItem?.retired;
|
|
91
|
+
if (!testIdWithMessages && resultItem?.tasks.some((t) => t.messages.length)) {
|
|
92
|
+
testIdWithMessages = test.item.extId;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const hasMultipleTests = tests.length > 1 || tests[0].children.size > 0;
|
|
97
|
+
const icon =
|
|
98
|
+
computedState === TestResultState.Unset
|
|
99
|
+
? hasMultipleTests
|
|
100
|
+
? testingRunAllIcon
|
|
101
|
+
: testingRunIcon
|
|
102
|
+
: testingStatesToIcons.get(computedState)!;
|
|
103
|
+
|
|
104
|
+
const iconColor = computedState === TestResultState.Unset ? defaultIconColor : testStatesToIconColors[computedState];
|
|
105
|
+
|
|
106
|
+
// const hoverMessage = hoverMessageParts.join(', ');
|
|
107
|
+
if (testIdWithMessages) {
|
|
108
|
+
// const args = encodeURIComponent(JSON.stringify([testIdWithMessages]));
|
|
109
|
+
// 这里应该使用 markdown 语法解析 command 命令给 hoverMessage 字段
|
|
110
|
+
// e.g (command:vscode.peekTestError?${args})
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* testing-run-glyph 这个样式类名是关键字段,不要随意改动
|
|
115
|
+
* 主要是用来防止与 debug 断点的事件冲突(例: hover、mousedown)
|
|
116
|
+
*/
|
|
117
|
+
let glyphMarginClassName = `${icon} ${iconColor} testing-run-glyph`;
|
|
118
|
+
if (retired) {
|
|
119
|
+
glyphMarginClassName += ' retired';
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
range: firstLineRange(range),
|
|
124
|
+
options: {
|
|
125
|
+
description: 'run-test-decoration',
|
|
126
|
+
isWholeLine: true,
|
|
127
|
+
glyphMarginClassName,
|
|
128
|
+
stickiness: monaco.editor.TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
abstract class RunTestDecoration extends Disposable {
|
|
134
|
+
@Autowired(IContextKeyService)
|
|
135
|
+
private readonly contextKeyService: IContextKeyService;
|
|
136
|
+
|
|
137
|
+
@Autowired(AbstractMenuService)
|
|
138
|
+
private readonly menuService: AbstractMenuService;
|
|
139
|
+
|
|
140
|
+
@Autowired(ICtxMenuRenderer)
|
|
141
|
+
private readonly ctxMenuRenderer: ICtxMenuRenderer;
|
|
142
|
+
|
|
143
|
+
public id = '';
|
|
144
|
+
|
|
145
|
+
public get line() {
|
|
146
|
+
return this.editorDecoration.range.startLineNumber;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
constructor(public editorDecoration: IModelDeltaDecoration, protected readonly editor: ICodeEditor) {
|
|
150
|
+
super();
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
public click(e: monaco.editor.IEditorMouseEvent): boolean {
|
|
154
|
+
if (e.target.position?.lineNumber !== this.line || e.target.type !== MouseTargetType.GUTTER_GLYPH_MARGIN) {
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (e.event.rightButton) {
|
|
159
|
+
this.showContextMenu(e);
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
this.defaultRun();
|
|
164
|
+
|
|
165
|
+
return true;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* 添加测试到装饰器
|
|
170
|
+
*/
|
|
171
|
+
public abstract merge(
|
|
172
|
+
other: IncrementalTestCollectionItem,
|
|
173
|
+
resultItem: TestResultItem | undefined,
|
|
174
|
+
): RunTestDecoration;
|
|
175
|
+
|
|
176
|
+
protected abstract defaultRun(): void;
|
|
177
|
+
|
|
178
|
+
protected abstract defaultDebug(): void;
|
|
179
|
+
|
|
180
|
+
protected abstract getContextMenuArgs(): string[];
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* 装饰器右键菜单
|
|
184
|
+
*/
|
|
185
|
+
protected getContextMenuActions(e: monaco.editor.IEditorMouseEvent): IMenu {
|
|
186
|
+
return this.menuService.createMenu(MenuId.TestingGlyphMarginContext, this.contextKeyService);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
private showContextMenu(e: monaco.editor.IEditorMouseEvent) {
|
|
190
|
+
const actions = this.getContextMenuActions(e);
|
|
191
|
+
const menuNodes = generateMergedCtxMenu({ menus: actions });
|
|
192
|
+
|
|
193
|
+
actions.dispose();
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* 这里有个小坑,之所以加个延时处理是因为
|
|
197
|
+
* 我们对 monacoEditor 做了 onContextMenu 和 onMouseDown 的监听,当触发鼠标右键的时候是先走 onMouseDown 再走 onContextMenu
|
|
198
|
+
* 此时 onMouseDown 里已经显示了右键菜单,正准备走 onContextMenu 的时候,焦点已经不是在 monaco 的 glyph margin 处,而是菜单项的 dom 上,那么就会触发浏览器自带的右键菜单
|
|
199
|
+
*/
|
|
200
|
+
setTimeout(() => {
|
|
201
|
+
this.ctxMenuRenderer.show({
|
|
202
|
+
anchor: e.event.browserEvent,
|
|
203
|
+
menuNodes,
|
|
204
|
+
args: this.getContextMenuArgs(),
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
private getGutterLabel() {}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
class MultiRunTestDecoration extends RunTestDecoration implements ITestDecoration {
|
|
213
|
+
@Autowired(TestServiceToken)
|
|
214
|
+
private readonly testService: TestServiceImpl;
|
|
215
|
+
|
|
216
|
+
constructor(
|
|
217
|
+
private readonly tests: {
|
|
218
|
+
test: IncrementalTestCollectionItem;
|
|
219
|
+
resultItem: TestResultItem | undefined;
|
|
220
|
+
}[],
|
|
221
|
+
editor: ICodeEditor,
|
|
222
|
+
) {
|
|
223
|
+
super(
|
|
224
|
+
createRunTestDecoration(
|
|
225
|
+
tests.map((t) => t.test),
|
|
226
|
+
tests.map((t) => t.resultItem),
|
|
227
|
+
),
|
|
228
|
+
editor,
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
public override merge(
|
|
233
|
+
test: IncrementalTestCollectionItem,
|
|
234
|
+
resultItem: TestResultItem | undefined,
|
|
235
|
+
): RunTestDecoration {
|
|
236
|
+
this.tests.push({ test, resultItem });
|
|
237
|
+
this.editorDecoration = createRunTestDecoration(
|
|
238
|
+
this.tests.map((t) => t.test),
|
|
239
|
+
this.tests.map((t) => t.resultItem),
|
|
240
|
+
);
|
|
241
|
+
return this;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
protected getContextMenuArgs() {
|
|
245
|
+
return this.tests.map((t) => t.test.item.extId);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
protected override defaultRun() {
|
|
249
|
+
return this.testService.runTests({
|
|
250
|
+
tests: this.tests.map(({ test }) => test),
|
|
251
|
+
group: TestRunProfileBitset.Run,
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
protected override defaultDebug() {
|
|
256
|
+
return this.testService.runTests({
|
|
257
|
+
tests: this.tests.map(({ test }) => test),
|
|
258
|
+
group: TestRunProfileBitset.Debug,
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
@Injectable({ multiple: true })
|
|
264
|
+
class RunSingleTestDecoration extends RunTestDecoration implements ITestDecoration {
|
|
265
|
+
@Autowired(INJECTOR_TOKEN)
|
|
266
|
+
private readonly injector: Injector;
|
|
267
|
+
|
|
268
|
+
@Autowired(TestServiceToken)
|
|
269
|
+
private readonly testService: TestServiceImpl;
|
|
270
|
+
|
|
271
|
+
constructor(
|
|
272
|
+
private readonly test: IncrementalTestCollectionItem,
|
|
273
|
+
editor: ICodeEditor,
|
|
274
|
+
private readonly resultItem: TestResultItem | undefined,
|
|
275
|
+
) {
|
|
276
|
+
super(createRunTestDecoration([test], [resultItem]), editor);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
public override merge(
|
|
280
|
+
test: IncrementalTestCollectionItem,
|
|
281
|
+
resultItem: TestResultItem | undefined,
|
|
282
|
+
): RunTestDecoration {
|
|
283
|
+
return this.injector.get(MultiRunTestDecoration, [
|
|
284
|
+
[
|
|
285
|
+
{ test: this.test, resultItem: this.resultItem },
|
|
286
|
+
{ test, resultItem },
|
|
287
|
+
],
|
|
288
|
+
this.editor,
|
|
289
|
+
]);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
protected getContextMenuArgs() {
|
|
293
|
+
return [this.test.item.extId];
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
protected override defaultRun() {
|
|
297
|
+
return this.testService.runTests({
|
|
298
|
+
tests: [this.test],
|
|
299
|
+
group: TestRunProfileBitset.Run,
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
protected override defaultDebug() {
|
|
304
|
+
return this.testService.runTests({
|
|
305
|
+
tests: [this.test],
|
|
306
|
+
group: TestRunProfileBitset.Debug,
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
@Injectable({ multiple: true })
|
|
312
|
+
class TestMessageDecoration implements ITestDecoration {
|
|
313
|
+
@Autowired(MonacoOverrideServiceRegistry)
|
|
314
|
+
private readonly overrideServicesRegistry: MonacoOverrideServiceRegistry;
|
|
315
|
+
|
|
316
|
+
@Autowired(TestingPeekOpenerServiceToken)
|
|
317
|
+
private readonly testingPeekOpenerService: TestingPeekOpenerServiceImpl;
|
|
318
|
+
|
|
319
|
+
private readonly decorationId = `testmessage-${uuid()}`;
|
|
320
|
+
private codeEditorService: MonacoCodeService;
|
|
321
|
+
private get monacoEditor(): ICodeEditor {
|
|
322
|
+
return this.editor.monacoEditor;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
public readonly editorDecoration: IModelDeltaDecoration;
|
|
326
|
+
public id = '';
|
|
327
|
+
|
|
328
|
+
constructor(
|
|
329
|
+
public readonly testMessage: ITestMessage,
|
|
330
|
+
private readonly messageUri: URI | undefined,
|
|
331
|
+
public readonly location: IRichLocation,
|
|
332
|
+
private readonly editor: IEditor,
|
|
333
|
+
) {
|
|
334
|
+
this.codeEditorService = this.overrideServicesRegistry.getRegisteredService(
|
|
335
|
+
ServiceNames.CODE_EDITOR_SERVICE,
|
|
336
|
+
) as MonacoCodeService;
|
|
337
|
+
|
|
338
|
+
const severity = testMessage.type;
|
|
339
|
+
const message =
|
|
340
|
+
typeof testMessage.message === 'string'
|
|
341
|
+
? removeAnsiEscapeCodes(testMessage.message)
|
|
342
|
+
: parseMarkdownText(testMessage.message.value);
|
|
343
|
+
this.codeEditorService.registerDecorationType(
|
|
344
|
+
'test-message-decoration',
|
|
345
|
+
this.decorationId,
|
|
346
|
+
{
|
|
347
|
+
after: {
|
|
348
|
+
contentText: message,
|
|
349
|
+
color: `${testMessageSeverityColors[severity]}`,
|
|
350
|
+
fontSize: `${this.monacoEditor.getOption(EditorOption.fontSize)}px`,
|
|
351
|
+
fontFamily: `var(${FONT_FAMILY_VAR})`,
|
|
352
|
+
padding: '0px 12px 0px 24px',
|
|
353
|
+
},
|
|
354
|
+
},
|
|
355
|
+
undefined,
|
|
356
|
+
this.monacoEditor,
|
|
357
|
+
);
|
|
358
|
+
|
|
359
|
+
const options = this.codeEditorService.resolveDecorationOptions(this.decorationId, true);
|
|
360
|
+
options.hoverMessage = typeof message === 'string' ? new MarkdownString().appendText(message) : message;
|
|
361
|
+
options.afterContentClassName = `${options.afterContentClassName} testing-inline-message-content`;
|
|
362
|
+
options.zIndex = 10;
|
|
363
|
+
options.className = `testing-inline-message-margin testing-inline-message-severity-${severity}`;
|
|
364
|
+
options.isWholeLine = true;
|
|
365
|
+
options.stickiness = monaco.editor.TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges;
|
|
366
|
+
options.collapseOnReplaceEdit = true;
|
|
367
|
+
|
|
368
|
+
this.editorDecoration = { range: firstLineRange(location.range), options };
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
click(e: monaco.editor.IEditorMouseEvent): boolean {
|
|
372
|
+
if (e.event.rightButton) {
|
|
373
|
+
return false;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (!this.messageUri) {
|
|
377
|
+
return false;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if (e.target.element?.className.includes(this.decorationId)) {
|
|
381
|
+
const ctor = this.testingPeekOpenerService.peekControllerMap.get(this.editor.currentUri?.toString()!);
|
|
382
|
+
if (ctor) {
|
|
383
|
+
ctor.toggle(this.messageUri);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
return false;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
dispose(): void {
|
|
391
|
+
this.codeEditorService.removeDecorationType(this.decorationId);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
@Injectable({ multiple: true })
|
|
396
|
+
export class TestDecorationsContribution implements IEditorFeatureContribution {
|
|
397
|
+
@Autowired(TestServiceToken)
|
|
398
|
+
private readonly testService: TestServiceImpl;
|
|
399
|
+
|
|
400
|
+
@Autowired(TestResultServiceToken)
|
|
401
|
+
private readonly testResultService: TestResultServiceImpl;
|
|
402
|
+
|
|
403
|
+
@Autowired(INJECTOR_TOKEN)
|
|
404
|
+
private readonly injector: Injector;
|
|
405
|
+
|
|
406
|
+
public get currentUri(): URI | null {
|
|
407
|
+
return this.editor.currentUri;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
private readonly disposer: Disposable = new Disposable();
|
|
411
|
+
private invalidatedMessages = new WeakSet<ITestMessage>();
|
|
412
|
+
|
|
413
|
+
private lastDecorations: ITestDecoration[] = [];
|
|
414
|
+
|
|
415
|
+
constructor(@Optional() private readonly editor: IEditor) {}
|
|
416
|
+
|
|
417
|
+
public contribute(): IDisposable {
|
|
418
|
+
this.setDecorations(this.currentUri);
|
|
419
|
+
|
|
420
|
+
this.disposer.addDispose(
|
|
421
|
+
this.editor.monacoEditor.onDidChangeModel((e: editorCommon.IModelChangedEvent) => {
|
|
422
|
+
this.setDecorations((e.newModelUrl as unknown as URI) || undefined);
|
|
423
|
+
}),
|
|
424
|
+
);
|
|
425
|
+
|
|
426
|
+
this.disposer.addDispose(
|
|
427
|
+
this.editor.monacoEditor.onDidChangeModelContent((e) => {
|
|
428
|
+
if (!this.currentUri) {
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
let update = false;
|
|
433
|
+
for (const change of e.changes) {
|
|
434
|
+
for (const deco of this.lastDecorations) {
|
|
435
|
+
if (
|
|
436
|
+
deco instanceof TestMessageDecoration &&
|
|
437
|
+
deco.location.range.startLineNumber >= change.range.startLineNumber &&
|
|
438
|
+
deco.location.range.endLineNumber <= change.range.endLineNumber
|
|
439
|
+
) {
|
|
440
|
+
this.invalidatedMessages.add(deco.testMessage);
|
|
441
|
+
update = true;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
if (update) {
|
|
447
|
+
this.setDecorations(this.currentUri);
|
|
448
|
+
}
|
|
449
|
+
}),
|
|
450
|
+
);
|
|
451
|
+
|
|
452
|
+
this.disposer.addDispose(
|
|
453
|
+
this.editor.monacoEditor.onContextMenu((e) => {
|
|
454
|
+
e.event.preventDefault();
|
|
455
|
+
e.event.stopPropagation();
|
|
456
|
+
return false;
|
|
457
|
+
}),
|
|
458
|
+
);
|
|
459
|
+
|
|
460
|
+
this.disposer.addDispose(
|
|
461
|
+
this.editor.monacoEditor.onMouseDown((e) => {
|
|
462
|
+
for (const decoration of this.lastDecorations) {
|
|
463
|
+
if (decoration.click(e)) {
|
|
464
|
+
e.event.stopPropagation();
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
}),
|
|
469
|
+
);
|
|
470
|
+
|
|
471
|
+
this.disposer.addDispose(
|
|
472
|
+
this.testResultService.onTestChanged(({ item: result }) => {
|
|
473
|
+
if (this.currentUri && result.item.uri && result.item.uri.toString() === this.currentUri.toString()) {
|
|
474
|
+
this.setDecorations(this.currentUri);
|
|
475
|
+
}
|
|
476
|
+
}),
|
|
477
|
+
);
|
|
478
|
+
|
|
479
|
+
this.disposer.addDispose(
|
|
480
|
+
Event.any<TestsDiff | ResultChangeEvent>(
|
|
481
|
+
this.testResultService.onResultsChanged,
|
|
482
|
+
this.testService.onDidProcessDiff,
|
|
483
|
+
)(() => this.setDecorations(this.currentUri)),
|
|
484
|
+
);
|
|
485
|
+
|
|
486
|
+
return this.disposer;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
private setDecorations(uri: URI | undefined | null): void {
|
|
490
|
+
if (!uri) {
|
|
491
|
+
this.clearDecorations();
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
this.editor.monacoEditor.changeDecorations((accessor) => {
|
|
496
|
+
const newDecorations: ITestDecoration[] = [];
|
|
497
|
+
|
|
498
|
+
for (const test of this.testService.collection.all) {
|
|
499
|
+
if (!test.item.range || test.item.uri?.toString() !== uri.toString()) {
|
|
500
|
+
continue;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
const stateLookup = this.testResultService.getStateById(test.item.extId);
|
|
504
|
+
const line = test.item.range.startLineNumber;
|
|
505
|
+
const resultItem = stateLookup?.[1];
|
|
506
|
+
const existing = newDecorations.findIndex((d) => d instanceof RunTestDecoration && d.line === line);
|
|
507
|
+
if (existing !== -1) {
|
|
508
|
+
newDecorations[existing] = (newDecorations[existing] as RunTestDecoration).merge(test, resultItem);
|
|
509
|
+
} else {
|
|
510
|
+
newDecorations.push(
|
|
511
|
+
this.injector.get(RunSingleTestDecoration, [test, this.editor!.monacoEditor, stateLookup?.[1]]),
|
|
512
|
+
);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
const lastResult = this.testResultService.results[0];
|
|
517
|
+
if (lastResult instanceof TestResultImpl) {
|
|
518
|
+
// task.otherMessages 的情况还未处理
|
|
519
|
+
|
|
520
|
+
for (const test of lastResult.tests) {
|
|
521
|
+
for (let taskId = 0; taskId < test.tasks.length; taskId++) {
|
|
522
|
+
const state = test.tasks[taskId];
|
|
523
|
+
for (let i = 0; i < state.messages.length; i++) {
|
|
524
|
+
const m = state.messages[i];
|
|
525
|
+
if (!this.invalidatedMessages.has(m) && hasValidLocation(uri, m)) {
|
|
526
|
+
const uri =
|
|
527
|
+
m.type === TestMessageType.Info
|
|
528
|
+
? undefined
|
|
529
|
+
: buildTestUri({
|
|
530
|
+
type: TestUriType.ResultActualOutput,
|
|
531
|
+
messageIndex: i,
|
|
532
|
+
taskIndex: taskId,
|
|
533
|
+
resultId: lastResult.id,
|
|
534
|
+
testExtId: test.item.extId,
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
newDecorations.push(this.injector.get(TestMessageDecoration, [m, uri, m.location, this.editor]));
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
accessor
|
|
545
|
+
.deltaDecorations(
|
|
546
|
+
this.lastDecorations.map((d) => d.id),
|
|
547
|
+
newDecorations.map((d) => d.editorDecoration),
|
|
548
|
+
)
|
|
549
|
+
.forEach((id, i) => (newDecorations[i].id = id));
|
|
550
|
+
this.lastDecorations = newDecorations;
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
private clearDecorations(): void {
|
|
555
|
+
if (!this.lastDecorations.length) {
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
this.editor.monacoEditor.changeDecorations((accessor) => {
|
|
560
|
+
for (const decoration of this.lastDecorations) {
|
|
561
|
+
accessor.removeDecoration(decoration.id);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
this.lastDecorations = [];
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { Injectable } from '@opensumi/di';
|
|
2
|
+
import { Disposable } from '@opensumi/ide-core-browser';
|
|
3
|
+
|
|
4
|
+
import { ITestProfileService, sorter } from '../common/test-profile';
|
|
5
|
+
import { ITestRunProfile, TestRunProfileBitset } from '../common/testCollection';
|
|
6
|
+
import { ITestController } from '../index';
|
|
7
|
+
|
|
8
|
+
@Injectable()
|
|
9
|
+
export class TestProfileServiceImpl extends Disposable implements ITestProfileService {
|
|
10
|
+
private readonly testProfiles = new Map<
|
|
11
|
+
string,
|
|
12
|
+
{
|
|
13
|
+
profiles: ITestRunProfile[];
|
|
14
|
+
controller: ITestController;
|
|
15
|
+
}
|
|
16
|
+
>();
|
|
17
|
+
|
|
18
|
+
removeProfile(controllerId: string, profileId?: number): void {
|
|
19
|
+
const controller = this.testProfiles.get(controllerId);
|
|
20
|
+
if (controller) {
|
|
21
|
+
if (profileId) {
|
|
22
|
+
const index = controller.profiles.findIndex((p) => p.profileId !== profileId);
|
|
23
|
+
if (index >= 0) {
|
|
24
|
+
controller.profiles.splice(index, 1);
|
|
25
|
+
}
|
|
26
|
+
} else {
|
|
27
|
+
this.testProfiles.delete(controllerId);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
addProfile(controller: ITestController, profile: ITestRunProfile): void {
|
|
33
|
+
let record = this.testProfiles.get(profile.controllerId);
|
|
34
|
+
if (record) {
|
|
35
|
+
record.profiles.push(profile);
|
|
36
|
+
record.profiles.sort(sorter);
|
|
37
|
+
} else {
|
|
38
|
+
record = {
|
|
39
|
+
profiles: [profile],
|
|
40
|
+
controller,
|
|
41
|
+
};
|
|
42
|
+
this.testProfiles.set(profile.controllerId, record);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
getBaseDefaultsProfile(group: TestRunProfileBitset): ITestRunProfile[] {
|
|
47
|
+
const resultProfiles: ITestRunProfile[] = [];
|
|
48
|
+
|
|
49
|
+
for (const { profiles } of this.testProfiles.values()) {
|
|
50
|
+
const profile = profiles.find((p) => p.group === group);
|
|
51
|
+
if (profile) {
|
|
52
|
+
resultProfiles.push(profile);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return resultProfiles;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
getControllerProfiles(controllerId: string): ITestRunProfile[] {
|
|
60
|
+
const record = this.testProfiles.get(controllerId);
|
|
61
|
+
if (record) {
|
|
62
|
+
return record.profiles;
|
|
63
|
+
}
|
|
64
|
+
return [];
|
|
65
|
+
}
|
|
66
|
+
}
|