#[doc(hidden)]
#[macro_export]
macro_rules! html_impl {
(($parent:expr) ) => { };
(($parent:expr) $tag:ident [$($attributes:tt)*] {$($children:tt)*} $($rest:tt)*) => {
#[allow(unused_mut)]
let mut list = Vec::new();
$crate::attributes!((list) $($attributes)*);
let tag = String::from(stringify!($tag));
#[allow(unused_mut)]
let mut element = $crate::Node::element(tag, list);
$crate::html_impl!((&mut element) $($children)*);
$parent.append_child(element);
$crate::html_impl!(($parent) $($rest)*);
};
(($parent:expr) $tag:ident [$($attributes:tt)*] $($rest:tt)*) => {
#[allow(unused_mut)]
let mut list = Vec::new();
$crate::attributes!((list) $($attributes)*);
let tag = String::from(stringify!($tag));
let element = $crate::Node::element(tag, list);
$parent.append_child(element);
$crate::html_impl!(($parent) $($rest)*);
};
(($parent:expr) $tag:ident {$($children:tt)*} $($rest:tt)*) => {
let tag = String::from(stringify!($tag));
#[allow(unused_mut)]
let mut element = $crate::Node::element(tag, vec![]);
$crate::html_impl!((&mut element) $($children)*);
$parent.append_child(element);
$crate::html_impl!(($parent) $($rest)*);
};
(($parent:expr) ($expression:expr) $($rest:tt)*) => {
$parent.append_child($crate::Node::from($expression));
$crate::html_impl!(($parent) $($rest)*);
};
(($parent:expr) $tag:ident $($rest:tt)*) => {
let tag = String::from(stringify!($tag));
let element = $crate::Node::element(tag, vec![]);
$parent.append_child(element);
$crate::html_impl!(($parent) $($rest)*);
}
}
#[macro_export]
macro_rules! html {
($($input:tt)*) => {{
let mut fragment = $crate::Node::fragment();
$crate::html_impl!((&mut fragment) $($input)*);
fragment
}};
}
#[cfg(test)]
mod tests {
use crate::{raw_text, text, Node};
#[test]
fn empty_element() {
assert_eq!(
html! {
div
div []
div {}
div[] {}
}
.to_string(),
"<div></div><div></div><div></div><div></div>"
);
assert_eq!(
html! {
custom_element {}
}
.to_string(),
"<custom-element></custom-element>"
);
}
#[test]
fn element_with_attributes() {
assert_eq!(
html! {
div [class = "container", "readonly", ""] {}
}
.to_string(),
"<div class=\"container\" readonly></div>"
);
assert_eq!(
html! {
div [data_one = "two"] {}
}
.to_string(),
"<div data-one=\"two\"></div>"
);
assert_eq!(
html! {
div [key = "a \"templating\" engine"] {}
}
.to_string(),
"<div key=\"a "templating" engine\"></div>"
);
assert_eq!(
html! {
div [key = "value"]
}
.to_string(),
"<div key=\"value\"></div>"
)
}
#[test]
fn element_with_attributes_and_children() {
assert_eq!(
html! {
div[class = "container"] {
p {}
span {}
}
}
.to_string(),
"<div class=\"container\"><p></p><span></span></div>"
)
}
#[test]
fn void_elements() {
assert_eq!(
html! {
img {
p {}
}
}
.to_string(),
"<img>"
);
assert_eq!(
html! {
IMG { p {} }
}
.to_string(),
"<img>"
);
}
#[test]
fn doctype_element_is_recognized() {
assert_eq!(
html! {
DOCtype {}
}
.to_string(),
"<!doctype>"
);
}
#[test]
fn escaping_strings() {
assert_eq!(
html! {
(text("foo"))
(text("<span>"))
(raw_text("<span>"))
}
.to_string(),
"foo<span><span>"
)
}
#[test]
fn interpolating_expressions() {
assert_eq!(
html! {
div {}
(text("hello"))
span {}
}
.to_string(),
"<div></div>hello<span></span>"
);
let node = html! {
button {
(text("submit"))
}
};
assert_eq!(
html! {
form {
(node)
}
}
.to_string(),
"<form><button>submit</button></form>"
);
let form = [
html! { input {} },
html! { button[type = "submit"] {} },
html! { select {} },
];
assert_eq!(
html! {
form {
(form)
}
}
.to_string(),
"<form><input><button type=\"submit\"></button><select></select></form>"
);
let no: Option<Node> = None;
let yes = Some(html! { (text("yes")) });
let success: Result<Node, ()> = Ok(html! { (text("success")) });
assert_eq!(html! { (no) (yes) (success) }.to_string(), "yessuccess");
}
#[test]
fn overriding_the_tagname() {
assert_eq!(
html! {
custom[data_tagname = "h1"] { }
}
.to_string(),
"<h1 data-tagname=\"h1\"></h1>"
);
}
}