♻️ Cambia en prepare_component() el tipo devuelto

Elimina `PrepareMarkup` como tipo de retorno de prepare_component() y de
`FnPrepareRender`, sustituyéndolo directamente por `Markup`. Se elimina
una capa innecesaria, ya que html! {} y html! { ... } cubren todos los
casos que ofrecía `PrepareMarkup`.
This commit is contained in:
Manuel Cillero 2026-03-17 20:04:26 +01:00
parent a5ee0fecb1
commit 3e1bc0fb0e
28 changed files with 241 additions and 335 deletions

107
tests/html_markup.rs Normal file
View file

@ -0,0 +1,107 @@
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::<bool>("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! { ("<b>& \" ' </b>") };
assert_eq!(markup.into_string(), "&lt;b&gt;&amp; &quot; ' &lt;/b&gt;");
}
#[pagetop::test]
async fn preescaped_in_html_macro_is_inserted_verbatim() {
let markup = html! { (PreEscaped("<b>bold</b><script>1<2</script>")) };
assert_eq!(markup.into_string(), "<b>bold</b><script>1<2</script>");
}
#[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 ☕ &amp; 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! { ("<b>x</b>") },
html! { (PreEscaped("<b>x</b>")) },
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"
);
}
}