1use crate::error::PdfError;
12use crate::forms::calculations::FieldValue;
13use crate::objects::{Dictionary, Object};
14use chrono::{DateTime, Utc};
15use std::collections::HashMap;
16use std::fmt;
17
18#[derive(Debug, Clone, Default)]
20pub struct FieldActionSystem {
21 actions: HashMap<String, FieldActions>,
23 event_history: Vec<ActionEvent>,
25 focused_field: Option<String>,
27 handlers: ActionHandlers,
29 settings: ActionSettings,
31}
32
33#[derive(Debug, Clone, Default)]
35pub struct FieldActions {
36 pub on_focus: Option<FieldAction>,
38 pub on_blur: Option<FieldAction>,
40 pub on_format: Option<FieldAction>,
42 pub on_keystroke: Option<FieldAction>,
44 pub on_calculate: Option<FieldAction>,
46 pub on_validate: Option<FieldAction>,
48 pub on_mouse_enter: Option<FieldAction>,
50 pub on_mouse_exit: Option<FieldAction>,
52 pub on_mouse_down: Option<FieldAction>,
54 pub on_mouse_up: Option<FieldAction>,
56}
57
58#[derive(Debug, Clone)]
60pub enum FieldAction {
61 JavaScript { script: String, async_exec: bool },
63 Format { format_type: FormatActionType },
65 Validate { validation_type: ValidateActionType },
67 Calculate { expression: String },
69 SubmitForm {
71 url: String,
72 fields: Vec<String>,
73 include_empty: bool,
74 },
75 ResetForm { fields: Vec<String>, exclude: bool },
77 ImportData { file_path: String },
79 SetField {
81 target_field: String,
82 value: FieldValue,
83 },
84 ShowHide { fields: Vec<String>, show: bool },
86 PlaySound { sound_name: String, volume: f32 },
88 Custom {
90 action_type: String,
91 parameters: HashMap<String, String>,
92 },
93}
94
95#[derive(Debug, Clone)]
97pub enum FormatActionType {
98 Number {
100 decimals: usize,
101 currency: Option<String>,
102 },
103 Percent { decimals: usize },
105 Date { format: String },
107 Time { format: String },
109 Special { format: SpecialFormatType },
111 Custom { script: String },
113}
114
115#[derive(Debug, Clone, Copy)]
117pub enum SpecialFormatType {
118 ZipCode,
119 ZipPlus4,
120 Phone,
121 SSN,
122}
123
124#[derive(Debug, Clone)]
126pub enum ValidateActionType {
127 Range { min: Option<f64>, max: Option<f64> },
129 Custom { script: String },
131}
132
133#[derive(Debug, Clone)]
135pub struct ActionEvent {
136 pub timestamp: DateTime<Utc>,
138 pub field_name: String,
140 pub event_type: ActionEventType,
142 pub action: Option<FieldAction>,
144 pub result: ActionResult,
146 pub data: HashMap<String, String>,
148}
149
150#[derive(Debug, Clone, PartialEq)]
152pub enum ActionEventType {
153 Focus,
154 Blur,
155 Format,
156 Keystroke,
157 Calculate,
158 Validate,
159 MouseEnter,
160 MouseExit,
161 MouseDown,
162 MouseUp,
163}
164
165#[derive(Debug, Clone)]
167pub enum ActionResult {
168 Success,
169 Failed(String),
170 Cancelled,
171 Modified(FieldValue),
172}
173
174pub type JsExecutor = fn(&str) -> Result<String, String>;
176
177pub type FormSubmitter = fn(&str, &[String]) -> Result<(), String>;
179
180pub type SoundPlayer = fn(&str, f32) -> Result<(), String>;
182
183pub type CustomHandler = fn(&str, &HashMap<String, String>) -> Result<(), String>;
185
186#[derive(Clone, Default)]
188pub struct ActionHandlers {
189 pub js_executor: Option<JsExecutor>,
191 pub form_submitter: Option<FormSubmitter>,
193 pub sound_player: Option<SoundPlayer>,
195 pub custom_handler: Option<CustomHandler>,
197}
198
199impl fmt::Debug for ActionHandlers {
200 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
201 f.debug_struct("ActionHandlers")
202 .field("js_executor", &self.js_executor.is_some())
203 .field("form_submitter", &self.form_submitter.is_some())
204 .field("sound_player", &self.sound_player.is_some())
205 .field("custom_handler", &self.custom_handler.is_some())
206 .finish()
207 }
208}
209
210#[derive(Debug, Clone)]
212pub struct ActionSettings {
213 pub enable_javascript: bool,
215 pub enable_form_submit: bool,
217 pub enable_sound: bool,
219 pub log_events: bool,
221 pub max_event_history: usize,
223}
224
225impl Default for ActionSettings {
226 fn default() -> Self {
227 Self {
228 enable_javascript: true,
229 enable_form_submit: false,
230 enable_sound: true,
231 log_events: true,
232 max_event_history: 1000,
233 }
234 }
235}
236
237impl FieldActionSystem {
238 pub fn new() -> Self {
240 Self::default()
241 }
242
243 pub fn with_settings(settings: ActionSettings) -> Self {
245 Self {
246 settings,
247 ..Self::default()
248 }
249 }
250
251 pub fn register_field_actions(&mut self, field_name: impl Into<String>, actions: FieldActions) {
253 self.actions.insert(field_name.into(), actions);
254 }
255
256 pub fn handle_focus(&mut self, field_name: impl Into<String>) -> Result<(), PdfError> {
258 let field_name = field_name.into();
259
260 if let Some(prev_field) = self.focused_field.clone() {
262 if prev_field != field_name {
263 self.handle_blur(prev_field)?;
264 }
265 }
266
267 self.focused_field = Some(field_name.clone());
269
270 if let Some(action) = self
272 .actions
273 .get(&field_name)
274 .and_then(|a| a.on_focus.clone())
275 {
276 self.execute_action(&field_name, ActionEventType::Focus, &action)?;
277 }
278
279 Ok(())
280 }
281
282 pub fn handle_blur(&mut self, field_name: impl Into<String>) -> Result<(), PdfError> {
284 let field_name = field_name.into();
285
286 if self.focused_field.as_ref() == Some(&field_name) {
288 self.focused_field = None;
289
290 if let Some(action) = self
292 .actions
293 .get(&field_name)
294 .and_then(|a| a.on_blur.clone())
295 {
296 self.execute_action(&field_name, ActionEventType::Blur, &action)?;
297 }
298 }
299
300 Ok(())
301 }
302
303 pub fn handle_format(
305 &mut self,
306 field_name: impl Into<String>,
307 value: &mut FieldValue,
308 ) -> Result<(), PdfError> {
309 let field_name = field_name.into();
310
311 if let Some(action) = self
312 .actions
313 .get(&field_name)
314 .and_then(|a| a.on_format.clone())
315 {
316 let result = self.execute_action(&field_name, ActionEventType::Format, &action)?;
317
318 if let ActionResult::Modified(new_value) = result {
320 *value = new_value;
321 }
322 }
323
324 Ok(())
325 }
326
327 pub fn handle_keystroke(
329 &mut self,
330 field_name: impl Into<String>,
331 _key: char,
332 _current_value: &str,
333 ) -> Result<bool, PdfError> {
334 let field_name = field_name.into();
335
336 if let Some(action) = self
337 .actions
338 .get(&field_name)
339 .and_then(|a| a.on_keystroke.clone())
340 {
341 let result = self.execute_action(&field_name, ActionEventType::Keystroke, &action)?;
342
343 return match result {
345 ActionResult::Success => Ok(true),
346 ActionResult::Cancelled => Ok(false),
347 _ => Ok(true),
348 };
349 }
350
351 Ok(true)
352 }
353
354 pub fn handle_validate(
356 &mut self,
357 field_name: impl Into<String>,
358 _value: &FieldValue,
359 ) -> Result<bool, PdfError> {
360 let field_name = field_name.into();
361
362 if let Some(action) = self
363 .actions
364 .get(&field_name)
365 .and_then(|a| a.on_validate.clone())
366 {
367 let result = self.execute_action(&field_name, ActionEventType::Validate, &action)?;
368
369 return match result {
371 ActionResult::Success => Ok(true),
372 ActionResult::Failed(_) => Ok(false),
373 _ => Ok(true),
374 };
375 }
376
377 Ok(true)
378 }
379
380 pub fn handle_calculate(
382 &mut self,
383 field_name: impl Into<String>,
384 value: &mut FieldValue,
385 ) -> Result<(), PdfError> {
386 let field_name = field_name.into();
387
388 if let Some(action) = self
389 .actions
390 .get(&field_name)
391 .and_then(|a| a.on_calculate.clone())
392 {
393 let result = self.execute_action(&field_name, ActionEventType::Calculate, &action)?;
394
395 if let ActionResult::Modified(new_value) = result {
397 *value = new_value;
398 }
399 }
400
401 Ok(())
402 }
403
404 fn execute_action(
406 &mut self,
407 field_name: &str,
408 event_type: ActionEventType,
409 action: &FieldAction,
410 ) -> Result<ActionResult, PdfError> {
411 let result = match action {
412 FieldAction::JavaScript { script, async_exec } => {
413 self.execute_javascript(script, *async_exec)
414 }
415 FieldAction::Format { format_type } => self.execute_format(format_type),
416 FieldAction::Validate { validation_type } => self.execute_validate(validation_type),
417 FieldAction::Calculate { expression } => self.execute_calculate(expression),
418 FieldAction::ShowHide { fields, show } => self.execute_show_hide(fields, *show),
419 FieldAction::SetField {
420 target_field,
421 value,
422 } => self.execute_set_field(target_field, value),
423 _ => Ok(ActionResult::Success),
424 };
425
426 if self.settings.log_events {
428 let result_for_log = result
429 .as_ref()
430 .map(|r| r.clone())
431 .unwrap_or_else(|e| ActionResult::Failed(e.to_string()));
432 self.log_event(field_name, event_type, Some(action.clone()), result_for_log);
433 }
434
435 result
436 }
437
438 fn execute_javascript(
440 &self,
441 script: &str,
442 _async_exec: bool,
443 ) -> Result<ActionResult, PdfError> {
444 if !self.settings.enable_javascript {
445 return Ok(ActionResult::Cancelled);
446 }
447
448 if let Some(executor) = self.handlers.js_executor {
449 match executor(script) {
450 Ok(_result) => Ok(ActionResult::Success),
451 Err(e) => Ok(ActionResult::Failed(e)),
452 }
453 } else {
454 Ok(ActionResult::Success)
456 }
457 }
458
459 fn execute_format(&self, _format_type: &FormatActionType) -> Result<ActionResult, PdfError> {
461 Ok(ActionResult::Success)
463 }
464
465 fn execute_validate(
467 &self,
468 validation_type: &ValidateActionType,
469 ) -> Result<ActionResult, PdfError> {
470 match validation_type {
471 ValidateActionType::Range { min: _, max: _ } => {
472 Ok(ActionResult::Success)
474 }
475 ValidateActionType::Custom { script } => self.execute_javascript(script, false),
476 }
477 }
478
479 fn execute_calculate(&self, _expression: &str) -> Result<ActionResult, PdfError> {
481 Ok(ActionResult::Success)
483 }
484
485 fn execute_show_hide(&self, _fields: &[String], _show: bool) -> Result<ActionResult, PdfError> {
487 Ok(ActionResult::Success)
489 }
490
491 fn execute_set_field(
493 &self,
494 _target_field: &str,
495 value: &FieldValue,
496 ) -> Result<ActionResult, PdfError> {
497 Ok(ActionResult::Modified(value.clone()))
499 }
500
501 fn log_event(
503 &mut self,
504 field_name: &str,
505 event_type: ActionEventType,
506 action: Option<FieldAction>,
507 result: ActionResult,
508 ) {
509 let event = ActionEvent {
510 timestamp: Utc::now(),
511 field_name: field_name.to_string(),
512 event_type,
513 action,
514 result,
515 data: HashMap::new(),
516 };
517
518 self.event_history.push(event);
519
520 if self.event_history.len() > self.settings.max_event_history {
522 self.event_history.remove(0);
523 }
524 }
525
526 pub fn get_focused_field(&self) -> Option<&String> {
528 self.focused_field.as_ref()
529 }
530
531 pub fn get_event_history(&self) -> &[ActionEvent] {
533 &self.event_history
534 }
535
536 pub fn clear_event_history(&mut self) {
538 self.event_history.clear();
539 }
540
541 pub fn set_js_executor(&mut self, executor: fn(&str) -> Result<String, String>) {
543 self.handlers.js_executor = Some(executor);
544 }
545
546 pub fn to_pdf_dict(&self, field_name: &str) -> Dictionary {
548 let mut dict = Dictionary::new();
549
550 if let Some(actions) = self.actions.get(field_name) {
551 let mut aa_dict = Dictionary::new();
553
554 if let Some(action) = &actions.on_focus {
555 aa_dict.set("Fo", self.action_to_object(action));
556 }
557 if let Some(action) = &actions.on_blur {
558 aa_dict.set("Bl", self.action_to_object(action));
559 }
560 if let Some(action) = &actions.on_format {
561 aa_dict.set("F", self.action_to_object(action));
562 }
563 if let Some(action) = &actions.on_keystroke {
564 aa_dict.set("K", self.action_to_object(action));
565 }
566 if let Some(action) = &actions.on_calculate {
567 aa_dict.set("C", self.action_to_object(action));
568 }
569 if let Some(action) = &actions.on_validate {
570 aa_dict.set("V", self.action_to_object(action));
571 }
572
573 if !aa_dict.is_empty() {
574 dict.set("AA", Object::Dictionary(aa_dict));
575 }
576 }
577
578 dict
579 }
580
581 fn action_to_object(&self, action: &FieldAction) -> Object {
583 let mut dict = Dictionary::new();
584
585 match action {
586 FieldAction::JavaScript { script, .. } => {
587 dict.set("S", Object::Name("JavaScript".to_string()));
588 dict.set("JS", Object::String(script.clone()));
589 }
590 FieldAction::SubmitForm { url, .. } => {
591 dict.set("S", Object::Name("SubmitForm".to_string()));
592 dict.set("F", Object::String(url.clone()));
593 }
594 FieldAction::ResetForm { .. } => {
595 dict.set("S", Object::Name("ResetForm".to_string()));
596 }
597 _ => {
598 dict.set("S", Object::Name("Unknown".to_string()));
599 }
600 }
601
602 Object::Dictionary(dict)
603 }
604}
605
606impl fmt::Display for ActionEvent {
607 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
608 write!(
609 f,
610 "[{}] Field '{}': {:?} -> {:?}",
611 self.timestamp.format("%H:%M:%S"),
612 self.field_name,
613 self.event_type,
614 self.result
615 )
616 }
617}
618
619#[cfg(test)]
620mod tests {
621 use super::*;
622
623 #[test]
624 fn test_focus_blur_events() {
625 let mut system = FieldActionSystem::new();
626
627 let actions = FieldActions {
628 on_focus: Some(FieldAction::JavaScript {
629 script: "console.log('Field focused');".to_string(),
630 async_exec: false,
631 }),
632 on_blur: Some(FieldAction::JavaScript {
633 script: "console.log('Field blurred');".to_string(),
634 async_exec: false,
635 }),
636 ..Default::default()
637 };
638
639 system.register_field_actions("test_field", actions);
640
641 system.handle_focus("test_field").unwrap();
643 assert_eq!(system.get_focused_field(), Some(&"test_field".to_string()));
644
645 system.handle_blur("test_field").unwrap();
647 assert_eq!(system.get_focused_field(), None);
648 }
649
650 #[test]
651 fn test_field_switching() {
652 let mut system = FieldActionSystem::new();
653
654 for field in ["field1", "field2"] {
656 let actions = FieldActions {
657 on_focus: Some(FieldAction::SetField {
658 target_field: "status".to_string(),
659 value: FieldValue::Text(format!("{} focused", field)),
660 }),
661 on_blur: Some(FieldAction::SetField {
662 target_field: "status".to_string(),
663 value: FieldValue::Text(format!("{} blurred", field)),
664 }),
665 ..Default::default()
666 };
667 system.register_field_actions(field, actions);
668 }
669
670 system.handle_focus("field1").unwrap();
672 assert_eq!(system.get_focused_field(), Some(&"field1".to_string()));
673
674 system.handle_focus("field2").unwrap();
676 assert_eq!(system.get_focused_field(), Some(&"field2".to_string()));
677 }
678
679 #[test]
680 fn test_validate_action() {
681 let mut system = FieldActionSystem::new();
682
683 let actions = FieldActions {
684 on_validate: Some(FieldAction::Validate {
685 validation_type: ValidateActionType::Range {
686 min: Some(0.0),
687 max: Some(100.0),
688 },
689 }),
690 ..Default::default()
691 };
692
693 system.register_field_actions("score", actions);
694
695 let valid = system
697 .handle_validate("score", &FieldValue::Number(50.0))
698 .unwrap();
699 assert!(valid);
700 }
701
702 #[test]
703 fn test_event_history() {
704 let mut system = FieldActionSystem::new();
705
706 let actions = FieldActions {
707 on_focus: Some(FieldAction::ShowHide {
708 fields: vec!["help_text".to_string()],
709 show: true,
710 }),
711 on_blur: Some(FieldAction::ShowHide {
712 fields: vec!["help_text".to_string()],
713 show: false,
714 }),
715 ..Default::default()
716 };
717
718 system.register_field_actions("field1", actions);
719
720 system.handle_focus("field1").unwrap();
722 system.handle_blur("field1").unwrap();
723
724 assert_eq!(system.get_event_history().len(), 2);
726 assert_eq!(
727 system.get_event_history()[0].event_type,
728 ActionEventType::Focus
729 );
730 assert_eq!(
731 system.get_event_history()[1].event_type,
732 ActionEventType::Blur
733 );
734 }
735
736 #[test]
737 fn test_format_action_types() {
738 let number_format = FormatActionType::Number {
740 decimals: 2,
741 currency: Some("USD".to_string()),
742 };
743
744 match number_format {
745 FormatActionType::Number { decimals, currency } => {
746 assert_eq!(decimals, 2);
747 assert_eq!(currency, Some("USD".to_string()));
748 }
749 _ => panic!("Expected Number format"),
750 }
751
752 let percent_format = FormatActionType::Percent { decimals: 1 };
754
755 match percent_format {
756 FormatActionType::Percent { decimals } => assert_eq!(decimals, 1),
757 _ => panic!("Expected Percent format"),
758 }
759
760 let date_format = FormatActionType::Date {
762 format: "mm/dd/yyyy".to_string(),
763 };
764
765 match date_format {
766 FormatActionType::Date { format } => assert_eq!(format, "mm/dd/yyyy"),
767 _ => panic!("Expected Date format"),
768 }
769
770 let special_format = FormatActionType::Special {
772 format: SpecialFormatType::ZipCode,
773 };
774
775 match special_format {
776 FormatActionType::Special { format } => {
777 matches!(format, SpecialFormatType::ZipCode);
778 }
779 _ => panic!("Expected Special format"),
780 }
781 }
782
783 #[test]
784 fn test_validate_action_types() {
785 let range_validate = ValidateActionType::Range {
787 min: Some(0.0),
788 max: Some(100.0),
789 };
790
791 match range_validate {
792 ValidateActionType::Range { min, max } => {
793 assert_eq!(min, Some(0.0));
794 assert_eq!(max, Some(100.0));
795 }
796 _ => panic!("Expected Range validation"),
797 }
798
799 let custom_validate = ValidateActionType::Custom {
801 script: "return value > 0;".to_string(),
802 };
803
804 match custom_validate {
805 ValidateActionType::Custom { script } => {
806 assert!(script.contains("return"));
807 }
808 _ => panic!("Expected Custom validation"),
809 }
810 }
811
812 #[test]
813 fn test_action_result() {
814 let success_result = ActionResult::Success;
816 matches!(success_result, ActionResult::Success);
817
818 let failed_result = ActionResult::Failed("Test error".to_string());
820 match failed_result {
821 ActionResult::Failed(msg) => assert_eq!(msg, "Test error"),
822 _ => panic!("Expected Failed result"),
823 }
824
825 let cancelled_result = ActionResult::Cancelled;
827 matches!(cancelled_result, ActionResult::Cancelled);
828
829 let modified_result = ActionResult::Modified(FieldValue::Text("Modified".to_string()));
831 match modified_result {
832 ActionResult::Modified(value) => {
833 assert_eq!(value, FieldValue::Text("Modified".to_string()));
834 }
835 _ => panic!("Expected Modified result"),
836 }
837 }
838
839 #[test]
840 fn test_action_settings() {
841 let settings = ActionSettings::default();
842 assert!(settings.enable_javascript);
843 assert!(!settings.enable_form_submit); assert!(settings.enable_sound);
845 assert!(settings.log_events);
846 assert_eq!(settings.max_event_history, 1000);
847
848 let custom_settings = ActionSettings {
850 enable_javascript: false,
851 enable_form_submit: true,
852 enable_sound: false,
853 log_events: false,
854 max_event_history: 500,
855 };
856 assert!(!custom_settings.enable_javascript);
857 assert!(custom_settings.enable_form_submit);
858 assert!(!custom_settings.enable_sound);
859 assert!(!custom_settings.log_events);
860 assert_eq!(custom_settings.max_event_history, 500);
861 }
862
863 #[test]
864 fn test_field_action_system_settings() {
865 let system = FieldActionSystem::new();
866
867 assert!(system.settings.enable_javascript);
869 assert!(!system.settings.enable_form_submit);
870 assert!(system.settings.enable_sound);
871 assert!(system.settings.log_events);
872 assert_eq!(system.settings.max_event_history, 1000);
873
874 let custom_settings = ActionSettings {
876 enable_javascript: false,
877 enable_form_submit: true,
878 enable_sound: false,
879 log_events: false,
880 max_event_history: 100,
881 };
882 let system_with_settings = FieldActionSystem::with_settings(custom_settings);
883 assert!(!system_with_settings.settings.enable_javascript);
884 assert!(system_with_settings.settings.enable_form_submit);
885 }
886
887 #[test]
888 fn test_clear_event_history() {
889 let mut system = FieldActionSystem::new();
890
891 let actions = FieldActions {
893 on_focus: Some(FieldAction::Custom {
894 action_type: "test".to_string(),
895 parameters: HashMap::new(),
896 }),
897 ..Default::default()
898 };
899
900 system.register_field_actions("field1", actions);
901 system.handle_focus("field1").unwrap();
902
903 assert!(system.get_event_history().len() > 0);
904
905 system.clear_event_history();
907 assert_eq!(system.get_event_history().len(), 0);
908 }
909
910 #[test]
911 fn test_mouse_actions() {
912 let mut system = FieldActionSystem::new();
913
914 let actions = FieldActions {
915 on_mouse_enter: Some(FieldAction::Custom {
916 action_type: "highlight".to_string(),
917 parameters: HashMap::from([("color".to_string(), "yellow".to_string())]),
918 }),
919 on_mouse_exit: Some(FieldAction::Custom {
920 action_type: "unhighlight".to_string(),
921 parameters: HashMap::new(),
922 }),
923 on_mouse_down: Some(FieldAction::Custom {
924 action_type: "pressed".to_string(),
925 parameters: HashMap::new(),
926 }),
927 on_mouse_up: Some(FieldAction::Custom {
928 action_type: "released".to_string(),
929 parameters: HashMap::new(),
930 }),
931 ..Default::default()
932 };
933
934 system.register_field_actions("button1", actions);
935
936 assert!(system.actions.contains_key("button1"));
939 let registered_actions = &system.actions["button1"];
940 assert!(registered_actions.on_mouse_enter.is_some());
941 assert!(registered_actions.on_mouse_exit.is_some());
942 assert!(registered_actions.on_mouse_down.is_some());
943 assert!(registered_actions.on_mouse_up.is_some());
944 }
945
946 #[test]
947 fn test_submit_form_action() {
948 let action = FieldAction::SubmitForm {
949 url: "https://example.com/submit".to_string(),
950 fields: vec!["name".to_string(), "email".to_string()],
951 include_empty: false,
952 };
953
954 match action {
955 FieldAction::SubmitForm {
956 url,
957 fields,
958 include_empty,
959 } => {
960 assert_eq!(url, "https://example.com/submit");
961 assert_eq!(fields.len(), 2);
962 assert!(!include_empty);
963 }
964 _ => panic!("Expected SubmitForm action"),
965 }
966 }
967
968 #[test]
969 fn test_reset_form_action() {
970 let action = FieldAction::ResetForm {
971 fields: vec!["field1".to_string(), "field2".to_string()],
972 exclude: true,
973 };
974
975 match action {
976 FieldAction::ResetForm { fields, exclude } => {
977 assert_eq!(fields.len(), 2);
978 assert!(exclude);
979 }
980 _ => panic!("Expected ResetForm action"),
981 }
982 }
983
984 #[test]
985 fn test_field_value_action() {
986 let action = FieldAction::SetField {
987 target_field: "total".to_string(),
988 value: FieldValue::Number(100.0),
989 };
990
991 match action {
992 FieldAction::SetField {
993 target_field,
994 value,
995 } => {
996 assert_eq!(target_field, "total");
997 assert_eq!(value, FieldValue::Number(100.0));
998 }
999 _ => panic!("Expected SetField action"),
1000 }
1001 }
1002
1003 #[test]
1004 fn test_action_event_types() {
1005 let focus = ActionEventType::Focus;
1007 let blur = ActionEventType::Blur;
1008 let format = ActionEventType::Format;
1009 let keystroke = ActionEventType::Keystroke;
1010 let calculate = ActionEventType::Calculate;
1011 let validate = ActionEventType::Validate;
1012
1013 assert_eq!(focus, ActionEventType::Focus);
1015 assert_eq!(blur, ActionEventType::Blur);
1016 assert_eq!(format, ActionEventType::Format);
1017 assert_eq!(keystroke, ActionEventType::Keystroke);
1018 assert_eq!(calculate, ActionEventType::Calculate);
1019 assert_eq!(validate, ActionEventType::Validate);
1020 assert_ne!(focus, blur);
1021 }
1022
1023 #[test]
1024 fn test_special_format_types() {
1025 let zip = SpecialFormatType::ZipCode;
1026 let zip_plus = SpecialFormatType::ZipPlus4;
1027 let phone = SpecialFormatType::Phone;
1028 let ssn = SpecialFormatType::SSN;
1029
1030 matches!(zip, SpecialFormatType::ZipCode);
1032 matches!(zip_plus, SpecialFormatType::ZipPlus4);
1033 matches!(phone, SpecialFormatType::Phone);
1034 matches!(ssn, SpecialFormatType::SSN);
1035 }
1036
1037 #[test]
1038 fn test_play_sound_action() {
1039 let action = FieldAction::PlaySound {
1040 sound_name: "beep".to_string(),
1041 volume: 0.5,
1042 };
1043
1044 match action {
1045 FieldAction::PlaySound { sound_name, volume } => {
1046 assert_eq!(sound_name, "beep");
1047 assert_eq!(volume, 0.5);
1048 }
1049 _ => panic!("Expected PlaySound action"),
1050 }
1051 }
1052
1053 #[test]
1054 fn test_import_data_action() {
1055 let action = FieldAction::ImportData {
1056 file_path: "/path/to/data.fdf".to_string(),
1057 };
1058
1059 match action {
1060 FieldAction::ImportData { file_path } => {
1061 assert_eq!(file_path, "/path/to/data.fdf");
1062 }
1063 _ => panic!("Expected ImportData action"),
1064 }
1065 }
1066
1067 #[test]
1068 fn test_show_hide_action() {
1069 let action = FieldAction::ShowHide {
1070 fields: vec!["field1".to_string(), "field2".to_string()],
1071 show: true,
1072 };
1073
1074 match action {
1075 FieldAction::ShowHide { fields, show } => {
1076 assert_eq!(fields.len(), 2);
1077 assert!(show);
1078 }
1079 _ => panic!("Expected ShowHide action"),
1080 }
1081 }
1082
1083 #[test]
1084 fn test_custom_action() {
1085 let mut params = HashMap::new();
1086 params.insert("key1".to_string(), "value1".to_string());
1087 params.insert("key2".to_string(), "value2".to_string());
1088
1089 let action = FieldAction::Custom {
1090 action_type: "custom_type".to_string(),
1091 parameters: params.clone(),
1092 };
1093
1094 match action {
1095 FieldAction::Custom {
1096 action_type,
1097 parameters,
1098 } => {
1099 assert_eq!(action_type, "custom_type");
1100 assert_eq!(parameters.len(), 2);
1101 assert_eq!(parameters.get("key1"), Some(&"value1".to_string()));
1102 }
1103 _ => panic!("Expected Custom action"),
1104 }
1105 }
1106}