use super::*;
use crate::{Point, TimeLiteral};
#[test]
fn delay_and_speed_preserve_boundaries_and_collapsed_durations() {
let tween = Tween::new(0.0_f64, 10.0, Duration::from_ticks(10));
let delayed = tween.clone().delay(Duration::from_ticks(5));
assert_eq!(delayed.duration(), Duration::from_ticks(15));
for (ticks, expected) in [
(i64::MIN, 0.0),
(5, 0.0),
(10, 5.0),
(15, 10.0),
(i64::MAX, 10.0),
] {
assert_eq!(delayed.sample(Time::from_ticks(ticks)), expected);
}
let faster = tween.clone().speed(2.0);
assert_eq!(faster.duration(), Duration::from_ticks(5));
assert_eq!(faster.sample(Time::from_ticks(2)), 4.0);
assert_eq!(faster.sample(Time::from_ticks(5)), 10.0);
assert_eq!(faster.sample(Time::from_ticks(i64::MIN)), 0.0);
let collapsed = tween.speed(100.0);
assert_eq!(collapsed.duration(), Duration::ZERO);
assert_eq!(collapsed.sample(BEFORE_START), 0.0);
assert_eq!(collapsed.sample(Time::ZERO), 10.0);
let maximum = Tween::new(0.0_f64, 1.0, Duration::from_ticks(i64::MAX as u64)).speed(2.0);
let near_end = maximum.duration() - Duration::from_ticks(1);
let value = maximum.sample(near_end.after_origin());
assert!(value.is_finite() && (0.0..=1.0).contains(&value));
}
#[test]
fn finite_combinators_use_exact_integer_boundaries_and_arbitrary_order() {
let tween = Tween::new(0.0_f64, 10.0, Duration::from_ticks(10));
let sequence = tween
.clone()
.then(Tween::new(100.0_f64, 110.0, Duration::from_ticks(5)));
assert_eq!(sequence.duration(), Duration::from_ticks(15));
for (ticks, expected) in [
(15, 110.0),
(0, 0.0),
(10, 100.0),
(12, 104.0),
(5, 5.0),
(i64::MIN, 0.0),
(i64::MAX, 110.0),
] {
assert_eq!(sequence.sample(Time::from_ticks(ticks)), expected);
}
let reversed = tween.clone().reverse();
assert_eq!(reversed.duration(), Duration::from_ticks(10));
for (ticks, expected) in [
(10, 0.0),
(0, 10.0),
(5, 5.0),
(i64::MIN, 10.0),
(i64::MAX, 0.0),
] {
assert_eq!(reversed.sample(Time::from_ticks(ticks)), expected);
}
let repeated = tween.clone().repeat(3);
assert_eq!(repeated.duration(), Duration::from_ticks(30));
for (ticks, expected) in [
(30, 10.0),
(0, 0.0),
(10, 0.0),
(20, 0.0),
(29, 9.0),
(i64::MIN, 0.0),
(i64::MAX, 10.0),
] {
assert_eq!(repeated.sample(Time::from_ticks(ticks)), expected);
}
let round_trip = tween.clone().ping_pong();
assert_eq!(round_trip.duration(), Duration::from_ticks(20));
for (ticks, expected) in [
(20, 0.0),
(0, 0.0),
(10, 10.0),
(15, 5.0),
(i64::MIN, 0.0),
(i64::MAX, 0.0),
] {
assert_eq!(round_trip.sample(Time::from_ticks(ticks)), expected);
}
assert_eq!(tween.repeat(0).sample(Time::from_ticks(i64::MAX)), 0.0);
let composed = round_trip
.repeat(2)
.speed(2.0)
.delay(Duration::from_ticks(5));
assert_eq!(composed.duration(), Duration::from_ticks(25));
assert_eq!(composed.sample(Time::from_ticks(10)), 10.0);
assert_eq!(composed.sample(Time::from_ticks(15)), 0.0);
}
#[test]
fn instantaneous_sources_keep_distinct_before_and_after_values() {
let instant = Tween::new(2.0_f64, 7.0, Duration::ZERO);
let delayed = instant.clone().delay(Duration::from_ticks(5));
assert_eq!(delayed.sample(Time::from_ticks(4)), 2.0);
assert_eq!(delayed.sample(Time::from_ticks(5)), 7.0);
let repeated = instant.clone().repeat(3);
assert_eq!(repeated.sample(BEFORE_START), 2.0);
assert_eq!(repeated.sample(Time::ZERO), 7.0);
assert_eq!(instant.clone().repeat(0).sample(Time::ZERO), 2.0);
assert_eq!(instant.clone().speed(2.0).sample(BEFORE_START), 2.0);
assert_eq!(instant.clone().reverse().sample(BEFORE_START), 7.0);
assert_eq!(instant.clone().reverse().sample(Time::ZERO), 2.0);
assert_eq!(instant.clone().ping_pong().sample(Time::ZERO), 2.0);
let sequence = instant.then(Tween::new(11.0_f64, 13.0, Duration::from_ticks(2)));
assert_eq!(sequence.sample(BEFORE_START), 2.0);
assert_eq!(sequence.sample(Time::ZERO), 11.0);
}
#[test]
fn invalid_compositions_and_duration_overflows_fail_at_construction() {
fn tween() -> Tween<f64> {
Tween::new(0.0_f64, 1.0, 1.seconds())
}
for factor in [0.0, -1.0, f64::NAN, f64::INFINITY] {
assert!(std::panic::catch_unwind(|| tween().speed(factor)).is_err());
}
fn maximum() -> Tween<f64> {
Tween::new(0.0_f64, 1.0, Duration::from_ticks(i64::MAX as u64))
}
assert_eq!(
maximum().repeat(1).duration(),
Duration::from_ticks(i64::MAX as u64)
);
assert_eq!(
maximum().speed(1.0).duration(),
Duration::from_ticks(i64::MAX as u64)
);
assert!(std::panic::catch_unwind(|| maximum().repeat(2)).is_err());
assert!(std::panic::catch_unwind(|| maximum().ping_pong()).is_err());
assert!(std::panic::catch_unwind(|| maximum().delay(Duration::from_ticks(1))).is_err());
assert!(std::panic::catch_unwind(|| maximum().then(tween())).is_err());
assert!(std::panic::catch_unwind(|| maximum().speed(0.5)).is_err());
}
#[test]
fn tween_preserves_tick_progress_signed_endpoints_and_overshoot() {
let tween = Tween::new(0.0_f64, 1.0, Duration::from_ticks(10_000_000_000));
let progress = 0.500_000_000_1;
assert_eq!(tween.sample(Time::from_ticks(5_000_000_001)), progress);
assert_ne!(progress, f64::from(progress as f32));
for (ticks, expected) in [
(i64::MIN, 0.0),
(0, 0.0),
(10_000_000_000, 1.0),
(i64::MAX, 1.0),
] {
assert_eq!(tween.sample(Time::from_ticks(ticks)), expected);
}
let instant = Tween::new(2.0_f64, 7.0, Duration::ZERO);
assert_eq!(instant.sample(Time::from_ticks(-1)), 2.0);
assert_eq!(instant.sample(Time::ZERO), 7.0);
let overshooting = Tween::new(0.0_f64, 1.0, 1.seconds())
.easing(Easing::cubic_bezier(0.3, 1.8, 0.7, 1.8).unwrap());
assert!(overshooting.sample(500.milliseconds().after_origin()) > 1.0);
assert_eq!(overshooting.sample(1.seconds().after_origin()), 1.0);
}
#[test]
fn map_zip_closures_and_type_erasure_preserve_timing_and_thread_safety() {
let tween = Tween::new(0.0_f64, 8.0, 2.seconds());
let mapped = tween
.clone()
.map(|value| Point::new(value, value * 2.0))
.boxed_finite();
let zipped = mapped.clone().zip(Tween::new(0.0_f64, 3.0, 3.seconds()));
assert_eq!(zipped.duration(), 3.seconds());
assert_eq!(
zipped.sample(1.seconds().after_origin()),
(Point::new(4.0, 8.0), 1.0)
);
assert_eq!(
zipped.sample(4.seconds().after_origin()),
(Point::new(8.0, 16.0), 3.0)
);
let signal = (|time: Time| time.ticks())
.map(|ticks| ticks.to_string())
.boxed();
assert_eq!(signal.sample(Time::from_ticks(-17)), "-17");
assert_eq!(
std::thread::spawn(move || mapped.sample(1.seconds().after_origin()))
.join()
.unwrap(),
Point::new(4.0, 8.0)
);
}