Change:
My extension's has a that replaces all root items whenever it is called. It calls the same function that creates the items upon extension activation. After the is run, I've verified that the correct root items have been created with set to . However, when the user tries to expand the item, the is not called on that item and no children are created. This simple extension demonstrates the bug: Steps to Reproduce: the extension above. It should immediately activate after the window refresh. the text explorer and observe the root item. Expand it to reveal two children. the Output channel to see that the resolve handler was called. the refresh icon and verify that "Refreshing" was written to the Output channel. to expand the new root. Verify that the resolve handler was never called. VS Code Version: 1.87.2
Thanks for triaging this Do you anticipate this being resolved before the next release? If not, do you have a recommended workaround I should try to implement in my extension?
A fix for this would be highly appreciated. This bug is a major annoyance for my daily work I started creating a new issue for , but googled first and found this one instead I even "fixed" the issue by adding the following line into the refreshHandler Like so: testController.refreshHandler = () ={ replaceRootTestItems(testController); ((item) =testController.resolveHandler(item)); };
Thanks for the tip I'll give that a try as a workaround.
<!-- AUTHORVERIFICATIONREQUEST --This bug has been fixed in the latest release of ! you can help us out by commenting if things are now working as expected. If things still don't seem right, please ensure you're on version of Insiders (today's or later - you can use in the command palette to check), and leave a comment letting us know what isn't working as expected. Happy Coding!
/verified
Thanks!
Thanks for fixing this!

Discussion:
import * as assert from 'assert'; import * as sinon from 'sinon'; <ins> import { timeout } from 'vs/base/common/async'; </ins> import { VSBuffer } from 'vs/base/common/buffer'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { Event } from 'vs/base/common/event';

const ds = ensureNoDisposablesAreLeakedInTestSuite(); let single: TestExtHostTestItemCollection; <ins> let resolveCalls: (string | undefined)[] = []; </ins> setup(() => { <ins> resolveCalls = []; </ins> single = ds.add(new TestExtHostTestItemCollection('ctrlId', 'root', { getDocument: () => undefined, } as Partial<ExtHostDocumentsAndEditors> as ExtHostDocumentsAndEditors)); single.resolveHandler = item => { <ins> resolveCalls.push(item?.id); </ins> if (item === undefined) { const a = new TestItemImpl('ctrlId', 'id-a', 'a', URI.file('/')); a.canResolveChildren = true;

assert.deepStrictEqual(single.collectDiff(), [ { op: TestDiffOpType.Update, <del> item: { extId: new TestId(['ctrlId', 'id-a']).toString(), expand: TestItemExpandState.Expanded, item: { label: 'Hello world' } }, </del> <ins> item: { extId: new TestId(['ctrlId', 'id-a']).toString(), item: { label: 'Hello world' } }, </ins> }, { op: TestDiffOpType.DocumentSynced,

assert.deepStrictEqual(single.collectDiff(), []); }); <ins> suite('expandibility restoration', () => { const doReplace = async (canResolveChildren = true) => { const uri = single.root.children.get('id-a')!.uri; const newA = new TestItemImpl('ctrlId', 'id-a', 'Hello world', uri); newA.canResolveChildren = canResolveChildren; single.root.children.replace([ newA, new TestItemImpl('ctrlId', 'id-b', single.root.children.get('id-b')!.label, uri), ]); await timeout(0); // drain microtasks }; test('does not restore an unexpanded state', async () => { await single.expand(single.root.id, 0); assert.deepStrictEqual(resolveCalls, [undefined]); await doReplace(); assert.deepStrictEqual(resolveCalls, [undefined]); }); test('restores resolve state on replacement', async () => { await single.expand(single.root.id, Infinity); assert.deepStrictEqual(resolveCalls, [undefined, 'id-a']); await doReplace(); assert.deepStrictEqual(resolveCalls, [undefined, 'id-a', 'id-a']); }); test('does not expand if new child is not expandable', async () => { await single.expand(single.root.id, Infinity); assert.deepStrictEqual(resolveCalls, [undefined, 'id-a']); await doReplace(false); assert.deepStrictEqual(resolveCalls, [undefined, 'id-a']); }); }); </ins> test('treats in-place replacement as mutation deeply', () => { single.expand(single.root.id, Infinity); single.collectDiff();

assert.deepStrictEqual(single.collectDiff(), [ { op: TestDiffOpType.Update, <del> item: { extId: new TestId(['ctrlId', 'id-a']).toString(), expand: TestItemExpandState.Expanded }, }, { op: TestDiffOpType.Update, </del> item: { extId: TestId.fromExtHostTestItem(oldAB, 'ctrlId').toString(), item: { label: 'Hello world' } }, }, {

this.options.getApiFor(oldActual).listener = undefined; internal.actual = actual; <ins> internal.resolveBarrier = undefined; </ins> internal.expand = TestItemExpandState.NotExpandable; // updated by `connectItemAndChildren` if (update) {

} } <ins> // Re-expand the element if it was previous expanded (#207574) const expandLevels = internal.expandLevels; if (expandLevels !== undefined) { // Wait until a microtask to allow the extension to finish setting up // properties of the element and children before we ask it to expand. queueMicrotask(() => { if (internal.expand === TestItemExpandState.Expandable) { internal.expandLevels = undefined; this.expand(fullId.toString(), expandLevels); } }); } </ins> // Mark ranges in the document as synced (#161320) this.documentSynced(internal.actual.uri); }