diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2d3fcdfacf9bc..853ba325270f4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,7 @@
## not yet released
- [scm] added support for dirty diff peek view [#13104](https://github.com/eclipse-theia/theia/pull/13104)
+- [test] stub VS Code `Test Coverage` API [#13631](https://github.com/eclipse-theia/theia/pull/13631)
[Breaking Changes:](#breaking_changes_not_yet_released)
- [scm] revised some of the dirty diff related types [#13104](https://github.com/eclipse-theia/theia/pull/13104)
diff --git a/packages/plugin-ext/src/plugin/plugin-context.ts b/packages/plugin-ext/src/plugin/plugin-context.ts
index 83c3f80a5c312..f405a67633964 100644
--- a/packages/plugin-ext/src/plugin/plugin-context.ts
+++ b/packages/plugin-ext/src/plugin/plugin-context.ts
@@ -203,7 +203,12 @@ import {
TerminalOutputAnchor,
TerminalQuickFixTerminalCommand,
TerminalQuickFixOpener,
- TestResultState
+ TestResultState,
+ BranchCoverage,
+ DeclarationCoverage,
+ FileCoverage,
+ StatementCoverage,
+ TestCoverageCount
} from './types-impl';
import { AuthenticationExtImpl } from './authentication-ext';
import { SymbolKind } from '../common/plugin-api-rpc-model';
@@ -1402,7 +1407,12 @@ export function createAPIFactory(
TerminalQuickFixTerminalCommand,
TerminalQuickFixOpener,
EditSessionIdentityMatch,
- TestResultState
+ TestResultState,
+ BranchCoverage,
+ DeclarationCoverage,
+ FileCoverage,
+ StatementCoverage,
+ TestCoverageCount
};
};
}
diff --git a/packages/plugin-ext/src/plugin/tests.ts b/packages/plugin-ext/src/plugin/tests.ts
index d6a45a7268aaa..70ae52f38a907 100644
--- a/packages/plugin-ext/src/plugin/tests.ts
+++ b/packages/plugin-ext/src/plugin/tests.ts
@@ -240,6 +240,8 @@ class TestRun implements theia.TestRun {
onDidEnd: Event = this.onDidEndEmitter.event;
private onWillFlushEmitter = new Emitter();
onWillFlush: Event = this.onWillFlushEmitter.event;
+ private onDidDisposeEmitter = new Emitter();
+ onDidDispose: Event = this.onDidDisposeEmitter.event;
readonly id: string;
private testStateDeltas = new Map();
@@ -293,6 +295,9 @@ class TestRun implements theia.TestRun {
this.proxy.$notifyTestRunEnded(this.controller.id, this.id);
}
+ /** @stubbed */
+ addCoverage(fileCoverage: theia.FileCoverage): void { }
+
private checkNotEnded(test: theia.TestItem): boolean {
if (this.ended) {
console.warn(`Setting the state of test "${test.id}" is a no - op after the run ends.`);
diff --git a/packages/plugin-ext/src/plugin/types-impl.ts b/packages/plugin-ext/src/plugin/types-impl.ts
index 0a0e7c5611407..d46a05d63c4f4 100644
--- a/packages/plugin-ext/src/plugin/types-impl.ts
+++ b/packages/plugin-ext/src/plugin/types-impl.ts
@@ -3330,6 +3330,72 @@ export class TestMessage implements theia.TestMessage {
constructor(public message: string | theia.MarkdownString) { }
}
+@es5ClassCompat
+export class TestCoverageCount {
+ constructor( public covered: number, public total: number) { }
+}
+
+@es5ClassCompat
+export class FileCoverage {
+
+ detailedCoverage?: theia.FileCoverageDetail[];
+
+ static fromDetails(uri: theia.Uri, details: theia.FileCoverageDetail[]): FileCoverage {
+ const statements = new TestCoverageCount(0, 0);
+ const branches = new TestCoverageCount(0, 0);
+ const decl = new TestCoverageCount(0, 0);
+
+ for (const detail of details) {
+ if (detail instanceof StatementCoverage) {
+ statements.total += 1;
+ statements.covered += detail.executed ? 1 : 0;
+
+ for (const branch of detail.branches) {
+ branches.total += 1;
+ branches.covered += branch.executed ? 1 : 0;
+ }
+ } else {
+ decl.total += 1;
+ decl.covered += detail.executed ? 1 : 0;
+ }
+ }
+
+ const coverage = new FileCoverage(
+ uri,
+ statements,
+ branches.total > 0 ? branches : undefined,
+ decl.total > 0 ? decl : undefined,
+ );
+
+ coverage.detailedCoverage = details;
+
+ return coverage;
+ }
+
+ constructor(
+ public uri: theia.Uri,
+ public statementCoverage: TestCoverageCount,
+ public branchCoverage?: TestCoverageCount,
+ public declarationCoverage?: TestCoverageCount,
+ ) { }
+}
+
+@es5ClassCompat
+export class StatementCoverage implements theia.StatementCoverage {
+ constructor(public executed: number | boolean, public location: Position | Range, public branches: BranchCoverage[] = []) { }
+}
+
+export class BranchCoverage implements theia.BranchCoverage {
+ constructor(public executed: number | boolean, public location?: Position | Range, public label?: string) { }
+}
+
+@es5ClassCompat
+export class DeclarationCoverage implements theia.DeclarationCoverage {
+ constructor(public name: string, public executed: number | boolean, public location: Position | Range) { }
+}
+
+export type FileCoverageDetail = StatementCoverage | DeclarationCoverage;
+
@es5ClassCompat
export class TimelineItem {
timestamp: number;
diff --git a/packages/plugin/src/theia.d.ts b/packages/plugin/src/theia.d.ts
index e8015f3e5cce4..3ffce743ddb84 100644
--- a/packages/plugin/src/theia.d.ts
+++ b/packages/plugin/src/theia.d.ts
@@ -16035,6 +16035,18 @@ export module '@theia/plugin' {
*/
runHandler: (request: TestRunRequest, token: CancellationToken) => Thenable | void;
+ /**
+ * An extension-provided function that provides detailed statement and
+ * function-level coverage for a file. The editor will call this when more
+ * detail is needed for a file, such as when it's opened in an editor or
+ * expanded in the **Test Coverage** view.
+ *
+ * The {@link FileCoverage} object passed to this function is the same instance
+ * emitted on {@link TestRun.addCoverage} calls associated with this profile.
+ * @stubbed
+ */
+ loadDetailedCoverage?: (testRun: TestRun, fileCoverage: FileCoverage, token: CancellationToken) => Thenable;
+
/**
* Deletes the run profile.
*/
@@ -16314,11 +16326,23 @@ export module '@theia/plugin' {
*/
appendOutput(output: string, location?: Location, test?: TestItem): void;
+ /**
+ * Adds coverage for a file in the run.
+ * @stubbed
+ */
+ addCoverage(fileCoverage: FileCoverage): void;
+
/**
* Signals the end of the test run. Any tests included in the run whose
* states have not been updated will have their state reset.
*/
end(): void;
+
+ /**
+ * An event fired when the editor is no longer interested in data
+ * associated with the test run.
+ */
+ onDidDispose: Event;
}
/**
@@ -16527,6 +16551,178 @@ export module '@theia/plugin' {
*/
constructor(message: string | MarkdownString);
}
+
+ /**
+ * A class that contains information about a covered resource. A count can
+ * be give for lines, branches, and declarations in a file.
+ */
+ export class TestCoverageCount {
+ /**
+ * Number of items covered in the file.
+ */
+ covered: number;
+ /**
+ * Total number of covered items in the file.
+ */
+ total: number;
+
+ /**
+ * @param covered Value for {@link TestCoverageCount.covered}
+ * @param total Value for {@link TestCoverageCount.total}
+ */
+ constructor(covered: number, total: number);
+ }
+
+ /**
+ * Contains coverage metadata for a file.
+ */
+ export class FileCoverage {
+ /**
+ * File URI.
+ */
+ readonly uri: Uri;
+
+ /**
+ * Statement coverage information. If the reporter does not provide statement
+ * coverage information, this can instead be used to represent line coverage.
+ */
+ statementCoverage: TestCoverageCount;
+
+ /**
+ * Branch coverage information.
+ */
+ branchCoverage?: TestCoverageCount;
+
+ /**
+ * Declaration coverage information. Depending on the reporter and
+ * language, this may be types such as functions, methods, or namespaces.
+ */
+ declarationCoverage?: TestCoverageCount;
+
+ /**
+ * Creates a {@link FileCoverage} instance with counts filled in from
+ * the coverage details.
+ * @param uri Covered file URI
+ * @param detailed Detailed coverage information
+ */
+ static fromDetails(uri: Uri, details: readonly FileCoverageDetail[]): FileCoverage;
+
+ /**
+ * @param uri Covered file URI
+ * @param statementCoverage Statement coverage information. If the reporter
+ * does not provide statement coverage information, this can instead be
+ * used to represent line coverage.
+ * @param branchCoverage Branch coverage information
+ * @param declarationCoverage Declaration coverage information
+ */
+ constructor(
+ uri: Uri,
+ statementCoverage: TestCoverageCount,
+ branchCoverage?: TestCoverageCount,
+ declarationCoverage?: TestCoverageCount,
+ );
+ }
+
+ /**
+ * Contains coverage information for a single statement or line.
+ */
+ export class StatementCoverage {
+ /**
+ * The number of times this statement was executed, or a boolean indicating
+ * whether it was executed if the exact count is unknown. If zero or false,
+ * the statement will be marked as un-covered.
+ */
+ executed: number | boolean;
+
+ /**
+ * Statement location.
+ */
+ location: Position | Range;
+
+ /**
+ * Coverage from branches of this line or statement. If it's not a
+ * conditional, this will be empty.
+ */
+ branches: BranchCoverage[];
+
+ /**
+ * @param location The statement position.
+ * @param executed The number of times this statement was executed, or a
+ * boolean indicating whether it was executed if the exact count is
+ * unknown. If zero or false, the statement will be marked as un-covered.
+ * @param branches Coverage from branches of this line. If it's not a
+ * conditional, this should be omitted.
+ */
+ constructor(executed: number | boolean, location: Position | Range, branches?: BranchCoverage[]);
+ }
+
+ /**
+ * Contains coverage information for a branch of a {@link StatementCoverage}.
+ */
+ export class BranchCoverage {
+ /**
+ * The number of times this branch was executed, or a boolean indicating
+ * whether it was executed if the exact count is unknown. If zero or false,
+ * the branch will be marked as un-covered.
+ */
+ executed: number | boolean;
+
+ /**
+ * Branch location.
+ */
+ location?: Position | Range;
+
+ /**
+ * Label for the branch, used in the context of "the ${label} branch was
+ * not taken," for example.
+ */
+ label?: string;
+
+ /**
+ * @param executed The number of times this branch was executed, or a
+ * boolean indicating whether it was executed if the exact count is
+ * unknown. If zero or false, the branch will be marked as un-covered.
+ * @param location The branch position.
+ */
+ constructor(executed: number | boolean, location?: Position | Range, label?: string);
+ }
+
+ /**
+ * Contains coverage information for a declaration. Depending on the reporter
+ * and language, this may be types such as functions, methods, or namespaces.
+ */
+ export class DeclarationCoverage {
+ /**
+ * Name of the declaration.
+ */
+ name: string;
+
+ /**
+ * The number of times this declaration was executed, or a boolean
+ * indicating whether it was executed if the exact count is unknown. If
+ * zero or false, the declaration will be marked as un-covered.
+ */
+ executed: number | boolean;
+
+ /**
+ * Declaration location.
+ */
+ location: Position | Range;
+
+ /**
+ * @param executed The number of times this declaration was executed, or a
+ * boolean indicating whether it was executed if the exact count is
+ * unknown. If zero or false, the declaration will be marked as un-covered.
+ * @param location The declaration position.
+ */
+ constructor(name: string, executed: number | boolean, location: Position | Range);
+ }
+
+ /**
+ * Coverage details returned from {@link TestRunProfile.loadDetailedCoverage}.
+ */
+ export type FileCoverageDetail = StatementCoverage | DeclarationCoverage;
+
/**
* Thenable is a common denominator between ES6 promises, Q, jquery.Deferred, WinJS.Promise,
* and others. This API makes no assumption about what promise library is being used which