grim-rs 0.1.9

Rust implementation of grim screenshot utility for Wayland
Documentation
use grim_rs::geometry::Region;
use proptest::prelude::*;

proptest! {
    #[test]
    fn box_getters_match_construction(x in -10000i32..10000, y in -10000i32..10000,
                                       w in 0i32..10000, h in 0i32..10000) {
        let b = Region::new(x, y, w, h);
        prop_assert_eq!(b.x(), x);
        prop_assert_eq!(b.y(), y);
        prop_assert_eq!(b.width(), w);
        prop_assert_eq!(b.height(), h);
    }

    #[test]
    fn box_is_empty_iff_zero_area(x in -1000i32..1000, y in -1000i32..1000,
                                   w in -100i32..100, h in -100i32..100) {
        let b = Region::new(x, y, w, h);
        let expected_empty = w <= 0 || h <= 0;
        prop_assert_eq!(b.is_empty(), expected_empty);
    }

    #[test]
    fn intersection_is_commutative(
        x1 in -1000i32..1000, y1 in -1000i32..1000, w1 in 1i32..500, h1 in 1i32..500,
        x2 in -1000i32..1000, y2 in -1000i32..1000, w2 in 1i32..500, h2 in 1i32..500
    ) {
        let box1 = Region::new(x1, y1, w1, h1);
        let box2 = Region::new(x2, y2, w2, h2);

        let int1 = box1.intersection(&box2);
        let int2 = box2.intersection(&box1);

        prop_assert_eq!(int1, int2, "Intersection should be commutative");
    }

    #[test]
    fn intersects_is_commutative(
        x1 in -1000i32..1000, y1 in -1000i32..1000, w1 in 1i32..500, h1 in 1i32..500,
        x2 in -1000i32..1000, y2 in -1000i32..1000, w2 in 1i32..500, h2 in 1i32..500
    ) {
        let box1 = Region::new(x1, y1, w1, h1);
        let box2 = Region::new(x2, y2, w2, h2);

        prop_assert_eq!(
            box1.intersects(&box2),
            box2.intersects(&box1),
            "intersects() should be commutative"
        );
    }

    #[test]
    fn intersection_exists_iff_intersects(
        x1 in -1000i32..1000, y1 in -1000i32..1000, w1 in 1i32..500, h1 in 1i32..500,
        x2 in -1000i32..1000, y2 in -1000i32..1000, w2 in 1i32..500, h2 in 1i32..500
    ) {
        let box1 = Region::new(x1, y1, w1, h1);
        let box2 = Region::new(x2, y2, w2, h2);

        let has_intersection = box1.intersection(&box2).is_some();
        let intersects = box1.intersects(&box2);

        prop_assert_eq!(
            has_intersection,
            intersects,
            "intersection() should return Some iff intersects() is true"
        );
    }

    #[test]
    fn intersection_is_subset_of_both(
        x1 in -1000i32..1000, y1 in -1000i32..1000, w1 in 1i32..500, h1 in 1i32..500,
        x2 in -1000i32..1000, y2 in -1000i32..1000, w2 in 1i32..500, h2 in 1i32..500
    ) {
        let box1 = Region::new(x1, y1, w1, h1);
        let box2 = Region::new(x2, y2, w2, h2);

        if let Some(intersection) = box1.intersection(&box2) {
            prop_assert!(
                intersection.x() >= box1.x() &&
                intersection.y() >= box1.y() &&
                intersection.x() + intersection.width() <= box1.x() + box1.width() &&
                intersection.y() + intersection.height() <= box1.y() + box1.height(),
                "Intersection should be within box1 bounds"
            );

            prop_assert!(
                intersection.x() >= box2.x() &&
                intersection.y() >= box2.y() &&
                intersection.x() + intersection.width() <= box2.x() + box2.width() &&
                intersection.y() + intersection.height() <= box2.y() + box2.height(),
                "Intersection should be within box2 bounds"
            );
        }
    }

    #[test]
    fn box_with_self_returns_self(x in -1000i32..1000, y in -1000i32..1000,
                                   w in 1i32..500, h in 1i32..500) {
        let b = Region::new(x, y, w, h);
        let intersection = b.intersection(&b);

        prop_assert_eq!(intersection, Some(b), "Box intersected with itself should return itself");
    }

    #[test]
    fn empty_boxes_dont_intersect(
        x1 in -1000i32..1000, y1 in -1000i32..1000,
        x2 in -1000i32..1000, y2 in -1000i32..1000
    ) {
        let empty1 = Region::new(x1, y1, 0, 0);
        let empty2 = Region::new(x2, y2, 0, 100);
        let empty3 = Region::new(x2, y2, 100, 0);

        prop_assert!(!empty1.intersects(&empty2), "Empty boxes should not intersect");
        prop_assert!(!empty2.intersects(&empty3), "Empty boxes should not intersect");
        prop_assert_eq!(empty1.intersection(&empty2), None);
        prop_assert_eq!(empty2.intersection(&empty3), None);
    }

    #[test]
    fn parse_and_display_roundtrip(x in -1000i32..1000, y in -1000i32..1000,
                                     w in 0i32..500, h in 0i32..500) {
        let original = Region::new(x, y, w, h);
        let serialized = original.to_string();
        let parsed: Region = serialized.parse().unwrap();

        prop_assert_eq!(parsed, original, "Parsing should be inverse of Display");
    }
}

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

    #[test]
    fn non_overlapping_boxes_dont_intersect() {
        let box1 = Region::new(0, 0, 10, 10);
        let box2 = Region::new(20, 20, 10, 10);

        assert!(!box1.intersects(&box2));
        assert_eq!(box1.intersection(&box2), None);
    }

    #[test]
    fn adjacent_boxes_dont_intersect() {
        let box1 = Region::new(0, 0, 10, 10);
        let box2 = Region::new(10, 0, 10, 10);

        assert!(!box1.intersects(&box2));
        assert_eq!(box1.intersection(&box2), None);
    }

    #[test]
    fn contained_box_intersection_is_smaller_box() {
        let outer = Region::new(0, 0, 100, 100);
        let inner = Region::new(25, 25, 50, 50);

        assert!(outer.intersects(&inner));
        let intersection = outer.intersection(&inner).unwrap();
        assert_eq!(intersection, inner);
    }

    #[test]
    fn negative_dimensions_are_empty() {
        let box1 = Region::new(0, 0, -10, 10);
        let box2 = Region::new(0, 0, 10, -10);
        let box3 = Region::new(0, 0, -10, -10);

        assert!(box1.is_empty());
        assert!(box2.is_empty());
        assert!(box3.is_empty());
    }
}