cranpose-testing 0.1.107

Testing utilities and harness for Cranpose
use cranpose_core::MutableState;
use cranpose_foundation::PointerEventKind;
use cranpose_macros::composable;
use cranpose_testing::ComposeTestRule;
use cranpose_ui::*;

#[composable]
fn gradient_follows_state_app(pointer_position: MutableState<Point>) {
    Column(
        Modifier::empty()
            .size(Size {
                width: 200.0,
                height: 200.0,
            })
            .then(Modifier::empty().draw_with_content({
                let position = pointer_position.get();
                eprintln!("Creating draw closure with position: {:?}", position);
                move |scope| {
                    eprintln!("Drawing with captured position: {:?}", position);
                    scope.draw_rect(Brush::radial_gradient(
                        vec![Color(1.0, 0.0, 0.0, 0.8), Color(0.0, 0.0, 0.0, 0.0)],
                        position,
                        50.0,
                    ));
                }
            }))
            .then(Modifier::empty().pointer_input((), {
                move |scope: PointerInputScope| async move {
                    scope
                        .await_pointer_event_scope(|await_scope| async move {
                            loop {
                                let event = await_scope.await_pointer_event().await;
                                if let PointerEventKind::Move = event.kind {
                                    eprintln!(
                                        "Pointer event: setting position to {:?}",
                                        event.position
                                    );
                                    pointer_position.set(Point {
                                        x: event.position.x,
                                        y: event.position.y,
                                    });
                                }
                            }
                        })
                        .await;
                }
            })),
        ColumnSpec::default(),
        || {
            Text(
                "Hover area",
                Modifier::empty().padding(8.0),
                TextStyle::default(),
            );
        },
    );
}

#[test]
fn test_manual_state_change_triggers_recomposition() {
    let _app_context = cranpose_ui::AppContext::new();
    let _app_context_scope = _app_context.enter_scope();
    _app_context.enter(cranpose_ui::reset_render_state_for_tests);

    let mut rule = ComposeTestRule::new();
    let runtime = rule.runtime_handle();

    let pointer_position = MutableState::with_runtime(Point { x: 0.0, y: 0.0 }, runtime.clone());

    eprintln!("\n=== Initial composition ===");
    rule.set_content({
        let pos = pointer_position;
        move || {
            gradient_follows_state_app(pos);
        }
    })
    .expect("initial render succeeds");

    assert_eq!(pointer_position.get().x, 0.0);
    eprintln!("Initial position: {:?}", pointer_position.get());

    eprintln!("\n=== Manually changing state to (100, 100) ===");
    pointer_position.set(Point { x: 100.0, y: 100.0 });

    eprintln!("=== Forcing recomposition ===");
    rule.pump_until_idle()
        .expect("recompose after state change");

    assert_eq!(pointer_position.get().x, 100.0);
    eprintln!("New position: {:?}", pointer_position.get());
    eprintln!("✓ Manual state changes trigger recomposition correctly\n");
}

#[composable]
fn working_gradient_app(pointer_position: MutableState<Point>) {
    Column(
        Modifier::empty()
            .size(Size {
                width: 200.0,
                height: 200.0,
            })
            .then(Modifier::empty().draw_with_content({
                move |scope| {
                    let position = pointer_position.get();
                    eprintln!("Drawing with current state position: {:?}", position);
                    scope.draw_rect(Brush::radial_gradient(
                        vec![Color(0.0, 1.0, 0.0, 0.8), Color(0.0, 0.0, 0.0, 0.0)],
                        position,
                        50.0,
                    ));
                }
            }))
            .then(Modifier::empty().pointer_input((), {
                move |scope: PointerInputScope| async move {
                    scope
                        .await_pointer_event_scope(|await_scope| async move {
                            loop {
                                let event = await_scope.await_pointer_event().await;
                                if let PointerEventKind::Move = event.kind {
                                    eprintln!("Setting position to {:?}", event.position);
                                    pointer_position.set(Point {
                                        x: event.position.x,
                                        y: event.position.y,
                                    });
                                }
                            }
                        })
                        .await;
                }
            })),
        ColumnSpec::default(),
        || {
            Text(
                "Hover area",
                Modifier::empty().padding(8.0),
                TextStyle::default(),
            );
        },
    );
}

#[test]
fn test_correct_pattern_reads_state_at_draw_time() {
    let _app_context = cranpose_ui::AppContext::new();
    let _app_context_scope = _app_context.enter_scope();
    _app_context.enter(cranpose_ui::reset_render_state_for_tests);

    let mut rule = ComposeTestRule::new();
    let runtime = rule.runtime_handle();

    let pointer_position = MutableState::with_runtime(Point { x: 0.0, y: 0.0 }, runtime.clone());

    eprintln!("\n=== Initial composition (correct pattern) ===");
    rule.set_content({
        let pos = pointer_position;
        move || {
            working_gradient_app(pos);
        }
    })
    .expect("initial render succeeds");

    eprintln!("Initial position: {:?}", pointer_position.get());

    eprintln!("\n=== Changing state to (100, 100) ===");
    pointer_position.set(Point { x: 100.0, y: 100.0 });

    eprintln!("New position: {:?}", pointer_position.get());
    eprintln!("✓ Correct pattern: state read at draw time\n");
}