pr19-improvements.test.ts 23 KB

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