ibid
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user