extraction.test.ts 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765
  1. /**
  2. * Extraction Tests
  3. *
  4. * Tests for the tree-sitter extraction system.
  5. */
  6. import { describe, it, expect, beforeEach, afterEach } from 'vitest';
  7. import * as fs from 'fs';
  8. import * as path from 'path';
  9. import * as os from 'os';
  10. import { CodeGraph } from '../src';
  11. import { extractFromSource } from '../src/extraction';
  12. import { detectLanguage, isLanguageSupported, getSupportedLanguages } from '../src/extraction/grammars';
  13. // Create a temporary directory for each test
  14. function createTempDir(): string {
  15. return fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-test-'));
  16. }
  17. // Clean up temporary directory
  18. function cleanupTempDir(dir: string): void {
  19. if (fs.existsSync(dir)) {
  20. fs.rmSync(dir, { recursive: true, force: true });
  21. }
  22. }
  23. describe('Language Detection', () => {
  24. it('should detect TypeScript files', () => {
  25. expect(detectLanguage('src/index.ts')).toBe('typescript');
  26. expect(detectLanguage('components/Button.tsx')).toBe('tsx');
  27. });
  28. it('should detect JavaScript files', () => {
  29. expect(detectLanguage('index.js')).toBe('javascript');
  30. expect(detectLanguage('App.jsx')).toBe('jsx');
  31. expect(detectLanguage('config.mjs')).toBe('javascript');
  32. });
  33. it('should detect Python files', () => {
  34. expect(detectLanguage('main.py')).toBe('python');
  35. });
  36. it('should detect Go files', () => {
  37. expect(detectLanguage('main.go')).toBe('go');
  38. });
  39. it('should detect Rust files', () => {
  40. expect(detectLanguage('lib.rs')).toBe('rust');
  41. });
  42. it('should detect Java files', () => {
  43. expect(detectLanguage('Main.java')).toBe('java');
  44. });
  45. it('should detect C files', () => {
  46. expect(detectLanguage('main.c')).toBe('c');
  47. expect(detectLanguage('utils.h')).toBe('c');
  48. });
  49. it('should detect C++ files', () => {
  50. expect(detectLanguage('main.cpp')).toBe('cpp');
  51. expect(detectLanguage('class.hpp')).toBe('cpp');
  52. });
  53. it('should detect C# files', () => {
  54. expect(detectLanguage('Program.cs')).toBe('csharp');
  55. });
  56. it('should detect PHP files', () => {
  57. expect(detectLanguage('index.php')).toBe('php');
  58. });
  59. it('should detect Ruby files', () => {
  60. expect(detectLanguage('app.rb')).toBe('ruby');
  61. });
  62. it('should detect Swift files', () => {
  63. expect(detectLanguage('ViewController.swift')).toBe('swift');
  64. });
  65. it('should detect Kotlin files', () => {
  66. expect(detectLanguage('MainActivity.kt')).toBe('kotlin');
  67. expect(detectLanguage('build.gradle.kts')).toBe('kotlin');
  68. });
  69. it('should return unknown for unsupported extensions', () => {
  70. expect(detectLanguage('styles.css')).toBe('unknown');
  71. expect(detectLanguage('data.json')).toBe('unknown');
  72. });
  73. });
  74. describe('Language Support', () => {
  75. it('should report supported languages', () => {
  76. expect(isLanguageSupported('typescript')).toBe(true);
  77. expect(isLanguageSupported('python')).toBe(true);
  78. expect(isLanguageSupported('go')).toBe(true);
  79. expect(isLanguageSupported('unknown')).toBe(false);
  80. });
  81. it('should list all supported languages', () => {
  82. const languages = getSupportedLanguages();
  83. expect(languages).toContain('typescript');
  84. expect(languages).toContain('javascript');
  85. expect(languages).toContain('python');
  86. expect(languages).toContain('go');
  87. expect(languages).toContain('rust');
  88. expect(languages).toContain('java');
  89. expect(languages).toContain('csharp');
  90. expect(languages).toContain('php');
  91. expect(languages).toContain('ruby');
  92. expect(languages).toContain('swift');
  93. expect(languages).toContain('kotlin');
  94. });
  95. });
  96. describe('TypeScript Extraction', () => {
  97. it('should extract function declarations', () => {
  98. const code = `
  99. export function processPayment(amount: number): Promise<Receipt> {
  100. return stripe.charge(amount);
  101. }
  102. `;
  103. const result = extractFromSource('payment.ts', code);
  104. expect(result.nodes).toHaveLength(1);
  105. expect(result.nodes[0]).toMatchObject({
  106. kind: 'function',
  107. name: 'processPayment',
  108. language: 'typescript',
  109. isExported: true,
  110. });
  111. expect(result.nodes[0]?.signature).toContain('amount: number');
  112. });
  113. it('should extract class declarations', () => {
  114. const code = `
  115. export class PaymentService {
  116. private stripe: StripeClient;
  117. constructor(apiKey: string) {
  118. this.stripe = new StripeClient(apiKey);
  119. }
  120. async charge(amount: number): Promise<Receipt> {
  121. return this.stripe.charge(amount);
  122. }
  123. }
  124. `;
  125. const result = extractFromSource('service.ts', code);
  126. const classNode = result.nodes.find((n) => n.kind === 'class');
  127. const methodNodes = result.nodes.filter((n) => n.kind === 'method');
  128. expect(classNode).toBeDefined();
  129. expect(classNode?.name).toBe('PaymentService');
  130. expect(classNode?.isExported).toBe(true);
  131. expect(methodNodes.length).toBeGreaterThanOrEqual(1);
  132. const chargeMethod = methodNodes.find((m) => m.name === 'charge');
  133. expect(chargeMethod).toBeDefined();
  134. });
  135. it('should extract interfaces', () => {
  136. const code = `
  137. export interface User {
  138. id: string;
  139. name: string;
  140. email: string;
  141. }
  142. `;
  143. const result = extractFromSource('types.ts', code);
  144. expect(result.nodes).toHaveLength(1);
  145. expect(result.nodes[0]).toMatchObject({
  146. kind: 'interface',
  147. name: 'User',
  148. isExported: true,
  149. });
  150. });
  151. it('should track function calls', () => {
  152. const code = `
  153. function main() {
  154. const result = processData();
  155. console.log(result);
  156. }
  157. `;
  158. const result = extractFromSource('main.ts', code);
  159. expect(result.unresolvedReferences.length).toBeGreaterThan(0);
  160. const calls = result.unresolvedReferences.filter((r) => r.referenceKind === 'calls');
  161. expect(calls.some((c) => c.referenceName === 'processData')).toBe(true);
  162. });
  163. });
  164. describe('Arrow Function Export Extraction', () => {
  165. it('should extract exported arrow functions assigned to const', () => {
  166. const code = `
  167. export const useAuth = (): AuthContextValue => {
  168. return useContext(AuthContext);
  169. };
  170. `;
  171. const result = extractFromSource('hooks.ts', code);
  172. expect(result.nodes).toHaveLength(1);
  173. expect(result.nodes[0]).toMatchObject({
  174. kind: 'function',
  175. name: 'useAuth',
  176. isExported: true,
  177. });
  178. });
  179. it('should extract exported function expressions assigned to const', () => {
  180. const code = `
  181. export const processData = function(input: string): string {
  182. return input.trim();
  183. };
  184. `;
  185. const result = extractFromSource('utils.ts', code);
  186. expect(result.nodes).toHaveLength(1);
  187. expect(result.nodes[0]).toMatchObject({
  188. kind: 'function',
  189. name: 'processData',
  190. isExported: true,
  191. });
  192. });
  193. it('should not extract non-exported arrow functions as exported', () => {
  194. const code = `
  195. const internalHelper = () => {
  196. return 42;
  197. };
  198. `;
  199. const result = extractFromSource('internal.ts', code);
  200. const helperNode = result.nodes.find((n) => n.name === 'internalHelper');
  201. expect(helperNode).toBeDefined();
  202. expect(helperNode?.isExported).toBeFalsy();
  203. });
  204. it('should still skip truly anonymous arrow functions', () => {
  205. const code = `
  206. const items = [1, 2, 3].map((x) => x * 2);
  207. `;
  208. const result = extractFromSource('anon.ts', code);
  209. // The inline arrow function passed to .map() has no variable_declarator parent
  210. // and should remain anonymous (skipped)
  211. const anonFunctions = result.nodes.filter(
  212. (n) => n.kind === 'function' && n.name === '<anonymous>'
  213. );
  214. expect(anonFunctions).toHaveLength(0);
  215. });
  216. it('should extract multiple exported arrow functions from the same file', () => {
  217. const code = `
  218. export const add = (a: number, b: number): number => a + b;
  219. export const subtract = (a: number, b: number): number => a - b;
  220. const internal = () => 'not exported';
  221. `;
  222. const result = extractFromSource('math.ts', code);
  223. const exported = result.nodes.filter((n) => n.kind === 'function' && n.isExported);
  224. expect(exported).toHaveLength(2);
  225. expect(exported.map((n) => n.name).sort()).toEqual(['add', 'subtract']);
  226. const internalNode = result.nodes.find((n) => n.name === 'internal');
  227. expect(internalNode).toBeDefined();
  228. expect(internalNode?.isExported).toBeFalsy();
  229. });
  230. it('should extract arrow functions in JavaScript files', () => {
  231. const code = `
  232. export const fetchData = async () => {
  233. const response = await fetch('/api/data');
  234. return response.json();
  235. };
  236. `;
  237. const result = extractFromSource('api.js', code);
  238. expect(result.nodes).toHaveLength(1);
  239. expect(result.nodes[0]).toMatchObject({
  240. kind: 'function',
  241. name: 'fetchData',
  242. isExported: true,
  243. });
  244. });
  245. });
  246. describe('Python Extraction', () => {
  247. it('should extract function definitions', () => {
  248. const code = `
  249. def calculate_total(items: list, tax_rate: float) -> float:
  250. """Calculate total with tax."""
  251. subtotal = sum(item.price for item in items)
  252. return subtotal * (1 + tax_rate)
  253. `;
  254. const result = extractFromSource('calc.py', code);
  255. expect(result.nodes).toHaveLength(1);
  256. expect(result.nodes[0]).toMatchObject({
  257. kind: 'function',
  258. name: 'calculate_total',
  259. language: 'python',
  260. });
  261. });
  262. it('should extract class definitions', () => {
  263. const code = `
  264. class UserService:
  265. """Service for managing users."""
  266. def __init__(self, db):
  267. self.db = db
  268. def get_user(self, user_id: str) -> User:
  269. return self.db.find_user(user_id)
  270. `;
  271. const result = extractFromSource('service.py', code);
  272. const classNode = result.nodes.find((n) => n.kind === 'class');
  273. expect(classNode).toBeDefined();
  274. expect(classNode?.name).toBe('UserService');
  275. });
  276. });
  277. describe('Go Extraction', () => {
  278. it('should extract function declarations', () => {
  279. const code = `
  280. package main
  281. func ProcessOrder(order Order) (Receipt, error) {
  282. // Process the order
  283. return Receipt{}, nil
  284. }
  285. `;
  286. const result = extractFromSource('main.go', code);
  287. const funcNode = result.nodes.find((n) => n.kind === 'function');
  288. expect(funcNode).toBeDefined();
  289. expect(funcNode?.name).toBe('ProcessOrder');
  290. });
  291. it('should extract method declarations', () => {
  292. const code = `
  293. package main
  294. type Service struct {
  295. db *Database
  296. }
  297. func (s *Service) GetUser(id string) (*User, error) {
  298. return s.db.FindUser(id)
  299. }
  300. `;
  301. const result = extractFromSource('service.go', code);
  302. const methodNode = result.nodes.find((n) => n.kind === 'method');
  303. expect(methodNode).toBeDefined();
  304. expect(methodNode?.name).toBe('GetUser');
  305. });
  306. });
  307. describe('Rust Extraction', () => {
  308. it('should extract function declarations', () => {
  309. const code = `
  310. pub fn process_data(input: &str) -> Result<Output, Error> {
  311. // Process data
  312. Ok(Output::new())
  313. }
  314. `;
  315. const result = extractFromSource('lib.rs', code);
  316. const funcNode = result.nodes.find((n) => n.kind === 'function');
  317. expect(funcNode).toBeDefined();
  318. expect(funcNode?.name).toBe('process_data');
  319. expect(funcNode?.visibility).toBe('public');
  320. });
  321. it('should extract struct declarations', () => {
  322. const code = `
  323. pub struct User {
  324. pub id: String,
  325. pub name: String,
  326. email: String,
  327. }
  328. `;
  329. const result = extractFromSource('models.rs', code);
  330. const structNode = result.nodes.find((n) => n.kind === 'struct');
  331. expect(structNode).toBeDefined();
  332. expect(structNode?.name).toBe('User');
  333. });
  334. it('should extract trait declarations', () => {
  335. const code = `
  336. pub trait Repository {
  337. fn find(&self, id: &str) -> Option<Entity>;
  338. fn save(&mut self, entity: Entity) -> Result<(), Error>;
  339. }
  340. `;
  341. const result = extractFromSource('traits.rs', code);
  342. const traitNode = result.nodes.find((n) => n.kind === 'trait');
  343. expect(traitNode).toBeDefined();
  344. expect(traitNode?.name).toBe('Repository');
  345. });
  346. });
  347. describe('Java Extraction', () => {
  348. it('should extract class declarations', () => {
  349. const code = `
  350. public class UserService {
  351. private final UserRepository repository;
  352. public UserService(UserRepository repository) {
  353. this.repository = repository;
  354. }
  355. public User getUser(String id) {
  356. return repository.findById(id);
  357. }
  358. }
  359. `;
  360. const result = extractFromSource('UserService.java', code);
  361. const classNode = result.nodes.find((n) => n.kind === 'class');
  362. expect(classNode).toBeDefined();
  363. expect(classNode?.name).toBe('UserService');
  364. expect(classNode?.visibility).toBe('public');
  365. });
  366. it('should extract method declarations', () => {
  367. const code = `
  368. public class Calculator {
  369. public static int add(int a, int b) {
  370. return a + b;
  371. }
  372. }
  373. `;
  374. const result = extractFromSource('Calculator.java', code);
  375. const methodNode = result.nodes.find((n) => n.kind === 'method' && n.name === 'add');
  376. expect(methodNode).toBeDefined();
  377. expect(methodNode?.isStatic).toBe(true);
  378. });
  379. });
  380. describe('C# Extraction', () => {
  381. it('should extract class declarations', () => {
  382. const code = `
  383. public class OrderService
  384. {
  385. private readonly IOrderRepository _repository;
  386. public OrderService(IOrderRepository repository)
  387. {
  388. _repository = repository;
  389. }
  390. public async Task<Order> GetOrderAsync(string id)
  391. {
  392. return await _repository.FindByIdAsync(id);
  393. }
  394. }
  395. `;
  396. const result = extractFromSource('OrderService.cs', code);
  397. const classNode = result.nodes.find((n) => n.kind === 'class');
  398. expect(classNode).toBeDefined();
  399. expect(classNode?.name).toBe('OrderService');
  400. expect(classNode?.visibility).toBe('public');
  401. });
  402. });
  403. describe('PHP Extraction', () => {
  404. it('should extract class declarations', () => {
  405. const code = `<?php
  406. class UserController
  407. {
  408. private UserService $userService;
  409. public function __construct(UserService $userService)
  410. {
  411. $this->userService = $userService;
  412. }
  413. public function show(string $id): User
  414. {
  415. return $this->userService->find($id);
  416. }
  417. }
  418. `;
  419. const result = extractFromSource('UserController.php', code);
  420. const classNode = result.nodes.find((n) => n.kind === 'class');
  421. expect(classNode).toBeDefined();
  422. expect(classNode?.name).toBe('UserController');
  423. });
  424. });
  425. describe('Swift Extraction', () => {
  426. it('should extract class declarations', () => {
  427. const code = `
  428. public class NetworkManager {
  429. private let session: URLSession
  430. public init(session: URLSession = .shared) {
  431. self.session = session
  432. }
  433. public func fetchData(from url: URL) async throws -> Data {
  434. let (data, _) = try await session.data(from: url)
  435. return data
  436. }
  437. }
  438. `;
  439. const result = extractFromSource('NetworkManager.swift', code);
  440. const classNode = result.nodes.find((n) => n.kind === 'class');
  441. expect(classNode).toBeDefined();
  442. expect(classNode?.name).toBe('NetworkManager');
  443. });
  444. it('should extract function declarations', () => {
  445. const code = `
  446. func calculateSum(_ numbers: [Int]) -> Int {
  447. return numbers.reduce(0, +)
  448. }
  449. public func formatCurrency(amount: Double) -> String {
  450. return String(format: "$%.2f", amount)
  451. }
  452. `;
  453. const result = extractFromSource('utils.swift', code);
  454. const functions = result.nodes.filter((n) => n.kind === 'function');
  455. expect(functions.length).toBeGreaterThanOrEqual(1);
  456. });
  457. it('should extract struct declarations', () => {
  458. const code = `
  459. public struct User {
  460. let id: UUID
  461. var name: String
  462. var email: String
  463. func displayName() -> String {
  464. return name
  465. }
  466. }
  467. `;
  468. const result = extractFromSource('User.swift', code);
  469. const structNode = result.nodes.find((n) => n.kind === 'struct');
  470. expect(structNode).toBeDefined();
  471. expect(structNode?.name).toBe('User');
  472. });
  473. it('should extract protocol declarations', () => {
  474. const code = `
  475. public protocol Repository {
  476. associatedtype Entity
  477. func find(id: String) async throws -> Entity?
  478. func save(_ entity: Entity) async throws
  479. }
  480. `;
  481. const result = extractFromSource('Repository.swift', code);
  482. const protocolNode = result.nodes.find((n) => n.kind === 'interface');
  483. expect(protocolNode).toBeDefined();
  484. expect(protocolNode?.name).toBe('Repository');
  485. });
  486. });
  487. describe('Kotlin Extraction', () => {
  488. it('should extract class declarations', () => {
  489. const code = `
  490. class UserRepository(private val database: Database) {
  491. fun findById(id: String): User? {
  492. return database.query("SELECT * FROM users WHERE id = ?", id)
  493. }
  494. suspend fun save(user: User) {
  495. database.insert(user)
  496. }
  497. }
  498. `;
  499. const result = extractFromSource('UserRepository.kt', code);
  500. const classNode = result.nodes.find((n) => n.kind === 'class');
  501. expect(classNode).toBeDefined();
  502. expect(classNode?.name).toBe('UserRepository');
  503. });
  504. it('should extract function declarations', () => {
  505. const code = `
  506. fun calculateTotal(items: List<Item>): Double {
  507. return items.sumOf { it.price }
  508. }
  509. suspend fun fetchUserData(userId: String): User {
  510. return api.getUser(userId)
  511. }
  512. `;
  513. const result = extractFromSource('utils.kt', code);
  514. const functions = result.nodes.filter((n) => n.kind === 'function');
  515. expect(functions.length).toBeGreaterThanOrEqual(1);
  516. });
  517. it('should detect suspend functions as async', () => {
  518. const code = `
  519. suspend fun loadData(): List<String> {
  520. delay(1000)
  521. return listOf("a", "b", "c")
  522. }
  523. `;
  524. const result = extractFromSource('loader.kt', code);
  525. const funcNode = result.nodes.find((n) => n.kind === 'function');
  526. expect(funcNode).toBeDefined();
  527. expect(funcNode?.isAsync).toBe(true);
  528. });
  529. });
  530. describe('Full Indexing', () => {
  531. let tempDir: string;
  532. beforeEach(() => {
  533. tempDir = createTempDir();
  534. });
  535. afterEach(() => {
  536. cleanupTempDir(tempDir);
  537. });
  538. it('should index a TypeScript file', async () => {
  539. // Create test file
  540. const srcDir = path.join(tempDir, 'src');
  541. fs.mkdirSync(srcDir);
  542. fs.writeFileSync(
  543. path.join(srcDir, 'utils.ts'),
  544. `
  545. export function add(a: number, b: number): number {
  546. return a + b;
  547. }
  548. export function multiply(a: number, b: number): number {
  549. return a * b;
  550. }
  551. `
  552. );
  553. // Initialize and index
  554. const cg = CodeGraph.initSync(tempDir);
  555. const result = await cg.indexAll();
  556. expect(result.success).toBe(true);
  557. expect(result.filesIndexed).toBe(1);
  558. expect(result.nodesCreated).toBeGreaterThanOrEqual(2);
  559. // Check nodes were stored
  560. const nodes = cg.getNodesInFile('src/utils.ts');
  561. expect(nodes.length).toBeGreaterThanOrEqual(2);
  562. const addFunc = nodes.find((n) => n.name === 'add');
  563. expect(addFunc).toBeDefined();
  564. expect(addFunc?.kind).toBe('function');
  565. cg.close();
  566. });
  567. it('should index multiple files', async () => {
  568. // Create test files
  569. const srcDir = path.join(tempDir, 'src');
  570. fs.mkdirSync(srcDir);
  571. fs.writeFileSync(
  572. path.join(srcDir, 'math.ts'),
  573. `export function add(a: number, b: number) { return a + b; }`
  574. );
  575. fs.writeFileSync(
  576. path.join(srcDir, 'string.ts'),
  577. `export function capitalize(s: string) { return s.toUpperCase(); }`
  578. );
  579. // Initialize and index
  580. const cg = CodeGraph.initSync(tempDir);
  581. const result = await cg.indexAll();
  582. expect(result.success).toBe(true);
  583. expect(result.filesIndexed).toBe(2);
  584. const files = cg.getFiles();
  585. expect(files.length).toBe(2);
  586. cg.close();
  587. });
  588. it('should track file hashes for incremental updates', async () => {
  589. // Create initial file
  590. const srcDir = path.join(tempDir, 'src');
  591. fs.mkdirSync(srcDir);
  592. fs.writeFileSync(path.join(srcDir, 'main.ts'), `export const x = 1;`);
  593. // Initialize and index
  594. const cg = CodeGraph.initSync(tempDir);
  595. await cg.indexAll();
  596. // Check file is tracked
  597. const file = cg.getFile('src/main.ts');
  598. expect(file).toBeDefined();
  599. expect(file?.contentHash).toBeDefined();
  600. // Modify file
  601. fs.writeFileSync(path.join(srcDir, 'main.ts'), `export const x = 2;`);
  602. // Check for changes
  603. const changes = cg.getChangedFiles();
  604. expect(changes.modified).toContain('src/main.ts');
  605. cg.close();
  606. });
  607. it('should sync and detect changes', async () => {
  608. // Create initial file
  609. const srcDir = path.join(tempDir, 'src');
  610. fs.mkdirSync(srcDir);
  611. fs.writeFileSync(
  612. path.join(srcDir, 'main.ts'),
  613. `export function original() { return 1; }`
  614. );
  615. // Initialize and index
  616. const cg = CodeGraph.initSync(tempDir);
  617. await cg.indexAll();
  618. const initialNodes = cg.getNodesInFile('src/main.ts');
  619. expect(initialNodes.some((n) => n.name === 'original')).toBe(true);
  620. // Modify file
  621. fs.writeFileSync(
  622. path.join(srcDir, 'main.ts'),
  623. `export function updated() { return 2; }`
  624. );
  625. // Sync
  626. const syncResult = await cg.sync();
  627. expect(syncResult.filesModified).toBe(1);
  628. // Check nodes were updated
  629. const updatedNodes = cg.getNodesInFile('src/main.ts');
  630. expect(updatedNodes.some((n) => n.name === 'updated')).toBe(true);
  631. expect(updatedNodes.some((n) => n.name === 'original')).toBe(false);
  632. cg.close();
  633. });
  634. });