atagen

monolith

public
Star0
Watch0
3a1eef6bf10b440ec762e4f6b99285eabcfb35d3
1
ssh://brewery@pls.lobotomise.me:33/atagen/monolith.git
crates/layout/src/tests.rsRust188 lines5.7 KB
1
use super::*;
2
use proptest::prelude::*;
3
4
const AREA: Rect = Rect::new(0.0, 0.0, 1000.0, 600.0);
5
6
#[derive(Clone, Debug)]
7
enum Op {
8
    Add(u8, bool),
9
    Remove(u8),
10
    Focus(u8),
11
    Move(Dir),
12
    Block(Dir),
13
    View(u8),
14
    ToggleView(u8),
15
    Retag(u8, u8),
16
    Float(u8),
17
    Expand(u8),
18
    Flip,
19
    Resize(bool, i8),
20
    Scratch(u8),
21
    Summon,
22
    Unplug,
23
    Replug,
24
}
25
26
fn dir() -> impl Strategy<Value = Dir> {
27
    prop_oneof![
28
        Just(Dir::Left),
29
        Just(Dir::Right),
30
        Just(Dir::Up),
31
        Just(Dir::Down)
32
    ]
33
}
34
35
fn op() -> impl Strategy<Value = Op> {
36
    prop_oneof![
37
        4 => (0u8..4, prop::bool::weighted(0.15)).prop_map(|(t, f)| Op::Add(t, f)),
38
        1 => any::<u8>().prop_map(Op::Remove),
39
        3 => any::<u8>().prop_map(Op::Focus),
40
        4 => dir().prop_map(Op::Move),
41
        2 => dir().prop_map(Op::Block),
42
        1 => (0u8..4).prop_map(Op::View),
43
        2 => (0u8..4).prop_map(Op::ToggleView),
44
        1 => (any::<u8>(), 0u8..4).prop_map(|(w, t)| Op::Retag(w, t)),
45
        1 => any::<u8>().prop_map(Op::Float),
46
        1 => (0u8..3).prop_map(Op::Expand),
47
        1 => Just(Op::Flip),
48
        1 => (any::<bool>(), any::<i8>()).prop_map(|(a, d)| Op::Resize(a, d)),
49
        1 => any::<u8>().prop_map(Op::Scratch),
50
        1 => Just(Op::Summon),
51
        1 => Just(Op::Unplug),
52
        1 => Just(Op::Replug),
53
    ]
54
}
55
56
fn nth(l: &Layout, i: u8) -> Option<WindowId> {
57
    let ids: Vec<WindowId> = l.windows().map(|(id, _)| id).collect();
58
    (!ids.is_empty()).then(|| ids[i as usize % ids.len()])
59
}
60
61
fn apply(l: &mut Layout, tags: &[TagId], op: &Op) {
62
    let active = l.active_output();
63
    match *op {
64
        Op::Add(t, f) => {
65
            let w = l.add_window(
66
                None,
67
                Some(tags[t as usize]),
68
                f.then(|| Rect::new(10.0, 10.0, 200.0, 100.0)),
69
            );
70
            if l.visible(w) {
71
                l.focus(w);
72
            }
73
        }
74
        Op::Remove(i) => nth(l, i).into_iter().for_each(|w| l.remove_window(w)),
75
        Op::Focus(i) => {
76
            if let Some(w) = nth(l, i).filter(|&w| l.visible(w)) {
77
                l.focus(w);
78
            }
79
        }
80
        Op::Move(d) => {
81
            l.move_window(d);
82
        }
83
        Op::Block(d) => {
84
            l.move_block(d);
85
        }
86
        Op::View(t) => active.into_iter().for_each(|o| l.view(o, tags[t as usize])),
87
        Op::ToggleView(t) => active
88
            .into_iter()
89
            .for_each(|o| l.toggle_view(o, tags[t as usize])),
90
        Op::Retag(i, t) => nth(l, i)
91
            .into_iter()
92
            .for_each(|w| l.set_tag(w, tags[t as usize])),
93
        Op::Float(i) => nth(l, i).into_iter().for_each(|w| l.toggle_floating(w)),
94
        Op::Expand(s) => {
95
            l.expand([ExpandScope::Window, ExpandScope::Block, ExpandScope::All][s as usize]);
96
        }
97
        Op::Flip => active.into_iter().for_each(|o| l.flip(o)),
98
        Op::Resize(h, d) => {
99
            let axis = if h { Axis::Horizontal } else { Axis::Vertical };
100
            l.resize(axis, f64::from(d) / 200.0);
101
        }
102
        Op::Scratch(i) => nth(l, i).into_iter().for_each(|w| l.scratchpad_add(w, "s")),
103
        Op::Summon => {
104
            l.summon("s");
105
        }
106
        Op::Unplug => {
107
            let b = l.outputs().find(|(_, out)| out.name == "b").map(|(o, _)| o);
108
            if let Some(o) = b {
109
                l.remove_output(o);
110
            }
111
        }
112
        Op::Replug => {
113
            if !l.outputs().any(|(_, out)| out.name == "b") {
114
                l.add_output("b", Rect::new(1000.0, 0.0, 800.0, 600.0));
115
            }
116
        }
117
    }
118
}
119
120
fn check_geometry(l: &Layout) {
121
    for (o, out) in l.outputs() {
122
        let arr = l.arrange(o);
123
        let shown: Vec<Rect> = arr
124
            .tiled
125
            .iter()
126
            .filter(|p| p.visible)
127
            .map(|p| p.rect)
128
            .collect();
129
        for r in &shown {
130
            assert!(r.w >= 0.0 && r.h >= 0.0);
131
            assert!(r.x >= out.area.x - 1e-6 && r.right() <= out.area.right() + 1e-6);
132
            assert!(r.y >= out.area.y - 1e-6 && r.bottom() <= out.area.bottom() + 1e-6);
133
        }
134
        for (i, a) in shown.iter().enumerate() {
135
            for b in &shown[i + 1..] {
136
                let ox = a.right().min(b.right()) - a.x.max(b.x);
137
                let oy = a.bottom().min(b.bottom()) - a.y.max(b.y);
138
                assert!(ox <= 1e-6 || oy <= 1e-6, "{a:?} overlaps {b:?}");
139
            }
140
        }
141
    }
142
}
143
144
proptest! {
145
    #![proptest_config(ProptestConfig::with_cases(2000))]
146
147
    #[test]
148
    fn random_commands_keep_the_model_sound(ops in prop::collection::vec(op(), 1..80)) {
149
        let mut l = Layout::default();
150
        l.add_output("a", AREA);
151
        l.add_output("b", Rect::new(1000.0, 0.0, 800.0, 600.0));
152
        let tags = [l.homeless(), l.tag("1"), l.tag("2"), l.tag("3")];
153
        for op in &ops {
154
            apply(&mut l, &tags, op);
155
            l.check();
156
            check_geometry(&l);
157
        }
158
    }
159
}
160
161
/// The hidden-slot rules from planner.html, demo 11.
162
#[test]
163
fn hidden_windows_keep_their_slots() {
164
    let (a, b, c, d, e) = (1, 2, 3, 4, 5);
165
    let mut t: Tree<u32> = Tree::default();
166
    t.insert_row(a);
167
    t.insert_after(d, a);
168
    t.insert_after(b, d);
169
    t.insert_row(c);
170
    t.insert_after(e, c);
171
    t.check();
172
    let hidden = |w: u32| w != d && w != e;
173
    let all = |_| true;
174
    let rect = |t: &Tree<u32>, w| {
175
        t.arrange(AREA, 0.0, all)
176
            .into_iter()
177
            .find(|p| p.0 == w)
178
            .unwrap()
179
            .1
180
    };
181
182
    assert!(t.move_window(a, Dir::Right, hidden));
183
    assert!(rect(&t, b).x < rect(&t, d).x && rect(&t, d).x < rect(&t, a).x);
184
185
    assert!(t.move_window(c, Dir::Up, hidden));
186
    t.check();
187
    assert_eq!(rect(&t, e).w, AREA.w, "E is demoted to a row of its own");
188
}