♻️ 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:
parent
a5ee0fecb1
commit
3e1bc0fb0e
28 changed files with 241 additions and 335 deletions
107
tests/html_markup.rs
Normal file
107
tests/html_markup.rs
Normal 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(), "<b>& " ' </b>");
|
||||
}
|
||||
|
||||
#[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 ☕ & 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"
|
||||
);
|
||||
}
|
||||
}
|
||||
144
tests/html_pm.rs
144
tests/html_pm.rs
|
|
@ -1,144 +0,0 @@
|
|||
use pagetop::prelude::*;
|
||||
|
||||
/// Componente mínimo para probar `PrepareMarkup` pasando por el ciclo real
|
||||
/// de renderizado de componentes (`ComponentRender`).
|
||||
#[derive(AutoDefault)]
|
||||
struct TestPrepareComponent {
|
||||
pm: PrepareMarkup,
|
||||
}
|
||||
|
||||
impl Component for TestPrepareComponent {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
pm: PrepareMarkup::None,
|
||||
}
|
||||
}
|
||||
|
||||
fn prepare_component(&self, _cx: &mut Context) -> PrepareMarkup {
|
||||
self.pm.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl TestPrepareComponent {
|
||||
fn render_pm(pm: PrepareMarkup) -> String {
|
||||
let mut c = TestPrepareComponent { pm };
|
||||
c.render(&mut Context::default()).into_string()
|
||||
}
|
||||
}
|
||||
|
||||
#[pagetop::test]
|
||||
async fn prepare_markup_none_is_empty_string() {
|
||||
assert_eq!(PrepareMarkup::None.into_string(), "");
|
||||
}
|
||||
|
||||
#[pagetop::test]
|
||||
async fn prepare_markup_escaped_escapes_html_and_ampersands() {
|
||||
let pm = PrepareMarkup::Escaped("<b>& \" ' </b>".to_string());
|
||||
assert_eq!(pm.into_string(), "<b>& " ' </b>");
|
||||
}
|
||||
|
||||
#[pagetop::test]
|
||||
async fn prepare_markup_raw_is_inserted_verbatim() {
|
||||
let pm = PrepareMarkup::Raw("<b>bold</b><script>1<2</script>".to_string());
|
||||
assert_eq!(pm.into_string(), "<b>bold</b><script>1<2</script>");
|
||||
}
|
||||
|
||||
#[pagetop::test]
|
||||
async fn prepare_markup_with_keeps_structure() {
|
||||
let pm = PrepareMarkup::With(html! {
|
||||
h2 { "Sample title" }
|
||||
p { "This is a paragraph." }
|
||||
});
|
||||
assert_eq!(
|
||||
pm.into_string(),
|
||||
"<h2>Sample title</h2><p>This is a paragraph.</p>"
|
||||
);
|
||||
}
|
||||
|
||||
#[pagetop::test]
|
||||
async fn prepare_markup_unicode_is_preserved() {
|
||||
// Texto con acentos y emojis debe conservarse (salvo el escape HTML de signos).
|
||||
let esc = PrepareMarkup::Escaped("Hello, tomorrow coffee ☕ & donuts!".into());
|
||||
assert_eq!(esc.into_string(), "Hello, tomorrow coffee ☕ & donuts!");
|
||||
|
||||
// Raw debe pasar íntegro.
|
||||
let raw = PrepareMarkup::Raw("Title — section © 2025".into());
|
||||
assert_eq!(raw.into_string(), "Title — section © 2025");
|
||||
}
|
||||
|
||||
#[pagetop::test]
|
||||
async fn prepare_markup_is_empty_semantics() {
|
||||
assert!(PrepareMarkup::None.is_empty());
|
||||
|
||||
assert!(PrepareMarkup::Escaped(String::new()).is_empty());
|
||||
assert!(PrepareMarkup::Escaped("".to_string()).is_empty());
|
||||
assert!(!PrepareMarkup::Escaped("x".to_string()).is_empty());
|
||||
|
||||
assert!(PrepareMarkup::Raw(String::new()).is_empty());
|
||||
assert!(PrepareMarkup::Raw("".to_string()).is_empty());
|
||||
assert!(!PrepareMarkup::Raw("a".into()).is_empty());
|
||||
|
||||
assert!(PrepareMarkup::With(html! {}).is_empty());
|
||||
assert!(!PrepareMarkup::With(html! { span { "!" } }).is_empty());
|
||||
|
||||
// Ojo: espacios NO deberían considerarse vacíos (comportamiento actual).
|
||||
assert!(!PrepareMarkup::Escaped(" ".into()).is_empty());
|
||||
assert!(!PrepareMarkup::Raw(" ".into()).is_empty());
|
||||
}
|
||||
|
||||
#[pagetop::test]
|
||||
async fn prepare_markup_does_not_double_escape_when_markup_is_reinjected_in_html_macro() {
|
||||
let mut cx = Context::default();
|
||||
|
||||
// Escaped: dentro de `html!` no debe volver a escaparse.
|
||||
let mut comp = TestPrepareComponent {
|
||||
pm: PrepareMarkup::Escaped("<i>x</i>".into()),
|
||||
};
|
||||
let markup = comp.render(&mut cx); // Markup
|
||||
let wrapped_escaped = html! { div { (markup) } }.into_string();
|
||||
assert_eq!(wrapped_escaped, "<div><i>x</i></div>");
|
||||
|
||||
// Raw: tampoco debe escaparse al integrarlo.
|
||||
let mut comp = TestPrepareComponent {
|
||||
pm: PrepareMarkup::Raw("<i>x</i>".into()),
|
||||
};
|
||||
let markup = comp.render(&mut cx);
|
||||
let wrapped_raw = html! { div { (markup) } }.into_string();
|
||||
assert_eq!(wrapped_raw, "<div><i>x</i></div>");
|
||||
|
||||
// With: debe incrustar el Markup tal cual.
|
||||
let mut comp = TestPrepareComponent {
|
||||
pm: PrepareMarkup::With(html! { span.title { "ok" } }),
|
||||
};
|
||||
let markup = comp.render(&mut cx);
|
||||
let wrapped_with = html! { div { (markup) } }.into_string();
|
||||
assert_eq!(wrapped_with, "<div><span class=\"title\">ok</span></div>");
|
||||
}
|
||||
|
||||
#[pagetop::test]
|
||||
async fn prepare_markup_equivalence_between_component_render_and_markup_reinjected_in_html_macro() {
|
||||
let cases = [
|
||||
PrepareMarkup::None,
|
||||
PrepareMarkup::Escaped("<b>x</b>".into()),
|
||||
PrepareMarkup::Raw("<b>x</b>".into()),
|
||||
PrepareMarkup::With(html! { b { "x" } }),
|
||||
];
|
||||
|
||||
for pm in cases {
|
||||
// Vía 1: renderizamos y obtenemos directamente el String.
|
||||
let via_component = TestPrepareComponent::render_pm(pm.clone());
|
||||
|
||||
// Vía 2: renderizamos, reinyectamos el Markup en `html!` y volvemos a obtener String.
|
||||
let via_macro = {
|
||||
let mut cx = Context::default();
|
||||
let mut comp = TestPrepareComponent { pm };
|
||||
let markup = comp.render(&mut cx);
|
||||
html! { (markup) }.into_string()
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
via_component, via_macro,
|
||||
"The output of component render and (Markup) inside html! must match"
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue