Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
212 changes: 134 additions & 78 deletions packages/docs-router/src/doc_examples/hook_test.rs
Original file line number Diff line number Diff line change
@@ -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<V: 'static>(
initialize: impl FnMut() -> V + 'static,
check: impl FnMut(V, MockProxy) + 'static,
mut final_check: impl FnMut(MockProxy) + 'static,
fn use_events<E: 'static, F: Future<Output = ()> + 'static>(
with_events: impl FnOnce(Receiver<E>) -> F,
) {
#[derive(Props)]
struct MockAppComponent<I: 'static, C: 'static> {
hook: Rc<RefCell<I>>,
check: Rc<RefCell<C>>,
}
use_hook(move || {
let context = consume_context::<EventContext<E>>();
let (sender, receiver) = futures::channel::mpsc::channel(1);
context.sender.borrow_mut().push(sender);
spawn(with_events(receiver));
})
}

impl<I, C> PartialEq for MockAppComponent<I, C> {
fn eq(&self, _: &Self) -> bool {
true
struct EventContext<E> {
sender: Rc<RefCell<Vec<Sender<E>>>>,
}

impl<E> Default for EventContext<E> {
fn default() -> Self {
Self {
sender: Rc::new(RefCell::new(Vec::new())),
}
}
}

impl<I, C> Clone for MockAppComponent<I, C> {
fn clone(&self) -> Self {
Self {
hook: self.hook.clone(),
check: self.check.clone(),
}
impl<E> Clone for EventContext<E> {
fn clone(&self) -> Self {
Self {
sender: Rc::clone(&self.sender),
}
}
}

fn mock_app<I: FnMut() -> V, C: FnMut(V, MockProxy), V>(
props: MockAppComponent<I, C>,
) -> Element {
let value = props.hook.borrow_mut()();

props.check.borrow_mut()(value, MockProxy::new());
struct TestDom<E = ()> {
vdom: RefCell<VirtualDom>,
context: EventContext<E>,
}

rsx! { div {} }
impl<E: 'static> Debug for TestDom<E> {
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<E: 'static> TestDom<E> {
fn new(component: impl FnMut() -> Element + 'static) -> Self {
#[derive(Props)]
struct MockAppComponent {
run: Rc<RefCell<Box<dyn FnMut() -> 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<dyn Fn()>,
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),
);
}
}