test(language-server): cover didClose region-cache clear end-to-end
Replaces the direct-call-only test with an over-stdio test that exercises server.ts's didClose handler itself, so it fails if the clearHtmlRegionCache wiring is removed or misparameterized.
This commit is contained in:
@@ -217,3 +217,91 @@ test("coalesces rapid document changes into one pending diagnostic analysis", as
|
|||||||
process.kill();
|
process.kill();
|
||||||
await process.exited;
|
await process.exited;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("didClose drops the region cache so a reopened document at the same version is rescanned", async () => {
|
||||||
|
// Regression coverage for the didClose wiring in server.ts: without the
|
||||||
|
// clearHtmlRegionCache(uri) call there, a document that closes and reopens
|
||||||
|
// at version 1 (a common restart point) matches the stale cache entry from
|
||||||
|
// the prior session. Open first WITHOUT a view block (caching "no HTML
|
||||||
|
// here" for this uri/version), close, then reopen the SAME uri at the SAME
|
||||||
|
// version WITH a view block covering the same offset: correct behaviour
|
||||||
|
// rescans and finds it, a stale cache still says "no HTML here" and
|
||||||
|
// suppresses the completions entirely.
|
||||||
|
const process = Bun.spawn(
|
||||||
|
["bun", "run", fileURLToPath(new URL("../src/server.ts", import.meta.url))],
|
||||||
|
{ stdin: "pipe", stdout: "pipe", stderr: "pipe" },
|
||||||
|
);
|
||||||
|
const uri = "file:///reopen.wrn";
|
||||||
|
const withoutView = `page A {
|
||||||
|
functions {
|
||||||
|
x
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
const withView = `page A {
|
||||||
|
view {
|
||||||
|
<
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
const position = { line: 2, character: 5 };
|
||||||
|
const reader = process.stdout.getReader();
|
||||||
|
let output = "";
|
||||||
|
async function readUntil(marker: string): Promise<void> {
|
||||||
|
while (!output.includes(marker)) {
|
||||||
|
const chunk = await reader.read();
|
||||||
|
if (chunk.done) break;
|
||||||
|
output += new TextDecoder().decode(chunk.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
process.stdin.write(packet({ jsonrpc: "2.0", id: 1, method: "initialize", params: {} }));
|
||||||
|
await process.stdin.flush();
|
||||||
|
await readUntil('"id":1');
|
||||||
|
|
||||||
|
process.stdin.write(
|
||||||
|
packet({
|
||||||
|
jsonrpc: "2.0",
|
||||||
|
method: "textDocument/didOpen",
|
||||||
|
params: { textDocument: { uri, version: 1, text: withoutView } },
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
process.stdin.write(
|
||||||
|
packet({
|
||||||
|
jsonrpc: "2.0",
|
||||||
|
id: 2,
|
||||||
|
method: "textDocument/completion",
|
||||||
|
params: { textDocument: { uri }, position },
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
await process.stdin.flush();
|
||||||
|
await readUntil('"id":2');
|
||||||
|
const firstReply = output.slice(output.indexOf('"id":2'));
|
||||||
|
expect(firstReply).not.toContain('"label":"div"');
|
||||||
|
|
||||||
|
process.stdin.write(
|
||||||
|
packet({ jsonrpc: "2.0", method: "textDocument/didClose", params: { textDocument: { uri } } }),
|
||||||
|
);
|
||||||
|
process.stdin.write(
|
||||||
|
packet({
|
||||||
|
jsonrpc: "2.0",
|
||||||
|
method: "textDocument/didOpen",
|
||||||
|
params: { textDocument: { uri, version: 1, text: withView } },
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
process.stdin.write(
|
||||||
|
packet({
|
||||||
|
jsonrpc: "2.0",
|
||||||
|
id: 3,
|
||||||
|
method: "textDocument/completion",
|
||||||
|
params: { textDocument: { uri }, position },
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
await process.stdin.flush();
|
||||||
|
await readUntil('"id":3');
|
||||||
|
const secondReply = output.slice(output.indexOf('"id":3'));
|
||||||
|
expect(secondReply).toContain('"label":"div"');
|
||||||
|
|
||||||
|
process.kill();
|
||||||
|
await process.exited;
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user