ferrovue 0.9.0

Vue templates compiled to Rust render functions: server-side rendering with no JavaScript at run time
Documentation
use super::*;
use crate::push_number;

fn shown(x: f64) -> String {
    let mut out = String::new();
    if x == 0.0 && x.is_sign_negative() {
        out.push('-');
    }
    push_number(&mut out, x);
    out
}

fn panics<T>(run: impl FnOnce() -> T + std::panic::UnwindSafe) -> bool {
    let hook = std::panic::take_hook();
    std::panic::set_hook(Box::new(|_| {}));
    let thrown = std::panic::catch_unwind(run).is_err();
    std::panic::set_hook(hook);
    thrown
}

#[test]
fn dates_are_javascripts() {
    let vectors: Vec<(String, String, serde_json::Value)> =
        serde_json::from_str(include_str!("../../tests/vectors/dates.json")).expect("date vectors");
    assert!(vectors.len() >= 4000, "the vectors were not all read");
    let number = |text: &str| -> f64 { text.parse().unwrap_or_else(|_| panic!("{text:?} parses")) };
    let mut accepted = 0;
    let mut refused = 0;
    let mut utc_refused = 0;
    for (op, input, want) in &vectors {
        if op == "parse" {
            if want.is_null() {
                refused += 1;
                assert!(
                    panics(|| js_date_parse(input)),
                    "{input:?} is outside the portable format"
                );
            } else {
                accepted += 1;
                assert_eq!(
                    Some(shown(js_date_parse(input)).as_str()),
                    want.as_str(),
                    "Date.parse({input:?})"
                );
            }
            continue;
        }
        if op == "UTC" {
            let mut parts = [f64::NAN, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0];
            for (part, text) in parts.iter_mut().zip(input.split(',')) {
                *part = number(text);
            }
            let utc = || {
                js_date_utc(
                    parts[0], parts[1], parts[2], parts[3], parts[4], parts[5], parts[6],
                )
            };
            if want.is_null() {
                utc_refused += 1;
                assert!(
                    panics(utc),
                    "Date.UTC({input}) is computed differently by each engine"
                );
            } else {
                assert_eq!(
                    Some(shown(utc()).as_str()),
                    want.as_str(),
                    "Date.UTC({input})"
                );
            }
            continue;
        }
        let t = js_time_clip(number(input));
        let text = match op.as_str() {
            "toUTCString" => Some(js_to_utc_string(t)),
            "toJSON" => js_to_json(t),
            "stringify" => Some(js_json_date(t)),
            _ => None,
        };
        if op == "toJSON" || text.is_some() {
            assert_eq!(text.as_deref(), want.as_str(), "{op}({input})");
            continue;
        }
        if op == "toISOString" {
            if want.get("throws").is_some() {
                assert!(
                    panics(|| js_to_iso_string(t)),
                    "toISOString({input}) throws"
                );
            } else {
                assert_eq!(
                    Some(js_to_iso_string(t).as_str()),
                    want.as_str(),
                    "toISOString({input})"
                );
            }
            continue;
        }
        let got = match op.as_str() {
            "Date" => t,
            "getUTCFullYear" => js_utc_full_year(t),
            "getUTCMonth" => js_utc_month(t),
            "getUTCDate" => js_utc_date(t),
            "getUTCDay" => js_utc_day(t),
            "getUTCHours" => js_utc_hours(t),
            "getUTCMinutes" => js_utc_minutes(t),
            "getUTCSeconds" => js_utc_seconds(t),
            "getUTCMilliseconds" => js_utc_milliseconds(t),
            other => panic!("unknown op {other}"),
        };
        assert_eq!(Some(shown(got).as_str()), want.as_str(), "{op}({input})");
    }
    assert!(accepted >= 1000, "{accepted} portable strings");
    assert!(refused >= 300, "{refused} strings outside the format");
    assert!(utc_refused >= 20, "{utc_refused} parts of Date.UTC refused");
}

#[test]
fn every_date_writes_the_string_it_reads_back_from() {
    for t in [
        -8.64e15,
        -62_198_755_200_001.0,
        -1.0,
        0.0,
        951_782_400_000.0,
        253_402_300_800_000.0,
        8.64e15,
    ] {
        assert_eq!(js_date_parse(&js_to_iso_string(t)), t);
    }
}