Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/facett

facett-app/tests/prop.rs

//! **Property layer** (component contract §5): invariants hold over arbitrary
//! op sequences, no GUI in the loop. Complements the unit + headless tests.

use facett_app::{DatePicker, Pagination, Router, Stepper};
use proptest::prelude::*;

proptest! {
    /// Pagination: the page is always in range and the item window is sane, no
    /// matter what next/prev/set_page sequence is applied.
    #[test]
    fn pagination_invariants(total in 0usize..10_000, page_size in 1usize..500, ops in prop::collection::vec(0u8..4, 0..60)) {
        let mut p = Pagination::new(total, page_size);
        for op in ops {
            match op {
                0 => { p.next_page(); }
                1 => { p.prev_page(); }
                2 => { p.set_page(usize::MAX / 2); }
                _ => { p.set_page(0); }
            }
            prop_assert!(p.page() < p.page_count());
            let (start, end) = p.range();
            prop_assert!(start <= end);
            prop_assert!(end <= total);
        }
    }

    /// Stepper: the value never escapes [min, max], whatever is set.
    #[test]
    fn stepper_stays_in_range(v in any::<i64>(), a in any::<i64>(), b in any::<i64>(), sets in prop::collection::vec(any::<i64>(), 0..20)) {
        let (lo, hi) = (a.min(b), a.max(b));
        let mut s = Stepper::new(v).range(lo, hi);
        prop_assert!(s.value() >= lo && s.value() <= hi);
        for x in sets {
            s.set_value(x);
            prop_assert!(s.value() >= lo && s.value() <= hi);
        }
    }

    /// Router: back-then-forward returns to the same route, and the current route
    /// is never empty after any navigation sequence.
    #[test]
    fn router_back_forward_consistent(routes in prop::collection::vec("[a-d]{1,3}", 1..20)) {
        let mut r = Router::new("start");
        for rt in &routes {
            r.navigate(rt.clone());
        }
        prop_assert!(!r.current().is_empty());
        let cur = r.current().to_string();
        if r.can_back() {
            prop_assert!(r.back());
            prop_assert!(r.can_forward());
            prop_assert!(r.forward());
            prop_assert_eq!(r.current(), cur);
        }
    }

    /// DatePicker: the viewed month stays 1..=12 across wraps, and a selected day
    /// is always a valid day of its month.
    #[test]
    fn datepicker_bounds(y in 1i32..3000, m in 1u32..=12, steps in prop::collection::vec(0u8..2, 0..40)) {
        let mut dp = DatePicker::new(y, m);
        for s in steps {
            if s == 0 {
                dp.prev_month();
            } else {
                dp.next_month();
            }
            let view = dp.state_json()["view"].as_str().unwrap().to_string();
            let mm: u32 = view[5..7].parse().unwrap();
            prop_assert!((1..=12).contains(&mm));
        }
        dp.select(y, m, 31);
        let (_, sm, sd) = dp.selected().unwrap();
        prop_assert!((1..=12).contains(&sm));
        prop_assert!((1..=31).contains(&sd));
    }
}