Skip to content

Commit 7b3282b

Browse files
authored
Remove children of stale tree nodes (microsoft#203783)
Possible fix for microsoft#192422
1 parent 645e218 commit 7b3282b

File tree

2 files changed

+122
-0
lines changed

2 files changed

+122
-0
lines changed

src/vs/base/browser/ui/tree/asyncDataTree.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -827,6 +827,7 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
827827
if (treeNode.collapsed) {
828828
node.hasChildren = !!this.dataSource.hasChildren(node.element);
829829
node.stale = true;
830+
this.setChildren(node, [], recursive, viewStateContext);
830831
return;
831832
}
832833
}

src/vs/base/test/browser/ui/tree/asyncDataTree.test.ts

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,127 @@ suite('AsyncDataTree', function () {
269269
assert(tree.getNode(model.get('a')).collapsed);
270270
});
271271

272+
test('issue #192422 - resolved collapsed nodes with changed children don\'t show old children', async () => {
273+
const container = document.createElement('div');
274+
let hasGottenAChildren = false;
275+
const dataSource = new class implements IAsyncDataSource<Element, Element> {
276+
hasChildren(element: Element): boolean {
277+
return !!element.children && element.children.length > 0;
278+
}
279+
async getChildren(element: Element): Promise<Element[]> {
280+
if (element.id === 'a') {
281+
if (!hasGottenAChildren) {
282+
hasGottenAChildren = true;
283+
} else {
284+
return [{ id: 'c' }];
285+
}
286+
}
287+
return element.children || [];
288+
}
289+
};
290+
291+
const model = new Model({
292+
id: 'root',
293+
children: [{
294+
id: 'a', children: [{ id: 'b' }]
295+
}]
296+
});
297+
298+
const tree = store.add(new AsyncDataTree<Element, Element>('test', container, new VirtualDelegate(), [new Renderer()], dataSource, { identityProvider: new IdentityProvider() }));
299+
tree.layout(200);
300+
301+
await tree.setInput(model.root);
302+
const a = model.get('a');
303+
const aNode = tree.getNode(a);
304+
assert(aNode.collapsed);
305+
await tree.expand(a);
306+
assert(!aNode.collapsed);
307+
assert.equal(aNode.children.length, 1);
308+
assert.equal(aNode.children[0].element.id, 'b');
309+
const bChild = container.querySelector('.monaco-list-row:nth-child(2)') as HTMLElement | undefined;
310+
assert.equal(bChild?.textContent, 'b');
311+
tree.collapse(a);
312+
assert(aNode.collapsed);
313+
314+
await tree.updateChildren(a);
315+
const aUpdated1 = model.get('a');
316+
const aNodeUpdated1 = tree.getNode(a);
317+
assert(aNodeUpdated1.collapsed);
318+
assert.equal(aNodeUpdated1.children.length, 0);
319+
let didCheckNoChildren = false;
320+
const event = tree.onDidChangeCollapseState(e => {
321+
const child = container.querySelector('.monaco-list-row:nth-child(2)') as HTMLElement | undefined;
322+
assert.equal(child, undefined);
323+
didCheckNoChildren = true;
324+
});
325+
await tree.expand(aUpdated1);
326+
event.dispose();
327+
assert(didCheckNoChildren);
328+
329+
const aNodeUpdated2 = tree.getNode(a);
330+
assert(!aNodeUpdated2.collapsed);
331+
assert.equal(aNodeUpdated2.children.length, 1);
332+
assert.equal(aNodeUpdated2.children[0].element.id, 'c');
333+
const child = container.querySelector('.monaco-list-row:nth-child(2)') as HTMLElement | undefined;
334+
assert.equal(child?.textContent, 'c');
335+
});
336+
337+
test('issue #192422 - resolved collapsed nodes with unchanged children immediately show children', async () => {
338+
const container = document.createElement('div');
339+
const dataSource = new class implements IAsyncDataSource<Element, Element> {
340+
hasChildren(element: Element): boolean {
341+
return !!element.children && element.children.length > 0;
342+
}
343+
async getChildren(element: Element): Promise<Element[]> {
344+
return element.children || [];
345+
}
346+
};
347+
348+
const model = new Model({
349+
id: 'root',
350+
children: [{
351+
id: 'a', children: [{ id: 'b' }]
352+
}]
353+
});
354+
355+
const tree = store.add(new AsyncDataTree<Element, Element>('test', container, new VirtualDelegate(), [new Renderer()], dataSource, { identityProvider: new IdentityProvider() }));
356+
tree.layout(200);
357+
358+
await tree.setInput(model.root);
359+
const a = model.get('a');
360+
const aNode = tree.getNode(a);
361+
assert(aNode.collapsed);
362+
await tree.expand(a);
363+
assert(!aNode.collapsed);
364+
assert.equal(aNode.children.length, 1);
365+
assert.equal(aNode.children[0].element.id, 'b');
366+
const bChild = container.querySelector('.monaco-list-row:nth-child(2)') as HTMLElement | undefined;
367+
assert.equal(bChild?.textContent, 'b');
368+
tree.collapse(a);
369+
assert(aNode.collapsed);
370+
371+
const aUpdated1 = model.get('a');
372+
const aNodeUpdated1 = tree.getNode(a);
373+
assert(aNodeUpdated1.collapsed);
374+
assert.equal(aNodeUpdated1.children.length, 1);
375+
let didCheckSameChildren = false;
376+
const event = tree.onDidChangeCollapseState(e => {
377+
const child = container.querySelector('.monaco-list-row:nth-child(2)') as HTMLElement | undefined;
378+
assert.equal(child?.textContent, 'b');
379+
didCheckSameChildren = true;
380+
});
381+
await tree.expand(aUpdated1);
382+
event.dispose();
383+
assert(didCheckSameChildren);
384+
385+
const aNodeUpdated2 = tree.getNode(a);
386+
assert(!aNodeUpdated2.collapsed);
387+
assert.equal(aNodeUpdated2.children.length, 1);
388+
assert.equal(aNodeUpdated2.children[0].element.id, 'b');
389+
const child = container.querySelector('.monaco-list-row:nth-child(2)') as HTMLElement | undefined;
390+
assert.equal(child?.textContent, 'b');
391+
});
392+
272393
test('support default collapse state per element', async () => {
273394
const container = document.createElement('div');
274395

0 commit comments

Comments
 (0)