#[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);
}