Repository: microsoft/vscode (TypeScript)
Date: 2021-06-24 10:33:40+00:00
Task type: Bug Fix

Issue:
TreeItem State None to Expanded
Issue Type: <b>Bug</b>

We, [Cloud Code](https://github.com/GoogleCloudPlatform/cloud-code-vscode) have a use case where a TreeItem could transition from `vscode.TreeItemCollapsibleState.None` to `vscode.TreeItemCollapsibleState.Expanded`. Currently, this functionality doesn't seem to work on a dynamic setting. 

I have created a simple repro here: https://github.com/vincentjocodes/vscode-tree-state-issue. Steps to repro the problem with the sample app are: 

1) Run Extension
2) Open the Explorer and see 2 nodes:
![image](https://user-images.githubusercontent.com/13400593/123178491-29d6c780-d43c-11eb-8afe-d735403e03c7.png)
3) Run `Event: Expand` command from the command palette
4) See that the items are collapsed:
![image](https://user-images.githubusercontent.com/13400593/123178556-4b37b380-d43c-11eb-9480-97d462f68df0.png)

I expect the items to be expanded, per [this line of code in the sample](https://github.com/vincentjocodes/vscode-tree-state-issue/blob/main/src/extension.ts#L74). There are no places in my code where I set `vscode.TreeItemCollapsibleState.Collapsed`. 

Please help. 

Thank you. 

VS Code version: Code 1.57.1 (507ce72a4466fbb27b715c3722558bb15afa9f48, 2021-06-17T13:28:32.912Z)
OS version: Darwin x64 19.6.0
Restricted Mode: No

<details>
<summary>System Info</summary>

|Item|Value|
|---|---|
|CPUs|Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz (12 x 2600)|
|GPU Status|2d_canvas: enabled<br>gpu_compositing: enabled<br>metal: disabled_off<br>multiple_raster_threads: enabled_on<br>oop_rasterization: enabled<br>opengl: enabled_on<br>rasterization: enabled<br>skia_renderer: disabled_off_ok<br>video_decode: enabled<br>webgl: enabled<br>webgl2: enabled|
|Load (avg)|3, 4, 4|
|Memory (System)|16.00GB (1.53GB free)|
|Process Argv|--crash-reporter-id ed7a5a7f-6e02-4080-a6f9-795012ddb483|
|Screen Reader|no|
|VM|0%|
</details><details>
<summary>A/B Experiments</summary>

```
vsliv368cf:30146710
vsreu685:30147344
python383cf:30185419
pythonvspyt700cf:30270857
pythonvspyt602:30300191
vspor879:30202332
vspor708:30202333
vspor363:30204092
vswsl492:30256859
vstes627:30244334
pythonvspyt639:30300192
pythontb:30283811
vspre833:30321513
pythonptprofiler:30281270
vshan820:30294714
pythondataviewer:30285071
vscus158:30321503
pythonvsuse255:30323308
vscorehov:30309549
vscod805cf:30301675
pythonvspyt200:30324779
binariesv615:30325510

```

</details>

<!-- generated by issue reporter -->


Issue discussion:

Solution patch:
diff --git a/src/vs/base/browser/ui/tree/asyncDataTree.ts b/src/vs/base/browser/ui/tree/asyncDataTree.ts
--- a/src/vs/base/browser/ui/tree/asyncDataTree.ts
+++ b/src/vs/base/browser/ui/tree/asyncDataTree.ts
@@ -881,6 +881,10 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
 						asyncDataTreeNode.children.forEach(node => dfs(node, node => this.nodes.delete(node.element as T)));
 						asyncDataTreeNode.children.splice(0, asyncDataTreeNode.children.length);
 						asyncDataTreeNode.stale = true;
+						if (this.collapseByDefault && !this.collapseByDefault(element)) {
+							asyncDataTreeNode.collapsedByDefault = false;
+							childrenToRefresh.push(asyncDataTreeNode);
+						}
 					} else {
 						childrenToRefresh.push(asyncDataTreeNode);
 					}


Test patch:
diff --git a/src/vs/base/test/browser/ui/tree/asyncDataTree.test.ts b/src/vs/base/test/browser/ui/tree/asyncDataTree.test.ts
--- a/src/vs/base/test/browser/ui/tree/asyncDataTree.test.ts
+++ b/src/vs/base/test/browser/ui/tree/asyncDataTree.test.ts
@@ -435,4 +435,45 @@ suite('AsyncDataTree', function () {
 
 		assert.deepStrictEqual(Array.from(container.querySelectorAll('.monaco-list-row')).map(e => e.textContent), ['a', 'b2']);
 	});
+
+	test('issue #127035 - tree should react to collapseByDefault toggles', async () => {
+		const container = document.createElement('div');
+		const model = new Model({
+			id: 'root',
+			children: [{
+				id: 'a'
+			}]
+		});
+
+		let collapseByDefault = () => true;
+		const tree = new AsyncDataTree<Element, Element>('test', container, new VirtualDelegate(), [new Renderer()], new DataSource(), {
+			identityProvider: new IdentityProvider(),
+			collapseByDefault: _ => collapseByDefault()
+		});
+		tree.layout(200);
+
+		await tree.setInput(model.root);
+		assert.strictEqual(container.querySelectorAll('.monaco-list-row').length, 1);
+
+		let twistie = container.querySelector('.monaco-list-row:first-child .monaco-tl-twistie') as HTMLElement;
+		assert(!twistie.classList.contains('collapsible'));
+		assert(!twistie.classList.contains('collapsed'));
+
+		collapseByDefault = () => false;
+		model.get('a').children = [{ id: 'aa' }];
+		await tree.updateChildren(model.root, true);
+
+		const rows = container.querySelectorAll('.monaco-list-row');
+		assert.strictEqual(rows.length, 2);
+
+		const aTwistie = rows.item(0).querySelector('.monaco-tl-twistie') as HTMLElement;
+		assert(aTwistie.classList.contains('collapsible'));
+		assert(!aTwistie.classList.contains('collapsed'));
+
+		const aaTwistie = rows.item(1).querySelector('.monaco-tl-twistie') as HTMLElement;
+		assert(!aaTwistie.classList.contains('collapsible'));
+		assert(!aaTwistie.classList.contains('collapsed'));
+
+		tree.dispose();
+	});
 });