use workers_rsx::html::HTML5Doctype;
use workers_rsx::{component, css, html, raw, rsx, Render};
#[test]
fn simple_element_no_children() {
let result = html! { <div /> };
assert_eq!(result, "<div></div>");
}
#[test]
fn simple_element_with_text_child() {
let result = html! { <p>"Hello"</p> };
assert_eq!(result, "<p>Hello</p>");
}
#[test]
fn simple_element_with_expression_child() {
let name = "world";
let result = html! { <span>{name}</span> };
assert_eq!(result, "<span>world</span>");
}
#[test]
fn nested_elements() {
let result = html! {
<div>
<p>"inner"</p>
</div>
};
assert_eq!(result, "<div><p>inner</p></div>");
}
#[test]
fn deeply_nested_elements() {
let result = html! {
<div>
<section>
<article>
<p>"deep"</p>
</article>
</section>
</div>
};
assert_eq!(result, "<div><section><article><p>deep</p></article></section></div>");
}
#[test]
fn multiple_children() {
let result = html! {
<div>
<p>"first"</p>
<p>"second"</p>
</div>
};
assert_eq!(result, "<div><p>first</p><p>second</p></div>");
}
#[test]
fn three_children() {
let result = html! {
<div>
<span>"a"</span>
<span>"b"</span>
<span>"c"</span>
</div>
};
assert_eq!(result, "<div><span>a</span><span>b</span><span>c</span></div>");
}
#[test]
fn single_attribute() {
let result = html! { <div class="container">"x"</div> };
assert!(result.contains("class=\"container\""));
assert!(result.contains("x</div>"));
}
#[test]
fn multiple_attributes() {
let result = html! { <a href="/home" class="link">"Home"</a> };
assert!(result.contains("href=\"/home\""));
assert!(result.contains("class=\"link\""));
assert!(result.contains(">Home</a>"));
}
#[test]
fn attribute_with_expression() {
let cls = "active";
let result = html! { <div class={cls}>"x"</div> };
assert!(result.contains("class=\"active\""));
}
#[test]
fn dash_delimited_attribute() {
let result = html! { <div data-id="123">"x"</div> };
assert!(result.contains("data-id=\"123\""));
}
#[test]
fn attribute_value_html_escaped() {
let result = html! { <div title={"a\"b&c"}>"x"</div> };
assert!(result.contains("a"b&c"));
}
#[test]
fn attribute_string_without_braces() {
let result = html! { <div class="foo">"x"</div> };
assert!(result.contains("class=\"foo\""));
}
#[test]
fn attribute_string_and_braces_equivalent() {
let a = html! { <div class="test">"x"</div> };
let b = html! { <div class={"test"}>"x"</div> };
assert_eq!(a, b);
}
#[test]
fn mixed_attribute_styles() {
let dynamic = "val";
let result = html! { <div class="static" id={dynamic}>"x"</div> };
assert!(result.contains("class=\"static\""));
assert!(result.contains("id=\"val\""));
}
#[test]
fn boolean_attribute() {
let result = html! { <input disabled /> };
assert!(result.contains(" disabled"));
assert!(!result.contains("disabled=\""));
}
#[test]
fn boolean_attribute_with_other_attrs() {
let result = html! { <input type={"text"} disabled /> };
assert!(result.contains("type=\"text\""));
assert!(result.contains(" disabled"));
}
#[test]
fn multiple_boolean_attributes() {
let result = html! { <input disabled readonly /> };
assert!(result.contains(" disabled"));
assert!(result.contains(" readonly"));
}
#[test]
fn void_element_br() {
let result = html! { <br /> };
assert_eq!(result, "<br>");
}
#[test]
fn void_element_hr() {
let result = html! { <hr /> };
assert_eq!(result, "<hr>");
}
#[test]
fn void_element_img() {
let result = html! { <img src={"photo.jpg"} alt={"A photo"} /> };
assert!(result.starts_with("<img"));
assert!(result.contains("src=\"photo.jpg\""));
assert!(result.contains("alt=\"A photo\""));
assert!(result.ends_with(">"));
assert!(!result.ends_with("/>"));
}
#[test]
fn void_element_input() {
let result = html! { <input type={"text"} /> };
assert!(result.starts_with("<input"));
assert!(result.ends_with(">"));
assert!(!result.ends_with("/>"));
}
#[test]
fn void_element_meta() {
let result = html! { <meta charset={"utf-8"} /> };
assert!(result.starts_with("<meta"));
assert!(result.ends_with(">"));
assert!(!result.ends_with("/>"));
}
#[test]
fn void_element_link() {
let result = html! { <link rel={"stylesheet"} href={"style.css"} /> };
assert!(result.starts_with("<link"));
assert!(result.ends_with(">"));
}
#[test]
fn void_element_with_content_still_renders_content() {
let result = html! { <br>"text"</br> };
assert!(result.contains("<br>"));
assert!(result.contains("text"));
}
#[test]
fn non_void_self_closing() {
let result = html! { <div /> };
assert_eq!(result, "<div></div>");
}
#[test]
fn string_literal_child() {
let result = html! { <p>"Hello, world!"</p> };
assert_eq!(result, "<p>Hello, world!</p>");
}
#[test]
fn string_literal_html_escaped() {
let result = html! { <p>"<script>alert('xss')</script>"</p> };
assert_eq!(
result,
"<p><script>alert('xss')</script></p>"
);
}
#[test]
fn multiple_string_literals() {
let result = html! { <p>"Hello, " "world!"</p> };
assert_eq!(result, "<p>Hello, world!</p>");
}
#[test]
fn string_literal_with_expression() {
let name = "Alice";
let result = html! { <p>"Hello, " {name} "!"</p> };
assert_eq!(result, "<p>Hello, Alice!</p>");
}
#[test]
fn expression_block_string() {
let msg = "hello";
let result = html! { <span>{msg}</span> };
assert_eq!(result, "<span>hello</span>");
}
#[test]
fn expression_block_html_escaped() {
let msg = "<b>bold</b>";
let result = html! { <span>{msg}</span> };
assert_eq!(result, "<span><b>bold</b></span>");
}
#[test]
fn expression_block_number() {
let n = 42;
let result = html! { <span>{n}</span> };
assert_eq!(result, "<span>42</span>");
}
#[test]
fn expression_block_float() {
let n = 3.14f64;
let result = html! { <span>{n}</span> };
assert_eq!(result, "<span>3.14</span>");
}
#[test]
fn expression_block_complex() {
let items = vec!["a", "b"];
let result = html! { <span>{items.len()}</span> };
assert_eq!(result, "<span>2</span>");
}
#[test]
fn text_directive_escapes_html() {
let val = "<b>bold</b>";
let result = html! { <p>{text val}</p> };
assert!(result.contains("<b>bold</b>"));
}
#[test]
fn text_directive_with_string() {
let val = "hello";
let result = html! { <p>{text val}</p> };
assert_eq!(result, "<p>hello</p>");
}
#[test]
fn text_directive_with_deref() {
let val = "hello";
let r = &val;
let result = html! { <p>{text *r}</p> };
assert_eq!(result, "<p>hello</p>");
}
#[test]
fn html_directive_no_escaping() {
let result = html! { <div>{html "<strong>bold</strong>"}</div> };
assert_eq!(result, "<div><strong>bold</strong></div>");
}
#[test]
fn html_directive_with_variable() {
let raw_html = "<em>italic</em>";
let result = html! { <div>{html raw_html}</div> };
assert_eq!(result, "<div><em>italic</em></div>");
}
#[test]
fn html_directive_entities() {
let result = html! { <div>{html "© 2026"}</div> };
assert_eq!(result, "<div>© 2026</div>");
}
#[test]
fn fragment_renders_children() {
let result = html! {
<>
<h1>"Title"</h1>
<p>"Body"</p>
</>
};
assert_eq!(result, "<h1>Title</h1><p>Body</p>");
}
#[test]
fn fragment_single_text() {
let result = html! {
<>
"just text"
</>
};
assert_eq!(result, "just text");
}
#[test]
fn fragment_single_child() {
let result = html! {
<>
<p>"only"</p>
</>
};
assert_eq!(result, "<p>only</p>");
}
#[test]
fn html5_doctype() {
let result = html! {
<>
<HTML5Doctype />
<html>
<head><title>"Test"</title></head>
<body>"Hello"</body>
</html>
</>
};
assert!(result.starts_with("<!DOCTYPE html>"));
assert!(result.contains("<html>"));
assert!(result.contains("<title>Test</title>"));
assert!(result.contains("Hello</body>"));
}
#[component]
fn SimpleComp() {
rsx! { <p>"simple"</p> }
}
#[component]
fn Greeting<'name>(name: &'name str) {
rsx! { <span>"Hello, " {name}</span> }
}
#[component]
fn Card<'title, C: Render>(title: &'title str, children: Option<C>) {
rsx! {
<div class={"card"}>
<h2>{title}</h2>
{children}
</div>
}
}
#[test]
fn component_no_props() {
let result = html! { <SimpleComp /> };
assert_eq!(result, "<p>simple</p>");
}
#[test]
fn component_with_props() {
let result = html! { <Greeting name={"World"} /> };
assert_eq!(result, "<span>Hello, World</span>");
}
#[test]
fn component_with_children() {
let result = html! {
<Card title={"My Card"}>
<p>"Card content"</p>
</Card>
};
assert!(result.contains("class=\"card\""));
assert!(result.contains("<h2>My Card</h2>"));
assert!(result.contains("<p>Card content</p>"));
}
#[test]
fn component_with_expression_prop() {
let who = "Rust";
let result = html! { <Greeting name={who} /> };
assert_eq!(result, "<span>Hello, Rust</span>");
}
#[test]
fn prop_shorthand() {
let name = "Alice";
let result = html! { <Greeting {name} /> };
assert_eq!(result, "<span>Hello, Alice</span>");
}
#[test]
fn prop_shorthand_with_other_props() {
let title = "Test";
let result = html! {
<Card {title}>
<p>"content"</p>
</Card>
};
assert!(result.contains("<h2>Test</h2>"));
}
#[test]
fn if_true() {
let show = true;
let result = html! {
<div>
if show {
<p>"visible"</p>
}
</div>
};
assert_eq!(result, "<div><p>visible</p></div>");
}
#[test]
fn if_false() {
let show = false;
let result = html! {
<div>
if show {
<p>"visible"</p>
}
</div>
};
assert_eq!(result, "<div></div>");
}
#[test]
fn if_else() {
let logged_in = false;
let result = html! {
<div>
if logged_in {
<span>"Welcome"</span>
} else {
<span>"Please log in"</span>
}
</div>
};
assert_eq!(result, "<div><span>Please log in</span></div>");
}
#[test]
fn if_else_true_branch() {
let logged_in = true;
let result = html! {
<div>
if logged_in {
<span>"Welcome"</span>
} else {
<span>"Please log in"</span>
}
</div>
};
assert_eq!(result, "<div><span>Welcome</span></div>");
}
#[test]
fn if_else_if_else() {
let role = "admin";
let result = html! {
<div>
if role == "admin" {
<span>"Admin"</span>
} else if role == "mod" {
<span>"Moderator"</span>
} else {
<span>"User"</span>
}
</div>
};
assert_eq!(result, "<div><span>Admin</span></div>");
}
#[test]
fn if_else_if_else_middle_branch() {
let role = "mod";
let result = html! {
<div>
if role == "mod" {
<span>"Moderator"</span>
} else if role == "admin" {
<span>"Admin"</span>
} else {
<span>"User"</span>
}
</div>
};
assert_eq!(result, "<div><span>Moderator</span></div>");
}
#[test]
fn if_else_if_else_last_branch() {
let role = "guest";
let result = html! {
<div>
if role == "admin" {
<span>"Admin"</span>
} else if role == "mod" {
<span>"Moderator"</span>
} else {
<span>"User"</span>
}
</div>
};
assert_eq!(result, "<div><span>User</span></div>");
}
#[test]
fn if_with_complex_condition() {
let x = 10;
let result = html! {
<div>
if x > 5 && x < 20 {
<span>"in range"</span>
}
</div>
};
assert_eq!(result, "<div><span>in range</span></div>");
}
#[test]
fn if_multiple_children_in_branch() {
let show = true;
let result = html! {
<div>
if show {
<p>"one"</p>
<p>"two"</p>
}
</div>
};
assert_eq!(result, "<div><p>one</p><p>two</p></div>");
}
#[test]
fn for_loop_basic() {
let items = vec!["a", "b", "c"];
let result = html! {
<ul>
for item in items.iter() {
<li>{text *item}</li>
}
</ul>
};
assert_eq!(result, "<ul><li>a</li><li>b</li><li>c</li></ul>");
}
#[test]
fn for_loop_empty() {
let items: Vec<&str> = vec![];
let result = html! {
<ul>
for item in items.iter() {
<li>{text *item}</li>
}
</ul>
};
assert_eq!(result, "<ul></ul>");
}
#[test]
fn for_loop_range() {
let result = html! {
<ul>
for i in 0..3 {
<li>{i}</li>
}
</ul>
};
assert_eq!(result, "<ul><li>0</li><li>1</li><li>2</li></ul>");
}
#[test]
fn for_loop_with_string_literal() {
let result = html! {
<ul>
for _i in 0..2 {
<li>"item"</li>
}
</ul>
};
assert_eq!(result, "<ul><li>item</li><li>item</li></ul>");
}
#[test]
fn for_loop_multiple_children() {
let result = html! {
<div>
for i in 0..2 {
<span>{i}</span>
<br />
}
</div>
};
assert_eq!(result, "<div><span>0</span><br><span>1</span><br></div>");
}
#[test]
fn for_loop_array() {
let result = html! {
<ul>
for name in ["Alice", "Bob"].iter() {
<li>{text *name}</li>
}
</ul>
};
assert_eq!(result, "<ul><li>Alice</li><li>Bob</li></ul>");
}
#[test]
fn match_first_arm() {
let status = "active";
let result = html! {
<div>
match status {
"active" => {
<span>"Active"</span>
},
_ => {
<span>"Other"</span>
},
}
</div>
};
assert_eq!(result, "<div><span>Active</span></div>");
}
#[test]
fn match_default_arm() {
let status = "unknown";
let result = html! {
<div>
match status {
"active" => {
<span>"Active"</span>
},
_ => {
<span>"Other"</span>
},
}
</div>
};
assert_eq!(result, "<div><span>Other</span></div>");
}
#[test]
fn match_multiple_arms() {
let color = "blue";
let result = html! {
<div>
match color {
"red" => {
<span>"Red"</span>
},
"blue" => {
<span>"Blue"</span>
},
"green" => {
<span>"Green"</span>
},
_ => {
<span>"Unknown"</span>
},
}
</div>
};
assert_eq!(result, "<div><span>Blue</span></div>");
}
#[test]
fn match_with_guard() {
let count = 5;
let result = html! {
<div>
match count {
n if n > 10 => {
<span>"many"</span>
},
n if n > 0 => {
<span>"some"</span>
},
_ => {
<span>"none"</span>
},
}
</div>
};
assert_eq!(result, "<div><span>some</span></div>");
}
#[test]
fn match_with_multiple_children_in_arm() {
let x = 1;
let result = html! {
<div>
match x {
1 => {
<p>"one"</p>
<p>"1"</p>
},
_ => {
<p>"other"</p>
},
}
</div>
};
assert_eq!(result, "<div><p>one</p><p>1</p></div>");
}
#[test]
fn let_binding_basic() {
let result = html! {
<div>
let greeting = "Hello";
<p>{greeting}</p>
</div>
};
assert_eq!(result, "<div><p>Hello</p></div>");
}
#[test]
fn let_binding_with_format() {
let name = "World";
let result = html! {
<div>
let msg = format!("Hello, {}!", name);
<p>{msg}</p>
</div>
};
assert_eq!(result, "<div><p>Hello, World!</p></div>");
}
#[test]
fn let_binding_with_match_expr() {
let status = "active";
let result = html! {
<div>
let label = match status {
"active" => "ON",
_ => "OFF",
};
<span>{label}</span>
</div>
};
assert_eq!(result, "<div><span>ON</span></div>");
}
#[test]
fn let_binding_used_in_attribute() {
let result = html! {
<div>
let cls = "highlight";
<span class={cls}>"text"</span>
</div>
};
assert!(result.contains("class=\"highlight\""));
assert!(result.contains("text</span>"));
}
#[test]
fn multiple_let_bindings() {
let result = html! {
<div>
let a = "Hello";
let b = "World";
<p>{a}</p>
<p>{b}</p>
</div>
};
assert_eq!(result, "<div><p>Hello</p><p>World</p></div>");
}
#[test]
fn let_binding_with_computation() {
let x = 5;
let result = html! {
<div>
let doubled = x * 2;
<span>{doubled}</span>
</div>
};
assert_eq!(result, "<div><span>10</span></div>");
}
#[test]
fn raw_macro() {
let content = raw!("<strong>bold</strong>");
let result = content.render();
assert_eq!(result, "<strong>bold</strong>");
}
#[test]
fn raw_owned_no_escaping() {
let content = workers_rsx::RawOwned("<em>italic</em>".to_string());
let result = content.render();
assert_eq!(result, "<em>italic</em>");
}
#[test]
fn escapes_ampersand() {
let result = html! { <p>"a & b"</p> };
assert_eq!(result, "<p>a & b</p>");
}
#[test]
fn escapes_less_than() {
let result = html! { <p>"a < b"</p> };
assert_eq!(result, "<p>a < b</p>");
}
#[test]
fn escapes_greater_than() {
let result = html! { <p>"a > b"</p> };
assert_eq!(result, "<p>a > b</p>");
}
#[test]
fn escapes_double_quote() {
let result = html! { <p>"he said \"hello\""</p> };
assert_eq!(result, "<p>he said "hello"</p>");
}
#[test]
fn escapes_single_quote() {
let result = html! { <p>"it's"</p> };
assert_eq!(result, "<p>it's</p>");
}
#[test]
fn expression_block_escapes() {
let dangerous = "<script>alert(1)</script>";
let result = html! { <div>{dangerous}</div> };
assert_eq!(
result,
"<div><script>alert(1)</script></div>"
);
}
#[test]
fn render_unit() {
let result = ().render();
assert_eq!(result, "");
}
#[test]
fn render_string() {
let result = String::from("hello <world>").render();
assert_eq!(result, "hello <world>");
}
#[test]
fn render_str() {
let result = "hello <world>".render();
assert_eq!(result, "hello <world>");
}
#[test]
fn render_option_some() {
let result = Some("hello").render();
assert_eq!(result, "hello");
}
#[test]
fn render_option_none() {
let result = Option::<&str>::None.render();
assert_eq!(result, "");
}
#[test]
fn render_vec() {
let result = vec!["a", "b", "c"].render();
assert_eq!(result, "abc");
}
#[test]
fn render_vec_empty() {
let result = Vec::<&str>::new().render();
assert_eq!(result, "");
}
#[test]
fn render_result_ok() {
let result = Result::<&str, &str>::Ok("ok").render();
assert_eq!(result, "ok");
}
#[test]
fn render_result_err() {
let result = Result::<&str, &str>::Err("err").render();
assert_eq!(result, "err");
}
#[test]
fn render_tuple_two() {
let result = ("hello ", "world").render();
assert_eq!(result, "hello world");
}
#[test]
fn render_tuple_three() {
let result = ("a", "b", "c").render();
assert_eq!(result, "abc");
}
#[test]
fn render_i32() {
let result = 42i32.render();
assert_eq!(result, "42");
}
#[test]
fn render_u64() {
let result = 100u64.render();
assert_eq!(result, "100");
}
#[test]
fn render_f64() {
let result = 3.14f64.render();
assert_eq!(result, "3.14");
}
#[test]
fn render_negative() {
let result = (-7i32).render();
assert_eq!(result, "-7");
}
#[test]
fn render_usize() {
let result = 0usize.render();
assert_eq!(result, "0");
}
#[component]
fn Badge<'label>(label: &'label str) {
rsx! { <span class={"badge"}>{label}</span> }
}
#[component]
fn Nav<C: Render>(children: Option<C>) {
rsx! { <nav>{children}</nav> }
}
#[test]
fn nested_components() {
let result = html! {
<Nav>
<Badge label={"Home"} />
<Badge label={"About"} />
</Nav>
};
assert_eq!(
result,
"<nav><span class=\"badge\">Home</span><span class=\"badge\">About</span></nav>"
);
}
#[test]
fn component_inside_conditional() {
let show = true;
let result = html! {
<div>
if show {
<Badge label={"Visible"} />
}
</div>
};
assert_eq!(result, "<div><span class=\"badge\">Visible</span></div>");
}
#[test]
fn component_inside_loop() {
let labels = vec!["A", "B"];
let result = html! {
<div>
for label in labels.iter() {
<Badge label={*label} />
}
</div>
};
assert_eq!(
result,
"<div><span class=\"badge\">A</span><span class=\"badge\">B</span></div>"
);
}
#[test]
fn component_inside_match() {
let kind = "badge";
let result = html! {
<div>
match kind {
"badge" => {
<Badge label={"Matched"} />
},
_ => {
<span>"Other"</span>
},
}
</div>
};
assert_eq!(
result,
"<div><span class=\"badge\">Matched</span></div>"
);
}
#[test]
fn full_html_page() {
let title = "Test Page";
let result = html! {
<>
<HTML5Doctype />
<html lang={"en"}>
<head>
<meta charset={"utf-8"} />
<title>{title}</title>
</head>
<body>
<h1>"Hello"</h1>
<p>"World"</p>
</body>
</html>
</>
};
assert!(result.starts_with("<!DOCTYPE html><html"));
assert!(result.contains("<title>Test Page</title>"));
assert!(result.contains("<h1>Hello</h1>"));
assert!(result.contains("<p>World</p>"));
assert!(result.ends_with("</html>"));
}
#[test]
fn empty_div() {
let result = html! { <div></div> };
assert_eq!(result, "<div></div>");
}
#[test]
fn element_with_only_expression() {
let x = 42;
let result = html! { <span>{x}</span> };
assert_eq!(result, "<span>42</span>");
}
#[test]
fn deeply_nested_conditionals() {
let a = true;
let b = true;
let result = html! {
<div>
if a {
<div>
if b {
<span>"deep"</span>
}
</div>
}
</div>
};
assert_eq!(result, "<div><div><span>deep</span></div></div>");
}
#[test]
fn conditional_with_string_literal() {
let show = true;
let result = html! {
<p>
if show {
"visible text"
} else {
"hidden text"
}
</p>
};
assert_eq!(result, "<p>visible text</p>");
}
#[test]
fn for_loop_with_index_in_attribute() {
let result = html! {
<div>
for i in 0..2 {
<div class={format!("item-{}", i)}>"x"</div>
}
</div>
};
assert!(result.contains("class=\"item-0\""));
assert!(result.contains("class=\"item-1\""));
}
#[test]
fn let_binding_used_in_child_component() {
let result = html! {
<div>
let lbl = "Dynamic";
<Badge label={lbl} />
</div>
};
assert_eq!(
result,
"<div><span class=\"badge\">Dynamic</span></div>"
);
}
#[test]
fn let_binding_used_in_conditional() {
let x = 10;
let result = html! {
<div>
let big = x > 5;
if big {
<span>"big"</span>
} else {
<span>"small"</span>
}
</div>
};
assert_eq!(result, "<div><span>big</span></div>");
}
#[test]
fn mixed_children_types() {
let name = "World";
let result = html! {
<div>
"Hello, "
{name}
"!"
<br />
<span>"done"</span>
</div>
};
assert_eq!(result, "<div>Hello, World!<br><span>done</span></div>");
}
#[test]
fn xss_in_text_child() {
let attack = "<img src=x onerror=alert(1)>";
let result = html! { <div>{attack}</div> };
assert!(!result.contains("<img"));
assert!(result.contains("<img"));
}
#[test]
fn xss_in_attribute() {
let attack = "\" onclick=\"alert(1)";
let result = html! { <div title={attack}>"x"</div> };
assert!(result.contains("""));
assert!(!result.contains("onclick=\"alert"));
}
#[test]
fn xss_in_string_literal() {
let result = html! { <div>"<script>bad</script>"</div> };
assert!(!result.contains("<script>"));
assert!(result.contains("<script>"));
}
#[test]
fn text_directive_prevents_xss() {
let attack = "<script>alert('xss')</script>";
let result = html! { <p>{text attack}</p> };
assert!(!result.contains("<script>"));
assert!(result.contains("<script>"));
}
#[test]
fn css_simple_rule() {
let styles = css! {
.container {
max-width: 600px;
margin: 0 auto;
}
};
assert!(styles.contains(".container"));
assert!(styles.contains("max-width:"));
assert!(styles.contains("600px"));
assert!(styles.contains("margin:"));
assert!(styles.contains("auto"));
}
#[test]
fn css_multiple_selectors() {
let styles = css! {
.title {
font-size: 2rem;
color: #333;
}
.subtitle {
font-size: 1rem;
}
};
assert!(styles.contains(".title"));
assert!(styles.contains("font-size:"));
assert!(styles.contains("2rem"));
assert!(styles.contains("#333"));
assert!(styles.contains(".subtitle"));
assert!(styles.contains("1rem"));
}
#[test]
fn css_id_selector() {
let styles = css! {
#main {
display: flex;
}
};
assert!(styles.contains("#main"));
assert!(styles.contains("display:"));
assert!(styles.contains("flex"));
}
#[test]
fn css_hyphenated_properties() {
let styles = css! {
.box {
background-color: red;
border-radius: 4px;
flex-direction: column;
}
};
assert!(styles.contains("background-color:"));
assert!(styles.contains("border-radius:"));
assert!(styles.contains("4px"));
assert!(styles.contains("flex-direction:"));
assert!(styles.contains("column"));
}
#[test]
fn css_pseudo_class() {
let styles = css! {
.btn:hover {
background: blue;
}
};
assert!(styles.contains(".btn:hover"));
assert!(styles.contains("background:"));
assert!(styles.contains("blue"));
}
#[test]
fn css_media_query() {
let styles = css! {
@media (max-width: 600px) {
.container {
padding: 1rem;
}
}
};
assert!(styles.contains("@media"));
assert!(styles.contains("max-width:"));
assert!(styles.contains("600px"));
assert!(styles.contains(".container"));
assert!(styles.contains("padding:"));
assert!(styles.contains("1rem"));
}
#[test]
fn css_used_in_style_tag() {
let styles = css! {
.title {
color: red;
}
};
let result = html! {
<style>{html styles}</style>
};
assert!(result.contains("<style>"));
assert!(result.contains(".title"));
assert!(result.contains("color:"));
assert!(result.contains("red"));
assert!(result.contains("</style>"));
}
#[test]
fn css_negative_value() {
let styles = css! {
.offset {
margin-top: -10px;
}
};
assert!(styles.contains("margin-top:"));
assert!(styles.contains("-10px"));
}
#[test]
fn css_zero_value() {
let styles = css! {
.reset {
margin: 0;
padding: 0;
}
};
assert!(styles.contains("margin:"));
assert!(styles.contains("padding:"));
}
#[test]
fn unquoted_text_simple() {
let result = html! { <p>Hello world</p> };
assert_eq!(result, "<p>Hello world</p>");
}
#[test]
fn unquoted_text_with_element_sibling() {
let result = html! { <div>Hello " " <span>world</span></div> };
assert_eq!(result, "<div>Hello <span>world</span></div>");
}
#[test]
fn unquoted_text_with_expression_sibling() {
let name = "Alice";
let result = html! { <p>Hello, " " {name}</p> };
assert_eq!(result, "<p>Hello, Alice</p>");
}
#[test]
fn unquoted_text_escapes_html() {
let result = html! { <p>Tom & Jerry</p> };
assert!(result.contains("&"));
}
#[test]
fn unquoted_text_multiple_words() {
let result = html! { <h1>Welcome to our website</h1> };
assert_eq!(result, "<h1>Welcome to our website</h1>");
}
#[test]
fn unquoted_text_with_punctuation() {
let result = html! { <p>Hello world!</p> };
assert_eq!(result, "<p>Hello world!</p>");
}
#[test]
fn unquoted_text_after_quoted() {
let result = html! { <div>{"Hello "} world</div> };
assert_eq!(result, "<div>Hello world</div>");
}
#[test]
fn tailwind_generates_style_tag() {
let result = html! { <div class="bg-blue-500 text-white">Hello</div> };
assert!(result.contains("<style>"), "should contain a <style> tag");
assert!(result.contains("</style>"), "should close the style tag");
assert!(result.contains("<div"), "should contain the div element");
}
#[test]
fn tailwind_no_classes_no_style_tag() {
let result = html! { <div>Hello</div> };
assert!(!result.contains("<style>"), "should not contain a <style> tag when no classes");
assert_eq!(result, "<div>Hello</div>");
}
#[test]
fn tailwind_extracts_from_nested_elements() {
let result = html! {
<div class="p-4">
<span class="text-lg">Hello</span>
</div>
};
assert!(result.contains("<style>"), "should contain a <style> tag for nested class attributes");
}
#[test]
fn tailwind_extracts_from_conditionals() {
let x = true;
let result = html! {
<div>
if x {
<p class="font-bold">Yes</p>
} else {
<p class="text-red-500">No</p>
}
</div>
};
assert!(result.contains("<style>"), "should extract classes from conditional branches");
}
#[test]
fn tailwind_injects_before_head_close() {
let result = html! {
<html>
<head><title>Test</title></head>
<body class="bg-white p-4">Hello</body>
</html>
};
let head_pos = result.find("</head>").unwrap();
let style_pos = result.find("<style>").unwrap();
assert!(style_pos < head_pos, "style tag should be injected before </head>");
assert!(result.contains("</style></head>"), "style tag should be right before </head>");
}