pr19-improvements.test.ts 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719
  1. /**
  2. * PR #19 Improvement Tests
  3. *
  4. * Tests for changes ported from PR #15 and #16:
  5. * - Lazy grammar loading
  6. * - Arrow function extraction (body traversal)
  7. * - Graph traversal 'both' direction fix
  8. * - Best-candidate resolution picking
  9. * - Schema v2 migration (filePath/language on unresolved_refs)
  10. * - Batch insert for unresolved refs
  11. * - SQLite performance pragmas
  12. * - MCP symbol disambiguation and output truncation
  13. * - CLI uninit command
  14. */
  15. import { describe, it, expect, beforeAll, beforeEach, afterEach } from 'vitest';
  16. import * as fs from 'fs';
  17. import * as path from 'path';
  18. import * as os from 'os';
  19. import { extractFromSource } from '../src/extraction';
  20. import {
  21. getParser,
  22. isLanguageSupported,
  23. getSupportedLanguages,
  24. clearParserCache,
  25. getUnavailableGrammarErrors,
  26. initGrammars,
  27. loadAllGrammars,
  28. } from '../src/extraction/grammars';
  29. beforeAll(async () => {
  30. await initGrammars();
  31. await loadAllGrammars();
  32. });
  33. // Create a temporary directory for each test
  34. function createTempDir(): string {
  35. return fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-pr19-test-'));
  36. }
  37. // Clean up temporary directory
  38. function cleanupTempDir(dir: string): void {
  39. if (fs.existsSync(dir)) {
  40. fs.rmSync(dir, { recursive: true, force: true });
  41. }
  42. }
  43. // Check if the node:sqlite backend is available (Node >= 22.5)
  44. function hasSqliteBindings(): boolean {
  45. try {
  46. const { DatabaseSync } = require('node:sqlite');
  47. const db = new DatabaseSync(':memory:');
  48. db.close();
  49. return true;
  50. } catch {
  51. return false;
  52. }
  53. }
  54. const HAS_SQLITE = hasSqliteBindings();
  55. // =============================================================================
  56. // Lazy Grammar Loading
  57. // =============================================================================
  58. describe('Lazy Grammar Loading', () => {
  59. afterEach(() => {
  60. clearParserCache();
  61. });
  62. it('should load grammars lazily on first use', () => {
  63. // Clear cache to force fresh load
  64. clearParserCache();
  65. // TypeScript should be loadable
  66. const parser = getParser('typescript');
  67. expect(parser).not.toBeNull();
  68. });
  69. it('should cache loaded grammars', () => {
  70. clearParserCache();
  71. const parser1 = getParser('typescript');
  72. const parser2 = getParser('typescript');
  73. // Same reference from cache
  74. expect(parser1).toBe(parser2);
  75. });
  76. it('should return null for unknown language', () => {
  77. const parser = getParser('unknown');
  78. expect(parser).toBeNull();
  79. });
  80. it('should handle unavailable grammars gracefully', () => {
  81. // 'unknown' is not a valid grammar, should not crash
  82. expect(isLanguageSupported('unknown')).toBe(false);
  83. });
  84. it('should report liquid as supported (custom extractor)', () => {
  85. expect(isLanguageSupported('liquid')).toBe(true);
  86. });
  87. it('should include liquid in supported languages', () => {
  88. const supported = getSupportedLanguages();
  89. expect(supported).toContain('liquid');
  90. });
  91. it('should return unavailable grammar errors as a record', () => {
  92. clearParserCache();
  93. const errors = getUnavailableGrammarErrors();
  94. // Should be a plain object (may or may not have entries depending on platform)
  95. expect(typeof errors).toBe('object');
  96. });
  97. it('should support multiple languages independently', () => {
  98. clearParserCache();
  99. // Load two different languages - one failing shouldn't affect the other
  100. const tsParser = getParser('typescript');
  101. const pyParser = getParser('python');
  102. expect(tsParser).not.toBeNull();
  103. expect(pyParser).not.toBeNull();
  104. expect(tsParser).not.toBe(pyParser);
  105. });
  106. it('should clear all caches on clearParserCache', () => {
  107. // Load a grammar
  108. getParser('typescript');
  109. // Clear
  110. clearParserCache();
  111. // Errors should be cleared too
  112. const errors = getUnavailableGrammarErrors();
  113. expect(Object.keys(errors)).toHaveLength(0);
  114. });
  115. });
  116. // =============================================================================
  117. // Arrow Function Extraction - Body Traversal
  118. // =============================================================================
  119. describe('Arrow Function Body Traversal', () => {
  120. it('should extract unresolved references from arrow function bodies', () => {
  121. const code = `
  122. export const useAuth = () => {
  123. const user = getUser();
  124. const token = generateToken(user);
  125. return { user, token };
  126. };
  127. `;
  128. const result = extractFromSource('hooks.ts', code);
  129. // The arrow function should be extracted
  130. const funcNode = result.nodes.find((n) => n.kind === 'function' && n.name === 'useAuth');
  131. expect(funcNode).toBeDefined();
  132. // Calls inside the body should be captured as unresolved references
  133. const calls = result.unresolvedReferences.filter((r) => r.referenceKind === 'calls');
  134. const callNames = calls.map((c) => c.referenceName);
  135. expect(callNames).toContain('getUser');
  136. expect(callNames).toContain('generateToken');
  137. });
  138. it('should extract unresolved references from function expression bodies', () => {
  139. const code = `
  140. export const processData = function(input: string): string {
  141. const cleaned = sanitize(input);
  142. return transform(cleaned);
  143. };
  144. `;
  145. const result = extractFromSource('utils.ts', code);
  146. const funcNode = result.nodes.find((n) => n.kind === 'function' && n.name === 'processData');
  147. expect(funcNode).toBeDefined();
  148. const calls = result.unresolvedReferences.filter((r) => r.referenceKind === 'calls');
  149. const callNames = calls.map((c) => c.referenceName);
  150. expect(callNames).toContain('sanitize');
  151. expect(callNames).toContain('transform');
  152. });
  153. it('should not create duplicate nodes for arrow functions', () => {
  154. const code = `
  155. export const handler = () => {
  156. doSomething();
  157. };
  158. `;
  159. const result = extractFromSource('handler.ts', code);
  160. // Should be exactly 1 function node, 0 variable nodes for 'handler'
  161. const funcNodes = result.nodes.filter((n) => n.name === 'handler' && n.kind === 'function');
  162. const varNodes = result.nodes.filter((n) => n.name === 'handler' && n.kind === 'variable');
  163. expect(funcNodes).toHaveLength(1);
  164. expect(varNodes).toHaveLength(0);
  165. });
  166. it('should extract nested calls in arrow functions in JavaScript', () => {
  167. const code = `
  168. export const fetchData = async () => {
  169. const response = await fetchAPI('/data');
  170. return parseResponse(response);
  171. };
  172. `;
  173. const result = extractFromSource('api.js', code);
  174. const funcNode = result.nodes.find((n) => n.name === 'fetchData');
  175. expect(funcNode).toBeDefined();
  176. expect(funcNode?.kind).toBe('function');
  177. const calls = result.unresolvedReferences.filter((r) => r.referenceKind === 'calls');
  178. const callNames = calls.map((c) => c.referenceName);
  179. expect(callNames).toContain('fetchAPI');
  180. expect(callNames).toContain('parseResponse');
  181. });
  182. });
  183. // =============================================================================
  184. // Graph Traversal 'both' Direction Fix
  185. // (requires better-sqlite3 - will use CodeGraph integration)
  186. // =============================================================================
  187. describe('Graph Traversal Both Direction', () => {
  188. let testDir: string;
  189. beforeEach(() => {
  190. testDir = createTempDir();
  191. });
  192. afterEach(() => {
  193. cleanupTempDir(testDir);
  194. });
  195. it.skipIf(!HAS_SQLITE)('should traverse both directions from a node', async () => {
  196. const CodeGraph = (await import('../src/index')).default;
  197. const srcDir = path.join(testDir, 'src');
  198. fs.mkdirSync(srcDir, { recursive: true });
  199. // A -> B -> C (A calls B, B calls C)
  200. fs.writeFileSync(path.join(srcDir, 'a.ts'), `
  201. import { funcB } from './b';
  202. export function funcA(): void { funcB(); }
  203. `);
  204. fs.writeFileSync(path.join(srcDir, 'b.ts'), `
  205. import { funcC } from './c';
  206. export function funcB(): void { funcC(); }
  207. `);
  208. fs.writeFileSync(path.join(srcDir, 'c.ts'), `
  209. export function funcC(): void { console.log('c'); }
  210. `);
  211. const cg = CodeGraph.initSync(testDir, {
  212. config: { include: ['src/**/*.ts'], exclude: [] },
  213. });
  214. await cg.indexAll();
  215. cg.resolveReferences();
  216. const functions = cg.getNodesByKind('function');
  217. const funcB = functions.find((n) => n.name === 'funcB');
  218. if (!funcB) {
  219. cg.destroy();
  220. return;
  221. }
  222. // Traverse 'both' from B - should find A (incoming caller) and C (outgoing callee)
  223. const subgraph = cg.traverse(funcB.id, {
  224. maxDepth: 1,
  225. direction: 'both',
  226. });
  227. // B itself + at least one neighbor in each direction
  228. expect(subgraph.nodes.size).toBeGreaterThanOrEqual(2);
  229. expect(subgraph.nodes.has(funcB.id)).toBe(true);
  230. cg.destroy();
  231. });
  232. });
  233. // =============================================================================
  234. // Best-Candidate Resolution
  235. // =============================================================================
  236. describe('Best-Candidate Resolution', () => {
  237. it.skipIf(!HAS_SQLITE)('should be testable via the resolution module types', async () => {
  238. const { ReferenceResolver } = await import('../src/resolution');
  239. expect(typeof ReferenceResolver.prototype.resolveOne).toBe('function');
  240. });
  241. });
  242. // =============================================================================
  243. // Schema v2 Migration
  244. // =============================================================================
  245. describe('Schema v2 Migration', () => {
  246. it.skipIf(!HAS_SQLITE)('should have correct current schema version', async () => {
  247. const { CURRENT_SCHEMA_VERSION } = await import('../src/db/migrations');
  248. expect(CURRENT_SCHEMA_VERSION).toBe(4);
  249. });
  250. it.skipIf(!HAS_SQLITE)('should have migration for version 2', async () => {
  251. const { getPendingMigrations } = await import('../src/db/migrations');
  252. expect(typeof getPendingMigrations).toBe('function');
  253. });
  254. });
  255. // =============================================================================
  256. // Database Layer: Batch Insert, getAllNodes, Pragmas
  257. // =============================================================================
  258. describe('Database Layer Improvements', () => {
  259. let testDir: string;
  260. beforeEach(() => {
  261. testDir = createTempDir();
  262. });
  263. afterEach(() => {
  264. cleanupTempDir(testDir);
  265. });
  266. it.skipIf(!HAS_SQLITE)('should support batch insert of unresolved refs', async () => {
  267. const { DatabaseConnection } = await import('../src/db');
  268. const { QueryBuilder } = await import('../src/db/queries');
  269. const dbPath = path.join(testDir, 'codegraph.db');
  270. const db = DatabaseConnection.initialize(dbPath);
  271. const queries = new QueryBuilder(db.getDb());
  272. // Insert a node first (needed as foreign key)
  273. queries.insertNode({
  274. id: 'func:test:1',
  275. kind: 'function',
  276. name: 'testFunc',
  277. qualifiedName: 'test::testFunc',
  278. filePath: 'test.ts',
  279. language: 'typescript',
  280. startLine: 1,
  281. endLine: 5,
  282. startColumn: 0,
  283. endColumn: 1,
  284. updatedAt: Date.now(),
  285. });
  286. // Batch insert unresolved refs with filePath and language
  287. queries.insertUnresolvedRefsBatch([
  288. {
  289. fromNodeId: 'func:test:1',
  290. referenceName: 'helperA',
  291. referenceKind: 'calls',
  292. line: 2,
  293. column: 4,
  294. filePath: 'test.ts',
  295. language: 'typescript',
  296. },
  297. {
  298. fromNodeId: 'func:test:1',
  299. referenceName: 'helperB',
  300. referenceKind: 'calls',
  301. line: 3,
  302. column: 4,
  303. filePath: 'test.ts',
  304. language: 'typescript',
  305. },
  306. ]);
  307. const refs = queries.getUnresolvedReferences();
  308. expect(refs).toHaveLength(2);
  309. expect(refs.map((r) => r.referenceName).sort()).toEqual(['helperA', 'helperB']);
  310. // Verify filePath and language are persisted
  311. expect(refs[0]?.filePath).toBe('test.ts');
  312. expect(refs[0]?.language).toBe('typescript');
  313. db.close();
  314. });
  315. it.skipIf(!HAS_SQLITE)('should support getAllNodes', async () => {
  316. const { DatabaseConnection } = await import('../src/db');
  317. const { QueryBuilder } = await import('../src/db/queries');
  318. const dbPath = path.join(testDir, 'codegraph.db');
  319. const db = DatabaseConnection.initialize(dbPath);
  320. const queries = new QueryBuilder(db.getDb());
  321. // Insert some nodes
  322. for (let i = 0; i < 3; i++) {
  323. queries.insertNode({
  324. id: `func:test:${i}`,
  325. kind: 'function',
  326. name: `func${i}`,
  327. qualifiedName: `test::func${i}`,
  328. filePath: 'test.ts',
  329. language: 'typescript',
  330. startLine: i * 10 + 1,
  331. endLine: i * 10 + 5,
  332. startColumn: 0,
  333. endColumn: 1,
  334. updatedAt: Date.now(),
  335. });
  336. }
  337. const allNodes = queries.getAllNodes();
  338. expect(allNodes).toHaveLength(3);
  339. expect(allNodes.map((n) => n.name).sort()).toEqual(['func0', 'func1', 'func2']);
  340. db.close();
  341. });
  342. it.skipIf(!HAS_SQLITE)('should set performance pragmas on initialization', async () => {
  343. const { DatabaseConnection } = await import('../src/db');
  344. const dbPath = path.join(testDir, 'codegraph.db');
  345. const db = DatabaseConnection.initialize(dbPath);
  346. const rawDb = db.getDb();
  347. // Check pragmas were set
  348. const synchronous = rawDb.pragma('synchronous', { simple: true });
  349. expect(synchronous).toBe(1); // NORMAL = 1
  350. const cacheSize = rawDb.pragma('cache_size', { simple: true }) as number;
  351. expect(cacheSize).toBe(-64000);
  352. const tempStore = rawDb.pragma('temp_store', { simple: true });
  353. expect(tempStore).toBe(2); // MEMORY = 2
  354. const mmapSize = rawDb.pragma('mmap_size', { simple: true }) as number;
  355. expect(mmapSize).toBe(268435456); // 256 MB
  356. db.close();
  357. });
  358. it.skipIf(!HAS_SQLITE)('should handle empty batch insert gracefully', async () => {
  359. const { DatabaseConnection } = await import('../src/db');
  360. const { QueryBuilder } = await import('../src/db/queries');
  361. const dbPath = path.join(testDir, 'codegraph.db');
  362. const db = DatabaseConnection.initialize(dbPath);
  363. const queries = new QueryBuilder(db.getDb());
  364. // Should not throw on empty array
  365. expect(() => queries.insertUnresolvedRefsBatch([])).not.toThrow();
  366. db.close();
  367. });
  368. });
  369. // =============================================================================
  370. // Resolution Warm Caches
  371. // =============================================================================
  372. describe('Resolution Warm Caches', () => {
  373. let testDir: string;
  374. beforeEach(() => {
  375. testDir = createTempDir();
  376. });
  377. afterEach(() => {
  378. cleanupTempDir(testDir);
  379. });
  380. it.skipIf(!HAS_SQLITE)('should warm caches and use them for lookups', async () => {
  381. const CodeGraph = (await import('../src/index')).default;
  382. const srcDir = path.join(testDir, 'src');
  383. fs.mkdirSync(srcDir, { recursive: true });
  384. fs.writeFileSync(path.join(srcDir, 'a.ts'), `
  385. export function myFunc(): void {}
  386. export function otherFunc(): void { myFunc(); }
  387. `);
  388. const cg = CodeGraph.initSync(testDir, {
  389. config: { include: ['src/**/*.ts'], exclude: [] },
  390. });
  391. await cg.indexAll();
  392. // resolveReferences internally calls warmCaches
  393. const result = cg.resolveReferences();
  394. // Should complete without error
  395. expect(result.stats.total).toBeGreaterThanOrEqual(0);
  396. cg.destroy();
  397. });
  398. });
  399. // =============================================================================
  400. // MCP Tool Improvements
  401. // =============================================================================
  402. describe('MCP Tool Improvements', () => {
  403. it.skipIf(!HAS_SQLITE)('should export ToolHandler class', async () => {
  404. const { ToolHandler } = await import('../src/mcp/tools');
  405. expect(typeof ToolHandler).toBe('function');
  406. });
  407. it.skipIf(!HAS_SQLITE)('should have findSymbol and truncateOutput as private methods', async () => {
  408. const { ToolHandler } = await import('../src/mcp/tools');
  409. const proto = ToolHandler.prototype;
  410. expect(typeof (proto as any).findSymbol).toBe('function');
  411. expect(typeof (proto as any).truncateOutput).toBe('function');
  412. });
  413. it.skipIf(!HAS_SQLITE)('should truncate output exceeding MAX_OUTPUT_LENGTH', async () => {
  414. const { ToolHandler } = await import('../src/mcp/tools');
  415. // Access private method for testing
  416. const handler = Object.create(ToolHandler.prototype);
  417. const truncate = (handler as any).truncateOutput.bind(handler);
  418. // Short text should not be truncated
  419. const short = 'Hello world';
  420. expect(truncate(short)).toBe(short);
  421. // Long text should be truncated
  422. const long = 'x'.repeat(20000);
  423. const result = truncate(long);
  424. expect(result.length).toBeLessThan(long.length);
  425. expect(result).toContain('... (output truncated)');
  426. });
  427. it.skipIf(!HAS_SQLITE)('should truncate at a clean line boundary', async () => {
  428. const { ToolHandler } = await import('../src/mcp/tools');
  429. const handler = Object.create(ToolHandler.prototype);
  430. const truncate = (handler as any).truncateOutput.bind(handler);
  431. // Build text with newlines exceeding the limit
  432. const lines: string[] = [];
  433. for (let i = 0; i < 500; i++) {
  434. lines.push(`Line ${i}: ${'a'.repeat(50)}`);
  435. }
  436. const text = lines.join('\n');
  437. const result = truncate(text);
  438. // Should end with truncation notice after a newline boundary
  439. expect(result).toContain('... (output truncated)');
  440. // Should not cut mid-line (the char before truncation notice should be \n)
  441. const beforeTruncation = result.split('\n\n... (output truncated)')[0]!;
  442. expect(beforeTruncation.endsWith('\n') || !beforeTruncation.includes('\0')).toBe(true);
  443. });
  444. describe('findSymbol disambiguation', () => {
  445. it.skipIf(!HAS_SQLITE)('should prefer exact name matches', async () => {
  446. const { ToolHandler } = await import('../src/mcp/tools');
  447. const CodeGraph = (await import('../src/index')).default;
  448. const tmpDir = createTempDir();
  449. const srcDir = path.join(tmpDir, 'src');
  450. fs.mkdirSync(srcDir, { recursive: true });
  451. fs.writeFileSync(path.join(srcDir, 'a.ts'), `
  452. export function getValue(): number { return 1; }
  453. export function getValueFromCache(): number { return 2; }
  454. `);
  455. const cg = CodeGraph.initSync(tmpDir, {
  456. config: { include: ['src/**/*.ts'], exclude: [] },
  457. });
  458. await cg.indexAll();
  459. const handler = new ToolHandler(cg);
  460. const findSymbol = (handler as any).findSymbol.bind(handler);
  461. const match = findSymbol(cg, 'getValue');
  462. expect(match).not.toBeNull();
  463. expect(match.node.name).toBe('getValue');
  464. // Should not have a disambiguation note for single exact match
  465. expect(match.note).toBe('');
  466. handler.closeAll();
  467. cg.destroy();
  468. cleanupTempDir(tmpDir);
  469. });
  470. it.skipIf(!HAS_SQLITE)('should note when multiple symbols share the same name', async () => {
  471. const { ToolHandler } = await import('../src/mcp/tools');
  472. const CodeGraph = (await import('../src/index')).default;
  473. const tmpDir = createTempDir();
  474. const srcDir = path.join(tmpDir, 'src');
  475. fs.mkdirSync(srcDir, { recursive: true });
  476. // Two files with the same function name
  477. fs.writeFileSync(path.join(srcDir, 'a.ts'), `
  478. export function handle(): void {}
  479. `);
  480. fs.writeFileSync(path.join(srcDir, 'b.ts'), `
  481. export function handle(): void {}
  482. `);
  483. const cg = CodeGraph.initSync(tmpDir, {
  484. config: { include: ['src/**/*.ts'], exclude: [] },
  485. });
  486. await cg.indexAll();
  487. const handler = new ToolHandler(cg);
  488. const findSymbol = (handler as any).findSymbol.bind(handler);
  489. const match = findSymbol(cg, 'handle');
  490. expect(match).not.toBeNull();
  491. expect(match.node.name).toBe('handle');
  492. // Should have a disambiguation note
  493. expect(match.note).toContain('2 symbols named "handle"');
  494. handler.closeAll();
  495. cg.destroy();
  496. cleanupTempDir(tmpDir);
  497. });
  498. it.skipIf(!HAS_SQLITE)('should return null when symbol is not found', async () => {
  499. const { ToolHandler } = await import('../src/mcp/tools');
  500. const CodeGraph = (await import('../src/index')).default;
  501. const tmpDir = createTempDir();
  502. const srcDir = path.join(tmpDir, 'src');
  503. fs.mkdirSync(srcDir, { recursive: true });
  504. fs.writeFileSync(path.join(srcDir, 'a.ts'), `export function foo(): void {}`);
  505. const cg = CodeGraph.initSync(tmpDir, {
  506. config: { include: ['src/**/*.ts'], exclude: [] },
  507. });
  508. await cg.indexAll();
  509. const handler = new ToolHandler(cg);
  510. const findSymbol = (handler as any).findSymbol.bind(handler);
  511. const match = findSymbol(cg, 'nonExistentSymbol');
  512. expect(match).toBeNull();
  513. handler.closeAll();
  514. cg.destroy();
  515. cleanupTempDir(tmpDir);
  516. });
  517. });
  518. });
  519. // =============================================================================
  520. // CLI uninit Command
  521. // =============================================================================
  522. describe('CLI uninit', () => {
  523. let testDir: string;
  524. beforeEach(() => {
  525. testDir = createTempDir();
  526. });
  527. afterEach(() => {
  528. cleanupTempDir(testDir);
  529. });
  530. it.skipIf(!HAS_SQLITE)('should uninitialize a project via CodeGraph.uninitialize()', async () => {
  531. const CodeGraph = (await import('../src/index')).default;
  532. // Initialize
  533. const cg = CodeGraph.initSync(testDir);
  534. expect(CodeGraph.isInitialized(testDir)).toBe(true);
  535. // Uninitialize
  536. cg.uninitialize();
  537. // .codegraph directory should be removed
  538. expect(CodeGraph.isInitialized(testDir)).toBe(false);
  539. });
  540. });
  541. // =============================================================================
  542. // Tree-sitter Version Pinning
  543. // =============================================================================
  544. describe('Tree-sitter WASM Setup', () => {
  545. it('should use web-tree-sitter and tree-sitter-wasms in dependencies', () => {
  546. const pkgPath = path.join(__dirname, '..', 'package.json');
  547. const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
  548. expect(pkg.dependencies['web-tree-sitter']).toBeDefined();
  549. expect(pkg.dependencies['tree-sitter-wasms']).toBeDefined();
  550. });
  551. it('should not have native tree-sitter in dependencies', () => {
  552. const pkgPath = path.join(__dirname, '..', 'package.json');
  553. const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
  554. expect(pkg.dependencies['tree-sitter']).toBeUndefined();
  555. expect(pkg.overrides).toBeUndefined();
  556. });
  557. });
  558. // =============================================================================
  559. // Embedder Float32Array Fix
  560. // =============================================================================
  561. describe('Float32Array Fix', () => {
  562. it('should correctly convert typed arrays (regression check)', () => {
  563. // Simulates the fix: Float32Array.from(Array.from(arr)) vs new Float32Array(arr.length)
  564. const source = new Float64Array([1.5, 2.5, 3.5, 4.5]);
  565. // The OLD buggy approach:
  566. const buggy = new Float32Array(source.length);
  567. // buggy is all zeros!
  568. expect(buggy[0]).toBe(0);
  569. expect(buggy[1]).toBe(0);
  570. // The NEW fixed approach:
  571. const fixed = Float32Array.from(Array.from(source));
  572. expect(fixed[0]).toBeCloseTo(1.5);
  573. expect(fixed[1]).toBeCloseTo(2.5);
  574. expect(fixed[2]).toBeCloseTo(3.5);
  575. expect(fixed[3]).toBeCloseTo(4.5);
  576. });
  577. });