atagen

monolith

public
Star0
Watch0
f27fa4c2ade9c2a037543c421da5fcb19636e644
1
ssh://brewery@pls.lobotomise.me:33/atagen/monolith.git

Blame: crates/layout/src/tests.rs

9baaafa4b167atagen2026-10-16 10:04 UTC1
use super::*;
9baaafa4b167atagen2026-10-16 10:04 UTC2
use proptest::prelude::*;
9baaafa4b167atagen2026-10-16 10:04 UTC3
9baaafa4b167atagen2026-10-16 10:04 UTC4
const AREA: Rect = Rect::new(0.0, 0.0, 1000.0, 600.0);
9baaafa4b167atagen2026-10-16 10:04 UTC5
9baaafa4b167atagen2026-10-16 10:04 UTC6
#[derive(Clone, Debug)]
9baaafa4b167atagen2026-10-16 10:04 UTC7
enum Op {
9baaafa4b167atagen2026-10-16 10:04 UTC8
    Add(u8, bool),
9baaafa4b167atagen2026-10-16 10:04 UTC9
    Remove(u8),
9baaafa4b167atagen2026-10-16 10:04 UTC10
    Focus(u8),
9baaafa4b167atagen2026-10-16 10:04 UTC11
    Move(Dir),
9baaafa4b167atagen2026-10-16 10:04 UTC12
    Block(Dir),
4444c779eb48atagen2026-10-16 12:07 UTC13
    Merge(Dir),
9baaafa4b167atagen2026-10-16 10:04 UTC14
    View(u8),
9baaafa4b167atagen2026-10-16 10:04 UTC15
    ToggleView(u8),
9baaafa4b167atagen2026-10-16 10:04 UTC16
    Retag(u8, u8),
9baaafa4b167atagen2026-10-16 10:04 UTC17
    Float(u8),
9baaafa4b167atagen2026-10-16 10:04 UTC18
    Expand(u8),
9baaafa4b167atagen2026-10-16 10:04 UTC19
    Flip,
9baaafa4b167atagen2026-10-16 10:04 UTC20
    Resize(bool, i8),
9baaafa4b167atagen2026-10-16 10:04 UTC21
    Scratch(u8),
9baaafa4b167atagen2026-10-16 10:04 UTC22
    Summon,
9baaafa4b167atagen2026-10-16 10:04 UTC23
    Unplug,
9baaafa4b167atagen2026-10-16 10:04 UTC24
    Replug,
9baaafa4b167atagen2026-10-16 10:04 UTC25
}
9baaafa4b167atagen2026-10-16 10:04 UTC26
9baaafa4b167atagen2026-10-16 10:04 UTC27
fn dir() -> impl Strategy<Value = Dir> {
9baaafa4b167atagen2026-10-16 10:04 UTC28
    prop_oneof![
9baaafa4b167atagen2026-10-16 10:04 UTC29
        Just(Dir::Left),
9baaafa4b167atagen2026-10-16 10:04 UTC30
        Just(Dir::Right),
9baaafa4b167atagen2026-10-16 10:04 UTC31
        Just(Dir::Up),
9baaafa4b167atagen2026-10-16 10:04 UTC32
        Just(Dir::Down)
9baaafa4b167atagen2026-10-16 10:04 UTC33
    ]
9baaafa4b167atagen2026-10-16 10:04 UTC34
}
9baaafa4b167atagen2026-10-16 10:04 UTC35
9baaafa4b167atagen2026-10-16 10:04 UTC36
fn op() -> impl Strategy<Value = Op> {
9baaafa4b167atagen2026-10-16 10:04 UTC37
    prop_oneof![
9baaafa4b167atagen2026-10-16 10:04 UTC38
        4 => (0u8..4, prop::bool::weighted(0.15)).prop_map(|(t, f)| Op::Add(t, f)),
9baaafa4b167atagen2026-10-16 10:04 UTC39
        1 => any::<u8>().prop_map(Op::Remove),
9baaafa4b167atagen2026-10-16 10:04 UTC40
        3 => any::<u8>().prop_map(Op::Focus),
9baaafa4b167atagen2026-10-16 10:04 UTC41
        4 => dir().prop_map(Op::Move),
9baaafa4b167atagen2026-10-16 10:04 UTC42
        2 => dir().prop_map(Op::Block),
4444c779eb48atagen2026-10-16 12:07 UTC43
        3 => dir().prop_map(Op::Merge),
9baaafa4b167atagen2026-10-16 10:04 UTC44
        1 => (0u8..4).prop_map(Op::View),
9baaafa4b167atagen2026-10-16 10:04 UTC45
        2 => (0u8..4).prop_map(Op::ToggleView),
9baaafa4b167atagen2026-10-16 10:04 UTC46
        1 => (any::<u8>(), 0u8..4).prop_map(|(w, t)| Op::Retag(w, t)),
9baaafa4b167atagen2026-10-16 10:04 UTC47
        1 => any::<u8>().prop_map(Op::Float),
9baaafa4b167atagen2026-10-16 10:04 UTC48
        1 => (0u8..3).prop_map(Op::Expand),
9baaafa4b167atagen2026-10-16 10:04 UTC49
        1 => Just(Op::Flip),
9baaafa4b167atagen2026-10-16 10:04 UTC50
        1 => (any::<bool>(), any::<i8>()).prop_map(|(a, d)| Op::Resize(a, d)),
9baaafa4b167atagen2026-10-16 10:04 UTC51
        1 => any::<u8>().prop_map(Op::Scratch),
9baaafa4b167atagen2026-10-16 10:04 UTC52
        1 => Just(Op::Summon),
9baaafa4b167atagen2026-10-16 10:04 UTC53
        1 => Just(Op::Unplug),
9baaafa4b167atagen2026-10-16 10:04 UTC54
        1 => Just(Op::Replug),
9baaafa4b167atagen2026-10-16 10:04 UTC55
    ]
9baaafa4b167atagen2026-10-16 10:04 UTC56
}
9baaafa4b167atagen2026-10-16 10:04 UTC57
9baaafa4b167atagen2026-10-16 10:04 UTC58
fn nth(l: &Layout, i: u8) -> Option<WindowId> {
9baaafa4b167atagen2026-10-16 10:04 UTC59
    let ids: Vec<WindowId> = l.windows().map(|(id, _)| id).collect();
9baaafa4b167atagen2026-10-16 10:04 UTC60
    (!ids.is_empty()).then(|| ids[i as usize % ids.len()])
9baaafa4b167atagen2026-10-16 10:04 UTC61
}
9baaafa4b167atagen2026-10-16 10:04 UTC62
9baaafa4b167atagen2026-10-16 10:04 UTC63
fn apply(l: &mut Layout, tags: &[TagId], op: &Op) {
9baaafa4b167atagen2026-10-16 10:04 UTC64
    let active = l.active_output();
9baaafa4b167atagen2026-10-16 10:04 UTC65
    match *op {
9baaafa4b167atagen2026-10-16 10:04 UTC66
        Op::Add(t, f) => {
9baaafa4b167atagen2026-10-16 10:04 UTC67
            let w = l.add_window(
9baaafa4b167atagen2026-10-16 10:04 UTC68
                None,
9baaafa4b167atagen2026-10-16 10:04 UTC69
                Some(tags[t as usize]),
9baaafa4b167atagen2026-10-16 10:04 UTC70
                f.then(|| Rect::new(10.0, 10.0, 200.0, 100.0)),
9baaafa4b167atagen2026-10-16 10:04 UTC71
            );
9baaafa4b167atagen2026-10-16 10:04 UTC72
            if l.visible(w) {
9baaafa4b167atagen2026-10-16 10:04 UTC73
                l.focus(w);
9baaafa4b167atagen2026-10-16 10:04 UTC74
            }
9baaafa4b167atagen2026-10-16 10:04 UTC75
        }
9baaafa4b167atagen2026-10-16 10:04 UTC76
        Op::Remove(i) => nth(l, i).into_iter().for_each(|w| l.remove_window(w)),
9baaafa4b167atagen2026-10-16 10:04 UTC77
        Op::Focus(i) => {
9baaafa4b167atagen2026-10-16 10:04 UTC78
            if let Some(w) = nth(l, i).filter(|&w| l.visible(w)) {
9baaafa4b167atagen2026-10-16 10:04 UTC79
                l.focus(w);
9baaafa4b167atagen2026-10-16 10:04 UTC80
            }
9baaafa4b167atagen2026-10-16 10:04 UTC81
        }
9baaafa4b167atagen2026-10-16 10:04 UTC82
        Op::Move(d) => {
9baaafa4b167atagen2026-10-16 10:04 UTC83
            l.move_window(d);
9baaafa4b167atagen2026-10-16 10:04 UTC84
        }
9baaafa4b167atagen2026-10-16 10:04 UTC85
        Op::Block(d) => {
4444c779eb48atagen2026-10-16 12:07 UTC86
            l.move_container(d);
4444c779eb48atagen2026-10-16 12:07 UTC87
        }
4444c779eb48atagen2026-10-16 12:07 UTC88
        Op::Merge(d) => {
4444c779eb48atagen2026-10-16 12:07 UTC89
            l.merge_window(d);
9baaafa4b167atagen2026-10-16 10:04 UTC90
        }
9baaafa4b167atagen2026-10-16 10:04 UTC91
        Op::View(t) => active.into_iter().for_each(|o| l.view(o, tags[t as usize])),
9baaafa4b167atagen2026-10-16 10:04 UTC92
        Op::ToggleView(t) => active
9baaafa4b167atagen2026-10-16 10:04 UTC93
            .into_iter()
9baaafa4b167atagen2026-10-16 10:04 UTC94
            .for_each(|o| l.toggle_view(o, tags[t as usize])),
9baaafa4b167atagen2026-10-16 10:04 UTC95
        Op::Retag(i, t) => nth(l, i)
9baaafa4b167atagen2026-10-16 10:04 UTC96
            .into_iter()
9baaafa4b167atagen2026-10-16 10:04 UTC97
            .for_each(|w| l.set_tag(w, tags[t as usize])),
9baaafa4b167atagen2026-10-16 10:04 UTC98
        Op::Float(i) => nth(l, i).into_iter().for_each(|w| l.toggle_floating(w)),
9baaafa4b167atagen2026-10-16 10:04 UTC99
        Op::Expand(s) => {
4444c779eb48atagen2026-10-16 12:07 UTC100
            l.expand(
4444c779eb48atagen2026-10-16 12:07 UTC101
                [
4444c779eb48atagen2026-10-16 12:07 UTC102
                    ExpandScope::Window,
4444c779eb48atagen2026-10-16 12:07 UTC103
                    ExpandScope::Container,
4444c779eb48atagen2026-10-16 12:07 UTC104
                    ExpandScope::All,
4444c779eb48atagen2026-10-16 12:07 UTC105
                ][s as usize],
4444c779eb48atagen2026-10-16 12:07 UTC106
            );
9baaafa4b167atagen2026-10-16 10:04 UTC107
        }
9baaafa4b167atagen2026-10-16 10:04 UTC108
        Op::Flip => active.into_iter().for_each(|o| l.flip(o)),
9baaafa4b167atagen2026-10-16 10:04 UTC109
        Op::Resize(h, d) => {
9baaafa4b167atagen2026-10-16 10:04 UTC110
            let axis = if h { Axis::Horizontal } else { Axis::Vertical };
9baaafa4b167atagen2026-10-16 10:04 UTC111
            l.resize(axis, f64::from(d) / 200.0);
9baaafa4b167atagen2026-10-16 10:04 UTC112
        }
9baaafa4b167atagen2026-10-16 10:04 UTC113
        Op::Scratch(i) => nth(l, i).into_iter().for_each(|w| l.scratchpad_add(w, "s")),
9baaafa4b167atagen2026-10-16 10:04 UTC114
        Op::Summon => {
9baaafa4b167atagen2026-10-16 10:04 UTC115
            l.summon("s");
9baaafa4b167atagen2026-10-16 10:04 UTC116
        }
9baaafa4b167atagen2026-10-16 10:04 UTC117
        Op::Unplug => {
9baaafa4b167atagen2026-10-16 10:04 UTC118
            let b = l.outputs().find(|(_, out)| out.name == "b").map(|(o, _)| o);
9baaafa4b167atagen2026-10-16 10:04 UTC119
            if let Some(o) = b {
9baaafa4b167atagen2026-10-16 10:04 UTC120
                l.remove_output(o);
9baaafa4b167atagen2026-10-16 10:04 UTC121
            }
9baaafa4b167atagen2026-10-16 10:04 UTC122
        }
9baaafa4b167atagen2026-10-16 10:04 UTC123
        Op::Replug => {
9baaafa4b167atagen2026-10-16 10:04 UTC124
            if !l.outputs().any(|(_, out)| out.name == "b") {
9baaafa4b167atagen2026-10-16 10:04 UTC125
                l.add_output("b", Rect::new(1000.0, 0.0, 800.0, 600.0));
9baaafa4b167atagen2026-10-16 10:04 UTC126
            }
9baaafa4b167atagen2026-10-16 10:04 UTC127
        }
9baaafa4b167atagen2026-10-16 10:04 UTC128
    }
9baaafa4b167atagen2026-10-16 10:04 UTC129
}
9baaafa4b167atagen2026-10-16 10:04 UTC130
9baaafa4b167atagen2026-10-16 10:04 UTC131
fn check_geometry(l: &Layout) {
9baaafa4b167atagen2026-10-16 10:04 UTC132
    for (o, out) in l.outputs() {
9baaafa4b167atagen2026-10-16 10:04 UTC133
        let arr = l.arrange(o);
9baaafa4b167atagen2026-10-16 10:04 UTC134
        let shown: Vec<Rect> = arr
9baaafa4b167atagen2026-10-16 10:04 UTC135
            .tiled
9baaafa4b167atagen2026-10-16 10:04 UTC136
            .iter()
9baaafa4b167atagen2026-10-16 10:04 UTC137
            .filter(|p| p.visible)
9baaafa4b167atagen2026-10-16 10:04 UTC138
            .map(|p| p.rect)
9baaafa4b167atagen2026-10-16 10:04 UTC139
            .collect();
9baaafa4b167atagen2026-10-16 10:04 UTC140
        for r in &shown {
9baaafa4b167atagen2026-10-16 10:04 UTC141
            assert!(r.w >= 0.0 && r.h >= 0.0);
9baaafa4b167atagen2026-10-16 10:04 UTC142
            assert!(r.x >= out.area.x - 1e-6 && r.right() <= out.area.right() + 1e-6);
9baaafa4b167atagen2026-10-16 10:04 UTC143
            assert!(r.y >= out.area.y - 1e-6 && r.bottom() <= out.area.bottom() + 1e-6);
9baaafa4b167atagen2026-10-16 10:04 UTC144
        }
9baaafa4b167atagen2026-10-16 10:04 UTC145
        for (i, a) in shown.iter().enumerate() {
9baaafa4b167atagen2026-10-16 10:04 UTC146
            for b in &shown[i + 1..] {
9baaafa4b167atagen2026-10-16 10:04 UTC147
                let ox = a.right().min(b.right()) - a.x.max(b.x);
9baaafa4b167atagen2026-10-16 10:04 UTC148
                let oy = a.bottom().min(b.bottom()) - a.y.max(b.y);
9baaafa4b167atagen2026-10-16 10:04 UTC149
                assert!(ox <= 1e-6 || oy <= 1e-6, "{a:?} overlaps {b:?}");
9baaafa4b167atagen2026-10-16 10:04 UTC150
            }
9baaafa4b167atagen2026-10-16 10:04 UTC151
        }
9baaafa4b167atagen2026-10-16 10:04 UTC152
    }
9baaafa4b167atagen2026-10-16 10:04 UTC153
}
9baaafa4b167atagen2026-10-16 10:04 UTC154
9baaafa4b167atagen2026-10-16 10:04 UTC155
proptest! {
9baaafa4b167atagen2026-10-16 10:04 UTC156
    #![proptest_config(ProptestConfig::with_cases(2000))]
9baaafa4b167atagen2026-10-16 10:04 UTC157
9baaafa4b167atagen2026-10-16 10:04 UTC158
    #[test]
9baaafa4b167atagen2026-10-16 10:04 UTC159
    fn random_commands_keep_the_model_sound(ops in prop::collection::vec(op(), 1..80)) {
9baaafa4b167atagen2026-10-16 10:04 UTC160
        let mut l = Layout::default();
9baaafa4b167atagen2026-10-16 10:04 UTC161
        l.add_output("a", AREA);
9baaafa4b167atagen2026-10-16 10:04 UTC162
        l.add_output("b", Rect::new(1000.0, 0.0, 800.0, 600.0));
9baaafa4b167atagen2026-10-16 10:04 UTC163
        let tags = [l.homeless(), l.tag("1"), l.tag("2"), l.tag("3")];
9baaafa4b167atagen2026-10-16 10:04 UTC164
        for op in &ops {
9baaafa4b167atagen2026-10-16 10:04 UTC165
            apply(&mut l, &tags, op);
9baaafa4b167atagen2026-10-16 10:04 UTC166
            l.check();
9baaafa4b167atagen2026-10-16 10:04 UTC167
            check_geometry(&l);
9baaafa4b167atagen2026-10-16 10:04 UTC168
        }
9baaafa4b167atagen2026-10-16 10:04 UTC169
    }
9baaafa4b167atagen2026-10-16 10:04 UTC170
}
9baaafa4b167atagen2026-10-16 10:04 UTC171
9baaafa4b167atagen2026-10-16 10:04 UTC172
/// The hidden-slot rules from planner.html, demo 11.
9baaafa4b167atagen2026-10-16 10:04 UTC173
#[test]
9baaafa4b167atagen2026-10-16 10:04 UTC174
fn hidden_windows_keep_their_slots() {
9baaafa4b167atagen2026-10-16 10:04 UTC175
    let (a, b, c, d, e) = (1, 2, 3, 4, 5);
9baaafa4b167atagen2026-10-16 10:04 UTC176
    let mut t: Tree<u32> = Tree::default();
9baaafa4b167atagen2026-10-16 10:04 UTC177
    t.insert_row(a);
9baaafa4b167atagen2026-10-16 10:04 UTC178
    t.insert_after(d, a);
9baaafa4b167atagen2026-10-16 10:04 UTC179
    t.insert_after(b, d);
9baaafa4b167atagen2026-10-16 10:04 UTC180
    t.insert_row(c);
9baaafa4b167atagen2026-10-16 10:04 UTC181
    t.insert_after(e, c);
9baaafa4b167atagen2026-10-16 10:04 UTC182
    t.check();
9baaafa4b167atagen2026-10-16 10:04 UTC183
    let hidden = |w: u32| w != d && w != e;
9baaafa4b167atagen2026-10-16 10:04 UTC184
    let all = |_| true;
9baaafa4b167atagen2026-10-16 10:04 UTC185
    let rect = |t: &Tree<u32>, w| {
9baaafa4b167atagen2026-10-16 10:04 UTC186
        t.arrange(AREA, 0.0, all)
9baaafa4b167atagen2026-10-16 10:04 UTC187
            .into_iter()
9baaafa4b167atagen2026-10-16 10:04 UTC188
            .find(|p| p.0 == w)
9baaafa4b167atagen2026-10-16 10:04 UTC189
            .unwrap()
9baaafa4b167atagen2026-10-16 10:04 UTC190
            .1
9baaafa4b167atagen2026-10-16 10:04 UTC191
    };
9baaafa4b167atagen2026-10-16 10:04 UTC192
9baaafa4b167atagen2026-10-16 10:04 UTC193
    assert!(t.move_window(a, Dir::Right, hidden));
9baaafa4b167atagen2026-10-16 10:04 UTC194
    assert!(rect(&t, b).x < rect(&t, d).x && rect(&t, d).x < rect(&t, a).x);
9baaafa4b167atagen2026-10-16 10:04 UTC195
9baaafa4b167atagen2026-10-16 10:04 UTC196
    assert!(t.move_window(c, Dir::Up, hidden));
9baaafa4b167atagen2026-10-16 10:04 UTC197
    t.check();
9baaafa4b167atagen2026-10-16 10:04 UTC198
    assert_eq!(rect(&t, e).w, AREA.w, "E is demoted to a row of its own");
9baaafa4b167atagen2026-10-16 10:04 UTC199
}
60f00dd4f2beatagen2026-10-16 11:50 UTC200
60f00dd4f2beatagen2026-10-16 11:50 UTC201
/// Moving out of a row toward a lane of hidden windows joins that lane, so
60f00dd4f2beatagen2026-10-16 11:50 UTC202
/// they share it when shown again instead of a new lane opening between.
60f00dd4f2beatagen2026-10-16 11:50 UTC203
#[test]
60f00dd4f2beatagen2026-10-16 11:50 UTC204
fn moving_toward_a_hidden_lane_joins_it() {
60f00dd4f2beatagen2026-10-16 11:50 UTC205
    let (a, b, c, d, e) = (1, 2, 3, 4, 5);
60f00dd4f2beatagen2026-10-16 11:50 UTC206
    let mut t: Tree<u32> = Tree::default();
60f00dd4f2beatagen2026-10-16 11:50 UTC207
    t.insert_row(a);
60f00dd4f2beatagen2026-10-16 11:50 UTC208
    t.insert_after(b, a);
60f00dd4f2beatagen2026-10-16 11:50 UTC209
    t.insert_after(c, b);
60f00dd4f2beatagen2026-10-16 11:50 UTC210
    t.insert_row(d);
60f00dd4f2beatagen2026-10-16 11:50 UTC211
    t.insert_after(e, c);
60f00dd4f2beatagen2026-10-16 11:50 UTC212
    let tag_two = |w: u32| w != a && w != d;
60f00dd4f2beatagen2026-10-16 11:50 UTC213
    assert!(t.move_window(e, Dir::Down, tag_two));
60f00dd4f2beatagen2026-10-16 11:50 UTC214
    t.check();
60f00dd4f2beatagen2026-10-16 11:50 UTC215
    let all = t.arrange(AREA, 0.0, |_| true);
60f00dd4f2beatagen2026-10-16 11:50 UTC216
    let rect = |w| all.iter().find(|p| p.0 == w).unwrap().1;
60f00dd4f2beatagen2026-10-16 11:50 UTC217
    assert_eq!(rect(e).y, rect(d).y);
60f00dd4f2beatagen2026-10-16 11:50 UTC218
    assert!(rect(d).x < rect(e).x);
60f00dd4f2beatagen2026-10-16 11:50 UTC219
    assert!(rect(a).y < rect(d).y);
60f00dd4f2beatagen2026-10-16 11:50 UTC220
}