Skip to content
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 packages/core-browser/src/contextkey/testing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ export const TestingHasAnyResults = new RawContextKey('testing.hasAnyResults', f
export const TestingIsRunning = new RawContextKey('testing.isRunning', false);
export const TestingIsInPeek = new RawContextKey('testing.isInPeek', true);
export const TestingIsPeekVisible = new RawContextKey('testing.isPeekVisible', false);
export const TestingCanRefreshTests = new RawContextKey('testing.canRefresh', false);
34 changes: 23 additions & 11 deletions packages/extension/src/browser/vscode/api/main.thread.tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,9 @@
import { Injectable, Optional, Autowired } from '@opensumi/di';
import { IRPCProtocol } from '@opensumi/ide-connection';
import { Logger } from '@opensumi/ide-core-browser';
import {
CancellationToken,
Disposable,
DisposableStore,
IDisposable,
URI,
} from '@opensumi/ide-core-common';
import { CancellationToken, Disposable, DisposableStore, IDisposable, URI } from '@opensumi/ide-core-common';
import { ITestController, ITestService, TestServiceToken } from '@opensumi/ide-testing';
import { ObservableValue } from '@opensumi/ide-testing/lib/common/observableValue';
import { ITestProfileService, TestProfileServiceToken } from '@opensumi/ide-testing/lib/common/test-profile';
import {
ITestResult,
Expand All @@ -31,7 +26,7 @@ import {
} from '@opensumi/ide-testing/lib/common/testCollection';
import { Range } from '@opensumi/monaco-editor-core/esm/vs/editor/common/core/range';

import { ExtHostAPIIdentifier, IExtHostTests, IMainThreadTesting } from '../../../common/vscode';
import { ExtHostAPIIdentifier, IExtHostTests, IMainThreadTesting, ITestControllerPatch } from '../../../common/vscode';

const reviveDiff = (diff: TestsDiff) => {
for (const entry of diff) {
Expand Down Expand Up @@ -59,6 +54,7 @@ export class MainThreadTestsImpl extends Disposable implements IMainThreadTestin
{
instance: ITestController;
label: string;
canRefresh: ObservableValue<boolean>;
disposable: IDisposable;
}
>();
Expand All @@ -77,12 +73,16 @@ export class MainThreadTestsImpl extends Disposable implements IMainThreadTestin
this.proxy = rpcProtocol.getProxy(ExtHostAPIIdentifier.ExtHostTests);
}

$registerTestController(controllerId: string, label: string): void {
$registerTestController(controllerId: string, label: string, canRefreshValue: boolean): void {
const disposable = new DisposableStore();
const canRefresh = disposable.add(new ObservableValue(canRefreshValue));

const controller: ITestController = {
id: controllerId,
label,
canRefresh,
configureRunProfile: (id) => this.proxy.$configureRunProfile(controllerId, id),
refreshTests: (token) => this.proxy.$refreshTests(controllerId, token),
runTests: (req, token) => this.proxy.$runControllerTests(req, token),
expandTest: (testId, levels) => this.proxy.$expandTest(testId, isFinite(levels) ? levels : -1),
};
Expand All @@ -93,12 +93,24 @@ export class MainThreadTestsImpl extends Disposable implements IMainThreadTestin
this.testProviderRegistrations.set(controllerId, {
instance: controller,
label,
canRefresh,
disposable,
});
}

$updateControllerLabel(controllerId: string, label: string): void {
this.logger.warn('test: updateControllerLabel>>', controllerId, label);
$updateController(controllerId: string, patch: ITestControllerPatch): void {
const controller = this.testProviderRegistrations.get(controllerId);
if (!controller) {
return;
}

if (patch.label !== undefined) {
controller.label = patch.label;
}

if (patch.canRefresh !== undefined) {
controller.canRefresh.value = patch.canRefresh;
}
}

$unregisterTestController(controllerId: string): void {
Expand Down
17 changes: 14 additions & 3 deletions packages/extension/src/common/vscode/tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@ import {
} from '@opensumi/ide-testing/lib/common/testCollection';

export interface IExtHostTests {
createTestController(controllerId: string, label: string): vscode.TestController;
createTestController(
controllerId: string,
label: string,
refreshHandler?: ((token: CancellationToken) => Thenable<void> | void) | undefined,
): vscode.TestController;

// #region API for main thread
$runControllerTests(req: RunTestForControllerRequest, token: CancellationToken): Promise<void>;
Expand All @@ -49,16 +53,23 @@ export interface IExtHostTests {
): Promise<CoverageDetails[]>;
/** Configures a test run config. */
$configureRunProfile(controllerId: string, configId: number): void;
/** Asks the controller to refresh its tests */
$refreshTests(controllerId: string, token: CancellationToken): Promise<void>;
// #endregion
}

export interface ITestControllerPatch {
label?: string;
canRefresh?: boolean;
}

export interface IMainThreadTesting {
// --- test lifecycle:

/** Registers that there's a test controller with the given ID */
$registerTestController(controllerId: string, label: string): void;
$registerTestController(controllerId: string, label: string, canRefresh: boolean): void;
/** Updates the label of an existing test controller. */
$updateControllerLabel(controllerId: string, label: string): void;
$updateController(controllerId: string, patch: ITestControllerPatch): void;
/** Disposes of the test controller with the given ID */
$unregisterTestController(controllerId: string): void;
/** Requests tests published to VS Code. */
Expand Down
4 changes: 2 additions & 2 deletions packages/extension/src/hosted/api/vscode/ext.host.api.impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,8 +232,8 @@ export function createApiFactory(
},
},
tests: {
createTestController(controllerId: string, label: string) {
return extHostTests.createTestController(controllerId, label);
createTestController(controllerId: string, label: string, refreshHandler?: () => Thenable<void> | void) {
return extHostTests.createTestController(controllerId, label, refreshHandler);
},
},
// 类型定义
Expand Down
21 changes: 18 additions & 3 deletions packages/extension/src/hosted/api/vscode/ext.host.tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,9 +189,17 @@ export class ExtHostTestsImpl implements IExtHostTests {
this.controllers.get(controllerId)?.profiles.get(profileId)?.configureHandler?.();
}

async $refreshTests(controllerId: string, token: CancellationToken) {
await this.controllers.get(controllerId)?.controller.refreshHandler?.(token);
}

// #endregion

createTestController(controllerId: string, label: string): TestController {
createTestController(
controllerId: string,
label: string,
refreshHandler?: (token: CancellationToken) => Thenable<void> | void,
): TestController {
if (this.controllers.has(controllerId)) {
throw new Error(`Attempt to insert a duplicate controller with ID "${controllerId}"`);
}
Expand All @@ -211,7 +219,14 @@ export class ExtHostTestsImpl implements IExtHostTests {
set label(value: string) {
label = value;
collection.root.label = value;
proxy.$updateControllerLabel(controllerId, label);
proxy.$updateController(controllerId, { label });
},
get refreshHandler() {
return refreshHandler;
},
set refreshHandler(value: ((token: CancellationToken) => Thenable<void> | void) | undefined) {
refreshHandler = value;
proxy.$updateController(controllerId, { canRefresh: !!value });
},
get id() {
return controllerId;
Expand Down Expand Up @@ -256,7 +271,7 @@ export class ExtHostTestsImpl implements IExtHostTests {
// back compat:
(controller as any).createRunConfiguration = controller.createRunProfile;

proxy.$registerTestController(controllerId, label);
proxy.$registerTestController(controllerId, label, !!refreshHandler);
disposable.add(toDisposable(() => proxy.$unregisterTestController(controllerId)));

const info: ControllerInfo = { controller, collection, profiles };
Expand Down
1 change: 1 addition & 0 deletions packages/testing/src/browser/icons/icons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export const testingStatesToIcons = new Map<TestResultState, string>([

export const testingRunIcon = getExternalIcon('run');
export const testingRunAllIcon = getExternalIcon('run-all');
export const testingRefreshTests = getExternalIcon('refresh');

export const getIconWithColor = (state: TestResultState) =>
`${testingStatesToIcons.get(state)} ${testStatesToIconColors[state]}` || '';
63 changes: 48 additions & 15 deletions packages/testing/src/browser/test.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
SlotLocation,
} from '@opensumi/ide-core-browser';
import { IContextKey, IContextKeyService } from '@opensumi/ide-core-browser/lib/context-key';
import { TestingServiceProviderCount } from '@opensumi/ide-core-browser/lib/contextkey/testing';
import { TestingCanRefreshTests, TestingServiceProviderCount } from '@opensumi/ide-core-browser/lib/contextkey/testing';
import { IMainLayoutService } from '@opensumi/ide-main-layout';

import { AmbiguousRunTestsRequest, ITestController, ITestService, TestId } from '../common';
Expand All @@ -27,6 +27,7 @@ import { TestingView } from './components/testing.view';
export class TestServiceImpl extends Disposable implements ITestService {
private controllers = new Map<string, ITestController>();
private controllerCount: IContextKey<number>;
private canRefreshTests: IContextKey<boolean>;

private readonly processDiffEmitter = new Emitter<TestsDiff>();
private viewId = '';
Expand All @@ -49,6 +50,24 @@ export class TestServiceImpl extends Disposable implements ITestService {
constructor() {
super();
this.controllerCount = TestingServiceProviderCount.bind(this.contextKeyService);
this.canRefreshTests = TestingCanRefreshTests.bind(this.contextKeyService);
}

public getTestController(controllerId: string): ITestController | undefined {
return this.controllers.get(controllerId);
}

public async refreshTests(controllerId?: string): Promise<void> {
const cts = new CancellationTokenSource();
try {
if (controllerId) {
await this.controllers.get(controllerId)?.refreshTests(cts.token);
} else {
await Promise.all([...this.controllers.values()].map((c) => c.refreshTests(cts.token)));
}
} finally {
cts.dispose(true);
}
}

private registerTestingExplorerView(): string {
Expand All @@ -70,27 +89,41 @@ export class TestServiceImpl extends Disposable implements ITestService {
registerTestController(id: string, testController: ITestController): IDisposable {
this.controllers.set(id, testController);
this.controllerCount.set(this.controllers.size);
this.updateCanRefresh();

if (this.controllers.size > 0 && !this.viewId) {
this.viewId = this.registerTestingExplorerView();
}

return Disposable.create(() => {
const diff: TestsDiff = [];
for (const root of this.collection.rootItems) {
if (root.controllerId === id) {
diff.push([TestDiffOpType.Remove, root.item.extId]);
}
}
this.publishDiff(id, diff);
const disposable = new Disposable();

if (this.controllers.delete(id)) {
this.controllerCount.set(this.controllers.size);
if (this.controllers.size === 0 && this.viewId) {
this.mainlayoutService.disposeContainer(this.viewId);
disposable.addDispose(
Disposable.create(() => {
const diff: TestsDiff = [];
for (const root of this.collection.rootItems) {
if (root.controllerId === id) {
diff.push([TestDiffOpType.Remove, root.item.extId]);
}
}
}
});
this.publishDiff(id, diff);

if (this.controllers.delete(id)) {
this.controllerCount.set(this.controllers.size);
this.updateCanRefresh();
if (this.controllers.size === 0 && this.viewId) {
this.mainlayoutService.disposeContainer(this.viewId);
}
}
}),
);

disposable.addDispose(testController.canRefresh.onDidChange(this.updateCanRefresh, this));

return disposable;
}

private updateCanRefresh() {
this.canRefreshTests.set(Array.from(this.controllers.values()).some((c) => c.canRefresh));
}

public async expandTest(id: string, levels: number) {
Expand Down
15 changes: 14 additions & 1 deletion packages/testing/src/browser/testing.contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
ToolbarRegistry,
URI,
} from '@opensumi/ide-core-browser';
import { TestingIsPeekVisible } from '@opensumi/ide-core-browser/lib/contextkey/testing';
import { TestingCanRefreshTests, TestingIsPeekVisible } from '@opensumi/ide-core-browser/lib/contextkey/testing';
import { IMenuRegistry, MenuContribution, MenuId } from '@opensumi/ide-core-browser/lib/menu/next';
import { Emitter, IMarkdownString } from '@opensumi/ide-core-common';
import {
Expand Down Expand Up @@ -47,6 +47,7 @@ import {
GoToTestCommand,
OpenMessageInEditor,
PeekTestError,
RefreshTestsCommand,
RuntAllTestCommand,
RuntTestCommand,
TestingDebugCurrentFile,
Expand Down Expand Up @@ -334,6 +335,12 @@ export class TestingContribution
await this.testService.runTests({ tests: roots, group });
};

commands.registerCommand(RefreshTestsCommand, {
execute: async () => {
await this.testService.refreshTests();
},
});

commands.registerCommand(RuntAllTestCommand, {
execute: async () => {
await runOrDebugAllTestsAction(TestRunProfileBitset.Run);
Expand Down Expand Up @@ -398,6 +405,12 @@ export class TestingContribution
}

registerToolbarItems(registry: ToolbarRegistry): void {
registry.registerItem({
id: RefreshTestsCommand.id,
command: RefreshTestsCommand.id,
viewId: Testing.ExplorerViewId,
when: TestingCanRefreshTests.equalsTo(true),
});
registry.registerItem({
id: RuntAllTestCommand.id,
command: RuntAllTestCommand.id,
Expand Down
6 changes: 6 additions & 0 deletions packages/testing/src/common/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,9 @@ export const DebugAllTestCommand: Command = {
label: 'Debug All Test',
iconClass: getIcon('debug-alt-small'),
};

export const RefreshTestsCommand: Command = {
id: 'testing.refresshTests',
label: 'Refresh Tests',
iconClass: getIcon('refresh'),
};
7 changes: 5 additions & 2 deletions packages/testing/src/common/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Event, IDisposable, CancellationToken } from '@opensumi/ide-core-browser';

import { ObservableValue } from './observableValue';
import { ITestResult } from './test-result';
import {
InternalTestItem,
Expand All @@ -15,9 +16,10 @@ export * from './testId';
export interface ITestController {
readonly id: string;
readonly label: string;

readonly canRefresh: ObservableValue<boolean>;
configureRunProfile(profileId: number): void;
expandTest(testId: string, levels: number): Promise<void>;
refreshTests(token: CancellationToken): Promise<void>;
runTests(request: RunTestForControllerRequest, token: CancellationToken): Promise<void>;
}

Expand All @@ -36,8 +38,9 @@ export interface ITestService {
runTests(req: AmbiguousRunTestsRequest, token?: CancellationToken): Promise<ITestResult>;
publishDiff(controllerId: string, diff: TestsDiff): void;
runResolvedTests(req: ResolvedTestRunRequest, token?: CancellationToken): Promise<ITestResult>;

onDidProcessDiff: Event<TestsDiff>;
getTestController(controllerId: string): ITestController | undefined;
refreshTests(controllerId?: string): Promise<void>;
}

export interface AmbiguousRunTestsRequest {
Expand Down
27 changes: 27 additions & 0 deletions packages/testing/src/common/observableValue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Disposable, Emitter, Event } from '@opensumi/ide-core-common';

export interface IObservableValue<T> {
onDidChange: Event<T>;
readonly value: T;
}

export class ObservableValue<T> extends Disposable implements IObservableValue<T> {
private readonly changeEmitter = this.registerDispose(new Emitter<T>());

public readonly onDidChange = this.changeEmitter.event;

public get value() {
return this._value;
}

public set value(v: T) {
if (v !== this._value) {
this._value = v;
this.changeEmitter.fire(v);
}
}

constructor(private _value: T) {
super();
}
}
Loading