diff --git a/src-tauri/src/commands/search.rs b/src-tauri/src/commands/search.rs index dc981bb..7a2d3c6 100644 --- a/src-tauri/src/commands/search.rs +++ b/src-tauri/src/commands/search.rs @@ -52,12 +52,8 @@ pub struct SearchResult { /// /// This is a naive implementation that reads entire files into memory. /// For production use, consider using streaming/buffered reads or the `grep-searcher` crate. -#[tauri::command] -pub async fn search_files( - query: String, - state: State<'_, SessionState>, -) -> Result, String> { - let root = get_project_root(&state)?; +/// Search files implementation (pure function for testing) +pub async fn search_files_impl(query: String, root: PathBuf) -> Result, String> { let root_clone = root.clone(); // Run computationally expensive search on a blocking thread @@ -105,6 +101,15 @@ pub async fn search_files( Ok(results) } +#[tauri::command] +pub async fn search_files( + query: String, + state: State<'_, SessionState>, +) -> Result, String> { + let root = get_project_root(&state)?; + search_files_impl(query, root).await +} + // ----------------------------------------------------------------------------- // Tests // ----------------------------------------------------------------------------- @@ -126,58 +131,11 @@ mod tests { } } - /// Helper to call search_files logic directly for testing - async fn call_search_files( - query: String, - state: &SessionState, - ) -> Result, String> { - let root = state.get_project_root()?; - let root_clone = root.clone(); - - let results = tauri::async_runtime::spawn_blocking(move || { - let mut matches = Vec::new(); - let walker = WalkBuilder::new(&root_clone).git_ignore(true).build(); - - for result in walker { - match result { - Ok(entry) => { - if !entry.file_type().map(|ft| ft.is_file()).unwrap_or(false) { - continue; - } - - let path = entry.path(); - if let Ok(content) = fs::read_to_string(path) { - if !content.contains(&query) { - continue; - } - let relative = path - .strip_prefix(&root_clone) - .unwrap_or(path) - .to_string_lossy() - .to_string(); - - matches.push(SearchResult { - path: relative, - matches: 1, - }); - } - } - Err(err) => eprintln!("Error walking dir: {}", err), - } - } - matches - }) - .await - .map_err(|e| format!("Search task failed: {}", e))?; - - Ok(results) - } - #[tokio::test] async fn test_search_files_no_project_open() { let state = create_test_state(None); - let result = call_search_files("test".to_string(), &state).await; + let result = state.get_project_root(); assert!(result.is_err()); assert_eq!(result.unwrap_err(), "No project is currently open."); @@ -191,7 +149,8 @@ mod tests { let state = create_test_state(Some(temp_dir.path().to_path_buf())); - let results = call_search_files("test".to_string(), &state).await.unwrap(); + let root = state.get_project_root().unwrap(); + let results = search_files_impl("test".to_string(), root).await.unwrap(); assert_eq!(results.len(), 1); assert_eq!(results[0].path, "test.txt"); @@ -209,9 +168,8 @@ mod tests { let state = create_test_state(Some(temp_dir.path().to_path_buf())); - let results = call_search_files("hello".to_string(), &state) - .await - .unwrap(); + let root = state.get_project_root().unwrap(); + let results = search_files_impl("hello".to_string(), root).await.unwrap(); assert_eq!(results.len(), 2); let paths: Vec<&str> = results.iter().map(|r| r.path.as_str()).collect(); @@ -226,7 +184,8 @@ mod tests { let state = create_test_state(Some(temp_dir.path().to_path_buf())); - let results = call_search_files("nonexistent".to_string(), &state) + let root = state.get_project_root().unwrap(); + let results = search_files_impl("nonexistent".to_string(), root) .await .unwrap(); @@ -241,15 +200,14 @@ mod tests { let state = create_test_state(Some(temp_dir.path().to_path_buf())); // Search for lowercase - should not match - let results = call_search_files("hello".to_string(), &state) + let root = state.get_project_root().unwrap(); + let results = search_files_impl("hello".to_string(), root.clone()) .await .unwrap(); assert_eq!(results.len(), 0); // Search for correct case - should match - let results = call_search_files("Hello".to_string(), &state) - .await - .unwrap(); + let results = search_files_impl("Hello".to_string(), root).await.unwrap(); assert_eq!(results.len(), 1); } @@ -266,9 +224,8 @@ mod tests { let state = create_test_state(Some(temp_dir.path().to_path_buf())); - let results = call_search_files("match".to_string(), &state) - .await - .unwrap(); + let root = state.get_project_root().unwrap(); + let results = search_files_impl("match".to_string(), root).await.unwrap(); assert_eq!(results.len(), 2); let paths: Vec<&str> = results.iter().map(|r| r.path.as_str()).collect(); @@ -296,7 +253,8 @@ mod tests { let state = create_test_state(Some(temp_dir.path().to_path_buf())); - let results = call_search_files("searchterm".to_string(), &state) + let root = state.get_project_root().unwrap(); + let results = search_files_impl("searchterm".to_string(), root) .await .unwrap(); @@ -324,7 +282,8 @@ mod tests { let state = create_test_state(Some(temp_dir.path().to_path_buf())); - let results = call_search_files("searchable".to_string(), &state) + let root = state.get_project_root().unwrap(); + let results = search_files_impl("searchable".to_string(), root) .await .unwrap(); @@ -340,26 +299,10 @@ mod tests { let state = create_test_state(Some(temp_dir.path().to_path_buf())); - let results = call_search_files("".to_string(), &state).await.unwrap(); + let root = state.get_project_root().unwrap(); + let results = search_files_impl("".to_string(), root).await.unwrap(); // Empty string is contained in all strings, so should match assert_eq!(results.len(), 1); } - - #[tokio::test] - async fn test_get_project_root_helper() { - // Test with no root set - let state = create_test_state(None); - let result = state.get_project_root(); - assert!(result.is_err()); - assert_eq!(result.unwrap_err(), "No project is currently open."); - - // Test with root set - let temp_dir = TempDir::new().unwrap(); - let path = temp_dir.path().to_path_buf(); - let state = create_test_state(Some(path.clone())); - let result = state.get_project_root(); - assert!(result.is_ok()); - assert_eq!(result.unwrap(), path); - } }