use pagetop::prelude::*; /// Componente mínimo para probar `Markup` pasando por el ciclo real de renderizado de componentes /// (`ComponentRender`). El parámetro de contexto `"renderable"` se usará para controlar si el /// componente se renderiza (`true` por defecto). #[derive(AutoDefault)] struct TestMarkupComponent { markup: Markup, } impl Component for TestMarkupComponent { fn new() -> Self { TestMarkupComponent::default() } fn is_renderable(&self, cx: &mut Context) -> bool { cx.param_or::("renderable", true) } fn prepare_component(&self, _cx: &mut Context) -> Markup { self.markup.clone() } } // **< Comportamiento de Markup >******************************************************************* #[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() { // Texto con acentos y emojis: sólo se escapan los signos HTML. let esc = html! { ("Hello, tomorrow coffee ☕ & donuts!") }; assert_eq!(esc.into_string(), "Hello, tomorrow coffee ☕ & donuts!"); // PreEscaped debe pasar íntegro. 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()); // Espacios NO se consideran vacíos. assert!(!html! { (" ") }.is_empty()); assert!(!html! { (PreEscaped(" ")) }.is_empty()); } // **< Markup a través del ciclo de componente >**************************************************** #[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).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 { // Vía 1: renderizamos a través del ciclo de componente. let via_component = { let mut cx = Context::default(); let mut comp = TestMarkupComponent { markup: markup.clone(), }; comp.render(&mut cx).into_string() }; // Vía 2: reinyectamos el Markup en `html!` directamente. let via_macro = html! { (markup) }.into_string(); assert_eq!( via_component, via_macro, "The output of component render and (Markup) inside html! must match" ); } }