presentar_core/
binding.rs

1//! Interactive state binding for reactive UI.
2//!
3//! This module provides mechanisms to bind UI widget properties to application
4//! state, enabling declarative data flow and automatic UI updates.
5//!
6//! # Binding Types
7//!
8//! - `Binding<T>` - Two-way binding for read/write access
9//! - `Derived<T>` - Read-only computed value from state
10//! - `PropertyPath` - Path to a property in state (e.g., "user.name")
11//!
12//! # Example
13//!
14//! ```ignore
15//! use presentar_core::binding::{Binding, Derived, PropertyPath};
16//!
17//! // Create a binding to state.count
18//! let count_binding = Binding::new(|| state.count, |v| state.count = v);
19//!
20//! // Create a derived value
21//! let doubled = Derived::new(|| state.count * 2);
22//! ```
23
24use serde::{Deserialize, Serialize};
25use std::any::Any;
26use std::fmt;
27use std::sync::{Arc, RwLock};
28
29/// Type alias for subscriber callbacks.
30type SubscriberFn<T> = Box<dyn Fn(&T) + Send + Sync>;
31
32/// Type alias for subscribers list.
33type Subscribers<T> = Arc<RwLock<Vec<SubscriberFn<T>>>>;
34
35/// A property path for accessing nested state.
36///
37/// Property paths use dot notation: "user.profile.name"
38#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
39pub struct PropertyPath {
40    segments: Vec<String>,
41}
42
43impl PropertyPath {
44    /// Create a new property path from a string.
45    #[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    /// Create an empty root path.
56    #[must_use]
57    pub const fn root() -> Self {
58        Self {
59            segments: Vec::new(),
60        }
61    }
62
63    /// Get path segments.
64    #[must_use]
65    pub fn segments(&self) -> &[String] {
66        &self.segments
67    }
68
69    /// Check if path is empty (root).
70    #[must_use]
71    pub fn is_root(&self) -> bool {
72        self.segments.is_empty()
73    }
74
75    /// Get the number of segments.
76    #[must_use]
77    pub fn len(&self) -> usize {
78        self.segments.len()
79    }
80
81    /// Check if path is empty.
82    #[must_use]
83    pub fn is_empty(&self) -> bool {
84        self.segments.is_empty()
85    }
86
87    /// Append a segment to the path.
88    #[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    /// Get the parent path.
96    #[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    /// Get the last segment (leaf name).
108    #[must_use]
109    pub fn leaf(&self) -> Option<&str> {
110        self.segments.last().map(String::as_str)
111    }
112
113    /// Convert to string representation.
114    #[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/// Binding direction for property connections.
133#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
134pub enum BindingDirection {
135    /// One-way binding: state → widget
136    #[default]
137    OneWay,
138    /// Two-way binding: state ↔ widget
139    TwoWay,
140    /// One-time binding: state → widget (initial only)
141    OneTime,
142}
143
144/// A binding configuration for connecting state to widget properties.
145#[derive(Debug, Clone)]
146pub struct BindingConfig {
147    /// Source path in state
148    pub source: PropertyPath,
149    /// Target property on widget
150    pub target: String,
151    /// Binding direction
152    pub direction: BindingDirection,
153    /// Optional transform function name
154    pub transform: Option<String>,
155    /// Optional fallback value (as string)
156    pub fallback: Option<String>,
157}
158
159impl BindingConfig {
160    /// Create a new one-way binding.
161    #[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    /// Create a new two-way binding.
173    #[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    /// Set a transform function.
185    #[must_use]
186    pub fn transform(mut self, name: impl Into<String>) -> Self {
187        self.transform = Some(name.into());
188        self
189    }
190
191    /// Set a fallback value.
192    #[must_use]
193    pub fn fallback(mut self, value: impl Into<String>) -> Self {
194        self.fallback = Some(value.into());
195        self
196    }
197}
198
199/// Trait for types that can be bound to state.
200pub trait Bindable: Any + Send + Sync {
201    /// Get bindings for this widget.
202    fn bindings(&self) -> Vec<BindingConfig>;
203
204    /// Set bindings for this widget.
205    fn set_bindings(&mut self, bindings: Vec<BindingConfig>);
206
207    /// Apply a binding value update.
208    fn apply_binding(&mut self, target: &str, value: &dyn Any) -> bool;
209
210    /// Get the current value for a binding target.
211    fn get_binding_value(&self, target: &str) -> Option<Box<dyn Any + Send>>;
212}
213
214/// A reactive cell that holds a value and notifies on changes.
215pub struct ReactiveCell<T> {
216    value: Arc<RwLock<T>>,
217    subscribers: Subscribers<T>,
218}
219
220impl<T: Clone + Send + Sync + 'static> ReactiveCell<T> {
221    /// Create a new reactive cell with an initial value.
222    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    /// Get the current value.
230    pub fn get(&self) -> T {
231        self.value
232            .read()
233            .expect("ReactiveCell lock poisoned")
234            .clone()
235    }
236
237    /// Set a new value, notifying subscribers.
238    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    /// Update the value using a function.
247    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    /// Subscribe to value changes.
259    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())), // Don't clone subscribers
283        }
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
304/// A computed value derived from other reactive sources.
305pub 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    /// Create a new computed value.
314    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    /// Get the computed value (caches result).
326    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    /// Mark the computed value as dirty (needs recomputation).
343    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/// A binding expression that can be evaluated against state.
364#[derive(Debug, Clone, Serialize, Deserialize)]
365pub struct BindingExpression {
366    /// Expression string (e.g., "{{ user.name }}" or "{{ count * 2 }}")
367    pub expression: String,
368    /// Parsed dependencies (property paths used)
369    pub dependencies: Vec<PropertyPath>,
370}
371
372impl BindingExpression {
373    /// Create a new binding expression.
374    #[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    /// Create a simple property binding.
385    #[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    /// Check if this is a simple property binding (no transforms).
396    #[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    /// Get the property path if this is a simple binding.
404    #[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                    // Extract property path from current (may have transforms)
431                    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/// Event binding that maps widget events to state messages.
454#[derive(Debug, Clone, Serialize, Deserialize)]
455pub struct EventBinding {
456    /// Widget event name (e.g., "click", "change", "submit")
457    pub event: String,
458    /// Action to dispatch
459    pub action: ActionBinding,
460}
461
462/// Action binding for state updates.
463#[derive(Debug, Clone, Serialize, Deserialize)]
464pub enum ActionBinding {
465    /// Set a property to a value
466    SetProperty {
467        /// Property path
468        path: PropertyPath,
469        /// Value expression
470        value: String,
471    },
472    /// Toggle a boolean property
473    ToggleProperty {
474        /// Property path
475        path: PropertyPath,
476    },
477    /// Increment a numeric property
478    IncrementProperty {
479        /// Property path
480        path: PropertyPath,
481        /// Amount to increment (default 1)
482        amount: Option<f64>,
483    },
484    /// Navigate to a route
485    Navigate {
486        /// Route path
487        route: String,
488    },
489    /// Dispatch a custom message
490    Dispatch {
491        /// Message type/name
492        message: String,
493        /// Optional payload
494        payload: Option<String>,
495    },
496    /// Execute multiple actions
497    Batch {
498        /// Actions to execute
499        actions: Vec<Self>,
500    },
501}
502
503impl EventBinding {
504    /// Create a new event binding.
505    #[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    /// Create a click event binding.
514    #[must_use]
515    pub fn on_click(action: ActionBinding) -> Self {
516        Self::new("click", action)
517    }
518
519    /// Create a change event binding.
520    #[must_use]
521    pub fn on_change(action: ActionBinding) -> Self {
522        Self::new("change", action)
523    }
524}
525
526impl ActionBinding {
527    /// Create a set property action.
528    #[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    /// Create a toggle action.
537    #[must_use]
538    pub fn toggle(path: impl Into<PropertyPath>) -> Self {
539        Self::ToggleProperty { path: path.into() }
540    }
541
542    /// Create an increment action.
543    #[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    /// Create an increment by amount action.
552    #[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    /// Create a navigate action.
561    #[must_use]
562    pub fn navigate(route: impl Into<String>) -> Self {
563        Self::Navigate {
564            route: route.into(),
565        }
566    }
567
568    /// Create a dispatch action.
569    #[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    /// Create a dispatch with payload action.
578    #[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    /// Create a batch of actions.
587    #[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// =============================================================================
596// BindingManager - Orchestrates State-Widget Bindings
597// =============================================================================
598
599/// Manages bindings between application state and widget properties.
600///
601/// The `BindingManager` provides:
602/// - Registration of two-way bindings
603/// - Automatic propagation of state changes to widgets
604/// - Handling of widget changes back to state
605/// - Debouncing support for frequent updates
606#[derive(Debug, Default)]
607pub struct BindingManager {
608    /// Active bindings
609    bindings: Vec<ActiveBinding>,
610    /// Whether to debounce updates
611    debounce_ms: Option<u32>,
612    /// Pending updates queue
613    pending_updates: Vec<PendingUpdate>,
614}
615
616/// An active binding between state and widget.
617#[derive(Debug, Clone)]
618pub struct ActiveBinding {
619    /// Unique binding ID
620    pub id: BindingId,
621    /// Widget ID
622    pub widget_id: String,
623    /// Binding configuration
624    pub config: BindingConfig,
625    /// Current state value (as string for simplicity)
626    pub current_value: Option<String>,
627    /// Whether binding is active
628    pub active: bool,
629}
630
631/// Unique binding identifier.
632#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
633pub struct BindingId(pub u64);
634
635/// A pending update to be applied.
636#[derive(Debug, Clone)]
637pub struct PendingUpdate {
638    /// Source (widget or state)
639    pub source: UpdateSource,
640    /// Property path
641    pub path: PropertyPath,
642    /// New value as string
643    pub value: String,
644    /// Timestamp
645    pub timestamp: u64,
646}
647
648/// Source of a binding update.
649#[derive(Debug, Clone, Copy, PartialEq, Eq)]
650pub enum UpdateSource {
651    /// Update from state
652    State,
653    /// Update from widget
654    Widget,
655}
656
657impl BindingManager {
658    /// Create a new binding manager.
659    #[must_use]
660    pub fn new() -> Self {
661        Self::default()
662    }
663
664    /// Set debounce delay in milliseconds.
665    #[must_use]
666    pub fn with_debounce(mut self, ms: u32) -> Self {
667        self.debounce_ms = Some(ms);
668        self
669    }
670
671    /// Register a binding between state and widget.
672    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    /// Unregister a binding.
685    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    /// Get bindings for a widget.
692    #[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    /// Get bindings for a state path.
701    #[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    /// Handle state change, propagate to widgets.
710    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            // Check if this path affects this binding
719            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    /// Handle widget change, propagate to state.
738    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            // Only two-way bindings propagate back to state
752            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    /// Queue an update (for debouncing).
768    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, // Would be set to actual timestamp
774        });
775    }
776
777    /// Flush pending updates.
778    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        // Drain into separate Vec to avoid borrow issues
783        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                    // For widget updates, we'd need widget_id context
792                    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    /// Get number of active bindings.
804    #[must_use]
805    pub fn active_count(&self) -> usize {
806        self.bindings.iter().filter(|b| b.active).count()
807    }
808
809    /// Clear all bindings.
810    pub fn clear(&mut self) {
811        self.bindings.clear();
812        self.pending_updates.clear();
813    }
814}
815
816/// Update to apply to a widget.
817#[derive(Debug, Clone)]
818pub struct WidgetUpdate {
819    /// Target widget ID
820    pub widget_id: String,
821    /// Property to update
822    pub property: String,
823    /// New value
824    pub value: String,
825}
826
827/// Update to apply to state.
828#[derive(Debug, Clone)]
829pub struct StateUpdate {
830    /// Property path
831    pub path: PropertyPath,
832    /// New value
833    pub value: String,
834}
835
836// =============================================================================
837// ValueConverter - Type Conversion for Bindings
838// =============================================================================
839
840/// Converts values between different types for binding.
841pub trait ValueConverter: Send + Sync {
842    /// Convert from source type to target type.
843    fn convert(&self, value: &str) -> Result<String, ConversionError>;
844
845    /// Convert back from target type to source type.
846    fn convert_back(&self, value: &str) -> Result<String, ConversionError>;
847}
848
849/// Error during value conversion.
850#[derive(Debug, Clone)]
851pub struct ConversionError {
852    /// Error message
853    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/// Identity converter (no conversion).
865#[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/// Boolean to string converter.
879#[derive(Debug, Default)]
880pub struct BoolToStringConverter {
881    /// String for true value
882    pub true_string: String,
883    /// String for false value
884    pub false_string: String,
885}
886
887impl BoolToStringConverter {
888    /// Create with "true"/"false" strings.
889    #[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    /// Create with custom strings.
898    #[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/// Number formatter converter.
932#[derive(Debug, Default)]
933pub struct NumberFormatConverter {
934    /// Decimal places
935    pub decimals: usize,
936    /// Prefix (e.g., "$")
937    pub prefix: String,
938    /// Suffix (e.g., "%")
939    pub suffix: String,
940}
941
942impl NumberFormatConverter {
943    /// Create default formatter.
944    #[must_use]
945    pub fn new() -> Self {
946        Self::default()
947    }
948
949    /// Set decimal places.
950    #[must_use]
951    pub fn decimals(mut self, places: usize) -> Self {
952        self.decimals = places;
953        self
954    }
955
956    /// Set prefix.
957    #[must_use]
958    pub fn prefix(mut self, prefix: impl Into<String>) -> Self {
959        self.prefix = prefix.into();
960        self
961    }
962
963    /// Set suffix.
964    #[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        // Strip prefix and suffix
983        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        // Validate it's a number
991        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    // =========================================================================
1004    // PropertyPath Tests
1005    // =========================================================================
1006
1007    #[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    // =========================================================================
1059    // BindingConfig Tests
1060    // =========================================================================
1061
1062    #[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    // =========================================================================
1089    // ReactiveCell Tests
1090    // =========================================================================
1091
1092    #[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); // Shares same underlying value
1141    }
1142
1143    // =========================================================================
1144    // Computed Tests
1145    // =========================================================================
1146
1147    #[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); // Only computed once
1170    }
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); // Computed twice
1189    }
1190
1191    // =========================================================================
1192    // BindingExpression Tests
1193    // =========================================================================
1194
1195    #[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    // =========================================================================
1222    // EventBinding Tests
1223    // =========================================================================
1224
1225    #[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    // =========================================================================
1244    // ActionBinding Tests
1245    // =========================================================================
1246
1247    #[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    // =========================================================================
1335    // BindingManager Tests
1336    // =========================================================================
1337
1338    #[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()); // One-way doesn't propagate back
1416    }
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    // =========================================================================
1449    // ValueConverter Tests
1450    // =========================================================================
1451
1452    #[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    // =========================================================================
1515    // Widget/State Update Tests
1516    // =========================================================================
1517
1518    #[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    // =========================================================================
1551    // PropertyPath Additional Tests
1552    // =========================================================================
1553
1554    #[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    // =========================================================================
1642    // BindingDirection Additional Tests
1643    // =========================================================================
1644
1645    #[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    // =========================================================================
1672    // BindingConfig Additional Tests
1673    // =========================================================================
1674
1675    #[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    // =========================================================================
1700    // ReactiveCell Additional Tests
1701    // =========================================================================
1702
1703    #[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    // =========================================================================
1741    // Computed Additional Tests
1742    // =========================================================================
1743
1744    #[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(); // Populate cache
1755        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); // Cached
1770
1771        computed.invalidate();
1772        assert_eq!(computed.get(), 2); // Recomputed
1773    }
1774
1775    // =========================================================================
1776    // BindingExpression Additional Tests
1777    // =========================================================================
1778
1779    #[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    // =========================================================================
1820    // EventBinding Additional Tests
1821    // =========================================================================
1822
1823    #[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    // =========================================================================
1845    // ActionBinding Additional Tests
1846    // =========================================================================
1847
1848    #[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    // =========================================================================
1882    // BindingManager Additional Tests
1883    // =========================================================================
1884
1885    #[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)); // Should not panic
1905    }
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        // Should match because user.name starts with user
1938        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    // =========================================================================
1975    // ActiveBinding Tests
1976    // =========================================================================
1977
1978    #[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    // =========================================================================
2005    // PendingUpdate Tests
2006    // =========================================================================
2007
2008    #[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    // =========================================================================
2033    // ConversionError Additional Tests
2034    // =========================================================================
2035
2036    #[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    // =========================================================================
2055    // ValueConverter Additional Tests
2056    // =========================================================================
2057
2058    #[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        // Value without prefix should still work
2129        assert_eq!(converter.convert_back("100").unwrap(), "100");
2130    }
2131}