9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 1 | use super::*; |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 2 | use proptest::prelude::*; |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 3 | |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 4 | const AREA: Rect = Rect::new(0.0, 0.0, 1000.0, 600.0); |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 5 | |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 6 | #[derive(Clone, Debug)] |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 7 | enum Op { |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 8 | Add(u8, bool), |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 9 | Remove(u8), |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 10 | Focus(u8), |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 11 | Move(Dir), |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 12 | Block(Dir), |
4444c779eb48 | atagen | 2026-10-16 12:07 UTC | 13 | Merge(Dir), |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 14 | View(u8), |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 15 | ToggleView(u8), |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 16 | Retag(u8, u8), |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 17 | Float(u8), |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 18 | Expand(u8), |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 19 | Flip, |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 20 | Resize(bool, i8), |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 21 | Scratch(u8), |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 22 | Summon, |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 23 | Unplug, |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 24 | Replug, |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 25 | } |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 26 | |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 27 | fn dir() -> impl Strategy<Value = Dir> { |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 28 | prop_oneof![ |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 29 | Just(Dir::Left), |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 30 | Just(Dir::Right), |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 31 | Just(Dir::Up), |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 32 | Just(Dir::Down) |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 33 | ] |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 34 | } |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 35 | |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 36 | fn op() -> impl Strategy<Value = Op> { |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 37 | prop_oneof![ |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 38 | 4 => (0u8..4, prop::bool::weighted(0.15)).prop_map(|(t, f)| Op::Add(t, f)), |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 39 | 1 => any::<u8>().prop_map(Op::Remove), |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 40 | 3 => any::<u8>().prop_map(Op::Focus), |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 41 | 4 => dir().prop_map(Op::Move), |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 42 | 2 => dir().prop_map(Op::Block), |
4444c779eb48 | atagen | 2026-10-16 12:07 UTC | 43 | 3 => dir().prop_map(Op::Merge), |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 44 | 1 => (0u8..4).prop_map(Op::View), |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 45 | 2 => (0u8..4).prop_map(Op::ToggleView), |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 46 | 1 => (any::<u8>(), 0u8..4).prop_map(|(w, t)| Op::Retag(w, t)), |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 47 | 1 => any::<u8>().prop_map(Op::Float), |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 48 | 1 => (0u8..3).prop_map(Op::Expand), |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 49 | 1 => Just(Op::Flip), |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 50 | 1 => (any::<bool>(), any::<i8>()).prop_map(|(a, d)| Op::Resize(a, d)), |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 51 | 1 => any::<u8>().prop_map(Op::Scratch), |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 52 | 1 => Just(Op::Summon), |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 53 | 1 => Just(Op::Unplug), |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 54 | 1 => Just(Op::Replug), |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 55 | ] |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 56 | } |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 57 | |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 58 | fn nth(l: &Layout, i: u8) -> Option<WindowId> { |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 59 | let ids: Vec<WindowId> = l.windows().map(|(id, _)| id).collect(); |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 60 | (!ids.is_empty()).then(|| ids[i as usize % ids.len()]) |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 61 | } |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 62 | |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 63 | fn apply(l: &mut Layout, tags: &[TagId], op: &Op) { |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 64 | let active = l.active_output(); |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 65 | match *op { |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 66 | Op::Add(t, f) => { |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 67 | let w = l.add_window( |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 68 | None, |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 69 | Some(tags[t as usize]), |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 70 | f.then(|| Rect::new(10.0, 10.0, 200.0, 100.0)), |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 71 | ); |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 72 | if l.visible(w) { |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 73 | l.focus(w); |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 74 | } |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 75 | } |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 76 | Op::Remove(i) => nth(l, i).into_iter().for_each(|w| l.remove_window(w)), |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 77 | Op::Focus(i) => { |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 78 | if let Some(w) = nth(l, i).filter(|&w| l.visible(w)) { |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 79 | l.focus(w); |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 80 | } |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 81 | } |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 82 | Op::Move(d) => { |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 83 | l.move_window(d); |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 84 | } |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 85 | Op::Block(d) => { |
4444c779eb48 | atagen | 2026-10-16 12:07 UTC | 86 | l.move_container(d); |
4444c779eb48 | atagen | 2026-10-16 12:07 UTC | 87 | } |
4444c779eb48 | atagen | 2026-10-16 12:07 UTC | 88 | Op::Merge(d) => { |
4444c779eb48 | atagen | 2026-10-16 12:07 UTC | 89 | l.merge_window(d); |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 90 | } |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 91 | Op::View(t) => active.into_iter().for_each(|o| l.view(o, tags[t as usize])), |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 92 | Op::ToggleView(t) => active |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 93 | .into_iter() |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 94 | .for_each(|o| l.toggle_view(o, tags[t as usize])), |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 95 | Op::Retag(i, t) => nth(l, i) |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 96 | .into_iter() |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 97 | .for_each(|w| l.set_tag(w, tags[t as usize])), |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 98 | Op::Float(i) => nth(l, i).into_iter().for_each(|w| l.toggle_floating(w)), |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 99 | Op::Expand(s) => { |
4444c779eb48 | atagen | 2026-10-16 12:07 UTC | 100 | l.expand( |
4444c779eb48 | atagen | 2026-10-16 12:07 UTC | 101 | [ |
4444c779eb48 | atagen | 2026-10-16 12:07 UTC | 102 | ExpandScope::Window, |
4444c779eb48 | atagen | 2026-10-16 12:07 UTC | 103 | ExpandScope::Container, |
4444c779eb48 | atagen | 2026-10-16 12:07 UTC | 104 | ExpandScope::All, |
4444c779eb48 | atagen | 2026-10-16 12:07 UTC | 105 | ][s as usize], |
4444c779eb48 | atagen | 2026-10-16 12:07 UTC | 106 | ); |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 107 | } |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 108 | Op::Flip => active.into_iter().for_each(|o| l.flip(o)), |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 109 | Op::Resize(h, d) => { |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 110 | let axis = if h { Axis::Horizontal } else { Axis::Vertical }; |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 111 | l.resize(axis, f64::from(d) / 200.0); |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 112 | } |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 113 | Op::Scratch(i) => nth(l, i).into_iter().for_each(|w| l.scratchpad_add(w, "s")), |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 114 | Op::Summon => { |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 115 | l.summon("s"); |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 116 | } |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 117 | Op::Unplug => { |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 118 | let b = l.outputs().find(|(_, out)| out.name == "b").map(|(o, _)| o); |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 119 | if let Some(o) = b { |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 120 | l.remove_output(o); |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 121 | } |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 122 | } |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 123 | Op::Replug => { |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 124 | if !l.outputs().any(|(_, out)| out.name == "b") { |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 125 | l.add_output("b", Rect::new(1000.0, 0.0, 800.0, 600.0)); |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 126 | } |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 127 | } |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 128 | } |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 129 | } |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 130 | |
3b1cf5ee2ea2 | atagen | 2026-10-17 13:53 UTC | 131 | /// The area a tiled window gets, zero for floating or unplaced ones. |
3b1cf5ee2ea2 | atagen | 2026-10-17 13:53 UTC | 132 | fn tiled_area(l: &Layout, w: WindowId) -> f64 { |
3b1cf5ee2ea2 | atagen | 2026-10-17 13:53 UTC | 133 | let Some(o) = l.window(w).and_then(|x| x.output) else { |
3b1cf5ee2ea2 | atagen | 2026-10-17 13:53 UTC | 134 | return 0.0; |
3b1cf5ee2ea2 | atagen | 2026-10-17 13:53 UTC | 135 | }; |
3b1cf5ee2ea2 | atagen | 2026-10-17 13:53 UTC | 136 | l.arrange(o) |
3b1cf5ee2ea2 | atagen | 2026-10-17 13:53 UTC | 137 | .tiled |
3b1cf5ee2ea2 | atagen | 2026-10-17 13:53 UTC | 138 | .iter() |
3b1cf5ee2ea2 | atagen | 2026-10-17 13:53 UTC | 139 | .find(|p| p.window == w && p.visible) |
3b1cf5ee2ea2 | atagen | 2026-10-17 13:53 UTC | 140 | .map_or(0.0, |p| p.rect.w * p.rect.h) |
3b1cf5ee2ea2 | atagen | 2026-10-17 13:53 UTC | 141 | } |
3b1cf5ee2ea2 | atagen | 2026-10-17 13:53 UTC | 142 | |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 143 | fn check_geometry(l: &Layout) { |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 144 | for (o, out) in l.outputs() { |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 145 | let arr = l.arrange(o); |
38125b3487d7 | atagen | 2026-10-17 05:02 UTC | 146 | let mut drawn: Vec<WindowId> = arr |
38125b3487d7 | atagen | 2026-10-17 05:02 UTC | 147 | .tiled |
38125b3487d7 | atagen | 2026-10-17 05:02 UTC | 148 | .iter() |
38125b3487d7 | atagen | 2026-10-17 05:02 UTC | 149 | .filter(|p| p.visible) |
38125b3487d7 | atagen | 2026-10-17 05:02 UTC | 150 | .map(|p| p.window) |
38125b3487d7 | atagen | 2026-10-17 05:02 UTC | 151 | .chain(arr.floating.iter().map(|&(w, _)| w)) |
38125b3487d7 | atagen | 2026-10-17 05:02 UTC | 152 | .chain(arr.fullscreen) |
38125b3487d7 | atagen | 2026-10-17 05:02 UTC | 153 | .collect(); |
38125b3487d7 | atagen | 2026-10-17 05:02 UTC | 154 | drawn.sort(); |
38125b3487d7 | atagen | 2026-10-17 05:02 UTC | 155 | drawn.dedup(); |
38125b3487d7 | atagen | 2026-10-17 05:02 UTC | 156 | let mut visible: Vec<WindowId> = l |
38125b3487d7 | atagen | 2026-10-17 05:02 UTC | 157 | .windows() |
38125b3487d7 | atagen | 2026-10-17 05:02 UTC | 158 | .filter(|&(w, win)| win.output == Some(o) && l.visible(w)) |
38125b3487d7 | atagen | 2026-10-17 05:02 UTC | 159 | .map(|(w, _)| w) |
38125b3487d7 | atagen | 2026-10-17 05:02 UTC | 160 | .collect(); |
38125b3487d7 | atagen | 2026-10-17 05:02 UTC | 161 | visible.sort(); |
38125b3487d7 | atagen | 2026-10-17 05:02 UTC | 162 | assert_eq!(drawn, visible, "arrangement disagrees with visible()"); |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 163 | let shown: Vec<Rect> = arr |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 164 | .tiled |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 165 | .iter() |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 166 | .filter(|p| p.visible) |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 167 | .map(|p| p.rect) |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 168 | .collect(); |
1c8e1b4009f5 | atagen | 2026-10-16 23:19 UTC | 169 | for p in arr.tiled.iter().filter(|p| !p.visible) { |
1c8e1b4009f5 | atagen | 2026-10-16 23:19 UTC | 170 | assert!(p.rect.w * p.rect.h <= 1e-6, "hidden {:?} has area", p.rect); |
1c8e1b4009f5 | atagen | 2026-10-16 23:19 UTC | 171 | } |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 172 | for r in &shown { |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 173 | assert!(r.w >= 0.0 && r.h >= 0.0); |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 174 | assert!(r.x >= out.area.x - 1e-6 && r.right() <= out.area.right() + 1e-6); |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 175 | assert!(r.y >= out.area.y - 1e-6 && r.bottom() <= out.area.bottom() + 1e-6); |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 176 | } |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 177 | for (i, a) in shown.iter().enumerate() { |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 178 | for b in &shown[i + 1..] { |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 179 | let ox = a.right().min(b.right()) - a.x.max(b.x); |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 180 | let oy = a.bottom().min(b.bottom()) - a.y.max(b.y); |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 181 | assert!(ox <= 1e-6 || oy <= 1e-6, "{a:?} overlaps {b:?}"); |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 182 | } |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 183 | } |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 184 | } |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 185 | } |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 186 | |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 187 | proptest! { |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 188 | #![proptest_config(ProptestConfig::with_cases(2000))] |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 189 | |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 190 | #[test] |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 191 | fn random_commands_keep_the_model_sound(ops in prop::collection::vec(op(), 1..80)) { |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 192 | let mut l = Layout::default(); |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 193 | l.add_output("a", AREA); |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 194 | l.add_output("b", Rect::new(1000.0, 0.0, 800.0, 600.0)); |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 195 | let tags = [l.homeless(), l.tag("1"), l.tag("2"), l.tag("3")]; |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 196 | for op in &ops { |
3b1cf5ee2ea2 | atagen | 2026-10-17 13:53 UTC | 197 | let moving = matches!(op, Op::Move(_) | Op::Merge(_) | Op::Block(_)); |
3b1cf5ee2ea2 | atagen | 2026-10-17 13:53 UTC | 198 | let shown_before = l.focused().filter(|&w| tiled_area(&l, w) > 1.0); |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 199 | apply(&mut l, &tags, op); |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 200 | l.check(); |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 201 | check_geometry(&l); |
3b1cf5ee2ea2 | atagen | 2026-10-17 13:53 UTC | 202 | if moving && let Some(w) = shown_before { |
3b1cf5ee2ea2 | atagen | 2026-10-17 13:53 UTC | 203 | prop_assert!(tiled_area(&l, w) > 1.0, "{w:?} vanished after {op:?}"); |
3b1cf5ee2ea2 | atagen | 2026-10-17 13:53 UTC | 204 | } |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 205 | } |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 206 | } |
38125b3487d7 | atagen | 2026-10-17 05:02 UTC | 207 | |
38125b3487d7 | atagen | 2026-10-17 05:02 UTC | 208 | #[test] |
38125b3487d7 | atagen | 2026-10-17 05:02 UTC | 209 | fn replugging_restores_what_unplugging_moved(ops in prop::collection::vec(op(), 1..60)) { |
38125b3487d7 | atagen | 2026-10-17 05:02 UTC | 210 | let mut l = Layout::default(); |
38125b3487d7 | atagen | 2026-10-17 05:02 UTC | 211 | l.add_output("a", AREA); |
38125b3487d7 | atagen | 2026-10-17 05:02 UTC | 212 | l.add_output("b", Rect::new(1000.0, 0.0, 800.0, 600.0)); |
38125b3487d7 | atagen | 2026-10-17 05:02 UTC | 213 | let tags = [l.homeless(), l.tag("1"), l.tag("2"), l.tag("3")]; |
38125b3487d7 | atagen | 2026-10-17 05:02 UTC | 214 | for op in &ops { |
38125b3487d7 | atagen | 2026-10-17 05:02 UTC | 215 | apply(&mut l, &tags, op); |
38125b3487d7 | atagen | 2026-10-17 05:02 UTC | 216 | } |
38125b3487d7 | atagen | 2026-10-17 05:02 UTC | 217 | let Some(b) = l.outputs().find(|(_, out)| out.name == "b").map(|(o, _)| o) else { |
38125b3487d7 | atagen | 2026-10-17 05:02 UTC | 218 | return Ok(()); |
38125b3487d7 | atagen | 2026-10-17 05:02 UTC | 219 | }; |
38125b3487d7 | atagen | 2026-10-17 05:02 UTC | 220 | let before = |l: &Layout, o| { |
38125b3487d7 | atagen | 2026-10-17 05:02 UTC | 221 | let out = l.output(o).unwrap(); |
38125b3487d7 | atagen | 2026-10-17 05:02 UTC | 222 | (out.tree.windows().collect::<Vec<_>>(), out.floating.clone(), out.viewed.clone()) |
38125b3487d7 | atagen | 2026-10-17 05:02 UTC | 223 | }; |
38125b3487d7 | atagen | 2026-10-17 05:02 UTC | 224 | let was = before(&l, b); |
38125b3487d7 | atagen | 2026-10-17 05:02 UTC | 225 | l.remove_output(b); |
38125b3487d7 | atagen | 2026-10-17 05:02 UTC | 226 | let b = l.add_output("b", Rect::new(1000.0, 0.0, 800.0, 600.0)); |
38125b3487d7 | atagen | 2026-10-17 05:02 UTC | 227 | l.check(); |
38125b3487d7 | atagen | 2026-10-17 05:02 UTC | 228 | prop_assert_eq!(before(&l, b), was); |
38125b3487d7 | atagen | 2026-10-17 05:02 UTC | 229 | } |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 230 | } |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 231 | |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 232 | /// The hidden-slot rules from planner.html, demo 11. |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 233 | #[test] |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 234 | fn hidden_windows_keep_their_slots() { |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 235 | let (a, b, c, d, e) = (1, 2, 3, 4, 5); |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 236 | let mut t: Tree<u32> = Tree::default(); |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 237 | t.insert_row(a); |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 238 | t.insert_after(d, a); |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 239 | t.insert_after(b, d); |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 240 | t.insert_row(c); |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 241 | t.insert_after(e, c); |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 242 | t.check(); |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 243 | let hidden = |w: u32| w != d && w != e; |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 244 | let all = |_| true; |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 245 | let rect = |t: &Tree<u32>, w| { |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 246 | t.arrange(AREA, 0.0, all) |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 247 | .into_iter() |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 248 | .find(|p| p.0 == w) |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 249 | .unwrap() |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 250 | .1 |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 251 | }; |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 252 | |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 253 | assert!(t.move_window(a, Dir::Right, hidden)); |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 254 | assert!(rect(&t, b).x < rect(&t, d).x && rect(&t, d).x < rect(&t, a).x); |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 255 | |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 256 | assert!(t.move_window(c, Dir::Up, hidden)); |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 257 | t.check(); |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 258 | assert_eq!(rect(&t, e).w, AREA.w, "E is demoted to a row of its own"); |
9baaafa4b167 | atagen | 2026-10-16 10:04 UTC | 259 | } |
60f00dd4f2be | atagen | 2026-10-16 11:50 UTC | 260 | |
60f00dd4f2be | atagen | 2026-10-16 11:50 UTC | 261 | /// Moving out of a row toward a lane of hidden windows joins that lane, so |
60f00dd4f2be | atagen | 2026-10-16 11:50 UTC | 262 | /// they share it when shown again instead of a new lane opening between. |
60f00dd4f2be | atagen | 2026-10-16 11:50 UTC | 263 | #[test] |
60f00dd4f2be | atagen | 2026-10-16 11:50 UTC | 264 | fn moving_toward_a_hidden_lane_joins_it() { |
60f00dd4f2be | atagen | 2026-10-16 11:50 UTC | 265 | let (a, b, c, d, e) = (1, 2, 3, 4, 5); |
60f00dd4f2be | atagen | 2026-10-16 11:50 UTC | 266 | let mut t: Tree<u32> = Tree::default(); |
60f00dd4f2be | atagen | 2026-10-16 11:50 UTC | 267 | t.insert_row(a); |
60f00dd4f2be | atagen | 2026-10-16 11:50 UTC | 268 | t.insert_after(b, a); |
60f00dd4f2be | atagen | 2026-10-16 11:50 UTC | 269 | t.insert_after(c, b); |
60f00dd4f2be | atagen | 2026-10-16 11:50 UTC | 270 | t.insert_row(d); |
60f00dd4f2be | atagen | 2026-10-16 11:50 UTC | 271 | t.insert_after(e, c); |
60f00dd4f2be | atagen | 2026-10-16 11:50 UTC | 272 | let tag_two = |w: u32| w != a && w != d; |
60f00dd4f2be | atagen | 2026-10-16 11:50 UTC | 273 | assert!(t.move_window(e, Dir::Down, tag_two)); |
60f00dd4f2be | atagen | 2026-10-16 11:50 UTC | 274 | t.check(); |
60f00dd4f2be | atagen | 2026-10-16 11:50 UTC | 275 | let all = t.arrange(AREA, 0.0, |_| true); |
60f00dd4f2be | atagen | 2026-10-16 11:50 UTC | 276 | let rect = |w| all.iter().find(|p| p.0 == w).unwrap().1; |
60f00dd4f2be | atagen | 2026-10-16 11:50 UTC | 277 | assert_eq!(rect(e).y, rect(d).y); |
60f00dd4f2be | atagen | 2026-10-16 11:50 UTC | 278 | assert!(rect(d).x < rect(e).x); |
60f00dd4f2be | atagen | 2026-10-16 11:50 UTC | 279 | assert!(rect(a).y < rect(d).y); |
60f00dd4f2be | atagen | 2026-10-16 11:50 UTC | 280 | } |
91957f0ae29e | atagen | 2026-10-17 09:24 UTC | 281 | |
91957f0ae29e | atagen | 2026-10-17 09:24 UTC | 282 | #[test] |
605c44c8c5fc | atagen | 2026-10-17 09:57 UTC | 283 | fn merging_steps_out_of_a_stack_before_joining_another() { |
91957f0ae29e | atagen | 2026-10-17 09:24 UTC | 284 | let (a, b, c) = (1, 2, 3); |
605c44c8c5fc | atagen | 2026-10-17 09:57 UTC | 285 | let stacked = || { |
605c44c8c5fc | atagen | 2026-10-17 09:57 UTC | 286 | let mut t: Tree<u32> = Tree::default(); |
605c44c8c5fc | atagen | 2026-10-17 09:57 UTC | 287 | t.insert_row(a); |
605c44c8c5fc | atagen | 2026-10-17 09:57 UTC | 288 | t.insert_after(b, a); |
605c44c8c5fc | atagen | 2026-10-17 09:57 UTC | 289 | t.insert_after(c, b); |
605c44c8c5fc | atagen | 2026-10-17 09:57 UTC | 290 | assert!(t.merge_window(b, Dir::Left, |_| true)); |
605c44c8c5fc | atagen | 2026-10-17 09:57 UTC | 291 | t |
605c44c8c5fc | atagen | 2026-10-17 09:57 UTC | 292 | }; |
605c44c8c5fc | atagen | 2026-10-17 09:57 UTC | 293 | let rects = |t: &Tree<u32>| { |
605c44c8c5fc | atagen | 2026-10-17 09:57 UTC | 294 | t.check(); |
605c44c8c5fc | atagen | 2026-10-17 09:57 UTC | 295 | let all = t.arrange(AREA, 0.0, |_| true); |
605c44c8c5fc | atagen | 2026-10-17 09:57 UTC | 296 | move |w| all.iter().find(|p: &&(u32, Rect)| p.0 == w).unwrap().1 |
605c44c8c5fc | atagen | 2026-10-17 09:57 UTC | 297 | }; |
605c44c8c5fc | atagen | 2026-10-17 09:57 UTC | 298 | |
605c44c8c5fc | atagen | 2026-10-17 09:57 UTC | 299 | let mut t = stacked(); |
605c44c8c5fc | atagen | 2026-10-17 09:57 UTC | 300 | assert!(t.merge_window(a, Dir::Right, |_| true)); |
605c44c8c5fc | atagen | 2026-10-17 09:57 UTC | 301 | let rect = rects(&t); |
605c44c8c5fc | atagen | 2026-10-17 09:57 UTC | 302 | assert!(rect(b).x < rect(a).x && rect(a).x < rect(c).x); |
605c44c8c5fc | atagen | 2026-10-17 09:57 UTC | 303 | assert_eq!(rect(a).h, AREA.h); |
605c44c8c5fc | atagen | 2026-10-17 09:57 UTC | 304 | assert!(t.merge_window(a, Dir::Right, |_| true)); |
605c44c8c5fc | atagen | 2026-10-17 09:57 UTC | 305 | let rect = rects(&t); |
605c44c8c5fc | atagen | 2026-10-17 09:57 UTC | 306 | assert_eq!(rect(a).x, rect(c).x); |
605c44c8c5fc | atagen | 2026-10-17 09:57 UTC | 307 | |
605c44c8c5fc | atagen | 2026-10-17 09:57 UTC | 308 | let mut t = stacked(); |
91957f0ae29e | atagen | 2026-10-17 09:24 UTC | 309 | assert!(t.merge_window(a, Dir::Up, |_| true)); |
605c44c8c5fc | atagen | 2026-10-17 09:57 UTC | 310 | let rect = rects(&t); |
91957f0ae29e | atagen | 2026-10-17 09:24 UTC | 311 | assert_eq!(rect(a).w, AREA.w); |
91957f0ae29e | atagen | 2026-10-17 09:24 UTC | 312 | assert!(rect(a).bottom() <= rect(b).y && rect(b).y == rect(c).y); |
91957f0ae29e | atagen | 2026-10-17 09:24 UTC | 313 | } |
54559daf11c3 | atagen | 2026-10-17 13:38 UTC | 314 | |
54559daf11c3 | atagen | 2026-10-17 13:38 UTC | 315 | /// A window alone on screen in a container of hidden ones expands among the |
54559daf11c3 | atagen | 2026-10-17 13:38 UTC | 316 | /// windows it is shown beside, not inside the container nobody can see. |
54559daf11c3 | atagen | 2026-10-17 13:38 UTC | 317 | #[test] |
54559daf11c3 | atagen | 2026-10-17 13:38 UTC | 318 | fn expand_skips_containers_of_hidden_windows() { |
54559daf11c3 | atagen | 2026-10-17 13:38 UTC | 319 | let (a, b, c) = (1, 2, 3); |
54559daf11c3 | atagen | 2026-10-17 13:38 UTC | 320 | let mut t: Tree<u32> = Tree::default(); |
54559daf11c3 | atagen | 2026-10-17 13:38 UTC | 321 | t.insert_row(a); |
54559daf11c3 | atagen | 2026-10-17 13:38 UTC | 322 | t.insert_after(b, a); |
54559daf11c3 | atagen | 2026-10-17 13:38 UTC | 323 | t.insert_after(c, b); |
54559daf11c3 | atagen | 2026-10-17 13:38 UTC | 324 | assert!(t.merge_window(a, Dir::Right, |_| true)); |
54559daf11c3 | atagen | 2026-10-17 13:38 UTC | 325 | let shown = |w: u32| w != a; |
54559daf11c3 | atagen | 2026-10-17 13:38 UTC | 326 | assert!(t.toggle_expand(b, 0, 1, shown)); |
54559daf11c3 | atagen | 2026-10-17 13:38 UTC | 327 | assert!(t.maximized(b, shown)); |
54559daf11c3 | atagen | 2026-10-17 13:38 UTC | 328 | let all = t.arrange(AREA, 0.0, shown); |
54559daf11c3 | atagen | 2026-10-17 13:38 UTC | 329 | let rect = |w| all.iter().find(|p| p.0 == w).unwrap().1; |
54559daf11c3 | atagen | 2026-10-17 13:38 UTC | 330 | assert_eq!(rect(b).w, AREA.w); |
54559daf11c3 | atagen | 2026-10-17 13:38 UTC | 331 | } |
3b1cf5ee2ea2 | atagen | 2026-10-17 13:53 UTC | 332 | |
3b1cf5ee2ea2 | atagen | 2026-10-17 13:53 UTC | 333 | /// Joining a stack hands the window's weight to it and leaving hands it back, |
3b1cf5ee2ea2 | atagen | 2026-10-17 13:53 UTC | 334 | /// so going in and out repeatedly leaves every window the size it was. |
3b1cf5ee2ea2 | atagen | 2026-10-17 13:53 UTC | 335 | #[test] |
3b1cf5ee2ea2 | atagen | 2026-10-17 13:53 UTC | 336 | fn stacking_in_and_out_does_not_drift() { |
3b1cf5ee2ea2 | atagen | 2026-10-17 13:53 UTC | 337 | for out in [Dir::Up, Dir::Down, Dir::Right] { |
3b1cf5ee2ea2 | atagen | 2026-10-17 13:53 UTC | 338 | let (a, b, c) = (1, 2, 3); |
3b1cf5ee2ea2 | atagen | 2026-10-17 13:53 UTC | 339 | let mut t: Tree<u32> = Tree::default(); |
3b1cf5ee2ea2 | atagen | 2026-10-17 13:53 UTC | 340 | t.insert_row(a); |
3b1cf5ee2ea2 | atagen | 2026-10-17 13:53 UTC | 341 | t.insert_after(b, a); |
3b1cf5ee2ea2 | atagen | 2026-10-17 13:53 UTC | 342 | t.insert_after(c, b); |
3b1cf5ee2ea2 | atagen | 2026-10-17 13:53 UTC | 343 | assert!(t.merge_window(c, Dir::Left, |_| true)); |
3b1cf5ee2ea2 | atagen | 2026-10-17 13:53 UTC | 344 | assert!(t.merge_window(c, out, |_| true)); |
3b1cf5ee2ea2 | atagen | 2026-10-17 13:53 UTC | 345 | let once = t.arrange(AREA, 0.0, |_| true); |
3b1cf5ee2ea2 | atagen | 2026-10-17 13:53 UTC | 346 | for _ in 0..8 { |
3b1cf5ee2ea2 | atagen | 2026-10-17 13:53 UTC | 347 | t.merge_window(c, Dir::Left, |_| true); |
3b1cf5ee2ea2 | atagen | 2026-10-17 13:53 UTC | 348 | t.merge_window(c, out, |_| true); |
3b1cf5ee2ea2 | atagen | 2026-10-17 13:53 UTC | 349 | } |
3b1cf5ee2ea2 | atagen | 2026-10-17 13:53 UTC | 350 | t.check(); |
3b1cf5ee2ea2 | atagen | 2026-10-17 13:53 UTC | 351 | assert_eq!(t.arrange(AREA, 0.0, |_| true), once, "stepping out {out:?}"); |
3b1cf5ee2ea2 | atagen | 2026-10-17 13:53 UTC | 352 | } |
3b1cf5ee2ea2 | atagen | 2026-10-17 13:53 UTC | 353 | } |