summaryrefslogtreecommitdiffhomepage
path: root/packages/core/tests/lsp/manager.test.ts
blob: e72041340d87e79a0b727a0c37098b408c81a5f9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
import { spawn } from "node:child_process";
import { mkdtemp, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { dirname, join } from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import type { Diagnostic } from "vscode-languageserver-types";
import { LspManager } from "../../src/lsp/manager.js";
import type { ResolvedLspServer } from "../../src/lsp/server.js";

const FIXTURE = join(dirname(fileURLToPath(import.meta.url)), "../fixture/lsp/fake-lsp-server.js");

function makeServer(id: string, extensions: string[]) {
	const counter = { count: 0 };
	const server: ResolvedLspServer = {
		id,
		extensions,
		spawn() {
			counter.count += 1;
			const proc = spawn(process.execPath, [FIXTURE], { stdio: "pipe" });
			return { process: proc as never };
		},
	};
	return { server, counter };
}

describe("lsp/manager (fake server)", () => {
	let root: string;
	let manager: LspManager;

	beforeEach(async () => {
		root = await mkdtemp(join(tmpdir(), "dispatch-lspmgr-"));
		manager = new LspManager();
	});
	afterEach(async () => {
		await manager.shutdownAll();
		await rm(root, { recursive: true, force: true });
	});

	it("hasServerForFile matches by extension", () => {
		const { server } = makeServer("fake", [".luau"]);
		expect(manager.hasServerForFile(join(root, "a.luau"), [server])).toBe(true);
		expect(manager.hasServerForFile(join(root, "a.ts"), [server])).toBe(false);
	});

	it("spawns lazily and reuses the client across calls", async () => {
		const { server, counter } = makeServer("fake", [".luau"]);
		const file = join(root, "a.luau");
		await writeFile(file, "local x = 1\n");

		const c1 = await manager.getClients({ file, root, servers: [server] });
		const c2 = await manager.getClients({ file, root, servers: [server] });
		expect(c1).toHaveLength(1);
		expect(c2).toHaveLength(1);
		expect(c1[0]).toBe(c2[0]);
		expect(counter.count).toBe(1);
	});

	it("does not spawn for a non-matching extension", async () => {
		const { server, counter } = makeServer("fake", [".luau"]);
		const file = join(root, "a.ts");
		await writeFile(file, "const x = 1\n");
		const clients = await manager.getClients({ file, root, servers: [server] });
		expect(clients).toHaveLength(0);
		expect(counter.count).toBe(0);
	});

	it("touchFile + getDiagnostics surfaces a pushed diagnostic", async () => {
		const { server } = makeServer("fake", [".luau"]);
		const file = join(root, "a.luau");
		await writeFile(file, "bad code\n");

		await manager.touchFile({ file, root, servers: [server] });
		const [client] = await manager.getClients({ file, root, servers: [server] });
		// Drive a push through the fake server.
		const diag: Diagnostic = {
			range: { start: { line: 0, character: 0 }, end: { line: 0, character: 3 } },
			severity: 1,
			message: "manager error",
		};
		await client.connection.sendRequest("test/publish-diagnostics", {
			uri: pathToFileURL(file).href,
			diagnostics: [diag],
		});
		await new Promise((r) => setTimeout(r, 50));

		const result = manager.getDiagnostics({ root, servers: [server], file });
		expect(result[file]?.[0]?.message).toBe("manager error");
	});

	it("request() forwards to clients and flattens results", async () => {
		const { server } = makeServer("fake", [".luau"]);
		const file = join(root, "a.luau");
		await writeFile(file, "local x = 1\n");
		await manager.touchFile({ file, root, servers: [server] });

		const results = await manager.request({
			file,
			root,
			servers: [server],
			method: "textDocument/definition",
			params: {
				textDocument: { uri: pathToFileURL(file).href },
				position: { line: 0, character: 6 },
			},
		});
		expect(results.length).toBeGreaterThan(0);
	});

	it("shutdownAll clears state so the next call respawns", async () => {
		const { server, counter } = makeServer("fake", [".luau"]);
		const file = join(root, "a.luau");
		await writeFile(file, "local x = 1\n");
		await manager.getClients({ file, root, servers: [server] });
		expect(counter.count).toBe(1);
		await manager.shutdownAll();
		await manager.getClients({ file, root, servers: [server] });
		expect(counter.count).toBe(2);
	});
});