Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[vscode] Support TestMessage#contextValue #13176

Merged
merged 1 commit into from
Dec 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

## Unreleased

- [plugin] Support TestMessage.contextValue from vscode API [#13176](https://github.com/eclipse-theia/theia/pull/13176) - contributed on behalf of STMicroelectronics
- [terminal] Use application shell methods for expanding/collapsing bottom panel for "Terminal: Toggle Terminal" command [#13131](https://github.com/eclipse-theia/theia/pull/13131)
- [workspace] Create an empty workspace if no workspace is active on updateWorkspaceFolders [#13181](https://github.com/eclipse-theia/theia/pull/13181) - contributed on behalf of STMicroelectronics

Expand Down
20 changes: 20 additions & 0 deletions packages/plugin-ext/src/common/test-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ export interface TestMessageDTO {
readonly actual?: string;
readonly location?: Location;
readonly message: string | MarkdownString;
readonly contextValue?: string;
}

export interface TestItemDTO {
Expand Down Expand Up @@ -131,3 +132,22 @@ export namespace TestItemReference {
}
}

export interface TestMessageArg {
testItemReference: TestItemReference | undefined,
testMessage: TestMessageDTO
}

export namespace TestMessageArg {
export function is(arg: unknown): arg is TestMessageArg {
return isObject<TestMessageArg>(arg)
&& isObject<TestMessageDTO>(arg.testMessage)
&& (MarkdownString.is(arg.testMessage.message) || typeof arg.testMessage.message === 'string');
}

export function create(testItemReference: TestItemReference | undefined, testMessageDTO: TestMessageDTO): TestMessageArg {
return {
testItemReference: testItemReference,
testMessage: testMessageDTO
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,13 @@ import { ScmRepository } from '@theia/scm/lib/browser/scm-repository';
import { ScmService } from '@theia/scm/lib/browser/scm-service';
import { TimelineItem } from '@theia/timeline/lib/common/timeline-model';
import { ScmCommandArg, TimelineCommandArg, TreeViewItemReference } from '../../../common';
import { TestItemReference, TestMessageArg } from '../../../common/test-types';
import { PluginScmProvider, PluginScmResource, PluginScmResourceGroup } from '../scm-main';
import { TreeViewWidget } from '../view/tree-view-widget';
import { CodeEditorWidgetUtil, codeToTheiaMappings, ContributionPoint } from './vscode-theia-menu-mappings';
import { TAB_BAR_TOOLBAR_CONTEXT_MENU } from '@theia/core/lib/browser/shell/tab-bar-toolbar';
import { TestItem, TestMessage } from '@theia/test/lib/browser/test-service';
import { fromLocation } from '../hierarchy/hierarchy-types-converters';

export type ArgumentAdapter = (...args: unknown[]) => unknown[];

Expand Down Expand Up @@ -79,6 +82,7 @@ export class PluginMenuCommandAdapter implements MenuCommandAdapter {
@postConstruct()
protected init(): void {
const toCommentArgs: ArgumentAdapter = (...args) => this.toCommentArgs(...args);
const toTestMessageArgs: ArgumentAdapter = (...args) => this.toTestMessageArgs(...args);
const firstArgOnly: ArgumentAdapter = (...args) => [args[0]];
const noArgs: ArgumentAdapter = () => [];
const toScmArgs: ArgumentAdapter = (...args) => this.toScmArgs(...args);
Expand All @@ -100,6 +104,7 @@ export class PluginMenuCommandAdapter implements MenuCommandAdapter {
['scm/resourceGroup/context', toScmArgs],
['scm/resourceState/context', toScmArgs],
['scm/title', () => [this.toScmArg(this.scmService.selectedRepository)]],
['testing/message/context', toTestMessageArgs],
['timeline/item/context', (...args) => this.toTimelineArgs(...args)],
['view/item/context', (...args) => this.toTreeArgs(...args)],
['view/title', noArgs],
Expand Down Expand Up @@ -230,6 +235,30 @@ export class PluginMenuCommandAdapter implements MenuCommandAdapter {
return timelineArgs;
}

protected toTestMessageArgs(...args: any[]): any[] {
let testItem: TestItem | undefined;
let testMessage: TestMessage | undefined;
for (const arg of args) {
if (TestItem.is(arg)) {
testItem = arg;
} else if (Array.isArray(arg) && TestMessage.is(arg[0])) {
testMessage = arg[0];
}
}
if (testMessage) {
const testItemReference = (testItem && testItem.controller) ? TestItemReference.create(testItem.controller.id, testItem.path) : undefined;
const testMessageDTO = {
message: testMessage.message,
actual: testMessage.actual,
expected: testMessage.expected,
contextValue: testMessage.contextValue,
location: testMessage.location ? fromLocation(testMessage.location) : undefined
};
return [TestMessageArg.create(testItemReference, testMessageDTO)];
tsmaeder marked this conversation as resolved.
Show resolved Hide resolved
}
return [];
}

protected toTimelineArg(arg: TimelineItem): TimelineCommandArg {
return {
timelineHandle: arg.handle,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { VIEW_ITEM_CONTEXT_MENU } from '../view/tree-view-widget';
import { WEBVIEW_CONTEXT_MENU, WebviewWidget } from '../webview/webview';
import { EDITOR_LINENUMBER_CONTEXT_MENU } from '@theia/editor/lib/browser/editor-linenumber-contribution';
import { TEST_VIEW_CONTEXT_MENU } from '@theia/test/lib/browser/view/test-view-contribution';
import { TEST_RUNS_CONTEXT_MENU } from '@theia/test/lib/browser/view/test-run-view-contribution';

export const PLUGIN_EDITOR_TITLE_MENU = ['plugin_editor/title'];
export const PLUGIN_EDITOR_TITLE_RUN_MENU = ['plugin_editor/title/run'];
Expand All @@ -57,6 +58,7 @@ export const implementedVSCodeContributionPoints = [
'scm/title',
'timeline/item/context',
'testing/item/context',
'testing/message/context',
'view/item/context',
'view/title',
'webview/context'
Expand All @@ -83,6 +85,7 @@ export const codeToTheiaMappings = new Map<ContributionPoint, MenuPath[]>([
['scm/resourceState/context', [ScmTreeWidget.RESOURCE_CONTEXT_MENU]],
['scm/title', [PLUGIN_SCM_TITLE_MENU]],
['testing/item/context', [TEST_VIEW_CONTEXT_MENU]],
['testing/message/context', [TEST_RUNS_CONTEXT_MENU]],
['timeline/item/context', [TIMELINE_ITEM_CONTEXT_MENU]],
['view/item/context', [VIEW_ITEM_CONTEXT_MENU]],
['view/title', [PLUGIN_VIEW_TITLE_MENU]],
Expand Down
26 changes: 25 additions & 1 deletion packages/plugin-ext/src/plugin/tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,11 @@ import { TestItemImpl, TestItemCollection } from './test-item';
import { AccumulatingTreeDeltaEmitter, TreeDelta } from '@theia/test/lib/common/tree-delta';
import {
TestItemDTO, TestOutputDTO, TestExecutionState, TestRunProfileDTO,
TestRunProfileKind, TestRunRequestDTO, TestStateChangeDTO, TestItemReference
TestRunProfileKind, TestRunRequestDTO, TestStateChangeDTO, TestItemReference, TestMessageArg, TestMessageDTO
} from '../common/test-types';
import { ChangeBatcher, observableProperty } from '@theia/test/lib/common/collections';
import { TestRunRequest } from './types-impl';
import { MarkdownString } from '../common/plugin-api-rpc-model';

type RefreshHandler = (token: theia.CancellationToken) => void | theia.Thenable<void>;
type ResolveHandler = (item: theia.TestItem | undefined) => theia.Thenable<void> | void;
Expand Down Expand Up @@ -335,6 +336,8 @@ export class TestingExtImpl implements TestingExt {
return this.toTestItem(arg);
} else if (Array.isArray(arg)) {
return arg.map(param => TestItemReference.is(param) ? this.toTestItem(param) : param);
tsmaeder marked this conversation as resolved.
Show resolved Hide resolved
} else if (TestMessageArg.is(arg)) {
return this.fromTestMessageArg(arg);
} else {
return arg;
}
Expand All @@ -343,6 +346,27 @@ export class TestingExtImpl implements TestingExt {

}

fromTestMessageArg(arg: TestMessageArg): { test?: theia.TestItem, message: theia.TestMessage } {
const testItem = arg.testItemReference ? this.toTestItem(arg.testItemReference) : undefined;
const message = this.toTestMessage(arg.testMessage);
return {
test: testItem,
message: message
};
}

toTestMessage(testMessage: TestMessageDTO): theia.TestMessage {
const message = MarkdownString.is(testMessage.message) ? Convert.toMarkdown(testMessage.message) : testMessage.message;

return {
message: message,
actualOutput: testMessage.actual,
expectedOutput: testMessage.expected,
contextValue: testMessage.contextValue,
location: testMessage.location ? Convert.toLocation(testMessage.location) : undefined
};
}

toTestItem(ref: TestItemReference): theia.TestItem {
const result = this.withController(ref.controllerId).items.find(ref.testPath);
if (!result) {
Expand Down
3 changes: 2 additions & 1 deletion packages/plugin-ext/src/plugin/type-converters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1644,7 +1644,8 @@ export namespace TestMessage {
location: fromLocation(message.location),
message: fromMarkdown(message.message)!,
expected: message.expectedOutput,
actual: message.actualOutput
actual: message.actualOutput,
contextValue: message.contextValue
}];
}
}
Expand Down
1 change: 1 addition & 0 deletions packages/plugin-ext/src/plugin/types-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3293,6 +3293,7 @@ export class TestMessage implements theia.TestMessage {
public expectedOutput?: string;
public actualOutput?: string;
public location?: theia.Location;
public contextValue?: string;

public static diff(message: string | theia.MarkdownString, expected: string, actual: string): theia.TestMessage {
const msg = new TestMessage(message);
Expand Down
31 changes: 31 additions & 0 deletions packages/plugin/src/theia.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16448,6 +16448,37 @@ export module '@theia/plugin' {
*/
location?: Location;

/**
* Context value of the test item. This can be used to contribute message-
* specific actions to the test peek view. The value set here can be found
* in the `testMessage` property of the following `menus` contribution points:
*
* - `testing/message/context` - context menu for the message in the results tree
* - `testing/message/content` - a prominent button overlaying editor content where
* the message is displayed.
*
* For example:
*
* ```json
* "contributes": {
* "menus": {
* "testing/message/content": [
* {
* "command": "extension.deleteCommentThread",
* "when": "testMessage == canApplyRichDiff"
* }
* ]
* }
* }
* ```
*
* The command will be called with an object containing:
* - `test`: the {@link TestItem} the message is associated with, *if* it
* is still present in the {@link TestController.items} collection.
* - `message`: the {@link TestMessage} instance.
*/
contextValue?: string;

/**
* Creates a new TestMessage that will present as a diff in the editor.
* @param message Message to display to the user.
Expand Down
8 changes: 8 additions & 0 deletions packages/test/src/browser/test-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,13 @@ export interface TestMessage {
readonly actual?: string;
readonly location: Location;
readonly message: string | MarkdownString;
readonly contextValue?: string;
}

export namespace TestMessage {
export function is(obj: unknown): obj is TestMessage {
return isObject<TestMessage>(obj) && (MarkdownString.is(obj.message) || typeof obj.message === 'string');
}
}

export interface TestState {
Expand Down Expand Up @@ -136,6 +143,7 @@ export interface TestItem {
readonly controller: TestController | undefined;
readonly canResolveChildren: boolean;
resolveChildren(): void;
readonly path: string[];
}

export namespace TestItem {
Expand Down
36 changes: 36 additions & 0 deletions packages/test/src/browser/view/test-context-key-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// *****************************************************************************
// Copyright (C) 2023 STMicroelectronics and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************

import { injectable, inject, postConstruct } from '@theia/core/shared/inversify';
import { ContextKeyService, ContextKey } from '@theia/core/lib/browser/context-key-service';

@injectable()
export class TestContextKeyService {

@inject(ContextKeyService)
protected readonly contextKeyService: ContextKeyService;

protected _contextValue: ContextKey<string | undefined>;
get contextValue(): ContextKey<string | undefined> {
return this._contextValue;
}

@postConstruct()
protected init(): void {
this._contextValue = this.contextKeyService.createKey<string | undefined>('testMessage', undefined);
}

}
10 changes: 9 additions & 1 deletion packages/test/src/browser/view/test-output-ui-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@
// *****************************************************************************

import { inject, injectable, postConstruct } from '@theia/core/shared/inversify';
import { TestController, TestOutputItem, TestRun, TestService, TestState, TestStateChangedEvent } from '../test-service';
import { TestController, TestFailure, TestOutputItem, TestRun, TestService, TestState, TestStateChangedEvent } from '../test-service';
import { Disposable, Emitter, Event } from '@theia/core';
import { TestContextKeyService } from './test-context-key-service';

export interface ActiveRunEvent {
controller: TestController;
Expand All @@ -41,6 +42,7 @@ interface ActiveTestRunInfo {

@injectable()
export class TestOutputUIModel {
@inject(TestContextKeyService) protected readonly testContextKeys: TestContextKeyService;
@inject(TestService) protected testService: TestService;

protected readonly activeRuns = new Map<string, ActiveTestRunInfo>();
Expand Down Expand Up @@ -139,6 +141,12 @@ export class TestOutputUIModel {
set selectedTestState(element: TestState | undefined) {
if (element !== this._selectedTestState) {
this._selectedTestState = element;
if (this._selectedTestState && TestFailure.is(this._selectedTestState.state)) {
const message = this._selectedTestState.state.messages[0];
this.testContextKeys.contextValue.set(message.contextValue);
} else {
this.testContextKeys.contextValue.reset();
}
this.onDidChangeSelectedTestStateEmitter.fire(element);
}
}
Expand Down
11 changes: 8 additions & 3 deletions packages/test/src/browser/view/test-run-widget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { ContextMenuRenderer, codicon } from '@theia/core/lib/browser';
import { IconThemeService } from '@theia/core/lib/browser/icon-theme-service';
import { ThemeService } from '@theia/core/lib/browser/theming';
import { ContextKeyService } from '@theia/core/lib/browser/context-key-service';
import { TestController, TestExecutionState, TestItem, TestOutputItem, TestRun, TestService } from '../test-service';
import { TestController, TestExecutionState, TestFailure, TestItem, TestMessage, TestOutputItem, TestRun, TestService } from '../test-service';
import * as React from '@theia/core/shared/react';
import { Disposable, DisposableCollection, Event, nls } from '@theia/core';
import { TestExecutionStateManager } from './test-execution-state-manager';
Expand Down Expand Up @@ -251,11 +251,16 @@ export class TestRunTreeWidget extends TreeWidget {
}
}

protected override toContextMenuArgs(node: SelectableTreeNode): (TestRun | TestItem)[] {
protected override toContextMenuArgs(node: SelectableTreeNode): (TestRun | TestItem | TestMessage[])[] {
if (node instanceof TestRunNode) {
return [node.run];
} else if (node instanceof TestItemNode) {
return [node.item];
const item = node.item;
const executionState = node.parent.run.getTestState(node.item);
if (TestFailure.is(executionState)) {
return [item, executionState.messages];
}
return [item];
}
return [];
}
Expand Down
2 changes: 2 additions & 0 deletions packages/test/src/browser/view/test-view-frontend-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,12 @@ import { TestOutputUIModel } from './test-output-ui-model';
import { TestRunTree, TestRunTreeWidget } from './test-run-widget';
import { TestResultViewContribution } from './test-result-view-contribution';
import { TEST_RUNS_CONTEXT_MENU, TestRunViewContribution } from './test-run-view-contribution';
import { TestContextKeyService } from './test-context-key-service';

export default new ContainerModule(bind => {

bindContributionProvider(bind, TestContribution);
bind(TestContextKeyService).toSelf().inSingletonScope();
bind(TestService).to(DefaultTestService).inSingletonScope();

bind(WidgetFactory).toDynamicValue(({ container }) => ({
Expand Down
Loading