use pagetop::prelude::*; /// Minimal component to test `Markup` going through the real component rendering cycle /// (`ComponentRender`). The context parameter `"renderable"` is used to control whether the /// component is rendered (`true` by default). #[derive(AutoDefault, Clone)] struct TestMarkupComponent { markup: Markup, } #[async_trait] impl Component for TestMarkupComponent { fn new() -> Self { Self::default() } fn is_renderable(&self, cx: &Context) -> bool { cx.param_or::("renderable", true) } async fn prepare(&self, _cx: &mut Context) -> Result { Ok(self.markup.clone()) } } // **< Markup behavior >**************************************************************************** #[pagetop::test] async fn string_in_html_macro_escapes_html_entities() { let markup = html! { ("& \" ' ") }; assert_eq!(markup.into_string(), "<b>& " ' </b>"); } #[pagetop::test] async fn preescaped_in_html_macro_is_inserted_verbatim() { let markup = html! { (PreEscaped("bold")) }; assert_eq!(markup.into_string(), "bold"); } #[pagetop::test] async fn unicode_is_preserved_in_markup() { // Text with accents and emoji: only HTML signs are escaped. let esc = html! { ("Hello, tomorrow coffee ☕ & donuts!") }; assert_eq!(esc.into_string(), "Hello, tomorrow coffee ☕ & donuts!"); // PreEscaped must pass through untouched. let raw = html! { (PreEscaped("Title — section © 2025")) }; assert_eq!(raw.into_string(), "Title — section © 2025"); } #[pagetop::test] async fn markup_is_empty_semantics() { assert!(html! {}.is_empty()); assert!(html! { ("") }.is_empty()); assert!(!html! { ("x") }.is_empty()); assert!(html! { (PreEscaped(String::new())) }.is_empty()); assert!(!html! { (PreEscaped("a")) }.is_empty()); assert!(html! { (String::new()) }.is_empty()); assert!(!html! { span { "!" } }.is_empty()); // Spaces are NOT considered empty. assert!(!html! { (" ") }.is_empty()); assert!(!html! { (PreEscaped(" ")) }.is_empty()); } // **< Markup through the component cycle >********************************************************* #[pagetop::test] async fn non_renderable_component_produces_empty_markup() { let mut cx = Context::default().with_param("renderable", false); let mut comp = TestMarkupComponent { markup: html! { p { "Should never be rendered" } }, }; assert_eq!(comp.render(&mut cx).await.into_string(), ""); } #[pagetop::test] async fn markup_from_component_equals_markup_reinjected_in_html_macro() { let cases = [ html! {}, html! { ("x") }, html! { (PreEscaped("x")) }, html! { b { "x" } }, ]; for markup in cases { // Path 1: we render through the component cycle. let via_component = { let mut cx = Context::default(); let mut comp = TestMarkupComponent { markup: markup.clone(), }; comp.render(&mut cx).await.into_string() }; // Path 2: we reinject the Markup into `html!` directly. let via_macro = html! { (markup) }.into_string(); assert_eq!( via_component, via_macro, "The output of component render and (Markup) inside html! must match" ); } }