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.
This commit is contained in:
LGUG2Z committed 2026-11-05 15:03:26 -08:00
1 parent acbe816fb4
commit 6c5945acae
1 file changed
+311
@@ -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<i32>,
flip: Option<Axis>,
) -> Vec<Rect> {
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<Rect>) -> Vec<Rect> {
v.sort_by_key(|r| (r.left, r.top, r.right, r.bottom));
v
}
// mirrors: layouts / <layout>, <n> 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 / <layout>, <n> windows: ... inside the work area
// mirrors: layouts / <layout>, <n> windows: ... and not on each other
// mirrors: tiling / <title> 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}");
}
}
}