use std::time::Duration;
use crate::commands::{Command, Direction, Operation, ResizeDirection};
use crate::config::{
Config, InitialWindowWidth, InitialWindowWidthMode, MainOptions, WindowParams,
};
use crate::ecs::SpawnWindowTrigger;
use crate::ecs::layout::LayoutStrip;
use crate::events::Event;
use crate::{assert_window_at, assert_window_size};
use bevy::math::IRect;
use bevy::prelude::*;
use super::*;
#[test]
fn new_window_starts_at_complement_of_focused_width() {
let mut params = WindowParams::new(".*", None);
params.width = Some(InitialWindowWidth::Mode(
InitialWindowWidthMode::ComplementFocused,
));
let config: Config = (MainOptions::default(), vec![params]).into();
TestHarness::new()
.with_windows(1)
.with_focused_window(0)
.on_iteration(0, move |world, state| {
world.insert_resource(config.clone());
let frame = IRect::new(0, 0, TEST_WINDOW_WIDTH, TEST_WINDOW_HEIGHT);
let window = state.spawn_window(TEST_PROCESS_ID, TEST_WORKSPACE_ID, 1, frame);
world.trigger(SpawnWindowTrigger(vec![window]));
})
.on_iteration(1, |world, _state| {
assert_window_size!(world, 0, TEST_WINDOW_WIDTH, 748);
assert_window_size!(world, 1, TEST_DISPLAY_WIDTH - TEST_WINDOW_WIDTH, 748);
})
.run(vec![
Event::MenuOpened { window_id: 0 },
Event::Command {
command: Command::PrintState,
},
]);
}
#[test]
fn new_window_starts_full_width_after_full_width_focus() {
let mut fixed = WindowParams::new(".*", None);
fixed.width = Some(InitialWindowWidth::Ratio(1.0));
let fixed_config: Config = (MainOptions::default(), vec![fixed]).into();
let mut dynamic = WindowParams::new(".*", None);
dynamic.width = Some(InitialWindowWidth::Mode(
InitialWindowWidthMode::ComplementFocused,
));
let dynamic_config: Config = (MainOptions::default(), vec![dynamic]).into();
TestHarness::new()
.with_config(fixed_config)
.with_windows(1)
.with_focused_window(0)
.on_iteration(0, move |world, state| {
assert_window_size!(world, 0, TEST_DISPLAY_WIDTH, 748);
world.insert_resource(dynamic_config.clone());
let frame = IRect::new(0, 0, TEST_WINDOW_WIDTH, TEST_WINDOW_HEIGHT);
let window = state.spawn_window(TEST_PROCESS_ID, TEST_WORKSPACE_ID, 1, frame);
world.trigger(SpawnWindowTrigger(vec![window]));
})
.on_iteration(1, |world, _state| {
assert_window_size!(world, 1, TEST_DISPLAY_WIDTH, 748);
})
.run(vec![
Event::MenuOpened { window_id: 0 },
Event::Command {
command: Command::PrintState,
},
]);
}
#[test]
fn configured_width_uses_raw_display_bounds_with_edge_padding() {
let options = MainOptions {
padding_left: Some(20),
padding_right: Some(30),
..MainOptions::default()
};
let mut params = WindowParams::new(".*", None);
params.width = Some(InitialWindowWidth::Ratio(0.5));
let config: Config = (options, vec![params]).into();
TestHarness::new()
.with_config(config)
.with_windows(1)
.on_iteration(0, |world, state| {
let frame = IRect::new(0, 0, TEST_WINDOW_WIDTH, TEST_WINDOW_HEIGHT);
let window = state.spawn_window(TEST_PROCESS_ID, TEST_WORKSPACE_ID, 1, frame);
world.trigger(SpawnWindowTrigger(vec![window]));
})
.on_iteration(1, |world, _state| {
assert_window_size!(world, 1, TEST_DISPLAY_WIDTH / 2, 748);
let mut query = world.query::<(&crate::manager::Window, &crate::ecs::WidthRatio)>();
let ratio = query
.iter(world)
.find_map(|(window, ratio)| (window.id() == 1).then_some(ratio.0));
assert_eq!(ratio, Some(0.5));
})
.run(vec![Event::MenuOpened { window_id: 0 }]);
}
#[test]
#[allow(clippy::too_many_lines)]
fn test_window_shuffle() {
const PADDING_LEFT: u16 = 3;
const PADDING_RIGHT: u16 = 5;
const PADDING_TOP: u16 = 7;
const PADDING_BOTTOM: u16 = 9;
const SLIVER_WIDTH: u16 = 5;
let commands = vec![
Event::MenuOpened { window_id: 0 }, Event::Command {
command: Command::PrintState,
},
Event::Command {
command: Command::Window(Operation::Focus(Direction::Last)),
}, Event::Command {
command: Command::Window(Operation::Focus(Direction::First)),
}, Event::Command {
command: Command::Window(Operation::Focus(Direction::East)),
}, Event::Command {
command: Command::Window(Operation::Stack(true)),
}, Event::Command {
command: Command::Window(Operation::Center),
}, Event::Command {
command: Command::Window(Operation::Focus(Direction::East)),
}, Event::Command {
command: Command::Window(Operation::Stack(true)),
}, Event::Command {
command: Command::Window(Operation::Center),
}, Event::Command {
command: Command::PrintState,
}, ];
let logical_width = TEST_WINDOW_WIDTH;
let top_edge = TEST_MENUBAR_HEIGHT + i32::from(PADDING_TOP);
let left_edge = i32::from(PADDING_LEFT);
let right_edge = TEST_DISPLAY_WIDTH - i32::from(PADDING_RIGHT);
let offscreen_right = right_edge - i32::from(SLIVER_WIDTH) + i32::from(PADDING_RIGHT);
let offscreen_left =
left_edge - logical_width + i32::from(SLIVER_WIDTH) - i32::from(PADDING_LEFT);
let centered = (TEST_DISPLAY_WIDTH - logical_width) / 2;
let mut params = WindowParams::new(".*", None);
params.vertical_padding = Some(3);
params.horizontal_padding = Some(2);
let config: Config = (
MainOptions {
padding_left: Some(PADDING_LEFT),
padding_right: Some(PADDING_RIGHT),
padding_top: Some(PADDING_TOP),
padding_bottom: Some(PADDING_BOTTOM),
..Default::default()
},
vec![params],
)
.into();
TestHarness::new()
.with_config(config)
.with_windows(5)
.on_iteration(2, move |world, _state| {
assert_window_at!(world, 0, offscreen_left, top_edge);
assert_window_at!(world, 1, offscreen_left, top_edge);
assert_window_at!(world, 2, right_edge - 3 * logical_width, top_edge);
assert_window_at!(world, 3, right_edge - 2 * logical_width, top_edge);
assert_window_at!(world, 4, right_edge - logical_width, top_edge);
})
.on_iteration(3, move |world, _state| {
assert_window_at!(world, 0, left_edge, top_edge);
assert_window_at!(world, 1, left_edge + logical_width, top_edge);
assert_window_at!(world, 2, left_edge + 2 * logical_width, top_edge);
assert_window_at!(world, 3, offscreen_right, top_edge);
assert_window_at!(world, 4, offscreen_right, top_edge);
})
.on_iteration(6, move |world, _state| {
assert_window_at!(world, 0, centered, top_edge);
assert_window_at!(world, 1, centered, 393);
assert_window_at!(world, 2, centered + logical_width, top_edge);
assert_window_at!(world, 3, offscreen_right, top_edge);
assert_window_at!(world, 4, offscreen_right, top_edge);
})
.on_iteration(10, move |world, _state| {
assert_window_at!(world, 0, centered, top_edge);
assert_window_at!(world, 1, centered, 271);
assert_window_at!(world, 2, centered, 515);
assert_window_at!(world, 3, centered + logical_width, top_edge);
assert_window_at!(world, 4, offscreen_right, top_edge);
})
.run(commands);
}
#[test]
fn test_window_balance() {
let commands = vec![
Event::MenuOpened { window_id: 0 },
Event::Command {
command: Command::Window(Operation::Resize(ResizeDirection::Grow)),
},
Event::Command {
command: Command::Window(Operation::Balance),
},
];
TestHarness::new()
.with_windows(3)
.on_iteration(1, |world, _state| {
assert_window_size!(world, 0, 512, 748);
})
.on_iteration(2, |world, _state| {
assert_window_size!(world, 0, 512, 748);
assert_window_size!(world, 1, 512, 748);
assert_window_size!(world, 2, 512, 748);
})
.run(commands);
}
#[test]
fn test_startup_windows() {
let commands = vec![
Event::MenuOpened { window_id: 0 },
Event::Command {
command: Command::Window(Operation::Focus(Direction::East)),
},
Event::Command {
command: Command::Window(Operation::Focus(Direction::East)),
},
Event::Command {
command: Command::Window(Operation::Focus(Direction::First)),
},
Event::Command {
command: Command::PrintState,
},
];
TestHarness::new()
.with_windows(5)
.on_iteration(4, |world, _state| {
assert_window_at!(world, 0, 0, TEST_MENUBAR_HEIGHT);
assert_window_at!(world, 1, 400, TEST_MENUBAR_HEIGHT);
assert_window_at!(world, 2, 800, TEST_MENUBAR_HEIGHT);
})
.run(commands);
}
#[test]
fn test_window_resize_grow_and_shrink_cycle() {
let commands = vec![
Event::MenuOpened { window_id: 0 },
Event::Command {
command: Command::Window(Operation::Resize(ResizeDirection::Grow)),
},
Event::Command {
command: Command::Window(Operation::Resize(ResizeDirection::Grow)),
},
Event::Command {
command: Command::Window(Operation::Resize(ResizeDirection::Grow)),
},
Event::Command {
command: Command::Window(Operation::Resize(ResizeDirection::Shrink)),
},
];
let config: Config = (
MainOptions {
preset_column_widths: vec![0.25, 0.5, 0.75],
..Default::default()
},
vec![],
)
.into();
TestHarness::new()
.with_config(config)
.with_windows(1)
.on_iteration(1, |world, _state| {
assert_window_size!(world, 0, 512, 748);
})
.on_iteration(2, |world, _state| {
assert_window_size!(world, 0, 768, 748);
})
.on_iteration(3, |world, _state| {
assert_window_size!(world, 0, 256, 748);
})
.on_iteration(4, |world, _state| {
assert_window_size!(world, 0, 768, 748);
})
.run(commands);
}
#[test]
fn test_window_vertical_resize_grow_and_shrink_cycle() {
const VIEWPORT_HEIGHT: i32 = TEST_DISPLAY_HEIGHT - TEST_MENUBAR_HEIGHT;
let commands = vec![
Event::MenuOpened { window_id: 0 }, Event::Command {
command: Command::Window(Operation::Focus(Direction::First)),
}, Event::Command {
command: Command::Window(Operation::Focus(Direction::East)),
}, Event::Command {
command: Command::Window(Operation::Stack(true)),
}, Event::Command {
command: Command::Window(Operation::ResizeVertical(ResizeDirection::Grow)),
}, Event::Command {
command: Command::Window(Operation::ResizeVertical(ResizeDirection::Grow)),
}, Event::Command {
command: Command::Window(Operation::ResizeVertical(ResizeDirection::Shrink)),
}, ];
let config: Config = (
MainOptions {
preset_stack_heights: vec![0.3, 0.5, 0.7],
..Default::default()
},
vec![],
)
.into();
TestHarness::new()
.with_config(config)
.with_windows(2)
.on_iteration(3, |world, _state| {
assert_window_size!(world, 0, TEST_WINDOW_WIDTH, VIEWPORT_HEIGHT / 2);
assert_window_size!(world, 1, TEST_WINDOW_WIDTH, VIEWPORT_HEIGHT / 2);
})
.on_iteration(4, |world, _state| {
assert_window_size!(world, 1, TEST_WINDOW_WIDTH, 524);
assert_window_size!(world, 0, TEST_WINDOW_WIDTH, VIEWPORT_HEIGHT - 524);
})
.on_iteration(5, |world, _state| {
assert_window_size!(world, 1, TEST_WINDOW_WIDTH, 224);
assert_window_size!(world, 0, TEST_WINDOW_WIDTH, VIEWPORT_HEIGHT - 224);
})
.on_iteration(6, |world, _state| {
assert_window_size!(world, 1, TEST_WINDOW_WIDTH, 524);
assert_window_size!(world, 0, TEST_WINDOW_WIDTH, VIEWPORT_HEIGHT - 524);
})
.run(commands);
}
#[test]
fn test_window_vertical_resize_leaves_the_neighbour_its_minimum() {
const VIEWPORT_HEIGHT: i32 = TEST_DISPLAY_HEIGHT - TEST_MENUBAR_HEIGHT;
const MIN_WINDOW_HEIGHT: i32 = 200;
let commands = vec![
Event::MenuOpened { window_id: 0 },
Event::Command {
command: Command::Window(Operation::Focus(Direction::First)),
},
Event::Command {
command: Command::Window(Operation::Focus(Direction::East)),
},
Event::Command {
command: Command::Window(Operation::Stack(true)),
},
Event::Command {
command: Command::Window(Operation::ResizeVertical(ResizeDirection::Grow)),
},
];
let config: Config = (
MainOptions {
preset_stack_heights: vec![0.5, 0.9],
..Default::default()
},
vec![],
)
.into();
TestHarness::new()
.with_config(config)
.with_windows(2)
.on_iteration(4, |world, _state| {
assert_window_size!(
world,
1,
TEST_WINDOW_WIDTH,
VIEWPORT_HEIGHT - MIN_WINDOW_HEIGHT
);
assert_window_size!(world, 0, TEST_WINDOW_WIDTH, MIN_WINDOW_HEIGHT);
})
.run(commands);
}
#[test]
fn test_window_vertical_resize_is_a_noop_outside_a_stack() {
const VIEWPORT_HEIGHT: i32 = TEST_DISPLAY_HEIGHT - TEST_MENUBAR_HEIGHT;
let commands = vec![
Event::MenuOpened { window_id: 0 },
Event::Command {
command: Command::Window(Operation::ResizeVertical(ResizeDirection::Shrink)),
},
];
let config: Config = (
MainOptions {
preset_stack_heights: vec![0.3, 0.5, 0.7],
..Default::default()
},
vec![],
)
.into();
TestHarness::new()
.with_config(config)
.with_windows(2)
.on_iteration(1, |world, _state| {
assert_window_size!(world, 0, TEST_WINDOW_WIDTH, VIEWPORT_HEIGHT);
assert_window_size!(world, 1, TEST_WINDOW_WIDTH, VIEWPORT_HEIGHT);
})
.run(commands);
}
#[test]
fn test_window_can_resize_to_two_display_widths_and_scroll() {
let commands = vec![
Event::MenuOpened { window_id: 0 },
Event::Command {
command: Command::Window(Operation::SetWidth(2.0)),
},
Event::Swipe {
delta: 0.3,
fingers: 3,
},
Event::Command {
command: Command::Window(Operation::Snap),
},
];
let config: Config = (
MainOptions {
swipe_gesture_fingers: Some(3),
animation_speed: Some(10000.0),
..Default::default()
},
vec![],
)
.into();
TestHarness::new()
.with_config(config)
.with_windows(1)
.on_iteration(1, |world, _state| {
assert_window_size!(world, 0, 2048, 748);
assert_oversized_window_is_pannable(world, 0);
})
.on_iteration(2, |world, _state| {
assert_window_size!(world, 0, 2048, 748);
assert_oversized_window_is_pannable(world, 0);
})
.on_iteration(3, |world, _state| {
assert_window_size!(world, 0, 2048, 748);
assert_oversized_window_is_pannable(world, 0);
})
.run(commands);
}
#[test]
fn test_lua_set_width_layout_op_resizes_the_focused_window() {
use crate::types::windowset::LayoutOp;
const VIEWPORT_HEIGHT: i32 = TEST_DISPLAY_HEIGHT - TEST_MENUBAR_HEIGHT;
let commands = vec![Event::Command {
command: Command::Layout(vec![LayoutOp::SetWidth {
window: 0,
ratio: 0.5,
}]),
}];
TestHarness::new()
.with_windows(1)
.on_iteration(2, |world, _state| {
assert_window_size!(world, 0, TEST_DISPLAY_WIDTH / 2, VIEWPORT_HEIGHT);
})
.run(commands);
}
fn assert_oversized_window_is_pannable(world: &mut World, id: i32) {
let mut query = world.query::<&crate::manager::Window>();
let window = query
.iter(world)
.find(|window| window.id() == id)
.expect("window not found");
let x = window.frame().min.x;
assert!(
(-TEST_DISPLAY_WIDTH..=0).contains(&x),
"oversized window must stay within its pannable range, got x={x}"
);
}
#[test]
fn test_floating_window_does_not_hold_a_slot_in_the_strip() {
let commands = vec![
Event::Command {
command: Command::PrintState,
}, Event::Command {
command: Command::Window(Operation::Manage),
}, Event::Command {
command: Command::PrintState,
}, ];
TestHarness::new()
.with_windows(3)
.on_iteration(0, |world, _state| {
assert_eq!(window_x(world, 0), 0);
assert_eq!(window_x(world, 1), TEST_WINDOW_WIDTH);
assert_eq!(window_x(world, 2), 2 * TEST_WINDOW_WIDTH);
})
.on_iteration(2, |world, _state| {
let entity = find_window_entity(0, world);
let mut query = world.query::<&LayoutStrip>();
assert!(
!query.iter(world).any(|strip| strip.contains(entity)),
"a floating window must not stay in any layout strip"
);
assert_eq!(
window_x(world, 1),
0,
"the tiled windows must close the gap the floating one left"
);
assert_eq!(window_x(world, 2), TEST_WINDOW_WIDTH);
})
.run(commands);
}
#[test]
fn test_unordered_window_releases_its_layout_space() {
let mut harness = TestHarness::new().with_windows(3);
harness.advance(Duration::from_millis(100));
assert_eq!(window_x(harness.app.world_mut(), 0), 0);
assert_eq!(window_x(harness.app.world_mut(), 1), TEST_WINDOW_WIDTH);
assert_eq!(window_x(harness.app.world_mut(), 2), TEST_WINDOW_WIDTH * 2);
harness.mock_state.os_vanish_window(1);
harness.advance(Duration::from_millis(1100));
let mut query = harness.app.world_mut().query::<&crate::manager::Window>();
assert!(
query
.iter(harness.app.world())
.all(|window| window.id() != 1),
"unordered window 1 should be removed within periodic check"
);
assert_eq!(window_x(harness.app.world_mut(), 0), 0);
assert_eq!(window_x(harness.app.world_mut(), 2), TEST_WINDOW_WIDTH);
}
#[test]
fn test_rule_floated_window_does_not_hold_a_slot_in_the_strip() {
let commands = vec![
Event::Command {
command: Command::PrintState,
},
Event::Command {
command: Command::PrintState,
},
];
let mut params = WindowParams::new("^Window 1$", None);
params.floating = Some(true);
let config: Config = (MainOptions::default(), vec![params]).into();
TestHarness::new()
.with_config(config)
.with_windows(3)
.on_iteration(1, |world, _state| {
let entity = find_window_entity(1, world);
let mut query = world.query::<&LayoutStrip>();
assert!(
!query.iter(world).any(|strip| strip.contains(entity)),
"a rule-floated window must not stay in any layout strip"
);
assert_eq!(
window_x(world, 2) - window_x(world, 0),
TEST_WINDOW_WIDTH,
"the tiled windows must close the gap the floating one left"
);
})
.run(commands);
}
#[test]
fn test_closing_window_of_live_app_closes_the_gap() {
let commands = vec![
Event::Command {
command: Command::PrintState,
}, Event::Command {
command: Command::PrintState,
}, Event::Command {
command: Command::PrintState,
}, ];
TestHarness::new()
.with_windows(3)
.on_iteration(0, |world, state| {
let left = window_x(world, 0);
assert_eq!(
window_x(world, 2) - left,
2 * TEST_WINDOW_WIDTH,
"three windows should tile side by side before the close"
);
state.os_close_window(1);
})
.on_iteration(2, |world, _state| {
assert!(
!window_exists(world, 1),
"closed window must be dropped from the world"
);
assert_eq!(
window_x(world, 2) - window_x(world, 0),
TEST_WINDOW_WIDTH,
"surviving windows must close the gap left by window 1"
);
})
.run(commands);
}
fn window_x(world: &mut World, id: i32) -> i32 {
let mut query = world.query::<&crate::manager::Window>();
query
.iter(world)
.find(|window| window.id() == id)
.unwrap_or_else(|| panic!("window {id} not found"))
.frame()
.min
.x
}
fn window_exists(world: &mut World, id: i32) -> bool {
let mut query = world.query::<&crate::manager::Window>();
query.iter(world).any(|window| window.id() == id)
}