#![cfg(declarative_view)]
use rust_widgets::core::{ObjectId, Rect};
use rust_widgets::data_binding::Binding;
use rust_widgets::view::{Node, ReactiveHost, View, ViewEngine};
use rust_widgets::widget::capability::properties_trait::widget_property_get;
use rust_widgets::widget::capability::CapabilityValue;
use rust_widgets::widget::{runtime, Widget, WidgetFactory};
struct LabelState<'a> {
text: &'a Binding<String>,
}
impl View for LabelState<'_> {
fn build(&self) -> Node {
Node::new("group_box").key("root").child(
Node::new("label")
.key("greeting")
.prop("text", CapabilityValue::String(self.text.get())),
)
}
}
fn real_creator() -> impl Fn(&Node) -> Option<ObjectId> {
let factory = WidgetFactory::new_with_defaults();
move |node: &Node| {
let widget: Box<dyn Widget> = factory.create(
&node.widget,
Rect::new(0, 0, 120, 32),
node.key_str().unwrap_or("anon"),
)?;
runtime::register(widget)
}
}
fn live_text(id: ObjectId) -> Option<String> {
runtime::with_widget(id, |widget| {
widget_property_get(widget, "text").ok().and_then(|value| match value {
CapabilityValue::String(text) => Some(text),
_ => None,
})
})
.flatten()
}
#[test]
fn binding_set_reaches_the_live_control_through_the_view_engine() {
let text = Binding::new(String::from("first"));
let mut host = ReactiveHost::new(LabelState { text: &text }, Box::new(real_creator()));
let mounted = host.mount();
assert!(
mounted.widgets_created >= 2 && mounted.errors.is_empty(),
"mount must create a real group box and label with no refused writes: {mounted:?}"
);
let label_id = host.engine().id_at(&[0]).expect("the label is the first child");
assert_eq!(live_text(label_id).as_deref(), Some("first"));
host.subscribe(&text);
text.set(String::from("second"));
let rebuilds = host.pump();
assert_eq!(
live_text(label_id).as_deref(),
Some("second"),
"after Binding::set the live control must hold the new value \
(rebuilds={rebuilds}, notifications={}, listeners={})",
host.notifications(),
text.listener_count()
);
}
#[test]
fn a_binding_driven_update_keeps_the_unchanged_control_identity() {
let text = Binding::new(String::from("v1"));
let creator = real_creator();
let mut engine = ViewEngine::new();
engine.mount(&LabelState { text: &text }, &creator);
let root_before = engine.id_at(&[]).expect("root mounted");
let label_before = engine.id_at(&[0]).expect("label mounted");
for value in ["v2", "v3"] {
text.set(String::from(value));
let report = engine.update(&LabelState { text: &text }, &creator);
assert!(
report.replaced_subtrees == 0,
"a text-only change must not replace any subtree: {report:?}"
);
}
let root_after = engine.id_at(&[]);
let label_after = engine.id_at(&[0]);
assert_eq!(
(root_after, label_after),
(Some(root_before), Some(label_before)),
"a text-only update must leave every control's identity alone — new ids here \
mean the tree was rebuilt rather than patched"
);
assert_eq!(
live_text(label_before).as_deref(),
Some("v3"),
"and the surviving control must hold the last value"
);
}