aura-gui 0.2.2

UI nativa acelerada por GPU para escritorio, Android e iOS, en un solo crate: estilo tipo Tailwind, componentes, sombras, animaciones y agentes de IA. Reposo a cero. Licencia AGPL-3.0 o comercial.
use super::*;

fn ms(value: u64) -> Duration {
    Duration::from_millis(value)
}

#[test]
fn progress_reaches_one_and_stops() {
    let mut clock = AnimationClock::default();
    let start = Instant::now();
    let animation = Animation::new(ms(100)).easing(Easing::Linear);
    assert_eq!(clock.progress("fade", animation, start), (0.0, true));
    let (halfway, running) = clock.progress("fade", animation, start + ms(50));
    assert!((halfway - 0.5).abs() < 0.01 && running);
    assert_eq!(clock.progress("fade", animation, start + ms(200)), (1.0, false));
}

#[test]
fn every_curve_starts_at_zero_and_ends_at_one() {
    let curves = [
        Easing::Linear,
        Easing::EaseIn,
        Easing::EaseOut,
        Easing::EaseInOut,
        Easing::STANDARD,
        Easing::SPRING,
        Easing::Spring { bounce: 0.0 },
        Easing::Spring { bounce: 0.6 },
    ];
    for curve in curves {
        assert!(curve.apply(0.0).abs() < 1.0e-4, "{curve:?} empieza en 0");
        assert!((curve.apply(1.0) - 1.0).abs() < 1.0e-4, "{curve:?} termina en 1");
    }
}

#[test]
fn cubic_bezier_matches_css_ease() {
    // `ease` de CSS = cubic-bezier(0.25, 0.1, 0.25, 1); a mitad de tiempo va por ~0.80.
    let ease = Easing::CubicBezier(0.25, 0.1, 0.25, 1.0);
    assert!((ease.apply(0.5) - 0.8024).abs() < 0.005);
    assert!((Easing::CubicBezier(0.0, 0.0, 1.0, 1.0).apply(0.3) - 0.3).abs() < 1.0e-3);
}

#[test]
fn a_bouncy_spring_overshoots_and_settles() {
    let bouncy = Easing::Spring { bounce: 0.5 };
    let peak = (1..100).map(|step| bouncy.apply(step as f32 / 100.0)).fold(0.0, f32::max);
    assert!(peak > 1.05, "rebota por encima del destino");
    assert!((bouncy.apply(0.99) - 1.0).abs() < 0.02, "casi quieto al final");
    let firm = Easing::Spring { bounce: 0.0 };
    assert!((1..100).all(|step| firm.apply(step as f32 / 100.0) <= 1.0));
}

#[test]
fn delay_waits_before_moving() {
    let animation = Animation::new(ms(100)).easing(Easing::Linear).delay(ms(50));
    assert_eq!(animation.sample(ms(30)), (0.0, true));
    let (value, _) = animation.sample(ms(100));
    assert!((value - 0.5).abs() < 0.01);
}

#[test]
fn repeats_and_ping_pong() {
    let twice = Animation::new(ms(100)).easing(Easing::Linear).repeat(Repeat::Times(2));
    let (value, running) = twice.sample(ms(150));
    assert!((value - 0.5).abs() < 0.01 && running);
    assert_eq!(twice.sample(ms(250)), (1.0, false));

    let pulse = Animation::new(ms(100)).easing(Easing::Linear).ping_pong();
    let (back, running) = pulse.sample(ms(175));
    assert!((back - 0.25).abs() < 0.01 && running, "vuelve en el segundo ciclo");
    assert!(pulse.sample(Duration::from_secs(3600)).1, "nunca termina");
}

#[test]
fn transitions_glide_from_where_they_were() {
    let mut clock = AnimationClock::default();
    let start = Instant::now();
    let animation = Animation::new(ms(100)).easing(Easing::Linear);
    assert_eq!(clock.transition("x", 0.0, animation, start), (0.0, false), "la primera vez no anima");
    assert_eq!(clock.transition("x", 10.0, animation, start), (0.0, true));
    let (halfway, running) = clock.transition("x", 10.0, animation, start + ms(50));
    assert!((halfway - 5.0).abs() < 0.01 && running);
    // Cambia de opinión a mitad: sale desde 5, no desde 0 ni desde 10.
    assert_eq!(clock.transition("x", 0.0, animation, start + ms(50)).0, halfway);
    assert_eq!(clock.transition("x", 0.0, animation, start + ms(200)), (0.0, false));
}

#[test]
fn colors_and_geometry_interpolate() {
    let black = Rgba { r: 0.0, g: 0.0, b: 0.0, a: 1.0 };
    let white = Rgba { r: 1.0, g: 1.0, b: 1.0, a: 1.0 };
    assert_eq!(black.lerp(white, 0.5).r, 0.5);
    let from = Point::new(Pixels(0.0), Pixels(10.0));
    assert_eq!(from.lerp(Point::new(Pixels(10.0), Pixels(30.0)), 0.5), Point::new(Pixels(5.0), Pixels(20.0)));
}

#[test]
fn the_wave_stays_between_zero_and_one() {
    let samples: Vec<f32> = (0..100).map(|i| wave(i as f32 * 0.03, 2.0, 0.0)).collect();
    assert!(samples.iter().all(|v| (0.0..=1.0).contains(v)));
    assert!(samples.iter().any(|v| *v > 0.95) && samples.iter().any(|v| *v < 0.05));
    assert!((wave(0.0, 1.0, 0.0) - 0.5).abs() < 1e-6);
}