cranpose-testing 0.0.63

Testing utilities and harness for Cranpose
Documentation
use super::*;
use cranpose_macros::composable;

#[derive(Default)]
struct TestContainerNode {
    parent: Option<NodeId>,
}

impl Node for TestContainerNode {
    fn on_attached_to_parent(&mut self, parent: NodeId) {
        self.parent = Some(parent);
    }
    fn on_removed_from_parent(&mut self) {
        self.parent = None;
    }
    fn parent(&self) -> Option<NodeId> {
        self.parent
    }
}

#[derive(Default)]
struct TestTextNode {
    content: String,
    parent: Option<NodeId>,
}

impl Node for TestTextNode {
    fn on_attached_to_parent(&mut self, parent: NodeId) {
        self.parent = Some(parent);
    }
    fn on_removed_from_parent(&mut self) {
        self.parent = None;
    }
    fn parent(&self) -> Option<NodeId> {
        self.parent
    }
}

#[allow(non_snake_case)]
#[composable]
fn Column(content: impl FnOnce()) {
    let id =
        with_current_composer(|composer| composer.emit_node(|| TestContainerNode { parent: None }));
    push_parent(id);
    content();
    pop_parent();
}

#[allow(non_snake_case)]
#[composable]
fn Text(value: String) {
    let initial_content = value.clone();
    let id = with_current_composer(|composer| {
        composer.emit_node(|| TestTextNode {
            content: initial_content,
            parent: None,
        })
    });
    with_node_mut(id, |node: &mut TestTextNode| {
        node.content = value;
    })
    .expect("update text node");
}

#[allow(non_snake_case)]
#[composable]
fn Parent(value: i32) {
    Column(|| {
        Child(value);
    });
}

#[allow(non_snake_case)]
#[composable]
fn Child(value: i32) {
    Text(format!("value: {}", value));
}

#[test]
fn test_child_recomposition_preserves_parent() {
    run_test_composition(|rule| {
        let runtime = rule.runtime_handle();
        let text_state = MutableState::with_runtime("Hello".to_string(), runtime.clone());

        rule.set_content({
            move || {
                Column(|| {
                    let value = text_state.value();
                    Text(value);
                });
            }
        })
        .expect("initial render succeeds");

        assert_eq!(rule.applier_mut().len(), 2);

        text_state.set_value("World".to_string());
        {
            let composition = rule.composition();
            let _ = composition
                .process_invalid_scopes()
                .expect("process invalid scopes");
        }

        assert_eq!(rule.applier_mut().len(), 2);
    });
}

#[test]
fn test_conditional_composable_preserves_siblings() {
    run_test_composition(|rule| {
        let runtime = rule.runtime_handle();
        let show_middle = MutableState::with_runtime(true, runtime.clone());

        rule.set_content({
            move || {
                Column(|| {
                    Text("A".to_string());
                    if show_middle.value() {
                        Text("B".to_string());
                    }
                    Text("C".to_string());
                });
            }
        })
        .expect("initial render succeeds");

        assert_eq!(rule.applier_mut().len(), 4);

        show_middle.set_value(false);
        rule.recomposition()
            .expect("second render with middle hidden");
        rule.pump_until_idle()
            .expect("drain pending work after hiding middle");
        assert_eq!(rule.applier_mut().len(), 3);

        show_middle.set_value(true);
        rule.recomposition()
            .expect("third render with middle visible");
        rule.pump_until_idle()
            .expect("drain pending work after showing middle");
        assert_eq!(rule.applier_mut().len(), 4);
    });
}

#[test]
fn test_skipped_composable_preserves_children() {
    run_test_composition(|rule| {
        rule.set_content(|| {
            Parent(1);
        })
        .expect("initial render succeeds");

        assert_eq!(rule.applier_mut().len(), 2);

        rule.recomposition().expect("recompose with stable input");
        assert_eq!(rule.applier_mut().len(), 2);
    });
}