@@ -0,0 +1,187 @@
1+//! Test suite for the Web and headless browsers.
2+
3+#![cfg(target_arch = "wasm32")]
4+
5+use bumpalo::Bump;
6+use dodrio::{Attribute, Node, Render, Vdom};
7+use log::*;
8+use wasm_bindgen::JsCast;
9+use wasm_bindgen_test::*;
10+
11+wasm_bindgen_test_configure!(run_in_browser);
12+
13+pub mod cached;
14+pub mod render;
15+
16+pub fn window() -> web_sys::Window {
17+ web_sys::window().expect("no global `window` exists")
18+}
19+
20+pub fn document() -> web_sys::Document {
21+ window()
22+ .document()
23+ .expect("should have a document on window")
24+}
25+
26+pub fn create_element(tag: &str) -> web_sys::Element {
27+ document()
28+ .create_element(tag)
29+ .expect("should create element OK")
30+}
31+
32+/// Ensure that logs go to the devtools console.
33+pub fn init_logging() {
34+ use std::sync::Once;
35+ static START: Once = Once::new();
36+ START.call_once(|| {
37+ console_log::init_with_level(Level::Trace).expect("could not initialize console_log");
38+ });
39+}
40+
41+/// Assert that the `container` contains the physical DOM tree that matches
42+/// `r`'s rendered virtual DOM.
43+pub fn assert_rendered<R: Render>(container: &web_sys::Element, r: &R) {
44+ init_logging();
45+
46+ let bump = &Bump::new();
47+ let node = r.render(bump);
48+ let child = container
49+ .first_child()
50+ .expect("container does not have anything rendered into it?");
51+ check_node(&child, &node);
52+
53+ fn stringify_actual_node(n: &web_sys::Node) -> String {
54+ if let Some(el) = n.dyn_ref::<web_sys::Element>() {
55+ el.outer_html()
56+ } else {
57+ format!("#text({:?})", n.text_content())
58+ }
59+ }
60+
61+ fn check_node(actual: &web_sys::Node, expected: &Node) {
62+ debug!("check_render:");
63+ debug!(" actual = {}", stringify_actual_node(&actual));
64+ debug!(" expected = {:#?}", expected);
65+ match expected {
66+ Node::Text(text_node) => {
67+ assert_eq!(
68+ actual.node_name().to_uppercase(),
69+ "#TEXT",
70+ "actual.node_name() == #TEXT"
71+ );
72+ assert_eq!(
73+ actual.text_content().unwrap_or_default(),
74+ text_node.text(),
75+ "actual.text_content() == expected.text()"
76+ );
77+ }
78+ Node::Element(elem) => {
79+ assert_eq!(
80+ actual.node_name().to_uppercase(),
81+ elem.tag_name().to_uppercase(),
82+ "actual.node_name() == expected.tag_name()"
83+ );
84+ let actual = actual
85+ .dyn_ref::<web_sys::Element>()
86+ .expect("`actual` should be an `Element`");
87+ check_attributes(actual.attributes(), elem.attributes());
88+ check_children(actual.child_nodes(), elem.children());
89+ }
90+ }
91+ }
92+
93+ fn check_attributes(actual: web_sys::NamedNodeMap, expected: &[Attribute]) {
94+ assert_eq!(
95+ actual.length(),
96+ expected.len() as u32,
97+ "actual's number of attributes == expected's number of attributes"
98+ );
99+ for attr in expected {
100+ let actual_attr = actual
101+ .get_named_item(attr.name)
102+ .expect(&format!("should have attribute \"{}\"", attr.name));
103+ assert_eq!(
104+ actual_attr.value(),
105+ attr.value,
106+ "actual attr value == expected attr value for attr \"{}\"",
107+ attr.name
108+ );
109+ }
110+ }
111+
112+ fn check_children(actual: web_sys::NodeList, expected: &[Node]) {
113+ assert_eq!(
114+ actual.length(),
115+ expected.len() as u32,
116+ "actual children length == expected children length"
117+ );
118+ for (i, child) in expected.iter().enumerate() {
119+ let actual_child = actual.item(i as u32).unwrap();
120+ check_node(&actual_child, child);
121+ }
122+ }
123+}
124+
125+/// Use the function `F` to render.
126+pub struct RenderFn<F>(F)
127+where
128+ F: for<'bump> Fn(&'bump Bump) -> Node<'bump>;
129+
130+impl<F> Render for RenderFn<F>
131+where
132+ F: for<'bump> Fn(&'bump Bump) -> Node<'bump>,
133+{
134+ fn render<'a, 'bump>(&'a self, bump: &'bump Bump) -> Node<'bump>
135+ where
136+ 'a: 'bump,
137+ {
138+ (self.0)(bump)
139+ }
140+}
141+
142+/// Assert that if we start by rendering the `before` virtual DOM tree into a
143+/// physical DOM tree, and then diff it with the `after` virtual DOM tree, then
144+/// the physical DOM tree correctly matches `after`.
145+pub fn assert_before_after<R, S>(before: R, after: S)
146+where
147+ R: Render,
148+ S: Render,
149+{
150+ let container = create_element("div");
151+
152+ debug!("====== Rendering the *before* DOM into the physical DOM ======");
153+ let mut vdom = Vdom::new(&container, &before);
154+ debug!("====== Checking the *before* DOM against the physical DOM ======");
155+ assert_rendered(&container, &before);
156+
157+ debug!("====== Rendering the *after* DOM into the physical DOM ======");
158+ vdom.render(&after);
159+ debug!("====== Checking the *after* DOM against the physical DOM ======");
160+ assert_rendered(&container, &after);
161+}
162+
163+/// A helper macro for decalaring a bunch of `assert_before_after` tests.
164+#[macro_export]
165+macro_rules! before_after {
166+ ( $(
167+ $name:ident {
168+ before($before_bump:ident) {
169+ $( $before:tt )*
170+ }
171+ after($after_bump:ident) {
172+ $( $after:tt )*
173+ }
174+ }
175+ )* ) => {
176+ $(
177+ #[wasm_bindgen_test]
178+ fn $name() {
179+ use crate::{assert_before_after, RenderFn};
180+ assert_before_after(
181+ RenderFn(|$before_bump| { $( $before )* }),
182+ RenderFn(|$after_bump| { $( $after )* })
183+ );
184+ }
185+ )*
186+ }
187+}