dioxus-core 0.8.0-alpha.3

Build fullstack web, desktop, and mobile apps with a single codebase.
Documentation
use dioxus::prelude::*;
use dioxus_core::{
    BorrowedAttributeValue, ElementId, Runtime, WriteMutations, generation, needs_update,
    queue_effect,
};
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};

#[test]
fn effect_queued_during_render_immediate_has_runtime() {
    fn app() -> Element {
        if generation() == 0 {
            needs_update();
        } else {
            queue_effect(|| {
                assert!(Runtime::try_current().is_some());
            });
        }

        rsx!({})
    }

    let mut dom = VirtualDom::new(app);

    dom.rebuild(&mut dioxus_core::NoOpMutations);
    dom.render_immediate(&mut dioxus_core::NoOpMutations);
}

static SHOW_EFFECT_CHILD: AtomicBool = AtomicBool::new(false);
static RENDERER_FLUSHED: AtomicBool = AtomicBool::new(false);
static EFFECT_RUNS: AtomicUsize = AtomicUsize::new(0);
static EFFECT_RAN_BEFORE_FLUSH: AtomicUsize = AtomicUsize::new(0);

#[test]
fn effects_run_after_renderer_flush_boundary() {
    SHOW_EFFECT_CHILD.store(false, Ordering::Relaxed);
    RENDERER_FLUSHED.store(false, Ordering::Relaxed);
    EFFECT_RUNS.store(0, Ordering::Relaxed);
    EFFECT_RAN_BEFORE_FLUSH.store(0, Ordering::Relaxed);

    let mut dom = VirtualDom::new(effect_flush_boundary_app);
    let mut renderer = BufferedRenderer;

    dom.rebuild(&mut renderer);
    renderer.flush();

    SHOW_EFFECT_CHILD.store(true, Ordering::Relaxed);
    RENDERER_FLUSHED.store(false, Ordering::Relaxed);
    dom.mark_dirty(ScopeId::APP);

    dom.render_immediate(&mut renderer);

    assert_eq!(
        EFFECT_RUNS.load(Ordering::Relaxed),
        0,
        "effect ran before the renderer flush boundary"
    );
    assert_eq!(
        EFFECT_RAN_BEFORE_FLUSH.load(Ordering::Relaxed),
        0,
        "effects must not run before the renderer has flushed queued mutations"
    );

    renderer.flush();
    dom.process_events();

    assert_eq!(
        EFFECT_RUNS.load(Ordering::Relaxed),
        1,
        "effect should run after the renderer flush boundary"
    );
    assert_eq!(
        EFFECT_RAN_BEFORE_FLUSH.load(Ordering::Relaxed),
        0,
        "effect observed unflushed renderer state"
    );
}

fn effect_flush_boundary_app() -> Element {
    if SHOW_EFFECT_CHILD.load(Ordering::Relaxed) {
        rsx! {
            EffectChild {}
        }
    } else {
        rsx! {
            div { "before" }
        }
    }
}

#[component]
fn EffectChild() -> Element {
    use_effect(|| {
        EFFECT_RUNS.fetch_add(1, Ordering::Relaxed);
        if !RENDERER_FLUSHED.load(Ordering::Relaxed) {
            EFFECT_RAN_BEFORE_FLUSH.fetch_add(1, Ordering::Relaxed);
        }
    });

    rsx! {
        div { id: "effect-child", "after" }
    }
}

#[derive(Default)]
struct BufferedRenderer;

impl BufferedRenderer {
    fn flush(&mut self) {
        RENDERER_FLUSHED.store(true, Ordering::Relaxed);
    }
}

impl WriteMutations for BufferedRenderer {
    fn push_id(&mut self, _id: ElementId) {}

    fn set_id(&mut self, _id: ElementId) {}

    fn child(&mut self, _index: usize) {}

    fn pop(&mut self) {}

    fn create_element(&mut self, _tag: &str, _ns: Option<&str>) {}

    fn create_text(&mut self, _value: &str) {}

    fn clone(&mut self) {}

    fn append_children(&mut self, _m: usize) {}

    fn replace_with(&mut self, _m: usize) {}

    fn insert_after(&mut self, _m: usize) {}

    fn insert_before(&mut self, _m: usize) {}

    fn set_attribute(
        &mut self,
        _name: &str,
        _ns: Option<&str>,
        _value: BorrowedAttributeValue<'_>,
    ) {
    }

    fn set_text(&mut self, _value: &str) {}

    fn add_event_listener(&mut self, _name: &str) {}

    fn remove_event_listener(&mut self, _name: &str) {}

    fn remove(&mut self) {}
}