1use serde::{Deserialize, Serialize};
25use std::any::Any;
26use std::fmt;
27use std::sync::{Arc, RwLock};
28
29type SubscriberFn<T> = Box<dyn Fn(&T) + Send + Sync>;
31
32type Subscribers<T> = Arc<RwLock<Vec<SubscriberFn<T>>>>;
34
35#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
39pub struct PropertyPath {
40 segments: Vec<String>,
41}
42
43impl PropertyPath {
44 #[must_use]
46 pub fn new(path: &str) -> Self {
47 let segments = path
48 .split('.')
49 .filter(|s| !s.is_empty())
50 .map(String::from)
51 .collect();
52 Self { segments }
53 }
54
55 #[must_use]
57 pub const fn root() -> Self {
58 Self {
59 segments: Vec::new(),
60 }
61 }
62
63 #[must_use]
65 pub fn segments(&self) -> &[String] {
66 &self.segments
67 }
68
69 #[must_use]
71 pub fn is_root(&self) -> bool {
72 self.segments.is_empty()
73 }
74
75 #[must_use]
77 pub fn len(&self) -> usize {
78 self.segments.len()
79 }
80
81 #[must_use]
83 pub fn is_empty(&self) -> bool {
84 self.segments.is_empty()
85 }
86
87 #[must_use]
89 pub fn join(&self, segment: &str) -> Self {
90 let mut segments = self.segments.clone();
91 segments.push(segment.to_string());
92 Self { segments }
93 }
94
95 #[must_use]
97 pub fn parent(&self) -> Option<Self> {
98 if self.segments.is_empty() {
99 None
100 } else {
101 let mut segments = self.segments.clone();
102 segments.pop();
103 Some(Self { segments })
104 }
105 }
106
107 #[must_use]
109 pub fn leaf(&self) -> Option<&str> {
110 self.segments.last().map(String::as_str)
111 }
112
113 #[must_use]
115 pub fn to_string_path(&self) -> String {
116 self.segments.join(".")
117 }
118}
119
120impl fmt::Display for PropertyPath {
121 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
122 write!(f, "{}", self.to_string_path())
123 }
124}
125
126impl From<&str> for PropertyPath {
127 fn from(s: &str) -> Self {
128 Self::new(s)
129 }
130}
131
132#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
134pub enum BindingDirection {
135 #[default]
137 OneWay,
138 TwoWay,
140 OneTime,
142}
143
144#[derive(Debug, Clone)]
146pub struct BindingConfig {
147 pub source: PropertyPath,
149 pub target: String,
151 pub direction: BindingDirection,
153 pub transform: Option<String>,
155 pub fallback: Option<String>,
157}
158
159impl BindingConfig {
160 #[must_use]
162 pub fn one_way(source: impl Into<PropertyPath>, target: impl Into<String>) -> Self {
163 Self {
164 source: source.into(),
165 target: target.into(),
166 direction: BindingDirection::OneWay,
167 transform: None,
168 fallback: None,
169 }
170 }
171
172 #[must_use]
174 pub fn two_way(source: impl Into<PropertyPath>, target: impl Into<String>) -> Self {
175 Self {
176 source: source.into(),
177 target: target.into(),
178 direction: BindingDirection::TwoWay,
179 transform: None,
180 fallback: None,
181 }
182 }
183
184 #[must_use]
186 pub fn transform(mut self, name: impl Into<String>) -> Self {
187 self.transform = Some(name.into());
188 self
189 }
190
191 #[must_use]
193 pub fn fallback(mut self, value: impl Into<String>) -> Self {
194 self.fallback = Some(value.into());
195 self
196 }
197}
198
199pub trait Bindable: Any + Send + Sync {
201 fn bindings(&self) -> Vec<BindingConfig>;
203
204 fn set_bindings(&mut self, bindings: Vec<BindingConfig>);
206
207 fn apply_binding(&mut self, target: &str, value: &dyn Any) -> bool;
209
210 fn get_binding_value(&self, target: &str) -> Option<Box<dyn Any + Send>>;
212}
213
214pub struct ReactiveCell<T> {
216 value: Arc<RwLock<T>>,
217 subscribers: Subscribers<T>,
218}
219
220impl<T: Clone + Send + Sync + 'static> ReactiveCell<T> {
221 pub fn new(value: T) -> Self {
223 Self {
224 value: Arc::new(RwLock::new(value)),
225 subscribers: Arc::new(RwLock::new(Vec::new())),
226 }
227 }
228
229 pub fn get(&self) -> T {
231 self.value
232 .read()
233 .expect("ReactiveCell lock poisoned")
234 .clone()
235 }
236
237 pub fn set(&self, value: T) {
239 {
240 let mut guard = self.value.write().expect("ReactiveCell lock poisoned");
241 *guard = value;
242 }
243 self.notify();
244 }
245
246 pub fn update<F>(&self, f: F)
248 where
249 F: FnOnce(&mut T),
250 {
251 {
252 let mut guard = self.value.write().expect("ReactiveCell lock poisoned");
253 f(&mut guard);
254 }
255 self.notify();
256 }
257
258 pub fn subscribe<F>(&self, callback: F)
260 where
261 F: Fn(&T) + Send + Sync + 'static,
262 {
263 self.subscribers
264 .write()
265 .expect("ReactiveCell lock poisoned")
266 .push(Box::new(callback));
267 }
268
269 fn notify(&self) {
270 let value = self.value.read().expect("ReactiveCell lock poisoned");
271 let subscribers = self.subscribers.read().expect("ReactiveCell lock poisoned");
272 for sub in subscribers.iter() {
273 sub(&value);
274 }
275 }
276}
277
278impl<T: Clone + Send + Sync> Clone for ReactiveCell<T> {
279 fn clone(&self) -> Self {
280 Self {
281 value: self.value.clone(),
282 subscribers: Arc::new(RwLock::new(Vec::new())), }
284 }
285}
286
287impl<T: Clone + Send + Sync + Default + 'static> Default for ReactiveCell<T> {
288 fn default() -> Self {
289 Self::new(T::default())
290 }
291}
292
293impl<T: Clone + Send + Sync + fmt::Debug + 'static> fmt::Debug for ReactiveCell<T> {
294 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
295 f.debug_struct("ReactiveCell")
296 .field(
297 "value",
298 &*self.value.read().expect("ReactiveCell lock poisoned"),
299 )
300 .finish_non_exhaustive()
301 }
302}
303
304pub struct Computed<T> {
306 #[allow(dead_code)]
307 compute: Box<dyn Fn() -> T + Send + Sync>,
308 cached: Arc<RwLock<Option<T>>>,
309 dirty: Arc<RwLock<bool>>,
310}
311
312impl<T: Clone + Send + Sync + 'static> Computed<T> {
313 pub fn new<F>(compute: F) -> Self
315 where
316 F: Fn() -> T + Send + Sync + 'static,
317 {
318 Self {
319 compute: Box::new(compute),
320 cached: Arc::new(RwLock::new(None)),
321 dirty: Arc::new(RwLock::new(true)),
322 }
323 }
324
325 pub fn get(&self) -> T {
327 let dirty = *self.dirty.read().expect("Computed lock poisoned");
328 if dirty {
329 let value = (self.compute)();
330 *self.cached.write().expect("Computed lock poisoned") = Some(value.clone());
331 *self.dirty.write().expect("Computed lock poisoned") = false;
332 value
333 } else {
334 self.cached
335 .read()
336 .expect("Computed lock poisoned")
337 .clone()
338 .expect("Computed cache should contain value when not dirty")
339 }
340 }
341
342 pub fn invalidate(&self) {
344 *self.dirty.write().expect("Computed lock poisoned") = true;
345 }
346}
347
348impl<T: Clone + Send + Sync + fmt::Debug + 'static> fmt::Debug for Computed<T> {
349 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
350 f.debug_struct("Computed")
351 .field(
352 "cached",
353 &*self.cached.read().expect("Computed lock poisoned"),
354 )
355 .field(
356 "dirty",
357 &*self.dirty.read().expect("Computed lock poisoned"),
358 )
359 .finish_non_exhaustive()
360 }
361}
362
363#[derive(Debug, Clone, Serialize, Deserialize)]
365pub struct BindingExpression {
366 pub expression: String,
368 pub dependencies: Vec<PropertyPath>,
370}
371
372impl BindingExpression {
373 #[must_use]
375 pub fn new(expression: impl Into<String>) -> Self {
376 let expression = expression.into();
377 let dependencies = Self::parse_dependencies(&expression);
378 Self {
379 expression,
380 dependencies,
381 }
382 }
383
384 #[must_use]
386 pub fn property(path: impl Into<PropertyPath>) -> Self {
387 let path: PropertyPath = path.into();
388 let expression = format!("{{{{ {} }}}}", path.to_string_path());
389 Self {
390 expression,
391 dependencies: vec![path],
392 }
393 }
394
395 #[must_use]
397 pub fn is_simple_property(&self) -> bool {
398 self.dependencies.len() == 1
399 && self.expression.trim().starts_with("{{")
400 && self.expression.trim().ends_with("}}")
401 }
402
403 #[must_use]
405 pub fn as_property(&self) -> Option<&PropertyPath> {
406 if self.is_simple_property() {
407 self.dependencies.first()
408 } else {
409 None
410 }
411 }
412
413 fn parse_dependencies(expression: &str) -> Vec<PropertyPath> {
414 let mut deps = Vec::new();
415 let mut in_binding = false;
416 let mut current = String::new();
417
418 let chars: Vec<char> = expression.chars().collect();
419 let mut i = 0;
420
421 while i < chars.len() {
422 if i + 1 < chars.len() && chars[i] == '{' && chars[i + 1] == '{' {
423 in_binding = true;
424 i += 2;
425 continue;
426 }
427
428 if i + 1 < chars.len() && chars[i] == '}' && chars[i + 1] == '}' {
429 if !current.is_empty() {
430 let path_str = current.split('|').next().unwrap_or("").trim();
432 if !path_str.is_empty() && !path_str.contains(|c: char| c.is_whitespace()) {
433 deps.push(PropertyPath::new(path_str));
434 }
435 current.clear();
436 }
437 in_binding = false;
438 i += 2;
439 continue;
440 }
441
442 if in_binding {
443 current.push(chars[i]);
444 }
445
446 i += 1;
447 }
448
449 deps
450 }
451}
452
453#[derive(Debug, Clone, Serialize, Deserialize)]
455pub struct EventBinding {
456 pub event: String,
458 pub action: ActionBinding,
460}
461
462#[derive(Debug, Clone, Serialize, Deserialize)]
464pub enum ActionBinding {
465 SetProperty {
467 path: PropertyPath,
469 value: String,
471 },
472 ToggleProperty {
474 path: PropertyPath,
476 },
477 IncrementProperty {
479 path: PropertyPath,
481 amount: Option<f64>,
483 },
484 Navigate {
486 route: String,
488 },
489 Dispatch {
491 message: String,
493 payload: Option<String>,
495 },
496 Batch {
498 actions: Vec<Self>,
500 },
501}
502
503impl EventBinding {
504 #[must_use]
506 pub fn new(event: impl Into<String>, action: ActionBinding) -> Self {
507 Self {
508 event: event.into(),
509 action,
510 }
511 }
512
513 #[must_use]
515 pub fn on_click(action: ActionBinding) -> Self {
516 Self::new("click", action)
517 }
518
519 #[must_use]
521 pub fn on_change(action: ActionBinding) -> Self {
522 Self::new("change", action)
523 }
524}
525
526impl ActionBinding {
527 #[must_use]
529 pub fn set(path: impl Into<PropertyPath>, value: impl Into<String>) -> Self {
530 Self::SetProperty {
531 path: path.into(),
532 value: value.into(),
533 }
534 }
535
536 #[must_use]
538 pub fn toggle(path: impl Into<PropertyPath>) -> Self {
539 Self::ToggleProperty { path: path.into() }
540 }
541
542 #[must_use]
544 pub fn increment(path: impl Into<PropertyPath>) -> Self {
545 Self::IncrementProperty {
546 path: path.into(),
547 amount: None,
548 }
549 }
550
551 #[must_use]
553 pub fn increment_by(path: impl Into<PropertyPath>, amount: f64) -> Self {
554 Self::IncrementProperty {
555 path: path.into(),
556 amount: Some(amount),
557 }
558 }
559
560 #[must_use]
562 pub fn navigate(route: impl Into<String>) -> Self {
563 Self::Navigate {
564 route: route.into(),
565 }
566 }
567
568 #[must_use]
570 pub fn dispatch(message: impl Into<String>) -> Self {
571 Self::Dispatch {
572 message: message.into(),
573 payload: None,
574 }
575 }
576
577 #[must_use]
579 pub fn dispatch_with(message: impl Into<String>, payload: impl Into<String>) -> Self {
580 Self::Dispatch {
581 message: message.into(),
582 payload: Some(payload.into()),
583 }
584 }
585
586 #[must_use]
588 pub fn batch(actions: impl IntoIterator<Item = Self>) -> Self {
589 Self::Batch {
590 actions: actions.into_iter().collect(),
591 }
592 }
593}
594
595#[derive(Debug, Default)]
607pub struct BindingManager {
608 bindings: Vec<ActiveBinding>,
610 debounce_ms: Option<u32>,
612 pending_updates: Vec<PendingUpdate>,
614}
615
616#[derive(Debug, Clone)]
618pub struct ActiveBinding {
619 pub id: BindingId,
621 pub widget_id: String,
623 pub config: BindingConfig,
625 pub current_value: Option<String>,
627 pub active: bool,
629}
630
631#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
633pub struct BindingId(pub u64);
634
635#[derive(Debug, Clone)]
637pub struct PendingUpdate {
638 pub source: UpdateSource,
640 pub path: PropertyPath,
642 pub value: String,
644 pub timestamp: u64,
646}
647
648#[derive(Debug, Clone, Copy, PartialEq, Eq)]
650pub enum UpdateSource {
651 State,
653 Widget,
655}
656
657impl BindingManager {
658 #[must_use]
660 pub fn new() -> Self {
661 Self::default()
662 }
663
664 #[must_use]
666 pub fn with_debounce(mut self, ms: u32) -> Self {
667 self.debounce_ms = Some(ms);
668 self
669 }
670
671 pub fn register(&mut self, widget_id: impl Into<String>, config: BindingConfig) -> BindingId {
673 let id = BindingId(self.bindings.len() as u64);
674 self.bindings.push(ActiveBinding {
675 id,
676 widget_id: widget_id.into(),
677 config,
678 current_value: None,
679 active: true,
680 });
681 id
682 }
683
684 pub fn unregister(&mut self, id: BindingId) {
686 if let Some(binding) = self.bindings.iter_mut().find(|b| b.id == id) {
687 binding.active = false;
688 }
689 }
690
691 #[must_use]
693 pub fn bindings_for_widget(&self, widget_id: &str) -> Vec<&ActiveBinding> {
694 self.bindings
695 .iter()
696 .filter(|b| b.active && b.widget_id == widget_id)
697 .collect()
698 }
699
700 #[must_use]
702 pub fn bindings_for_path(&self, path: &PropertyPath) -> Vec<&ActiveBinding> {
703 self.bindings
704 .iter()
705 .filter(|b| b.active && &b.config.source == path)
706 .collect()
707 }
708
709 pub fn on_state_change(&mut self, path: &PropertyPath, value: &str) -> Vec<WidgetUpdate> {
711 let mut updates = Vec::new();
712
713 for binding in &mut self.bindings {
714 if !binding.active {
715 continue;
716 }
717
718 if &binding.config.source == path
720 || path
721 .to_string_path()
722 .starts_with(&binding.config.source.to_string_path())
723 {
724 binding.current_value = Some(value.to_string());
725
726 updates.push(WidgetUpdate {
727 widget_id: binding.widget_id.clone(),
728 property: binding.config.target.clone(),
729 value: value.to_string(),
730 });
731 }
732 }
733
734 updates
735 }
736
737 pub fn on_widget_change(
739 &mut self,
740 widget_id: &str,
741 property: &str,
742 value: &str,
743 ) -> Vec<StateUpdate> {
744 let mut updates = Vec::new();
745
746 for binding in &self.bindings {
747 if !binding.active {
748 continue;
749 }
750
751 if binding.config.direction != BindingDirection::TwoWay {
753 continue;
754 }
755
756 if binding.widget_id == widget_id && binding.config.target == property {
757 updates.push(StateUpdate {
758 path: binding.config.source.clone(),
759 value: value.to_string(),
760 });
761 }
762 }
763
764 updates
765 }
766
767 pub fn queue_update(&mut self, source: UpdateSource, path: PropertyPath, value: String) {
769 self.pending_updates.push(PendingUpdate {
770 source,
771 path,
772 value,
773 timestamp: 0, });
775 }
776
777 pub fn flush(&mut self) -> (Vec<WidgetUpdate>, Vec<StateUpdate>) {
779 let mut widget_updates = Vec::new();
780 let mut state_updates = Vec::new();
781
782 let updates: Vec<PendingUpdate> = self.pending_updates.drain(..).collect();
784
785 for update in updates {
786 match update.source {
787 UpdateSource::State => {
788 widget_updates.extend(self.on_state_change(&update.path, &update.value));
789 }
790 UpdateSource::Widget => {
791 state_updates.push(StateUpdate {
793 path: update.path,
794 value: update.value,
795 });
796 }
797 }
798 }
799
800 (widget_updates, state_updates)
801 }
802
803 #[must_use]
805 pub fn active_count(&self) -> usize {
806 self.bindings.iter().filter(|b| b.active).count()
807 }
808
809 pub fn clear(&mut self) {
811 self.bindings.clear();
812 self.pending_updates.clear();
813 }
814}
815
816#[derive(Debug, Clone)]
818pub struct WidgetUpdate {
819 pub widget_id: String,
821 pub property: String,
823 pub value: String,
825}
826
827#[derive(Debug, Clone)]
829pub struct StateUpdate {
830 pub path: PropertyPath,
832 pub value: String,
834}
835
836pub trait ValueConverter: Send + Sync {
842 fn convert(&self, value: &str) -> Result<String, ConversionError>;
844
845 fn convert_back(&self, value: &str) -> Result<String, ConversionError>;
847}
848
849#[derive(Debug, Clone)]
851pub struct ConversionError {
852 pub message: String,
854}
855
856impl fmt::Display for ConversionError {
857 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
858 write!(f, "conversion error: {}", self.message)
859 }
860}
861
862impl std::error::Error for ConversionError {}
863
864#[derive(Debug, Default)]
866pub struct IdentityConverter;
867
868impl ValueConverter for IdentityConverter {
869 fn convert(&self, value: &str) -> Result<String, ConversionError> {
870 Ok(value.to_string())
871 }
872
873 fn convert_back(&self, value: &str) -> Result<String, ConversionError> {
874 Ok(value.to_string())
875 }
876}
877
878#[derive(Debug, Default)]
880pub struct BoolToStringConverter {
881 pub true_string: String,
883 pub false_string: String,
885}
886
887impl BoolToStringConverter {
888 #[must_use]
890 pub fn new() -> Self {
891 Self {
892 true_string: "true".to_string(),
893 false_string: "false".to_string(),
894 }
895 }
896
897 #[must_use]
899 pub fn with_strings(true_str: impl Into<String>, false_str: impl Into<String>) -> Self {
900 Self {
901 true_string: true_str.into(),
902 false_string: false_str.into(),
903 }
904 }
905}
906
907impl ValueConverter for BoolToStringConverter {
908 fn convert(&self, value: &str) -> Result<String, ConversionError> {
909 match value {
910 "true" | "1" | "yes" => Ok(self.true_string.clone()),
911 "false" | "0" | "no" => Ok(self.false_string.clone()),
912 _ => Err(ConversionError {
913 message: format!("cannot convert '{value}' to bool"),
914 }),
915 }
916 }
917
918 fn convert_back(&self, value: &str) -> Result<String, ConversionError> {
919 if value == self.true_string {
920 Ok("true".to_string())
921 } else if value == self.false_string {
922 Ok("false".to_string())
923 } else {
924 Err(ConversionError {
925 message: format!("cannot convert '{value}' back to bool"),
926 })
927 }
928 }
929}
930
931#[derive(Debug, Default)]
933pub struct NumberFormatConverter {
934 pub decimals: usize,
936 pub prefix: String,
938 pub suffix: String,
940}
941
942impl NumberFormatConverter {
943 #[must_use]
945 pub fn new() -> Self {
946 Self::default()
947 }
948
949 #[must_use]
951 pub fn decimals(mut self, places: usize) -> Self {
952 self.decimals = places;
953 self
954 }
955
956 #[must_use]
958 pub fn prefix(mut self, prefix: impl Into<String>) -> Self {
959 self.prefix = prefix.into();
960 self
961 }
962
963 #[must_use]
965 pub fn suffix(mut self, suffix: impl Into<String>) -> Self {
966 self.suffix = suffix.into();
967 self
968 }
969}
970
971impl ValueConverter for NumberFormatConverter {
972 fn convert(&self, value: &str) -> Result<String, ConversionError> {
973 let num: f64 = value.parse().map_err(|_| ConversionError {
974 message: format!("cannot parse '{value}' as number"),
975 })?;
976
977 let formatted = format!("{:.prec$}", num, prec = self.decimals);
978 Ok(format!("{}{}{}", self.prefix, formatted, self.suffix))
979 }
980
981 fn convert_back(&self, value: &str) -> Result<String, ConversionError> {
982 let stripped = value
984 .strip_prefix(&self.prefix)
985 .unwrap_or(value)
986 .strip_suffix(&self.suffix)
987 .unwrap_or(value)
988 .trim();
989
990 let _: f64 = stripped.parse().map_err(|_| ConversionError {
992 message: format!("cannot parse '{stripped}' as number"),
993 })?;
994
995 Ok(stripped.to_string())
996 }
997}
998
999#[cfg(test)]
1000mod tests {
1001 use super::*;
1002
1003 #[test]
1008 fn test_property_path_new() {
1009 let path = PropertyPath::new("user.profile.name");
1010 assert_eq!(path.segments(), &["user", "profile", "name"]);
1011 }
1012
1013 #[test]
1014 fn test_property_path_root() {
1015 let path = PropertyPath::root();
1016 assert!(path.is_root());
1017 assert!(path.is_empty());
1018 }
1019
1020 #[test]
1021 fn test_property_path_len() {
1022 let path = PropertyPath::new("a.b.c");
1023 assert_eq!(path.len(), 3);
1024 }
1025
1026 #[test]
1027 fn test_property_path_join() {
1028 let path = PropertyPath::new("user");
1029 let joined = path.join("name");
1030 assert_eq!(joined.to_string_path(), "user.name");
1031 }
1032
1033 #[test]
1034 fn test_property_path_parent() {
1035 let path = PropertyPath::new("user.profile.name");
1036 let parent = path.parent().unwrap();
1037 assert_eq!(parent.to_string_path(), "user.profile");
1038 }
1039
1040 #[test]
1041 fn test_property_path_leaf() {
1042 let path = PropertyPath::new("user.profile.name");
1043 assert_eq!(path.leaf(), Some("name"));
1044 }
1045
1046 #[test]
1047 fn test_property_path_display() {
1048 let path = PropertyPath::new("a.b.c");
1049 assert_eq!(format!("{path}"), "a.b.c");
1050 }
1051
1052 #[test]
1053 fn test_property_path_from_str() {
1054 let path: PropertyPath = "user.name".into();
1055 assert_eq!(path.segments(), &["user", "name"]);
1056 }
1057
1058 #[test]
1063 fn test_binding_config_one_way() {
1064 let config = BindingConfig::one_way("count", "value");
1065 assert_eq!(config.source.to_string_path(), "count");
1066 assert_eq!(config.target, "value");
1067 assert_eq!(config.direction, BindingDirection::OneWay);
1068 }
1069
1070 #[test]
1071 fn test_binding_config_two_way() {
1072 let config = BindingConfig::two_way("user.name", "text");
1073 assert_eq!(config.direction, BindingDirection::TwoWay);
1074 }
1075
1076 #[test]
1077 fn test_binding_config_transform() {
1078 let config = BindingConfig::one_way("count", "label").transform("toString");
1079 assert_eq!(config.transform, Some("toString".to_string()));
1080 }
1081
1082 #[test]
1083 fn test_binding_config_fallback() {
1084 let config = BindingConfig::one_way("user.name", "text").fallback("Anonymous");
1085 assert_eq!(config.fallback, Some("Anonymous".to_string()));
1086 }
1087
1088 #[test]
1093 fn test_reactive_cell_new() {
1094 let cell = ReactiveCell::new(42);
1095 assert_eq!(cell.get(), 42);
1096 }
1097
1098 #[test]
1099 fn test_reactive_cell_set() {
1100 let cell = ReactiveCell::new(0);
1101 cell.set(100);
1102 assert_eq!(cell.get(), 100);
1103 }
1104
1105 #[test]
1106 fn test_reactive_cell_update() {
1107 let cell = ReactiveCell::new(10);
1108 cell.update(|v| *v *= 2);
1109 assert_eq!(cell.get(), 20);
1110 }
1111
1112 #[test]
1113 fn test_reactive_cell_subscribe() {
1114 use std::sync::atomic::{AtomicI32, Ordering};
1115
1116 let cell = ReactiveCell::new(0);
1117 let count = Arc::new(AtomicI32::new(0));
1118 let count_clone = count.clone();
1119
1120 cell.subscribe(move |v| {
1121 count_clone.store(*v, Ordering::SeqCst);
1122 });
1123
1124 cell.set(42);
1125 assert_eq!(count.load(Ordering::SeqCst), 42);
1126 }
1127
1128 #[test]
1129 fn test_reactive_cell_default() {
1130 let cell: ReactiveCell<i32> = ReactiveCell::default();
1131 assert_eq!(cell.get(), 0);
1132 }
1133
1134 #[test]
1135 fn test_reactive_cell_clone() {
1136 let cell1 = ReactiveCell::new(10);
1137 let cell2 = cell1.clone();
1138
1139 cell1.set(20);
1140 assert_eq!(cell2.get(), 20); }
1142
1143 #[test]
1148 fn test_computed_new() {
1149 let computed = Computed::new(|| 42);
1150 assert_eq!(computed.get(), 42);
1151 }
1152
1153 #[test]
1154 fn test_computed_caches() {
1155 use std::sync::atomic::{AtomicUsize, Ordering};
1156
1157 let call_count = Arc::new(AtomicUsize::new(0));
1158 let call_count_clone = call_count.clone();
1159
1160 let computed = Computed::new(move || {
1161 call_count_clone.fetch_add(1, Ordering::SeqCst);
1162 42
1163 });
1164
1165 computed.get();
1166 computed.get();
1167 computed.get();
1168
1169 assert_eq!(call_count.load(Ordering::SeqCst), 1); }
1171
1172 #[test]
1173 fn test_computed_invalidate() {
1174 use std::sync::atomic::{AtomicUsize, Ordering};
1175
1176 let call_count = Arc::new(AtomicUsize::new(0));
1177 let call_count_clone = call_count.clone();
1178
1179 let computed = Computed::new(move || {
1180 call_count_clone.fetch_add(1, Ordering::SeqCst);
1181 42
1182 });
1183
1184 computed.get();
1185 computed.invalidate();
1186 computed.get();
1187
1188 assert_eq!(call_count.load(Ordering::SeqCst), 2); }
1190
1191 #[test]
1196 fn test_binding_expression_new() {
1197 let expr = BindingExpression::new("{{ user.name }}");
1198 assert_eq!(expr.dependencies.len(), 1);
1199 assert_eq!(expr.dependencies[0].to_string_path(), "user.name");
1200 }
1201
1202 #[test]
1203 fn test_binding_expression_property() {
1204 let expr = BindingExpression::property("count");
1205 assert!(expr.is_simple_property());
1206 assert_eq!(expr.as_property().unwrap().to_string_path(), "count");
1207 }
1208
1209 #[test]
1210 fn test_binding_expression_with_transform() {
1211 let expr = BindingExpression::new("{{ count | format }}");
1212 assert_eq!(expr.dependencies[0].to_string_path(), "count");
1213 }
1214
1215 #[test]
1216 fn test_binding_expression_multiple_deps() {
1217 let expr = BindingExpression::new("{{ first }} and {{ second }}");
1218 assert_eq!(expr.dependencies.len(), 2);
1219 }
1220
1221 #[test]
1226 fn test_event_binding_new() {
1227 let binding = EventBinding::new("click", ActionBinding::toggle("visible"));
1228 assert_eq!(binding.event, "click");
1229 }
1230
1231 #[test]
1232 fn test_event_binding_on_click() {
1233 let binding = EventBinding::on_click(ActionBinding::dispatch("submit"));
1234 assert_eq!(binding.event, "click");
1235 }
1236
1237 #[test]
1238 fn test_event_binding_on_change() {
1239 let binding = EventBinding::on_change(ActionBinding::set("value", "new"));
1240 assert_eq!(binding.event, "change");
1241 }
1242
1243 #[test]
1248 fn test_action_binding_set() {
1249 let action = ActionBinding::set("user.name", "Alice");
1250 if let ActionBinding::SetProperty { path, value } = action {
1251 assert_eq!(path.to_string_path(), "user.name");
1252 assert_eq!(value, "Alice");
1253 } else {
1254 panic!("Expected SetProperty");
1255 }
1256 }
1257
1258 #[test]
1259 fn test_action_binding_toggle() {
1260 let action = ActionBinding::toggle("visible");
1261 if let ActionBinding::ToggleProperty { path } = action {
1262 assert_eq!(path.to_string_path(), "visible");
1263 } else {
1264 panic!("Expected ToggleProperty");
1265 }
1266 }
1267
1268 #[test]
1269 fn test_action_binding_increment() {
1270 let action = ActionBinding::increment("count");
1271 if let ActionBinding::IncrementProperty { path, amount } = action {
1272 assert_eq!(path.to_string_path(), "count");
1273 assert!(amount.is_none());
1274 } else {
1275 panic!("Expected IncrementProperty");
1276 }
1277 }
1278
1279 #[test]
1280 fn test_action_binding_increment_by() {
1281 let action = ActionBinding::increment_by("score", 10.0);
1282 if let ActionBinding::IncrementProperty { amount, .. } = action {
1283 assert_eq!(amount, Some(10.0));
1284 } else {
1285 panic!("Expected IncrementProperty");
1286 }
1287 }
1288
1289 #[test]
1290 fn test_action_binding_navigate() {
1291 let action = ActionBinding::navigate("/home");
1292 if let ActionBinding::Navigate { route } = action {
1293 assert_eq!(route, "/home");
1294 } else {
1295 panic!("Expected Navigate");
1296 }
1297 }
1298
1299 #[test]
1300 fn test_action_binding_dispatch() {
1301 let action = ActionBinding::dispatch("submit");
1302 if let ActionBinding::Dispatch { message, payload } = action {
1303 assert_eq!(message, "submit");
1304 assert!(payload.is_none());
1305 } else {
1306 panic!("Expected Dispatch");
1307 }
1308 }
1309
1310 #[test]
1311 fn test_action_binding_dispatch_with() {
1312 let action = ActionBinding::dispatch_with("submit", "form_data");
1313 if let ActionBinding::Dispatch { message, payload } = action {
1314 assert_eq!(message, "submit");
1315 assert_eq!(payload, Some("form_data".to_string()));
1316 } else {
1317 panic!("Expected Dispatch");
1318 }
1319 }
1320
1321 #[test]
1322 fn test_action_binding_batch() {
1323 let action = ActionBinding::batch([
1324 ActionBinding::increment("count"),
1325 ActionBinding::navigate("/next"),
1326 ]);
1327 if let ActionBinding::Batch { actions } = action {
1328 assert_eq!(actions.len(), 2);
1329 } else {
1330 panic!("Expected Batch");
1331 }
1332 }
1333
1334 #[test]
1339 fn test_binding_manager_new() {
1340 let manager = BindingManager::new();
1341 assert_eq!(manager.active_count(), 0);
1342 }
1343
1344 #[test]
1345 fn test_binding_manager_register() {
1346 let mut manager = BindingManager::new();
1347 let id = manager.register("widget1", BindingConfig::one_way("count", "text"));
1348 assert_eq!(id.0, 0);
1349 assert_eq!(manager.active_count(), 1);
1350 }
1351
1352 #[test]
1353 fn test_binding_manager_unregister() {
1354 let mut manager = BindingManager::new();
1355 let id = manager.register("widget1", BindingConfig::one_way("count", "text"));
1356 manager.unregister(id);
1357 assert_eq!(manager.active_count(), 0);
1358 }
1359
1360 #[test]
1361 fn test_binding_manager_bindings_for_widget() {
1362 let mut manager = BindingManager::new();
1363 manager.register("widget1", BindingConfig::one_way("count", "text"));
1364 manager.register("widget1", BindingConfig::one_way("name", "label"));
1365 manager.register("widget2", BindingConfig::one_way("other", "value"));
1366
1367 let bindings = manager.bindings_for_widget("widget1");
1368 assert_eq!(bindings.len(), 2);
1369 }
1370
1371 #[test]
1372 fn test_binding_manager_bindings_for_path() {
1373 let mut manager = BindingManager::new();
1374 manager.register("widget1", BindingConfig::one_way("user.name", "text"));
1375 manager.register("widget2", BindingConfig::one_way("user.name", "label"));
1376
1377 let path = PropertyPath::new("user.name");
1378 let bindings = manager.bindings_for_path(&path);
1379 assert_eq!(bindings.len(), 2);
1380 }
1381
1382 #[test]
1383 fn test_binding_manager_on_state_change() {
1384 let mut manager = BindingManager::new();
1385 manager.register("widget1", BindingConfig::one_way("count", "text"));
1386 manager.register("widget2", BindingConfig::one_way("count", "label"));
1387
1388 let path = PropertyPath::new("count");
1389 let updates = manager.on_state_change(&path, "42");
1390
1391 assert_eq!(updates.len(), 2);
1392 assert!(updates.iter().any(|u| u.widget_id == "widget1"));
1393 assert!(updates.iter().any(|u| u.widget_id == "widget2"));
1394 }
1395
1396 #[test]
1397 fn test_binding_manager_on_widget_change_two_way() {
1398 let mut manager = BindingManager::new();
1399 manager.register("input1", BindingConfig::two_way("user.name", "value"));
1400
1401 let updates = manager.on_widget_change("input1", "value", "Alice");
1402
1403 assert_eq!(updates.len(), 1);
1404 assert_eq!(updates[0].path.to_string_path(), "user.name");
1405 assert_eq!(updates[0].value, "Alice");
1406 }
1407
1408 #[test]
1409 fn test_binding_manager_on_widget_change_one_way_no_propagate() {
1410 let mut manager = BindingManager::new();
1411 manager.register("label1", BindingConfig::one_way("count", "text"));
1412
1413 let updates = manager.on_widget_change("label1", "text", "new value");
1414
1415 assert!(updates.is_empty()); }
1417
1418 #[test]
1419 fn test_binding_manager_with_debounce() {
1420 let manager = BindingManager::new().with_debounce(100);
1421 assert_eq!(manager.debounce_ms, Some(100));
1422 }
1423
1424 #[test]
1425 fn test_binding_manager_queue_and_flush() {
1426 let mut manager = BindingManager::new();
1427 manager.register("widget1", BindingConfig::one_way("count", "text"));
1428
1429 manager.queue_update(
1430 UpdateSource::State,
1431 PropertyPath::new("count"),
1432 "42".to_string(),
1433 );
1434
1435 let (widget_updates, _) = manager.flush();
1436 assert_eq!(widget_updates.len(), 1);
1437 }
1438
1439 #[test]
1440 fn test_binding_manager_clear() {
1441 let mut manager = BindingManager::new();
1442 manager.register("w1", BindingConfig::one_way("a", "b"));
1443 manager.register("w2", BindingConfig::one_way("c", "d"));
1444 manager.clear();
1445 assert_eq!(manager.active_count(), 0);
1446 }
1447
1448 #[test]
1453 fn test_identity_converter() {
1454 let converter = IdentityConverter;
1455 assert_eq!(converter.convert("hello").unwrap(), "hello");
1456 assert_eq!(converter.convert_back("world").unwrap(), "world");
1457 }
1458
1459 #[test]
1460 fn test_bool_to_string_converter() {
1461 let converter = BoolToStringConverter::new();
1462 assert_eq!(converter.convert("true").unwrap(), "true");
1463 assert_eq!(converter.convert("false").unwrap(), "false");
1464 assert_eq!(converter.convert("1").unwrap(), "true");
1465 assert_eq!(converter.convert("0").unwrap(), "false");
1466 }
1467
1468 #[test]
1469 fn test_bool_to_string_converter_custom() {
1470 let converter = BoolToStringConverter::with_strings("Yes", "No");
1471 assert_eq!(converter.convert("true").unwrap(), "Yes");
1472 assert_eq!(converter.convert("false").unwrap(), "No");
1473 assert_eq!(converter.convert_back("Yes").unwrap(), "true");
1474 assert_eq!(converter.convert_back("No").unwrap(), "false");
1475 }
1476
1477 #[test]
1478 fn test_bool_to_string_converter_error() {
1479 let converter = BoolToStringConverter::new();
1480 assert!(converter.convert("invalid").is_err());
1481 }
1482
1483 #[test]
1484 fn test_number_format_converter() {
1485 let converter = NumberFormatConverter::new().decimals(2);
1486 assert_eq!(converter.convert("42").unwrap(), "42.00");
1487 assert_eq!(converter.convert("3.14159").unwrap(), "3.14");
1488 }
1489
1490 #[test]
1491 fn test_number_format_converter_with_prefix_suffix() {
1492 let converter = NumberFormatConverter::new()
1493 .decimals(2)
1494 .prefix("$")
1495 .suffix(" USD");
1496 assert_eq!(converter.convert("100").unwrap(), "$100.00 USD");
1497 assert_eq!(converter.convert_back("$100.00 USD").unwrap(), "100.00");
1498 }
1499
1500 #[test]
1501 fn test_number_format_converter_error() {
1502 let converter = NumberFormatConverter::new();
1503 assert!(converter.convert("not a number").is_err());
1504 }
1505
1506 #[test]
1507 fn test_conversion_error_display() {
1508 let err = ConversionError {
1509 message: "test error".to_string(),
1510 };
1511 assert!(err.to_string().contains("test error"));
1512 }
1513
1514 #[test]
1519 fn test_widget_update_struct() {
1520 let update = WidgetUpdate {
1521 widget_id: "input1".to_string(),
1522 property: "value".to_string(),
1523 value: "Hello".to_string(),
1524 };
1525 assert_eq!(update.widget_id, "input1");
1526 }
1527
1528 #[test]
1529 fn test_state_update_struct() {
1530 let update = StateUpdate {
1531 path: PropertyPath::new("user.name"),
1532 value: "Alice".to_string(),
1533 };
1534 assert_eq!(update.path.to_string_path(), "user.name");
1535 }
1536
1537 #[test]
1538 fn test_binding_id_default() {
1539 let id = BindingId::default();
1540 assert_eq!(id.0, 0);
1541 }
1542
1543 #[test]
1544 fn test_update_source_eq() {
1545 assert_eq!(UpdateSource::State, UpdateSource::State);
1546 assert_eq!(UpdateSource::Widget, UpdateSource::Widget);
1547 assert_ne!(UpdateSource::State, UpdateSource::Widget);
1548 }
1549
1550 #[test]
1555 fn test_property_path_empty_string() {
1556 let path = PropertyPath::new("");
1557 assert!(path.is_empty());
1558 assert!(path.is_root());
1559 }
1560
1561 #[test]
1562 fn test_property_path_trailing_dots() {
1563 let path = PropertyPath::new("user.name.");
1564 assert_eq!(path.segments(), &["user", "name"]);
1565 }
1566
1567 #[test]
1568 fn test_property_path_leading_dots() {
1569 let path = PropertyPath::new(".user.name");
1570 assert_eq!(path.segments(), &["user", "name"]);
1571 }
1572
1573 #[test]
1574 fn test_property_path_multiple_dots() {
1575 let path = PropertyPath::new("user..name");
1576 assert_eq!(path.segments(), &["user", "name"]);
1577 }
1578
1579 #[test]
1580 fn test_property_path_parent_of_root() {
1581 let path = PropertyPath::root();
1582 assert!(path.parent().is_none());
1583 }
1584
1585 #[test]
1586 fn test_property_path_leaf_of_root() {
1587 let path = PropertyPath::root();
1588 assert!(path.leaf().is_none());
1589 }
1590
1591 #[test]
1592 fn test_property_path_single_segment() {
1593 let path = PropertyPath::new("count");
1594 assert_eq!(path.len(), 1);
1595 assert_eq!(path.leaf(), Some("count"));
1596 let parent = path.parent().unwrap();
1597 assert!(parent.is_empty());
1598 }
1599
1600 #[test]
1601 fn test_property_path_hash() {
1602 use std::collections::HashSet;
1603
1604 let mut set = HashSet::new();
1605 set.insert(PropertyPath::new("user.name"));
1606 set.insert(PropertyPath::new("user.email"));
1607
1608 assert!(set.contains(&PropertyPath::new("user.name")));
1609 assert!(!set.contains(&PropertyPath::new("other")));
1610 }
1611
1612 #[test]
1613 fn test_property_path_clone() {
1614 let path = PropertyPath::new("a.b.c");
1615 let cloned = path.clone();
1616 assert_eq!(path, cloned);
1617 }
1618
1619 #[test]
1620 fn test_property_path_debug() {
1621 let path = PropertyPath::new("user.name");
1622 let debug = format!("{:?}", path);
1623 assert!(debug.contains("PropertyPath"));
1624 }
1625
1626 #[test]
1627 fn test_property_path_serialize() {
1628 let path = PropertyPath::new("user.name");
1629 let json = serde_json::to_string(&path).unwrap();
1630 assert!(json.contains("user"));
1631 assert!(json.contains("name"));
1632 }
1633
1634 #[test]
1635 fn test_property_path_deserialize() {
1636 let json = r#"{"segments":["user","profile"]}"#;
1637 let path: PropertyPath = serde_json::from_str(json).unwrap();
1638 assert_eq!(path.to_string_path(), "user.profile");
1639 }
1640
1641 #[test]
1646 fn test_binding_direction_default() {
1647 assert_eq!(BindingDirection::default(), BindingDirection::OneWay);
1648 }
1649
1650 #[test]
1651 fn test_binding_direction_all_variants() {
1652 assert_eq!(BindingDirection::OneWay, BindingDirection::OneWay);
1653 assert_eq!(BindingDirection::TwoWay, BindingDirection::TwoWay);
1654 assert_eq!(BindingDirection::OneTime, BindingDirection::OneTime);
1655 }
1656
1657 #[test]
1658 fn test_binding_direction_clone() {
1659 let dir = BindingDirection::TwoWay;
1660 let cloned = dir;
1661 assert_eq!(dir, cloned);
1662 }
1663
1664 #[test]
1665 fn test_binding_direction_debug() {
1666 let dir = BindingDirection::OneTime;
1667 let debug = format!("{:?}", dir);
1668 assert!(debug.contains("OneTime"));
1669 }
1670
1671 #[test]
1676 fn test_binding_config_chained_builders() {
1677 let config = BindingConfig::one_way("count", "label")
1678 .transform("toString")
1679 .fallback("N/A");
1680
1681 assert_eq!(config.transform, Some("toString".to_string()));
1682 assert_eq!(config.fallback, Some("N/A".to_string()));
1683 }
1684
1685 #[test]
1686 fn test_binding_config_clone() {
1687 let config = BindingConfig::two_way("user.name", "value");
1688 let cloned = config.clone();
1689 assert_eq!(cloned.direction, BindingDirection::TwoWay);
1690 }
1691
1692 #[test]
1693 fn test_binding_config_debug() {
1694 let config = BindingConfig::one_way("path", "prop");
1695 let debug = format!("{:?}", config);
1696 assert!(debug.contains("BindingConfig"));
1697 }
1698
1699 #[test]
1704 fn test_reactive_cell_multiple_subscribers() {
1705 use std::sync::atomic::{AtomicI32, Ordering};
1706
1707 let cell = ReactiveCell::new(0);
1708 let count1 = Arc::new(AtomicI32::new(0));
1709 let count2 = Arc::new(AtomicI32::new(0));
1710 let c1 = count1.clone();
1711 let c2 = count2.clone();
1712
1713 cell.subscribe(move |v| {
1714 c1.store(*v, Ordering::SeqCst);
1715 });
1716 cell.subscribe(move |v| {
1717 c2.store(*v * 2, Ordering::SeqCst);
1718 });
1719
1720 cell.set(10);
1721 assert_eq!(count1.load(Ordering::SeqCst), 10);
1722 assert_eq!(count2.load(Ordering::SeqCst), 20);
1723 }
1724
1725 #[test]
1726 fn test_reactive_cell_debug() {
1727 let cell = ReactiveCell::new(42);
1728 let debug = format!("{:?}", cell);
1729 assert!(debug.contains("ReactiveCell"));
1730 assert!(debug.contains("42"));
1731 }
1732
1733 #[test]
1734 fn test_reactive_cell_string() {
1735 let cell = ReactiveCell::new("hello".to_string());
1736 cell.set("world".to_string());
1737 assert_eq!(cell.get(), "world");
1738 }
1739
1740 #[test]
1745 fn test_computed_with_closure_capture() {
1746 let base = 10;
1747 let computed = Computed::new(move || base * 2);
1748 assert_eq!(computed.get(), 20);
1749 }
1750
1751 #[test]
1752 fn test_computed_debug() {
1753 let computed = Computed::new(|| 42);
1754 computed.get(); let debug = format!("{:?}", computed);
1756 assert!(debug.contains("Computed"));
1757 }
1758
1759 #[test]
1760 fn test_computed_invalidate_recomputes() {
1761 use std::sync::atomic::{AtomicI32, Ordering};
1762
1763 let counter = Arc::new(AtomicI32::new(0));
1764 let counter_clone = counter.clone();
1765
1766 let computed = Computed::new(move || counter_clone.fetch_add(1, Ordering::SeqCst) + 1);
1767
1768 assert_eq!(computed.get(), 1);
1769 assert_eq!(computed.get(), 1); computed.invalidate();
1772 assert_eq!(computed.get(), 2); }
1774
1775 #[test]
1780 fn test_binding_expression_no_deps() {
1781 let expr = BindingExpression::new("Hello World");
1782 assert!(expr.dependencies.is_empty());
1783 }
1784
1785 #[test]
1786 fn test_binding_expression_complex() {
1787 let expr = BindingExpression::new("{{ user.name | uppercase }} ({{ user.age }})");
1788 assert_eq!(expr.dependencies.len(), 2);
1789 }
1790
1791 #[test]
1792 fn test_binding_expression_not_simple() {
1793 let expr = BindingExpression::new("Hello {{ name }}!");
1794 assert!(!expr.is_simple_property());
1795 assert!(expr.as_property().is_none());
1796 }
1797
1798 #[test]
1799 fn test_binding_expression_clone() {
1800 let expr = BindingExpression::property("count");
1801 let cloned = expr.clone();
1802 assert_eq!(cloned.expression, expr.expression);
1803 }
1804
1805 #[test]
1806 fn test_binding_expression_debug() {
1807 let expr = BindingExpression::new("{{ test }}");
1808 let debug = format!("{:?}", expr);
1809 assert!(debug.contains("BindingExpression"));
1810 }
1811
1812 #[test]
1813 fn test_binding_expression_serialize() {
1814 let expr = BindingExpression::property("count");
1815 let json = serde_json::to_string(&expr).unwrap();
1816 assert!(json.contains("expression"));
1817 }
1818
1819 #[test]
1824 fn test_event_binding_clone() {
1825 let binding = EventBinding::on_click(ActionBinding::toggle("visible"));
1826 let cloned = binding.clone();
1827 assert_eq!(cloned.event, "click");
1828 }
1829
1830 #[test]
1831 fn test_event_binding_debug() {
1832 let binding = EventBinding::new("submit", ActionBinding::dispatch("send"));
1833 let debug = format!("{:?}", binding);
1834 assert!(debug.contains("EventBinding"));
1835 }
1836
1837 #[test]
1838 fn test_event_binding_serialize() {
1839 let binding = EventBinding::on_click(ActionBinding::toggle("flag"));
1840 let json = serde_json::to_string(&binding).unwrap();
1841 assert!(json.contains("click"));
1842 }
1843
1844 #[test]
1849 fn test_action_binding_empty_batch() {
1850 let action = ActionBinding::batch([]);
1851 if let ActionBinding::Batch { actions } = action {
1852 assert!(actions.is_empty());
1853 } else {
1854 panic!("Expected Batch");
1855 }
1856 }
1857
1858 #[test]
1859 fn test_action_binding_clone() {
1860 let action = ActionBinding::set("path", "value");
1861 let cloned = action.clone();
1862 if let ActionBinding::SetProperty { path, .. } = cloned {
1863 assert_eq!(path.to_string_path(), "path");
1864 }
1865 }
1866
1867 #[test]
1868 fn test_action_binding_debug() {
1869 let action = ActionBinding::toggle("flag");
1870 let debug = format!("{:?}", action);
1871 assert!(debug.contains("ToggleProperty"));
1872 }
1873
1874 #[test]
1875 fn test_action_binding_serialize() {
1876 let action = ActionBinding::increment("counter");
1877 let json = serde_json::to_string(&action).unwrap();
1878 assert!(json.contains("IncrementProperty"));
1879 }
1880
1881 #[test]
1886 fn test_binding_manager_default() {
1887 let manager = BindingManager::default();
1888 assert_eq!(manager.active_count(), 0);
1889 assert!(manager.debounce_ms.is_none());
1890 }
1891
1892 #[test]
1893 fn test_binding_manager_multiple_registers() {
1894 let mut manager = BindingManager::new();
1895 let id1 = manager.register("w1", BindingConfig::one_way("a", "b"));
1896 let id2 = manager.register("w1", BindingConfig::one_way("c", "d"));
1897 assert_ne!(id1.0, id2.0);
1898 assert_eq!(manager.active_count(), 2);
1899 }
1900
1901 #[test]
1902 fn test_binding_manager_unregister_nonexistent() {
1903 let mut manager = BindingManager::new();
1904 manager.unregister(BindingId(999)); }
1906
1907 #[test]
1908 fn test_binding_manager_inactive_not_counted() {
1909 let mut manager = BindingManager::new();
1910 let id = manager.register("w1", BindingConfig::one_way("a", "b"));
1911 manager.register("w2", BindingConfig::one_way("c", "d"));
1912 manager.unregister(id);
1913 assert_eq!(manager.active_count(), 1);
1914 }
1915
1916 #[test]
1917 fn test_binding_manager_bindings_for_widget_empty() {
1918 let manager = BindingManager::new();
1919 assert!(manager.bindings_for_widget("nonexistent").is_empty());
1920 }
1921
1922 #[test]
1923 fn test_binding_manager_bindings_for_path_empty() {
1924 let manager = BindingManager::new();
1925 let path = PropertyPath::new("nonexistent");
1926 assert!(manager.bindings_for_path(&path).is_empty());
1927 }
1928
1929 #[test]
1930 fn test_binding_manager_on_state_change_nested_path() {
1931 let mut manager = BindingManager::new();
1932 manager.register("w1", BindingConfig::one_way("user", "data"));
1933
1934 let path = PropertyPath::new("user.name");
1935 let updates = manager.on_state_change(&path, "Alice");
1936
1937 assert_eq!(updates.len(), 1);
1939 }
1940
1941 #[test]
1942 fn test_binding_manager_on_state_change_inactive() {
1943 let mut manager = BindingManager::new();
1944 let id = manager.register("w1", BindingConfig::one_way("count", "text"));
1945 manager.unregister(id);
1946
1947 let path = PropertyPath::new("count");
1948 let updates = manager.on_state_change(&path, "42");
1949
1950 assert!(updates.is_empty());
1951 }
1952
1953 #[test]
1954 fn test_binding_manager_queue_widget_update() {
1955 let mut manager = BindingManager::new();
1956 manager.queue_update(
1957 UpdateSource::Widget,
1958 PropertyPath::new("field"),
1959 "value".to_string(),
1960 );
1961
1962 let (widget_updates, state_updates) = manager.flush();
1963 assert!(widget_updates.is_empty());
1964 assert_eq!(state_updates.len(), 1);
1965 }
1966
1967 #[test]
1968 fn test_binding_manager_debug() {
1969 let manager = BindingManager::new();
1970 let debug = format!("{:?}", manager);
1971 assert!(debug.contains("BindingManager"));
1972 }
1973
1974 #[test]
1979 fn test_active_binding_clone() {
1980 let binding = ActiveBinding {
1981 id: BindingId(1),
1982 widget_id: "widget".to_string(),
1983 config: BindingConfig::one_way("path", "prop"),
1984 current_value: Some("value".to_string()),
1985 active: true,
1986 };
1987 let cloned = binding.clone();
1988 assert_eq!(cloned.id, BindingId(1));
1989 }
1990
1991 #[test]
1992 fn test_active_binding_debug() {
1993 let binding = ActiveBinding {
1994 id: BindingId(0),
1995 widget_id: "w".to_string(),
1996 config: BindingConfig::one_way("a", "b"),
1997 current_value: None,
1998 active: true,
1999 };
2000 let debug = format!("{:?}", binding);
2001 assert!(debug.contains("ActiveBinding"));
2002 }
2003
2004 #[test]
2009 fn test_pending_update_clone() {
2010 let update = PendingUpdate {
2011 source: UpdateSource::State,
2012 path: PropertyPath::new("count"),
2013 value: "42".to_string(),
2014 timestamp: 12345,
2015 };
2016 let cloned = update.clone();
2017 assert_eq!(cloned.timestamp, 12345);
2018 }
2019
2020 #[test]
2021 fn test_pending_update_debug() {
2022 let update = PendingUpdate {
2023 source: UpdateSource::Widget,
2024 path: PropertyPath::new("field"),
2025 value: "val".to_string(),
2026 timestamp: 0,
2027 };
2028 let debug = format!("{:?}", update);
2029 assert!(debug.contains("PendingUpdate"));
2030 }
2031
2032 #[test]
2037 fn test_conversion_error_debug() {
2038 let err = ConversionError {
2039 message: "test".to_string(),
2040 };
2041 let debug = format!("{:?}", err);
2042 assert!(debug.contains("ConversionError"));
2043 }
2044
2045 #[test]
2046 fn test_conversion_error_clone() {
2047 let err = ConversionError {
2048 message: "original".to_string(),
2049 };
2050 let cloned = err.clone();
2051 assert_eq!(cloned.message, "original");
2052 }
2053
2054 #[test]
2059 fn test_identity_converter_default() {
2060 let converter = IdentityConverter::default();
2061 assert_eq!(converter.convert("test").unwrap(), "test");
2062 }
2063
2064 #[test]
2065 fn test_identity_converter_debug() {
2066 let converter = IdentityConverter;
2067 let debug = format!("{:?}", converter);
2068 assert!(debug.contains("IdentityConverter"));
2069 }
2070
2071 #[test]
2072 fn test_bool_converter_yes_no() {
2073 let converter = BoolToStringConverter::new();
2074 assert_eq!(converter.convert("yes").unwrap(), "true");
2075 assert_eq!(converter.convert("no").unwrap(), "false");
2076 }
2077
2078 #[test]
2079 fn test_bool_converter_default() {
2080 let converter = BoolToStringConverter::default();
2081 assert_eq!(converter.true_string, "");
2082 assert_eq!(converter.false_string, "");
2083 }
2084
2085 #[test]
2086 fn test_bool_converter_debug() {
2087 let converter = BoolToStringConverter::new();
2088 let debug = format!("{:?}", converter);
2089 assert!(debug.contains("BoolToStringConverter"));
2090 }
2091
2092 #[test]
2093 fn test_bool_converter_convert_back_error() {
2094 let converter = BoolToStringConverter::with_strings("Y", "N");
2095 assert!(converter.convert_back("Maybe").is_err());
2096 }
2097
2098 #[test]
2099 fn test_number_format_default() {
2100 let converter = NumberFormatConverter::default();
2101 assert_eq!(converter.decimals, 0);
2102 assert!(converter.prefix.is_empty());
2103 assert!(converter.suffix.is_empty());
2104 }
2105
2106 #[test]
2107 fn test_number_format_debug() {
2108 let converter = NumberFormatConverter::new().decimals(2);
2109 let debug = format!("{:?}", converter);
2110 assert!(debug.contains("NumberFormatConverter"));
2111 }
2112
2113 #[test]
2114 fn test_number_format_negative() {
2115 let converter = NumberFormatConverter::new().decimals(2);
2116 assert_eq!(converter.convert("-42.5").unwrap(), "-42.50");
2117 }
2118
2119 #[test]
2120 fn test_number_format_convert_back_error() {
2121 let converter = NumberFormatConverter::new();
2122 assert!(converter.convert_back("not-a-number").is_err());
2123 }
2124
2125 #[test]
2126 fn test_number_format_strip_partial() {
2127 let converter = NumberFormatConverter::new().prefix("$");
2128 assert_eq!(converter.convert_back("100").unwrap(), "100");
2130 }
2131}