/** * Unit tests for index.ts formatting functions */ // ============================================================================ // Test utilities // ============================================================================ const tests: Array<{ name: string; fn: () => void | Promise }> = []; function test(name: string, fn: () => void | Promise) { tests.push({ name, fn }); } function assertEqual(actual: T, expected: T, message?: string) { const a = JSON.stringify(actual); const e = JSON.stringify(expected); if (a !== e) throw new Error(message || `Expected ${e}, got ${a}`); } // ============================================================================ // Import the module to test internal functions // We need to test via the execute function since formatters are private // Or we can extract and test the logic directly // ============================================================================ import { uriToPath, findSymbolPosition, formatDiagnostic, filterDiagnosticsBySeverity } from "../lsp-core.js"; // ============================================================================ // uriToPath tests // ============================================================================ test("uriToPath: converts file:// URI to path", () => { const result = uriToPath("file:///Users/test/file.ts"); assertEqual(result, "/Users/test/file.ts"); }); test("uriToPath: handles encoded characters", () => { const result = uriToPath("file:///Users/test/my%20file.ts"); assertEqual(result, "/Users/test/my file.ts"); }); test("uriToPath: passes through non-file URIs", () => { const result = uriToPath("/some/path.ts"); assertEqual(result, "/some/path.ts"); }); test("uriToPath: handles invalid URIs gracefully", () => { const result = uriToPath("not-a-valid-uri"); assertEqual(result, "not-a-valid-uri"); }); // ============================================================================ // findSymbolPosition tests // ============================================================================ test("findSymbolPosition: finds exact match", () => { const symbols = [ { 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: [] }, { 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: [] }, ]; const pos = findSymbolPosition(symbols as any, "greet"); assertEqual(pos, { line: 5, character: 10 }); }); test("findSymbolPosition: finds partial match", () => { const symbols = [ { 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: [] }, ]; const pos = findSymbolPosition(symbols as any, "user"); assertEqual(pos, { line: 3, character: 0 }); }); test("findSymbolPosition: prefers exact over partial", () => { const symbols = [ { 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: [] }, { 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: [] }, ]; const pos = findSymbolPosition(symbols as any, "user"); assertEqual(pos, { line: 5, character: 0 }); }); test("findSymbolPosition: searches nested children", () => { const symbols = [ { name: "MyClass", range: { start: { line: 0, character: 0 }, end: { line: 10, character: 0 } }, selectionRange: { start: { line: 0, character: 0 }, end: { line: 0, character: 7 } }, kind: 5, children: [ { 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: [] }, ] }, ]; const pos = findSymbolPosition(symbols as any, "myMethod"); assertEqual(pos, { line: 2, character: 2 }); }); test("findSymbolPosition: returns null for no match", () => { const symbols = [ { 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: [] }, ]; const pos = findSymbolPosition(symbols as any, "bar"); assertEqual(pos, null); }); test("findSymbolPosition: case insensitive", () => { const symbols = [ { 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: [] }, ]; const pos = findSymbolPosition(symbols as any, "myfunction"); assertEqual(pos, { line: 0, character: 0 }); }); // ============================================================================ // formatDiagnostic tests // ============================================================================ test("formatDiagnostic: formats error", () => { const diag = { range: { start: { line: 5, character: 10 }, end: { line: 5, character: 15 } }, message: "Type 'number' is not assignable to type 'string'", severity: 1, }; const result = formatDiagnostic(diag as any); assertEqual(result, "ERROR [6:11] Type 'number' is not assignable to type 'string'"); }); test("formatDiagnostic: formats warning", () => { const diag = { range: { start: { line: 0, character: 0 }, end: { line: 0, character: 5 } }, message: "Unused variable", severity: 2, }; const result = formatDiagnostic(diag as any); assertEqual(result, "WARN [1:1] Unused variable"); }); test("formatDiagnostic: formats info", () => { const diag = { range: { start: { line: 2, character: 4 }, end: { line: 2, character: 10 } }, message: "Consider using const", severity: 3, }; const result = formatDiagnostic(diag as any); assertEqual(result, "INFO [3:5] Consider using const"); }); test("formatDiagnostic: formats hint", () => { const diag = { range: { start: { line: 0, character: 0 }, end: { line: 0, character: 1 } }, message: "Prefer arrow function", severity: 4, }; const result = formatDiagnostic(diag as any); assertEqual(result, "HINT [1:1] Prefer arrow function"); }); // ============================================================================ // filterDiagnosticsBySeverity tests // ============================================================================ test("filterDiagnosticsBySeverity: all returns everything", () => { const diags = [ { severity: 1, message: "error", range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } } }, { severity: 2, message: "warning", range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } } }, { severity: 3, message: "info", range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } } }, { severity: 4, message: "hint", range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } } }, ]; const result = filterDiagnosticsBySeverity(diags as any, "all"); assertEqual(result.length, 4); }); test("filterDiagnosticsBySeverity: error returns only errors", () => { const diags = [ { severity: 1, message: "error", range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } } }, { severity: 2, message: "warning", range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } } }, ]; const result = filterDiagnosticsBySeverity(diags as any, "error"); assertEqual(result.length, 1); assertEqual(result[0].message, "error"); }); test("filterDiagnosticsBySeverity: warning returns errors and warnings", () => { const diags = [ { severity: 1, message: "error", range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } } }, { severity: 2, message: "warning", range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } } }, { severity: 3, message: "info", range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } } }, ]; const result = filterDiagnosticsBySeverity(diags as any, "warning"); assertEqual(result.length, 2); }); test("filterDiagnosticsBySeverity: info returns errors, warnings, and info", () => { const diags = [ { severity: 1, message: "error", range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } } }, { severity: 2, message: "warning", range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } } }, { severity: 3, message: "info", range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } } }, { severity: 4, message: "hint", range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } } }, ]; const result = filterDiagnosticsBySeverity(diags as any, "info"); assertEqual(result.length, 3); }); // ============================================================================ // Run tests // ============================================================================ async function runTests(): Promise { console.log("Running index.ts unit tests...\n"); let passed = 0; let failed = 0; for (const { name, fn } of tests) { try { await fn(); console.log(` ${name}... ✓`); passed++; } catch (error) { const msg = error instanceof Error ? error.message : String(error); console.log(` ${name}... ✗`); console.log(` Error: ${msg}\n`); failed++; } } console.log(`\n${passed} passed, ${failed} failed`); if (failed > 0) { process.exit(1); } } runTests();