on master help sign in
dodrio/tests/web/js_api.rs
153 lines 4.6 kB history raw
1use super::{assert_rendered, create_element, RenderFn};
2use dodrio::{builder::*, Node, Render, RenderContext, Vdom};
3use dodrio_js_api::JsRender;
4use futures::channel::oneshot;
5use js_sys::Object;
6use std::rc::Rc;
7use wasm_bindgen::prelude::*;
8use wasm_bindgen::JsCast;
9use wasm_bindgen_test::*;
11pub struct WrapJs {
12 inner: JsRender,
15impl WrapJs {
16 pub fn new() -> (WrapJs, oneshot::Receiver<()>, Closure<dyn FnMut()>) {
17 let (sender, receiver) = oneshot::channel();
19 let on_after_render = Closure::wrap(Box::new({
20 let mut sender = Some(sender);
21 move || {
22 sender
23 .take()
24 .expect_throw("on_after_render should only be called once")
25 .send(())
26 .expect_throw("receiver should not have been dropped");
27 }
28 }) as Box<dyn FnMut()>);
30 let component = WrapJs {
31 inner: JsRender::new(JsComponent::new(&on_after_render)),
32 };
34 (component, receiver, on_after_render)
35 }
38impl<'a> Render<'a> for WrapJs {
39 fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
40 div(&cx)
41 .attr("class", "wrap-js")
42 .children([self.inner.render(cx)])
43 .finish()
44 }
47#[wasm_bindgen]
48extern "C" {
49 #[wasm_bindgen(extends = Object)]
50 #[derive(Clone, Debug)]
51 type JsComponent;
53 #[wasm_bindgen(constructor)]
54 fn new(on_after_render: &Closure<dyn FnMut()>) -> JsComponent;
57fn eval_js_rendering_component() {
58 use std::sync::Once;
59 static EVAL: Once = Once::new();
60 EVAL.call_once(|| {
61 super::init_logging();
62 js_sys::eval(
63 r#"
64 window.JsComponent = class JsComponent {
65 constructor(onAfterRender) {
66 this.count = 0;
67 this.onAfterRender = onAfterRender;
68 }
70 render() {
71 return {
72 tagName: "span",
73 attributes: [{ name: "class", value: "js-component" }],
74 listeners: [{ on: "click", callback: this.onClick.bind(this) }],
75 children: [
76 "Here is some plain text",
77 {
78 tagName: "b",
79 children: ["...and here is some bold text"]
80 },
81 String(this.count),
82 ]
83 };
84 }
86 async onClick(vdom, event) {
87 this.count++;
88 await vdom.render();
89 this.onAfterRender();
90 }
91 };
92 "#,
93 )
94 .expect_throw("should eval JS component OK");
95 });
98#[wasm_bindgen_test]
99async fn can_use_js_rendering_components() {
100 eval_js_rendering_component();
102 let container = create_element("div");
103 let (component, receiver, _closure) = WrapJs::new();
104 let _vdom = Rc::new(Vdom::new(&container, component));
106 assert_rendered(
107 &container,
108 &RenderFn(|cx| {
109 div(&cx)
110 .attr("class", "wrap-js")
111 .children([span(&cx)
112 .attr("class", "js-component")
113 .children([
114 text("Here is some plain text"),
115 b(&cx)
116 .children([text("...and here is some bold text")])
117 .finish(),
118 text("0"),
119 ])
120 .finish()])
121 .finish()
122 }),
123 );
125 container
126 .query_selector(".js-component")
127 .expect_throw("should querySelector OK")
128 .expect_throw("should find `.js-component` element OK")
129 .dyn_into::<web_sys::HtmlElement>()
130 .expect_throw("should be an `HTMLElement` object")
131 .click();
133 receiver.await.unwrap();
135 assert_rendered(
136 &container,
137 &RenderFn(|cx| {
138 div(&cx)
139 .attr("class", "wrap-js")
140 .children([span(&cx)
141 .attr("class", "js-component")
142 .children([
143 text("Here is some plain text"),
144 b(&cx)
145 .children([text("...and here is some bold text")])
146 .finish(),
147 text("1"),
148 ])
149 .finish()])
150 .finish()
151 }),
152 );