diff --git a/src/vs/workbench/api/browser/mainThreadTesting.ts b/src/vs/workbench/api/browser/mainThreadTesting.ts index 3b127f10a7909753fd790231819ec329c6f25262..95fc3e9ae7f5cacb294be61a9b6fe186f8068e1f 100644 --- a/src/vs/workbench/api/browser/mainThreadTesting.ts +++ b/src/vs/workbench/api/browser/mainThreadTesting.ts @@ -51,6 +51,17 @@ export class MainThreadTesting extends Disposable implements MainThreadTestingSh } } + /** + * @inheritdoc + */ + $retireTest(extId: string): void { + for (const result of this.resultService.results) { + if (result instanceof LiveTestResult) { + result.retire(extId); + } + } + } + /** * @inheritdoc */ diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index c6f3bd834ac15a0dad475b25df9d65ed3e80275b..a169d48fd4fd25d5c0fea020280918792722786e 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1843,6 +1843,7 @@ export interface MainThreadTestingShape { $publishDiff(resource: ExtHostTestingResource, uri: UriComponents, diff: TestsDiff): void; $updateTestStateInRun(runId: string, testId: string, state: ITestState): void; $runTests(req: RunTestsRequest, token: CancellationToken): Promise; + $retireTest(extId: string): void; } // --- proxy identifiers diff --git a/src/vs/workbench/api/common/extHostTesting.ts b/src/vs/workbench/api/common/extHostTesting.ts index bab357e004309c15e61763e4ef1d570ad8cc79b7..e1c86d4975cd684a776570b3f222a2b025dd12cc 100644 --- a/src/vs/workbench/api/common/extHostTesting.ts +++ b/src/vs/workbench/api/common/extHostTesting.ts @@ -170,6 +170,14 @@ export class ExtHostTesting implements ExtHostTestingShape { collection.addRoot(hierarchy.root, id); Promise.resolve(hierarchy.discoveredInitialTests).then(() => collection.pushDiff([TestDiffOpType.DeltaDiscoverComplete, -1])); hierarchy.onDidChangeTest(e => collection.onItemChange(e, id)); + hierarchy.onDidInvalidateTest?.(e => { + const internal = collection.getTestByReference(e); + if (!internal) { + console.warn(`Received a TestProvider.onDidInvalidateTest for a test that does not currently exist.`); + } else { + this.proxy.$retireTest(internal.item.extId); + } + }); } catch (e) { console.error(e); } diff --git a/src/vs/workbench/contrib/testing/browser/explorerProjections/hierarchalByLocation.ts b/src/vs/workbench/contrib/testing/browser/explorerProjections/hierarchalByLocation.ts index 42fdc4e2cdb2243ee32f316b3b61197f2144ae49..d785bf0de24d514d20c6a2305d72063d1eebe34e 100644 --- a/src/vs/workbench/contrib/testing/browser/explorerProjections/hierarchalByLocation.ts +++ b/src/vs/workbench/contrib/testing/browser/explorerProjections/hierarchalByLocation.ts @@ -80,11 +80,14 @@ export class HierarchicalByLocationProjection extends Disposable implements ITes })); // when test states change, reflect in the tree - this._register(results.onTestChanged(([, { item, state, computedState }]) => { + // todo: optimize this to avoid needing to iterate + this._register(results.onTestChanged(([, { item, state, retired, computedState }]) => { for (const i of this.items.values()) { if (i.test.item.extId === item.extId) { i.ownState = state.state; + i.retired = retired; refreshComputedState(computedStateAccessor, i, this.addUpdated, computedState); + this.addUpdated(i); this.updateEmitter.fire(); return; } @@ -229,6 +232,7 @@ export class HierarchicalByLocationProjection extends Disposable implements ITes const prevState = this.results.getStateByExtId(item.test.item.extId)?.[1]; if (prevState) { item.ownState = prevState.state.state; + item.retired = prevState.retired; refreshComputedState(computedStateAccessor, item, this.addUpdated, prevState.computedState); } } diff --git a/src/vs/workbench/contrib/testing/browser/explorerProjections/hierarchalNodes.ts b/src/vs/workbench/contrib/testing/browser/explorerProjections/hierarchalNodes.ts index fb1dc15352670933a0072e21e4eaec981a5f4917..4e4dc956bddede92e5eb56ae5ce02d0952e21230 100644 --- a/src/vs/workbench/contrib/testing/browser/explorerProjections/hierarchalNodes.ts +++ b/src/vs/workbench/contrib/testing/browser/explorerProjections/hierarchalNodes.ts @@ -41,6 +41,7 @@ export class HierarchicalElement implements ITestTreeElement { } public state = TestRunState.Unset; + public retired = false; public ownState = TestRunState.Unset; constructor(public readonly test: InternalTestItem, public readonly parentItem: HierarchicalFolder | HierarchicalElement) { @@ -73,6 +74,7 @@ export class HierarchicalFolder implements ITestTreeElement { return Iterable.concatNested(Iterable.map(this.children, c => c.debuggable)); } + public retired = false; public state = TestRunState.Unset; public ownState = TestRunState.Unset; diff --git a/src/vs/workbench/contrib/testing/browser/explorerProjections/index.ts b/src/vs/workbench/contrib/testing/browser/explorerProjections/index.ts index 092a4f3799ceb1673aaaea4997f77ba2b5c2f132..f4176f445e5fe976297cc7ba75ab80978d48d370 100644 --- a/src/vs/workbench/contrib/testing/browser/explorerProjections/index.ts +++ b/src/vs/workbench/contrib/testing/browser/explorerProjections/index.ts @@ -87,6 +87,11 @@ export interface ITestTreeElement { */ state: TestRunState; + /** + * Whether the node's test result is 'retired' -- from an outdated test run. + */ + readonly retired: boolean; + readonly ownState: TestRunState; readonly label: string; readonly parentItem: ITestTreeElement | null; diff --git a/src/vs/workbench/contrib/testing/browser/media/testing.css b/src/vs/workbench/contrib/testing/browser/media/testing.css index 9f8e820b408b09707d6bcab54afc1a535097e638..3943b0bd98d05122c4dd7c9da5eadb32c8c57e9f 100644 --- a/src/vs/workbench/contrib/testing/browser/media/testing.css +++ b/src/vs/workbench/contrib/testing/browser/media/testing.css @@ -41,6 +41,10 @@ margin-right: 0.25em; } +.test-explorer .computed-state.retired { + opacity: 0.7; +} + .test-explorer .test-explorer-messages { padding: 0 12px 8px; } diff --git a/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts b/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts index f60571440b55f900904d66c297a0e4a28a9c3f63..b1a3c35be4369896cb382aa361210bc58bb46545 100644 --- a/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts +++ b/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts @@ -628,10 +628,19 @@ class ListAccessibilityProvider implements IListAccessibilityProvider[] = [[root.id]]; + while (queue.length) { + for (const id of queue.pop()!) { + const entry = this.testByInternalId.get(id); + if (entry && !entry.retired) { + entry.retired = true; + queue.push(entry.children); + this.changeEmitter.fire(entry); + } + } + } + } + /** * Adds a test, by its ID, to the test run. This can end up being called * if tests were started while discovery was still happening, so initially @@ -363,6 +387,7 @@ class HydratedTestResult implements ITestResult { for (const [key, value] of serialized.items) { this.map.set(key, value); + value.retired = true; for (const message of value.state.messages) { if (message.location) { message.location.uri = URI.revive(message.location.uri);