lsp-pi 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,235 @@
1
+ /**
2
+ * Unit tests for index.ts formatting functions
3
+ */
4
+
5
+ // ============================================================================
6
+ // Test utilities
7
+ // ============================================================================
8
+
9
+ const tests: Array<{ name: string; fn: () => void | Promise<void> }> = [];
10
+
11
+ function test(name: string, fn: () => void | Promise<void>) {
12
+ tests.push({ name, fn });
13
+ }
14
+
15
+ function assertEqual<T>(actual: T, expected: T, message?: string) {
16
+ const a = JSON.stringify(actual);
17
+ const e = JSON.stringify(expected);
18
+ if (a !== e) throw new Error(message || `Expected ${e}, got ${a}`);
19
+ }
20
+
21
+ // ============================================================================
22
+ // Import the module to test internal functions
23
+ // We need to test via the execute function since formatters are private
24
+ // Or we can extract and test the logic directly
25
+ // ============================================================================
26
+
27
+ import { uriToPath, findSymbolPosition, formatDiagnostic, filterDiagnosticsBySeverity } from "../lsp-core.js";
28
+
29
+ // ============================================================================
30
+ // uriToPath tests
31
+ // ============================================================================
32
+
33
+ test("uriToPath: converts file:// URI to path", () => {
34
+ const result = uriToPath("file:///Users/test/file.ts");
35
+ assertEqual(result, "/Users/test/file.ts");
36
+ });
37
+
38
+ test("uriToPath: handles encoded characters", () => {
39
+ const result = uriToPath("file:///Users/test/my%20file.ts");
40
+ assertEqual(result, "/Users/test/my file.ts");
41
+ });
42
+
43
+ test("uriToPath: passes through non-file URIs", () => {
44
+ const result = uriToPath("/some/path.ts");
45
+ assertEqual(result, "/some/path.ts");
46
+ });
47
+
48
+ test("uriToPath: handles invalid URIs gracefully", () => {
49
+ const result = uriToPath("not-a-valid-uri");
50
+ assertEqual(result, "not-a-valid-uri");
51
+ });
52
+
53
+ // ============================================================================
54
+ // findSymbolPosition tests
55
+ // ============================================================================
56
+
57
+ test("findSymbolPosition: finds exact match", () => {
58
+ const symbols = [
59
+ { name: "greet", range: { start: { line: 5, character: 10 }, end: { line: 5, character: 15 } }, selectionRange: { start: { line: 5, character: 10 }, end: { line: 5, character: 15 } }, kind: 12, children: [] },
60
+ { name: "hello", range: { start: { line: 10, character: 0 }, end: { line: 10, character: 5 } }, selectionRange: { start: { line: 10, character: 0 }, end: { line: 10, character: 5 } }, kind: 12, children: [] },
61
+ ];
62
+ const pos = findSymbolPosition(symbols as any, "greet");
63
+ assertEqual(pos, { line: 5, character: 10 });
64
+ });
65
+
66
+ test("findSymbolPosition: finds partial match", () => {
67
+ const symbols = [
68
+ { name: "getUserName", range: { start: { line: 3, character: 0 }, end: { line: 3, character: 11 } }, selectionRange: { start: { line: 3, character: 0 }, end: { line: 3, character: 11 } }, kind: 12, children: [] },
69
+ ];
70
+ const pos = findSymbolPosition(symbols as any, "user");
71
+ assertEqual(pos, { line: 3, character: 0 });
72
+ });
73
+
74
+ test("findSymbolPosition: prefers exact over partial", () => {
75
+ const symbols = [
76
+ { name: "userName", range: { start: { line: 1, character: 0 }, end: { line: 1, character: 8 } }, selectionRange: { start: { line: 1, character: 0 }, end: { line: 1, character: 8 } }, kind: 12, children: [] },
77
+ { name: "user", range: { start: { line: 5, character: 0 }, end: { line: 5, character: 4 } }, selectionRange: { start: { line: 5, character: 0 }, end: { line: 5, character: 4 } }, kind: 12, children: [] },
78
+ ];
79
+ const pos = findSymbolPosition(symbols as any, "user");
80
+ assertEqual(pos, { line: 5, character: 0 });
81
+ });
82
+
83
+ test("findSymbolPosition: searches nested children", () => {
84
+ const symbols = [
85
+ {
86
+ name: "MyClass",
87
+ range: { start: { line: 0, character: 0 }, end: { line: 10, character: 0 } },
88
+ selectionRange: { start: { line: 0, character: 0 }, end: { line: 0, character: 7 } },
89
+ kind: 5,
90
+ children: [
91
+ { name: "myMethod", range: { start: { line: 2, character: 2 }, end: { line: 4, character: 2 } }, selectionRange: { start: { line: 2, character: 2 }, end: { line: 2, character: 10 } }, kind: 6, children: [] },
92
+ ]
93
+ },
94
+ ];
95
+ const pos = findSymbolPosition(symbols as any, "myMethod");
96
+ assertEqual(pos, { line: 2, character: 2 });
97
+ });
98
+
99
+ test("findSymbolPosition: returns null for no match", () => {
100
+ const symbols = [
101
+ { name: "foo", range: { start: { line: 0, character: 0 }, end: { line: 0, character: 3 } }, selectionRange: { start: { line: 0, character: 0 }, end: { line: 0, character: 3 } }, kind: 12, children: [] },
102
+ ];
103
+ const pos = findSymbolPosition(symbols as any, "bar");
104
+ assertEqual(pos, null);
105
+ });
106
+
107
+ test("findSymbolPosition: case insensitive", () => {
108
+ const symbols = [
109
+ { name: "MyFunction", range: { start: { line: 0, character: 0 }, end: { line: 0, character: 10 } }, selectionRange: { start: { line: 0, character: 0 }, end: { line: 0, character: 10 } }, kind: 12, children: [] },
110
+ ];
111
+ const pos = findSymbolPosition(symbols as any, "myfunction");
112
+ assertEqual(pos, { line: 0, character: 0 });
113
+ });
114
+
115
+ // ============================================================================
116
+ // formatDiagnostic tests
117
+ // ============================================================================
118
+
119
+ test("formatDiagnostic: formats error", () => {
120
+ const diag = {
121
+ range: { start: { line: 5, character: 10 }, end: { line: 5, character: 15 } },
122
+ message: "Type 'number' is not assignable to type 'string'",
123
+ severity: 1,
124
+ };
125
+ const result = formatDiagnostic(diag as any);
126
+ assertEqual(result, "ERROR [6:11] Type 'number' is not assignable to type 'string'");
127
+ });
128
+
129
+ test("formatDiagnostic: formats warning", () => {
130
+ const diag = {
131
+ range: { start: { line: 0, character: 0 }, end: { line: 0, character: 5 } },
132
+ message: "Unused variable",
133
+ severity: 2,
134
+ };
135
+ const result = formatDiagnostic(diag as any);
136
+ assertEqual(result, "WARN [1:1] Unused variable");
137
+ });
138
+
139
+ test("formatDiagnostic: formats info", () => {
140
+ const diag = {
141
+ range: { start: { line: 2, character: 4 }, end: { line: 2, character: 10 } },
142
+ message: "Consider using const",
143
+ severity: 3,
144
+ };
145
+ const result = formatDiagnostic(diag as any);
146
+ assertEqual(result, "INFO [3:5] Consider using const");
147
+ });
148
+
149
+ test("formatDiagnostic: formats hint", () => {
150
+ const diag = {
151
+ range: { start: { line: 0, character: 0 }, end: { line: 0, character: 1 } },
152
+ message: "Prefer arrow function",
153
+ severity: 4,
154
+ };
155
+ const result = formatDiagnostic(diag as any);
156
+ assertEqual(result, "HINT [1:1] Prefer arrow function");
157
+ });
158
+
159
+ // ============================================================================
160
+ // filterDiagnosticsBySeverity tests
161
+ // ============================================================================
162
+
163
+ test("filterDiagnosticsBySeverity: all returns everything", () => {
164
+ const diags = [
165
+ { severity: 1, message: "error", range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } } },
166
+ { severity: 2, message: "warning", range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } } },
167
+ { severity: 3, message: "info", range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } } },
168
+ { severity: 4, message: "hint", range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } } },
169
+ ];
170
+ const result = filterDiagnosticsBySeverity(diags as any, "all");
171
+ assertEqual(result.length, 4);
172
+ });
173
+
174
+ test("filterDiagnosticsBySeverity: error returns only errors", () => {
175
+ const diags = [
176
+ { severity: 1, message: "error", range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } } },
177
+ { severity: 2, message: "warning", range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } } },
178
+ ];
179
+ const result = filterDiagnosticsBySeverity(diags as any, "error");
180
+ assertEqual(result.length, 1);
181
+ assertEqual(result[0].message, "error");
182
+ });
183
+
184
+ test("filterDiagnosticsBySeverity: warning returns errors and warnings", () => {
185
+ const diags = [
186
+ { severity: 1, message: "error", range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } } },
187
+ { severity: 2, message: "warning", range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } } },
188
+ { severity: 3, message: "info", range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } } },
189
+ ];
190
+ const result = filterDiagnosticsBySeverity(diags as any, "warning");
191
+ assertEqual(result.length, 2);
192
+ });
193
+
194
+ test("filterDiagnosticsBySeverity: info returns errors, warnings, and info", () => {
195
+ const diags = [
196
+ { severity: 1, message: "error", range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } } },
197
+ { severity: 2, message: "warning", range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } } },
198
+ { severity: 3, message: "info", range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } } },
199
+ { severity: 4, message: "hint", range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } } },
200
+ ];
201
+ const result = filterDiagnosticsBySeverity(diags as any, "info");
202
+ assertEqual(result.length, 3);
203
+ });
204
+
205
+ // ============================================================================
206
+ // Run tests
207
+ // ============================================================================
208
+
209
+ async function runTests(): Promise<void> {
210
+ console.log("Running index.ts unit tests...\n");
211
+
212
+ let passed = 0;
213
+ let failed = 0;
214
+
215
+ for (const { name, fn } of tests) {
216
+ try {
217
+ await fn();
218
+ console.log(` ${name}... ✓`);
219
+ passed++;
220
+ } catch (error) {
221
+ const msg = error instanceof Error ? error.message : String(error);
222
+ console.log(` ${name}... ✗`);
223
+ console.log(` Error: ${msg}\n`);
224
+ failed++;
225
+ }
226
+ }
227
+
228
+ console.log(`\n${passed} passed, ${failed} failed`);
229
+
230
+ if (failed > 0) {
231
+ process.exit(1);
232
+ }
233
+ }
234
+
235
+ runTests();