This commit is contained in:
Dave
2026-01-27 15:28:09 +00:00
parent c24f44bf51
commit 1f11eaedab

View File

@@ -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<Vec<SearchResult>, 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<Vec<SearchResult>, 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<Vec<SearchResult>, 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<Vec<SearchResult>, 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);
}
}