|
| 1 | +use gittype::extractor::{ChunkType, CodeExtractor, ExtractionOptions}; |
| 2 | +use std::fs; |
| 3 | +use tempfile::TempDir; |
| 4 | + |
| 5 | +#[test] |
| 6 | +fn test_javascript_function_extraction() { |
| 7 | + let temp_dir = TempDir::new().unwrap(); |
| 8 | + let file_path = temp_dir.path().join("test.js"); |
| 9 | + |
| 10 | + let js_code = r#" |
| 11 | +function calculateSum(a, b) { |
| 12 | + return a + b; |
| 13 | +} |
| 14 | +
|
| 15 | +function greetUser(name) { |
| 16 | + return `Hello, ${name}!`; |
| 17 | +} |
| 18 | +
|
| 19 | +async function fetchUserData(userId) { |
| 20 | + const response = await fetch(`/api/users/${userId}`); |
| 21 | + return response.json(); |
| 22 | +} |
| 23 | +"#; |
| 24 | + fs::write(&file_path, js_code).unwrap(); |
| 25 | + |
| 26 | + let mut extractor = CodeExtractor::new().unwrap(); |
| 27 | + let chunks = extractor |
| 28 | + .extract_chunks(temp_dir.path(), ExtractionOptions::default()) |
| 29 | + .unwrap(); |
| 30 | + |
| 31 | + assert_eq!(chunks.len(), 3); |
| 32 | + |
| 33 | + let function_chunks: Vec<_> = chunks |
| 34 | + .iter() |
| 35 | + .filter(|c| matches!(c.chunk_type, ChunkType::Function)) |
| 36 | + .collect(); |
| 37 | + assert_eq!(function_chunks.len(), 3); |
| 38 | + |
| 39 | + let function_names: Vec<&String> = function_chunks.iter().map(|c| &c.name).collect(); |
| 40 | + assert!(function_names.contains(&&"calculateSum".to_string())); |
| 41 | + assert!(function_names.contains(&&"greetUser".to_string())); |
| 42 | + assert!(function_names.contains(&&"fetchUserData".to_string())); |
| 43 | +} |
| 44 | + |
| 45 | +#[test] |
| 46 | +fn test_javascript_arrow_function_extraction() { |
| 47 | + let temp_dir = TempDir::new().unwrap(); |
| 48 | + let file_path = temp_dir.path().join("test.js"); |
| 49 | + |
| 50 | + let js_code = r#" |
| 51 | +const add = (a, b) => a + b; |
| 52 | +
|
| 53 | +const multiply = (x, y) => { |
| 54 | + return x * y; |
| 55 | +}; |
| 56 | +
|
| 57 | +const processData = async (data) => { |
| 58 | + const processed = data.map(item => item.value); |
| 59 | + return processed; |
| 60 | +}; |
| 61 | +"#; |
| 62 | + fs::write(&file_path, js_code).unwrap(); |
| 63 | + |
| 64 | + let mut extractor = CodeExtractor::new().unwrap(); |
| 65 | + let chunks = extractor |
| 66 | + .extract_chunks(temp_dir.path(), ExtractionOptions::default()) |
| 67 | + .unwrap(); |
| 68 | + |
| 69 | + assert_eq!(chunks.len(), 3); |
| 70 | + |
| 71 | + let function_chunks: Vec<_> = chunks |
| 72 | + .iter() |
| 73 | + .filter(|c| matches!(c.chunk_type, ChunkType::Function)) |
| 74 | + .collect(); |
| 75 | + assert_eq!(function_chunks.len(), 3); |
| 76 | + |
| 77 | + let function_names: Vec<&String> = function_chunks.iter().map(|c| &c.name).collect(); |
| 78 | + assert!(function_names.contains(&&"add".to_string())); |
| 79 | + assert!(function_names.contains(&&"multiply".to_string())); |
| 80 | + assert!(function_names.contains(&&"processData".to_string())); |
| 81 | +} |
| 82 | + |
| 83 | +#[test] |
| 84 | +fn test_javascript_class_extraction() { |
| 85 | + let temp_dir = TempDir::new().unwrap(); |
| 86 | + let file_path = temp_dir.path().join("test.js"); |
| 87 | + |
| 88 | + let js_code = r#" |
| 89 | +class UserManager { |
| 90 | + constructor(apiKey) { |
| 91 | + this.apiKey = apiKey; |
| 92 | + this.users = []; |
| 93 | + } |
| 94 | + |
| 95 | + async loadUsers() { |
| 96 | + try { |
| 97 | + const response = await fetchData('/users', { |
| 98 | + headers: { 'Authorization': `Bearer ${this.apiKey}` } |
| 99 | + }); |
| 100 | + this.users = response.data; |
| 101 | + return this.users; |
| 102 | + } catch (error) { |
| 103 | + console.error('Failed to load users:', error); |
| 104 | + throw error; |
| 105 | + } |
| 106 | + } |
| 107 | + |
| 108 | + findUser = (id) => { |
| 109 | + return this.users.find(user => user.id === id); |
| 110 | + }; |
| 111 | +} |
| 112 | +
|
| 113 | +class EventEmitter { |
| 114 | + constructor() { |
| 115 | + this.events = {}; |
| 116 | + } |
| 117 | + |
| 118 | + on(eventName, callback) { |
| 119 | + if (!this.events[eventName]) { |
| 120 | + this.events[eventName] = []; |
| 121 | + } |
| 122 | + this.events[eventName].push(callback); |
| 123 | + } |
| 124 | + |
| 125 | + emit(eventName, data) { |
| 126 | + if (this.events[eventName]) { |
| 127 | + this.events[eventName].forEach(callback => callback(data)); |
| 128 | + } |
| 129 | + } |
| 130 | +} |
| 131 | +"#; |
| 132 | + fs::write(&file_path, js_code).unwrap(); |
| 133 | + |
| 134 | + let mut extractor = CodeExtractor::new().unwrap(); |
| 135 | + let chunks = extractor |
| 136 | + .extract_chunks(temp_dir.path(), ExtractionOptions::default()) |
| 137 | + .unwrap(); |
| 138 | + |
| 139 | + // Should find 2 classes + their methods |
| 140 | + let class_chunks: Vec<_> = chunks |
| 141 | + .iter() |
| 142 | + .filter(|c| matches!(c.chunk_type, ChunkType::Class)) |
| 143 | + .collect(); |
| 144 | + assert_eq!(class_chunks.len(), 2); |
| 145 | + |
| 146 | + let class_names: Vec<&String> = class_chunks.iter().map(|c| &c.name).collect(); |
| 147 | + assert!(class_names.contains(&&"UserManager".to_string())); |
| 148 | + assert!(class_names.contains(&&"EventEmitter".to_string())); |
| 149 | +} |
| 150 | + |
| 151 | +#[test] |
| 152 | +fn test_javascript_export_extraction() { |
| 153 | + let temp_dir = TempDir::new().unwrap(); |
| 154 | + let file_path = temp_dir.path().join("test.js"); |
| 155 | + |
| 156 | + let js_code = r#" |
| 157 | +export function validateEmail(email) { |
| 158 | + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; |
| 159 | + return emailRegex.test(email); |
| 160 | +} |
| 161 | +
|
| 162 | +export class ApiClient { |
| 163 | + constructor(baseUrl) { |
| 164 | + this.baseUrl = baseUrl; |
| 165 | + } |
| 166 | + |
| 167 | + async get(endpoint) { |
| 168 | + const response = await fetch(`${this.baseUrl}${endpoint}`); |
| 169 | + return response.json(); |
| 170 | + } |
| 171 | +} |
| 172 | +
|
| 173 | +export const config = { |
| 174 | + apiUrl: process.env.API_URL || 'http://localhost:3000', |
| 175 | + timeout: 5000 |
| 176 | +}; |
| 177 | +
|
| 178 | +export default class UserService { |
| 179 | + constructor(apiClient) { |
| 180 | + this.apiClient = apiClient; |
| 181 | + } |
| 182 | + |
| 183 | + async getUser(id) { |
| 184 | + return this.apiClient.get(`/users/${id}`); |
| 185 | + } |
| 186 | +} |
| 187 | +"#; |
| 188 | + fs::write(&file_path, js_code).unwrap(); |
| 189 | + |
| 190 | + let mut extractor = CodeExtractor::new().unwrap(); |
| 191 | + let chunks = extractor |
| 192 | + .extract_chunks(temp_dir.path(), ExtractionOptions::default()) |
| 193 | + .unwrap(); |
| 194 | + |
| 195 | + // Should find exported functions and classes |
| 196 | + let function_chunks: Vec<_> = chunks |
| 197 | + .iter() |
| 198 | + .filter(|c| matches!(c.chunk_type, ChunkType::Function)) |
| 199 | + .collect(); |
| 200 | + let class_chunks: Vec<_> = chunks |
| 201 | + .iter() |
| 202 | + .filter(|c| matches!(c.chunk_type, ChunkType::Class)) |
| 203 | + .collect(); |
| 204 | + |
| 205 | + assert!(!function_chunks.is_empty()); |
| 206 | + assert!(!class_chunks.is_empty()); |
| 207 | + |
| 208 | + let all_names: Vec<&String> = chunks.iter().map(|c| &c.name).collect(); |
| 209 | + assert!(all_names.contains(&&"validateEmail".to_string())); |
| 210 | + assert!(all_names.contains(&&"ApiClient".to_string())); |
| 211 | + assert!(all_names.contains(&&"UserService".to_string())); |
| 212 | +} |
| 213 | + |
| 214 | +#[test] |
| 215 | +fn test_javascript_object_method_extraction() { |
| 216 | + let temp_dir = TempDir::new().unwrap(); |
| 217 | + let file_path = temp_dir.path().join("test.js"); |
| 218 | + |
| 219 | + let js_code = r#" |
| 220 | +const utils = { |
| 221 | + formatDate: (date) => { |
| 222 | + return date.toLocaleDateString(); |
| 223 | + }, |
| 224 | + |
| 225 | + calculateAge: function(birthDate) { |
| 226 | + const today = new Date(); |
| 227 | + return today.getFullYear() - birthDate.getFullYear(); |
| 228 | + } |
| 229 | +}; |
| 230 | +
|
| 231 | +const eventHandlers = { |
| 232 | + handleClick: (event) => { |
| 233 | + console.log('Button clicked:', event.target); |
| 234 | + }, |
| 235 | + |
| 236 | + handleSubmit: async function(formData) { |
| 237 | + try { |
| 238 | + const response = await fetch('/api/submit', { |
| 239 | + method: 'POST', |
| 240 | + body: formData |
| 241 | + }); |
| 242 | + return response.json(); |
| 243 | + } catch (error) { |
| 244 | + console.error('Submit failed:', error); |
| 245 | + } |
| 246 | + } |
| 247 | +}; |
| 248 | +"#; |
| 249 | + fs::write(&file_path, js_code).unwrap(); |
| 250 | + |
| 251 | + let mut extractor = CodeExtractor::new().unwrap(); |
| 252 | + let chunks = extractor |
| 253 | + .extract_chunks(temp_dir.path(), ExtractionOptions::default()) |
| 254 | + .unwrap(); |
| 255 | + |
| 256 | + // Should find method assignments |
| 257 | + let method_chunks: Vec<_> = chunks |
| 258 | + .iter() |
| 259 | + .filter(|c| matches!(c.chunk_type, ChunkType::Method)) |
| 260 | + .collect(); |
| 261 | + |
| 262 | + if !method_chunks.is_empty() { |
| 263 | + let method_names: Vec<&String> = method_chunks.iter().map(|c| &c.name).collect(); |
| 264 | + // These might be detected as methods if the parser can handle object property assignments |
| 265 | + println!("Found methods: {:?}", method_names); |
| 266 | + } |
| 267 | +} |
| 268 | + |
| 269 | +#[test] |
| 270 | +fn test_javascript_mixed_patterns() { |
| 271 | + let temp_dir = TempDir::new().unwrap(); |
| 272 | + let file_path = temp_dir.path().join("test.js"); |
| 273 | + |
| 274 | + let js_code = r#" |
| 275 | +import { fetchData } from './api.js'; |
| 276 | +
|
| 277 | +class UserManager { |
| 278 | + constructor(apiKey) { |
| 279 | + this.apiKey = apiKey; |
| 280 | + this.users = []; |
| 281 | + } |
| 282 | + |
| 283 | + async loadUsers() { |
| 284 | + try { |
| 285 | + const response = await fetchData('/users', { |
| 286 | + headers: { 'Authorization': `Bearer ${this.apiKey}` } |
| 287 | + }); |
| 288 | + this.users = response.data; |
| 289 | + return this.users; |
| 290 | + } catch (error) { |
| 291 | + console.error('Failed to load users:', error); |
| 292 | + throw error; |
| 293 | + } |
| 294 | + } |
| 295 | + |
| 296 | + findUser = (id) => { |
| 297 | + return this.users.find(user => user.id === id); |
| 298 | + }; |
| 299 | +} |
| 300 | +
|
| 301 | +function processUsers(users) { |
| 302 | + return users.map(user => ({ |
| 303 | + ...user, |
| 304 | + displayName: `${user.firstName} ${user.lastName}` |
| 305 | + })); |
| 306 | +} |
| 307 | +
|
| 308 | +const filterActiveUsers = (users) => { |
| 309 | + return users.filter(user => user.isActive); |
| 310 | +}; |
| 311 | +
|
| 312 | +const userService = { |
| 313 | + async createUser(userData) { |
| 314 | + const response = await fetch('/api/users', { |
| 315 | + method: 'POST', |
| 316 | + headers: { 'Content-Type': 'application/json' }, |
| 317 | + body: JSON.stringify(userData) |
| 318 | + }); |
| 319 | + return response.json(); |
| 320 | + } |
| 321 | +}; |
| 322 | +
|
| 323 | +export default UserManager; |
| 324 | +"#; |
| 325 | + fs::write(&file_path, js_code).unwrap(); |
| 326 | + |
| 327 | + let mut extractor = CodeExtractor::new().unwrap(); |
| 328 | + let chunks = extractor |
| 329 | + .extract_chunks(temp_dir.path(), ExtractionOptions::default()) |
| 330 | + .unwrap(); |
| 331 | + |
| 332 | + // Should find at least the class and functions |
| 333 | + assert!(!chunks.is_empty()); |
| 334 | + |
| 335 | + let class_count = chunks |
| 336 | + .iter() |
| 337 | + .filter(|c| matches!(c.chunk_type, ChunkType::Class)) |
| 338 | + .count(); |
| 339 | + let function_count = chunks |
| 340 | + .iter() |
| 341 | + .filter(|c| matches!(c.chunk_type, ChunkType::Function)) |
| 342 | + .count(); |
| 343 | + |
| 344 | + assert!(class_count >= 1, "Should find at least 1 class"); |
| 345 | + assert!(function_count >= 2, "Should find at least 2 functions"); |
| 346 | + |
| 347 | + let chunk_names: Vec<&String> = chunks.iter().map(|c| &c.name).collect(); |
| 348 | + assert!(chunk_names.contains(&&"UserManager".to_string())); |
| 349 | + assert!(chunk_names.contains(&&"processUsers".to_string())); |
| 350 | + assert!(chunk_names.contains(&&"filterActiveUsers".to_string())); |
| 351 | +} |
0 commit comments