1338 lines
34 KiB
Rust
1338 lines
34 KiB
Rust
mod any {
|
|
use zng::{
|
|
prelude::*,
|
|
var::{ArcVar, BoxedVar, ContextVar},
|
|
};
|
|
|
|
#[test]
|
|
fn downcast_ref_rc() {
|
|
let any_var = var(true).boxed_any();
|
|
assert!(any_var.as_any().downcast_ref::<ArcVar<bool>>().is_some())
|
|
}
|
|
|
|
#[test]
|
|
fn downcast_ref_boxed() {
|
|
let any_var = var(true).boxed().boxed_any();
|
|
assert!(any_var.as_any().downcast_ref::<ArcVar<bool>>().is_some())
|
|
}
|
|
|
|
#[test]
|
|
fn downcast_ref_context_var() {
|
|
context_var! {
|
|
static FOO_VAR: bool = true;
|
|
}
|
|
let any_var = FOO_VAR.boxed_any();
|
|
assert!(any_var.as_any().downcast_ref::<ContextVar<bool>>().is_some());
|
|
}
|
|
|
|
#[test]
|
|
fn downcast_double_boxed() {
|
|
let any_var = var(true).boxed_any().double_boxed_any();
|
|
assert!(any_var.downcast_ref::<BoxedVar<bool>>().is_some())
|
|
}
|
|
|
|
#[test]
|
|
fn downcast_rc() {
|
|
let any_var = var(true).boxed_any();
|
|
let any_box = any_var.as_any();
|
|
assert!(any_box.downcast_ref::<ArcVar<bool>>().is_some());
|
|
}
|
|
|
|
#[test]
|
|
fn downcast_boxed() {
|
|
let any_var = var(true).boxed().boxed_any();
|
|
let any_box = any_var.as_any();
|
|
assert!(any_box.downcast_ref::<ArcVar<bool>>().is_some());
|
|
}
|
|
}
|
|
|
|
mod bindings {
|
|
use zng::{prelude::*, var::VARS};
|
|
use zng_app::AppControlFlow;
|
|
|
|
#[test]
|
|
fn one_way_binding() {
|
|
let a = var(10);
|
|
let b = var("".to_txt());
|
|
|
|
let mut app = APP.minimal().run_headless(false);
|
|
app.update(false).assert_wait();
|
|
|
|
a.bind_map(&b, |a| a.to_txt()).perm();
|
|
|
|
let mut updated = 0;
|
|
let _ = app.update_observe(
|
|
|| {
|
|
updated += 1;
|
|
},
|
|
false,
|
|
);
|
|
assert_eq!(0, updated);
|
|
|
|
a.set(20);
|
|
|
|
let mut updated = false;
|
|
let _ = app.update_observe(
|
|
|| {
|
|
if !updated {
|
|
updated = true;
|
|
assert_eq!(Some(20i32), a.get_new());
|
|
assert_eq!(Some("20".to_txt()), b.get_new());
|
|
}
|
|
},
|
|
false,
|
|
);
|
|
assert!(updated);
|
|
|
|
a.set(13);
|
|
|
|
updated = false;
|
|
let _ = app.update_observe(
|
|
|| {
|
|
if !updated {
|
|
updated = true;
|
|
assert_eq!(Some(13i32), a.get_new());
|
|
assert_eq!(Some("13".to_txt()), b.get_new());
|
|
}
|
|
},
|
|
false,
|
|
);
|
|
assert!(updated);
|
|
}
|
|
|
|
#[test]
|
|
fn two_way_binding() {
|
|
let a = var(10);
|
|
let b = var("".to_txt());
|
|
|
|
let mut app = APP.minimal().run_headless(false);
|
|
app.update(false).assert_wait();
|
|
|
|
a.bind_map_bidi(&b, |a| a.to_txt(), |b| b.parse().unwrap()).perm();
|
|
|
|
let mut update_count = 0;
|
|
let _ = app.update_observe(
|
|
|| {
|
|
update_count += 1;
|
|
},
|
|
false,
|
|
);
|
|
assert_eq!(0, update_count);
|
|
|
|
a.set(20);
|
|
|
|
let mut updated = false;
|
|
let _ = app.update_observe(
|
|
|| {
|
|
if !updated {
|
|
updated = true;
|
|
assert_eq!(Some(20i32), a.get_new());
|
|
assert_eq!(Some("20".to_txt()), b.get_new());
|
|
}
|
|
},
|
|
false,
|
|
);
|
|
assert!(updated);
|
|
|
|
b.set("55");
|
|
|
|
updated = false;
|
|
let _ = app.update_observe(
|
|
|| {
|
|
if !updated {
|
|
updated = true;
|
|
assert_eq!(Some("55".to_txt()), b.get_new());
|
|
assert_eq!(Some(55i32), a.get_new());
|
|
}
|
|
},
|
|
false,
|
|
);
|
|
assert!(updated);
|
|
}
|
|
|
|
#[test]
|
|
fn one_way_filtered_binding() {
|
|
let a = var(10);
|
|
let b = var("".to_txt());
|
|
|
|
let mut app = APP.minimal().run_headless(false);
|
|
app.update(false).assert_wait();
|
|
|
|
a.bind_filter_map(&b, |a| if *a == 13 { None } else { Some(a.to_txt()) }).perm();
|
|
|
|
let mut update_count = 0;
|
|
let _ = app.update_observe(
|
|
|| {
|
|
update_count += 1;
|
|
},
|
|
false,
|
|
);
|
|
assert_eq!(0, update_count);
|
|
|
|
a.set(20);
|
|
|
|
let mut updated = false;
|
|
let _ = app.update_observe(
|
|
|| {
|
|
if !updated {
|
|
updated = true;
|
|
assert_eq!(Some(20i32), a.get_new());
|
|
assert_eq!(Some("20".to_txt()), b.get_new());
|
|
}
|
|
},
|
|
false,
|
|
);
|
|
assert!(updated);
|
|
|
|
a.set(13);
|
|
|
|
updated = false;
|
|
let _ = app.update_observe(
|
|
|| {
|
|
if !updated {
|
|
updated = true;
|
|
assert_eq!(Some(13i32), a.get_new());
|
|
assert_eq!("20".to_txt(), b.get());
|
|
assert!(!b.is_new());
|
|
}
|
|
},
|
|
false,
|
|
);
|
|
assert!(updated);
|
|
}
|
|
|
|
#[test]
|
|
fn two_way_filtered_binding() {
|
|
let a = var(10);
|
|
let b = var("".to_txt());
|
|
|
|
let mut app = APP.minimal().run_headless(false);
|
|
app.update(false).assert_wait();
|
|
|
|
a.bind_filter_map_bidi(&b, |a| Some(a.to_txt()), |b| b.parse().ok()).perm();
|
|
|
|
let mut update_count = 0;
|
|
let _ = app.update_observe(
|
|
|| {
|
|
update_count += 1;
|
|
},
|
|
false,
|
|
);
|
|
assert_eq!(0, update_count);
|
|
|
|
a.set(20);
|
|
|
|
let mut updated = false;
|
|
let _ = app.update_observe(
|
|
|| {
|
|
if !updated {
|
|
updated = true;
|
|
assert_eq!(Some(20i32), a.get_new());
|
|
assert_eq!(Some("20".to_txt()), b.get_new());
|
|
}
|
|
},
|
|
false,
|
|
);
|
|
assert!(updated);
|
|
|
|
b.set("55");
|
|
|
|
updated = false;
|
|
let _ = app.update_observe(
|
|
|| {
|
|
if !updated {
|
|
updated = true;
|
|
assert_eq!(Some("55".to_txt()), b.get_new());
|
|
assert_eq!(Some(55i32), a.get_new());
|
|
}
|
|
},
|
|
false,
|
|
);
|
|
assert!(updated);
|
|
|
|
b.set("not a i32");
|
|
|
|
updated = false;
|
|
let _ = app.update_observe(
|
|
|| {
|
|
if !updated {
|
|
updated = true;
|
|
assert_eq!(Some("not a i32".to_txt()), b.get_new());
|
|
assert_eq!(55i32, a.get());
|
|
assert!(!a.is_new());
|
|
}
|
|
},
|
|
false,
|
|
);
|
|
assert!(updated);
|
|
}
|
|
|
|
#[test]
|
|
fn binding_chain() {
|
|
let a = var(0);
|
|
let b = var(0);
|
|
let c = var(0);
|
|
let d = var(0);
|
|
|
|
let mut app = APP.minimal().run_headless(false);
|
|
app.update(false).assert_wait();
|
|
|
|
a.bind_map(&b, |a| *a + 1).perm();
|
|
b.bind_map(&c, |b| *b + 1).perm();
|
|
c.bind_map(&d, |c| *c + 1).perm();
|
|
|
|
let mut update_count = 0;
|
|
let _ = app.update_observe(
|
|
|| {
|
|
update_count += 1;
|
|
},
|
|
false,
|
|
);
|
|
assert_eq!(0, update_count);
|
|
|
|
a.set(20);
|
|
|
|
let mut updated = false;
|
|
let _ = app.update_observe(
|
|
|| {
|
|
if !updated {
|
|
updated = true;
|
|
|
|
assert_eq!(Some(20), a.get_new());
|
|
assert_eq!(Some(21), b.get_new());
|
|
assert_eq!(Some(22), c.get_new());
|
|
assert_eq!(Some(23), d.get_new());
|
|
}
|
|
},
|
|
false,
|
|
);
|
|
assert!(updated);
|
|
|
|
a.set(30);
|
|
|
|
let mut updated = false;
|
|
let _ = app.update_observe(
|
|
|| {
|
|
if !updated {
|
|
updated = true;
|
|
|
|
assert_eq!(Some(30), a.get_new());
|
|
assert_eq!(Some(31), b.get_new());
|
|
assert_eq!(Some(32), c.get_new());
|
|
assert_eq!(Some(33), d.get_new());
|
|
}
|
|
},
|
|
false,
|
|
);
|
|
assert!(updated);
|
|
}
|
|
|
|
#[test]
|
|
fn binding_bidi_chain() {
|
|
let a = var(0);
|
|
let b = var(0);
|
|
let c = var(0);
|
|
let d = var(0);
|
|
|
|
let mut app = APP.minimal().run_headless(false);
|
|
app.update(false).assert_wait();
|
|
|
|
a.bind_bidi(&b).perm();
|
|
b.bind_bidi(&c).perm();
|
|
c.bind_bidi(&d).perm();
|
|
|
|
let mut update_count = 0;
|
|
let _ = app.update_observe(
|
|
|| {
|
|
update_count += 1;
|
|
},
|
|
false,
|
|
);
|
|
assert_eq!(0, update_count);
|
|
|
|
a.set(20);
|
|
|
|
let mut updated = false;
|
|
let _ = app.update_observe(
|
|
|| {
|
|
if !updated {
|
|
updated = true;
|
|
|
|
assert_eq!(Some(20), a.get_new());
|
|
assert_eq!(Some(20), b.get_new());
|
|
assert_eq!(Some(20), c.get_new());
|
|
assert_eq!(Some(20), d.get_new());
|
|
}
|
|
},
|
|
false,
|
|
);
|
|
assert!(updated);
|
|
|
|
d.set(30);
|
|
|
|
let mut updated = false;
|
|
let _ = app.update_observe(
|
|
|| {
|
|
if !updated {
|
|
updated = true;
|
|
|
|
assert_eq!(Some(30), a.get_new());
|
|
assert_eq!(Some(30), b.get_new());
|
|
assert_eq!(Some(30), c.get_new());
|
|
assert_eq!(Some(30), d.get_new());
|
|
}
|
|
},
|
|
false,
|
|
);
|
|
assert!(updated);
|
|
}
|
|
|
|
#[test]
|
|
fn binding_drop() {
|
|
let a = var(1);
|
|
let b = var(1);
|
|
|
|
let mut app = APP.minimal().run_headless(false);
|
|
|
|
let handle = a.bind_map(&b, |i| *i + 1);
|
|
|
|
a.set(10);
|
|
|
|
let mut updated = false;
|
|
let _ = app.update_observe(
|
|
|| {
|
|
if !updated {
|
|
updated = true;
|
|
|
|
assert_eq!(Some(10), a.get_new());
|
|
assert_eq!(Some(11), b.get_new());
|
|
}
|
|
},
|
|
false,
|
|
);
|
|
assert!(updated);
|
|
|
|
drop(handle);
|
|
|
|
a.set(100);
|
|
|
|
updated = false;
|
|
let _ = app.update_observe(
|
|
|| {
|
|
if !updated {
|
|
updated = true;
|
|
|
|
assert_eq!(Some(100), a.get_new());
|
|
assert!(!b.is_new());
|
|
assert_eq!(11, b.get());
|
|
}
|
|
},
|
|
false,
|
|
);
|
|
assert!(updated);
|
|
|
|
assert_eq!(1, a.strong_count());
|
|
assert_eq!(1, b.strong_count());
|
|
}
|
|
|
|
#[test]
|
|
fn binding_bidi_set_both() {
|
|
let mut app = APP.minimal().run_headless(false);
|
|
|
|
let a = var(1);
|
|
let b = var(1);
|
|
a.bind_bidi(&b).perm();
|
|
|
|
a.set(10);
|
|
b.set(20);
|
|
app.update(false).assert_wait();
|
|
|
|
assert_eq!(20, a.get());
|
|
assert_eq!(20, b.get());
|
|
}
|
|
|
|
#[test]
|
|
fn binding_update_order() {
|
|
let mut app = APP.minimal().run_headless(false);
|
|
|
|
let a = var(0);
|
|
let b = var(0);
|
|
a.bind(&b).perm();
|
|
|
|
a.set(1);
|
|
b.set(10);
|
|
|
|
app.update(false).assert_wait();
|
|
|
|
assert_eq!(10, b.get());
|
|
}
|
|
|
|
#[test]
|
|
fn binding_update_order2() {
|
|
let mut app = APP.minimal().run_headless(false);
|
|
|
|
let a = var(0);
|
|
let b = var(0);
|
|
|
|
a.set(1);
|
|
b.set(a.get());
|
|
a.bind(&b).perm();
|
|
|
|
app.update(false).assert_wait();
|
|
|
|
assert_eq!(0, b.get());
|
|
}
|
|
|
|
#[test]
|
|
fn binding_update_order3() {
|
|
let mut app = APP.minimal().run_headless(false);
|
|
|
|
let a = var(0);
|
|
let b = var(0);
|
|
|
|
a.set(1);
|
|
b.set_from(&a);
|
|
a.bind(&b).perm();
|
|
|
|
app.update(false).assert_wait();
|
|
|
|
assert_eq!(1, b.get());
|
|
}
|
|
|
|
#[test]
|
|
fn animation_and_set_from() {
|
|
let mut app = APP.minimal().run_headless(false);
|
|
|
|
let a = var(0);
|
|
let b = var(0);
|
|
|
|
VARS.animate(clmv!(a, |_| {
|
|
a.set(1);
|
|
APP.exit();
|
|
}))
|
|
.perm();
|
|
|
|
b.set_from(&a);
|
|
a.bind(&b).perm();
|
|
|
|
while let AppControlFlow::Wait = app.update(true) {}
|
|
|
|
assert_eq!(1, a.get());
|
|
assert_eq!(1, b.get());
|
|
}
|
|
}
|
|
|
|
mod context {
|
|
use zng::{
|
|
app::{AppExtended, AppExtension, HeadlessApp},
|
|
prelude::*,
|
|
prelude_wgt::*,
|
|
var::{AnyWhenVarBuilder, ContextInitHandle},
|
|
};
|
|
|
|
context_var! {
|
|
static TEST_VAR: Txt = "";
|
|
}
|
|
|
|
app_local! {
|
|
static PROBE_ID: Txt = const { Txt::from_static("") };
|
|
}
|
|
|
|
#[property(CONTEXT, default(TEST_VAR))]
|
|
fn test_prop(child: impl UiNode, value: impl IntoVar<Txt>) -> impl UiNode {
|
|
with_context_var(child, TEST_VAR, value)
|
|
}
|
|
|
|
#[property(CONTEXT, default(TEST_VAR))]
|
|
fn test_prop_a(child: impl UiNode, value: impl IntoVar<Txt>) -> impl UiNode {
|
|
test_prop(child, value)
|
|
}
|
|
#[property(CONTEXT, default(TEST_VAR))]
|
|
fn test_prop_b(child: impl UiNode, value: impl IntoVar<Txt>) -> impl UiNode {
|
|
test_prop(child, value)
|
|
}
|
|
|
|
#[property(CONTEXT)]
|
|
fn probe(child: impl UiNode, var: impl IntoVar<Txt>) -> impl UiNode {
|
|
let var = var.into_var();
|
|
match_node(child, move |_, op| {
|
|
if let UiNodeOp::Init = op {
|
|
*PROBE_ID.write() = var.get();
|
|
}
|
|
})
|
|
}
|
|
#[property(CONTEXT)]
|
|
fn probe_a(child: impl UiNode, var: impl IntoVar<Txt>) -> impl UiNode {
|
|
probe(child, var)
|
|
}
|
|
#[property(CONTEXT)]
|
|
fn probe_b(child: impl UiNode, var: impl IntoVar<Txt>) -> impl UiNode {
|
|
probe(child, var)
|
|
}
|
|
|
|
#[property(EVENT)]
|
|
fn on_init(child: impl UiNode, mut handler: impl WidgetHandler<()>) -> impl UiNode {
|
|
match_node(child, move |child, op| match op {
|
|
UiNodeOp::Init => {
|
|
child.init();
|
|
handler.event(&());
|
|
}
|
|
UiNodeOp::Update { updates } => {
|
|
child.update(updates);
|
|
handler.update();
|
|
}
|
|
_ => {}
|
|
})
|
|
}
|
|
|
|
#[widget($crate::context::TestWgt)]
|
|
struct TestWgt(WidgetBase);
|
|
impl TestWgt {
|
|
fn widget_intrinsic(&mut self) {
|
|
self.widget_builder().push_build_action(|wgt| {
|
|
if let Some(child) = wgt.capture_ui_node(property_id!(Self::child)) {
|
|
wgt.set_child(child);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
#[property(CHILD, capture, widget_impl(TestWgt))]
|
|
fn child(child: impl UiNode) {}
|
|
|
|
fn test_app(app: AppExtended<impl AppExtension>, root: impl UiNode) -> HeadlessApp {
|
|
zng_app::test_log();
|
|
|
|
let mut app = app.run_headless(false);
|
|
WINDOWS.open(async move { window::WindowRoot::new_test(root) });
|
|
let _ = app.update(false);
|
|
app
|
|
}
|
|
|
|
#[test]
|
|
fn context_var_basic() {
|
|
let _test = test_app(
|
|
APP.defaults(),
|
|
TestWgt! {
|
|
test_prop = "test!";
|
|
|
|
child = TestWgt! {
|
|
probe = TEST_VAR;
|
|
}
|
|
},
|
|
);
|
|
|
|
assert_eq!(&*PROBE_ID.read(), &Txt::from("test!"));
|
|
}
|
|
|
|
#[test]
|
|
fn context_var_map() {
|
|
let _test = test_app(
|
|
APP.defaults(),
|
|
TestWgt! {
|
|
test_prop = "test!";
|
|
|
|
child = TestWgt! {
|
|
probe = TEST_VAR.map(|t| formatx!("map {t}"));
|
|
}
|
|
},
|
|
);
|
|
|
|
assert_eq!(&*PROBE_ID.read(), &Txt::from("map test!"));
|
|
}
|
|
|
|
#[test]
|
|
fn context_var_map_cloned() {
|
|
let app = APP.defaults();
|
|
|
|
// mapped context var should depend on the context.
|
|
|
|
let mapped = TEST_VAR.map(|t| formatx!("map {t}"));
|
|
|
|
let _test = test_app(
|
|
app,
|
|
TestWgt! {
|
|
test_prop_a = "A!";
|
|
|
|
child = TestWgt! {
|
|
probe = mapped.clone();
|
|
test_prop_b = "B!";
|
|
|
|
child = TestWgt! {
|
|
probe = mapped;
|
|
}
|
|
}
|
|
},
|
|
);
|
|
|
|
assert_eq!(&*PROBE_ID.read(), &Txt::from("map B!"));
|
|
}
|
|
|
|
#[test]
|
|
fn context_var_map_cloned3() {
|
|
let app = APP.defaults();
|
|
// mapped context var should depend on the context.
|
|
|
|
let mapped = TEST_VAR.map(|t| formatx!("map {t}"));
|
|
let _test = test_app(
|
|
app,
|
|
TestWgt! {
|
|
test_prop = "A!";
|
|
|
|
child = TestWgt! {
|
|
probe = mapped.clone();
|
|
test_prop = "B!";
|
|
|
|
child = TestWgt! {
|
|
probe = mapped.clone();
|
|
test_prop = "C!";
|
|
|
|
child = TestWgt! {
|
|
probe = mapped;
|
|
test_prop = "D!";
|
|
}
|
|
}
|
|
}
|
|
},
|
|
);
|
|
|
|
assert_eq!(&*PROBE_ID.read(), &Txt::from("map C!"));
|
|
}
|
|
|
|
#[test]
|
|
fn context_var_map_not_cloned() {
|
|
let app = APP.defaults();
|
|
|
|
// sanity check for `context_var_map_cloned`
|
|
|
|
let _test = test_app(
|
|
app,
|
|
TestWgt! {
|
|
test_prop_a = "A!";
|
|
|
|
child = TestWgt! {
|
|
probe = TEST_VAR.map(|t| formatx!("map {t}"));
|
|
test_prop_b = "B!";
|
|
|
|
child = TestWgt! {
|
|
probe = TEST_VAR.map(|t| formatx!("map {t}"));
|
|
}
|
|
}
|
|
},
|
|
);
|
|
|
|
assert_eq!(&*PROBE_ID.read(), &Txt::from("map B!"));
|
|
}
|
|
|
|
#[test]
|
|
fn context_var_map_moved_app_ctx() {
|
|
let _app = APP.minimal();
|
|
|
|
let mapped = TEST_VAR.map(|t| formatx!("map {t}"));
|
|
let a = TEST_VAR.with_context_var(ContextInitHandle::new(), "A", || mapped.get());
|
|
|
|
let b = TEST_VAR.with_context_var(ContextInitHandle::new(), "B", || mapped.get());
|
|
|
|
assert_ne!(a, b);
|
|
}
|
|
|
|
#[test]
|
|
fn context_var_cloned_same_widget() {
|
|
let app = APP.defaults();
|
|
|
|
let mapped = TEST_VAR.map(|t| formatx!("map {t}"));
|
|
|
|
let _test = test_app(
|
|
app,
|
|
TestWgt! {
|
|
test_prop_a = "A!";
|
|
probe_a = mapped.clone();
|
|
test_prop_b = "B!";
|
|
probe_b = mapped;
|
|
},
|
|
);
|
|
|
|
assert_eq!(&*PROBE_ID.read(), &Txt::from("map B!"));
|
|
}
|
|
|
|
#[test]
|
|
fn context_var_set() {
|
|
let mut app = test_app(APP.defaults(), NilUiNode);
|
|
|
|
let backing_var = var(Txt::from(""));
|
|
|
|
TEST_VAR.with_context_var(ContextInitHandle::new(), backing_var.clone(), || {
|
|
let t = TEST_VAR;
|
|
assert!(t.capabilities().contains(VarCapability::MODIFY));
|
|
t.set("set!").unwrap();
|
|
});
|
|
|
|
let _ = app.update(false);
|
|
assert_eq!(backing_var.get(), "set!");
|
|
}
|
|
|
|
#[test]
|
|
fn context_var_binding() {
|
|
let app = APP.defaults();
|
|
|
|
let input_var = var("Input!".to_txt());
|
|
let other_var = var(".".to_txt());
|
|
|
|
let mut test = test_app(
|
|
app,
|
|
TestWgt! {
|
|
test_prop = input_var.clone();
|
|
on_init = hn_once!(other_var, |_| {
|
|
TEST_VAR.bind(&other_var).perm();
|
|
});
|
|
child = NilUiNode;
|
|
},
|
|
);
|
|
|
|
test.update(false).assert_wait();
|
|
|
|
assert_eq!(".", other_var.get());
|
|
|
|
input_var.set("Update!");
|
|
|
|
test.update(false).assert_wait();
|
|
|
|
assert_eq!("Update!", input_var.get());
|
|
assert_eq!("Update!", other_var.get());
|
|
}
|
|
|
|
#[test]
|
|
fn context_var_recursion_when1() {
|
|
let _scope = APP.minimal();
|
|
|
|
let var = when_var! {
|
|
false => var("hello".to_txt()),
|
|
_ => TEST_VAR,
|
|
};
|
|
|
|
let r = TEST_VAR.with_context_var(ContextInitHandle::new(), var.clone(), || var.get());
|
|
|
|
assert_eq!("", r);
|
|
}
|
|
|
|
#[test]
|
|
fn context_var_recursion_when2() {
|
|
let _scope = APP.minimal();
|
|
|
|
let var = when_var! {
|
|
true => TEST_VAR,
|
|
_ => var("hello".to_txt()),
|
|
};
|
|
|
|
let r = TEST_VAR.with_context_var(ContextInitHandle::new(), var.clone(), || var.get());
|
|
|
|
assert_eq!("", r);
|
|
}
|
|
|
|
#[test]
|
|
fn context_var_recursion_issue_when_any() {
|
|
let _scope = APP.minimal();
|
|
|
|
let mut var = AnyWhenVarBuilder::new(TEST_VAR);
|
|
var.push(self::var(false), self::var("hello".to_txt()));
|
|
let var = var.build().unwrap();
|
|
|
|
let r = TEST_VAR.with_context_var(ContextInitHandle::new(), var.clone(), || var.get());
|
|
|
|
assert_eq!("", r);
|
|
}
|
|
|
|
#[test]
|
|
fn context_var_recursion_merge() {
|
|
let _scope = APP.minimal();
|
|
|
|
let var = merge_var!(TEST_VAR, var(true), |t, _| t.clone());
|
|
|
|
let r = TEST_VAR.with_context_var(ContextInitHandle::new(), var.clone(), || var.get());
|
|
|
|
assert_eq!("", r);
|
|
}
|
|
}
|
|
|
|
mod flat_map {
|
|
use std::fmt;
|
|
use zng::{prelude::*, var::ArcVar};
|
|
|
|
#[derive(Clone)]
|
|
pub struct Foo {
|
|
pub bar: bool,
|
|
pub var: ArcVar<usize>,
|
|
}
|
|
impl PartialEq for Foo {
|
|
fn eq(&self, other: &Self) -> bool {
|
|
self.bar == other.bar && self.var.var_ptr() == other.var.var_ptr()
|
|
}
|
|
}
|
|
impl fmt::Debug for Foo {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
f.debug_struct("Foo").field("bar", &self.bar).finish_non_exhaustive()
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
pub fn flat_map() {
|
|
let mut app = APP.minimal().run_headless(false);
|
|
|
|
let source = var(Foo { bar: true, var: var(32) });
|
|
|
|
let test = source.flat_map(|f| f.var.clone());
|
|
|
|
assert_eq!(32, test.get());
|
|
|
|
source.get().var.set(42usize);
|
|
|
|
let _ = app.update_observe(
|
|
|| {
|
|
assert!(test.is_new());
|
|
assert_eq!(42, test.get());
|
|
},
|
|
false,
|
|
);
|
|
|
|
let old_var = source.get().var;
|
|
source.set(Foo { bar: false, var: var(192) });
|
|
let _ = app.update_observe(
|
|
|| {
|
|
assert!(test.is_new());
|
|
assert_eq!(192, test.get());
|
|
},
|
|
false,
|
|
);
|
|
|
|
old_var.set(220usize);
|
|
let _ = app.update_observe(
|
|
|| {
|
|
assert!(!test.is_new());
|
|
assert_eq!(192, test.get());
|
|
},
|
|
false,
|
|
);
|
|
}
|
|
}
|
|
|
|
mod modify_importance {
|
|
use zng::{prelude::*, var::VARS};
|
|
|
|
#[test]
|
|
pub fn set_same_importance() {
|
|
let mut app = APP.minimal().run_headless(false);
|
|
|
|
let test = var(Txt::from_static("v0"));
|
|
test.set("v1");
|
|
app.update(false).assert_wait();
|
|
assert_eq!("v1", test.get());
|
|
let importance = test.modify_importance();
|
|
|
|
test.set("v2");
|
|
app.update(false).assert_wait();
|
|
assert_eq!("v2", test.get());
|
|
assert_eq!(importance, test.modify_importance());
|
|
}
|
|
|
|
#[test]
|
|
pub fn set_same_importance_in_vars() {
|
|
let mut app = APP.minimal().run_headless(false);
|
|
|
|
let test = var(Txt::from_static("v0"));
|
|
test.set("v1");
|
|
app.update(false).assert_wait();
|
|
assert_eq!("v1", test.get());
|
|
let importance = VARS.current_modify().importance();
|
|
|
|
test.set("v2");
|
|
app.update(false).assert_wait();
|
|
assert_eq!("v2", test.get());
|
|
assert_eq!(importance, VARS.current_modify().importance());
|
|
}
|
|
|
|
#[test]
|
|
pub fn animate_set_diff_importance() {
|
|
let mut app = APP.minimal().run_headless(false);
|
|
|
|
let test = var(Txt::from_static("v0"));
|
|
test.set("v1");
|
|
app.update(false).assert_wait();
|
|
assert_eq!("v1", test.get());
|
|
let importance = test.modify_importance();
|
|
|
|
test.step("v2", 0.ms()).perm();
|
|
app.run_task(async_clmv!(test, {
|
|
test.wait_animation().await;
|
|
}));
|
|
assert_eq!("v2", test.get());
|
|
assert!(importance <= test.modify_importance());
|
|
let importance = test.modify_importance();
|
|
|
|
test.set("v3");
|
|
app.update(false).assert_wait();
|
|
assert_eq!("v3", test.get());
|
|
assert!(importance <= test.modify_importance());
|
|
}
|
|
|
|
#[test]
|
|
pub fn animate_set_diff_importance_in_vars() {
|
|
let mut app = APP.minimal().run_headless(false);
|
|
|
|
let test = var(Txt::from_static("v0"));
|
|
test.set("v1");
|
|
app.update(false).assert_wait();
|
|
assert_eq!("v1", test.get());
|
|
let importance = VARS.current_modify().importance();
|
|
|
|
test.step("v2", 0.ms()).perm();
|
|
app.run_task(async_clmv!(test, {
|
|
test.wait_animation().await;
|
|
}));
|
|
assert_eq!("v2", test.get());
|
|
assert!(importance <= VARS.current_modify().importance());
|
|
let importance = VARS.current_modify().importance();
|
|
|
|
test.set("v3");
|
|
app.update(false).assert_wait();
|
|
assert_eq!("v3", test.get());
|
|
assert!(importance <= VARS.current_modify().importance());
|
|
}
|
|
|
|
#[test]
|
|
pub fn animate_in_hook() {
|
|
let mut app = APP.minimal().run_headless(false);
|
|
|
|
let test = var(Txt::from_static("v0"));
|
|
let ease = var(0i32);
|
|
test.hook_any(Box::new(clmv!(ease, |_| {
|
|
ease.ease(100, 10.ms(), easing::linear).perm();
|
|
false // once
|
|
})))
|
|
.perm();
|
|
let importance = VARS.current_modify().importance();
|
|
|
|
test.set("v1");
|
|
app.update(false).assert_wait();
|
|
assert_eq!("v1", test.get());
|
|
|
|
app.run_task(async_clmv!(ease, {
|
|
ease.wait_animation().await;
|
|
}));
|
|
assert_eq!(100, ease.get());
|
|
|
|
assert!(importance < VARS.current_modify().importance());
|
|
}
|
|
}
|
|
|
|
mod cow {
|
|
use std::sync::Arc;
|
|
|
|
use zng::{prelude::*, task::parking_lot::Mutex};
|
|
|
|
#[test]
|
|
pub fn cow_base_update() {
|
|
let mut app = APP.minimal().run_headless(false);
|
|
|
|
let base = var(false);
|
|
let cow = base.cow();
|
|
|
|
base.set(true);
|
|
app.update(false).assert_wait();
|
|
|
|
assert!(base.get());
|
|
assert!(cow.get());
|
|
}
|
|
|
|
#[test]
|
|
pub fn cow_update() {
|
|
let mut app = APP.minimal().run_headless(false);
|
|
|
|
let base = var(false);
|
|
let cow = base.cow();
|
|
|
|
cow.set(true);
|
|
app.update(false).assert_wait();
|
|
|
|
assert!(!base.get());
|
|
assert!(cow.get());
|
|
}
|
|
|
|
#[test]
|
|
pub fn cow_update_full() {
|
|
let mut app = APP.minimal().run_headless(false);
|
|
|
|
let base = var(0);
|
|
let cow = base.cow();
|
|
|
|
let base_values = Arc::new(Mutex::new(vec![]));
|
|
let cow_values = Arc::new(Mutex::new(vec![]));
|
|
base.trace_value(clmv!(base_values, |v| base_values.lock().push(*v.value()))).perm();
|
|
cow.trace_value(clmv!(cow_values, |v| cow_values.lock().push(*v.value()))).perm();
|
|
|
|
base.set(1);
|
|
app.update(false).assert_wait();
|
|
|
|
assert_eq!(1, base.get());
|
|
assert_eq!(1, cow.get());
|
|
|
|
cow.set(2);
|
|
app.update(false).assert_wait();
|
|
|
|
assert_eq!(1, base.get());
|
|
assert_eq!(2, cow.get());
|
|
|
|
assert_eq!(&base_values.lock()[..], &[0, 1]);
|
|
assert_eq!(&cow_values.lock()[..], &[0, 1, 2]);
|
|
|
|
base.set(3);
|
|
app.update(false).assert_wait();
|
|
base.set(4);
|
|
app.update(false).assert_wait();
|
|
assert_eq!(&base_values.lock()[..], &[0, 1, 3, 4]);
|
|
assert_eq!(&cow_values.lock()[..], &[0, 1, 2]);
|
|
}
|
|
}
|
|
|
|
mod multi {
|
|
use std::sync::Arc;
|
|
|
|
use zng::{prelude::*, task::parking_lot::Mutex};
|
|
|
|
#[test]
|
|
fn multi_bidi() {
|
|
let mut app = APP.minimal().run_headless(false);
|
|
|
|
let a = var(false);
|
|
let b = a.map_bidi(
|
|
|&a| if a { 1i32 } else { 0 },
|
|
|&b| match b {
|
|
0 => false,
|
|
1 => true,
|
|
n => panic!("invalid test {n}"),
|
|
},
|
|
);
|
|
|
|
let a_values = Arc::new(Mutex::new(vec![]));
|
|
let b_values = Arc::new(Mutex::new(vec![]));
|
|
a.trace_value(clmv!(a_values, |v| a_values.lock().push(*v.value()))).perm();
|
|
b.trace_value(clmv!(b_values, |v| b_values.lock().push(*v.value()))).perm();
|
|
|
|
assert!(!a.get());
|
|
assert_eq!(b.get(), 0);
|
|
|
|
a.set(true);
|
|
app.update(false).assert_wait();
|
|
assert!(a.get());
|
|
assert_eq!(b.get(), 1);
|
|
|
|
assert_eq!(&a_values.lock()[..], &[false, true]);
|
|
assert_eq!(&b_values.lock()[..], &[0, 1]);
|
|
}
|
|
}
|
|
|
|
mod threads {
|
|
use zng::prelude::*;
|
|
|
|
#[test]
|
|
fn set_from_other_thread_once() {
|
|
let mut app = APP.minimal().run_headless(false);
|
|
|
|
let test = var(1);
|
|
|
|
task::spawn(async_clmv!(test, {
|
|
test.set(2);
|
|
}));
|
|
|
|
let test = async move {
|
|
while test.get() != 2 {
|
|
test.wait_update().await;
|
|
}
|
|
};
|
|
app.run_task(task::with_deadline(test, 20.secs())).unwrap().unwrap();
|
|
}
|
|
|
|
#[test]
|
|
fn set_from_other_thread_many() {
|
|
let mut app = APP.minimal().run_headless(false);
|
|
|
|
let test = var(1);
|
|
|
|
task::spawn(async_clmv!(test, {
|
|
for i in 2..=100 {
|
|
test.set(i);
|
|
if i % 10 == 0 {
|
|
task::deadline(2.ms()).await;
|
|
}
|
|
}
|
|
}));
|
|
|
|
let mut prev = 0;
|
|
let test = async move {
|
|
loop {
|
|
let new = test.get();
|
|
assert!(prev < new, "{prev} < {new}");
|
|
if new == 100 {
|
|
break;
|
|
}
|
|
prev = new;
|
|
test.wait_update().await;
|
|
}
|
|
};
|
|
|
|
app.run_task(task::with_deadline(test, 40.secs())).unwrap().unwrap();
|
|
}
|
|
}
|
|
|
|
mod contextualized {
|
|
use zng::{
|
|
prelude::*,
|
|
var::{ContextInitHandle, ContextualizedVar},
|
|
};
|
|
|
|
#[test]
|
|
fn nested_contextualized_vars() {
|
|
let mut app = APP.defaults().run_headless(false);
|
|
|
|
let var = var(0u32);
|
|
let source = ContextualizedVar::new(move || var.clone());
|
|
let mapped = source.map(|n| n + 1);
|
|
let mapped2 = mapped.map(|n| n - 1);
|
|
let mapped2_copy = mapped2.clone();
|
|
|
|
// init, same effect as subscribe in widgets, the last to init breaks the other.
|
|
assert_eq!(0, mapped2.get());
|
|
assert_eq!(0, mapped2_copy.get());
|
|
|
|
source.set(10u32).unwrap();
|
|
let mut updated = false;
|
|
app.update_observe(
|
|
|| {
|
|
if !updated {
|
|
updated = true;
|
|
assert_eq!(Some(10), mapped2.get_new());
|
|
assert_eq!(Some(10), mapped2_copy.get_new());
|
|
}
|
|
},
|
|
false,
|
|
)
|
|
.assert_wait();
|
|
|
|
assert!(updated);
|
|
}
|
|
|
|
#[test]
|
|
fn nested_contextualized_vars_diff_contexts() {
|
|
let mut app = APP.defaults().run_headless(false);
|
|
|
|
let var = var(0u32);
|
|
let source = ContextualizedVar::new(move || var.clone());
|
|
let mapped = source.map(|n| n + 1);
|
|
let mapped2 = mapped.map(|n| n - 1);
|
|
let mapped2_copy = mapped2.clone();
|
|
|
|
// init, same effect as subscribe in widgets, the last to init breaks the other.
|
|
assert_eq!(0, mapped2.get());
|
|
let other_ctx = ContextInitHandle::new();
|
|
other_ctx.with_context(|| {
|
|
assert_eq!(0, mapped2_copy.get());
|
|
});
|
|
|
|
source.set(10u32).unwrap();
|
|
let mut updated = false;
|
|
app.update_observe(
|
|
|| {
|
|
if !updated {
|
|
updated = true;
|
|
assert_eq!(Some(10), mapped2.get_new());
|
|
other_ctx.with_context(|| {
|
|
assert_eq!(Some(10), mapped2_copy.get_new());
|
|
});
|
|
}
|
|
},
|
|
false,
|
|
)
|
|
.assert_wait();
|
|
|
|
assert!(updated);
|
|
}
|
|
}
|
|
|
|
mod vec {
|
|
use zng::{
|
|
prelude::*,
|
|
var::{ObservableVec, VecChange},
|
|
};
|
|
|
|
#[test]
|
|
fn basic_usage() {
|
|
let mut app = APP.minimal().run_headless(false);
|
|
|
|
let list = var(ObservableVec::<u32>::new());
|
|
|
|
list.modify(|a| {
|
|
a.to_mut().push(32);
|
|
});
|
|
app.update_observe(
|
|
|| {
|
|
assert!(list.is_new());
|
|
|
|
list.with_new(|l| {
|
|
assert_eq!(&[32], &l[..]);
|
|
assert_eq!(&[VecChange::Insert { index: 0, count: 1 }], l.changes());
|
|
});
|
|
},
|
|
false,
|
|
)
|
|
.assert_wait();
|
|
|
|
list.modify(|a| {
|
|
a.to_mut().push(33);
|
|
});
|
|
app.update_observe(
|
|
|| {
|
|
assert!(list.is_new());
|
|
|
|
list.with_new(|l| {
|
|
assert_eq!(&[32, 33], &l[..]);
|
|
assert_eq!(&[VecChange::Insert { index: 1, count: 1 }], l.changes());
|
|
});
|
|
},
|
|
false,
|
|
)
|
|
.assert_wait();
|
|
}
|
|
}
|
|
|
|
mod response {
|
|
use zng::prelude::*;
|
|
|
|
#[test]
|
|
fn race_condition() {
|
|
let mut app = APP.minimal().run_headless(false);
|
|
|
|
for _ in 0..10 {
|
|
let a = task::respond(async {
|
|
task::deadline(1.ms()).await;
|
|
'a'
|
|
});
|
|
let b = task::respond(async {
|
|
task::deadline(1.ms()).await;
|
|
'b'
|
|
});
|
|
let ab = task::respond(async {
|
|
let mut r = String::new();
|
|
for v in [a, b] {
|
|
r.push(v.wait_into_rsp().await);
|
|
}
|
|
r
|
|
});
|
|
|
|
let ab = app
|
|
.run_task(async { task::with_deadline(ab.wait_into_rsp(), 20.secs()).await })
|
|
.unwrap()
|
|
.unwrap();
|
|
|
|
assert_eq!(ab, "ab");
|
|
}
|
|
}
|
|
}
|