From 6c5945acaee3f8485e237d6085c1a0f08ecca35f Mon Sep 17 00:00:00 2001 From: LGUG2Z Date: Sat, 31 Oct 2026 17:07:50 -0700 Subject: [PATCH] test(layouts): every layout, area, count, padding and flip as native checks The layouts, flipping, layout_flip, padding, tiling and closing e2e scenarios assert, among other things, that each layout puts one tile per window inside the work area, none over another, that a flip mirrors the layout and that container padding shrinks each tile. That is arithmetic of komorebi-layouts and needs no window manager. komorebi-layouts/tests/layouts_everywhere.rs runs the eight layouts of the layouts scenario over three work areas (one off the origin, one with negative left), 1 to 12 windows and paddings none/0/10/30, and asserts: one tile per window; inside the area; no overlap; tiles fill the area exactly (all but grid); fewer windows give a tile at least as big; one window has the area less padding; padding shrinks every tile; the three flips equal the unflipped tiles mirrored as a set; a vertical flip only moves tiles up or down. Each test names the e2e checks it mirrors. What stays e2e only: that a daemon hands these rectangles to the OS, and that the OS frames agree (borders, client rounding, timing, the layouts scenario's wait for the windows to settle). Verified: cargo test -p komorebi-layouts --test layouts_everywhere, 8 passed (native, Linux). No source change, no bug found. --- komorebi-layouts/tests/layouts_everywhere.rs | 311 +++++++++++++++++++ 1 file changed, 311 insertions(+) create mode 100644 komorebi-layouts/tests/layouts_everywhere.rs diff --git a/komorebi-layouts/tests/layouts_everywhere.rs b/komorebi-layouts/tests/layouts_everywhere.rs new file mode 100644 index 00000000..4e043697 --- /dev/null +++ b/komorebi-layouts/tests/layouts_everywhere.rs @@ -0,0 +1,311 @@ +//! What the `layouts`, `flipping`, `layout_flip`, `padding` and `tiling` end-to-end scenarios +//! assert about where the layout puts the windows, asserted on the arithmetic alone: every layout, +//! with every count of windows the scenarios use (and more), is inside the work area, with no +//! window over another, and mirrored by a flip. +//! +//! Not covered here, and so still end-to-end: that a daemon takes the layout's rectangles to the +//! operating system and that the operating system's frames agree with them (border widths, client +//! rounding, timing). + +use komorebi_layouts::Arrangement; +use komorebi_layouts::Axis; +use komorebi_layouts::DefaultLayout; +use komorebi_layouts::Rect; +use std::num::NonZeroUsize; + +/// The layouts the `layouts` scenario goes through (its `LAYOUTS`). +const LAYOUTS: [DefaultLayout; 8] = [ + DefaultLayout::BSP, + DefaultLayout::Columns, + DefaultLayout::Rows, + DefaultLayout::VerticalStack, + DefaultLayout::HorizontalStack, + DefaultLayout::UltrawideVerticalStack, + DefaultLayout::Grid, + DefaultLayout::RightMainVerticalStack, +]; + +/// The layouts that tile the whole of the work area (`Grid` leaves a gap with a count it cannot +/// fill with whole cells). +fn fills_the_area(layout: DefaultLayout) -> bool { + layout != DefaultLayout::Grid +} + +const AREAS: [Rect; 3] = [ + Rect { + left: 0, + top: 0, + right: 1920, + bottom: 1080, + }, + // a work area that is not at the origin, as one of a second monitor is + Rect { + left: 1920, + top: 40, + right: 1280, + bottom: 984, + }, + // one that is not a nice number + Rect { + left: -1366, + top: 0, + right: 1366, + bottom: 728, + }, +]; + +fn tiles( + layout: DefaultLayout, + area: &Rect, + count: usize, + padding: Option, + flip: Option, +) -> Vec { + layout.calculate( + area, + NonZeroUsize::new(count).unwrap(), + padding, + flip, + &[], + 0, + None, + &[], + ) +} + +fn inside(r: &Rect, area: &Rect) -> bool { + r.left >= area.left + && r.top >= area.top + && r.left + r.right <= area.left + area.right + && r.top + r.bottom <= area.top + area.bottom +} + +fn overlap(a: &Rect, b: &Rect) -> bool { + a.left < b.left + b.right + && b.left < a.left + a.right + && a.top < b.top + b.bottom + && b.top < a.top + a.bottom +} + +fn area_of(r: &Rect) -> i64 { + i64::from(r.right) * i64::from(r.bottom) +} + +/// `r` mirrored in `area` along `axis`. +fn mirrored(r: &Rect, area: &Rect, axis: Axis) -> Rect { + let mut m = *r; + if matches!(axis, Axis::Horizontal | Axis::HorizontalAndVertical) { + m.left = area.left + (area.left + area.right) - (r.left + r.right); + } + if matches!(axis, Axis::Vertical | Axis::HorizontalAndVertical) { + m.top = area.top + (area.top + area.bottom) - (r.top + r.bottom); + } + m +} + +fn sorted(mut v: Vec) -> Vec { + v.sort_by_key(|r| (r.left, r.top, r.right, r.bottom)); + v +} + +// mirrors: layouts / , windows: every window is where the layout says +#[test] +fn every_layout_gives_one_tile_per_window() { + for layout in LAYOUTS { + for area in &AREAS { + for count in 1..=12 { + assert_eq!( + tiles(layout, area, count, None, None).len(), + count, + "{layout}, {count} windows" + ); + } + } + } +} + +// mirrors: layouts / , windows: ... inside the work area +// mirrors: layouts / , windows: ... and not on each other +// mirrors: tiling / is inside the work area; <a> and <b> do not overlap +#[test] +fn every_layout_keeps_every_tile_inside_the_work_area_and_off_the_others() { + for layout in LAYOUTS { + for area in &AREAS { + for count in 1..=12 { + for padding in [None, Some(0), Some(10), Some(30)] { + let t = tiles(layout, area, count, padding, None); + for (i, r) in t.iter().enumerate() { + assert!( + inside(r, area), + "{layout}, {count} windows, padding {padding:?}: tile {i} {r:?} is outside {area:?}" + ); + // BSP halves a tile per window: past eight windows, with padding on each + // side, a tile of a 1080 px high area is legitimately nothing + assert!( + count > 8 || r.right > 0 && r.bottom > 0, + "{layout}, {count} windows, padding {padding:?}: tile {i} {r:?} is empty" + ); + for (j, o) in t.iter().enumerate().skip(i + 1) { + assert!( + !overlap(r, o), + "{layout}, {count} windows, padding {padding:?}: tiles {i} {r:?} and {j} {o:?} overlap" + ); + } + } + } + } + } + } +} + +// mirrors: tiling / the windows share the work area: none takes all of its width +// mirrors: closing / the windows that are left take more room between them +#[test] +fn without_padding_the_tiles_fill_the_work_area_and_fewer_windows_get_more_room() { + for layout in LAYOUTS.into_iter().filter(|l| fills_the_area(*l)) { + for area in &AREAS { + for count in 1..=12 { + let t = tiles(layout, area, count, None, None); + let total: i64 = t.iter().map(area_of).sum(); + assert_eq!( + total, + area_of(area), + "{layout}, {count} windows leave a gap in {area:?}: {t:?}" + ); + } + // one window fewer: each of the others has at least as much room on average + for count in 2..=12 { + let more = tiles(layout, area, count - 1, None, None); + let less = tiles(layout, area, count, None, None); + let biggest = |v: &[Rect]| v.iter().map(area_of).max().unwrap(); + assert!( + biggest(&more) >= biggest(&less), + "{layout}: {count} windows has a bigger tile than {} windows", + count - 1 + ); + } + } + } +} + +// mirrors: layouts / <layout>, 1 windows: ... (a single window has the whole work area, less +// padding) +#[test] +fn a_single_window_has_the_work_area_less_the_container_padding() { + for layout in LAYOUTS { + for area in &AREAS { + assert_eq!(tiles(layout, area, 1, None, None), vec![*area], "{layout}"); + let p = 10; + let t = tiles(layout, area, 1, Some(p), None); + assert_eq!( + t, + vec![Rect { + left: area.left + p, + top: area.top + p, + right: area.right - 2 * p, + bottom: area.bottom - 2 * p, + }], + "{layout}" + ); + } + } +} + +// mirrors: padding / a container padding 30 px more leaves the windows less room +// mirrors: padding / with the padding as it was, the windows ... have the room they had +#[test] +fn container_padding_takes_room_from_every_tile_and_taking_it_away_gives_it_back() { + for layout in LAYOUTS { + let area = &AREAS[0]; + for count in 1..=8 { + let plain = tiles(layout, area, count, None, None); + let padded = tiles(layout, area, count, Some(30), None); + for (i, (a, b)) in plain.iter().zip(&padded).enumerate() { + assert!( + area_of(b) < area_of(a), + "{layout}, {count} windows: tile {i} did not shrink: {a:?} -> {b:?}" + ); + } + assert_eq!( + plain, + tiles(layout, area, count, None, None), + "{layout}: deterministic" + ); + assert_eq!( + plain, + tiles(layout, area, count, Some(0), None), + "{layout}, {count} windows: padding 0 is no padding" + ); + } + } +} + +// mirrors: flipping / after 'flip-layout horizontal' every window is where the mirror of its place is +// mirrors: flipping / flipped again, every window is where it was +// mirrors: layout_flip / <label>: flipped along both axes +#[test] +fn a_flipped_layout_is_the_layout_mirrored_as_a_set_of_tiles() { + for layout in LAYOUTS { + for area in &AREAS { + for count in 1..=8 { + for axis in [ + Axis::Horizontal, + Axis::Vertical, + Axis::HorizontalAndVertical, + ] { + let plain = tiles(layout, area, count, None, None); + let flipped = tiles(layout, area, count, None, Some(axis)); + let want: Vec<Rect> = plain.iter().map(|r| mirrored(r, area, axis)).collect(); + assert_eq!( + sorted(flipped), + sorted(want), + "{layout}, {count} windows, flip {axis}" + ); + } + } + } + } +} + +// mirrors: layout_flip / <label>: ... the windows have only gone from top to bottom +#[test] +fn a_vertical_flip_only_moves_a_tile_up_or_down() { + for layout in LAYOUTS { + let area = &AREAS[0]; + for count in 1..=8 { + let plain = tiles(layout, area, count, None, None); + let flipped = tiles(layout, area, count, None, Some(Axis::Vertical)); + // the same widths and the same columns: each tile has a partner at the same left and width + let columns = |v: &[Rect]| { + sorted( + v.iter() + .map(|r| Rect { + left: r.left, + top: 0, + right: r.right, + bottom: 0, + }) + .collect(), + ) + }; + assert_eq!( + columns(&plain), + columns(&flipped), + "{layout}, {count} windows" + ); + } + } +} + +// mirrors: layouts / <layout>, <n> windows: every window is where the layout says (the layout +// is the same whenever it is asked for, so the daemon can compare with the OS) +#[test] +fn the_layout_is_a_function_of_its_arguments() { + for layout in LAYOUTS { + for count in 1..=8 { + let a = tiles(layout, &AREAS[1], count, Some(10), Some(Axis::Horizontal)); + let b = tiles(layout, &AREAS[1], count, Some(10), Some(Axis::Horizontal)); + assert_eq!(a, b, "{layout}"); + } + } +}