From dab3ac07ff7a53a0ed1757ad163d86177bf08dbe Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 30 Jun 2025 15:28:10 -0500 Subject: [PATCH] Fix hook test example --- .../docs-router/src/doc_examples/hook_test.rs | 212 +++++++++++------- 1 file changed, 134 insertions(+), 78 deletions(-) diff --git a/packages/docs-router/src/doc_examples/hook_test.rs b/packages/docs-router/src/doc_examples/hook_test.rs index af213521e..3379278b0 100644 --- a/packages/docs-router/src/doc_examples/hook_test.rs +++ b/packages/docs-router/src/doc_examples/hook_test.rs @@ -1,104 +1,160 @@ -use futures::FutureExt; -use std::{cell::RefCell, rc::Rc, sync::Arc, thread::Scope}; - -use dioxus::{dioxus_core::NoOpMutations, prelude::*}; - -#[test] -fn test() { - test_hook( - || use_signal(|| 0), - |mut value, mut proxy| match proxy.generation { - 0 => { - value.set(1); +use dioxus::prelude::*; +use futures::{ + channel::mpsc::{Receiver, Sender}, + StreamExt, +}; +use std::{cell::RefCell, fmt::Debug, future::Future, rc::Rc}; + +#[tokio::test] +async fn test_use_resource() { + let mut dom = TestDom::new(|| { + let mut signal = use_signal(|| 0); + + use_events(|mut events| async move { + while let Some(event) = events.next().await { + signal.set(event); } - 1 => { - assert_eq!(*value.read(), 1); - value.set(2); + }); + + let async_doubled = use_resource(move || async move { + tokio::time::sleep(std::time::Duration::from_millis(100)).await; + signal * 2 + }); + + rsx! { + if let Some(value) = async_doubled() { + div { "{value}" } + } else { + div { "Loading..." } } - 2 => { - proxy.rerun(); - } - 3 => {} - _ => todo!(), - }, - |proxy| assert_eq!(proxy.generation, 4), - ); + } + }); + + // Initial render should show "Loading..." + dom.assert_eq(rsx! { div { "Loading..." } }); + + dom.run_async().await; + + // After the async resource resolves, it should show "0" + dom.assert_eq(rsx! { div { "0" } }); + + dom.send(1); + + // While it is resolving it should show the old value "0" + dom.assert_eq(rsx! { div { "0" } }); + + dom.run_async().await; + + // After the async resource resolves again, it should show "2" + dom.assert_eq(rsx! { div { "2" } }); } -fn test_hook( - initialize: impl FnMut() -> V + 'static, - check: impl FnMut(V, MockProxy) + 'static, - mut final_check: impl FnMut(MockProxy) + 'static, +fn use_events + 'static>( + with_events: impl FnOnce(Receiver) -> F, ) { - #[derive(Props)] - struct MockAppComponent { - hook: Rc>, - check: Rc>, - } + use_hook(move || { + let context = consume_context::>(); + let (sender, receiver) = futures::channel::mpsc::channel(1); + context.sender.borrow_mut().push(sender); + spawn(with_events(receiver)); + }) +} - impl PartialEq for MockAppComponent { - fn eq(&self, _: &Self) -> bool { - true +struct EventContext { + sender: Rc>>>, +} + +impl Default for EventContext { + fn default() -> Self { + Self { + sender: Rc::new(RefCell::new(Vec::new())), } } +} - impl Clone for MockAppComponent { - fn clone(&self) -> Self { - Self { - hook: self.hook.clone(), - check: self.check.clone(), - } +impl Clone for EventContext { + fn clone(&self) -> Self { + Self { + sender: Rc::clone(&self.sender), } } +} - fn mock_app V, C: FnMut(V, MockProxy), V>( - props: MockAppComponent, - ) -> Element { - let value = props.hook.borrow_mut()(); - - props.check.borrow_mut()(value, MockProxy::new()); +struct TestDom { + vdom: RefCell, + context: EventContext, +} - rsx! { div {} } +impl Debug for TestDom { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(&self.render()) } +} - let mut vdom = VirtualDom::new_with_props( - mock_app, - MockAppComponent { - hook: Rc::new(RefCell::new(initialize)), - check: Rc::new(RefCell::new(check)), - }, - ); - - vdom.rebuild_in_place(); +impl TestDom { + fn new(component: impl FnMut() -> Element + 'static) -> Self { + #[derive(Props)] + struct MockAppComponent { + run: Rc Element>>>, + } - while vdom.wait_for_work().now_or_never().is_some() { - vdom.render_immediate(&mut NoOpMutations); - } + impl PartialEq for MockAppComponent { + fn eq(&self, _: &Self) -> bool { + true + } + } - vdom.in_runtime(|| { - ScopeId::ROOT.in_runtime(|| { - final_check(MockProxy::new()); - }) - }) -} + impl Clone for MockAppComponent { + fn clone(&self) -> Self { + Self { + run: self.run.clone(), + } + } + } -struct MockProxy { - rerender: Arc, - pub generation: usize, -} + fn mock_app(props: MockAppComponent) -> Element { + props.run.borrow_mut()() + } -impl MockProxy { - fn new() -> Self { - let generation = generation(); - let rerender = schedule_update(); + let context = EventContext::default(); + let mut vdom = VirtualDom::new_with_props( + mock_app, + MockAppComponent { + run: Rc::new(RefCell::new(Box::new(component))), + }, + ); + vdom.provide_root_context(context.clone()); + vdom.rebuild_in_place(); Self { - rerender, - generation, + vdom: RefCell::new(vdom), + context, } } - pub fn rerun(&mut self) { - (self.rerender)(); + async fn run_async(&mut self) { + self.vdom.borrow_mut().wait_for_work().await; + } + + fn render(&self) -> String { + self.vdom + .borrow_mut() + .render_immediate(&mut dioxus_core::NoOpMutations); + dioxus::ssr::render(&self.vdom.borrow()) + } + + fn send(&self, event: E) + where + E: Clone, + { + let mut senders = self.context.sender.borrow_mut(); + senders.retain_mut(|sender| sender.try_send(event.clone()).is_ok()); + } + + fn assert_eq(&self, other: Element) { + pretty_assertions::assert_eq!( + self.render(), + dioxus::ssr::render_element(other), + ); } }