use super::*;
use crate::{Point, TimeLiteral};
#[test]
fn delay_and_speed_preserve_boundaries_signed_signals_and_collapsed_durations() {
let tween = Tween::new(0.0_f64, 10.0, Time::from_ticks(10));
let delayed = tween.clone().delay(Time::from_ticks(5));
assert_eq!(delayed.duration(), Some(Time::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(), Some(Time::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(), Some(Time::ZERO));
assert_eq!(collapsed.sample(BEFORE_START), 0.0);
assert_eq!(collapsed.sample(Time::ZERO), 10.0);
let signed = |time: Time| time.ticks();
assert_eq!(
signed
.delay(Time::from_ticks(5))
.sample(Time::from_ticks(-3)),
-8
);
assert_eq!(signed.speed(2.0).sample(Time::from_ticks(-3)), -6);
assert_eq!(
signed.speed(1.0).sample(Time::from_ticks(i64::MIN)),
i64::MIN
);
assert_eq!(
Constant::new(7).delay(1.seconds()).speed(2.0).duration(),
None
);
let maximum = Tween::new(0.0_f64, 1.0, Time::from_ticks(i64::MAX)).speed(2.0);
let near_end = maximum.duration().unwrap() - Time::from_ticks(1);
let value = maximum.sample(near_end);
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, Time::from_ticks(10));
let sequence = tween
.clone()
.then(Tween::new(100.0_f64, 110.0, Time::from_ticks(5)));
assert_eq!(sequence.duration(), Some(Time::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(), Some(Time::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(), Some(Time::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(), Some(Time::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(Time::from_ticks(5));
assert_eq!(composed.duration(), Some(Time::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, Time::ZERO);
let delayed = instant.clone().delay(Time::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, Time::from_ticks(2)));
assert_eq!(sequence.sample(BEFORE_START), 2.0);
assert_eq!(sequence.sample(Time::ZERO), 11.0);
}
#[test]
fn invalid_or_infinite_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());
}
assert!(std::panic::catch_unwind(|| Tween::new(0.0_f64, 1.0, BEFORE_START)).is_err());
assert!(std::panic::catch_unwind(|| tween().delay(BEFORE_START)).is_err());
let infinite = Constant::new(0.0_f64);
assert!(std::panic::catch_unwind(|| infinite.clone().then(tween())).is_err());
assert!(std::panic::catch_unwind(|| tween().then(infinite.clone())).is_err());
assert!(std::panic::catch_unwind(|| infinite.clone().reverse()).is_err());
assert!(std::panic::catch_unwind(|| infinite.clone().repeat(0)).is_err());
assert!(std::panic::catch_unwind(|| infinite.clone().ping_pong()).is_err());
fn maximum() -> Tween<f64> {
Tween::new(0.0_f64, 1.0, Time::from_ticks(i64::MAX))
}
assert_eq!(
maximum().repeat(1).duration(),
Some(Time::from_ticks(i64::MAX))
);
assert_eq!(
maximum().speed(1.0).duration(),
Some(Time::from_ticks(i64::MAX))
);
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(Time::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());
struct Invalid;
impl Animation<f64> for Invalid {
fn sample(&self, _time: Time) -> f64 {
0.0
}
fn duration(&self) -> Option<Duration> {
Some(BEFORE_START)
}
}
assert!(std::panic::catch_unwind(|| Invalid.reverse()).is_err());
}
#[test]
fn tween_preserves_tick_progress_signed_endpoints_and_overshoot() {
let tween = Tween::new(0.0_f64, 1.0, Time::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, Time::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::CubicBezier {
x1: 0.3,
y1: 1.8,
x2: 0.7,
y2: 1.8,
});
assert!(overshooting.sample(500.milliseconds()) > 1.0);
assert_eq!(overshooting.sample(1.seconds()), 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();
let zipped = mapped.clone().zip(Tween::new(0.0_f64, 3.0, 3.seconds()));
assert_eq!(zipped.duration(), Some(3.seconds()));
assert_eq!(zipped.sample(1.seconds()), (Point::new(4.0, 8.0), 1.0));
assert_eq!(zipped.sample(4.seconds()), (Point::new(8.0, 16.0), 3.0));
assert_eq!(tween.zip(Constant::new(7)).duration(), None);
let signal = (|time: Time| time.ticks())
.map(|ticks| ticks.to_string())
.boxed();
assert_eq!(signal.duration(), None);
assert_eq!(signal.sample(Time::from_ticks(-17)), "-17");
assert_eq!(
std::thread::spawn(move || mapped.sample(1.seconds()))
.join()
.unwrap(),
Point::new(4.0, 8.0)
);
}