Skip to content

Commit e79aa2c

Browse files
roblourensCopilot
andcommitted
fix: mock ITextModelService to avoid async WebKit disposal leak
Replace real TextModelResolverService with a synchronous mock that returns tracked disposables. This eliminates the async disposal chain in TextResourceEditorModel.destroyReferencedObject that could not be reliably flushed on WebKit browsers, causing Suite 1 afterEach failures on macOS/Browser CI. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 9a7c2e8 commit e79aa2c

1 file changed

Lines changed: 19 additions & 10 deletions

File tree

src/vs/workbench/contrib/chat/test/browser/widget/chatWidgetDisposal.test.ts

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,14 @@ import * as assert from 'assert';
77
import { mainWindow } from '../../../../../../base/browser/window.js';
88
import { Emitter, Event } from '../../../../../../base/common/event.js';
99
import { MarkdownString } from '../../../../../../base/common/htmlContent.js';
10-
import { DisposableStore, DisposableTracker, IDisposable, setDisposableTracker, toDisposable } from '../../../../../../base/common/lifecycle.js';
10+
import { DisposableStore, DisposableTracker, IDisposable, IReference, setDisposableTracker, toDisposable } from '../../../../../../base/common/lifecycle.js';
1111
import { observableValue, constObservable } from '../../../../../../base/common/observable.js';
1212
import { URI } from '../../../../../../base/common/uri.js';
1313
import { mock } from '../../../../../../base/test/common/mock.js';
1414
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../base/test/common/utils.js';
1515
import { Range } from '../../../../../../editor/common/core/range.js';
1616
import { OffsetRange } from '../../../../../../editor/common/core/ranges/offsetRange.js';
17+
import { ITextModel } from '../../../../../../editor/common/model.js';
1718
import { ServiceCollection } from '../../../../../../platform/instantiation/common/serviceCollection.js';
1819
import { ILogService, NullLogService } from '../../../../../../platform/log/common/log.js';
1920
import { IConfigurationService } from '../../../../../../platform/configuration/common/configuration.js';
@@ -69,6 +70,7 @@ import { ITerminalService, ITerminalChatService, ITerminalEditorService, ITermin
6970
import { IAiEditTelemetryService } from '../../../../editTelemetry/browser/telemetry/aiEditTelemetry/aiEditTelemetryService.js';
7071
import { IHoverService } from '../../../../../../platform/hover/browser/hover.js';
7172
import type { IManagedHoverContent, IManagedHoverOptions } from '../../../../../../base/browser/ui/hover/hover.js';
73+
import { ITextModelContentProvider, ITextModelService, IResolvedTextEditorModel } from '../../../../../../editor/common/services/resolverService.js';
7274

7375
function createChatServiceCollection(disposables: DisposableStore): ServiceCollection {
7476
const collection = new ServiceCollection();
@@ -256,6 +258,19 @@ function createChatServiceCollection(disposables: DisposableStore): ServiceColle
256258
showAndFocusLastHover: () => undefined,
257259
showManagedHover: () => undefined,
258260
} satisfies IHoverService);
261+
// Override the real TextModelResolverService (set up by workbenchInstantiationService)
262+
// with a mock that resolves synchronously. The real service creates a
263+
// TextResourceEditorModel with an async disposal chain that cannot be
264+
// reliably flushed in WebKit browser tests.
265+
collection.set(ITextModelService, new class extends mock<ITextModelService>() {
266+
override async createModelReference(): Promise<IReference<IResolvedTextEditorModel>> {
267+
const ref = toDisposable(() => { });
268+
return Object.assign(ref, { object: { textEditorModel: null! as ITextModel, isReadonly: () => false } });
269+
}
270+
override registerTextModelContentProvider(_scheme: string, _provider: ITextModelContentProvider) {
271+
return toDisposable(() => { });
272+
}
273+
}());
259274
return collection;
260275
}
261276

@@ -294,15 +309,9 @@ suite('ChatWidget Disposal', function () {
294309
let parentElement: HTMLElement;
295310

296311
async function flushMicrotasks() {
297-
// Flush microtask queue multiple rounds to allow nested async
298-
// chains to settle (e.g. TextModelResolverService.destroyReferencedObject).
299-
// Mix in macrotask yields (setTimeout 0) for platforms where chained
300-
// awaits schedule through the macrotask queue (WebKit browser).
301-
for (let round = 0; round < 3; round++) {
302-
for (let i = 0; i < 20; i++) {
303-
await new Promise<void>(r => queueMicrotask(r));
304-
}
305-
await new Promise<void>(r => setTimeout(r, 0));
312+
// Flush microtask queue to allow nested async chains to settle.
313+
for (let i = 0; i < 20; i++) {
314+
await new Promise<void>(r => queueMicrotask(r));
306315
}
307316
}
308317

0 commit comments

Comments
 (0)