🌐 (tests): Traduce comentarios de tests a inglés

This commit is contained in:
Manuel Cillero 2026-07-19 23:34:38 +02:00
parent af548b03c9
commit 55159f6d8f
10 changed files with 90 additions and 91 deletions

View file

@ -1,9 +1,9 @@
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
// `Children` basadas en identificador (`InsertAfterId`, `RemoveById`, etc.).
// Component with configurable id and fixed output text. The id allows testing the identifier-based
// `Children` operations (`InsertAfterId`, `RemoveById`, etc.).
#[derive(AutoDefault, Clone)]
struct TestComp {
@ -27,7 +27,7 @@ impl Component for 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 {
let mut c = Self::default();
c.props.alter_prop(PropsOp::set_id(id.to_string()));
@ -35,7 +35,7 @@ impl TestComp {
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 {
let mut c = Self::default();
c.text = text.to_string();
@ -43,7 +43,7 @@ impl TestComp {
}
}
// **< Child >***************************************************************************************
// **< Child >**************************************************************************************
#[pagetop::test]
async fn child_default_is_empty() {
@ -78,7 +78,7 @@ async fn child_from_component_is_equivalent_to_with() {
#[pagetop::test]
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 clone = original.clone();
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() {
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()));
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()));
assert_eq!(c.len(), 1);
assert_eq!(c.render(&mut cx).await.into_string(), "first");
@ -297,7 +297,7 @@ async fn embed_id_returns_component_id() {
#[pagetop::test]
async fn embed_get_is_some_when_component_present() {
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_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() {
let original = Embed::with(TestComp::tagged("orig", "text"));
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() {
comp.props
.alter_prop(PropsOp::set_id("clone-id".to_string()));

View file

@ -1,9 +1,9 @@
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
/// que sólo necesitan el subsistema de localización y las extensiones registradas, no un router.
/// The tests in this module render components directly with `Context::default()`, so they only need
/// the localization subsystem and the registered extensions, not a router.
async fn setup() {
Application::new().await;
}
@ -15,10 +15,10 @@ async fn poweredby_default_shows_only_pagetop_recognition() {
let mut p = PoweredBy::default();
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"));
// Y NO debe mostrar el bloque de copyright.
// And should NOT show the copyright block.
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();
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;
assert!(
html.contains(app_name),
"HTML should include the application name"
);
// Debe existir el span de copyright.
// The copyright span must exist.
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();
assert!(!html.contains("poweredby__copyright"));
// El reconocimiento a PageTop siempre debe aparecer.
// The PageTop acknowledgment must always appear.
assert!(html.contains("poweredby__pagetop"));
}
@ -84,11 +84,11 @@ async fn poweredby_link_points_to_crates_io() {
async fn poweredby_getter_reflects_internal_state() {
setup().await;
// Por defecto no hay copyright.
// There is no copyright by default.
let p0 = PoweredBy::default();
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 c1 = p1.copyright().expect("Expected copyright to exist");
assert!(c1.contains(&Utc::now().format("%Y").to_string()));

View file

@ -20,9 +20,9 @@ pub struct Test {
pub float_value: f32,
}
// La *feature* `testing` (activo con `cargo ts` / `cargo tw`) fija el modo "test" en tiempo de
// compilación dentro de `config::CONFIG_VALUES`, de forma que `global::SETTINGS` y cualquier
// `include_config!` local cargan automáticamente la configuración del modo "test".
// The `testing` *feature* (active with `cargo ts` / `cargo tw`) fixes the "test" mode at compile
// time inside `config::CONFIG_VALUES`, so that `global::SETTINGS` and any local `include_config!`
// automatically load the "test" mode configuration.
#[pagetop::test]
async fn check_global_config() {

View file

@ -34,10 +34,9 @@ async fn panic_in_handler_returns_minimal_500_page_instead_of_crashing() {
// **< ErrorPage::NotFound >************************************************************************
// `EXTENSIONS` es un `OnceLock` global (`core/extension/all.rs`): se inicializa una sola vez por
// binario de test. Todos los tests de este fichero comparten la misma extensión raíz
// (`PanicExtension`) para que el orden de ejecución en paralelo no cambie qué rutas quedan
// registradas.
// `EXTENSIONS` is a global `OnceLock` (`core/extension/all.rs`): it is initialized only once per
// test binary. All tests in this file share the same root extension (`PanicExtension`) so that the
// parallel execution order does not change which routes end up registered.
#[pagetop::test]
async fn unknown_route_returns_themed_404_page() {
let app = web::test::init_router(Application::prepare(&PanicExtension).await.test());

View file

@ -1,8 +1,8 @@
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).
/// 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,
@ -23,7 +23,7 @@ impl Component for TestMarkupComponent {
}
}
// **< Comportamiento de Markup >*******************************************************************
// **< Markup behavior >****************************************************************************
#[pagetop::test]
async fn string_in_html_macro_escapes_html_entities() {
@ -39,11 +39,11 @@ async fn preescaped_in_html_macro_is_inserted_verbatim() {
#[pagetop::test]
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!") };
assert_eq!(esc.into_string(), "Hello, tomorrow coffee ☕ &amp; donuts!");
// PreEscaped debe pasar íntegro.
// PreEscaped must pass through untouched.
let raw = html! { (PreEscaped("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());
// Espacios NO se consideran vacíos.
// Spaces are NOT considered empty.
assert!(!html! { (" ") }.is_empty());
assert!(!html! { (PreEscaped(" ")) }.is_empty());
}
// **< Markup a través del ciclo de componente >****************************************************
// **< Markup through the component cycle >*********************************************************
#[pagetop::test]
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 {
// Vía 1: renderizamos a través del ciclo de componente.
// Path 1: we render through the component cycle.
let via_component = {
let mut cx = Context::default();
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()
};
// 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();
assert_eq!(

View file

@ -42,7 +42,7 @@ async fn props_set_replaces_existing_value() {
#[pagetop::test]
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"));
assert_eq!(
html! { span (p) {} }.into_string(),
@ -109,7 +109,7 @@ async fn props_escapes_double_quotes_in_value() {
#[pagetop::test]
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();
assert_eq!(
html! { button (p) { "x" } }.into_string(),
@ -139,7 +139,7 @@ async fn props_multiple_attrs_preserve_order_in_html_macro() {
#[pagetop::test]
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");
assert_eq!(
html! { button #mybtn .btn (p) { "Go" } }.into_string(),
@ -189,7 +189,7 @@ async fn props_conditional_expression_in_html_macro() {
#[pagetop::test]
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>");
}
@ -254,7 +254,7 @@ async fn get_prop_id_matches_get_id() {
#[pagetop::test]
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");
assert_eq!(
html! { button (p) {} }.into_string(),
@ -289,7 +289,7 @@ async fn props_chained_set_and_remove_yields_expected_state() {
#[pagetop::test]
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");
assert_eq!(
html! { span (p) {} }.into_string(),

View file

@ -55,9 +55,9 @@ async fn add_style_value_preserves_case_and_non_ascii() {
#[pagetop::test]
async fn add_style_value_may_contain_semicolons() {
// A diferencia de PropsOp::Set("style", ...), que interpreta la cadena como declaraciones
// separadas por ";", AddStyle recibe la propiedad y el valor ya separados, así que un ";"
// dentro del valor (p. ej. una data URI) no supone ningún problema.
// Unlike PropsOp::Set("style", ...), which interprets the string as declarations separated by
// ";", AddStyle receives the property and the value already separated, so a ";" inside the
// value (e.g. a data URI) is not a problem.
let p = Props::default().with_prop(PropsOp::add_style(
"background",
"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"));
}
// Límite conocido de PropsOp::Set("style", ...): no es un análisis CSS completo. Unos paréntesis
// sin cerrar arrastran el resto de la cadena a la misma declaración. Este test fija el
// comportamiento actual para que un cambio futuro sea deliberado, no accidental.
// Known limitation of PropsOp::Set("style", ...): it is not a full CSS parser. Unclosed parentheses
// drag the rest of the string into the same declaration. This test pins the current behavior so
// that a future change is deliberate, not accidental.
#[pagetop::test]
async fn styles_reset_unbalanced_parens_swallows_rest_of_string() {
let p = Props::default().with_prop(PropsOp::set(

View file

@ -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);
// 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);
@ -16,7 +16,7 @@ async fn unit_value_empty_and_auto_and_zero_without_unit() {
#[pagetop::test]
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("-5pt").unwrap(), UnitValue::Pt(-5));
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("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("15Pt").unwrap(), UnitValue::Pt(15));
}
@ -55,7 +55,7 @@ async fn unit_value_relative_floats_with_signs_and_spaces_and_case() {
#[pagetop::test]
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("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]
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]
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("2q").is_err());
// Falta número.
// Missing number.
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]
async fn unit_value_errors_non_numeric_numbers() {
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());
}
@ -190,22 +190,22 @@ async fn unit_value_serde_deserialize_struct_and_array() {
#[pagetop::test]
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("1.rem").unwrap(),
UnitValue::RelRem(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());
}
#[pagetop::test]
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);
// Se acepta cualquiera de los dos formatos como válidos.
// Either of the two formats is accepted as valid.
let s = v.to_string();
assert!(
s == "-0em" || s == "0em",
@ -215,9 +215,9 @@ async fn unit_value_display_keeps_minus_zero_for_relatives() {
#[pagetop::test]
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));
// 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("0x10px").is_err());
}

View file

@ -1,6 +1,6 @@
use pagetop::prelude::*;
// **< Tema con plantilla propia >******************************************************************
// **< Theme with its own template >****************************************************************
struct MarkerTemplate;
@ -36,19 +36,19 @@ async fn render_active_template(cx: &mut Context) -> String {
template.render(cx).await.into_string()
}
// **< Context::template() sigue al tema activo >***************************************************
// **< Context::template() follows the active theme >***********************************************
#[pagetop::test]
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);
assert_ne!(
render_active_template(&mut cx).await,
"marker-template-output"
);
// Tras cambiar de tema con `with_theme()`, la plantilla activa pasa a ser la de ese tema, sin
// necesidad de llamar a `with_template()` explícitamente.
// After changing theme with `with_theme()`, the active template becomes that theme's, with no
// need to call `with_template()` explicitly.
let mut cx = Context::new(None).with_theme(&MarkerTheme);
assert_eq!(
render_active_template(&mut cx).await,
@ -58,8 +58,8 @@ async fn with_theme_updates_the_effective_template() {
#[pagetop::test]
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
// llame después, en cualquier orden.
// A template explicitly set with `with_template()` prevails even if `with_theme()` is called
// afterwards, regardless of order.
let mut cx = Context::new(None)
.with_template(&MarkerTemplate)
.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]
async fn page_admin_template_follows_a_later_with_theme() {

View file

@ -45,15 +45,15 @@ fn assert_owned(input: &str, expected: &str) {
#[pagetop::test]
async fn normalize_errors() {
// Caso especial: cadena vacía.
// Special case: empty string.
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 "] {
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 [
"©",
"á",
@ -70,7 +70,7 @@ async fn normalize_errors() {
#[pagetop::test]
async fn normalize_borrowed_trim_and_already_normalized() {
// Sólo recorte (incluyendo separadores al final).
// Trimming only (including trailing separators).
for (input, expected) in [
(" a", "a"),
("a ", "a"),
@ -89,7 +89,7 @@ async fn normalize_borrowed_trim_and_already_normalized() {
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 [
"a",
"a b",
@ -107,12 +107,12 @@ async fn normalize_borrowed_trim_and_already_normalized() {
"path/to/resource",
"foo+bar=baz",
"a-._:/+=",
"a\x1Bb", // Byte de control ASCII: se conserva tal cual.
"a\x1Bb", // ASCII control byte: preserved as-is.
] {
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 [
("foo bar\t", "foo bar"),
("foo bar\r\n", "foo bar"),
@ -124,7 +124,7 @@ async fn normalize_borrowed_trim_and_already_normalized() {
#[pagetop::test]
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 [
("A", "a"),
("Foo", "foo"),
@ -141,7 +141,7 @@ async fn normalize_owned_due_to_uppercase() {
("ETag:W/\"XYZ\"", "etag:w/\"xyz\""),
("Foo+Bar=Baz", "foo+bar=baz"),
("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);
}
@ -149,12 +149,12 @@ async fn normalize_owned_due_to_uppercase() {
#[pagetop::test]
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")] {
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 [
("a\tb", "a b"),
("a\nb", "a b"),
@ -168,7 +168,7 @@ async fn normalize_owned_due_to_internal_whitespace() {
assert_owned(input, expected);
}
// Mezclas de separadores.
// Mixed separators.
for (input, expected) in [
("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);
}
// 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 [
(" a b ", "a b"),
(" a\tb ", "a b"),
@ -192,7 +192,7 @@ async fn normalize_owned_due_to_internal_whitespace() {
#[pagetop::test]
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 [
(" Foo BAR\tbaz ", "foo bar baz"),
("\nFOO\rbar\tBAZ\n", "foo bar baz"),
@ -209,17 +209,17 @@ async fn normalize_owned_due_to_mixed_causes() {
#[pagetop::test]
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")] {
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"] {
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")] {
assert_borrowed(input, expected);
}
@ -227,7 +227,7 @@ async fn normalize_borrowed_vs_owned_edge_cases() {
#[pagetop::test]
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 = [
"a",
"a b c",
@ -243,7 +243,7 @@ async fn normalize_is_idempotent() {
];
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() {
continue;
}