phlow 3.0.0

An engine for scripting reactive browsers in Rust by adding custom views to structures
Documentation
#[macro_use]
extern crate phlow;

use std::any::Any;
use std::ffi::c_void;
use std::ptr::NonNull;
use std::sync::Arc;

use phlow::{
    ObjectRef, PhlowVTable, TextViewInstance, view_defining_methods_for_type,
    view_functions_of_any, view_functions_of_val,
};

environment!();

#[extensions(String)]
mod string_extensions {
    use phlow::{PhlowView, ProtoView};

    #[view]
    fn raw_text_view(_value: &String, view: impl ProtoView<String>) -> impl PhlowView {
        view.text()
            .title("Raw")
            .priority(1)
            .text(|value| value.clone())
    }

    #[view]
    fn decorated_text_view(_value: &String, view: impl ProtoView<String>) -> impl PhlowView {
        view.text()
            .title("Decorated")
            .priority(2)
            .text(|value| format!("value: {value}"))
    }
}

#[test]
fn discovers_views_for_typed_values() {
    let value = "Hello".to_string();
    let mut methods = view_functions_of_val(&value);
    methods.sort_by_key(|method| method.name());

    let names = methods
        .iter()
        .map(|method| method.name())
        .collect::<Vec<_>>();
    assert_eq!(names, vec!["decorated_text_view", "raw_text_view"]);

    let raw_view = methods
        .into_iter()
        .find(|method| method.name() == "raw_text_view")
        .unwrap()
        .as_view(&value);

    assert_eq!(raw_view.get_title(), "Raw");
    assert_eq!(raw_view.get_priority(), 1);
    assert_eq!(raw_view.get_view_type(), "text_view");

    let instance = raw_view.create_instance(ObjectRef::new(&value));
    let instance = instance
        .as_any()
        .downcast_ref::<TextViewInstance>()
        .unwrap();
    assert_eq!(instance.text, "Hello");
}

#[test]
fn discovers_views_for_dyn_any_values() {
    let value = "Hello".to_string();
    let any_value: &dyn Any = &value;
    let mut methods = view_functions_of_any(any_value);
    methods.sort_by_key(|method| method.name());

    let names = methods
        .iter()
        .map(|method| method.name())
        .collect::<Vec<_>>();
    assert_eq!(names, vec!["decorated_text_view", "raw_text_view"]);

    let decorated_view = methods
        .into_iter()
        .find(|method| method.name() == "decorated_text_view")
        .unwrap()
        .as_view_for_any(any_value);

    assert_eq!(decorated_view.get_title(), "Decorated");
    assert_eq!(decorated_view.get_priority(), 2);
    assert_eq!(decorated_view.get_view_type(), "text_view");

    let instance = decorated_view.create_instance(ObjectRef::new(&value));
    let instance = instance
        .as_any()
        .downcast_ref::<TextViewInstance>()
        .unwrap();
    assert_eq!(instance.text, "value: Hello");
}

#[test]
fn phlow_view_trait_objects_are_send_and_sync() {
    fn assert_send_sync<T: Send + Sync>() {}

    assert_send_sync::<Box<dyn phlow::PhlowView>>();
    assert_send_sync::<Arc<dyn phlow::PhlowView>>();
}

#[test]
fn view_instance_trait_objects_are_send() {
    fn assert_send<T: Send>() {}

    assert_send::<Box<dyn phlow::ViewInstance>>();
}

#[test]
fn object_ref_can_be_created_from_an_erased_raw_pointer_for_a_closure() {
    let value = "Hello".to_string();
    let raw_ptr = NonNull::from(&value).cast::<c_void>();
    let raw_view = view_functions_of_val(&value)
        .into_iter()
        .find(|method| method.name() == "raw_text_view")
        .unwrap()
        .as_view(&value);

    let instance =
        unsafe { ObjectRef::with_erased_ptr(raw_ptr, |object| raw_view.create_instance(object)) };
    let instance = instance
        .as_any()
        .downcast_ref::<TextViewInstance>()
        .unwrap();

    assert_eq!(instance.text, "Hello");
}

#[test]
fn vtable_of_any_builds_a_to_string_function() {
    let value = "Hello".to_string();
    let vtable = phlow::vtable_of_any(&value);

    assert!(vtable.supports_type_name());
    assert_eq!(vtable.type_name(), Some(std::any::type_name::<String>()));
    assert!(vtable.supports_to_string());
    assert_eq!(
        vtable.to_string(ObjectRef::new(&value)),
        Some("Hello".to_string())
    );
    let from_vtable = vtable
        .view_defining_methods_for_type()
        .into_iter()
        .map(|method| method.name())
        .collect::<Vec<_>>();
    let from_function = view_defining_methods_for_type::<String>()
        .into_iter()
        .map(|method| method.name())
        .collect::<Vec<_>>();

    assert_eq!(from_vtable, from_function);
    let views = vtable.views(ObjectRef::new(&value));
    let mut view_names = views
        .into_iter()
        .map(|view| view.get_title().to_string())
        .collect::<Vec<_>>();
    view_names.sort();

    assert_eq!(view_names, vec!["Decorated".to_string(), "Raw".to_string()]);
}

#[test]
fn vtable_of_type_builds_a_to_string_function() {
    let value = "Hello".to_string();
    let vtable = phlow::vtable_of_type::<String>();

    assert!(vtable.supports_type_name());
    assert_eq!(vtable.type_name(), Some(std::any::type_name::<String>()));
    assert!(vtable.supports_to_string());
    assert_eq!(
        vtable.to_string(ObjectRef::new(&value)),
        Some("Hello".to_string())
    );
    let from_vtable = vtable
        .view_defining_methods_for_type()
        .into_iter()
        .map(|method| method.name())
        .collect::<Vec<_>>();
    let from_function = view_defining_methods_for_type::<String>()
        .into_iter()
        .map(|method| method.name())
        .collect::<Vec<_>>();

    assert_eq!(from_vtable, from_function);
    let views = vtable.views(ObjectRef::new(&value));
    let mut view_names = views
        .into_iter()
        .map(|view| view.get_title().to_string())
        .collect::<Vec<_>>();
    view_names.sort();

    assert_eq!(view_names, vec!["Decorated".to_string(), "Raw".to_string()]);
}

#[test]
fn vtable_returns_none_for_missing_type_name_and_to_string() {
    let value = "Hello".to_string();
    let vtable = PhlowVTable {
        type_name_fn: None,
        to_string_fn: None,
        as_view_fn: None,
        defining_methods_fn: None,
    };

    assert!(!vtable.supports_type_name());
    assert_eq!(vtable.type_name(), None);
    assert!(!vtable.supports_to_string());
    assert_eq!(vtable.to_string(ObjectRef::new(&value)), None);
}