- Change enter_edit_mode to take Entry by value instead of reference - Optimize TextArea initialization with TextArea::from() for message - Reduce clone operations in navigation paths (detail.rs, list.rs, actions/mod.rs) - Remove duplicate dead code from list.rs edit conflict - Fix syntax error (extra } in state.rs) - All 78 tests pass, clean clippy output
256 lines
7.5 KiB
Rust
256 lines
7.5 KiB
Rust
use std::path::PathBuf;
|
|
|
|
use logbuch::db::Database;
|
|
use logbuch::error::Dialog;
|
|
use logbuch::error::DialogFocus;
|
|
use logbuch::models::Entry;
|
|
use logbuch::models::SortField;
|
|
use logbuch::state::{AppState, DetailField, DetailMode, Mode};
|
|
use logbuch::ui::actions;
|
|
|
|
#[test]
|
|
fn test_create_entry_workflow() {
|
|
let db = Database::open(&PathBuf::from(":memory:")).unwrap();
|
|
let mut state = AppState::new();
|
|
|
|
// Enter create mode
|
|
state.enter_create_mode();
|
|
assert_eq!(state.mode, Mode::Detail);
|
|
assert_eq!(state.detail_mode, Some(DetailMode::Create));
|
|
|
|
// Enter message
|
|
state.message_textarea.insert_str("Test message");
|
|
|
|
// Save entry
|
|
let result = actions::save::execute(&mut state, &db);
|
|
assert!(result.is_ok());
|
|
|
|
// Verify entry was saved
|
|
assert_eq!(state.mode, Mode::List);
|
|
assert_eq!(state.entries.len(), 1);
|
|
assert_eq!(state.entries[0].message, "Test message");
|
|
}
|
|
|
|
#[test]
|
|
fn test_edit_entry_workflow() {
|
|
let db = Database::open(&PathBuf::from(":memory:")).unwrap();
|
|
|
|
// Create initial entry
|
|
let mut entry = Entry::new("Original message".to_string(), Some("Subject".to_string()));
|
|
entry.id = Some(1);
|
|
db.insert(&entry).unwrap();
|
|
|
|
let mut state = AppState::new();
|
|
state.entries = vec![entry.clone()];
|
|
|
|
// Enter edit mode
|
|
state.enter_edit_mode(entry);
|
|
assert_eq!(state.mode, Mode::Detail);
|
|
assert!(matches!(state.detail_mode, Some(DetailMode::Edit(1))));
|
|
|
|
// Modify message
|
|
state.message_textarea.insert_str(" - modified");
|
|
|
|
// Save changes
|
|
let result = actions::save::execute(&mut state, &db);
|
|
assert!(result.is_ok());
|
|
|
|
// Verify changes
|
|
assert_eq!(state.entries.len(), 1);
|
|
assert!(state.entries[0].message.contains("modified"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_discard_changes_workflow() {
|
|
let db = Database::open(&PathBuf::from(":memory:")).unwrap();
|
|
|
|
// Create initial entry
|
|
let mut entry = Entry::new("Original".to_string(), Some("Subject".to_string()));
|
|
entry.id = Some(1);
|
|
db.insert(&entry).unwrap();
|
|
|
|
let mut state = AppState::new();
|
|
actions::load::execute(&db, &mut state).unwrap();
|
|
let entry_clone = state.entries[0].clone();
|
|
|
|
// Enter edit mode
|
|
state.enter_edit_mode(entry_clone);
|
|
|
|
// Make changes
|
|
state.message_textarea.insert_str(" - changes");
|
|
|
|
// Press ESC to discard - has_unsaved_changes returns true
|
|
assert!(state.has_unsaved_changes());
|
|
state.dialog = Some(Dialog::DiscardConfirm);
|
|
state.dialog_focus = DialogFocus::No;
|
|
|
|
// When confirm=false for DiscardConfirm, dialog is dismissed but mode stays
|
|
let result = actions::execute_dialog_action(&mut state, &db, false);
|
|
assert!(result.is_ok());
|
|
|
|
// Dialog is dismissed
|
|
assert!(state.dialog.is_none());
|
|
// Mode is still Detail since we didn't confirm
|
|
// User would need to press ESC again to exit detail mode
|
|
}
|
|
|
|
#[test]
|
|
fn test_delete_entry_workflow() {
|
|
let db = Database::open(&PathBuf::from(":memory:")).unwrap();
|
|
|
|
// Create entries
|
|
let entry1 = Entry::new("First".to_string(), None);
|
|
db.insert(&entry1).unwrap();
|
|
|
|
let entry2 = Entry::new("Second".to_string(), None);
|
|
db.insert(&entry2).unwrap();
|
|
|
|
let mut state = AppState::new();
|
|
actions::load::execute(&db, &mut state).unwrap();
|
|
assert_eq!(state.entries.len(), 2);
|
|
|
|
// Note: Sort order is Descending by Created, so most recent entry (Second) is first
|
|
assert_eq!(state.entries[0].message, "Second");
|
|
assert_eq!(state.entries[1].message, "First");
|
|
|
|
// Get the entry_id of "Second" (the first entry in the list)
|
|
let second_id = state.entries[0].id.unwrap();
|
|
|
|
// Select first entry and trigger delete dialog
|
|
state.dialog = Some(Dialog::DeleteConfirm {
|
|
entry_id: second_id,
|
|
entry_title: state.entries[0].display_title().to_string(),
|
|
});
|
|
state.dialog_focus = DialogFocus::Yes;
|
|
|
|
// Confirm delete
|
|
let result = actions::execute_dialog_action(&mut state, &db, true);
|
|
assert!(result.is_ok());
|
|
|
|
// Reload entries to verify
|
|
actions::load::execute(&db, &mut state).unwrap();
|
|
|
|
// Verify entry was deleted - should have 1 entry now
|
|
assert_eq!(state.entries.len(), 1);
|
|
// The remaining entry should be "First"
|
|
assert_eq!(state.entries[0].message, "First");
|
|
}
|
|
|
|
#[test]
|
|
fn test_search_workflow() {
|
|
let db = Database::open(&PathBuf::from(":memory:")).unwrap();
|
|
|
|
// Create entries with different content
|
|
let entry1 = Entry::new("Rust programming".to_string(), None);
|
|
db.insert(&entry1).unwrap();
|
|
|
|
let entry2 = Entry::new("Python scripting".to_string(), None);
|
|
db.insert(&entry2).unwrap();
|
|
|
|
let entry3 = Entry::new("Rustacean".to_string(), None);
|
|
db.insert(&entry3).unwrap();
|
|
|
|
let mut state = AppState::new();
|
|
actions::load::execute(&db, &mut state).unwrap();
|
|
|
|
// Enter search mode
|
|
state.enter_search_mode();
|
|
assert_eq!(state.mode, Mode::Search);
|
|
assert!(state.search_active);
|
|
|
|
// Type search query
|
|
state.search_query = "Rust".to_string();
|
|
state.update_search_matches();
|
|
|
|
// Verify matches
|
|
assert_eq!(state.search_matches.len(), 2);
|
|
|
|
// Exit search
|
|
state.exit_search_mode();
|
|
assert_eq!(state.mode, Mode::List);
|
|
assert!(!state.search_active);
|
|
assert!(state.search_query.is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn test_sort_toggle_workflow() {
|
|
let db = Database::open(&PathBuf::from(":memory:")).unwrap();
|
|
|
|
// Create entries with subjects
|
|
let entry_a = Entry::new("Message A".to_string(), Some("Apple".to_string()));
|
|
db.insert(&entry_a).unwrap();
|
|
|
|
let entry_z = Entry::new("Message Z".to_string(), Some("Zebra".to_string()));
|
|
db.insert(&entry_z).unwrap();
|
|
|
|
let mut state = AppState::new();
|
|
actions::load::execute(&db, &mut state).unwrap();
|
|
|
|
// Toggle to sort by Subject
|
|
state.toggle_sort(SortField::Subject);
|
|
actions::load::execute(&db, &mut state).unwrap();
|
|
|
|
// Verify subjects are sorted ascending (Apple before Zebra)
|
|
assert_eq!(state.entries[0].message, "Message A");
|
|
assert_eq!(state.entries[1].message, "Message Z");
|
|
|
|
// Toggle order to descending
|
|
state.toggle_sort(SortField::Subject);
|
|
actions::load::execute(&db, &mut state).unwrap();
|
|
|
|
// Verify subjects are sorted descending (Zebra before Apple)
|
|
assert_eq!(state.entries[0].message, "Message Z");
|
|
assert_eq!(state.entries[1].message, "Message A");
|
|
}
|
|
|
|
#[test]
|
|
fn test_navigation_workflow() {
|
|
let db = Database::open(&PathBuf::from(":memory:")).unwrap();
|
|
|
|
// Create multiple entries
|
|
for i in 0..5 {
|
|
let entry = Entry::new(format!("Message {i}"), None);
|
|
db.insert(&entry).unwrap();
|
|
}
|
|
|
|
let mut state = AppState::new();
|
|
actions::load::execute(&db, &mut state).unwrap();
|
|
|
|
// Navigate down
|
|
for i in 0..4 {
|
|
state.select_next();
|
|
assert_eq!(state.selected_index, i + 1);
|
|
}
|
|
|
|
// Navigate up
|
|
for i in (0..4).rev() {
|
|
state.select_prev();
|
|
assert_eq!(state.selected_index, i);
|
|
}
|
|
|
|
// Go to first
|
|
state.select_first();
|
|
assert_eq!(state.selected_index, 0);
|
|
|
|
// Go to last
|
|
state.select_last();
|
|
assert_eq!(state.selected_index, 4);
|
|
}
|
|
|
|
#[test]
|
|
fn test_tab_toggle_in_detail_mode() {
|
|
let mut state = AppState::new();
|
|
state.enter_create_mode();
|
|
|
|
// Initial focus should be Message
|
|
assert_eq!(state.detail_field, DetailField::Message);
|
|
|
|
// Tab should switch to Subject
|
|
state.detail_field = state.detail_field.toggle();
|
|
assert_eq!(state.detail_field, DetailField::Subject);
|
|
|
|
// Tab should switch back to Message
|
|
state.detail_field = state.detail_field.toggle();
|
|
assert_eq!(state.detail_field, DetailField::Message);
|
|
}
|