//! Verify that tasks get polled by the virtualdom properly, and that we escape wait_for_work safely use std::{sync::atomic::AtomicUsize, time::Duration}; use dioxus::prelude::*; async fn run_vdom(app: fn() -> Element) { let mut dom = VirtualDom::new(app); dom.rebuild(&mut dioxus_core::NoOpMutations); tokio::select! { _ = dom.wait_for_work() => {} _ = tokio::time::sleep(Duration::from_millis(500)) => {} }; } #[tokio::test] async fn running_async() { static POLL_COUNT: AtomicUsize = AtomicUsize::new(0); fn app() -> Element { use_hook(|| { spawn(async { for x in 0..10 { tokio::time::sleep(Duration::from_micros(50)).await; POLL_COUNT.fetch_add(x, std::sync::atomic::Ordering::Relaxed); } }); spawn(async { for x in 0..10 { tokio::time::sleep(Duration::from_micros(25)).await; POLL_COUNT.fetch_add(x * 2, std::sync::atomic::Ordering::Relaxed); } }); }); rsx!({}) } run_vdom(app).await; // By the time the tasks are finished, we should've accumulated ticks from two tasks // Be warned that by setting the delay to too short, tokio might not schedule in the tasks assert_eq!( POLL_COUNT.fetch_add(0, std::sync::atomic::Ordering::Relaxed), 135 ); } /// Prove that yield_now doesn't cause a deadlock #[tokio::test] async fn yield_now_works() { thread_local! { static SEQUENCE: std::cell::RefCell> = std::cell::RefCell::new(Vec::new()); } fn app() -> Element { // these two tasks should yield to eachother use_hook(|| { spawn(async move { for _ in 0..10 { tokio::task::yield_now().await; SEQUENCE.with(|s| s.borrow_mut().push(1)); } }) }); use_hook(|| { spawn(async move { for _ in 0..10 { tokio::task::yield_now().await; SEQUENCE.with(|s| s.borrow_mut().push(2)); } }) }); rsx!({}) } run_vdom(app).await; SEQUENCE.with(|s| assert_eq!(s.borrow().len(), 20)); } /// Ensure that calling wait_for_flush waits for dioxus to finish its syncrhonous work #[tokio::test] async fn flushing() { thread_local! { static SEQUENCE: std::cell::RefCell> = std::cell::RefCell::new(Vec::new()); static BROADCAST: (tokio::sync::broadcast::Sender<()>, tokio::sync::broadcast::Receiver<()>) = tokio::sync::broadcast::channel(1); } fn app() -> Element { if generation() > 0 { println!("App"); SEQUENCE.with(|s| s.borrow_mut().push(0)); } use_hook(|| { spawn(async move { needs_update(); }); }); use_hook(|| { spawn(async move { for _ in 0..10 { BROADCAST.with(|b| b.1.resubscribe()).recv().await.unwrap(); println!("Task 1 recved"); flush_sync().await; println!("Task 1"); SEQUENCE.with(|s| s.borrow_mut().push(1)); } }) }); use_hook(|| { spawn(async move { for _ in 0..10 { BROADCAST.with(|b| b.1.resubscribe()).recv().await.unwrap(); println!("Task 2 recved"); flush_sync().await; println!("Task 2"); SEQUENCE.with(|s| s.borrow_mut().push(2)); } }) }); rsx! {} } let mut dom = VirtualDom::new(app); dom.rebuild(&mut dioxus_core::NoOpMutations); let fut = async { // Trigger the flush by waiting for work for _ in 0..30 { BROADCAST.with(|b| b.0.send(()).unwrap()); dom.mark_dirty(ScopeId(0)); tokio::select! { _ = dom.wait_for_work() => {} _ = tokio::time::sleep(Duration::from_millis(10)) => {} } dom.render_immediate(&mut dioxus_core::NoOpMutations); println!("Flushed"); } }; tokio::select! { _ = fut => {} _ = tokio::time::sleep(Duration::from_millis(500)) => { println!("Aborting due to timeout"); } }; SEQUENCE.with(|s| { let s = s.borrow(); println!("{:?}", s); assert_eq!(s.len(), 30); // We need to check if every three elements look like [0, 1, 2] or [0, 2, 1] let mut has_seen_1 = false; for (i, &x) in s.iter().enumerate() { let stage = i % 3; if stage == 0 { assert_eq!(x, 0); } else if stage == 1 { assert!(x == 1 || x == 2); has_seen_1 = x == 1; } else if stage == 2 { if has_seen_1 { assert_eq!(x, 2); } else { assert_eq!(x, 1); } } } }); }