Another fix for "element with id not registered" error (#281269)

Fixes microsoft/vscode-pull-request-github#8073
This commit is contained in:
Alex Ross
2025-12-04 17:49:41 +01:00
committed by GitHub
parent 3015de171b
commit 472dee5115
2 changed files with 162 additions and 0 deletions

View File

@@ -103,4 +103,143 @@ suite('vscode API - tree', () => {
assert.fail(error.message);
}
});
test('TreeView - element already registered after refresh', async function () {
this.timeout(60_000);
type ParentElement = { readonly kind: 'parent' };
type ChildElement = { readonly kind: 'leaf'; readonly version: number };
type TreeElement = ParentElement | ChildElement;
class ParentRefreshTreeDataProvider implements vscode.TreeDataProvider<TreeElement> {
private readonly changeEmitter = new vscode.EventEmitter<TreeElement | undefined>();
private readonly rootRequestEmitter = new vscode.EventEmitter<number>();
private readonly childRequestEmitter = new vscode.EventEmitter<number>();
private readonly rootRequests: DeferredPromise<TreeElement[]>[] = [];
private readonly childRequests: DeferredPromise<TreeElement[]>[] = [];
private readonly parentElement: ParentElement = { kind: 'parent' };
private childVersion = 0;
private currentChild: ChildElement = { kind: 'leaf', version: 0 };
readonly onDidChangeTreeData = this.changeEmitter.event;
getChildren(element?: TreeElement): Thenable<TreeElement[]> {
if (!element) {
const deferred = new DeferredPromise<TreeElement[]>();
this.rootRequests.push(deferred);
this.rootRequestEmitter.fire(this.rootRequests.length);
return deferred.p;
}
if (element.kind === 'parent') {
const deferred = new DeferredPromise<TreeElement[]>();
this.childRequests.push(deferred);
this.childRequestEmitter.fire(this.childRequests.length);
return deferred.p;
}
return Promise.resolve([]);
}
getTreeItem(element: TreeElement): vscode.TreeItem {
if (element.kind === 'parent') {
const item = new vscode.TreeItem('parent', vscode.TreeItemCollapsibleState.Collapsed);
item.id = 'parent';
return item;
}
const item = new vscode.TreeItem('duplicate', vscode.TreeItemCollapsibleState.None);
item.id = 'dup';
return item;
}
getParent(element: TreeElement): TreeElement | undefined {
if (element.kind === 'leaf') {
return this.parentElement;
}
return undefined;
}
getCurrentChild(): ChildElement {
return this.currentChild;
}
replaceChild(): ChildElement {
this.childVersion++;
this.currentChild = { kind: 'leaf', version: this.childVersion };
return this.currentChild;
}
async waitForRootRequestCount(count: number): Promise<void> {
while (this.rootRequests.length < count) {
await asPromise(this.rootRequestEmitter.event);
}
}
async waitForChildRequestCount(count: number): Promise<void> {
while (this.childRequests.length < count) {
await asPromise(this.childRequestEmitter.event);
}
}
async resolveNextRootRequest(elements?: TreeElement[]): Promise<void> {
const next = this.rootRequests.shift();
if (!next) {
return;
}
await next.complete(elements ?? [this.parentElement]);
}
async resolveChildRequestAt(index: number, elements?: TreeElement[]): Promise<void> {
const request = this.childRequests[index];
if (!request) {
return;
}
this.childRequests.splice(index, 1);
await request.complete(elements ?? [this.currentChild]);
}
dispose(): void {
this.changeEmitter.dispose();
this.rootRequestEmitter.dispose();
this.childRequestEmitter.dispose();
while (this.rootRequests.length) {
this.rootRequests.shift()!.complete([]);
}
while (this.childRequests.length) {
this.childRequests.shift()!.complete([]);
}
}
}
const provider = new ParentRefreshTreeDataProvider();
disposables.push(provider);
const treeView = vscode.window.createTreeView('test.treeRefresh', { treeDataProvider: provider });
disposables.push(treeView);
const initialChild = provider.getCurrentChild();
const firstReveal = (treeView.reveal(initialChild, { expand: true })
.then(() => ({ error: undefined as Error | undefined })) as Promise<{ error: Error | undefined }>)
.catch(error => ({ error }));
await provider.waitForRootRequestCount(1);
await provider.resolveNextRootRequest();
await provider.waitForChildRequestCount(1);
const staleChild = provider.getCurrentChild();
const refreshedChild = provider.replaceChild();
const secondReveal = (treeView.reveal(refreshedChild, { expand: true })
.then(() => ({ error: undefined as Error | undefined })) as Promise<{ error: Error | undefined }>)
.catch(error => ({ error }));
await provider.waitForChildRequestCount(2);
await provider.resolveChildRequestAt(1, [refreshedChild]);
await delay(0);
await provider.resolveChildRequestAt(0, [staleChild]);
const [firstResult, secondResult] = await Promise.all([firstReveal, secondReveal]);
const error = firstResult.error ?? secondResult.error;
if (error && /Element with id .+ is already registered/.test(error.message)) {
assert.fail(error.message);
}
});
});