use super::*;
const SAMPLES: usize = 1024;
const LARGE: f64 = 1e150;
const HUGE: f64 = 1e300;
const NEAR_MAX: f64 = f64::MAX / 4.0;
const BOUNDED_EASINGS: &[AdvancedEasing] = &[
AdvancedEasing::Linear,
AdvancedEasing::InConst,
AdvancedEasing::OutConst,
AdvancedEasing::InOutConst,
AdvancedEasing::InSine,
AdvancedEasing::OutSine,
AdvancedEasing::InOutSine,
AdvancedEasing::InQuad,
AdvancedEasing::OutQuad,
AdvancedEasing::InOutQuad,
AdvancedEasing::InCubic,
AdvancedEasing::OutCubic,
AdvancedEasing::InOutCubic,
AdvancedEasing::InQuart,
AdvancedEasing::OutQuart,
AdvancedEasing::InOutQuart,
AdvancedEasing::InQuint,
AdvancedEasing::OutQuint,
AdvancedEasing::InOutQuint,
AdvancedEasing::InExpo,
AdvancedEasing::OutExpo,
AdvancedEasing::InOutExpo,
AdvancedEasing::InCirc,
AdvancedEasing::OutCirc,
AdvancedEasing::InOutCirc,
AdvancedEasing::InBounce,
AdvancedEasing::OutBounce,
AdvancedEasing::InOutBounce,
];
const OVERSHOOT_EASINGS: &[AdvancedEasing] = &[
AdvancedEasing::InBack,
AdvancedEasing::OutBack,
AdvancedEasing::InOutBack,
AdvancedEasing::InElastic,
AdvancedEasing::OutElastic,
AdvancedEasing::InOutElastic,
];
const NON_CONST_EASINGS: &[AdvancedEasing] = &[
AdvancedEasing::Linear,
AdvancedEasing::InSine,
AdvancedEasing::OutSine,
AdvancedEasing::InOutSine,
AdvancedEasing::InQuad,
AdvancedEasing::OutQuad,
AdvancedEasing::InOutQuad,
AdvancedEasing::InCubic,
AdvancedEasing::OutCubic,
AdvancedEasing::InOutCubic,
AdvancedEasing::InQuart,
AdvancedEasing::OutQuart,
AdvancedEasing::InOutQuart,
AdvancedEasing::InQuint,
AdvancedEasing::OutQuint,
AdvancedEasing::InOutQuint,
AdvancedEasing::InExpo,
AdvancedEasing::OutExpo,
AdvancedEasing::InOutExpo,
AdvancedEasing::InCirc,
AdvancedEasing::OutCirc,
AdvancedEasing::InOutCirc,
AdvancedEasing::InBack,
AdvancedEasing::OutBack,
AdvancedEasing::InOutBack,
AdvancedEasing::InElastic,
AdvancedEasing::OutElastic,
AdvancedEasing::InOutElastic,
AdvancedEasing::InBounce,
AdvancedEasing::OutBounce,
AdvancedEasing::InOutBounce,
];
#[test]
fn ease_extreme_endpoints_finite() {
for &ease in AdvancedEasing::ALL {
let r = ease.ease::<f64, 1>(-HUGE, HUGE, 0.5);
assert!(r.is_finite(), "{ease:?}: ease(-HUGE, HUGE, 0.5) = {r}");
}
}
#[test]
fn ease_near_max_endpoints_finite() {
for &ease in AdvancedEasing::ALL {
let r = ease.ease::<f64, 1>(NEAR_MAX, NEAR_MAX, 0.5);
assert!(
r.is_finite(),
"{ease:?}: ease(NEAR_MAX, NEAR_MAX, 0.5) = {r}"
);
}
}
#[test]
fn ease_t_out_of_range_clamps() {
for &ease in AdvancedEasing::ALL {
let r_neg = ease.ease::<f64, 1>(1.0, 100.0, -100.0);
assert_eq!(r_neg, 1.0, "{ease:?}: t<0 should return from");
let r_over = ease.ease::<f64, 1>(1.0, 100.0, 100.0);
assert_eq!(r_over, 100.0, "{ease:?}: t>1 should return to");
}
}
#[test]
fn ease_nan_propagates() {
for &ease in AdvancedEasing::ALL {
let r = ease.ease::<f64, 1>(f64::NAN, 100.0, 0.5);
assert!(r.is_nan(), "{ease:?}: NaN from should give NaN, got {r}");
}
}
#[test]
fn derivative_extreme_endpoints_finite() {
for &ease in NON_CONST_EASINGS {
let r = ease.derivative::<f64, 1>(-LARGE, LARGE, 0.37);
assert!(r.is_finite(), "{ease:?}: deriv(-LARGE, LARGE, 0.37) = {r}");
}
}
#[test]
fn derivative_outside_range_is_zero() {
for &ease in AdvancedEasing::ALL {
let r = ease.derivative::<f64, 1>(1.0, 100.0, -1.0);
assert_eq!(r, 0.0, "{ease:?}: t<0 derivative should be 0");
let r = ease.derivative::<f64, 1>(1.0, 100.0, 2.0);
assert_eq!(r, 0.0, "{ease:?}: t>1 derivative should be 0");
}
}
#[test]
fn derivative_nan_propagates() {
for &ease in AdvancedEasing::ALL {
let r = ease.derivative::<f64, 1>(f64::NAN, 100.0, 0.37);
assert!(r.is_nan(), "{ease:?}: NaN from should give NaN, got {r}");
}
}
#[test]
fn integrate_in_unit_range_finite() {
for &ease in AdvancedEasing::ALL {
for &t in &[0.0, 0.1, 0.5, 0.9, 1.0] {
let r = ease.integrate::<f64, 1>(-HUGE, HUGE, t);
assert!(r.is_finite(), "{ease:?}: integ(-HUGE, HUGE, {t}) = {r}");
}
}
}
#[test]
fn integrate_extreme_same_sign_finite() {
for &ease in AdvancedEasing::ALL {
let r = ease.integrate::<f64, 1>(HUGE, HUGE, 0.5);
assert!(r.is_finite(), "{ease:?}: integ(HUGE, HUGE, 0.5) = {r}");
}
}
#[test]
fn integrate_negative_t_finite() {
for &ease in AdvancedEasing::ALL {
let r = ease.integrate::<f64, 1>(1.0, 100.0, -1e10);
assert!(r.is_finite(), "{ease:?}: integ(1, 100, -1e10) = {r}");
}
}
#[test]
fn integrate_t_above_1_finite_for_moderate_t() {
for &ease in AdvancedEasing::ALL {
let r = ease.integrate::<f64, 1>(1.0, 100.0, 2.0);
assert!(r.is_finite(), "{ease:?}: integ(1, 100, 2.0) = {r}");
}
}
#[test]
fn integrate_nan_propagates() {
for &ease in AdvancedEasing::ALL {
let r = ease.integrate::<f64, 1>(f64::NAN, 100.0, 0.5);
assert!(r.is_nan(), "{ease:?}: NaN from should give NaN, got {r}");
}
}
#[test]
fn reciprocal_same_sign_positive_finite() {
for &ease in BOUNDED_EASINGS {
let r = ease.integrate_reciprocal::<f64, 1>(1.0, 100.0, 0.5, SAMPLES);
assert!(
r.is_some_and(|v| v.is_finite()),
"{ease:?}: recip(1, 100, 0.5) should be finite, got {r:?}"
);
}
}
#[test]
fn reciprocal_same_sign_negative_finite() {
for &ease in BOUNDED_EASINGS {
let r = ease.integrate_reciprocal::<f64, 1>(-100.0, -1.0, 0.5, SAMPLES);
assert!(
r.is_some_and(|v| v.is_finite()),
"{ease:?}: recip(-100, -1, 0.5) should be finite, got {r:?}"
);
}
}
#[test]
fn reciprocal_extreme_ratio_finite() {
for &ease in BOUNDED_EASINGS {
if matches!(
ease,
AdvancedEasing::InConst | AdvancedEasing::OutConst | AdvancedEasing::InOutConst
) {
continue;
}
let r = ease.integrate_reciprocal::<f64, 1>(1e-10, 1e10, 0.5, SAMPLES);
assert!(
r.is_some_and(|v| v.is_finite()),
"{ease:?}: recip(1e-10, 1e10, 0.5) should be finite, got {r:?}"
);
}
}
#[test]
fn reciprocal_huge_endpoints_finite() {
for &ease in AdvancedEasing::ALL {
let r = ease.integrate_reciprocal::<f64, 1>(HUGE, HUGE, 0.5, SAMPLES);
assert!(
r.is_some_and(|v| v.is_finite()),
"{ease:?}: recip(HUGE, HUGE, 0.5) should be finite, got {r:?}"
);
}
}
#[test]
fn reciprocal_zero_endpoints_return_none() {
for &ease in AdvancedEasing::ALL {
if matches!(ease, AdvancedEasing::OutConst) {
continue;
}
let r = ease.integrate_reciprocal::<f64, 1>(0.0, 100.0, 0.5, SAMPLES);
assert!(
r.is_none(),
"{ease:?}: recip(0, 100, 0.5) should be None (pole), got {r:?}"
);
}
}
#[test]
fn reciprocal_opposite_sign_return_none() {
for &ease in AdvancedEasing::ALL {
if matches!(
ease,
AdvancedEasing::InConst | AdvancedEasing::OutConst | AdvancedEasing::InOutConst
) {
continue;
}
let r = ease.integrate_reciprocal::<f64, 1>(-1.0, 1.0, 0.5, SAMPLES);
assert!(
r.is_none(),
"{ease:?}: recip(-1, 1, 0.5) should be None (sign flip), got {r:?}"
);
}
}
#[test]
fn reciprocal_t0_returns_zero() {
for &ease in AdvancedEasing::ALL {
let r = ease.integrate_reciprocal::<f64, 1>(1.0, 100.0, 0.0, SAMPLES);
assert_eq!(
r,
Some(0.0),
"{ease:?}: recip(1, 100, 0.0) should be 0, got {r:?}"
);
}
}
#[test]
fn reciprocal_negative_t_finite() {
for &ease in AdvancedEasing::ALL {
let r = ease.integrate_reciprocal::<f64, 1>(1.0, 100.0, -1.0, SAMPLES);
assert!(
r.is_some_and(|v| v.is_finite()),
"{ease:?}: recip(1, 100, -1.0) should be finite, got {r:?}"
);
}
}
#[test]
fn reciprocal_t_above_1_finite() {
for &ease in BOUNDED_EASINGS {
let r = ease.integrate_reciprocal::<f64, 1>(1.0, 100.0, 1.5, SAMPLES);
assert!(
r.is_some_and(|v| v.is_finite()),
"{ease:?}: recip(1, 100, 1.5) should be finite, got {r:?}"
);
}
}
#[test]
fn reciprocal_from_equals_to_finite() {
for &ease in AdvancedEasing::ALL {
let r = ease.integrate_reciprocal::<f64, 1>(50.0, 50.0, 0.5, SAMPLES);
assert!(
r.is_some_and(|v| v.is_finite()),
"{ease:?}: recip(50, 50, 0.5) should be finite, got {r:?}"
);
}
}
#[test]
fn reciprocal_overshoot_safe_when_e_crit_outside_range() {
let r = AdvancedEasing::InBack.integrate_reciprocal::<f64, 1>(100.0, 200.0, 0.5, SAMPLES);
assert!(
r.is_some_and(|v| v.is_finite()),
"InBack safe overshoot should be finite, got {r:?}"
);
}
#[test]
fn reciprocal_overshoot_pole_when_e_crit_inside_range() {
let r = AdvancedEasing::InBack.integrate_reciprocal::<f64, 1>(1.0, 12.0, 0.5, SAMPLES);
assert!(
r.is_none(),
"InBack pole overshoot should be None, got {r:?}"
);
}
#[test]
fn reciprocal_overshoot_all_variants_safe_ratio() {
for &ease in OVERSHOOT_EASINGS {
let r = ease.integrate_reciprocal::<f64, 1>(100.0, 101.0, 0.5, SAMPLES);
assert!(
r.is_some_and(|v| v.is_finite()),
"{ease:?}: safe ratio should be finite, got {r:?}"
);
}
}
#[test]
fn reciprocal_out_const_zero_from_finite() {
let r = AdvancedEasing::OutConst.integrate_reciprocal::<f64, 1>(0.0, 100.0, 0.5, SAMPLES);
assert!(
r.is_some_and(|v| v.is_finite()),
"OutConst recip(0, 100, 0.5) should be finite, got {r:?}"
);
}
#[test]
fn reciprocal_nan_returns_none() {
for &ease in AdvancedEasing::ALL {
let r = ease.integrate_reciprocal::<f64, 1>(f64::NAN, 100.0, 0.5, SAMPLES);
assert!(
r.is_none(),
"{ease:?}: NaN from should give None, got {r:?}"
);
}
}
#[test]
fn solve_ease_extreme_val_clamps() {
for &ease in Easing::ALL {
let from: f64 = 10.0;
let to: f64 = 90.0;
let r_below = ease.solve_ease::<f64, 1>(from, to, f64::MIN);
assert!(
r_below == 0.0,
"{ease:?}: solve_ease(MIN) should clamp to 0, got {r_below}"
);
let r_above = ease.solve_ease::<f64, 1>(from, to, f64::MAX);
assert!(
r_above == 1.0,
"{ease:?}: solve_ease(MAX) should clamp to 1, got {r_above}"
);
}
}
#[test]
fn solve_ease_from_equals_to_returns_zero() {
for &ease in Easing::ALL {
let r = ease.solve_ease::<f64, 1>(50.0, 50.0, 50.0);
assert_eq!(
r, 0.0,
"{ease:?}: solve_ease(from==to) should be 0, got {r}"
);
}
}
#[test]
fn solve_ease_huge_endpoints_finite() {
for &ease in Easing::ALL {
let mid = HUGE + 1.0;
let r = ease.solve_ease::<f64, 1>(HUGE, HUGE + 2.0, mid);
assert!(
r.is_finite() && (0.0..=1.0).contains(&r),
"{ease:?}: solve_ease(HUGE, HUGE+2, mid) = {r}, should be in [0,1]"
);
}
}
#[test]
fn solve_ease_nan_propagates() {
for &ease in Easing::ALL {
let r = ease.solve_ease::<f64, 1>(f64::NAN, 100.0, 50.0);
assert!(r.is_nan(), "{ease:?}: NaN from should give NaN, got {r}");
}
}
#[test]
fn solve_integrate_sign_flip_returns_none() {
for &ease in Easing::ALL {
let r = ease.solve_integrate::<f64, 1>(-1.0, 1.0, 0.5);
assert!(
r.is_none(),
"{ease:?}: solve_integrate(-1, 1, 0.5) should be None (sign flip), got {r:?}"
);
}
}
#[test]
fn solve_integrate_extreme_val_clamps() {
for &ease in Easing::ALL {
let from: f64 = 10.0;
let to: f64 = 90.0;
let r_below = ease.solve_integrate::<f64, 1>(from, to, f64::MIN);
assert!(
r_below == Some(0.0),
"{ease:?}: solve_integrate(MIN) should clamp to 0, got {r_below:?}"
);
let r_above = ease.solve_integrate::<f64, 1>(from, to, f64::MAX);
assert!(
r_above == Some(1.0),
"{ease:?}: solve_integrate(MAX) should clamp to 1, got {r_above:?}"
);
}
}
#[test]
fn solve_integrate_from_equals_to_finite() {
for &ease in Easing::ALL {
let r = ease.solve_integrate::<f64, 1>(50.0, 50.0, 25.0).unwrap();
assert!(
(0.0..=1.0).contains(&r),
"{ease:?}: solve_integrate(50, 50, 25) = {r}, should be in [0,1]"
);
}
}
#[test]
fn solve_integrate_huge_endpoints_finite() {
for &ease in Easing::ALL {
let r = ease
.solve_integrate::<f64, 1>(HUGE, HUGE + 1.0, HUGE)
.unwrap();
assert!(
(0.0..=1.0).contains(&r),
"{ease:?}: solve_integrate(HUGE, HUGE+1, HUGE) = {r}, should be in [0,1]"
);
}
}
#[test]
fn solve_integrate_nan_returns_none() {
for &ease in Easing::ALL {
let r = ease.solve_integrate::<f64, 1>(f64::NAN, 100.0, 50.0);
assert!(
r.is_none(),
"{ease:?}: NaN from should give None, got {r:?}"
);
}
}
#[test]
fn solve_integrate_output_in_unit_range() {
for &ease in Easing::ALL {
let from: f64 = 1.0;
let to: f64 = 100.0;
for &t in &[0.0, 0.1, 0.25, 0.5, 0.75, 0.9, 1.0] {
let val = ease.integrate::<f64, 1>(from, to, t);
let solved = ease.solve_integrate::<f64, 1>(from, to, val).unwrap();
assert!(
(0.0..=1.0).contains(&solved),
"{ease:?}: solve_integrate(t={t}) = {solved}, should be in [0,1]"
);
}
}
}