🌐 (tests): Traduce comentarios de tests a inglés
This commit is contained in:
parent
af548b03c9
commit
55159f6d8f
10 changed files with 90 additions and 91 deletions
|
|
@ -1,9 +1,9 @@
|
||||||
use pagetop::prelude::*;
|
use pagetop::prelude::*;
|
||||||
|
|
||||||
// **< TestComp - componente mínimo para los tests >************************************************
|
// **< TestComp - minimal component for the tests >*************************************************
|
||||||
//
|
//
|
||||||
// Componente con id configurable y texto fijo de salida. El id permite probar las operaciones de
|
// Component with configurable id and fixed output text. The id allows testing the identifier-based
|
||||||
// `Children` basadas en identificador (`InsertAfterId`, `RemoveById`, etc.).
|
// `Children` operations (`InsertAfterId`, `RemoveById`, etc.).
|
||||||
|
|
||||||
#[derive(AutoDefault, Clone)]
|
#[derive(AutoDefault, Clone)]
|
||||||
struct TestComp {
|
struct TestComp {
|
||||||
|
|
@ -27,7 +27,7 @@ impl Component for TestComp {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TestComp {
|
impl TestComp {
|
||||||
/// Crea un componente con id y texto de salida fijos.
|
/// Creates a component with a fixed id and output text.
|
||||||
fn tagged(id: &str, text: &str) -> Self {
|
fn tagged(id: &str, text: &str) -> Self {
|
||||||
let mut c = Self::default();
|
let mut c = Self::default();
|
||||||
c.props.alter_prop(PropsOp::set_id(id.to_string()));
|
c.props.alter_prop(PropsOp::set_id(id.to_string()));
|
||||||
|
|
@ -35,7 +35,7 @@ impl TestComp {
|
||||||
c
|
c
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Crea un componente sin id, con texto de salida fijo.
|
/// Creates a component with no id, with fixed output text.
|
||||||
fn text(text: &str) -> Self {
|
fn text(text: &str) -> Self {
|
||||||
let mut c = Self::default();
|
let mut c = Self::default();
|
||||||
c.text = text.to_string();
|
c.text = text.to_string();
|
||||||
|
|
@ -43,7 +43,7 @@ impl TestComp {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// **< Child >***************************************************************************************
|
// **< Child >**************************************************************************************
|
||||||
|
|
||||||
#[pagetop::test]
|
#[pagetop::test]
|
||||||
async fn child_default_is_empty() {
|
async fn child_default_is_empty() {
|
||||||
|
|
@ -78,7 +78,7 @@ async fn child_from_component_is_equivalent_to_with() {
|
||||||
|
|
||||||
#[pagetop::test]
|
#[pagetop::test]
|
||||||
async fn child_clone_is_deep() {
|
async fn child_clone_is_deep() {
|
||||||
// Modificar el clon no debe afectar al original.
|
// Modifying the clone must not affect the original.
|
||||||
let original = Child::with(TestComp::text("original"));
|
let original = Child::with(TestComp::text("original"));
|
||||||
let clone = original.clone();
|
let clone = original.clone();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|
@ -114,11 +114,11 @@ async fn children_add_appends_in_order() {
|
||||||
async fn children_add_if_empty_only_adds_when_list_is_empty() {
|
async fn children_add_if_empty_only_adds_when_list_is_empty() {
|
||||||
let mut cx = Context::default();
|
let mut cx = Context::default();
|
||||||
|
|
||||||
// Se añade porque la lista está vacía.
|
// It gets added because the list is empty.
|
||||||
let c = Children::new().with_child(ChildOp::AddIfEmpty(TestComp::text("first").into()));
|
let c = Children::new().with_child(ChildOp::AddIfEmpty(TestComp::text("first").into()));
|
||||||
assert_eq!(c.len(), 1);
|
assert_eq!(c.len(), 1);
|
||||||
|
|
||||||
// No se añade porque ya hay un elemento.
|
// It does not get added because there is already an element.
|
||||||
let c = c.with_child(ChildOp::AddIfEmpty(TestComp::text("second").into()));
|
let c = c.with_child(ChildOp::AddIfEmpty(TestComp::text("second").into()));
|
||||||
assert_eq!(c.len(), 1);
|
assert_eq!(c.len(), 1);
|
||||||
assert_eq!(c.render(&mut cx).await.into_string(), "first");
|
assert_eq!(c.render(&mut cx).await.into_string(), "first");
|
||||||
|
|
@ -297,7 +297,7 @@ async fn embed_id_returns_component_id() {
|
||||||
#[pagetop::test]
|
#[pagetop::test]
|
||||||
async fn embed_get_is_some_when_component_present() {
|
async fn embed_get_is_some_when_component_present() {
|
||||||
let embed = Embed::with(TestComp::tagged("abc", "hello"));
|
let embed = Embed::with(TestComp::tagged("abc", "hello"));
|
||||||
// `get()` devuelve Some; la lectura del id verifica que accede al componente correctamente.
|
// `get()` returns Some; reading the id verifies that it accesses the component correctly.
|
||||||
assert!(embed.get().is_some());
|
assert!(embed.get().is_some());
|
||||||
assert_eq!(embed.id(), Some("abc".to_string()));
|
assert_eq!(embed.id(), Some("abc".to_string()));
|
||||||
}
|
}
|
||||||
|
|
@ -332,7 +332,7 @@ async fn embed_with_component_none_empties_embed() {
|
||||||
async fn embed_clone_is_deep() {
|
async fn embed_clone_is_deep() {
|
||||||
let original = Embed::with(TestComp::tagged("orig", "text"));
|
let original = Embed::with(TestComp::tagged("orig", "text"));
|
||||||
let mut clone = original.clone();
|
let mut clone = original.clone();
|
||||||
// Mutar el clon no debe afectar al original.
|
// Mutating the clone must not affect the original.
|
||||||
if let Some(comp) = clone.get_mut() {
|
if let Some(comp) = clone.get_mut() {
|
||||||
comp.props
|
comp.props
|
||||||
.alter_prop(PropsOp::set_id("clone-id".to_string()));
|
.alter_prop(PropsOp::set_id("clone-id".to_string()));
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
use pagetop::prelude::*;
|
use pagetop::prelude::*;
|
||||||
|
|
||||||
/// Inicializa PageTop (locale, extensiones...) una sola vez para toda la suite.
|
/// Initializes PageTop (locale, extensions...) once for the whole suite.
|
||||||
///
|
///
|
||||||
/// Los tests de este módulo renderizan componentes directamente con `Context::default()`, por lo
|
/// The tests in this module render components directly with `Context::default()`, so they only need
|
||||||
/// que sólo necesitan el subsistema de localización y las extensiones registradas, no un router.
|
/// the localization subsystem and the registered extensions, not a router.
|
||||||
async fn setup() {
|
async fn setup() {
|
||||||
Application::new().await;
|
Application::new().await;
|
||||||
}
|
}
|
||||||
|
|
@ -15,10 +15,10 @@ async fn poweredby_default_shows_only_pagetop_recognition() {
|
||||||
let mut p = PoweredBy::default();
|
let mut p = PoweredBy::default();
|
||||||
let html = p.render(&mut Context::default()).await.into_string();
|
let html = p.render(&mut Context::default()).await.into_string();
|
||||||
|
|
||||||
// Debe mostrar el bloque de reconocimiento a PageTop.
|
// Should show the PageTop acknowledgment block.
|
||||||
assert!(html.contains("poweredby__pagetop"));
|
assert!(html.contains("poweredby__pagetop"));
|
||||||
|
|
||||||
// Y NO debe mostrar el bloque de copyright.
|
// And should NOT show the copyright block.
|
||||||
assert!(!html.contains("poweredby__copyright"));
|
assert!(!html.contains("poweredby__copyright"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -32,14 +32,14 @@ async fn poweredby_new_includes_current_year_and_app_name() {
|
||||||
let year = Utc::now().format("%Y").to_string();
|
let year = Utc::now().format("%Y").to_string();
|
||||||
assert!(html.contains(&year), "HTML should include the current year");
|
assert!(html.contains(&year), "HTML should include the current year");
|
||||||
|
|
||||||
// El nombre de la app proviene de `global::SETTINGS.app.name`.
|
// The app name comes from `global::SETTINGS.app.name`.
|
||||||
let app_name = &global::SETTINGS.app.name;
|
let app_name = &global::SETTINGS.app.name;
|
||||||
assert!(
|
assert!(
|
||||||
html.contains(app_name),
|
html.contains(app_name),
|
||||||
"HTML should include the application name"
|
"HTML should include the application name"
|
||||||
);
|
);
|
||||||
|
|
||||||
// Debe existir el span de copyright.
|
// The copyright span must exist.
|
||||||
assert!(html.contains("poweredby__copyright"));
|
assert!(html.contains("poweredby__copyright"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -63,7 +63,7 @@ async fn poweredby_with_copyright_none_hides_text() {
|
||||||
let html = p.render(&mut Context::default()).await.into_string();
|
let html = p.render(&mut Context::default()).await.into_string();
|
||||||
|
|
||||||
assert!(!html.contains("poweredby__copyright"));
|
assert!(!html.contains("poweredby__copyright"));
|
||||||
// El reconocimiento a PageTop siempre debe aparecer.
|
// The PageTop acknowledgment must always appear.
|
||||||
assert!(html.contains("poweredby__pagetop"));
|
assert!(html.contains("poweredby__pagetop"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -84,11 +84,11 @@ async fn poweredby_link_points_to_crates_io() {
|
||||||
async fn poweredby_getter_reflects_internal_state() {
|
async fn poweredby_getter_reflects_internal_state() {
|
||||||
setup().await;
|
setup().await;
|
||||||
|
|
||||||
// Por defecto no hay copyright.
|
// There is no copyright by default.
|
||||||
let p0 = PoweredBy::default();
|
let p0 = PoweredBy::default();
|
||||||
assert_eq!(p0.copyright(), None);
|
assert_eq!(p0.copyright(), None);
|
||||||
|
|
||||||
// Y `new()` lo inicializa con año + nombre de app.
|
// And `new()` initializes it with year + app name.
|
||||||
let p1 = PoweredBy::new();
|
let p1 = PoweredBy::new();
|
||||||
let c1 = p1.copyright().expect("Expected copyright to exist");
|
let c1 = p1.copyright().expect("Expected copyright to exist");
|
||||||
assert!(c1.contains(&Utc::now().format("%Y").to_string()));
|
assert!(c1.contains(&Utc::now().format("%Y").to_string()));
|
||||||
|
|
|
||||||
|
|
@ -20,9 +20,9 @@ pub struct Test {
|
||||||
pub float_value: f32,
|
pub float_value: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
// La *feature* `testing` (activo con `cargo ts` / `cargo tw`) fija el modo "test" en tiempo de
|
// The `testing` *feature* (active with `cargo ts` / `cargo tw`) fixes the "test" mode at compile
|
||||||
// compilación dentro de `config::CONFIG_VALUES`, de forma que `global::SETTINGS` y cualquier
|
// time inside `config::CONFIG_VALUES`, so that `global::SETTINGS` and any local `include_config!`
|
||||||
// `include_config!` local cargan automáticamente la configuración del modo "test".
|
// automatically load the "test" mode configuration.
|
||||||
|
|
||||||
#[pagetop::test]
|
#[pagetop::test]
|
||||||
async fn check_global_config() {
|
async fn check_global_config() {
|
||||||
|
|
|
||||||
|
|
@ -34,10 +34,9 @@ async fn panic_in_handler_returns_minimal_500_page_instead_of_crashing() {
|
||||||
|
|
||||||
// **< ErrorPage::NotFound >************************************************************************
|
// **< ErrorPage::NotFound >************************************************************************
|
||||||
|
|
||||||
// `EXTENSIONS` es un `OnceLock` global (`core/extension/all.rs`): se inicializa una sola vez por
|
// `EXTENSIONS` is a global `OnceLock` (`core/extension/all.rs`): it is initialized only once per
|
||||||
// binario de test. Todos los tests de este fichero comparten la misma extensión raíz
|
// test binary. All tests in this file share the same root extension (`PanicExtension`) so that the
|
||||||
// (`PanicExtension`) para que el orden de ejecución en paralelo no cambie qué rutas quedan
|
// parallel execution order does not change which routes end up registered.
|
||||||
// registradas.
|
|
||||||
#[pagetop::test]
|
#[pagetop::test]
|
||||||
async fn unknown_route_returns_themed_404_page() {
|
async fn unknown_route_returns_themed_404_page() {
|
||||||
let app = web::test::init_router(Application::prepare(&PanicExtension).await.test());
|
let app = web::test::init_router(Application::prepare(&PanicExtension).await.test());
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
use pagetop::prelude::*;
|
use pagetop::prelude::*;
|
||||||
|
|
||||||
/// Componente mínimo para probar `Markup` pasando por el ciclo real de renderizado de componentes
|
/// Minimal component to test `Markup` going through the real component rendering cycle
|
||||||
/// (`ComponentRender`). El parámetro de contexto `"renderable"` se usará para controlar si el
|
/// (`ComponentRender`). The context parameter `"renderable"` is used to control whether the
|
||||||
/// componente se renderiza (`true` por defecto).
|
/// component is rendered (`true` by default).
|
||||||
#[derive(AutoDefault, Clone)]
|
#[derive(AutoDefault, Clone)]
|
||||||
struct TestMarkupComponent {
|
struct TestMarkupComponent {
|
||||||
markup: Markup,
|
markup: Markup,
|
||||||
|
|
@ -23,7 +23,7 @@ impl Component for TestMarkupComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// **< Comportamiento de Markup >*******************************************************************
|
// **< Markup behavior >****************************************************************************
|
||||||
|
|
||||||
#[pagetop::test]
|
#[pagetop::test]
|
||||||
async fn string_in_html_macro_escapes_html_entities() {
|
async fn string_in_html_macro_escapes_html_entities() {
|
||||||
|
|
@ -39,11 +39,11 @@ async fn preescaped_in_html_macro_is_inserted_verbatim() {
|
||||||
|
|
||||||
#[pagetop::test]
|
#[pagetop::test]
|
||||||
async fn unicode_is_preserved_in_markup() {
|
async fn unicode_is_preserved_in_markup() {
|
||||||
// Texto con acentos y emojis: sólo se escapan los signos HTML.
|
// Text with accents and emoji: only HTML signs are escaped.
|
||||||
let esc = html! { ("Hello, tomorrow coffee ☕ & donuts!") };
|
let esc = html! { ("Hello, tomorrow coffee ☕ & donuts!") };
|
||||||
assert_eq!(esc.into_string(), "Hello, tomorrow coffee ☕ & donuts!");
|
assert_eq!(esc.into_string(), "Hello, tomorrow coffee ☕ & donuts!");
|
||||||
|
|
||||||
// PreEscaped debe pasar íntegro.
|
// PreEscaped must pass through untouched.
|
||||||
let raw = html! { (PreEscaped("Title — section © 2025")) };
|
let raw = html! { (PreEscaped("Title — section © 2025")) };
|
||||||
assert_eq!(raw.into_string(), "Title — section © 2025");
|
assert_eq!(raw.into_string(), "Title — section © 2025");
|
||||||
}
|
}
|
||||||
|
|
@ -62,12 +62,12 @@ async fn markup_is_empty_semantics() {
|
||||||
|
|
||||||
assert!(!html! { span { "!" } }.is_empty());
|
assert!(!html! { span { "!" } }.is_empty());
|
||||||
|
|
||||||
// Espacios NO se consideran vacíos.
|
// Spaces are NOT considered empty.
|
||||||
assert!(!html! { (" ") }.is_empty());
|
assert!(!html! { (" ") }.is_empty());
|
||||||
assert!(!html! { (PreEscaped(" ")) }.is_empty());
|
assert!(!html! { (PreEscaped(" ")) }.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
// **< Markup a través del ciclo de componente >****************************************************
|
// **< Markup through the component cycle >*********************************************************
|
||||||
|
|
||||||
#[pagetop::test]
|
#[pagetop::test]
|
||||||
async fn non_renderable_component_produces_empty_markup() {
|
async fn non_renderable_component_produces_empty_markup() {
|
||||||
|
|
@ -88,7 +88,7 @@ async fn markup_from_component_equals_markup_reinjected_in_html_macro() {
|
||||||
];
|
];
|
||||||
|
|
||||||
for markup in cases {
|
for markup in cases {
|
||||||
// Vía 1: renderizamos a través del ciclo de componente.
|
// Path 1: we render through the component cycle.
|
||||||
let via_component = {
|
let via_component = {
|
||||||
let mut cx = Context::default();
|
let mut cx = Context::default();
|
||||||
let mut comp = TestMarkupComponent {
|
let mut comp = TestMarkupComponent {
|
||||||
|
|
@ -97,7 +97,7 @@ async fn markup_from_component_equals_markup_reinjected_in_html_macro() {
|
||||||
comp.render(&mut cx).await.into_string()
|
comp.render(&mut cx).await.into_string()
|
||||||
};
|
};
|
||||||
|
|
||||||
// Vía 2: reinyectamos el Markup en `html!` directamente.
|
// Path 2: we reinject the Markup into `html!` directly.
|
||||||
let via_macro = html! { (markup) }.into_string();
|
let via_macro = html! { (markup) }.into_string();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ async fn props_set_replaces_existing_value() {
|
||||||
|
|
||||||
#[pagetop::test]
|
#[pagetop::test]
|
||||||
async fn props_set_does_not_create_duplicate_key() {
|
async fn props_set_does_not_create_duplicate_key() {
|
||||||
// Reasignar la misma clave debe reemplazar el valor, no añadir una entrada duplicada.
|
// Reassigning the same key must replace the value, not add a duplicate entry.
|
||||||
let p = Props::new("key", "v1").with_prop(PropsOp::set("key", "v2"));
|
let p = Props::new("key", "v1").with_prop(PropsOp::set("key", "v2"));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
html! { span (p) {} }.into_string(),
|
html! { span (p) {} }.into_string(),
|
||||||
|
|
@ -109,7 +109,7 @@ async fn props_escapes_double_quotes_in_value() {
|
||||||
|
|
||||||
#[pagetop::test]
|
#[pagetop::test]
|
||||||
async fn props_empty_in_html_macro_produces_no_attributes() {
|
async fn props_empty_in_html_macro_produces_no_attributes() {
|
||||||
// Una Props vacía no debe emitir ni siquiera un espacio en blanco extra.
|
// An empty Props must not emit even an extra blank space.
|
||||||
let p = Props::default();
|
let p = Props::default();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
html! { button (p) { "x" } }.into_string(),
|
html! { button (p) { "x" } }.into_string(),
|
||||||
|
|
@ -139,7 +139,7 @@ async fn props_multiple_attrs_preserve_order_in_html_macro() {
|
||||||
|
|
||||||
#[pagetop::test]
|
#[pagetop::test]
|
||||||
async fn props_alongside_class_and_id_in_html_macro() {
|
async fn props_alongside_class_and_id_in_html_macro() {
|
||||||
// El splice siempre se emite después de class e id, independientemente del orden escrito.
|
// The splice is always emitted after class and id, regardless of the order they are written in.
|
||||||
let p = Props::new("hx-get", "/api");
|
let p = Props::new("hx-get", "/api");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
html! { button #mybtn .btn (p) { "Go" } }.into_string(),
|
html! { button #mybtn .btn (p) { "Go" } }.into_string(),
|
||||||
|
|
@ -189,7 +189,7 @@ async fn props_conditional_expression_in_html_macro() {
|
||||||
|
|
||||||
#[pagetop::test]
|
#[pagetop::test]
|
||||||
async fn props_splice_empty_string_emits_nothing() {
|
async fn props_splice_empty_string_emits_nothing() {
|
||||||
// Un splice vacío no emite ningún atributo ni espacio extra.
|
// An empty splice emits no attribute nor extra space.
|
||||||
assert_eq!(html! { span ("") { "x" } }.into_string(), "<span>x</span>");
|
assert_eq!(html! { span ("") { "x" } }.into_string(), "<span>x</span>");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -254,7 +254,7 @@ async fn get_prop_id_matches_get_id() {
|
||||||
|
|
||||||
#[pagetop::test]
|
#[pagetop::test]
|
||||||
async fn props_hx_target_value_with_hash_renders_correctly() {
|
async fn props_hx_target_value_with_hash_renders_correctly() {
|
||||||
// Regresión: r#"..."# se cerraba prematuramente al encontrar `"#lista"`.
|
// Regression: r#"..."# used to close prematurely when it found `"#list"`.
|
||||||
let p = Props::new("hx-target", "#list");
|
let p = Props::new("hx-target", "#list");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
html! { button (p) {} }.into_string(),
|
html! { button (p) {} }.into_string(),
|
||||||
|
|
@ -289,7 +289,7 @@ async fn props_chained_set_and_remove_yields_expected_state() {
|
||||||
|
|
||||||
#[pagetop::test]
|
#[pagetop::test]
|
||||||
async fn props_with_empty_attr_name_renders_without_validation() {
|
async fn props_with_empty_attr_name_renders_without_validation() {
|
||||||
// Comportamiento documentado: los nombres no se validan; el HTML resultante no es estándar.
|
// Documented behavior: names are not validated; the resulting HTML is not standard.
|
||||||
let p = Props::new("", "val");
|
let p = Props::new("", "val");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
html! { span (p) {} }.into_string(),
|
html! { span (p) {} }.into_string(),
|
||||||
|
|
|
||||||
|
|
@ -55,9 +55,9 @@ async fn add_style_value_preserves_case_and_non_ascii() {
|
||||||
|
|
||||||
#[pagetop::test]
|
#[pagetop::test]
|
||||||
async fn add_style_value_may_contain_semicolons() {
|
async fn add_style_value_may_contain_semicolons() {
|
||||||
// A diferencia de PropsOp::Set("style", ...), que interpreta la cadena como declaraciones
|
// Unlike PropsOp::Set("style", ...), which interprets the string as declarations separated by
|
||||||
// separadas por ";", AddStyle recibe la propiedad y el valor ya separados, así que un ";"
|
// ";", AddStyle receives the property and the value already separated, so a ";" inside the
|
||||||
// dentro del valor (p. ej. una data URI) no supone ningún problema.
|
// value (e.g. a data URI) is not a problem.
|
||||||
let p = Props::default().with_prop(PropsOp::add_style(
|
let p = Props::default().with_prop(PropsOp::add_style(
|
||||||
"background",
|
"background",
|
||||||
"url(data:image/png;base64,AAAA)",
|
"url(data:image/png;base64,AAAA)",
|
||||||
|
|
@ -188,9 +188,9 @@ async fn styles_reset_mixes_declarations_with_and_without_parens() {
|
||||||
assert_styles(&p, Some("color: red; background: url(a;b); margin: 0"));
|
assert_styles(&p, Some("color: red; background: url(a;b); margin: 0"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Límite conocido de PropsOp::Set("style", ...): no es un análisis CSS completo. Unos paréntesis
|
// Known limitation of PropsOp::Set("style", ...): it is not a full CSS parser. Unclosed parentheses
|
||||||
// sin cerrar arrastran el resto de la cadena a la misma declaración. Este test fija el
|
// drag the rest of the string into the same declaration. This test pins the current behavior so
|
||||||
// comportamiento actual para que un cambio futuro sea deliberado, no accidental.
|
// that a future change is deliberate, not accidental.
|
||||||
#[pagetop::test]
|
#[pagetop::test]
|
||||||
async fn styles_reset_unbalanced_parens_swallows_rest_of_string() {
|
async fn styles_reset_unbalanced_parens_swallows_rest_of_string() {
|
||||||
let p = Props::default().with_prop(PropsOp::set(
|
let p = Props::default().with_prop(PropsOp::set(
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ async fn unit_value_empty_and_auto_and_zero_without_unit() {
|
||||||
assert_eq!(UnitValue::from_str("auto").unwrap(), UnitValue::Auto);
|
assert_eq!(UnitValue::from_str("auto").unwrap(), UnitValue::Auto);
|
||||||
assert_eq!(UnitValue::from_str("AUTO").unwrap(), UnitValue::Auto);
|
assert_eq!(UnitValue::from_str("AUTO").unwrap(), UnitValue::Auto);
|
||||||
|
|
||||||
// Cero sin unidad.
|
// Zero without a unit.
|
||||||
assert_eq!(UnitValue::from_str("0").unwrap(), UnitValue::Zero);
|
assert_eq!(UnitValue::from_str("0").unwrap(), UnitValue::Zero);
|
||||||
assert_eq!(UnitValue::from_str("+0").unwrap(), UnitValue::Zero);
|
assert_eq!(UnitValue::from_str("+0").unwrap(), UnitValue::Zero);
|
||||||
assert_eq!(UnitValue::from_str("-0").unwrap(), UnitValue::Zero);
|
assert_eq!(UnitValue::from_str("-0").unwrap(), UnitValue::Zero);
|
||||||
|
|
@ -16,7 +16,7 @@ async fn unit_value_empty_and_auto_and_zero_without_unit() {
|
||||||
|
|
||||||
#[pagetop::test]
|
#[pagetop::test]
|
||||||
async fn unit_value_absolute_integers_with_signs_and_spaces_and_case() {
|
async fn unit_value_absolute_integers_with_signs_and_spaces_and_case() {
|
||||||
// Positivos, negativos y con espacios.
|
// Positive, negative, and with spaces.
|
||||||
assert_eq!(UnitValue::from_str("12px").unwrap(), UnitValue::Px(12));
|
assert_eq!(UnitValue::from_str("12px").unwrap(), UnitValue::Px(12));
|
||||||
assert_eq!(UnitValue::from_str("-5pt").unwrap(), UnitValue::Pt(-5));
|
assert_eq!(UnitValue::from_str("-5pt").unwrap(), UnitValue::Pt(-5));
|
||||||
assert_eq!(UnitValue::from_str(" 7 cm ").unwrap(), UnitValue::Cm(7));
|
assert_eq!(UnitValue::from_str(" 7 cm ").unwrap(), UnitValue::Cm(7));
|
||||||
|
|
@ -24,7 +24,7 @@ async fn unit_value_absolute_integers_with_signs_and_spaces_and_case() {
|
||||||
assert_eq!(UnitValue::from_str(" 13 mm ").unwrap(), UnitValue::Mm(13));
|
assert_eq!(UnitValue::from_str(" 13 mm ").unwrap(), UnitValue::Mm(13));
|
||||||
assert_eq!(UnitValue::from_str("4 pc").unwrap(), UnitValue::Pc(4));
|
assert_eq!(UnitValue::from_str("4 pc").unwrap(), UnitValue::Pc(4));
|
||||||
|
|
||||||
// Insensibilidad a mayúsculas.
|
// Case insensitivity.
|
||||||
assert_eq!(UnitValue::from_str("10PX").unwrap(), UnitValue::Px(10));
|
assert_eq!(UnitValue::from_str("10PX").unwrap(), UnitValue::Px(10));
|
||||||
assert_eq!(UnitValue::from_str("15Pt").unwrap(), UnitValue::Pt(15));
|
assert_eq!(UnitValue::from_str("15Pt").unwrap(), UnitValue::Pt(15));
|
||||||
}
|
}
|
||||||
|
|
@ -55,7 +55,7 @@ async fn unit_value_relative_floats_with_signs_and_spaces_and_case() {
|
||||||
|
|
||||||
#[pagetop::test]
|
#[pagetop::test]
|
||||||
async fn unit_value_whitespace_between_number_and_unit_is_allowed() {
|
async fn unit_value_whitespace_between_number_and_unit_is_allowed() {
|
||||||
// Hay espacio entre número y unidad (la implementación actual lo admite).
|
// There is a space between number and unit (the current implementation allows it).
|
||||||
assert_eq!(UnitValue::from_str("12 px").unwrap(), UnitValue::Px(12));
|
assert_eq!(UnitValue::from_str("12 px").unwrap(), UnitValue::Px(12));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
UnitValue::from_str("1.5 rem").unwrap(),
|
UnitValue::from_str("1.5 rem").unwrap(),
|
||||||
|
|
@ -113,7 +113,7 @@ async fn unit_value_percentage_trimming_and_signs() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ERRORES ESPERADOS (no cambiar los mensajes; con is_err() basta).
|
// EXPECTED ERRORS (don't change the messages; is_err() is enough).
|
||||||
|
|
||||||
#[pagetop::test]
|
#[pagetop::test]
|
||||||
async fn unit_value_errors_missing_unit_for_non_zero() {
|
async fn unit_value_errors_missing_unit_for_non_zero() {
|
||||||
|
|
@ -136,10 +136,10 @@ async fn unit_value_errors_decimals_in_absolute_units() {
|
||||||
|
|
||||||
#[pagetop::test]
|
#[pagetop::test]
|
||||||
async fn unit_value_errors_unknown_units_or_bad_percentages() {
|
async fn unit_value_errors_unknown_units_or_bad_percentages() {
|
||||||
// Unidad no soportada.
|
// Unsupported unit.
|
||||||
assert!(UnitValue::from_str("10ch").is_err());
|
assert!(UnitValue::from_str("10ch").is_err());
|
||||||
assert!(UnitValue::from_str("2q").is_err());
|
assert!(UnitValue::from_str("2q").is_err());
|
||||||
// Falta número.
|
// Missing number.
|
||||||
assert!(UnitValue::from_str("%").is_err());
|
assert!(UnitValue::from_str("%").is_err());
|
||||||
assert!(UnitValue::from_str(" % ").is_err());
|
assert!(UnitValue::from_str(" % ").is_err());
|
||||||
}
|
}
|
||||||
|
|
@ -147,7 +147,7 @@ async fn unit_value_errors_unknown_units_or_bad_percentages() {
|
||||||
#[pagetop::test]
|
#[pagetop::test]
|
||||||
async fn unit_value_errors_non_numeric_numbers() {
|
async fn unit_value_errors_non_numeric_numbers() {
|
||||||
assert!(UnitValue::from_str("NaNem").is_err());
|
assert!(UnitValue::from_str("NaNem").is_err());
|
||||||
// Decimal no permitido por FromStr.
|
// Decimal not allowed by FromStr.
|
||||||
assert!(UnitValue::from_str("1,5rem").is_err());
|
assert!(UnitValue::from_str("1,5rem").is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -190,22 +190,22 @@ async fn unit_value_serde_deserialize_struct_and_array() {
|
||||||
|
|
||||||
#[pagetop::test]
|
#[pagetop::test]
|
||||||
async fn unit_value_accepts_dot5_and_1dot_shorthand_for_relatives() {
|
async fn unit_value_accepts_dot5_and_1dot_shorthand_for_relatives() {
|
||||||
// `.5` y `1.` se parsean correctamente en relativas.
|
// `.5` and `1.` parse correctly for relative units.
|
||||||
assert_eq!(UnitValue::from_str(".5em").unwrap(), UnitValue::RelEm(0.5));
|
assert_eq!(UnitValue::from_str(".5em").unwrap(), UnitValue::RelEm(0.5));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
UnitValue::from_str("1.rem").unwrap(),
|
UnitValue::from_str("1.rem").unwrap(),
|
||||||
UnitValue::RelRem(1.0)
|
UnitValue::RelRem(1.0)
|
||||||
);
|
);
|
||||||
assert_eq!(UnitValue::from_str("1.vh").unwrap(), UnitValue::RelVh(1.0));
|
assert_eq!(UnitValue::from_str("1.vh").unwrap(), UnitValue::RelVh(1.0));
|
||||||
// Sin unidad debe seguir fallando.
|
// Without a unit it must keep failing.
|
||||||
assert!(UnitValue::from_str("1.").is_err());
|
assert!(UnitValue::from_str("1.").is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pagetop::test]
|
#[pagetop::test]
|
||||||
async fn unit_value_display_keeps_minus_zero_for_relatives() {
|
async fn unit_value_display_keeps_minus_zero_for_relatives() {
|
||||||
// Comportamiento actual: f32 Display muestra "-0" si el valor es -0.0.
|
// Current behavior: f32 Display shows "-0" if the value is -0.0.
|
||||||
let v = UnitValue::RelEm(-0.0);
|
let v = UnitValue::RelEm(-0.0);
|
||||||
// Se acepta cualquiera de los dos formatos como válidos.
|
// Either of the two formats is accepted as valid.
|
||||||
let s = v.to_string();
|
let s = v.to_string();
|
||||||
assert!(
|
assert!(
|
||||||
s == "-0em" || s == "0em",
|
s == "-0em" || s == "0em",
|
||||||
|
|
@ -215,9 +215,9 @@ async fn unit_value_display_keeps_minus_zero_for_relatives() {
|
||||||
|
|
||||||
#[pagetop::test]
|
#[pagetop::test]
|
||||||
async fn unit_value_rejects_non_decimal_notations() {
|
async fn unit_value_rejects_non_decimal_notations() {
|
||||||
// Octal, los ceros a la izquierda (p. ej. `"020px"`) se interpretan en **base 10** (`20px`).
|
// Leading zeros (e.g. `"020px"`) are interpreted in **base 10** (`20px`), not octal.
|
||||||
assert_eq!(UnitValue::from_str("020px").unwrap(), UnitValue::Px(20));
|
assert_eq!(UnitValue::from_str("020px").unwrap(), UnitValue::Px(20));
|
||||||
// Notación científica y bases no decimales (p. ej., `"1e3vw"`, `"0x10px"`) no están soportadas.
|
// Scientific notation and non-decimal bases (e.g., `"1e3vw"`, `"0x10px"`) are not supported.
|
||||||
assert!(UnitValue::from_str("1e3vw").is_err());
|
assert!(UnitValue::from_str("1e3vw").is_err());
|
||||||
assert!(UnitValue::from_str("0x10px").is_err());
|
assert!(UnitValue::from_str("0x10px").is_err());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use pagetop::prelude::*;
|
use pagetop::prelude::*;
|
||||||
|
|
||||||
// **< Tema con plantilla propia >******************************************************************
|
// **< Theme with its own template >****************************************************************
|
||||||
|
|
||||||
struct MarkerTemplate;
|
struct MarkerTemplate;
|
||||||
|
|
||||||
|
|
@ -36,19 +36,19 @@ async fn render_active_template(cx: &mut Context) -> String {
|
||||||
template.render(cx).await.into_string()
|
template.render(cx).await.into_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
// **< Context::template() sigue al tema activo >***************************************************
|
// **< Context::template() follows the active theme >***********************************************
|
||||||
|
|
||||||
#[pagetop::test]
|
#[pagetop::test]
|
||||||
async fn with_theme_updates_the_effective_template() {
|
async fn with_theme_updates_the_effective_template() {
|
||||||
// Sin cambiar de tema, la plantilla activa no es la de `MarkerTheme`.
|
// Without changing theme, the active template is not `MarkerTheme`'s.
|
||||||
let mut cx = Context::new(None);
|
let mut cx = Context::new(None);
|
||||||
assert_ne!(
|
assert_ne!(
|
||||||
render_active_template(&mut cx).await,
|
render_active_template(&mut cx).await,
|
||||||
"marker-template-output"
|
"marker-template-output"
|
||||||
);
|
);
|
||||||
|
|
||||||
// Tras cambiar de tema con `with_theme()`, la plantilla activa pasa a ser la de ese tema, sin
|
// After changing theme with `with_theme()`, the active template becomes that theme's, with no
|
||||||
// necesidad de llamar a `with_template()` explícitamente.
|
// need to call `with_template()` explicitly.
|
||||||
let mut cx = Context::new(None).with_theme(&MarkerTheme);
|
let mut cx = Context::new(None).with_theme(&MarkerTheme);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
render_active_template(&mut cx).await,
|
render_active_template(&mut cx).await,
|
||||||
|
|
@ -58,8 +58,8 @@ async fn with_theme_updates_the_effective_template() {
|
||||||
|
|
||||||
#[pagetop::test]
|
#[pagetop::test]
|
||||||
async fn explicit_template_is_not_overridden_by_a_later_with_theme() {
|
async fn explicit_template_is_not_overridden_by_a_later_with_theme() {
|
||||||
// Una plantilla fijada explícitamente con `with_template()` prevalece aunque `with_theme()` se
|
// A template explicitly set with `with_template()` prevails even if `with_theme()` is called
|
||||||
// llame después, en cualquier orden.
|
// afterwards, regardless of order.
|
||||||
let mut cx = Context::new(None)
|
let mut cx = Context::new(None)
|
||||||
.with_template(&MarkerTemplate)
|
.with_template(&MarkerTemplate)
|
||||||
.with_theme(&pagetop::base::theme::Basic);
|
.with_theme(&pagetop::base::theme::Basic);
|
||||||
|
|
@ -70,7 +70,7 @@ async fn explicit_template_is_not_overridden_by_a_later_with_theme() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// **< Page::admin() sigue al tema activo >*********************************************************
|
// **< Page::admin() follows the active theme >*****************************************************
|
||||||
|
|
||||||
#[pagetop::test]
|
#[pagetop::test]
|
||||||
async fn page_admin_template_follows_a_later_with_theme() {
|
async fn page_admin_template_follows_a_later_with_theme() {
|
||||||
|
|
|
||||||
|
|
@ -45,15 +45,15 @@ fn assert_owned(input: &str, expected: &str) {
|
||||||
|
|
||||||
#[pagetop::test]
|
#[pagetop::test]
|
||||||
async fn normalize_errors() {
|
async fn normalize_errors() {
|
||||||
// Caso especial: cadena vacía.
|
// Special case: empty string.
|
||||||
assert_err("", util::NormalizeAsciiError::IsEmpty);
|
assert_err("", util::NormalizeAsciiError::IsEmpty);
|
||||||
|
|
||||||
// Sólo separadores ASCII: tras el recorte no queda nada.
|
// Only ASCII separators: nothing is left after trimming.
|
||||||
for input in [" ", " ", "\t", "\n", "\r", "\t \n\r "] {
|
for input in [" ", " ", "\t", "\n", "\r", "\t \n\r "] {
|
||||||
assert_err(input, util::NormalizeAsciiError::EmptyAfterTrimming);
|
assert_err(input, util::NormalizeAsciiError::EmptyAfterTrimming);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cualquier byte no-ASCII debe fallar, aunque el resto pueda normalizarse.
|
// Any non-ASCII byte must fail, even if the rest could be normalized.
|
||||||
for input in [
|
for input in [
|
||||||
"©",
|
"©",
|
||||||
"á",
|
"á",
|
||||||
|
|
@ -70,7 +70,7 @@ async fn normalize_errors() {
|
||||||
|
|
||||||
#[pagetop::test]
|
#[pagetop::test]
|
||||||
async fn normalize_borrowed_trim_and_already_normalized() {
|
async fn normalize_borrowed_trim_and_already_normalized() {
|
||||||
// Sólo recorte (incluyendo separadores al final).
|
// Trimming only (including trailing separators).
|
||||||
for (input, expected) in [
|
for (input, expected) in [
|
||||||
(" a", "a"),
|
(" a", "a"),
|
||||||
("a ", "a"),
|
("a ", "a"),
|
||||||
|
|
@ -89,7 +89,7 @@ async fn normalize_borrowed_trim_and_already_normalized() {
|
||||||
assert_borrowed(input, expected);
|
assert_borrowed(input, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ya normalizado (minúsculas y un único espacio entre tokens).
|
// Already normalized (lowercase and a single space between tokens).
|
||||||
for input in [
|
for input in [
|
||||||
"a",
|
"a",
|
||||||
"a b",
|
"a b",
|
||||||
|
|
@ -107,12 +107,12 @@ async fn normalize_borrowed_trim_and_already_normalized() {
|
||||||
"path/to/resource",
|
"path/to/resource",
|
||||||
"foo+bar=baz",
|
"foo+bar=baz",
|
||||||
"a-._:/+=",
|
"a-._:/+=",
|
||||||
"a\x1Bb", // Byte de control ASCII: se conserva tal cual.
|
"a\x1Bb", // ASCII control byte: preserved as-is.
|
||||||
] {
|
] {
|
||||||
assert_borrowed(input, input);
|
assert_borrowed(input, input);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Separador "raro" al final de la cadena: se recorta y se devuelve porción.
|
// "Unusual" separator at the end of the string: it is trimmed and a slice is returned.
|
||||||
for (input, expected) in [
|
for (input, expected) in [
|
||||||
("foo bar\t", "foo bar"),
|
("foo bar\t", "foo bar"),
|
||||||
("foo bar\r\n", "foo bar"),
|
("foo bar\r\n", "foo bar"),
|
||||||
|
|
@ -124,7 +124,7 @@ async fn normalize_borrowed_trim_and_already_normalized() {
|
||||||
|
|
||||||
#[pagetop::test]
|
#[pagetop::test]
|
||||||
async fn normalize_owned_due_to_uppercase() {
|
async fn normalize_owned_due_to_uppercase() {
|
||||||
// Sólo por mayúsculas (y otros ASCII que se preservan).
|
// Only due to uppercase (and other ASCII that is preserved).
|
||||||
for (input, expected) in [
|
for (input, expected) in [
|
||||||
("A", "a"),
|
("A", "a"),
|
||||||
("Foo", "foo"),
|
("Foo", "foo"),
|
||||||
|
|
@ -141,7 +141,7 @@ async fn normalize_owned_due_to_uppercase() {
|
||||||
("ETag:W/\"XYZ\"", "etag:w/\"xyz\""),
|
("ETag:W/\"XYZ\"", "etag:w/\"xyz\""),
|
||||||
("Foo+Bar=Baz", "foo+bar=baz"),
|
("Foo+Bar=Baz", "foo+bar=baz"),
|
||||||
("A-._:/+=", "a-._:/+="),
|
("A-._:/+=", "a-._:/+="),
|
||||||
("A\x1BB", "a\x1bb"), // Sólo letras en minúsculas; el byte de control se conserva.
|
("A\x1BB", "a\x1bb"), // Only letters get lowercased; the control byte is preserved.
|
||||||
] {
|
] {
|
||||||
assert_owned(input, expected);
|
assert_owned(input, expected);
|
||||||
}
|
}
|
||||||
|
|
@ -149,12 +149,12 @@ async fn normalize_owned_due_to_uppercase() {
|
||||||
|
|
||||||
#[pagetop::test]
|
#[pagetop::test]
|
||||||
async fn normalize_owned_due_to_internal_whitespace() {
|
async fn normalize_owned_due_to_internal_whitespace() {
|
||||||
// Espacios consecutivos (deben colapsar a un único espacio).
|
// Consecutive spaces (must collapse to a single space).
|
||||||
for (input, expected) in [("a b", "a b"), ("a b", "a b")] {
|
for (input, expected) in [("a b", "a b"), ("a b", "a b")] {
|
||||||
assert_owned(input, expected);
|
assert_owned(input, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Separadores ASCII distintos de ' ' entre tokens (tab, newline, CR, CRLF).
|
// ASCII separators other than ' ' between tokens (tab, newline, CR, CRLF).
|
||||||
for (input, expected) in [
|
for (input, expected) in [
|
||||||
("a\tb", "a b"),
|
("a\tb", "a b"),
|
||||||
("a\nb", "a b"),
|
("a\nb", "a b"),
|
||||||
|
|
@ -168,7 +168,7 @@ async fn normalize_owned_due_to_internal_whitespace() {
|
||||||
assert_owned(input, expected);
|
assert_owned(input, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mezclas de separadores.
|
// Mixed separators.
|
||||||
for (input, expected) in [
|
for (input, expected) in [
|
||||||
("a \t \n b", "a b"),
|
("a \t \n b", "a b"),
|
||||||
("a\t \n b", "a b"),
|
("a\t \n b", "a b"),
|
||||||
|
|
@ -180,7 +180,7 @@ async fn normalize_owned_due_to_internal_whitespace() {
|
||||||
assert_owned(input, expected);
|
assert_owned(input, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
// El resultado nunca debe tener espacios al inicio/fin (tras normalizar).
|
// The result must never have leading/trailing spaces (after normalizing).
|
||||||
for (input, expected) in [
|
for (input, expected) in [
|
||||||
(" a b ", "a b"),
|
(" a b ", "a b"),
|
||||||
(" a\tb ", "a b"),
|
(" a\tb ", "a b"),
|
||||||
|
|
@ -192,7 +192,7 @@ async fn normalize_owned_due_to_internal_whitespace() {
|
||||||
|
|
||||||
#[pagetop::test]
|
#[pagetop::test]
|
||||||
async fn normalize_owned_due_to_mixed_causes() {
|
async fn normalize_owned_due_to_mixed_causes() {
|
||||||
// Combinaciones de mayúsculas y separador no normalizado.
|
// Combinations of uppercase and non-normalized separators.
|
||||||
for (input, expected) in [
|
for (input, expected) in [
|
||||||
(" Foo BAR\tbaz ", "foo bar baz"),
|
(" Foo BAR\tbaz ", "foo bar baz"),
|
||||||
("\nFOO\rbar\tBAZ\n", "foo bar baz"),
|
("\nFOO\rbar\tBAZ\n", "foo bar baz"),
|
||||||
|
|
@ -209,17 +209,17 @@ async fn normalize_owned_due_to_mixed_causes() {
|
||||||
|
|
||||||
#[pagetop::test]
|
#[pagetop::test]
|
||||||
async fn normalize_borrowed_vs_owned_edge_cases() {
|
async fn normalize_borrowed_vs_owned_edge_cases() {
|
||||||
// Un sólo token con separador al final.
|
// A single token with a trailing separator.
|
||||||
for (input, expected) in [("x ", "x"), ("x\t", "x"), ("x\n", "x"), ("x\r\n", "x")] {
|
for (input, expected) in [("x ", "x"), ("x\t", "x"), ("x\n", "x"), ("x\r\n", "x")] {
|
||||||
assert_borrowed(input, expected);
|
assert_borrowed(input, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dos tokens con separador no normalizado.
|
// Two tokens with a non-normalized separator.
|
||||||
for input in ["x y", "x\t\ty", "x \t y", "x\r\ny"] {
|
for input in ["x y", "x\t\ty", "x \t y", "x\r\ny"] {
|
||||||
assert_owned(input, "x y");
|
assert_owned(input, "x y");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dos tokens con separación limpia.
|
// Two tokens with a clean separator.
|
||||||
for (input, expected) in [("x y ", "x y"), ("x y\t", "x y"), ("x y\r\n", "x y")] {
|
for (input, expected) in [("x y ", "x y"), ("x y\t", "x y"), ("x y\r\n", "x y")] {
|
||||||
assert_borrowed(input, expected);
|
assert_borrowed(input, expected);
|
||||||
}
|
}
|
||||||
|
|
@ -227,7 +227,7 @@ async fn normalize_borrowed_vs_owned_edge_cases() {
|
||||||
|
|
||||||
#[pagetop::test]
|
#[pagetop::test]
|
||||||
async fn normalize_is_idempotent() {
|
async fn normalize_is_idempotent() {
|
||||||
// La normalización debe ser idempotente: normalizar el resultado no cambia nada.
|
// Normalization must be idempotent: normalizing the result changes nothing.
|
||||||
let cases = [
|
let cases = [
|
||||||
"a",
|
"a",
|
||||||
"a b c",
|
"a b c",
|
||||||
|
|
@ -243,7 +243,7 @@ async fn normalize_is_idempotent() {
|
||||||
];
|
];
|
||||||
|
|
||||||
for &input in &cases {
|
for &input in &cases {
|
||||||
// Todos son ASCII, pero se deja este control por si se amplía la lista en el futuro.
|
// All are ASCII, but this check is kept in case the list is expanded in the future.
|
||||||
if !input.is_ascii() {
|
if !input.is_ascii() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue