dodrio 0.2.0

A fast, bump-allocated virtual DOM library.
Documentation
use super::{assert_rendered, create_element, RenderFn};
use dodrio::{builder::*, Node, Render, RenderContext, Vdom};
use dodrio_js_api::JsRender;
use futures::channel::oneshot;
use js_sys::Object;
use std::rc::Rc;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use wasm_bindgen_test::*;

pub struct WrapJs {
    inner: JsRender,
}

impl WrapJs {
    pub fn new() -> (WrapJs, oneshot::Receiver<()>, Closure<dyn FnMut()>) {
        let (sender, receiver) = oneshot::channel();

        let on_after_render = Closure::wrap(Box::new({
            let mut sender = Some(sender);
            move || {
                sender
                    .take()
                    .expect_throw("on_after_render should only be called once")
                    .send(())
                    .expect_throw("receiver should not have been dropped");
            }
        }) as Box<dyn FnMut()>);

        let component = WrapJs {
            inner: JsRender::new(JsComponent::new(&on_after_render)),
        };

        (component, receiver, on_after_render)
    }
}

impl<'a> Render<'a> for WrapJs {
    fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
        div(&cx)
            .attr("class", "wrap-js")
            .children([self.inner.render(cx)])
            .finish()
    }
}

#[wasm_bindgen]
extern "C" {
    #[wasm_bindgen(extends = Object)]
    #[derive(Clone, Debug)]
    type JsComponent;

    #[wasm_bindgen(constructor)]
    fn new(on_after_render: &Closure<dyn FnMut()>) -> JsComponent;
}

fn eval_js_rendering_component() {
    use std::sync::Once;
    static EVAL: Once = Once::new();
    EVAL.call_once(|| {
        super::init_logging();
        js_sys::eval(
            r#"
            window.JsComponent = class JsComponent {
                constructor(onAfterRender) {
                    this.count = 0;
                    this.onAfterRender = onAfterRender;
                }

                render() {
                    return {
                        tagName: "span",
                        attributes: [{ name: "class", value: "js-component" }],
                        listeners: [{ on: "click", callback: this.onClick.bind(this) }],
                        children: [
                            "Here is some plain text",
                            {
                                tagName: "b",
                                children: ["...and here is some bold text"]
                            },
                            String(this.count),
                        ]
                    };
                }

                async onClick(vdom, event) {
                    this.count++;
                    await vdom.render();
                    this.onAfterRender();
                }
            };
            "#,
        )
        .expect_throw("should eval JS component OK");
    });
}

#[wasm_bindgen_test]
async fn can_use_js_rendering_components() {
    eval_js_rendering_component();

    let container = create_element("div");
    let (component, receiver, _closure) = WrapJs::new();
    let _vdom = Rc::new(Vdom::new(&container, component));

    assert_rendered(
        &container,
        &RenderFn(|cx| {
            div(&cx)
                .attr("class", "wrap-js")
                .children([span(&cx)
                    .attr("class", "js-component")
                    .children([
                        text("Here is some plain text"),
                        b(&cx)
                            .children([text("...and here is some bold text")])
                            .finish(),
                        text("0"),
                    ])
                    .finish()])
                .finish()
        }),
    );

    container
        .query_selector(".js-component")
        .expect_throw("should querySelector OK")
        .expect_throw("should find `.js-component` element OK")
        .dyn_into::<web_sys::HtmlElement>()
        .expect_throw("should be an `HTMLElement` object")
        .click();

    receiver.await.unwrap();

    assert_rendered(
        &container,
        &RenderFn(|cx| {
            div(&cx)
                .attr("class", "wrap-js")
                .children([span(&cx)
                    .attr("class", "js-component")
                    .children([
                        text("Here is some plain text"),
                        b(&cx)
                            .children([text("...and here is some bold text")])
                            .finish(),
                        text("1"),
                    ])
                    .finish()])
                .finish()
        }),
    );
}