cranpose 0.1.149

Cranpose runtime and UI facade
Documentation
use std::time::{Duration, Instant};

pub(crate) const ACCESSIBILITY_PUBLISH_INTERVAL: Duration = Duration::from_millis(100);

pub(crate) struct AccessibilityPublishPolicy {
    enabled: bool,
    last_publish: Option<Instant>,
    pending_deadline: Option<Instant>,
}

impl AccessibilityPublishPolicy {
    pub(crate) fn new() -> Self {
        Self {
            enabled: false,
            last_publish: None,
            pending_deadline: None,
        }
    }

    pub(crate) fn update_enabled(&mut self, enabled: bool) -> bool {
        let became_enabled = enabled && !self.enabled;
        if became_enabled {
            self.last_publish = None;
        }
        if !enabled {
            self.pending_deadline = None;
        }
        self.enabled = enabled;
        became_enabled
    }

    pub(crate) fn try_begin_publish(&mut self, now: Instant) -> bool {
        if !self.enabled {
            return false;
        }
        match self.last_publish {
            Some(last) if now.duration_since(last) < ACCESSIBILITY_PUBLISH_INTERVAL => {
                self.pending_deadline = Some(last + ACCESSIBILITY_PUBLISH_INTERVAL);
                false
            }
            _ => {
                self.pending_deadline = None;
                self.last_publish = Some(now);
                true
            }
        }
    }

    pub(crate) fn wake_deadline(&self) -> Option<Instant> {
        self.pending_deadline
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    fn policy_enabled_at(now: Instant) -> AccessibilityPublishPolicy {
        let mut policy = AccessibilityPublishPolicy::new();
        assert!(policy.update_enabled(true));
        assert!(policy.try_begin_publish(now));
        policy
    }

    #[test]
    fn disabled_policy_never_publishes_and_arms_no_wake() {
        let mut policy = AccessibilityPublishPolicy::new();
        let now = Instant::now();
        assert!(!policy.try_begin_publish(now));
        assert_eq!(policy.wake_deadline(), None);
    }

    #[test]
    fn first_publish_after_enabling_is_immediate() {
        let mut policy = AccessibilityPublishPolicy::new();
        assert!(policy.update_enabled(true));
        assert!(policy.try_begin_publish(Instant::now()));
    }

    #[test]
    fn enabling_twice_reports_the_transition_once() {
        let mut policy = AccessibilityPublishPolicy::new();
        assert!(policy.update_enabled(true));
        assert!(!policy.update_enabled(true));
    }

    #[test]
    fn re_enabling_after_disable_reports_a_fresh_transition() {
        let mut policy = AccessibilityPublishPolicy::new();
        assert!(policy.update_enabled(true));
        assert!(!policy.update_enabled(false));
        assert!(policy.update_enabled(true));
    }

    #[test]
    fn publish_inside_the_window_is_refused_with_a_wake_deadline() {
        let start = Instant::now();
        let mut policy = policy_enabled_at(start);
        let inside = start + ACCESSIBILITY_PUBLISH_INTERVAL / 2;
        assert!(!policy.try_begin_publish(inside));
        assert_eq!(
            policy.wake_deadline(),
            Some(start + ACCESSIBILITY_PUBLISH_INTERVAL)
        );
    }

    #[test]
    fn publish_after_the_window_opens_is_allowed_and_clears_the_wake() {
        let start = Instant::now();
        let mut policy = policy_enabled_at(start);
        assert!(!policy.try_begin_publish(start + ACCESSIBILITY_PUBLISH_INTERVAL / 2));
        let open = start + ACCESSIBILITY_PUBLISH_INTERVAL;
        assert!(policy.try_begin_publish(open));
        assert_eq!(policy.wake_deadline(), None);
    }

    #[test]
    fn window_open_probe_that_finds_no_change_leaves_no_wake() {
        let start = Instant::now();
        let mut policy = policy_enabled_at(start);
        assert!(!policy.try_begin_publish(start + ACCESSIBILITY_PUBLISH_INTERVAL / 2));
        assert!(policy.try_begin_publish(start + ACCESSIBILITY_PUBLISH_INTERVAL));
        assert_eq!(policy.wake_deadline(), None);
    }

    #[test]
    fn a_probe_consumes_the_window_even_when_nothing_publishes() {
        let start = Instant::now();
        let mut policy = policy_enabled_at(start);
        let probe = start + ACCESSIBILITY_PUBLISH_INTERVAL;
        assert!(policy.try_begin_publish(probe));
        assert!(!policy.try_begin_publish(probe + Duration::from_millis(16)));
        assert!(policy.try_begin_publish(probe + ACCESSIBILITY_PUBLISH_INTERVAL));
    }

    #[test]
    fn disabling_clears_a_pending_wake() {
        let start = Instant::now();
        let mut policy = policy_enabled_at(start);
        assert!(!policy.try_begin_publish(start + ACCESSIBILITY_PUBLISH_INTERVAL / 2));
        assert!(policy.wake_deadline().is_some());
        policy.update_enabled(false);
        assert_eq!(policy.wake_deadline(), None);
    }

    #[test]
    fn re_enabling_publishes_immediately_even_right_after_a_publish() {
        let start = Instant::now();
        let mut policy = policy_enabled_at(start);
        policy.update_enabled(false);
        assert!(policy.update_enabled(true));
        assert!(policy.try_begin_publish(start + Duration::from_millis(1)));
    }
}