update wasm-bindgen testing approaches

This commit is contained in:
Greg Johnston 2024-06-02 13:51:47 -04:00
parent 88ab9693db
commit c360f0ed0d
6 changed files with 79 additions and 53 deletions

View File

@ -18,3 +18,4 @@ console_error_panic_hook = "0.1.7"
wasm-bindgen = "0.2" wasm-bindgen = "0.2"
wasm-bindgen-test = "0.3.0" wasm-bindgen-test = "0.3.0"
web-sys = "0.3" web-sys = "0.3"
gloo-timers = { version = "0.3", features = ["futures"] }

View File

@ -1,19 +1,21 @@
use counter::*; use counter::*;
use leptos::*; use gloo_timers::future::TimeoutFuture;
use leptos::mount::mount_to;
use leptos::prelude::*;
use wasm_bindgen::JsCast; use wasm_bindgen::JsCast;
use wasm_bindgen_test::*; use wasm_bindgen_test::*;
wasm_bindgen_test_configure!(run_in_browser); wasm_bindgen_test_configure!(run_in_browser);
#[wasm_bindgen_test] #[wasm_bindgen_test]
fn clear() { async fn clear() {
let document = leptos::document(); let document = document();
let test_wrapper = document.create_element("section").unwrap(); let test_wrapper = document.create_element("section").unwrap();
let _ = document.body().unwrap().append_child(&test_wrapper); let _ = document.body().unwrap().append_child(&test_wrapper);
// start by rendering our counter and mounting it to the DOM // start by rendering our counter and mounting it to the DOM
// note that we start at the initial value of 10 // note that we start at the initial value of 10
mount_to( let _dispose = mount_to(
test_wrapper.clone().unchecked_into(), test_wrapper.clone().unchecked_into(),
|| view! { <SimpleCounter initial_value=10 step=1/> }, || view! { <SimpleCounter initial_value=10 step=1/> },
); );
@ -30,59 +32,63 @@ fn clear() {
// now let's click the `clear` button // now let's click the `clear` button
clear.click(); clear.click();
// the reactive system is built on top of the async system, so changes are not reflected
// synchronously in the DOM
// in order to detect the changes here, we'll just yield for a brief time after each change,
// allowing the effects that update the view to run
TimeoutFuture::new(10).await;
// now let's test the <div> against the expected value // now let's test the <div> against the expected value
// we can do this by testing its `outerHTML` // we can do this by testing its `outerHTML`
let runtime = create_runtime(); assert_eq!(div.outer_html(), {
assert_eq!( // it's as if we're creating it with a value of 0, right?
div.outer_html(), let (value, _set_value) = signal(0);
// here we spawn a mini reactive system, just to render the
// test case
{
// it's as if we're creating it with a value of 0, right?
let (value, _set_value) = create_signal(0);
// we can remove the event listeners because they're not rendered to HTML // we can remove the event listeners because they're not rendered to HTML
view! { view! {
<div> <div>
<button>"Clear"</button> <button>"Clear"</button>
<button>"-1"</button> <button>"-1"</button>
<span>"Value: " {value} "!"</span> <span>"Value: " {value} "!"</span>
<button>"+1"</button> <button>"+1"</button>
</div> </div>
}
// the view returned an HtmlElement<Div>, which is a smart pointer for
// a DOM element. So we can still just call .outer_html()
.outer_html()
} }
); // Leptos supports multiple backend renderers for HTML elements
// .into_view() here is just a convenient way of specifying "use the regular DOM renderer"
.into_view()
// views are lazy -- they describe a DOM tree but don't create it yet
// calling .build() will actually build the DOM elements
.build()
// .build() returned an ElementState, which is a smart pointer for
// a DOM element. So we can still just call .outer_html(), which access the outerHTML on
// the actual DOM element
.outer_html()
});
// There's actually an easier way to do this... // There's actually an easier way to do this...
// We can just test against a <SimpleCounter/> with the initial value 0 // We can just test against a <SimpleCounter/> with the initial value 0
assert_eq!(test_wrapper.inner_html(), { assert_eq!(test_wrapper.inner_html(), {
let comparison_wrapper = document.create_element("section").unwrap(); let comparison_wrapper = document.create_element("section").unwrap();
leptos::mount_to( let _dispose = mount_to(
comparison_wrapper.clone().unchecked_into(), comparison_wrapper.clone().unchecked_into(),
|| view! { <SimpleCounter initial_value=0 step=1/>}, || view! { <SimpleCounter initial_value=0 step=1/>},
); );
comparison_wrapper.inner_html() comparison_wrapper.inner_html()
}); });
runtime.dispose();
} }
#[wasm_bindgen_test] #[wasm_bindgen_test]
fn inc() { async fn inc() {
let document = leptos::document(); let document = document();
let test_wrapper = document.create_element("section").unwrap(); let test_wrapper = document.create_element("section").unwrap();
let _ = document.body().unwrap().append_child(&test_wrapper); let _ = document.body().unwrap().append_child(&test_wrapper);
mount_to( let _dispose = mount_to(
test_wrapper.clone().unchecked_into(), test_wrapper.clone().unchecked_into(),
|| view! { <SimpleCounter initial_value=0 step=1/> }, || view! { <SimpleCounter initial_value=0 step=1/> },
); );
// You can do testing with vanilla DOM operations // You can do testing with vanilla DOM operations
let _document = leptos::document();
let div = test_wrapper.query_selector("div").unwrap().unwrap(); let div = test_wrapper.query_selector("div").unwrap().unwrap();
let clear = div let clear = div
.first_child() .first_child()
@ -108,6 +114,8 @@ fn inc() {
inc.click(); inc.click();
inc.click(); inc.click();
TimeoutFuture::new(10).await;
assert_eq!(text.text_content(), Some("Value: 2!".to_string())); assert_eq!(text.text_content(), Some("Value: 2!".to_string()));
dec.click(); dec.click();
@ -115,19 +123,21 @@ fn inc() {
dec.click(); dec.click();
dec.click(); dec.click();
TimeoutFuture::new(10).await;
assert_eq!(text.text_content(), Some("Value: -2!".to_string())); assert_eq!(text.text_content(), Some("Value: -2!".to_string()));
clear.click(); clear.click();
assert_eq!(text.text_content(), Some("Value: 0!".to_string())); TimeoutFuture::new(10).await;
let runtime = create_runtime(); assert_eq!(text.text_content(), Some("Value: 0!".to_string()));
// Or you can test against a sample view! // Or you can test against a sample view!
assert_eq!( assert_eq!(
div.outer_html(), div.outer_html(),
{ {
let (value, _) = create_signal(0); let (value, _) = signal(0);
view! { view! {
<div> <div>
<button>"Clear"</button> <button>"Clear"</button>
@ -137,16 +147,20 @@ fn inc() {
</div> </div>
} }
} }
.into_view()
.build()
.outer_html() .outer_html()
); );
inc.click(); inc.click();
TimeoutFuture::new(10).await;
assert_eq!( assert_eq!(
div.outer_html(), div.outer_html(),
{ {
// because we've clicked, it's as if the signal is starting at 1 // because we've clicked, it's as if the signal is starting at 1
let (value, _) = create_signal(1); let (value, _) = signal(1);
view! { view! {
<div> <div>
<button>"Clear"</button> <button>"Clear"</button>
@ -156,8 +170,8 @@ fn inc() {
</div> </div>
} }
} }
.into_view()
.build()
.outer_html() .outer_html()
); );
runtime.dispose();
} }

View File

@ -10,8 +10,6 @@ lto = true
[dependencies] [dependencies]
leptos = { path = "../../leptos" } leptos = { path = "../../leptos" }
console_log = "1"
log = "0.4"
console_error_panic_hook = "0.1.7" console_error_panic_hook = "0.1.7"
[dev-dependencies] [dev-dependencies]
@ -19,6 +17,7 @@ wasm-bindgen = "0.2"
wasm-bindgen-test = "0.3.34" wasm-bindgen-test = "0.3.34"
pretty_assertions = "1.3.0" pretty_assertions = "1.3.0"
rstest = "0.17.0" rstest = "0.17.0"
gloo-timers = { version = "0.3", features = ["futures"] }
[dev-dependencies.web-sys] [dev-dependencies.web-sys]
features = ["HtmlElement", "XPathResult"] features = ["HtmlElement", "XPathResult"]

View File

@ -1,9 +1,7 @@
use counter_without_macros::counter; use counter_without_macros::counter;
use leptos::*;
/// Show the counter /// Show the counter
pub fn main() { pub fn main() {
_ = console_log::init_with_level(log::Level::Debug);
console_error_panic_hook::set_once(); console_error_panic_hook::set_once();
mount::mount_to_body(|| counter(0, 1)) leptos::mount::mount_to_body(|| counter(0, 1))
} }

View File

@ -1,5 +1,6 @@
use counter_without_macros::counter; use counter_without_macros::counter;
use leptos::*; use gloo_timers::future::TimeoutFuture;
use leptos::prelude::*;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use wasm_bindgen::JsCast; use wasm_bindgen::JsCast;
use wasm_bindgen_test::*; use wasm_bindgen_test::*;
@ -8,27 +9,32 @@ use web_sys::HtmlElement;
wasm_bindgen_test_configure!(run_in_browser); wasm_bindgen_test_configure!(run_in_browser);
#[wasm_bindgen_test] #[wasm_bindgen_test]
fn should_increment_counter() { async fn should_increment_counter() {
open_counter(); open_counter();
click_increment(); click_increment();
click_increment(); click_increment();
// reactive changes run asynchronously, so yield briefly before observing the DOM
TimeoutFuture::new(10).await;
assert_eq!(see_text(), Some("Value: 2!".to_string())); assert_eq!(see_text(), Some("Value: 2!".to_string()));
} }
#[wasm_bindgen_test] #[wasm_bindgen_test]
fn should_decrement_counter() { async fn should_decrement_counter() {
open_counter(); open_counter();
click_decrement(); click_decrement();
click_decrement(); click_decrement();
TimeoutFuture::new(10).await;
assert_eq!(see_text(), Some("Value: -2!".to_string())); assert_eq!(see_text(), Some("Value: -2!".to_string()));
} }
#[wasm_bindgen_test] #[wasm_bindgen_test]
fn should_clear_counter() { async fn should_clear_counter() {
open_counter(); open_counter();
click_increment(); click_increment();
@ -36,18 +42,18 @@ fn should_clear_counter() {
click_clear(); click_clear();
TimeoutFuture::new(10).await;
assert_eq!(see_text(), Some("Value: 0!".to_string())); assert_eq!(see_text(), Some("Value: 0!".to_string()));
} }
fn open_counter() { fn open_counter() {
remove_existing_counter(); remove_existing_counter();
mount_to_body(move || counter(0, 1)); leptos::mount::mount_to_body(move || counter(0, 1));
} }
fn remove_existing_counter() { fn remove_existing_counter() {
if let Some(counter) = if let Some(counter) = document().query_selector("body div").unwrap() {
leptos::document().query_selector("body div").unwrap()
{
counter.remove(); counter.remove();
} }
} }
@ -74,7 +80,7 @@ fn see_text() -> Option<String> {
fn find_by_text(text: &str) -> HtmlElement { fn find_by_text(text: &str) -> HtmlElement {
let xpath = format!("//*[text()='{}']", text); let xpath = format!("//*[text()='{}']", text);
let document = leptos::document(); let document = document();
document document
.evaluate(&xpath, &document) .evaluate(&xpath, &document)
.unwrap() .unwrap()

View File

@ -12,7 +12,7 @@ use const_str_slice_concat::{
const_concat, const_concat_with_prefix, str_from_buffer, const_concat, const_concat_with_prefix, str_from_buffer,
}; };
use next_tuple::NextTuple; use next_tuple::NextTuple;
use std::marker::PhantomData; use std::{marker::PhantomData, ops::Deref};
mod custom; mod custom;
mod elements; mod elements;
@ -413,6 +413,14 @@ pub struct ElementState<At, Ch, R: Renderer> {
rndr: PhantomData<R>, rndr: PhantomData<R>,
} }
impl<At, Ch, R: Renderer> Deref for ElementState<At, Ch, R> {
type Target = R::Element;
fn deref(&self) -> &Self::Target {
&self.el
}
}
impl<At, Ch, R> Mountable<R> for ElementState<At, Ch, R> impl<At, Ch, R> Mountable<R> for ElementState<At, Ch, R>
where where
R: Renderer, R: Renderer,