From a7457f57490bb54f31d682711636f961d7dd2557 Mon Sep 17 00:00:00 2001 From: filipriec Date: Wed, 25 Jun 2025 23:00:51 +0200 Subject: [PATCH] frontend tui tests --- Cargo.lock | 44 ++++- client/Cargo.toml | 5 + client/src/components/form/form.rs | 4 +- client/tests/form_tests.rs | 262 +++++++++++++++++++++++++++++ client/tests/mod.rs | 3 + 5 files changed, 314 insertions(+), 4 deletions(-) create mode 100644 client/tests/form_tests.rs create mode 100644 client/tests/mod.rs diff --git a/Cargo.lock b/Cargo.lock index eab615d..2f68e41 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -163,6 +163,28 @@ dependencies = [ "abi_stable", ] +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + [[package]] name = "async-trait" version = "0.1.88" @@ -531,10 +553,12 @@ dependencies = [ "prost", "prost-types", "ratatui", + "rstest", "serde", "serde_json", "time", "tokio", + "tokio-test", "toml", "tonic", "tracing", @@ -542,6 +566,7 @@ dependencies = [ "tui-textarea", "unicode-segmentation", "unicode-width 0.2.0", + "uuid", ] [[package]] @@ -3949,6 +3974,19 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-test" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2468baabc3311435b55dd935f702f42cd1b8abb7e754fb7dfb16bd36aa88f9f7" +dependencies = [ + "async-stream", + "bytes", + "futures-core", + "tokio", + "tokio-stream", +] + [[package]] name = "tokio-util" version = "0.7.14" @@ -4279,12 +4317,14 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" +checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" dependencies = [ "getrandom 0.3.2", + "js-sys", "serde", + "wasm-bindgen", ] [[package]] diff --git a/client/Cargo.toml b/client/Cargo.toml index 3fdf47e..71e1c22 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -31,3 +31,8 @@ unicode-width = "0.2.0" [features] default = [] ui-debug = [] + +[dev-dependencies] +rstest = "0.25.0" +tokio-test = "0.4.4" +uuid = { version = "1.17.0", features = ["v4"] } diff --git a/client/src/components/form/form.rs b/client/src/components/form/form.rs index 78f4d73..181837f 100644 --- a/client/src/components/form/form.rs +++ b/client/src/components/form/form.rs @@ -1,10 +1,10 @@ // src/components/form/form.rs -use crate::components::common::autocomplete; // <--- ADD THIS IMPORT +use crate::components::common::autocomplete; use crate::components::handlers::canvas::render_canvas; use crate::config::colors::themes::Theme; use crate::state::app::highlight::HighlightState; use crate::state::pages::canvas_state::CanvasState; -use crate::state::pages::form::FormState; // <--- CHANGE THIS IMPORT +use crate::state::pages::form::FormState; use ratatui::{ layout::{Alignment, Constraint, Direction, Layout, Margin, Rect}, style::Style, diff --git a/client/tests/form_tests.rs b/client/tests/form_tests.rs new file mode 100644 index 0000000..00b9bd1 --- /dev/null +++ b/client/tests/form_tests.rs @@ -0,0 +1,262 @@ +// client/tests/form_tests.rs +use rstest::{fixture, rstest}; +use std::collections::HashMap; +use client::state::pages::form::{FormState, FieldDefinition}; +use client::state::pages::canvas_state::CanvasState; + +#[fixture] +fn test_form_state() -> FormState { + let fields = vec![ + FieldDefinition { + display_name: "Company".to_string(), + data_key: "firma".to_string(), + is_link: false, + link_target_table: None, + }, + FieldDefinition { + display_name: "Phone".to_string(), + data_key: "telefon".to_string(), + is_link: false, + link_target_table: None, + }, + FieldDefinition { + display_name: "Email".to_string(), + data_key: "email".to_string(), + is_link: false, + link_target_table: None, + }, + ]; + + FormState::new("test_profile".to_string(), "test_table".to_string(), fields) +} + +#[fixture] +fn test_form_data() -> HashMap { + let mut data = HashMap::new(); + data.insert("firma".to_string(), "Test Company".to_string()); + data.insert("telefon".to_string(), "+421123456789".to_string()); + data.insert("email".to_string(), "test@example.com".to_string()); + data +} + +#[rstest] +fn test_form_state_creation(test_form_state: FormState) { + assert_eq!(test_form_state.profile_name, "test_profile"); + assert_eq!(test_form_state.table_name, "test_table"); + assert_eq!(test_form_state.fields.len(), 3); + assert_eq!(test_form_state.current_field(), 0); + assert!(!test_form_state.has_unsaved_changes()); +} + +#[rstest] +fn test_form_field_navigation(mut test_form_state: FormState) { + // Test initial field + assert_eq!(test_form_state.current_field(), 0); + + // Test navigation to next field + test_form_state.set_current_field(1); + assert_eq!(test_form_state.current_field(), 1); + + // Test navigation to last field + test_form_state.set_current_field(2); + assert_eq!(test_form_state.current_field(), 2); + + // Test invalid field (should not crash) + test_form_state.set_current_field(999); + assert_eq!(test_form_state.current_field(), 2); // Should stay at valid field +} + +#[rstest] +fn test_form_data_entry(mut test_form_state: FormState) { + // Test entering data in first field + *test_form_state.get_current_input_mut() = "Test Company".to_string(); + test_form_state.set_has_unsaved_changes(true); + + assert_eq!(test_form_state.get_current_input(), "Test Company"); + assert!(test_form_state.has_unsaved_changes()); +} + +#[rstest] +fn test_form_field_switching_with_data(mut test_form_state: FormState) { + // Enter data in first field + *test_form_state.get_current_input_mut() = "Company Name".to_string(); + + // Switch to second field + test_form_state.set_current_field(1); + *test_form_state.get_current_input_mut() = "+421123456789".to_string(); + + // Switch back to first field + test_form_state.set_current_field(0); + assert_eq!(test_form_state.get_current_input(), "Company Name"); + + // Switch to second field again + test_form_state.set_current_field(1); + assert_eq!(test_form_state.get_current_input(), "+421123456789"); +} + +#[rstest] +fn test_form_reset_functionality(mut test_form_state: FormState) { + // Add some data + test_form_state.set_current_field(0); + *test_form_state.get_current_input_mut() = "Test Company".to_string(); + test_form_state.set_current_field(1); + *test_form_state.get_current_input_mut() = "+421123456789".to_string(); + test_form_state.set_has_unsaved_changes(true); + test_form_state.id = 123; + test_form_state.current_position = 5; + + // Reset the form + test_form_state.reset_to_empty(); + + // Verify reset + assert_eq!(test_form_state.id, 0); + assert!(!test_form_state.has_unsaved_changes()); + assert_eq!(test_form_state.current_field(), 0); + + // Check all fields are empty + for i in 0..test_form_state.fields.len() { + test_form_state.set_current_field(i); + assert!(test_form_state.get_current_input().is_empty()); + } +} + +#[rstest] +fn test_form_update_from_response(mut test_form_state: FormState, test_form_data: HashMap) { + let position = 3; + + // Update form with response data + test_form_state.update_from_response(&test_form_data, position); + + // Verify data was loaded + assert_eq!(test_form_state.current_position, position); + assert!(!test_form_state.has_unsaved_changes()); + assert_eq!(test_form_state.current_field(), 0); + + // Check field values + test_form_state.set_current_field(0); + assert_eq!(test_form_state.get_current_input(), "Test Company"); + + test_form_state.set_current_field(1); + assert_eq!(test_form_state.get_current_input(), "+421123456789"); + + test_form_state.set_current_field(2); + assert_eq!(test_form_state.get_current_input(), "test@example.com"); +} + +#[rstest] +fn test_form_cursor_position(mut test_form_state: FormState) { + // Test initial cursor position + assert_eq!(test_form_state.current_cursor_pos(), 0); + + // Add some text + *test_form_state.get_current_input_mut() = "Test Company".to_string(); + + // Test cursor positioning + test_form_state.set_current_cursor_pos(5); + assert_eq!(test_form_state.current_cursor_pos(), 5); + + // Test cursor bounds + test_form_state.set_current_cursor_pos(999); + // Should be clamped to text length + assert!(test_form_state.current_cursor_pos() <= "Test Company".len()); +} + +#[rstest] +fn test_form_field_display_names(test_form_state: FormState) { + let field_names = test_form_state.fields(); + + assert_eq!(field_names.len(), 3); + assert_eq!(field_names[0], "Company"); + assert_eq!(field_names[1], "Phone"); + assert_eq!(field_names[2], "Email"); +} + +#[rstest] +fn test_form_inputs_vector(mut test_form_state: FormState) { + // Add data to fields + test_form_state.set_current_field(0); + *test_form_state.get_current_input_mut() = "Company A".to_string(); + + test_form_state.set_current_field(1); + *test_form_state.get_current_input_mut() = "123456789".to_string(); + + test_form_state.set_current_field(2); + *test_form_state.get_current_input_mut() = "test@test.com".to_string(); + + // Get inputs vector + let inputs = test_form_state.inputs(); + + assert_eq!(inputs.len(), 3); + assert_eq!(inputs[0], "Company A"); + assert_eq!(inputs[1], "123456789"); + assert_eq!(inputs[2], "test@test.com"); +} + +#[rstest] +fn test_form_position_management(mut test_form_state: FormState) { + // Test initial position + assert_eq!(test_form_state.current_position, 1); + assert_eq!(test_form_state.total_count, 0); + + // Set some values + test_form_state.total_count = 10; + test_form_state.current_position = 5; + + assert_eq!(test_form_state.current_position, 5); + assert_eq!(test_form_state.total_count, 10); + + // Test reset affects position + test_form_state.reset_to_empty(); + assert_eq!(test_form_state.current_position, 11); // total_count + 1 +} + +#[rstest] +fn test_form_autocomplete_state(mut test_form_state: FormState) { + // Test initial autocomplete state + assert!(!test_form_state.autocomplete_active); + assert!(test_form_state.autocomplete_suggestions.is_empty()); + assert!(test_form_state.selected_suggestion_index.is_none()); + + // Test deactivating autocomplete + test_form_state.autocomplete_active = true; + test_form_state.deactivate_autocomplete(); + + assert!(!test_form_state.autocomplete_active); + assert!(test_form_state.autocomplete_suggestions.is_empty()); + assert!(test_form_state.selected_suggestion_index.is_none()); + assert!(!test_form_state.autocomplete_loading); +} + +#[rstest] +fn test_form_empty_data_handling(mut test_form_state: FormState) { + let empty_data = HashMap::new(); + + // Update with empty data + test_form_state.update_from_response(&empty_data, 1); + + // All fields should be empty + for i in 0..test_form_state.fields.len() { + test_form_state.set_current_field(i); + assert!(test_form_state.get_current_input().is_empty()); + } +} + +#[rstest] +fn test_form_partial_data_handling(mut test_form_state: FormState) { + let mut partial_data = HashMap::new(); + partial_data.insert("firma".to_string(), "Partial Company".to_string()); + // Intentionally missing telefon and email + + test_form_state.update_from_response(&partial_data, 1); + + // First field should have data + test_form_state.set_current_field(0); + assert_eq!(test_form_state.get_current_input(), "Partial Company"); + + // Other fields should be empty + test_form_state.set_current_field(1); + assert!(test_form_state.get_current_input().is_empty()); + + test_form_state.set_current_field(2); + assert!(test_form_state.get_current_input().is_empty()); +} diff --git a/client/tests/mod.rs b/client/tests/mod.rs new file mode 100644 index 0000000..b15a781 --- /dev/null +++ b/client/tests/mod.rs @@ -0,0 +1,3 @@ +// tests/mod.rs + +pub mod form_tests;