Files
logbuch/tests/ui_workflows.rs
itsscb 7568b1ef6e fix: optimize enter_edit_mode and fix borrow checker errors
- 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
2026-01-07 12:17:47 +01:00

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);
}