use float_cmp::assert_approx_eq;
use super::{
Delay, Direction, Duration, Easing, FillMode, IterationCount, Timing, TimingPhase,
TimingSampleState,
};
#[test]
fn duration_and_delay_sanitize_invalid_values() {
assert_approx_eq!(
f64,
Duration::from_millis(-10.0).as_millis(),
0.0,
epsilon = 1e-10
);
assert_approx_eq!(
f64,
Duration::from_millis(f64::NAN).as_millis(),
0.0,
epsilon = 1e-10
);
assert_approx_eq!(
f64,
Duration::from_secs(1.5).as_millis(),
1_500.0,
epsilon = 1e-10
);
assert_approx_eq!(
f64,
Delay::from_millis(-5.0).as_millis(),
0.0,
epsilon = 1e-10
);
assert_approx_eq!(
f64,
Delay::from_secs(0.25).as_millis(),
250.0,
epsilon = 1e-10
);
}
#[test]
fn timing_builder_stores_playback_configuration() {
let timing = Timing::new(120.0)
.with_delay(Delay::from_millis(40.0))
.with_direction(Direction::AlternateReverse)
.with_fill_mode(FillMode::Both)
.with_easing(Easing::EaseInOut)
.with_iterations(IterationCount::count(3))
.with_playback_rate(2.0);
assert_approx_eq!(f64, timing.duration.as_millis(), 120.0, epsilon = 1e-10);
assert_approx_eq!(f64, timing.delay.as_millis(), 40.0, epsilon = 1e-10);
assert_eq!(timing.direction, Direction::AlternateReverse);
assert_eq!(timing.fill_mode, FillMode::Both);
assert_eq!(timing.easing, Easing::EaseInOut);
assert_eq!(timing.iterations, IterationCount::count(3));
assert_approx_eq!(f64, timing.playback_rate, 2.0, epsilon = 1e-10);
}
#[test]
fn iteration_count_clamps_zero_to_one() {
assert_eq!(IterationCount::count(0).finite_count(), Some(1));
assert_eq!(IterationCount::count(2).finite_count(), Some(2));
assert_eq!(IterationCount::infinite().finite_count(), None);
}
#[test]
fn iteration_count_accepts_u32_builder_input() {
let timing = Timing::new(100.0).with_iterations(3);
assert_eq!(timing.iterations.finite_count(), Some(3));
}
#[test]
fn timing_reports_finite_total_duration() {
let timing = Timing::new(100.0)
.with_delay(Delay::from_millis(25.0))
.with_iterations(IterationCount::count(4));
assert_eq!(
timing.active_duration().map(Duration::as_millis),
Some(400.0)
);
assert_eq!(
timing.total_duration().map(Duration::as_millis),
Some(425.0)
);
}
#[test]
fn infinite_timing_has_no_finite_total_duration() {
let timing = Timing::new(100.0).with_iterations(IterationCount::infinite());
assert_eq!(timing.active_duration(), None);
assert_eq!(timing.total_duration(), None);
}
#[test]
fn elapsed_time_normalizes_before_active_and_after_end() {
let timing = Timing::new(100.0)
.with_delay(Delay::from_millis(50.0))
.with_iterations(IterationCount::count(2));
let before = timing.normalize_elapsed(25.0);
assert_eq!(before.phase, TimingPhase::BeforeStart);
assert_eq!(before.sample_state, TimingSampleState::BeforeStart);
assert!(!before.has_sample());
assert_approx_eq!(f64, before.iteration_progress, 0.0, epsilon = 1e-10);
let active = timing.normalize_elapsed(175.0);
assert_eq!(active.phase, TimingPhase::Active);
assert_eq!(active.sample_state, TimingSampleState::Active);
assert!(active.has_sample());
assert_eq!(active.current_iteration_index, Some(1));
assert_eq!(active.completed_iterations, 1);
assert_approx_eq!(f64, active.iteration_progress, 0.25, epsilon = 1e-10);
assert_approx_eq!(f64, active.active_progress, 1.25, epsilon = 1e-10);
let after = timing.normalize_elapsed(250.0);
assert_eq!(after.phase, TimingPhase::AfterEnd);
assert_eq!(after.sample_state, TimingSampleState::AfterEnd);
assert!(!after.has_sample());
assert_eq!(after.current_iteration_index, None);
assert_eq!(after.completed_iterations, 2);
assert_approx_eq!(f64, after.iteration_progress, 1.0, epsilon = 1e-10);
assert_approx_eq!(f64, after.active_progress, 2.0, epsilon = 1e-10);
}
#[test]
fn delay_normalization_starts_active_sampling_at_delay_boundary() {
let timing = Timing::new(100.0).with_delay(Delay::from_millis(50.0));
let before = timing.normalize_elapsed(49.0);
assert_eq!(before.phase, TimingPhase::BeforeStart);
assert_eq!(before.sample_state, TimingSampleState::BeforeStart);
let start = timing.normalize_elapsed(50.0);
assert_eq!(start.phase, TimingPhase::Active);
assert_eq!(start.sample_state, TimingSampleState::Active);
assert_eq!(start.current_iteration_index, Some(0));
assert_eq!(start.completed_iterations, 0);
assert_approx_eq!(f64, start.iteration_progress, 0.0, epsilon = 1e-10);
assert_approx_eq!(f64, start.active_progress, 0.0, epsilon = 1e-10);
}
#[test]
fn zero_duration_normalizes_to_completion_state() {
let timing = Timing::new(0.0).with_fill_mode(FillMode::Forwards);
let normalized = timing.normalize_elapsed(0.0);
assert_eq!(normalized.phase, TimingPhase::AfterEnd);
assert_eq!(normalized.sample_state, TimingSampleState::ForwardsFill);
assert_eq!(normalized.current_iteration_index, None);
assert_eq!(normalized.completed_iterations, 1);
assert_approx_eq!(f64, normalized.iteration_progress, 1.0, epsilon = 1e-10);
assert_approx_eq!(f64, normalized.active_progress, 1.0, epsilon = 1e-10);
}
#[test]
fn playback_rate_scales_elapsed_time() {
let timing = Timing::new(100.0).with_playback_rate(2.0);
let normalized = timing.normalize_elapsed(25.0);
assert_eq!(normalized.phase, TimingPhase::Active);
assert_approx_eq!(f64, normalized.iteration_progress, 0.5, epsilon = 1e-10);
}
#[test]
fn playback_rate_scales_delay_and_active_elapsed_time_together() {
let timing = Timing::new(100.0)
.with_delay(Delay::from_millis(50.0))
.with_playback_rate(2.0);
let before = timing.normalize_elapsed(20.0);
assert_eq!(before.phase, TimingPhase::BeforeStart);
let active = timing.normalize_elapsed(50.0);
assert_eq!(active.phase, TimingPhase::Active);
assert_approx_eq!(f64, active.iteration_progress, 0.5, epsilon = 1e-10);
assert_approx_eq!(f64, active.active_progress, 0.5, epsilon = 1e-10);
}
#[test]
fn invalid_playback_rate_falls_back_to_normal_speed() {
let timing = Timing::new(100.0).with_playback_rate(0.0);
assert_approx_eq!(f64, timing.playback_rate, 1.0, epsilon = 1e-10);
assert_approx_eq!(
f64,
timing.normalize_elapsed(25.0).iteration_progress,
0.25,
epsilon = 1e-10
);
}
#[test]
fn fill_mode_none_emits_no_samples_outside_active_interval() {
let timing = Timing::new(100.0)
.with_delay(Delay::from_millis(50.0))
.with_fill_mode(FillMode::None);
let before = timing.normalize_elapsed(25.0);
assert_eq!(before.phase, TimingPhase::BeforeStart);
assert_eq!(before.sample_state, TimingSampleState::BeforeStart);
assert!(!before.has_sample());
assert!(!before.is_filled());
assert_approx_eq!(f64, before.iteration_progress, 0.0, epsilon = 1e-10);
let after = timing.normalize_elapsed(150.0);
assert_eq!(after.phase, TimingPhase::AfterEnd);
assert_eq!(after.sample_state, TimingSampleState::AfterEnd);
assert!(!after.has_sample());
assert!(!after.is_filled());
assert_approx_eq!(f64, after.iteration_progress, 1.0, epsilon = 1e-10);
}
#[test]
fn backwards_fill_emits_the_first_sample_before_start_only() {
let timing = Timing::new(100.0)
.with_delay(Delay::from_millis(50.0))
.with_fill_mode(FillMode::Backwards);
let before = timing.normalize_elapsed(25.0);
assert_eq!(before.phase, TimingPhase::BeforeStart);
assert_eq!(before.sample_state, TimingSampleState::BackwardsFill);
assert!(before.has_sample());
assert!(before.is_filled());
assert_approx_eq!(f64, before.iteration_progress, 0.0, epsilon = 1e-10);
let after = timing.normalize_elapsed(150.0);
assert_eq!(after.sample_state, TimingSampleState::AfterEnd);
assert!(!after.has_sample());
}
#[test]
fn forwards_fill_emits_the_final_sample_after_end_only() {
let timing = Timing::new(100.0)
.with_delay(Delay::from_millis(50.0))
.with_fill_mode(FillMode::Forwards);
let before = timing.normalize_elapsed(25.0);
assert_eq!(before.sample_state, TimingSampleState::BeforeStart);
assert!(!before.has_sample());
let after = timing.normalize_elapsed(150.0);
assert_eq!(after.phase, TimingPhase::AfterEnd);
assert_eq!(after.sample_state, TimingSampleState::ForwardsFill);
assert!(after.has_sample());
assert!(after.is_filled());
assert_approx_eq!(f64, after.iteration_progress, 1.0, epsilon = 1e-10);
}
#[test]
fn both_fill_emits_samples_before_start_and_after_end() {
let timing = Timing::new(100.0)
.with_delay(Delay::from_millis(50.0))
.with_fill_mode(FillMode::Both);
let before = timing.normalize_elapsed(25.0);
assert_eq!(before.sample_state, TimingSampleState::BackwardsFill);
assert!(before.has_sample());
assert!(before.is_filled());
let active = timing.normalize_elapsed(75.0);
assert_eq!(active.sample_state, TimingSampleState::Active);
assert!(active.has_sample());
assert!(!active.is_filled());
let after = timing.normalize_elapsed(150.0);
assert_eq!(after.sample_state, TimingSampleState::ForwardsFill);
assert!(after.has_sample());
assert!(after.is_filled());
}
#[test]
fn normal_direction_samples_each_iteration_forward() {
let timing = Timing::new(100.0)
.with_direction(Direction::Normal)
.with_iterations(2);
let first = timing.normalize_elapsed(25.0);
assert_eq!(first.current_iteration_index, Some(0));
assert_eq!(first.completed_iterations, 0);
assert_approx_eq!(f64, first.iteration_progress, 0.25, epsilon = 1e-10);
let second = timing.normalize_elapsed(125.0);
assert_eq!(second.current_iteration_index, Some(1));
assert_eq!(second.completed_iterations, 1);
assert_approx_eq!(f64, second.iteration_progress, 0.25, epsilon = 1e-10);
}
#[test]
fn repeated_iterations_preserve_active_progress_and_complete_at_boundary() {
let timing = Timing::new(100.0)
.with_iterations(IterationCount::count(3))
.with_fill_mode(FillMode::Forwards);
let middle = timing.normalize_elapsed(250.0);
assert_eq!(middle.phase, TimingPhase::Active);
assert_eq!(middle.current_iteration_index, Some(2));
assert_eq!(middle.completed_iterations, 2);
assert_approx_eq!(f64, middle.iteration_progress, 0.5, epsilon = 1e-10);
assert_approx_eq!(f64, middle.active_progress, 2.5, epsilon = 1e-10);
let complete = timing.normalize_elapsed(300.0);
assert_eq!(complete.phase, TimingPhase::AfterEnd);
assert_eq!(complete.sample_state, TimingSampleState::ForwardsFill);
assert_eq!(complete.current_iteration_index, None);
assert_eq!(complete.completed_iterations, 3);
assert_approx_eq!(f64, complete.iteration_progress, 1.0, epsilon = 1e-10);
assert_approx_eq!(f64, complete.active_progress, 3.0, epsilon = 1e-10);
}
#[test]
fn reverse_direction_samples_each_iteration_backward() {
let timing = Timing::new(100.0)
.with_direction(Direction::Reverse)
.with_iterations(2);
let first = timing.normalize_elapsed(25.0);
assert_eq!(first.current_iteration_index, Some(0));
assert_eq!(first.completed_iterations, 0);
assert_approx_eq!(f64, first.iteration_progress, 0.75, epsilon = 1e-10);
let second = timing.normalize_elapsed(125.0);
assert_eq!(second.current_iteration_index, Some(1));
assert_eq!(second.completed_iterations, 1);
assert_approx_eq!(f64, second.iteration_progress, 0.75, epsilon = 1e-10);
}
#[test]
fn reverse_direction_repeats_backward_and_completes_at_start_value() {
let timing = Timing::new(100.0)
.with_direction(Direction::Reverse)
.with_fill_mode(FillMode::Forwards)
.with_iterations(IterationCount::count(2));
let first_start = timing.normalize_elapsed(0.0);
assert_eq!(first_start.current_iteration_index, Some(0));
assert_eq!(first_start.completed_iterations, 0);
assert_approx_eq!(f64, first_start.iteration_progress, 1.0, epsilon = 1e-10);
let second_start = timing.normalize_elapsed(100.0);
assert_eq!(second_start.current_iteration_index, Some(1));
assert_eq!(second_start.completed_iterations, 1);
assert_approx_eq!(f64, second_start.iteration_progress, 1.0, epsilon = 1e-10);
let complete = timing.normalize_elapsed(200.0);
assert_eq!(complete.phase, TimingPhase::AfterEnd);
assert_eq!(complete.sample_state, TimingSampleState::ForwardsFill);
assert_approx_eq!(f64, complete.iteration_progress, 0.0, epsilon = 1e-10);
}
#[test]
fn alternate_direction_switches_odd_iterations_to_reverse() {
let timing = Timing::new(100.0)
.with_direction(Direction::Alternate)
.with_iterations(3);
let first = timing.normalize_elapsed(25.0);
assert_eq!(first.current_iteration_index, Some(0));
assert_eq!(first.completed_iterations, 0);
assert_approx_eq!(f64, first.iteration_progress, 0.25, epsilon = 1e-10);
let second = timing.normalize_elapsed(125.0);
assert_eq!(second.current_iteration_index, Some(1));
assert_eq!(second.completed_iterations, 1);
assert_approx_eq!(f64, second.iteration_progress, 0.75, epsilon = 1e-10);
let third = timing.normalize_elapsed(225.0);
assert_eq!(third.current_iteration_index, Some(2));
assert_eq!(third.completed_iterations, 2);
assert_approx_eq!(f64, third.iteration_progress, 0.25, epsilon = 1e-10);
}
#[test]
fn alternate_reverse_direction_switches_odd_iterations_to_forward() {
let timing = Timing::new(100.0)
.with_direction(Direction::AlternateReverse)
.with_iterations(3);
let first = timing.normalize_elapsed(25.0);
assert_eq!(first.current_iteration_index, Some(0));
assert_eq!(first.completed_iterations, 0);
assert_approx_eq!(f64, first.iteration_progress, 0.75, epsilon = 1e-10);
let second = timing.normalize_elapsed(125.0);
assert_eq!(second.current_iteration_index, Some(1));
assert_eq!(second.completed_iterations, 1);
assert_approx_eq!(f64, second.iteration_progress, 0.25, epsilon = 1e-10);
let third = timing.normalize_elapsed(225.0);
assert_eq!(third.current_iteration_index, Some(2));
assert_eq!(third.completed_iterations, 2);
assert_approx_eq!(f64, third.iteration_progress, 0.75, epsilon = 1e-10);
}
#[test]
fn direction_sampling_applies_before_easing() {
let timing = Timing::new(100.0)
.with_direction(Direction::Reverse)
.with_easing(Easing::EaseIn);
let normalized = timing.normalize_elapsed(25.0);
assert_approx_eq!(f64, normalized.iteration_progress, 0.75, epsilon = 1e-10);
}
#[test]
fn direction_controls_backwards_and_forwards_fill_endpoints() {
let reverse = Timing::new(100.0)
.with_delay(Delay::from_millis(50.0))
.with_direction(Direction::Reverse)
.with_fill_mode(FillMode::Both);
let reverse_before = reverse.normalize_elapsed(25.0);
assert_eq!(
reverse_before.sample_state,
TimingSampleState::BackwardsFill
);
assert_approx_eq!(f64, reverse_before.iteration_progress, 1.0, epsilon = 1e-10);
let reverse_after = reverse.normalize_elapsed(150.0);
assert_eq!(reverse_after.sample_state, TimingSampleState::ForwardsFill);
assert_approx_eq!(f64, reverse_after.iteration_progress, 0.0, epsilon = 1e-10);
let alternate = Timing::new(100.0)
.with_direction(Direction::Alternate)
.with_fill_mode(FillMode::Forwards)
.with_iterations(2);
let alternate_after = alternate.normalize_elapsed(200.0);
assert_eq!(
alternate_after.sample_state,
TimingSampleState::ForwardsFill
);
assert_approx_eq!(
f64,
alternate_after.iteration_progress,
0.0,
epsilon = 1e-10
);
}
#[test]
fn zero_duration_uses_configured_iteration_count() {
let timing = Timing::new(0.0)
.with_iterations(3)
.with_fill_mode(FillMode::Forwards);
let normalized = timing.normalize_elapsed(0.0);
assert_eq!(normalized.phase, TimingPhase::AfterEnd);
assert_eq!(normalized.sample_state, TimingSampleState::ForwardsFill);
assert_eq!(normalized.current_iteration_index, None);
assert_eq!(normalized.completed_iterations, 3);
assert_approx_eq!(f64, normalized.active_progress, 3.0, epsilon = 1e-10);
}
#[test]
fn direction_sampling_clamps_nan_progress() {
assert_approx_eq!(
f64,
Direction::Normal.sample_progress(0, f64::NAN),
0.0,
epsilon = 1e-10
);
assert_approx_eq!(
f64,
Direction::Reverse.sample_progress(0, f64::NAN),
1.0,
epsilon = 1e-10
);
}
#[test]
fn huge_duration_inputs_saturate() {
assert!(Duration::from_secs(f64::MAX).as_millis().is_finite());
assert!(Delay::from_secs(f64::MAX).as_millis().is_finite());
}
#[test]
fn active_duration_overflow_returns_none() {
let timing = Timing::new(f64::MAX).with_iterations(2);
assert_eq!(timing.active_duration(), None);
}
#[test]
fn total_duration_overflow_returns_none() {
let timing = Timing::new(f64::MAX)
.with_delay(Delay::from_secs(f64::MAX))
.with_iterations(1);
assert_eq!(timing.total_duration(), None);
}
#[test]
fn infinite_iterations_with_huge_elapsed_returns_valid_sample() {
let timing = Timing::new(100.0)
.with_iterations(IterationCount::infinite())
.with_playback_rate(2.0);
let normalized = timing.normalize_elapsed(f64::MAX);
assert_eq!(normalized.phase, TimingPhase::Active);
assert_eq!(normalized.sample_state, TimingSampleState::Active);
assert!(normalized.has_sample());
assert!(normalized.iteration_progress.is_finite());
assert!(normalized.active_progress.is_finite());
}