@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,183 @@
|
|
|
1
|
+
import React, { useEffect, useRef, useState } from 'react';
|
|
2
|
+
import ReactDOM from 'react-dom';
|
|
3
|
+
|
|
4
|
+
import { IOpenerService, useInjectable } from '@opensumi/ide-core-browser';
|
|
5
|
+
import { Disposable, IMarkdownString, Schemes, URI } from '@opensumi/ide-core-common';
|
|
6
|
+
import {
|
|
7
|
+
EditorCollectionService,
|
|
8
|
+
getSimpleEditorOptions,
|
|
9
|
+
IDiffEditor,
|
|
10
|
+
IEditorDocumentModelService,
|
|
11
|
+
} from '@opensumi/ide-editor/lib/browser';
|
|
12
|
+
import { Markdown } from '@opensumi/ide-markdown';
|
|
13
|
+
import { IDiffEditorOptions, IEditorOptions } from '@opensumi/ide-monaco/lib/browser/monaco-api/editor';
|
|
14
|
+
|
|
15
|
+
import { TestPeekMessageToken } from '../../common';
|
|
16
|
+
import { ITestErrorMessage } from '../../common/testCollection';
|
|
17
|
+
import styles from '../components/testing.module.less';
|
|
18
|
+
|
|
19
|
+
import { TestDto } from './test-output-peek';
|
|
20
|
+
import { TestingPeekMessageServiceImpl } from './test-peek-message.service';
|
|
21
|
+
|
|
22
|
+
enum EContainerType {
|
|
23
|
+
DIFF,
|
|
24
|
+
PLANTTEXT,
|
|
25
|
+
MARKDOWN,
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const commonEditorOptions: IEditorOptions = {
|
|
29
|
+
scrollBeyondLastLine: false,
|
|
30
|
+
scrollbar: {
|
|
31
|
+
verticalScrollbarSize: 14,
|
|
32
|
+
horizontal: 'auto',
|
|
33
|
+
useShadows: true,
|
|
34
|
+
verticalHasArrows: false,
|
|
35
|
+
horizontalHasArrows: false,
|
|
36
|
+
alwaysConsumeMouseWheel: false,
|
|
37
|
+
},
|
|
38
|
+
fixedOverflowWidgets: true,
|
|
39
|
+
readOnly: true,
|
|
40
|
+
minimap: {
|
|
41
|
+
enabled: false,
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const diffEditorOptions: IDiffEditorOptions = {
|
|
46
|
+
...commonEditorOptions,
|
|
47
|
+
enableSplitViewResizing: true,
|
|
48
|
+
renderOverviewRuler: false,
|
|
49
|
+
ignoreTrimWhitespace: false,
|
|
50
|
+
renderSideBySide: true,
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const getMessage = (dto?: TestDto) =>
|
|
54
|
+
dto ? dto.messages[dto.messageIndex] : { message: '', actual: '', expected: '' };
|
|
55
|
+
|
|
56
|
+
const ShadowContent = ({ root, children }) => ReactDOM.createPortal(children, root);
|
|
57
|
+
|
|
58
|
+
const MarkdownContentProvider = (props: { dto: TestDto | undefined }) => {
|
|
59
|
+
const openerService: IOpenerService = useInjectable(IOpenerService);
|
|
60
|
+
|
|
61
|
+
const shadowRootRef = useRef<HTMLDivElement | null>(null);
|
|
62
|
+
const [message, useMessage] = useState<string>('');
|
|
63
|
+
const [shadowRoot, setShadowRoot] = useState<ShadowRoot | null>(null);
|
|
64
|
+
|
|
65
|
+
const handleLinkClick = (uri: URI) => {
|
|
66
|
+
if (uri && uri.scheme === Schemes.command) {
|
|
67
|
+
openerService.open(uri);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
React.useEffect(() => {
|
|
72
|
+
if (shadowRootRef.current) {
|
|
73
|
+
const { dto } = props;
|
|
74
|
+
const shadowRootElement = shadowRootRef.current.attachShadow({ mode: 'open' });
|
|
75
|
+
if (!shadowRoot) {
|
|
76
|
+
setShadowRoot(shadowRootElement);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const mdStr = getMessage(dto).message;
|
|
80
|
+
// 不处理 \t 制表符
|
|
81
|
+
const message = mdStr ? (mdStr as IMarkdownString).value.replace(/\t/g, '') : '';
|
|
82
|
+
useMessage(message);
|
|
83
|
+
}
|
|
84
|
+
}, []);
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<div ref={shadowRootRef} className={styles.preview_markdown}>
|
|
88
|
+
{shadowRoot && (
|
|
89
|
+
<ShadowContent root={shadowRoot}>
|
|
90
|
+
<Markdown content={message} onLinkClick={handleLinkClick}></Markdown>
|
|
91
|
+
</ShadowContent>
|
|
92
|
+
)}
|
|
93
|
+
</div>
|
|
94
|
+
);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const DiffContentProvider = React.memo((props: { dto: TestDto | undefined }) => {
|
|
98
|
+
const { dto } = props;
|
|
99
|
+
const documentModelService: IEditorDocumentModelService = useInjectable(IEditorDocumentModelService);
|
|
100
|
+
const editorCollectionService: EditorCollectionService = useInjectable(EditorCollectionService);
|
|
101
|
+
const editorRef = useRef<HTMLDivElement>(null);
|
|
102
|
+
|
|
103
|
+
useEffect(() => {
|
|
104
|
+
if (!dto) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
let diffEditor: IDiffEditor;
|
|
108
|
+
|
|
109
|
+
const { expectedUri, actualUri } = dto;
|
|
110
|
+
Promise.all([
|
|
111
|
+
documentModelService.createModelReference(expectedUri),
|
|
112
|
+
documentModelService.createModelReference(actualUri),
|
|
113
|
+
]).then((data) => {
|
|
114
|
+
const [original, modified] = data;
|
|
115
|
+
const { actual, expected } = getMessage(dto) as ITestErrorMessage;
|
|
116
|
+
|
|
117
|
+
diffEditor = editorCollectionService.createDiffEditor(editorRef.current!, {
|
|
118
|
+
...getSimpleEditorOptions(),
|
|
119
|
+
...diffEditorOptions,
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
const originalModel = original.instance.getMonacoModel();
|
|
123
|
+
const modifiedModel = modified.instance.getMonacoModel();
|
|
124
|
+
|
|
125
|
+
originalModel.setValue(expected!);
|
|
126
|
+
modifiedModel.setValue(actual!);
|
|
127
|
+
|
|
128
|
+
diffEditor.compare(original, modified);
|
|
129
|
+
|
|
130
|
+
diffEditor.originalEditor.monacoEditor.setModel(originalModel);
|
|
131
|
+
diffEditor.modifiedEditor.monacoEditor.setModel(modifiedModel);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
return () => {
|
|
135
|
+
if (diffEditor) {
|
|
136
|
+
diffEditor.dispose();
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
}, []);
|
|
140
|
+
|
|
141
|
+
return <div ref={editorRef} style={{ height: 'inherit' }}></div>;
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
export const TestMessageContainer = () => {
|
|
145
|
+
const testingPeekMessageService: TestingPeekMessageServiceImpl = useInjectable(TestPeekMessageToken);
|
|
146
|
+
|
|
147
|
+
const [type, setType] = useState<EContainerType>();
|
|
148
|
+
const [dto, setDto] = useState<TestDto>();
|
|
149
|
+
|
|
150
|
+
useEffect(() => {
|
|
151
|
+
const disposer: Disposable = new Disposable();
|
|
152
|
+
|
|
153
|
+
disposer.addDispose(
|
|
154
|
+
testingPeekMessageService.onDidReveal(async (dto: TestDto) => {
|
|
155
|
+
setDto(dto);
|
|
156
|
+
const message = getMessage(dto);
|
|
157
|
+
if (dto.isDiffable) {
|
|
158
|
+
setType(EContainerType.DIFF);
|
|
159
|
+
} else if (!(dto.isDiffable || typeof message.message === 'string')) {
|
|
160
|
+
setType(EContainerType.MARKDOWN);
|
|
161
|
+
} else {
|
|
162
|
+
setType(EContainerType.PLANTTEXT);
|
|
163
|
+
}
|
|
164
|
+
}),
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
return () => {
|
|
168
|
+
disposer.dispose();
|
|
169
|
+
};
|
|
170
|
+
}, []);
|
|
171
|
+
|
|
172
|
+
return (
|
|
173
|
+
<div className={styles.test_output_peek_message_container} style={{ height: '100%' }}>
|
|
174
|
+
{type === EContainerType.DIFF ? (
|
|
175
|
+
<DiffContentProvider dto={dto} />
|
|
176
|
+
) : type === EContainerType.MARKDOWN ? (
|
|
177
|
+
<MarkdownContentProvider dto={dto} />
|
|
178
|
+
) : type === EContainerType.PLANTTEXT ? (
|
|
179
|
+
getMessage(dto).message
|
|
180
|
+
) : null}
|
|
181
|
+
</div>
|
|
182
|
+
);
|
|
183
|
+
};
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import { Injectable, Optional, Autowired, INJECTOR_TOKEN, Injector } from '@opensumi/di';
|
|
2
|
+
import { EDITOR_COMMANDS, IContextKeyService, Schemes } from '@opensumi/ide-core-browser';
|
|
3
|
+
import { CommandService, Disposable, IDisposable, MutableDisposable, URI } from '@opensumi/ide-core-common';
|
|
4
|
+
import { IEditor, IEditorFeatureContribution } from '@opensumi/ide-editor/lib/browser';
|
|
5
|
+
import { Range } from '@opensumi/monaco-editor-core/esm/vs/editor/common/core/range';
|
|
6
|
+
import * as editorCommon from '@opensumi/monaco-editor-core/esm/vs/editor/common/editorCommon';
|
|
7
|
+
|
|
8
|
+
import { ITestResult, TestResultServiceToken } from '../../common/test-result';
|
|
9
|
+
import { IRichLocation, ITestItem, ITestMessage, TestMessageType, TestResultItem } from '../../common/testCollection';
|
|
10
|
+
import { TestingPeekOpenerServiceToken } from '../../common/testingPeekOpener';
|
|
11
|
+
import { isDiffable } from '../../common/testingStates';
|
|
12
|
+
import { TestContextKey } from '../test-contextkey.service';
|
|
13
|
+
import { TestResultServiceImpl } from '../test.result.service';
|
|
14
|
+
|
|
15
|
+
import { buildTestUri, TestUriType } from './../../common/testingUri';
|
|
16
|
+
import { TestingPeekOpenerServiceImpl } from './test-peek-opener.service';
|
|
17
|
+
import { TestingOutputPeek } from './test-peek-widget';
|
|
18
|
+
|
|
19
|
+
export class TestDto {
|
|
20
|
+
public readonly test: ITestItem;
|
|
21
|
+
public readonly messages: ITestMessage[];
|
|
22
|
+
public readonly expectedUri: URI;
|
|
23
|
+
public readonly actualUri: URI;
|
|
24
|
+
public readonly messageUri: URI;
|
|
25
|
+
public readonly revealLocation: IRichLocation | undefined;
|
|
26
|
+
|
|
27
|
+
public get isDiffable() {
|
|
28
|
+
const message = this.messages[this.messageIndex];
|
|
29
|
+
return message.type === TestMessageType.Error && isDiffable(message);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
constructor(
|
|
33
|
+
public readonly resultId: string,
|
|
34
|
+
test: TestResultItem,
|
|
35
|
+
public readonly taskIndex: number,
|
|
36
|
+
public readonly messageIndex: number,
|
|
37
|
+
) {
|
|
38
|
+
this.test = test.item;
|
|
39
|
+
this.messages = test.tasks[taskIndex].messages;
|
|
40
|
+
this.messageIndex = messageIndex;
|
|
41
|
+
|
|
42
|
+
const parts = { messageIndex, resultId, taskIndex, testExtId: test.item.extId };
|
|
43
|
+
this.expectedUri = buildTestUri({ ...parts, type: TestUriType.ResultExpectedOutput });
|
|
44
|
+
this.actualUri = buildTestUri({ ...parts, type: TestUriType.ResultActualOutput });
|
|
45
|
+
this.messageUri = buildTestUri({ ...parts, type: TestUriType.ResultMessage });
|
|
46
|
+
|
|
47
|
+
const message = this.messages[this.messageIndex];
|
|
48
|
+
this.revealLocation =
|
|
49
|
+
message.location ??
|
|
50
|
+
(test.item.uri && test.item.range ? { uri: test.item.uri, range: Range.lift(test.item.range) } : undefined);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
@Injectable({ multiple: true })
|
|
55
|
+
export class TestOutputPeekContribution implements IEditorFeatureContribution {
|
|
56
|
+
@Autowired(INJECTOR_TOKEN)
|
|
57
|
+
private readonly injector: Injector;
|
|
58
|
+
|
|
59
|
+
@Autowired(TestResultServiceToken)
|
|
60
|
+
private readonly testResultService: TestResultServiceImpl;
|
|
61
|
+
|
|
62
|
+
@Autowired(TestingPeekOpenerServiceToken)
|
|
63
|
+
private readonly testingPeekOpenerService: TestingPeekOpenerServiceImpl;
|
|
64
|
+
|
|
65
|
+
@Autowired(IContextKeyService)
|
|
66
|
+
private readonly contextKeyService: IContextKeyService;
|
|
67
|
+
|
|
68
|
+
@Autowired(CommandService)
|
|
69
|
+
private readonly commandService: CommandService;
|
|
70
|
+
|
|
71
|
+
private readonly testContextKey: TestContextKey;
|
|
72
|
+
|
|
73
|
+
private readonly disposer: Disposable = new Disposable();
|
|
74
|
+
|
|
75
|
+
private readonly peekView = new MutableDisposable<TestingOutputPeek>();
|
|
76
|
+
|
|
77
|
+
private currentPeekUri: URI | undefined;
|
|
78
|
+
|
|
79
|
+
constructor(@Optional() private readonly editor: IEditor) {
|
|
80
|
+
this.testContextKey = this.injector.get(TestContextKey, [(this.editor.monacoEditor as any)._contextKeyService]);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
private allMessages(results: readonly ITestResult[]): {
|
|
84
|
+
result: ITestResult;
|
|
85
|
+
test: TestResultItem;
|
|
86
|
+
taskIndex: number;
|
|
87
|
+
messageIndex: number;
|
|
88
|
+
}[] {
|
|
89
|
+
const messages: {
|
|
90
|
+
result: ITestResult;
|
|
91
|
+
test: TestResultItem;
|
|
92
|
+
taskIndex: number;
|
|
93
|
+
messageIndex: number;
|
|
94
|
+
}[] = [];
|
|
95
|
+
|
|
96
|
+
for (const result of results) {
|
|
97
|
+
for (const test of result.tests) {
|
|
98
|
+
for (let taskIndex = 0; taskIndex < test.tasks.length; taskIndex++) {
|
|
99
|
+
for (let messageIndex = 0; messageIndex < test.tasks[taskIndex].messages.length; messageIndex++) {
|
|
100
|
+
messages.push({ result, test, taskIndex, messageIndex });
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return messages;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
public contribute(): IDisposable {
|
|
110
|
+
this.disposer.addDispose(
|
|
111
|
+
this.editor.monacoEditor.onDidChangeModel((e: editorCommon.IModelChangedEvent) => {
|
|
112
|
+
if (e.newModelUrl?.scheme !== Schemes.file) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
this.testingPeekOpenerService.setPeekContrib(e.newModelUrl as unknown as URI, this);
|
|
117
|
+
}),
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
this.disposer.addDispose(
|
|
121
|
+
Disposable.create(() => {
|
|
122
|
+
if (this.editor.currentUri) {
|
|
123
|
+
this.testingPeekOpenerService.delPeekContrib(this.editor.currentUri);
|
|
124
|
+
}
|
|
125
|
+
}),
|
|
126
|
+
);
|
|
127
|
+
return this.disposer;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
public toggle(uri: URI) {
|
|
131
|
+
if (this.currentPeekUri?.toString() === uri.toString()) {
|
|
132
|
+
this.peekView.clear();
|
|
133
|
+
} else {
|
|
134
|
+
this.show(uri);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
public openCurrentInEditor() {
|
|
139
|
+
const current = this.peekView.value?.current;
|
|
140
|
+
if (!current) {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (current.isDiffable) {
|
|
145
|
+
this.commandService.executeCommand(
|
|
146
|
+
EDITOR_COMMANDS.API_OPEN_DIFF_EDITOR_COMMAND_ID,
|
|
147
|
+
current.expectedUri,
|
|
148
|
+
current.actualUri,
|
|
149
|
+
'Expected <-> Actual',
|
|
150
|
+
);
|
|
151
|
+
} else {
|
|
152
|
+
this.commandService.executeCommand(EDITOR_COMMANDS.OPEN_RESOURCE.id, current.messageUri, {
|
|
153
|
+
preview: false,
|
|
154
|
+
focus: true,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
public removePeek() {
|
|
160
|
+
this.peekView.value?.hide();
|
|
161
|
+
this.peekView.clear();
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
public async show(uri: URI): Promise<void> {
|
|
165
|
+
const dto = this.testResultService.retrieveTest(uri);
|
|
166
|
+
if (!dto) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (!this.peekView.value) {
|
|
171
|
+
this.peekView.value = this.injector.get(TestingOutputPeek, [this.editor.monacoEditor, this.contextKeyService]);
|
|
172
|
+
this.disposer.addDispose(
|
|
173
|
+
this.peekView.value.onDidClose(() => {
|
|
174
|
+
this.testContextKey.contextTestingIsPeekVisible.set(false);
|
|
175
|
+
this.currentPeekUri = undefined;
|
|
176
|
+
this.peekView.value = undefined;
|
|
177
|
+
}),
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
this.testContextKey.contextTestingIsPeekVisible.set(true);
|
|
181
|
+
this.peekView.value.create();
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
this.peekView.value.setModel(dto);
|
|
185
|
+
this.currentPeekUri = uri;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
public async openAndShow(uri: URI) {
|
|
189
|
+
const dto = this.testResultService.retrieveTest(uri);
|
|
190
|
+
if (!dto) {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (!dto.revealLocation || dto.revealLocation.uri.toString() === this.editor.currentUri?.toString()) {
|
|
195
|
+
return this.show(uri);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const ctor = this.testingPeekOpenerService.peekControllerMap.get(uri.toString());
|
|
199
|
+
|
|
200
|
+
if (!ctor) {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
this.removePeek();
|
|
205
|
+
|
|
206
|
+
await this.commandService.executeCommand(EDITOR_COMMANDS.OPEN_RESOURCE.id, URI.parse(uri.toString()), {
|
|
207
|
+
preview: true,
|
|
208
|
+
focus: true,
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
ctor.show(uri);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
public next() {
|
|
215
|
+
const dto = this.peekView.value?.current;
|
|
216
|
+
if (!dto) {
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
let found = false;
|
|
221
|
+
const getAllMessage = this.allMessages(this.testResultService.results);
|
|
222
|
+
|
|
223
|
+
for (const { messageIndex, taskIndex, result, test } of getAllMessage) {
|
|
224
|
+
if (found) {
|
|
225
|
+
this.openAndShow(
|
|
226
|
+
buildTestUri({
|
|
227
|
+
type: TestUriType.ResultMessage,
|
|
228
|
+
messageIndex,
|
|
229
|
+
taskIndex,
|
|
230
|
+
resultId: result.id,
|
|
231
|
+
testExtId: test.item.extId,
|
|
232
|
+
}),
|
|
233
|
+
);
|
|
234
|
+
return;
|
|
235
|
+
} else if (
|
|
236
|
+
dto.test.extId === test.item.extId &&
|
|
237
|
+
dto.messageIndex === messageIndex &&
|
|
238
|
+
dto.taskIndex === taskIndex &&
|
|
239
|
+
dto.resultId === result.id
|
|
240
|
+
) {
|
|
241
|
+
found = true;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
public previous() {
|
|
247
|
+
const dto = this.peekView.value?.current;
|
|
248
|
+
if (!dto) {
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
let previous: { messageIndex: number; taskIndex: number; result: ITestResult; test: TestResultItem } | undefined;
|
|
253
|
+
|
|
254
|
+
const getAllMessage = this.allMessages(this.testResultService.results);
|
|
255
|
+
|
|
256
|
+
for (const m of getAllMessage) {
|
|
257
|
+
if (
|
|
258
|
+
dto.test.extId === m.test.item.extId &&
|
|
259
|
+
dto.messageIndex === m.messageIndex &&
|
|
260
|
+
dto.taskIndex === m.taskIndex &&
|
|
261
|
+
dto.resultId === m.result.id
|
|
262
|
+
) {
|
|
263
|
+
if (!previous) {
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
this.openAndShow(
|
|
268
|
+
buildTestUri({
|
|
269
|
+
type: TestUriType.ResultMessage,
|
|
270
|
+
messageIndex: previous.messageIndex,
|
|
271
|
+
taskIndex: previous.taskIndex,
|
|
272
|
+
resultId: previous.result.id,
|
|
273
|
+
testExtId: previous.test.item.extId,
|
|
274
|
+
}),
|
|
275
|
+
);
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
previous = m;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Injectable, Autowired } from '@opensumi/di';
|
|
2
|
+
import { Disposable, URI, Emitter } from '@opensumi/ide-core-common';
|
|
3
|
+
|
|
4
|
+
import { ITestingPeekMessageService } from '../../common';
|
|
5
|
+
|
|
6
|
+
import { TestDto } from './test-output-peek';
|
|
7
|
+
|
|
8
|
+
@Injectable()
|
|
9
|
+
export class TestingPeekMessageServiceImpl extends Disposable implements ITestingPeekMessageService {
|
|
10
|
+
public readonly _didReveal = new Emitter<TestDto>();
|
|
11
|
+
public readonly _visibilityChange = new Emitter<boolean>();
|
|
12
|
+
|
|
13
|
+
public readonly onDidReveal = this._didReveal.event;
|
|
14
|
+
public readonly onVisibilityChange = this._visibilityChange.event;
|
|
15
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { Injectable, Autowired } from '@opensumi/di';
|
|
2
|
+
import { Disposable, URI } from '@opensumi/ide-core-common';
|
|
3
|
+
import { WorkbenchEditorService } from '@opensumi/ide-editor';
|
|
4
|
+
import { ITextEditorOptions } from '@opensumi/monaco-editor-core/esm/vs/platform/editor/common/editor';
|
|
5
|
+
|
|
6
|
+
import { ITestResult, TestResultServiceToken } from '../../common/test-result';
|
|
7
|
+
import { ITestMessage, ITestTaskState, TestResultItem } from '../../common/testCollection';
|
|
8
|
+
import { ITestingPeekOpenerService } from '../../common/testingPeekOpener';
|
|
9
|
+
import { buildTestUri, ParsedTestUri, TestUriType } from '../../common/testingUri';
|
|
10
|
+
import { TestResultServiceImpl } from '../test.result.service';
|
|
11
|
+
|
|
12
|
+
import { TestOutputPeekContribution } from './test-output-peek';
|
|
13
|
+
|
|
14
|
+
type TestUriWithDocument = ParsedTestUri & { documentUri: URI };
|
|
15
|
+
|
|
16
|
+
const mapFindTestMessage = <T>(
|
|
17
|
+
test: TestResultItem,
|
|
18
|
+
fn: (task: ITestTaskState, message: ITestMessage, messageIndex: number, taskIndex: number) => T | undefined,
|
|
19
|
+
) => {
|
|
20
|
+
for (let taskIndex = 0; taskIndex < test.tasks.length; taskIndex++) {
|
|
21
|
+
const task = test.tasks[taskIndex];
|
|
22
|
+
for (let messageIndex = 0; messageIndex < task.messages.length; messageIndex++) {
|
|
23
|
+
const r = fn(task, task.messages[messageIndex], messageIndex, taskIndex);
|
|
24
|
+
if (r !== undefined) {
|
|
25
|
+
return r;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return undefined;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
@Injectable()
|
|
34
|
+
export class TestingPeekOpenerServiceImpl extends Disposable implements ITestingPeekOpenerService {
|
|
35
|
+
@Autowired(WorkbenchEditorService)
|
|
36
|
+
private editorService: WorkbenchEditorService;
|
|
37
|
+
|
|
38
|
+
@Autowired(TestResultServiceToken)
|
|
39
|
+
private readonly testResultService: TestResultServiceImpl;
|
|
40
|
+
|
|
41
|
+
declare _serviceBrand: undefined;
|
|
42
|
+
|
|
43
|
+
private lastUri?: TestUriWithDocument;
|
|
44
|
+
|
|
45
|
+
public readonly peekControllerMap: Map<string, TestOutputPeekContribution> = new Map();
|
|
46
|
+
|
|
47
|
+
public get currentUri(): URI | undefined {
|
|
48
|
+
return this.editorService.currentEditor?.currentUri!;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
private async showPeekFromUri(testUri: TestUriWithDocument, options?: ITextEditorOptions) {
|
|
52
|
+
const editor = this.editorService.currentEditor;
|
|
53
|
+
|
|
54
|
+
this.lastUri = testUri;
|
|
55
|
+
|
|
56
|
+
const uri = buildTestUri(this.lastUri);
|
|
57
|
+
const ctor = this.peekControllerMap.get(editor?.currentUri!.toString()!);
|
|
58
|
+
ctor?.show(uri);
|
|
59
|
+
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
private getAnyCandidateMessage() {
|
|
64
|
+
const seen = new Set<string>();
|
|
65
|
+
for (const result of this.testResultService.results) {
|
|
66
|
+
for (const test of result.tests) {
|
|
67
|
+
if (seen.has(test.item.extId)) {
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
seen.add(test.item.extId);
|
|
72
|
+
const found = mapFindTestMessage(
|
|
73
|
+
test,
|
|
74
|
+
(task, message, messageIndex, taskIndex) =>
|
|
75
|
+
message.location && {
|
|
76
|
+
type: TestUriType.ResultMessage,
|
|
77
|
+
testExtId: test.item.extId,
|
|
78
|
+
resultId: result.id,
|
|
79
|
+
taskIndex,
|
|
80
|
+
messageIndex,
|
|
81
|
+
documentUri: message.location.uri,
|
|
82
|
+
},
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
if (found) {
|
|
86
|
+
return found;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
public setPeekContrib(uri: URI, contrib: TestOutputPeekContribution): this {
|
|
95
|
+
const uriStr = uri.toString();
|
|
96
|
+
if (!this.peekControllerMap.has(uriStr)) {
|
|
97
|
+
this.peekControllerMap.set(uriStr, contrib);
|
|
98
|
+
}
|
|
99
|
+
return this;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
public delPeekContrib(uri: URI): this {
|
|
103
|
+
const uriStr = uri.toString();
|
|
104
|
+
this.peekControllerMap.delete(uriStr);
|
|
105
|
+
return this;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
public tryPeekFirstError(result: ITestResult, test: TestResultItem, options?: Partial<ITextEditorOptions>): boolean {
|
|
109
|
+
throw new Error('Method not implemented.');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
public async open(): Promise<boolean> {
|
|
113
|
+
let uri: any;
|
|
114
|
+
|
|
115
|
+
if (!uri) {
|
|
116
|
+
uri = this.getAnyCandidateMessage();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (!uri) {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return this.showPeekFromUri(uri);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
public closeAllPeeks(): void {
|
|
127
|
+
for (const ctor of this.peekControllerMap.values()) {
|
|
128
|
+
ctor.removePeek();
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// @import '~@opensumi/ide-monaco-enhance/lib/browser/styles.module.less';
|
|
2
|
+
|
|
3
|
+
.peekview-widget .head {
|
|
4
|
+
box-sizing: border-box;
|
|
5
|
+
display: flex;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.peekview-widget .head .peekview-title {
|
|
9
|
+
display: flex;
|
|
10
|
+
align-items: center;
|
|
11
|
+
font-size: 13px;
|
|
12
|
+
margin-left: 20px;
|
|
13
|
+
min-width: 0;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.peekview-widget .head .peekview-title.clickable {
|
|
17
|
+
cursor: pointer;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.peekview-widget .head .peekview-title .dirname:not(:empty) {
|
|
21
|
+
font-size: 0.9em;
|
|
22
|
+
margin-left: 0.5em;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.peekview-widget .head .peekview-title .meta {
|
|
26
|
+
white-space: nowrap;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.peekview-widget .head .peekview-title .dirname {
|
|
30
|
+
white-space: nowrap;
|
|
31
|
+
color: var(--descriptionForeground);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.peekview-widget .head .peekview-title .filename {
|
|
35
|
+
color: var(--kt-accentForeground);
|
|
36
|
+
overflow: hidden;
|
|
37
|
+
text-overflow: ellipsis;
|
|
38
|
+
white-space: nowrap;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.peekview-widget .head .peekview-title .meta:not(:empty)::before {
|
|
42
|
+
content: '-';
|
|
43
|
+
padding: 0 0.3em;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.peekview-widget .head .peekview-actions {
|
|
47
|
+
display: flex;
|
|
48
|
+
justify-content: right;
|
|
49
|
+
|
|
50
|
+
.action-item {
|
|
51
|
+
line-height: inherit;
|
|
52
|
+
cursor: pointer;
|
|
53
|
+
margin-left: 6px;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.peekview-widget > .body {
|
|
58
|
+
border-top: 1px solid;
|
|
59
|
+
position: relative;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.peekview-widget .head .peekview-title .codicon {
|
|
63
|
+
margin-right: 4px;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.testing-output-peek-container {
|
|
67
|
+
border-top-color: var(--errorForeground);
|
|
68
|
+
border-bottom-color: var(--errorForeground);
|
|
69
|
+
border-top-width: 1px;
|
|
70
|
+
border-bottom-width: 1px;
|
|
71
|
+
border-top-style: solid;
|
|
72
|
+
border-bottom-style: solid;
|
|
73
|
+
top: 6px;
|
|
74
|
+
overflow: hidden;
|
|
75
|
+
|
|
76
|
+
.head {
|
|
77
|
+
background-color: var(--testing-peekHeaderBackground);
|
|
78
|
+
height: 22px;
|
|
79
|
+
line-height: 22px;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.body {
|
|
83
|
+
border-color: var(--errorForeground);
|
|
84
|
+
height: inherit;
|
|
85
|
+
}
|
|
86
|
+
}
|