@theia/test 1.53.0-next.4 → 1.53.0-next.55

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.
@@ -1,271 +1,271 @@
1
- // *****************************************************************************
2
- // Copyright (C) 2023 STMicroelectronics and others.
3
- //
4
- // This program and the accompanying materials are made available under the
5
- // terms of the Eclipse Public License v. 2.0 which is available at
6
- // http://www.eclipse.org/legal/epl-2.0.
7
- //
8
- // This Source Code may also be made available under the following Secondary
9
- // Licenses when the conditions for such availability set forth in the Eclipse
10
- // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
- // with the GNU Classpath Exception which is available at
12
- // https://www.gnu.org/software/classpath/license.html.
13
- //
14
- // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
- // *****************************************************************************
16
-
17
- import { injectable, inject, postConstruct } from '@theia/core/shared/inversify';
18
- import { TreeWidget, TreeModel, TreeProps, CompositeTreeNode, TreeNode, TreeImpl, NodeProps, SelectableTreeNode } from '@theia/core/lib/browser/tree';
19
- import { ContextMenuRenderer, codicon } from '@theia/core/lib/browser';
20
- import { IconThemeService } from '@theia/core/lib/browser/icon-theme-service';
21
- import { ThemeService } from '@theia/core/lib/browser/theming';
22
- import { ContextKeyService } from '@theia/core/lib/browser/context-key-service';
23
- import { TestController, TestExecutionState, TestFailure, TestItem, TestMessage, TestOutputItem, TestRun, TestService } from '../test-service';
24
- import * as React from '@theia/core/shared/react';
25
- import { Disposable, DisposableCollection, Event, nls } from '@theia/core';
26
- import { TestExecutionStateManager } from './test-execution-state-manager';
27
- import { TestOutputUIModel } from './test-output-ui-model';
28
-
29
- class TestRunNode implements TreeNode, SelectableTreeNode {
30
- constructor(readonly counter: number, readonly id: string, readonly run: TestRun, readonly parent: CompositeTreeNode) { }
31
-
32
- get name(): string {
33
- return this.run.name || nls.localize('theia/test/testRunDefaultName', '{0} run {1}', this.run.controller.label, this.counter);
34
- };
35
-
36
- expanded?: boolean;
37
- selected: boolean = false;
38
- children: TestItemNode[] = [];
39
- }
40
-
41
- class TestItemNode implements TreeNode, SelectableTreeNode {
42
- constructor(readonly id: string, readonly item: TestItem, readonly parent: TestRunNode) { }
43
- selected: boolean = false;
44
-
45
- get name(): string {
46
- return this.item.label;
47
- }
48
- }
49
-
50
- interface RunInfo {
51
- node: TestRunNode;
52
- disposable: Disposable;
53
- tests: Map<TestItem, TestItemNode>;
54
- }
55
-
56
- @injectable()
57
- export class TestRunTree extends TreeImpl {
58
- private ROOT: CompositeTreeNode = {
59
- id: 'TestResults',
60
- name: 'Test Results',
61
- parent: undefined,
62
- children: [],
63
- visible: false
64
- };
65
-
66
- @inject(TestService) protected readonly testService: TestService;
67
-
68
- private controllerListeners = new Map<string, Disposable>();
69
-
70
- private runs = new Map<TestRun, RunInfo>();
71
- private nextId = 0;
72
-
73
- @postConstruct()
74
- init(): void {
75
- this.root = this.ROOT;
76
- this.testService.getControllers().forEach(controller => {
77
- this.addController(controller);
78
- });
79
-
80
- this.testService.onControllersChanged(controllerDelta => {
81
- controllerDelta.removed?.forEach(controller => {
82
- this.controllerListeners.get(controller)?.dispose();
83
- });
84
-
85
- controllerDelta.added?.forEach(controller => this.addController(controller));
86
- });
87
- }
88
-
89
- private addController(controller: TestController): void {
90
- controller.testRuns.forEach(run => this.addRun(run));
91
- const listeners = new DisposableCollection();
92
- this.controllerListeners.set(controller.id, listeners);
93
-
94
- listeners.push(controller.onRunsChanged(runDelta => {
95
- runDelta.removed?.forEach(run => {
96
- this.runs.get(run)?.disposable.dispose();
97
- this.runs.delete(run);
98
- this.refresh(this.ROOT);
99
- });
100
- runDelta.added?.forEach(run => {
101
- this.addRun(run);
102
- this.refresh(this.ROOT);
103
- });
104
- }));
105
- }
106
-
107
- private addRun(run: TestRun): void {
108
- const newNode = this.createRunNode(run);
109
- const affected: TestItemNode[] = [];
110
-
111
- const disposables = new DisposableCollection();
112
-
113
- disposables.push(run.onDidChangeTestState(deltas => {
114
- let needsRefresh = false;
115
- deltas.forEach(delta => {
116
- if (delta.newState) {
117
- if (delta.newState.state > TestExecutionState.Queued) {
118
- const testNode = info.tests.get(delta.test);
119
- if (!testNode) {
120
- if (info.tests.size === 0) {
121
- newNode.expanded = true;
122
- }
123
- info.tests.set(delta.test, this.createTestItemNode(newNode, delta.test));
124
- needsRefresh = true;
125
- } else {
126
- affected.push(testNode);
127
- }
128
- }
129
- } else {
130
- info.tests.delete(delta.test);
131
- needsRefresh = true;
132
- }
133
- });
134
- if (needsRefresh) {
135
- this.refresh(newNode);
136
- } else {
137
- this.onDidUpdateEmitter.fire(affected);
138
- }
139
- }));
140
- disposables.push(run.onDidChangeProperty(() => this.onDidUpdateEmitter.fire([])));
141
- const info = {
142
- node: newNode,
143
- disposable: disposables,
144
-
145
- tests: new Map(run.items.filter(item => (run.getTestState(item)?.state || 0) > TestExecutionState.Queued).map(item => [item, this.createTestItemNode(newNode, item)]))
146
- };
147
- this.runs.set(run, info);
148
- }
149
-
150
- protected createRunNode(run: TestRun): TestRunNode {
151
- return new TestRunNode(this.nextId, `id-${this.nextId++}`, run, this.ROOT);
152
- }
153
-
154
- createTestItemNode(parent: TestRunNode, item: TestItem): TestItemNode {
155
- return new TestItemNode(`testitem-${this.nextId++}`, item, parent);
156
- }
157
-
158
- protected override async resolveChildren(parent: CompositeTreeNode): Promise<TreeNode[]> {
159
- if (parent === this.ROOT) {
160
- return Promise.resolve([...this.runs.values()].reverse().map(info => info.node));
161
- } else if (parent instanceof TestRunNode) {
162
- const runInfo = this.runs.get(parent.run);
163
- if (runInfo) {
164
- return Promise.resolve([...runInfo.tests.values()]);
165
- } else {
166
- return Promise.resolve([]);
167
- }
168
- } else {
169
- return Promise.resolve([]);
170
- }
171
- }
172
- }
173
-
174
- @injectable()
175
- export class TestRunTreeWidget extends TreeWidget {
176
-
177
- static ID = 'test-run-widget';
178
-
179
- @inject(IconThemeService) protected readonly iconThemeService: IconThemeService;
180
- @inject(ContextKeyService) protected readonly contextKeys: ContextKeyService;
181
- @inject(ThemeService) protected readonly themeService: ThemeService;
182
- @inject(TestExecutionStateManager) protected readonly stateManager: TestExecutionStateManager;
183
- @inject(TestOutputUIModel) protected readonly uiModel: TestOutputUIModel;
184
-
185
- constructor(
186
- @inject(TreeProps) props: TreeProps,
187
- @inject(TreeModel) model: TreeModel,
188
- @inject(ContextMenuRenderer) contextMenuRenderer: ContextMenuRenderer,
189
- ) {
190
- super(props, model, contextMenuRenderer);
191
- this.id = TestRunTreeWidget.ID;
192
- this.title.label = nls.localize('theia/test/testRuns', 'Test Runs');
193
- this.title.caption = nls.localize('theia/test/testRuns', 'Test Runs');
194
- this.title.iconClass = codicon('run');
195
- this.title.closable = true;
196
- }
197
-
198
- @postConstruct()
199
- protected override init(): void {
200
- super.init();
201
- this.addClass('theia-test-result-view');
202
- this.model.onSelectionChanged(() => {
203
- const node = this.model.selectedNodes[0];
204
- if (node instanceof TestRunNode) {
205
- this.uiModel.selectedOutputSource = {
206
- get output(): readonly TestOutputItem[] {
207
- return node.run.getOutput();
208
- },
209
- onDidAddTestOutput: Event.map(node.run.onDidChangeTestOutput, evt => evt.map(item => item[1]))
210
- };
211
- } else if (node instanceof TestItemNode) {
212
- this.uiModel.selectedOutputSource = {
213
- get output(): readonly TestOutputItem[] {
214
- return node.parent.run.getOutput(node.item);
215
- },
216
- onDidAddTestOutput: Event.map(node.parent.run.onDidChangeTestOutput, evt => evt.filter(item => item[0] === node.item).map(item => item[1]))
217
- };
218
- this.uiModel.selectedTestState = node.parent.run.getTestState(node.item);
219
- }
220
- });
221
- }
222
-
223
- protected override renderTree(model: TreeModel): React.ReactNode {
224
- if (CompositeTreeNode.is(this.model.root) && this.model.root.children.length > 0) {
225
- return super.renderTree(model);
226
- }
227
- return <div className='theia-widget-noInfo noMarkers'>{nls.localizeByDefault('No tests have been found in this workspace yet.')}</div>;
228
- }
229
-
230
- protected getTestStateClass(state: TestExecutionState | undefined): string {
231
- switch (state) {
232
- case TestExecutionState.Queued: return `${codicon('history')} queued`;
233
- case TestExecutionState.Running: return `${codicon('sync')} codicon-modifier-spin running`;
234
- case TestExecutionState.Skipped: return `${codicon('debug-step-over')} skipped`;
235
- case TestExecutionState.Failed: return `${codicon('error')} failed`;
236
- case TestExecutionState.Errored: return `${codicon('issues')} errored`;
237
- case TestExecutionState.Passed: return `${codicon('pass')} passed`;
238
- default: return codicon('circle');
239
- }
240
- }
241
-
242
- protected override renderIcon(node: TreeNode, props: NodeProps): React.ReactNode {
243
- if (node instanceof TestItemNode) {
244
- const state = node.parent.run.getTestState(node.item)?.state;
245
- return <div className={this.getTestStateClass(state)}></div >;
246
- } else if (node instanceof TestRunNode) {
247
- const icon = node.run.isRunning ? `${codicon('sync')} codicon-modifier-spin running` : codicon('circle');
248
- return <div className={icon}></div >;
249
- } else {
250
- return super.renderIcon(node, props);
251
- }
252
- }
253
-
254
- protected override toContextMenuArgs(node: SelectableTreeNode): (TestRun | TestItem | TestMessage[])[] {
255
- if (node instanceof TestRunNode) {
256
- return [node.run];
257
- } else if (node instanceof TestItemNode) {
258
- const item = node.item;
259
- const executionState = node.parent.run.getTestState(node.item);
260
- if (TestFailure.is(executionState)) {
261
- return [item, executionState.messages];
262
- }
263
- return [item];
264
- }
265
- return [];
266
- }
267
-
268
- override storeState(): object {
269
- return {}; // don't store any state for now
270
- }
271
- }
1
+ // *****************************************************************************
2
+ // Copyright (C) 2023 STMicroelectronics and others.
3
+ //
4
+ // This program and the accompanying materials are made available under the
5
+ // terms of the Eclipse Public License v. 2.0 which is available at
6
+ // http://www.eclipse.org/legal/epl-2.0.
7
+ //
8
+ // This Source Code may also be made available under the following Secondary
9
+ // Licenses when the conditions for such availability set forth in the Eclipse
10
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ // with the GNU Classpath Exception which is available at
12
+ // https://www.gnu.org/software/classpath/license.html.
13
+ //
14
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
+ // *****************************************************************************
16
+
17
+ import { injectable, inject, postConstruct } from '@theia/core/shared/inversify';
18
+ import { TreeWidget, TreeModel, TreeProps, CompositeTreeNode, TreeNode, TreeImpl, NodeProps, SelectableTreeNode } from '@theia/core/lib/browser/tree';
19
+ import { ContextMenuRenderer, codicon } from '@theia/core/lib/browser';
20
+ import { IconThemeService } from '@theia/core/lib/browser/icon-theme-service';
21
+ import { ThemeService } from '@theia/core/lib/browser/theming';
22
+ import { ContextKeyService } from '@theia/core/lib/browser/context-key-service';
23
+ import { TestController, TestExecutionState, TestFailure, TestItem, TestMessage, TestOutputItem, TestRun, TestService } from '../test-service';
24
+ import * as React from '@theia/core/shared/react';
25
+ import { Disposable, DisposableCollection, Event, nls } from '@theia/core';
26
+ import { TestExecutionStateManager } from './test-execution-state-manager';
27
+ import { TestOutputUIModel } from './test-output-ui-model';
28
+
29
+ class TestRunNode implements TreeNode, SelectableTreeNode {
30
+ constructor(readonly counter: number, readonly id: string, readonly run: TestRun, readonly parent: CompositeTreeNode) { }
31
+
32
+ get name(): string {
33
+ return this.run.name || nls.localize('theia/test/testRunDefaultName', '{0} run {1}', this.run.controller.label, this.counter);
34
+ };
35
+
36
+ expanded?: boolean;
37
+ selected: boolean = false;
38
+ children: TestItemNode[] = [];
39
+ }
40
+
41
+ class TestItemNode implements TreeNode, SelectableTreeNode {
42
+ constructor(readonly id: string, readonly item: TestItem, readonly parent: TestRunNode) { }
43
+ selected: boolean = false;
44
+
45
+ get name(): string {
46
+ return this.item.label;
47
+ }
48
+ }
49
+
50
+ interface RunInfo {
51
+ node: TestRunNode;
52
+ disposable: Disposable;
53
+ tests: Map<TestItem, TestItemNode>;
54
+ }
55
+
56
+ @injectable()
57
+ export class TestRunTree extends TreeImpl {
58
+ private ROOT: CompositeTreeNode = {
59
+ id: 'TestResults',
60
+ name: 'Test Results',
61
+ parent: undefined,
62
+ children: [],
63
+ visible: false
64
+ };
65
+
66
+ @inject(TestService) protected readonly testService: TestService;
67
+
68
+ private controllerListeners = new Map<string, Disposable>();
69
+
70
+ private runs = new Map<TestRun, RunInfo>();
71
+ private nextId = 0;
72
+
73
+ @postConstruct()
74
+ init(): void {
75
+ this.root = this.ROOT;
76
+ this.testService.getControllers().forEach(controller => {
77
+ this.addController(controller);
78
+ });
79
+
80
+ this.testService.onControllersChanged(controllerDelta => {
81
+ controllerDelta.removed?.forEach(controller => {
82
+ this.controllerListeners.get(controller)?.dispose();
83
+ });
84
+
85
+ controllerDelta.added?.forEach(controller => this.addController(controller));
86
+ });
87
+ }
88
+
89
+ private addController(controller: TestController): void {
90
+ controller.testRuns.forEach(run => this.addRun(run));
91
+ const listeners = new DisposableCollection();
92
+ this.controllerListeners.set(controller.id, listeners);
93
+
94
+ listeners.push(controller.onRunsChanged(runDelta => {
95
+ runDelta.removed?.forEach(run => {
96
+ this.runs.get(run)?.disposable.dispose();
97
+ this.runs.delete(run);
98
+ this.refresh(this.ROOT);
99
+ });
100
+ runDelta.added?.forEach(run => {
101
+ this.addRun(run);
102
+ this.refresh(this.ROOT);
103
+ });
104
+ }));
105
+ }
106
+
107
+ private addRun(run: TestRun): void {
108
+ const newNode = this.createRunNode(run);
109
+ const affected: TestItemNode[] = [];
110
+
111
+ const disposables = new DisposableCollection();
112
+
113
+ disposables.push(run.onDidChangeTestState(deltas => {
114
+ let needsRefresh = false;
115
+ deltas.forEach(delta => {
116
+ if (delta.newState) {
117
+ if (delta.newState.state > TestExecutionState.Queued) {
118
+ const testNode = info.tests.get(delta.test);
119
+ if (!testNode) {
120
+ if (info.tests.size === 0) {
121
+ newNode.expanded = true;
122
+ }
123
+ info.tests.set(delta.test, this.createTestItemNode(newNode, delta.test));
124
+ needsRefresh = true;
125
+ } else {
126
+ affected.push(testNode);
127
+ }
128
+ }
129
+ } else {
130
+ info.tests.delete(delta.test);
131
+ needsRefresh = true;
132
+ }
133
+ });
134
+ if (needsRefresh) {
135
+ this.refresh(newNode);
136
+ } else {
137
+ this.onDidUpdateEmitter.fire(affected);
138
+ }
139
+ }));
140
+ disposables.push(run.onDidChangeProperty(() => this.onDidUpdateEmitter.fire([])));
141
+ const info = {
142
+ node: newNode,
143
+ disposable: disposables,
144
+
145
+ tests: new Map(run.items.filter(item => (run.getTestState(item)?.state || 0) > TestExecutionState.Queued).map(item => [item, this.createTestItemNode(newNode, item)]))
146
+ };
147
+ this.runs.set(run, info);
148
+ }
149
+
150
+ protected createRunNode(run: TestRun): TestRunNode {
151
+ return new TestRunNode(this.nextId, `id-${this.nextId++}`, run, this.ROOT);
152
+ }
153
+
154
+ createTestItemNode(parent: TestRunNode, item: TestItem): TestItemNode {
155
+ return new TestItemNode(`testitem-${this.nextId++}`, item, parent);
156
+ }
157
+
158
+ protected override async resolveChildren(parent: CompositeTreeNode): Promise<TreeNode[]> {
159
+ if (parent === this.ROOT) {
160
+ return Promise.resolve([...this.runs.values()].reverse().map(info => info.node));
161
+ } else if (parent instanceof TestRunNode) {
162
+ const runInfo = this.runs.get(parent.run);
163
+ if (runInfo) {
164
+ return Promise.resolve([...runInfo.tests.values()]);
165
+ } else {
166
+ return Promise.resolve([]);
167
+ }
168
+ } else {
169
+ return Promise.resolve([]);
170
+ }
171
+ }
172
+ }
173
+
174
+ @injectable()
175
+ export class TestRunTreeWidget extends TreeWidget {
176
+
177
+ static ID = 'test-run-widget';
178
+
179
+ @inject(IconThemeService) protected readonly iconThemeService: IconThemeService;
180
+ @inject(ContextKeyService) protected readonly contextKeys: ContextKeyService;
181
+ @inject(ThemeService) protected readonly themeService: ThemeService;
182
+ @inject(TestExecutionStateManager) protected readonly stateManager: TestExecutionStateManager;
183
+ @inject(TestOutputUIModel) protected readonly uiModel: TestOutputUIModel;
184
+
185
+ constructor(
186
+ @inject(TreeProps) props: TreeProps,
187
+ @inject(TreeModel) model: TreeModel,
188
+ @inject(ContextMenuRenderer) contextMenuRenderer: ContextMenuRenderer,
189
+ ) {
190
+ super(props, model, contextMenuRenderer);
191
+ this.id = TestRunTreeWidget.ID;
192
+ this.title.label = nls.localize('theia/test/testRuns', 'Test Runs');
193
+ this.title.caption = nls.localize('theia/test/testRuns', 'Test Runs');
194
+ this.title.iconClass = codicon('run');
195
+ this.title.closable = true;
196
+ }
197
+
198
+ @postConstruct()
199
+ protected override init(): void {
200
+ super.init();
201
+ this.addClass('theia-test-result-view');
202
+ this.model.onSelectionChanged(() => {
203
+ const node = this.model.selectedNodes[0];
204
+ if (node instanceof TestRunNode) {
205
+ this.uiModel.selectedOutputSource = {
206
+ get output(): readonly TestOutputItem[] {
207
+ return node.run.getOutput();
208
+ },
209
+ onDidAddTestOutput: Event.map(node.run.onDidChangeTestOutput, evt => evt.map(item => item[1]))
210
+ };
211
+ } else if (node instanceof TestItemNode) {
212
+ this.uiModel.selectedOutputSource = {
213
+ get output(): readonly TestOutputItem[] {
214
+ return node.parent.run.getOutput(node.item);
215
+ },
216
+ onDidAddTestOutput: Event.map(node.parent.run.onDidChangeTestOutput, evt => evt.filter(item => item[0] === node.item).map(item => item[1]))
217
+ };
218
+ this.uiModel.selectedTestState = node.parent.run.getTestState(node.item);
219
+ }
220
+ });
221
+ }
222
+
223
+ protected override renderTree(model: TreeModel): React.ReactNode {
224
+ if (CompositeTreeNode.is(this.model.root) && this.model.root.children.length > 0) {
225
+ return super.renderTree(model);
226
+ }
227
+ return <div className='theia-widget-noInfo noMarkers'>{nls.localizeByDefault('No tests have been found in this workspace yet.')}</div>;
228
+ }
229
+
230
+ protected getTestStateClass(state: TestExecutionState | undefined): string {
231
+ switch (state) {
232
+ case TestExecutionState.Queued: return `${codicon('history')} queued`;
233
+ case TestExecutionState.Running: return `${codicon('sync')} codicon-modifier-spin running`;
234
+ case TestExecutionState.Skipped: return `${codicon('debug-step-over')} skipped`;
235
+ case TestExecutionState.Failed: return `${codicon('error')} failed`;
236
+ case TestExecutionState.Errored: return `${codicon('issues')} errored`;
237
+ case TestExecutionState.Passed: return `${codicon('pass')} passed`;
238
+ default: return codicon('circle');
239
+ }
240
+ }
241
+
242
+ protected override renderIcon(node: TreeNode, props: NodeProps): React.ReactNode {
243
+ if (node instanceof TestItemNode) {
244
+ const state = node.parent.run.getTestState(node.item)?.state;
245
+ return <div className={this.getTestStateClass(state)}></div >;
246
+ } else if (node instanceof TestRunNode) {
247
+ const icon = node.run.isRunning ? `${codicon('sync')} codicon-modifier-spin running` : codicon('circle');
248
+ return <div className={icon}></div >;
249
+ } else {
250
+ return super.renderIcon(node, props);
251
+ }
252
+ }
253
+
254
+ protected override toContextMenuArgs(node: SelectableTreeNode): (TestRun | TestItem | TestMessage[])[] {
255
+ if (node instanceof TestRunNode) {
256
+ return [node.run];
257
+ } else if (node instanceof TestItemNode) {
258
+ const item = node.item;
259
+ const executionState = node.parent.run.getTestState(node.item);
260
+ if (TestFailure.is(executionState)) {
261
+ return [item, executionState.messages];
262
+ }
263
+ return [item];
264
+ }
265
+ return [];
266
+ }
267
+
268
+ override storeState(): object {
269
+ return {}; // don't store any state for now
270
+ }
271
+ }