Skip to content

Commit 294738e

Browse files
JustinGroteCopilot
andauthored
Fix didOpen handler binding (#5496)
* Fix didOpen getting stuck due to undefined this * Add session tests * test: invoke middleware handlers without method binding Agent-Logs-Url: https://github.com/PowerShell/vscode-powershell/sessions/f08084c8-dc63-46ac-8904-e2c86582ee90 Co-authored-by: JustinGrote <15258962+JustinGrote@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: JustinGrote <15258962+JustinGrote@users.noreply.github.com>
1 parent 68654f0 commit 294738e

2 files changed

Lines changed: 178 additions & 6 deletions

File tree

‎src/session.ts‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -490,21 +490,21 @@ export class SessionManager implements Middleware {
490490
// is fully initialized. This prevents stale parser diagnostics (e.g.
491491
// unresolved custom attribute types) that would otherwise appear because
492492
// textDocument/didOpen is sent before the server's type resolution is ready.
493-
public async didOpen(
493+
public didOpen = async (
494494
document: vscode.TextDocument,
495495
next: (document: vscode.TextDocument) => Promise<void>,
496-
): Promise<void> {
496+
): Promise<void> => {
497497
await this.started.promise;
498498
return next(document);
499-
}
499+
};
500500

501-
public async didChange(
501+
public didChange = async (
502502
event: vscode.TextDocumentChangeEvent,
503503
next: (event: vscode.TextDocumentChangeEvent) => Promise<void>,
504-
): Promise<void> {
504+
): Promise<void> => {
505505
await this.started.promise;
506506
return next(event);
507-
}
507+
};
508508

509509
// TODO: Is this used by the magic of "Middleware" in the client library?
510510
public resolveCodeLens(

‎test/core/session.test.ts‎

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
import { type TelemetryReporter } from "@vscode/extension-telemetry";
5+
import * as assert from "assert";
6+
import Sinon from "sinon";
7+
import * as vscode from "vscode";
8+
import type { DocumentSelector } from "vscode-languageclient";
9+
import { SessionManager } from "../../src/session";
10+
import { stubInterface, testLogger } from "../utils";
11+
12+
describe("SessionManager middleware", () => {
13+
afterEach(() => {
14+
Sinon.restore();
15+
});
16+
17+
it("delays didOpen until session initialization completes", async () => {
18+
const registerCommandStub = Sinon.stub(
19+
vscode.commands,
20+
"registerCommand",
21+
).returns(disposableStub());
22+
const onDidChangeConfigurationStub = Sinon.stub(
23+
vscode.workspace,
24+
"onDidChangeConfiguration",
25+
).returns(disposableStub());
26+
27+
Sinon.stub(vscode.languages, "createLanguageStatusItem").returns(
28+
stubInterface<vscode.LanguageStatusItem>({
29+
text: "",
30+
detail: "",
31+
busy: false,
32+
severity: vscode.LanguageStatusSeverity.Information,
33+
dispose: () => {
34+
return;
35+
},
36+
}),
37+
);
38+
39+
const manager = new SessionManager(
40+
stubInterface<vscode.ExtensionContext>({
41+
globalStorageUri: vscode.Uri.file("C:/tmp"),
42+
extensionMode: vscode.ExtensionMode.Test,
43+
subscriptions: [],
44+
logUri: vscode.Uri.file("C:/tmp"),
45+
}),
46+
testLogger,
47+
["powershell"] as DocumentSelector,
48+
"Visual Studio Code",
49+
"PowerShell",
50+
"2026.5.0",
51+
"ms-vscode",
52+
stubInterface<TelemetryReporter>(),
53+
);
54+
55+
const document = stubInterface<vscode.TextDocument>();
56+
let nextCalled = false;
57+
const next = Sinon.spy(
58+
(_document: vscode.TextDocument): Promise<void> => {
59+
nextCalled = true;
60+
return Promise.resolve();
61+
},
62+
);
63+
64+
const didOpen = manager.didOpen;
65+
const didOpenPromise = didOpen(document, next);
66+
67+
await Promise.resolve();
68+
assert.equal(
69+
nextCalled,
70+
false,
71+
"didOpen should wait for initialization",
72+
);
73+
74+
(
75+
manager as unknown as {
76+
started: PromiseWithResolvers<undefined>;
77+
}
78+
).started.resolve(undefined);
79+
80+
await didOpenPromise;
81+
assert.equal(
82+
nextCalled,
83+
true,
84+
"didOpen should run after initialization",
85+
);
86+
assert.ok(
87+
registerCommandStub.calledThrice,
88+
"SessionManager should register its commands in constructor",
89+
);
90+
assert.ok(
91+
onDidChangeConfigurationStub.calledOnce,
92+
"SessionManager should register configuration listener in constructor",
93+
);
94+
});
95+
96+
it("delays didChange until session initialization completes", async () => {
97+
Sinon.stub(vscode.commands, "registerCommand").returns(
98+
disposableStub(),
99+
);
100+
Sinon.stub(vscode.workspace, "onDidChangeConfiguration").returns(
101+
disposableStub(),
102+
);
103+
104+
Sinon.stub(vscode.languages, "createLanguageStatusItem").returns(
105+
stubInterface<vscode.LanguageStatusItem>({
106+
text: "",
107+
detail: "",
108+
busy: false,
109+
severity: vscode.LanguageStatusSeverity.Information,
110+
dispose: () => {
111+
return;
112+
},
113+
}),
114+
);
115+
116+
const manager = new SessionManager(
117+
stubInterface<vscode.ExtensionContext>({
118+
globalStorageUri: vscode.Uri.file("C:/tmp"),
119+
extensionMode: vscode.ExtensionMode.Test,
120+
subscriptions: [],
121+
logUri: vscode.Uri.file("C:/tmp"),
122+
}),
123+
testLogger,
124+
["powershell"] as DocumentSelector,
125+
"Visual Studio Code",
126+
"PowerShell",
127+
"2026.5.0",
128+
"ms-vscode",
129+
stubInterface<TelemetryReporter>(),
130+
);
131+
132+
const changeEvent = stubInterface<vscode.TextDocumentChangeEvent>();
133+
let nextCalled = false;
134+
const next = Sinon.spy(
135+
(_event: vscode.TextDocumentChangeEvent): Promise<void> => {
136+
nextCalled = true;
137+
return Promise.resolve();
138+
},
139+
);
140+
141+
const didChange = manager.didChange;
142+
const didChangePromise = didChange(changeEvent, next);
143+
144+
await Promise.resolve();
145+
assert.equal(
146+
nextCalled,
147+
false,
148+
"didChange should wait for initialization",
149+
);
150+
151+
(
152+
manager as unknown as {
153+
started: PromiseWithResolvers<undefined>;
154+
}
155+
).started.resolve(undefined);
156+
157+
await didChangePromise;
158+
assert.equal(
159+
nextCalled,
160+
true,
161+
"didChange should run after initialization",
162+
);
163+
});
164+
});
165+
166+
function disposableStub(): vscode.Disposable {
167+
return {
168+
dispose: (): void => {
169+
return;
170+
},
171+
};
172+
}

0 commit comments

Comments
 (0)