[html] Añade soporte para unidades CSS

This commit is contained in:
Manuel Cillero 2025-10-13 13:13:33 +02:00
parent ed22d3d591
commit 3c848cb368
7 changed files with 494 additions and 5 deletions

223
tests/html_unit.rs Normal file
View file

@ -0,0 +1,223 @@
use pagetop::prelude::*;
use std::str::FromStr;
#[pagetop::test]
async fn unit_value_empty_and_auto_and_zero_without_unit() {
assert_eq!(UnitValue::from_str("").unwrap(), UnitValue::None);
assert_eq!(UnitValue::from_str("auto").unwrap(), UnitValue::Auto);
assert_eq!(UnitValue::from_str("AUTO").unwrap(), UnitValue::Auto);
// Cero sin unidad.
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);
}
#[pagetop::test]
async fn unit_value_absolute_integers_with_signs_and_spaces_and_case() {
// Positivos, negativos y con espacios.
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));
assert_eq!(UnitValue::from_str("+9 in").unwrap(), UnitValue::In(9));
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.
assert_eq!(UnitValue::from_str("10PX").unwrap(), UnitValue::Px(10));
assert_eq!(UnitValue::from_str("15Pt").unwrap(), UnitValue::Pt(15));
}
#[pagetop::test]
async fn unit_value_relative_floats_with_signs_and_spaces_and_case() {
assert_eq!(
UnitValue::from_str("1.25rem").unwrap(),
UnitValue::RelRem(1.25)
);
assert_eq!(
UnitValue::from_str("-0.5em").unwrap(),
UnitValue::RelEm(-0.5)
);
assert_eq!(
UnitValue::from_str(" 33% ").unwrap(),
UnitValue::RelPct(33.0)
);
assert_eq!(
UnitValue::from_str(" -12.5 vh").unwrap(),
UnitValue::RelVh(-12.5)
);
assert_eq!(
UnitValue::from_str(" 8.0 VW ").unwrap(),
UnitValue::RelVw(8.0)
);
}
#[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).
assert_eq!(UnitValue::from_str("12 px").unwrap(), UnitValue::Px(12));
assert_eq!(
UnitValue::from_str("1.5 rem").unwrap(),
UnitValue::RelRem(1.5)
);
assert_eq!(
UnitValue::from_str("25 %").unwrap(),
UnitValue::RelPct(25.0)
);
}
#[pagetop::test]
async fn unit_value_roundtrip_display_keeps_expected_format() {
let cases = [
("", UnitValue::None, ""),
("auto", UnitValue::Auto, "auto"),
("0", UnitValue::Zero, "0"),
("12px", UnitValue::Px(12), "12px"),
("-5pt", UnitValue::Pt(-5), "-5pt"),
("7cm", UnitValue::Cm(7), "7cm"),
("33%", UnitValue::RelPct(33.0), "33%"),
("1.25rem", UnitValue::RelRem(1.25), "1.25rem"),
("2em", UnitValue::RelEm(2.0), "2em"),
("-0.5vh", UnitValue::RelVh(-0.5), "-0.5vh"),
("8vw", UnitValue::RelVw(8.0), "8vw"),
];
for (input, expected_value, expected_display) in cases {
let parsed = UnitValue::from_str(input).unwrap();
assert_eq!(
parsed, expected_value,
"parsed mismatch for input `{input}`"
);
assert_eq!(
parsed.to_string(),
expected_display,
"display mismatch for input `{input}`"
);
}
}
#[pagetop::test]
async fn unit_value_percentage_trimming_and_signs() {
assert_eq!(
UnitValue::from_str(" 12.5 % ").unwrap(),
UnitValue::RelPct(12.5)
);
assert_eq!(
UnitValue::from_str("-0.0%").unwrap(),
UnitValue::RelPct(-0.0)
);
assert_eq!(
UnitValue::from_str("+15%").unwrap(),
UnitValue::RelPct(15.0)
);
}
// ERRORES ESPERADOS (no cambiar los mensajes; con is_err() basta).
#[pagetop::test]
async fn unit_value_errors_missing_unit_for_non_zero() {
assert!(
UnitValue::from_str("12").is_err(),
"non-zero without unit must error"
);
assert!(
UnitValue::from_str(" -3 ").is_err(),
"non-zero without unit must error"
);
}
#[pagetop::test]
async fn unit_value_errors_decimals_in_absolute_units() {
assert!(UnitValue::from_str("1.5px").is_err());
assert!(UnitValue::from_str("-2.0pt").is_err());
assert!(UnitValue::from_str("+0.1cm").is_err());
}
#[pagetop::test]
async fn unit_value_errors_unknown_units_or_bad_percentages() {
// Unidad no soportada.
assert!(UnitValue::from_str("10ch").is_err());
assert!(UnitValue::from_str("2q").is_err());
// Falta número.
assert!(UnitValue::from_str("%").is_err());
assert!(UnitValue::from_str(" % ").is_err());
}
#[pagetop::test]
async fn unit_value_errors_non_numeric_numbers() {
assert!(UnitValue::from_str("NaNem").is_err());
// Decimal no permitido por FromStr.
assert!(UnitValue::from_str("1,5rem").is_err());
}
#[pagetop::test]
async fn unit_value_serde_deserialize_struct_and_array() {
use serde::Deserialize;
#[derive(Deserialize, Debug, PartialEq)]
struct BoxStyle {
width: UnitValue,
height: UnitValue,
margin: UnitValue,
}
let json = r#"{ "width": "12px", "height": "1.5rem", "margin": "0" }"#;
let s: BoxStyle = serde_json::from_str(json).unwrap();
assert_eq!(s.width, UnitValue::Px(12));
assert_eq!(s.height, UnitValue::RelRem(1.5));
assert_eq!(s.margin, UnitValue::Zero);
#[derive(Deserialize, Debug, PartialEq)]
struct Many {
values: Vec<UnitValue>,
}
let json_arr = r#"{ "values": ["", "auto", "33%", "8vw", "7 cm", "-5pt"] }"#;
let m: Many = serde_json::from_str(json_arr).unwrap();
assert_eq!(
m.values,
vec![
UnitValue::None,
UnitValue::Auto,
UnitValue::RelPct(33.0),
UnitValue::RelVw(8.0),
UnitValue::Cm(7),
UnitValue::Pt(-5),
]
);
}
#[pagetop::test]
async fn unit_value_accepts_dot5_and_1dot_shorthand_for_relatives() {
// `.5` y `1.` se parsean correctamente en relativas.
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.
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.
let v = UnitValue::RelEm(-0.0);
// Se acepta cualquiera de los dos formatos como válidos.
let s = v.to_string();
assert!(
s == "-0em" || s == "0em",
"current Display prints `{s}` for -0.0; both are acceptable in tests"
);
}
#[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`).
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.
assert!(UnitValue::from_str("1e3vw").is_err());
assert!(UnitValue::from_str("0x10px").is_err());
}