use dampen_core::parser;
use dampen_dev::subscription::{FileWatcherRecipe, watch_files};
use dampen_dev::watcher::{FileWatcher, FileWatcherConfig};
use std::fs;
use std::hash::Hasher;
use std::path::PathBuf;
use std::thread;
use std::time::Duration;
use tempfile::TempDir;
fn setup_test_dir() -> TempDir {
TempDir::new().expect("Failed to create temp directory")
}
#[test]
fn test_filewatcher_integration_with_parsing() {
let temp_dir = setup_test_dir();
let config = FileWatcherConfig {
watch_paths: vec![temp_dir.path().to_path_buf()],
debounce_ms: 50,
extension_filter: ".dampen".to_string(),
recursive: true,
};
let mut watcher = FileWatcher::new(config).expect("Failed to create watcher");
watcher
.watch(temp_dir.path().to_path_buf())
.expect("Failed to watch directory");
thread::sleep(Duration::from_millis(100));
let test_file = temp_dir.path().join("test.dampen");
let valid_xml = r#"<dampen version="1.1" encoding="utf-8">
<column spacing="10">
<text value="Hello World" />
<button label="Click me" on_click="handle_click" />
</column>
</dampen>"#;
fs::write(&test_file, valid_xml).expect("Failed to create test file");
thread::sleep(Duration::from_millis(150));
let receiver = watcher.receiver();
let path = receiver
.recv_timeout(Duration::from_secs(2))
.expect("Should receive file event");
assert_eq!(path, test_file, "Should receive event for test file");
let content = fs::read_to_string(&path).expect("Should be able to read file");
let document = parser::parse(&content).expect("Should parse valid XML");
let root = &document.root;
assert!(matches!(root.kind, dampen_core::ir::WidgetKind::Column));
assert_eq!(root.children.len(), 2);
println!("✓ FileWatcher integration with parsing successful");
}
#[test]
fn test_filewatcher_file_modification_detection() {
let temp_dir = setup_test_dir();
let test_file = temp_dir.path().join("modify_test.dampen");
fs::write(
&test_file,
r#"<dampen version="1.1" encoding="utf-8"><text value="Initial" /></dampen>"#,
)
.expect("Failed to create initial file");
thread::sleep(Duration::from_millis(100));
let config = FileWatcherConfig {
watch_paths: vec![temp_dir.path().to_path_buf()],
debounce_ms: 50,
extension_filter: ".dampen".to_string(),
recursive: true,
};
let mut watcher = FileWatcher::new(config).expect("Failed to create watcher");
watcher
.watch(temp_dir.path().to_path_buf())
.expect("Failed to watch directory");
thread::sleep(Duration::from_millis(100));
let receiver = watcher.receiver();
while receiver.try_recv().is_ok() {}
fs::write(
&test_file,
r#"<dampen version="1.1" encoding="utf-8"><text value="Modified" /></dampen>"#,
)
.expect("Failed to modify file");
thread::sleep(Duration::from_millis(150));
let path = receiver
.recv_timeout(Duration::from_secs(2))
.expect("Should receive modification event");
assert_eq!(path, test_file);
let content = fs::read_to_string(&path).expect("Should read modified file");
assert!(content.contains("Modified"));
println!("✓ File modification detection working");
}
#[test]
fn test_parse_error_handling() {
let invalid_xml = r#"<dampen version="1.1" encoding="utf-8">
<text value="Unclosed tag"
</dampen>"#;
let result = parser::parse(invalid_xml);
assert!(result.is_err(), "Should fail to parse invalid XML");
let error = result.unwrap_err();
assert!(!error.message.is_empty(), "Error should have a message");
println!("✓ Parse error detected: {}", error.message);
}
#[test]
fn test_watcher_error_nonexistent_path() {
let nonexistent_path = PathBuf::from("/tmp/dampen-test-nonexistent-99999");
let config = FileWatcherConfig {
watch_paths: vec![nonexistent_path.clone()],
debounce_ms: 100,
extension_filter: ".dampen".to_string(),
recursive: true,
};
let mut watcher = FileWatcher::new(config).expect("Watcher creation should succeed");
let result = watcher.watch(nonexistent_path.clone());
assert!(result.is_err(), "Should fail to watch non-existent path");
println!("✓ Watcher error detected for non-existent path");
}
#[test]
fn test_multiple_file_events() {
let temp_dir = setup_test_dir();
let config = FileWatcherConfig {
watch_paths: vec![temp_dir.path().to_path_buf()],
debounce_ms: 50,
extension_filter: ".dampen".to_string(),
recursive: true,
};
let mut watcher = FileWatcher::new(config).expect("Failed to create watcher");
watcher
.watch(temp_dir.path().to_path_buf())
.expect("Failed to watch directory");
thread::sleep(Duration::from_millis(100));
let file1 = temp_dir.path().join("file1.dampen");
let file2 = temp_dir.path().join("file2.dampen");
fs::write(
&file1,
r#"<dampen version="1.1" encoding="utf-8"><text value="File 1" /></dampen>"#,
)
.expect("Failed to create file1");
fs::write(
&file2,
r#"<dampen version="1.1" encoding="utf-8"><text value="File 2" /></dampen>"#,
)
.expect("Failed to create file2");
thread::sleep(Duration::from_millis(150));
let receiver = watcher.receiver();
let mut events = Vec::new();
while let Ok(path) = receiver.try_recv() {
events.push(path);
}
assert!(!events.is_empty(), "Should receive at least one event");
println!(
"✓ Multiple file events handled ({} events received)",
events.len()
);
}
#[test]
fn test_filewatcher_recipe_hash_uniqueness() {
use iced::advanced::subscription::{Hasher, Recipe};
fn hash_recipe(recipe: &FileWatcherRecipe) -> u64 {
let mut hasher = Hasher::default();
recipe.hash(&mut hasher);
hasher.finish()
}
let recipe1 = FileWatcherRecipe::new(vec![PathBuf::from("/tmp/test")], 100);
let recipe2 = FileWatcherRecipe::new(vec![PathBuf::from("/tmp/test")], 100);
assert_eq!(
hash_recipe(&recipe1),
hash_recipe(&recipe2),
"Same config should hash equally"
);
let recipe3 = FileWatcherRecipe::new(vec![PathBuf::from("/tmp/other")], 100);
assert_ne!(
hash_recipe(&recipe1),
hash_recipe(&recipe3),
"Different paths should hash differently"
);
let recipe4 = FileWatcherRecipe::new(vec![PathBuf::from("/tmp/test")], 200);
assert_ne!(
hash_recipe(&recipe1),
hash_recipe(&recipe4),
"Different debounce should hash differently"
);
println!("✓ Recipe hash uniqueness verified");
}
#[test]
fn test_subscription_api_creation() {
let paths = vec![PathBuf::from("/tmp/test")];
let subscription = watch_files(paths, 100);
println!("✓ Subscription API creation successful");
let _: iced::Subscription<dampen_dev::subscription::FileEvent> = subscription;
}
#[test]
fn test_parse_success_with_valid_document() {
let valid_xml = r#"<dampen version="1.1" encoding="utf-8">
<column spacing="20">
<text value="Hello" size="24" />
<button label="Click" on_click="action" />
<row>
<text value="Nested" />
</row>
</column>
</dampen>"#;
let document = parser::parse(valid_xml).expect("Should parse valid XML");
let root = &document.root;
assert!(matches!(root.kind, dampen_core::ir::WidgetKind::Column));
assert_eq!(root.children.len(), 3);
println!("✓ Valid document parsed successfully");
}
#[test]
fn test_error_recovery_simulation() {
let temp_dir = setup_test_dir();
let config = FileWatcherConfig {
watch_paths: vec![temp_dir.path().to_path_buf()],
debounce_ms: 30,
extension_filter: ".dampen".to_string(),
recursive: true,
};
let mut watcher = FileWatcher::new(config).expect("Failed to create watcher");
watcher
.watch(temp_dir.path().to_path_buf())
.expect("Failed to watch directory");
thread::sleep(Duration::from_millis(50));
let invalid_file = temp_dir.path().join("invalid.dampen");
fs::write(
&invalid_file,
r#"<dampen version="1.1" encoding="utf-8"><broken"#,
)
.expect("Failed to create invalid file");
thread::sleep(Duration::from_millis(80));
let valid_file = temp_dir.path().join("valid.dampen");
fs::write(
&valid_file,
r#"<dampen version="1.1" encoding="utf-8"><text value="Valid" /></dampen>"#,
)
.expect("Failed to create valid file");
thread::sleep(Duration::from_millis(80));
let receiver = watcher.receiver();
let mut received_paths = Vec::new();
while let Ok(path) = receiver.try_recv() {
received_paths.push(path);
}
assert!(
!received_paths.is_empty(),
"Watcher should still be receiving events"
);
assert!(
received_paths.iter().any(|p| p == &invalid_file)
|| received_paths.iter().any(|p| p == &valid_file),
"Should have detected at least one file"
);
println!("✓ Error recovery: watcher continues after errors");
}
#[test]
fn test_watcher_shutdown_detection() {
let temp_dir = setup_test_dir();
let config = FileWatcherConfig {
watch_paths: vec![temp_dir.path().to_path_buf()],
debounce_ms: 50,
extension_filter: ".dampen".to_string(),
recursive: true,
};
let mut watcher = FileWatcher::new(config).expect("Failed to create watcher");
watcher
.watch(temp_dir.path().to_path_buf())
.expect("Failed to watch directory");
thread::sleep(Duration::from_millis(100));
let test_file = temp_dir.path().join("test.dampen");
fs::write(
&test_file,
r#"<dampen version="1.1" encoding="utf-8"><text value="Test" /></dampen>"#,
)
.expect("Failed to create file");
thread::sleep(Duration::from_millis(150));
let receiver = watcher.receiver();
let path = receiver
.recv_timeout(Duration::from_secs(2))
.expect("Should receive file event");
assert_eq!(path, test_file);
while receiver.recv_timeout(Duration::from_millis(50)).is_ok() {}
let result = receiver.recv_timeout(Duration::from_millis(200));
assert!(
result.is_err(),
"recv_timeout should timeout when no events, got: {:?}",
result
);
fs::write(
&test_file,
r#"<dampen version="1.1" encoding="utf-8"><text value="Modified" /></dampen>"#,
)
.expect("Failed to modify file");
thread::sleep(Duration::from_millis(150));
let path = receiver
.recv_timeout(Duration::from_secs(2))
.expect("Should receive modification event");
assert_eq!(path, test_file);
println!("✓ Watcher uses recv_timeout and doesn't block indefinitely");
}