cranpose-native 0.9.21

UniFFI runtime for native Cranpose host views
Documentation
use std::{cell::Cell, rc::Rc};

use cranpose::prelude::*;
use cranpose_core::rememberMutableStateOf;
use cranpose_native::{NativeContent, NativeError, NativeSession, SendToHost, WebView};

fn component() -> std::sync::Arc<NativeSession> {
    NativeSession::new(|| {
        let control = Rc::new(Cell::new(None::<cranpose_core::MutableState<i32>>));
        let draw_control = Rc::clone(&control);
        NativeContent::new(move || {
            let count = rememberMutableStateOf(|| 0);
            draw_control.set(Some(count));
            SendToHost("count", count.value().to_string());
            WebView(
                "https://example.com",
                Modifier::empty().width(200.0).height(100.0),
                |_| {},
            );
        })
        .on_event(move |event| {
            if event.name == "increment" {
                let count = control.get().expect("composed count");
                count.set(count.value() + 1);
            }
        })
    })
}

#[test]
fn independent_sessions_exchange_events_and_preserve_native_identity() {
    let a = component();
    let b = component();
    let first = pollster::block_on(a.frame(400, 300, 2.0)).expect("first");
    let second = pollster::block_on(b.frame(400, 300, 2.0)).expect("second");
    assert_eq!(first.slots[0].value, "https://example.com");
    assert!(second.events.iter().any(|event| event.value == "0"));
    a.send_event("increment".into(), String::new())
        .expect("event");
    let changed = pollster::block_on(a.frame(400, 300, 2.0)).expect("changed");
    assert!(
        changed
            .events
            .iter()
            .any(|event| event.name == "count" && event.value == "1")
    );
    assert_eq!(changed.slots[0].id, first.slots[0].id);
    assert_eq!(changed.slots[0].value, first.slots[0].value);
    let unchanged = pollster::block_on(b.frame(400, 300, 2.0)).expect("isolated");
    assert!(!unchanged.events.iter().any(|event| event.value == "1"));
    a.shutdown();
    assert!(matches!(
        pollster::block_on(a.frame(400, 300, 2.0)),
        Err(NativeError::Closed)
    ));
    assert!(a.send_event("increment".into(), String::new()).is_err());
    b.shutdown();
}

#[test]
fn hidden_session_resumes_and_invalid_size_does_not_poison_it() {
    let session = component();
    pollster::block_on(session.frame(400, 300, 2.0)).expect("initial");
    session.set_visible(false).expect("hide");
    session
        .send_event("increment".into(), String::new())
        .expect("event while hidden");
    assert!(
        pollster::block_on(session.frame(400, 300, 2.0))
            .expect("hidden")
            .pixels
            .is_empty()
    );
    session.set_visible(true).expect("show");
    let resumed = pollster::block_on(session.frame(600, 400, 2.0)).expect("resumed");
    assert_eq!(resumed.pixels.len(), 600 * 400 * 4);
    assert!(resumed.events.iter().any(|event| event.value == "1"));
    assert!(pollster::block_on(session.frame(0, 1, 1.0)).is_err());
    pollster::block_on(session.frame(400, 300, 2.0)).expect("recover");
    session.shutdown();
    session.shutdown();
}