euv-ui 0.28.21

Reusable UI component library for the euv framework, providing buttons, cards, modals, inputs, and more.
Documentation
use super::*;

fn debounced(delay: u32) -> DebouncedValue<i32> {
    DebouncedValue::new(delay)
}

fn throttled(interval: u32) -> ThrottledValue<i32> {
    ThrottledValue::new(interval)
}

#[test]
fn a_fresh_debounced_value_holds_the_default() {
    let value: DebouncedValue<i32> = debounced(100);
    assert_eq!(value.get(), 0, "it starts at the type default");
    assert!(!value.is_pending(), "and nothing is waiting to commit");
}

#[test]
fn setting_a_debounced_value_does_not_change_it_yet() {
    let value: DebouncedValue<i32> = debounced(100);
    value.set(5, 0);
    assert_eq!(
        value.get(),
        0,
        "the point of debouncing is to not commit yet"
    );
    assert!(value.is_pending(), "the new value is parked as pending");
}

#[test]
fn a_debounced_value_commits_once_the_delay_elapses() {
    let value: DebouncedValue<i32> = debounced(100);
    value.set(5, 1_000);
    let committed: bool = value.tick(1_050);
    assert!(!committed, "50ms into a 100ms delay is too early");
    let late: bool = value.tick(1_100);
    assert!(late, "exactly at the delay the value commits");
    assert_eq!(
        value.get(),
        5,
        "and the parked value becomes the current one"
    );
    assert!(!value.is_pending(), "nothing is left pending");
}

#[test]
fn ticking_a_debounced_value_before_the_delay_changes_nothing() {
    let value: DebouncedValue<i32> = debounced(100);
    value.set(5, 0);
    let committed: bool = value.tick(99);
    assert!(!committed, "one millisecond short is still pending");
    assert_eq!(value.get(), 0, "so the current value is untouched");
    assert!(value.is_pending(), "and the pending value is still parked");
}

#[test]
fn only_the_most_recent_debounced_value_survives() {
    let value: DebouncedValue<i32> = debounced(100);
    value.set(1, 0);
    value.set(2, 10);
    value.set(3, 20);
    let too_early: bool = value.tick(100);
    assert!(
        !too_early,
        "the window restarts on every set, so t = 100 is only 80ms after the last one"
    );
    let committed: bool = value.tick(120);
    assert!(
        committed,
        "a full delay after the most recent set commits it"
    );
    assert_eq!(value.get(), 3, "and only the latest input is committed");
}

#[test]
fn cancelling_a_debounced_value_drops_the_pending_input() {
    let value: DebouncedValue<i32> = debounced(100);
    value.set(5, 0);
    value.cancel();
    assert!(!value.is_pending(), "cancelling clears the parked value");
    let committed: bool = value.tick(1_000);
    assert!(!committed, "so a later tick has nothing to commit");
    assert_eq!(value.get(), 0, "and the current value never moved");
}

#[test]
fn ticking_an_idle_debounced_value_reports_nothing_committed() {
    let value: DebouncedValue<i32> = debounced(100);
    let committed: bool = value.tick(10_000);
    assert!(!committed, "with nothing pending a tick is a no-op");
}

#[test]
fn a_zero_delay_debounced_value_commits_on_the_next_tick() {
    let value: DebouncedValue<i32> = debounced(0);
    value.set(7, 0);
    let committed: bool = value.tick(0);
    assert!(
        committed,
        "a zero delay has already elapsed by the next tick"
    );
    assert_eq!(value.get(), 7, "so the value lands immediately");
}

#[test]
fn a_fresh_throttled_value_holds_the_default() {
    let value: ThrottledValue<i32> = throttled(100);
    assert_eq!(value.get(), 0, "it starts at the type default");
    assert!(!value.is_throttling(), "and is not cooling down");
}

#[test]
fn the_first_throttled_write_commits_immediately() {
    let value: ThrottledValue<i32> = throttled(100);
    value.set(5, 0);
    assert_eq!(
        value.get(),
        5,
        "a throttle only delays the writes that follow the first one"
    );
    assert!(value.is_throttling(), "and opens a cooldown window");
}

#[test]
fn a_write_inside_the_cooldown_is_deferred() {
    let value: ThrottledValue<i32> = throttled(100);
    value.set(1, 0);
    value.set(2, 10);
    assert_eq!(value.get(), 1, "the second write does not land yet");
}

#[test]
fn a_deferred_write_lands_once_the_cooldown_expires() {
    let value: ThrottledValue<i32> = throttled(100);
    value.set(1, 0);
    value.set(2, 10);
    let committed: bool = value.tick(50);
    assert!(!committed, "the cooldown is still running");
    let later: bool = value.tick(100);
    assert!(later, "at the interval the deferred value lands");
    assert_eq!(value.get(), 2, "and it replaces the committed one");
    assert!(!value.is_throttling(), "the cooldown closes");
}

#[test]
fn only_the_last_write_inside_a_cooldown_survives() {
    let value: ThrottledValue<i32> = throttled(100);
    value.set(1, 0);
    value.set(2, 10);
    value.set(3, 20);
    let committed: bool = value.tick(100);
    assert!(committed, "the cooldown expires normally");
    assert_eq!(
        value.get(),
        3,
        "but only the most recent input is committed"
    );
}

#[test]
fn a_tick_that_finds_no_pending_write_closes_the_cooldown_without_committing() {
    let value: ThrottledValue<i32> = throttled(100);
    value.set(1, 0);
    let committed: bool = value.tick(100);
    assert!(
        !committed,
        "nothing was deferred, so the expiry has nothing to commit"
    );
    assert!(!value.is_throttling(), "and the cooldown closes regardless");
}

#[test]
fn cancelling_a_throttle_drops_the_deferred_write() {
    let value: ThrottledValue<i32> = throttled(100);
    value.set(1, 0);
    value.set(2, 10);
    value.cancel();
    assert!(!value.is_throttling(), "cancelling closes the cooldown");
    let committed: bool = value.tick(100);
    assert!(!committed, "and the deferred write is gone");
    assert_eq!(value.get(), 1, "so the committed value never moves");
}

#[test]
fn a_zero_interval_throttle_commits_every_write() {
    let value: ThrottledValue<i32> = throttled(0);
    value.set(1, 0);
    value.set(2, 1);
    assert_eq!(
        value.get(),
        2,
        "with no interval the cooldown branch is never taken, so every write lands"
    );
    assert!(!value.is_throttling(), "and no cooldown is ever opened");
}

#[test]
fn a_throttled_value_reports_its_state_through_display() {
    let value: ThrottledValue<i32> = throttled(100);
    let idle: String = format!("{value}");
    assert!(idle.starts_with("ThrottledValue("), "got {idle}");
    value.set(1, 0);
    let cooling: String = format!("{value}");
    assert!(
        cooling.contains("cooldown"),
        "the cooling state is spelled out, got {cooling}"
    );
}

#[test]
fn a_debounced_value_reports_its_state_through_display() {
    let value: DebouncedValue<i32> = debounced(100);
    let idle: String = format!("{value}");
    assert!(idle.starts_with("DebouncedValue("), "got {idle}");
    value.set(4, 0);
    let pending: String = format!("{value}");
    assert!(
        pending.contains("pending=4"),
        "the parked value is visible in the debug form, got {pending}"
    );
}