use std::sync::Arc;
use std::time::Duration;
use bevy::prelude::*;
use objc2_core_foundation::CGPoint;
use crate::commands::{Command, Direction, MoveFocus, Operation};
use crate::config::{Config, MainOptions, WindowParams, parse_command};
use crate::ecs::display::FloatingLayer;
use crate::ecs::{
ActiveWorkspaceMarker, FocusedMarker, ManualStripOffset, NativeFullscreenMarker, Position,
Unmanaged, layout::LayoutStrip,
};
use crate::ecs::{RepositionMarker, SpawnWindowTrigger};
use crate::events::Event;
use crate::manager::{Origin, Size, Window};
use crate::platform::{Modifiers, WinID};
use crate::{assert_focused, assert_window_at, assert_window_size};
use super::*;
#[test]
fn native_fullscreen_transition_removes_window_from_original_strip_without_focus_marker() {
const FULLSCREEN_WORKSPACE_ID: WorkspaceId = TEST_WORKSPACE_ID + 100;
TestHarness::new()
.with_windows(2)
.on_iteration(0, |world, state| {
let focused = world
.query_filtered::<Entity, With<FocusedMarker>>()
.iter(world)
.collect::<Vec<_>>();
for entity in focused {
world.entity_mut(entity).remove::<FocusedMarker>();
}
state.update_window(0, |window| {
window.workspace_id = FULLSCREEN_WORKSPACE_ID;
window.is_full_screen = true;
});
state.activate_workspace(TEST_DISPLAY_ID, FULLSCREEN_WORKSPACE_ID, true);
})
.on_iteration(1, |world, _state| {
let fullscreen_window = find_window_entity(0, world);
let sibling_window = find_window_entity(1, world);
let mut strips = world.query::<(&LayoutStrip, Option<&NativeFullscreenMarker>)>();
let original_strip = strips
.iter(world)
.find_map(|(strip, marker)| {
(strip.id() == TEST_WORKSPACE_ID && marker.is_none()).then_some(strip)
})
.expect("original strip");
assert!(
!original_strip.contains(fullscreen_window),
"fullscreen window must not leave a reserved column in the original strip"
);
assert!(original_strip.contains(sibling_window));
let (fullscreen_strip, fullscreen_marker) = strips
.iter(world)
.find(|(strip, _)| strip.id() == FULLSCREEN_WORKSPACE_ID)
.expect("fullscreen strip");
assert!(fullscreen_strip.contains(fullscreen_window));
assert!(fullscreen_marker.is_some());
})
.on_iteration(2, |world, _state| {
let fullscreen_window = find_window_entity(0, world);
let sibling_window = find_window_entity(1, world);
let mut strips = world.query::<&LayoutStrip>();
let original_strip = strips
.iter(world)
.find(|strip| strip.id() == TEST_WORKSPACE_ID)
.expect("original strip");
assert!(original_strip.contains(fullscreen_window));
assert!(original_strip.contains(sibling_window));
assert_eq!(
original_strip
.index_of(fullscreen_window)
.expect("restored fullscreen window index"),
0
);
assert!(
strips
.iter(world)
.all(|strip| strip.id() != FULLSCREEN_WORKSPACE_ID)
);
})
.run(vec![
Event::Command {
command: Command::PrintState,
},
Event::SpaceChanged,
Event::SpaceDestroyed {
space_id: FULLSCREEN_WORKSPACE_ID,
},
]);
}
#[test]
fn frontmost_floating_window_is_focused_after_setup() {
let mut params = WindowParams::new(".*", None);
params.floating = Some(true);
let config: Config = (MainOptions::default(), vec![params]).into();
TestHarness::new()
.with_config(config)
.with_windows(1)
.with_focused_window(0)
.on_iteration(0, |world, _state| {
assert_focused!(world, 0);
let entity = find_window_entity(0, world);
assert!(world.entity(entity).contains::<Unmanaged>());
})
.run(vec![Event::MenuOpened { window_id: 0 }]);
}
#[test]
fn floating_grid_window_uses_active_display_usable_origin() {
let options = MainOptions {
padding_left: Some(40),
padding_top: Some(15),
..MainOptions::default()
};
let mut params = WindowParams::new(".*", None);
params.floating = Some(true);
params.grid = Some("1:1:0:0:1:1".to_string());
let config: Config = (options, vec![params]).into();
TestHarness::new()
.with_config(config)
.on_iteration(1, |world, state| {
let origin = Origin::new(0, 0);
let size = Size::new(TEST_WINDOW_WIDTH, TEST_WINDOW_HEIGHT);
let frame = IRect::from_corners(origin, origin + size);
let window = state.spawn_window(TEST_PROCESS_ID, TEST_WORKSPACE_ID, 0, frame);
world.trigger(SpawnWindowTrigger(vec![window]));
})
.on_iteration(3, |world, _state| {
assert_window_at!(world, 0, 40, TEST_MENUBAR_HEIGHT + 15);
})
.run(vec![
Event::MenuOpened { window_id: 0 },
Event::Command {
command: Command::PrintState,
},
Event::Command {
command: Command::PrintState,
},
Event::Command {
command: Command::PrintState,
},
]);
}
#[test]
fn test_dont_focus() {
let commands = vec![
Event::MenuOpened { window_id: 0 }, Event::Command {
command: Command::Window(Operation::Focus(Direction::Last)),
}, Event::Command {
command: Command::Window(Operation::Focus(Direction::First)),
}, Event::Command {
command: Command::PrintState,
}, ];
let offscreen_right = TEST_DISPLAY_WIDTH - 5;
let mut params = WindowParams::new(".*", None);
params.dont_focus = Some(true);
params.index = Some(100);
let config: Config = (MainOptions::default(), vec![params]).into();
let harness = TestHarness::new().with_config(config).with_windows(3);
harness
.on_iteration(1, move |world, state| {
let origin = Origin::new(0, 0);
let size = Size::new(TEST_WINDOW_WIDTH, TEST_WINDOW_HEIGHT);
let frame = IRect::from_corners(origin, origin + size);
let window = state.spawn_window(TEST_PROCESS_ID, TEST_WORKSPACE_ID, 3, frame);
world.trigger(SpawnWindowTrigger(vec![window]));
})
.on_iteration(3, move |world, _| {
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);
assert_window_at!(world, 3, offscreen_right, TEST_MENUBAR_HEIGHT);
assert_focused!(world, 0);
})
.run(commands);
}
#[test]
fn test_focus_window_by_number() {
assert!(parse_command(&["window", "focus", "0"]).is_err());
let command = parse_command(&["window", "focus", "2"]).unwrap();
TestHarness::new()
.with_windows(3)
.on_iteration(1, |world, _state| assert_focused!(world, 1))
.on_iteration(2, |world, _state| assert_focused!(world, 1))
.on_iteration(3, |world, _state| assert_focused!(world, 2))
.run(vec![
Event::MenuOpened { window_id: 0 },
Event::Command {
command: command.clone(),
},
Event::Command {
command: Command::Window(Operation::Manage),
},
Event::Command { command },
]);
}
#[test]
fn test_offscreen_windows_preserve_height() {
let expected_height = TEST_DISPLAY_HEIGHT - TEST_MENUBAR_HEIGHT;
let commands = vec![
Event::MenuOpened { window_id: 0 },
Event::Command {
command: Command::Window(Operation::Focus(Direction::First)),
},
];
TestHarness::new()
.with_windows(5)
.on_iteration(1, move |world, _state| {
assert_window_size!(world, 4, TEST_WINDOW_WIDTH, expected_height);
assert_window_size!(world, 3, TEST_WINDOW_WIDTH, expected_height);
assert_window_size!(world, 2, TEST_WINDOW_WIDTH, expected_height);
assert_window_size!(world, 1, TEST_WINDOW_WIDTH, expected_height);
assert_window_size!(world, 0, TEST_WINDOW_WIDTH, expected_height);
})
.run(commands);
}
#[test]
fn test_sliver_smaller_than_edge_padding() {
const PADDING: u16 = 8;
const SLIVER: u16 = 1;
let commands = vec![
Event::MenuOpened { window_id: 0 },
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::Last)),
},
];
let top_edge = TEST_MENUBAR_HEIGHT + i32::from(PADDING);
let right_edge = TEST_DISPLAY_WIDTH - i32::from(PADDING);
let offscreen_right = TEST_DISPLAY_WIDTH - i32::from(SLIVER);
let offscreen_left = i32::from(SLIVER) - TEST_WINDOW_WIDTH;
let left_edge = i32::from(PADDING);
let config: Config = (
MainOptions {
sliver_width: Some(SLIVER),
padding_top: Some(PADDING),
padding_bottom: Some(PADDING),
padding_left: Some(PADDING),
padding_right: Some(PADDING),
..Default::default()
},
vec![],
)
.into();
TestHarness::new()
.with_config(config)
.with_windows(5)
.on_iteration(2, move |world, _state| {
assert_window_at!(world, 0, left_edge, top_edge);
assert_window_at!(world, 1, left_edge + TEST_WINDOW_WIDTH, top_edge);
assert_window_at!(world, 2, left_edge + 2 * TEST_WINDOW_WIDTH, top_edge);
assert_window_at!(world, 3, offscreen_right, top_edge);
assert_window_at!(world, 4, offscreen_right, top_edge);
})
.on_iteration(3, 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 * TEST_WINDOW_WIDTH, top_edge);
assert_window_at!(world, 3, right_edge - 2 * TEST_WINDOW_WIDTH, top_edge);
assert_window_at!(world, 4, right_edge - TEST_WINDOW_WIDTH, top_edge);
})
.run(commands);
}
#[test]
fn test_scrolling() {
let commands = vec![
Event::MenuOpened { window_id: 0 },
Event::Command {
command: Command::Window(Operation::Focus(Direction::Last)),
},
Event::Command {
command: Command::Window(Operation::Focus(Direction::First)),
},
Event::Command {
command: Command::PrintState,
},
Event::Swipe {
delta: 0.04,
fingers: 3,
},
Event::Command {
command: Command::PrintState,
},
];
let config: Config = (
MainOptions {
swipe_gesture_fingers: Some(3),
..Default::default()
},
vec![],
)
.into();
TestHarness::new()
.with_config(config)
.with_windows(3)
.on_iteration(3, move |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);
})
.on_iteration(5, move |world, _state| {
assert_window_at!(world, 0, -186, TEST_MENUBAR_HEIGHT);
assert_window_at!(world, 1, 214, TEST_MENUBAR_HEIGHT);
assert_window_at!(world, 2, 614, TEST_MENUBAR_HEIGHT);
})
.run(commands);
}
#[test]
#[allow(clippy::float_cmp)]
fn test_scrolling_stop() {
let commands = vec![
Event::MenuOpened { window_id: 0 },
Event::Swipe {
delta: 0.3,
fingers: 3,
},
Event::TouchpadDown,
];
let config: Config = (
MainOptions {
swipe_gesture_fingers: Some(3),
..Default::default()
},
vec![],
)
.into();
TestHarness::new()
.with_config(config)
.with_windows(3)
.on_iteration(3, |world, _state| {
use crate::ecs::Scrolling;
let mut query = world.query::<&Scrolling>();
let scroll = query.single(world).unwrap();
assert_eq!(scroll.velocity, 0.0);
assert!(scroll.is_user_swiping);
})
.run(commands);
}
#[test]
fn test_window_hidden_ratio() {
let commands = vec![
Event::MenuOpened { window_id: 0 },
Event::Swipe {
delta: 0.3,
fingers: 3,
},
Event::Command {
command: Command::Window(Operation::Focus(Direction::First)),
},
];
let config: Config = (
MainOptions {
window_hidden_ratio: Some(0.5),
animation_speed: Some(10000.0),
swipe_gesture_fingers: Some(3),
..Default::default()
},
vec![],
)
.into();
TestHarness::new()
.with_config(config)
.with_windows(2)
.on_iteration(2, |world, _state| {
let entity = find_window_entity(0, world);
let window = world.get::<Window>(entity).expect("finding window");
assert!(window.frame().min.x < 0);
})
.run(commands);
}
#[test]
fn test_window_swap_brings_focused_into_view() {
let commands = vec![
Event::MenuOpened { window_id: 0 },
Event::Command {
command: Command::PrintState,
},
Event::Command {
command: Command::Window(Operation::Center),
},
Event::Command {
command: Command::Window(Operation::Swap(Direction::Last)),
},
Event::Command {
command: Command::PrintState,
},
];
let config: Config = (
MainOptions {
animation_speed: Some(10000.0),
..Default::default()
},
vec![],
)
.into();
let centered = (TEST_DISPLAY_WIDTH - TEST_WINDOW_WIDTH) / 2;
let right_edge = TEST_DISPLAY_WIDTH - TEST_WINDOW_WIDTH;
TestHarness::new()
.with_config(config)
.with_windows(5)
.on_iteration(2, move |world, _state| {
assert_window_at!(world, 0, centered, TEST_MENUBAR_HEIGHT);
})
.on_iteration(4, move |world, _state| {
assert_window_at!(world, 0, right_edge, TEST_MENUBAR_HEIGHT);
assert_window_at!(
world,
4,
right_edge - TEST_WINDOW_WIDTH,
TEST_MENUBAR_HEIGHT
);
assert_focused!(world, 0);
})
.run(commands);
}
#[test]
fn test_window_swap_keeps_strip_when_in_view() {
let commands = vec![
Event::MenuOpened { window_id: 0 },
Event::Command {
command: Command::Window(Operation::Focus(Direction::Last)),
},
Event::Command {
command: Command::Window(Operation::Swap(Direction::West)),
},
];
let config: Config = (
MainOptions {
animation_speed: Some(10000.0),
..Default::default()
},
vec![],
)
.into();
TestHarness::new()
.with_config(config)
.with_windows(2)
.on_iteration(2, |world, _state| {
assert_window_at!(world, 1, 0, TEST_MENUBAR_HEIGHT);
assert_window_at!(world, 0, TEST_WINDOW_WIDTH, TEST_MENUBAR_HEIGHT);
assert_focused!(world, 1);
})
.run(commands);
}
#[test]
fn test_rapid_focus_not_swallowed() {
let mut harness = TestHarness::new().with_windows(5);
harness.run(vec![
Event::MenuOpened { window_id: 0 },
Event::Command {
command: Command::Window(Operation::Focus(Direction::Last)),
},
Event::Command {
command: Command::PrintState,
},
]);
assert_focused!(harness.world(), 4);
let focus_west = Event::Command {
command: Command::Window(Operation::Focus(Direction::West)),
};
for _ in 0..3 {
harness
.app
.world_mut()
.write_message::<Event>(focus_west.clone());
harness.app.update();
}
assert_focused!(harness.world(), 1);
}
#[test]
fn test_stale_focus_event_ignored() {
let commands = vec![
Event::MenuOpened { window_id: 0 },
Event::Command {
command: Command::Window(Operation::Focus(Direction::East)),
},
Event::WindowFocused { window_id: 4 },
Event::Command {
command: Command::PrintState,
},
];
TestHarness::new()
.with_windows(5)
.on_iteration(1, |world, _state| {
assert_focused!(world, 1);
})
.on_iteration(2, |world, _state| {
assert_focused!(world, 1);
})
.run(commands);
}
#[test]
fn test_repeated_external_focus_reshuffles_already_focused_window() {
let commands = vec![
Event::MenuOpened { window_id: 0 },
Event::Command {
command: Command::PrintState,
},
Event::Command {
command: Command::PrintState,
},
Event::Command {
command: Command::PrintState,
},
Event::Command {
command: Command::PrintState,
},
];
TestHarness::new()
.with_windows(5)
.on_iteration(1, |world, _state| {
assert_focused!(world, 0);
let mut query = world.query::<(Entity, &LayoutStrip, Has<ActiveWorkspaceMarker>)>();
let (entity, _, _) = query
.iter(world)
.find(|(_, _, active)| *active)
.expect("active strip");
world.commands().entity(entity).insert((
Position(Origin::new(0, 0)),
RepositionMarker(Origin::new(-TEST_DISPLAY_WIDTH, 0)),
));
})
.on_iteration(2, |_world, state| {
state.focus_window(0);
})
.on_iteration(4, |world, _state| {
assert_focused!(world, 0);
assert_window_at!(world, 0, 0, TEST_MENUBAR_HEIGHT);
})
.run(commands);
}
#[test]
fn test_external_focus_reactivates_hidden_virtual_strip_when_marker_is_stale() {
let commands = vec![
Event::Command {
command: Command::PrintState,
},
Event::Command {
command: Command::Window(Operation::VirtualNumber(1)),
},
Event::WindowFocused { window_id: 0 },
Event::Command {
command: Command::PrintState,
},
];
TestHarness::new()
.with_windows(1)
.on_iteration(1, |world, _state| {
let mut query = world.query::<(&LayoutStrip, Has<ActiveWorkspaceMarker>)>();
let active = query
.iter(world)
.find_map(|(strip, active)| active.then_some(strip.virtual_index))
.expect("an active virtual strip");
assert_eq!(active, 1);
assert_focused!(world, 0);
})
.on_iteration(3, |world, _state| {
let mut query = world.query::<(&LayoutStrip, Has<ActiveWorkspaceMarker>)>();
let active = query
.iter(world)
.find_map(|(strip, active)| active.then_some(strip.virtual_index))
.expect("an active virtual strip");
assert_eq!(active, 0);
assert_window_at!(world, 0, 0, TEST_MENUBAR_HEIGHT);
assert_focused!(world, 0);
})
.run(commands);
}
fn focused_window_id(world: &mut World) -> i32 {
let mut q = world.query::<(&Window, Has<crate::ecs::FocusedMarker>)>();
q.iter(world)
.find_map(|(w, f)| f.then_some(w.id()))
.expect("a focused window")
}
fn entity_to_window_id(world: &mut World, entity: Entity) -> i32 {
let mut q = world.query::<(&Window, Entity)>();
q.iter(world)
.find_map(|(w, e)| (e == entity).then_some(w.id()))
.expect("entity must be a Window")
}
fn active_strip_first_id(world: &mut World) -> i32 {
let entity = {
let mut q = world.query_filtered::<&LayoutStrip, With<ActiveWorkspaceMarker>>();
let strip = q.single(world).expect("a single active strip");
strip
.first()
.expect("strip should have a column")
.top()
.expect("column should have a top entity")
};
entity_to_window_id(world, entity)
}
fn active_strip_last_id(world: &mut World) -> i32 {
let entity = {
let mut q = world.query_filtered::<&LayoutStrip, With<ActiveWorkspaceMarker>>();
let strip = q.single(world).expect("a single active strip");
strip
.last()
.expect("strip should have a column")
.top()
.expect("column should have a top entity")
};
entity_to_window_id(world, entity)
}
fn remove_focused_from_all_strips(world: &mut World) {
let entity = {
let mut q = world.query_filtered::<Entity, With<crate::ecs::FocusedMarker>>();
q.single(world).expect("a single focused entity")
};
let mut q = world.query::<&mut LayoutStrip>();
for mut strip in q.iter_mut(world) {
while strip.contains(entity) {
strip.remove(entity);
}
}
}
#[test]
fn test_focus_recovers_when_focused_window_is_outside_strip() {
let commands = vec![
Event::MenuOpened { window_id: 0 },
Event::Command {
command: Command::Window(Operation::Focus(Direction::East)),
},
];
TestHarness::new()
.with_windows(3)
.on_iteration(0, |world, _state| {
remove_focused_from_all_strips(world);
})
.on_iteration(1, |world, _state| {
let focused = focused_window_id(world);
assert_ne!(
focused, 0,
"focus must leave the off-strip window 0 when pressing East",
);
let expected = active_strip_first_id(world);
assert_eq!(
focused, expected,
"East from outside the strip enters at the first (leftmost) column",
);
})
.run(commands);
}
#[test]
fn test_stray_background_tab_is_folded_into_the_visible_tab() {
use bevy::ecs::system::RunSystemOnce as _;
use crate::ecs::{Bounds, Position};
let mut harness = TestHarness::new().with_windows(2);
for _ in 0..3 {
harness.app.update();
}
harness.mock_state.update_window(1, |window| {
window.visible = false;
});
let world = harness.app.world_mut();
let leader = find_window_entity(0, world);
let background = find_window_entity(1, world);
let position = world.get::<Position>(leader).expect("a position").clone();
let bounds = world.get::<Bounds>(leader).expect("bounds").clone();
world.entity_mut(background).insert((position, bounds));
{
let mut strips = world.query_filtered::<&LayoutStrip, With<ActiveWorkspaceMarker>>();
let strip = strips.single(world).expect("one active strip");
assert_eq!(strip.len(), 2, "the tabs start out in columns of their own");
}
world
.run_system_once(crate::ecs::systems::regroup_stray_native_tabs)
.expect("the regrouping system runs");
let mut strips = world.query_filtered::<&LayoutStrip, With<ActiveWorkspaceMarker>>();
let strip = strips.single(world).expect("one active strip");
assert_eq!(strip.len(), 1, "the stray column is gone");
assert!(strip.tabbed(background), "the background tab is a tab now");
assert!(strip.tabbed(leader));
}
#[test]
fn test_on_screen_background_tab_is_folded_into_the_visible_tab() {
use bevy::ecs::system::RunSystemOnce as _;
use crate::ecs::{Bounds, Position};
let mut harness = TestHarness::new().with_windows(2);
for _ in 0..3 {
harness.app.update();
}
harness.mock_state.set_background_tab(1, true);
let world = harness.app.world_mut();
let leader = find_window_entity(0, world);
let background = find_window_entity(1, world);
let position = world.get::<Position>(leader).expect("a position").clone();
let bounds = world.get::<Bounds>(leader).expect("bounds").clone();
world.entity_mut(background).insert((position, bounds));
{
let mut strips = world.query_filtered::<&LayoutStrip, With<ActiveWorkspaceMarker>>();
let strip = strips.single(world).expect("one active strip");
assert_eq!(strip.len(), 2, "the tabs start out in columns of their own");
}
world
.run_system_once(crate::ecs::systems::regroup_stray_native_tabs)
.expect("the regrouping system runs");
let mut strips = world.query_filtered::<&LayoutStrip, With<ActiveWorkspaceMarker>>();
let strip = strips.single(world).expect("one active strip");
assert_eq!(strip.len(), 1, "the stray column is gone");
assert!(strip.tabbed(background), "the background tab is a tab now");
assert!(strip.tabbed(leader));
}
#[test]
fn test_silent_accessibility_list_does_not_fold_visible_windows() {
use bevy::ecs::system::RunSystemOnce as _;
use crate::ecs::{Bounds, Position};
let mut harness = TestHarness::new().with_windows(2);
for _ in 0..3 {
harness.app.update();
}
harness.mock_state.set_background_tab(0, true);
harness.mock_state.set_background_tab(1, true);
let world = harness.app.world_mut();
let leader = find_window_entity(0, world);
let other = find_window_entity(1, world);
let position = world.get::<Position>(leader).expect("a position").clone();
let bounds = world.get::<Bounds>(leader).expect("bounds").clone();
world.entity_mut(other).insert((position, bounds));
world
.run_system_once(crate::ecs::systems::regroup_stray_native_tabs)
.expect("the regrouping system runs");
let mut strips = world.query_filtered::<&LayoutStrip, With<ActiveWorkspaceMarker>>();
let strip = strips.single(world).expect("one active strip");
assert_eq!(strip.len(), 2, "both windows keep their own column");
}
#[test]
fn test_focus_event_follows_the_window_the_app_says_is_focused() {
let commands = vec![
Event::MenuOpened { window_id: 0 },
Event::Command {
command: Command::PrintState,
},
Event::WindowFocused { window_id: 0 },
Event::Command {
command: Command::PrintState,
},
];
TestHarness::new()
.with_windows(2)
.on_iteration(1, |world, state| {
assert_eq!(focused_window_id(world), 0);
state.set_focused_window(1);
})
.on_iteration(3, |world, _state| {
assert_eq!(
focused_window_id(world),
1,
"the focus event must follow the app to the window it actually focused",
);
})
.run(commands);
}
#[test]
fn test_focus_west_from_outside_strip_enters_at_last_column() {
let commands = vec![
Event::MenuOpened { window_id: 0 },
Event::Command {
command: Command::Window(Operation::Focus(Direction::West)),
},
];
TestHarness::new()
.with_windows(3)
.on_iteration(0, |world, _state| {
remove_focused_from_all_strips(world);
})
.on_iteration(1, |world, _state| {
let focused = focused_window_id(world);
let expected = active_strip_last_id(world);
assert_ne!(focused, 0);
assert_eq!(
focused, expected,
"West from outside the strip enters at the last (rightmost) column",
);
})
.run(commands);
}
#[test]
fn test_external_focus_restores_app_hidden_window_to_original_virtual_strip() {
let commands = vec![
Event::Command {
command: Command::PrintState,
},
Event::ApplicationHidden {
pid: TEST_PROCESS_ID,
},
Event::Command {
command: Command::Window(Operation::VirtualNumber(1)),
},
Event::ApplicationVisible {
pid: TEST_PROCESS_ID,
},
Event::WindowFocused { window_id: 0 },
Event::Command {
command: Command::PrintState,
},
];
TestHarness::new()
.with_windows(1)
.on_iteration(2, |world, _state| {
let mut query = world.query::<(&LayoutStrip, Has<ActiveWorkspaceMarker>)>();
let active = query
.iter(world)
.find_map(|(strip, active)| active.then_some(strip.virtual_index))
.expect("an active virtual strip");
assert_eq!(active, 1);
})
.on_iteration(5, |world, _state| {
let mut query = world.query::<(&LayoutStrip, Has<ActiveWorkspaceMarker>)>();
let active = query
.iter(world)
.find_map(|(strip, active)| active.then_some(strip.virtual_index))
.expect("an active virtual strip");
assert_eq!(active, 0);
assert_window_at!(world, 0, 0, TEST_MENUBAR_HEIGHT);
assert_focused!(world, 0);
})
.run(commands);
}
#[test]
fn test_external_focus_restores_hidden_window_without_visible_event() {
let ignored_repositions = Arc::new(std::sync::atomic::AtomicUsize::new(0));
let commands = vec![
Event::Command {
command: Command::PrintState,
},
Event::ApplicationHidden {
pid: TEST_PROCESS_ID,
},
Event::Command {
command: Command::Window(Operation::VirtualNumber(1)),
},
Event::WindowFocused { window_id: 0 },
Event::Command {
command: Command::PrintState,
},
];
TestHarness::new()
.with_windows(1)
.on_iteration(1, move |world, _state| {
let mut query = world.query::<&mut Window>();
let mut window = query
.iter_mut(world)
.find(|window| window.id() == 0)
.expect("window 0");
window.reposition(Origin::new(0, TEST_DISPLAY_HEIGHT));
ignored_repositions.store(1, std::sync::atomic::Ordering::SeqCst);
})
.on_iteration(4, |world, _state| {
let mut query = world.query::<(&LayoutStrip, Has<ActiveWorkspaceMarker>)>();
let active = query
.iter(world)
.find_map(|(strip, active)| active.then_some(strip.virtual_index))
.expect("an active virtual strip");
assert_eq!(active, 0);
assert_window_at!(world, 0, 0, TEST_MENUBAR_HEIGHT);
assert_focused!(world, 0);
})
.run(commands);
}
#[test]
fn mouse_in_bottom_right_corner_does_not_change_focus() {
let commands = vec![
Event::MenuOpened { window_id: 0 },
Event::Command {
command: Command::Window(Operation::Focus(Direction::West)),
},
Event::MouseMoved {
point: CGPoint {
x: 1010.0,
y: 750.0,
},
modifiers: Modifiers::empty(),
},
];
TestHarness::new()
.with_windows(3)
.on_iteration(2, |world, _state| {
assert_focused!(world, 0);
})
.run(commands);
}
#[test]
fn mouse_outside_corner_still_changes_focus() {
use crate::events::Event;
use crate::platform::Modifiers;
use objc2_core_foundation::CGPoint;
let commands = vec![
Event::MenuOpened { window_id: 0 },
Event::Command {
command: Command::Window(Operation::Focus(Direction::West)),
},
Event::MouseMoved {
point: CGPoint { x: 500.0, y: 400.0 },
modifiers: Modifiers::empty(),
},
];
TestHarness::new()
.with_windows(3)
.on_iteration(2, |world, _state| {
assert_focused!(world, 1);
})
.run(commands);
}
#[test]
fn toggle_floating_layer_flips_state() {
fn current_layer(world: &mut World) -> FloatingLayer {
let mut query = world.query::<&FloatingLayer>();
*query
.query(world)
.iter()
.find(|layer| layer.workspace_id == TEST_WORKSPACE_ID)
.expect("active workspace has FloatingLayer")
}
let commands = vec![
Event::Command {
command: Command::PrintState,
},
Event::Command {
command: Command::Window(Operation::ToggleFloatingLayer),
},
Event::Command {
command: Command::Window(Operation::ToggleFloatingLayer),
},
];
TestHarness::new()
.with_config(Config::default())
.with_windows(3)
.on_iteration(0, |world, _state| {
assert!(!current_layer(world).front);
})
.on_iteration(1, |world, _state| {
assert!(current_layer(world).front);
})
.on_iteration(2, |world, _state| {
assert!(!current_layer(world).front);
})
.run(commands);
}
#[test]
fn test_unfloat_after_virtual_switch_uses_active_workspace() {
let commands = vec![
Event::Command {
command: Command::PrintState,
},
Event::Command {
command: Command::Window(Operation::Manage),
},
Event::Command {
command: Command::Window(Operation::VirtualNumber(1)),
},
Event::Command {
command: Command::Window(Operation::Manage),
},
];
TestHarness::new()
.with_windows(2)
.on_iteration(3, |world, _state| {
let entity = find_window_entity(0, world);
let mut query = world.query_filtered::<&LayoutStrip, With<ActiveWorkspaceMarker>>();
let strip = query.single(world).expect("an active virtual workspace");
assert_eq!(strip.virtual_index, 1);
assert!(strip.contains(entity));
})
.run(commands);
}
#[test]
fn focus_unmanaged_ignores_floats_from_other_workspaces() {
let workspaces = vec![TEST_WORKSPACE_ID, TEST_WORKSPACE_ID + 1];
let harness = TestHarness::new()
.with_display(
TEST_DISPLAY_ID,
IRect::new(0, 0, TEST_DISPLAY_WIDTH, TEST_DISPLAY_HEIGHT),
workspaces,
)
.with_workspace_window(0, TEST_WORKSPACE_ID, |_| {})
.with_workspace_window(99, TEST_WORKSPACE_ID + 1, |w| {
w.frame = IRect::new(600, 0, 600 + TEST_WINDOW_WIDTH, TEST_WINDOW_HEIGHT);
});
let commands = vec![
Event::MenuOpened { window_id: 0 },
Event::Command {
command: Command::Window(Operation::FocusUnmanaged),
},
Event::Command {
command: Command::PrintState,
},
Event::Command {
command: Command::Window(Operation::Focus(Direction::East)),
},
];
harness
.on_iteration(2, |world, _state| {
let off_workspace_float = find_window_entity(99, world);
world
.entity_mut(off_workspace_float)
.insert(Unmanaged::Floating);
assert_focused!(world, 0);
})
.on_iteration(3, |world, _state| {
let active_float = find_window_entity(0, world);
world.entity_mut(active_float).insert(Unmanaged::Floating);
assert_focused!(world, 0);
})
.on_iteration(4, |world, _state| {
assert_focused!(world, 0);
})
.run(commands);
}
#[test]
fn test_mid_strip_insertion_preserves_window_x() {
let config: Config = (
MainOptions {
insert_windows_mid_strip: Some(true),
swipe_gesture_fingers: Some(3),
..Default::default()
},
vec![],
)
.into();
let harness = TestHarness::new().with_config(config).with_windows(8);
let commands = vec![
Event::MenuOpened { window_id: 0 },
Event::Command {
command: Command::Window(Operation::VirtualMoveNumber(1, MoveFocus::Stay)),
},
Event::Command {
command: Command::Window(Operation::VirtualMoveNumber(1, MoveFocus::Stay)),
},
Event::Command {
command: Command::Window(Operation::VirtualMoveNumber(1, MoveFocus::Stay)),
},
Event::Command {
command: Command::Window(Operation::VirtualMoveNumber(1, MoveFocus::Stay)),
},
Event::Swipe {
delta: 0.06,
fingers: 3,
},
Event::MenuOpened { window_id: 0 },
Event::Command {
command: Command::Window(Operation::VirtualNumber(1)),
},
Event::Swipe {
delta: 0.04,
fingers: 3,
},
Event::MenuOpened { window_id: 0 },
Event::Command {
command: Command::Window(Operation::VirtualNumber(0)),
},
Event::Command {
command: Command::Window(Operation::VirtualMoveNumber(1, MoveFocus::Follow)),
},
Event::Command {
command: Command::PrintState,
},
];
let previous_offset = std::rc::Rc::new(std::cell::RefCell::new(0));
let previous_offset2 = previous_offset.clone();
harness
.on_iteration(10, move |world, _state| {
let mut q =
world.query_filtered::<(&Window, &Position), With<crate::ecs::FocusedMarker>>();
let (_, position) = q.single(world).expect("a focused window");
previous_offset.replace(position.x);
assert_ne!(position.x, 0);
})
.on_iteration(11, move |world, _state| {
let mut q =
world.query_filtered::<(&Window, &Position), With<crate::ecs::FocusedMarker>>();
let (_, position) = q.single(world).expect("a focused window");
assert_eq!(position.x, previous_offset2.take());
})
.run(commands);
}
#[test]
fn test_move_appends_to_end_by_default() {
let mut h = TestHarness::new().with_windows(3);
let pump = |h: &mut TestHarness, cmd: Command| {
h.app
.world_mut()
.write_message::<Event>(Event::Command { command: cmd });
for _ in 0..6 {
h.app.update();
for event in h.mock_state.drain_events() {
h.app.world_mut().write_message::<Event>(event);
}
}
};
pump(
&mut h,
Command::Window(Operation::VirtualMoveNumber(1, MoveFocus::Stay)),
);
let mover = focused_window_id(h.app.world_mut());
pump(
&mut h,
Command::Window(Operation::VirtualMoveNumber(1, MoveFocus::Follow)),
);
let last = {
let world = h.app.world_mut();
let mut q = world.query::<(&LayoutStrip, Has<ActiveWorkspaceMarker>)>();
let entity = q
.iter(world)
.find_map(|(s, a)| a.then(|| s.all_windows()))
.and_then(|windows| windows.last().copied())
.expect("active strip with windows");
let mut wq = world.query::<(Entity, &Window)>();
wq.iter(world)
.find_map(|(ent, w)| (ent == entity).then_some(w.id()))
.expect("window id")
};
assert_eq!(
last, mover,
"default move should append to the end of the strip"
);
}
#[test]
fn test_follow_move_brings_appended_window_on_screen() {
let mut h = TestHarness::new().with_windows(5);
let pump = |h: &mut TestHarness, cmd: Command| {
h.app
.world_mut()
.write_message::<Event>(Event::Command { command: cmd });
for _ in 0..8 {
h.app.update();
for event in h.mock_state.drain_events() {
h.app.world_mut().write_message::<Event>(event);
}
}
};
for _ in 0..3 {
pump(
&mut h,
Command::Window(Operation::VirtualMoveNumber(1, MoveFocus::Stay)),
);
}
let mover = focused_window_id(h.app.world_mut());
pump(
&mut h,
Command::Window(Operation::VirtualMoveNumber(1, MoveFocus::Follow)),
);
assert_focused!(h.app.world_mut(), mover);
let frame = {
let world = h.app.world_mut();
let mut q = world.query::<&Window>();
q.iter(world)
.find(|w| w.id() == mover)
.expect("moved window")
.frame()
};
assert!(
frame.min.x >= 0 && frame.max.x <= TEST_DISPLAY_WIDTH,
"moved window must be fully on-screen, got frame x {}..{} (display width {})",
frame.min.x,
frame.max.x,
TEST_DISPLAY_WIDTH,
);
}
#[test]
fn test_mid_strip_move_does_not_animate() {
let config: Config = (
MainOptions {
insert_windows_mid_strip: Some(true),
animation_speed: Some(12.0),
virtual_workspace_animations: Some(false),
swipe_gesture_fingers: Some(3),
..Default::default()
},
vec![],
)
.into();
let mut h = TestHarness::new().with_config(config).with_windows(8);
let pump = |h: &mut TestHarness, c: Command| {
h.app
.world_mut()
.write_message::<Event>(Event::Command { command: c });
for _ in 0..8 {
h.app.update();
for e in h.mock_state.drain_events() {
h.app.world_mut().write_message::<Event>(e);
}
}
};
for _ in 0..4 {
pump(
&mut h,
Command::Window(Operation::VirtualMoveNumber(1, MoveFocus::Stay)),
);
}
pump(&mut h, Command::Window(Operation::VirtualNumber(1)));
h.app.world_mut().write_message::<Event>(Event::Swipe {
delta: 0.3,
fingers: 3,
});
for _ in 0..6 {
h.app.update();
for e in h.mock_state.drain_events() {
h.app.world_mut().write_message::<Event>(e);
}
}
pump(&mut h, Command::Window(Operation::VirtualNumber(0)));
h.app.world_mut().write_message::<Event>(Event::Swipe {
delta: 0.3,
fingers: 3,
});
for _ in 0..6 {
h.app.update();
for e in h.mock_state.drain_events() {
h.app.world_mut().write_message::<Event>(e);
}
}
h.app.world_mut().write_message::<Event>(Event::Command {
command: Command::Window(Operation::VirtualMoveNumber(1, MoveFocus::Follow)),
});
for step in 0..10 {
h.app.update();
for e in h.mock_state.drain_events() {
h.app.world_mut().write_message::<Event>(e);
}
let world = h.app.world_mut();
let mut q = world.query_filtered::<&Window, With<RepositionMarker>>();
let animating: Vec<i32> = q.iter(world).map(|w| w.id()).collect();
assert!(
animating.is_empty(),
"step {step}: no window should animate during a mid-strip move, got {animating:?}",
);
}
}
#[test]
fn test_virtual_workspace_switch_no_horizontal_slide_no_animations() {
let config: Config = (
MainOptions {
virtual_workspace_animations: Some(false),
animation_speed: Some(12.0),
swipe_gesture_fingers: Some(3),
..Default::default()
},
vec![],
)
.into();
let mut h = TestHarness::new().with_config(config).with_windows(5);
let pump_event = |h: &mut TestHarness, ev: Event| {
h.app.world_mut().write_message::<Event>(ev);
for _ in 0..8 {
h.app.update();
for e in h.mock_state.drain_events() {
h.app.world_mut().write_message::<Event>(e);
}
}
};
let pump = |h: &mut TestHarness, c: Command| pump_event(h, Event::Command { command: c });
pump(&mut h, Command::PrintState);
pump_event(
&mut h,
Event::Swipe {
delta: 0.3,
fingers: 3,
},
);
let strip_x_after_scroll = {
let world = h.app.world_mut();
let mut q = world.query_filtered::<&Position, With<ActiveWorkspaceMarker>>();
q.single(world)
.expect("exactly one active strip after scroll")
.0
.x
};
assert_ne!(
strip_x_after_scroll, 0,
"test setup: strip should be scrolled to a non-zero position, got 0"
);
pump(&mut h, Command::Window(Operation::VirtualNumber(1)));
h.app.world_mut().write_message::<Event>(Event::Command {
command: Command::Window(Operation::VirtualNumber(0)),
});
for _ in 0..10 {
h.app.update();
for e in h.mock_state.drain_events() {
h.app.world_mut().write_message::<Event>(e);
}
}
let strip_x_final = {
let world = h.app.world_mut();
let mut q = world.query_filtered::<&Position, With<ActiveWorkspaceMarker>>();
q.single(world)
.expect("exactly one active strip after switch-back")
.0
.x
};
assert_eq!(
strip_x_final, strip_x_after_scroll,
"strip x must equal the pre-switch scroll position after VW switch-back (no sideways slide). \
Expected {strip_x_after_scroll}, got {strip_x_final}"
);
}
#[test]
fn test_ensure_visible_snap_does_not_animate_with_animations_off() {
let config: Config = (
MainOptions {
virtual_workspace_animations: Some(false),
animation_speed: Some(12.0),
..Default::default()
},
vec![],
)
.into();
let mut h = TestHarness::new().with_config(config).with_windows(5);
h.app.world_mut().write_message::<Event>(Event::Command {
command: Command::PrintState,
});
for _ in 0..8 {
h.app.update();
for e in h.mock_state.drain_events() {
h.app.world_mut().write_message::<Event>(e);
}
}
let off_screen_window = find_window_entity(4, h.app.world_mut());
h.app
.world_mut()
.entity_mut(off_screen_window)
.insert(crate::ecs::EnsureVisibleMarker { snap: true });
for step in 0..10 {
h.app.update();
for e in h.mock_state.drain_events() {
h.app.world_mut().write_message::<Event>(e);
}
let world = h.app.world_mut();
let mut q = world.query_filtered::<Entity, (With<LayoutStrip>, With<RepositionMarker>)>();
assert!(
q.iter(world).next().is_none(),
"step {step}: strip must never animate when EnsureVisibleMarker::snap is true \
and virtual_workspace_animations is false"
);
}
let world = h.app.world_mut();
let mut q = world.query_filtered::<&Position, With<ActiveWorkspaceMarker>>();
let strip_x = q.single(world).expect("exactly one active strip").0.x;
assert_ne!(
strip_x, 0,
"test setup: the strip must actually have scrolled to expose window 4"
);
}
#[test]
fn test_ensure_visible_without_snap_still_animates() {
let config: Config = (
MainOptions {
virtual_workspace_animations: Some(false),
animation_speed: Some(0.5),
..Default::default()
},
vec![],
)
.into();
let mut h = TestHarness::new().with_config(config).with_windows(5);
h.app.world_mut().write_message::<Event>(Event::Command {
command: Command::PrintState,
});
for _ in 0..8 {
h.app.update();
for e in h.mock_state.drain_events() {
h.app.world_mut().write_message::<Event>(e);
}
}
let off_screen_window = find_window_entity(4, h.app.world_mut());
h.app
.world_mut()
.entity_mut(off_screen_window)
.insert(crate::ecs::EnsureVisibleMarker { snap: false });
h.app.update();
for e in h.mock_state.drain_events() {
h.app.world_mut().write_message::<Event>(e);
}
let world = h.app.world_mut();
let mut q = world.query_filtered::<Entity, (With<LayoutStrip>, With<RepositionMarker>)>();
assert!(
q.iter(world).next().is_some(),
"an ordinary (non-restore) ensure_visible correction must still animate, \
regardless of virtual_workspace_animations"
);
}
#[test]
fn test_snap_strip_marker_forces_snap_for_under_threshold_move() {
let config: Config = (
MainOptions {
virtual_workspace_animations: Some(false),
animation_speed: Some(12.0),
..Default::default()
},
vec![],
)
.into();
let mut h = TestHarness::new().with_config(config).with_windows(3);
let pump = |h: &mut TestHarness, c: Command| {
h.app
.world_mut()
.write_message::<Event>(Event::Command { command: c });
for _ in 0..8 {
h.app.update();
for e in h.mock_state.drain_events() {
h.app.world_mut().write_message::<Event>(e);
}
}
};
pump(&mut h, Command::PrintState);
pump(&mut h, Command::Window(Operation::Focus(Direction::East)));
pump(&mut h, Command::Window(Operation::Stack(true)));
for _ in 0..20 {
h.app.update();
for e in h.mock_state.drain_events() {
h.app.world_mut().write_message::<Event>(e);
}
}
let stack_member = find_window_entity(1, h.app.world_mut());
let strip_entity = {
let world = h.app.world_mut();
let mut q = world.query_filtered::<Entity, With<ActiveWorkspaceMarker>>();
q.single(world).expect("exactly one active strip")
};
assert!(
h.app
.world_mut()
.get::<RepositionMarker>(stack_member)
.is_none(),
"test setup: the stack must have fully settled before perturbing it"
);
h.app
.world_mut()
.get_mut::<Position>(stack_member)
.expect("stack member has a Position")
.0
.y -= 300;
h.app
.world_mut()
.spawn(crate::ecs::workspace::SnapStripMarker {
strip: strip_entity,
});
h.app
.world_mut()
.get_mut::<Position>(strip_entity)
.expect("strip has a Position")
.set_changed();
for _ in 0..5 {
h.app.update();
for e in h.mock_state.drain_events() {
h.app.world_mut().write_message::<Event>(e);
}
}
assert!(
h.app
.world_mut()
.get::<RepositionMarker>(stack_member)
.is_none(),
"a strip named by a live SnapStripMarker must snap its members directly, not animate"
);
}
#[test]
fn test_under_threshold_move_animates_without_snap_strip_marker() {
let config: Config = (
MainOptions {
virtual_workspace_animations: Some(false),
animation_speed: Some(12.0),
..Default::default()
},
vec![],
)
.into();
let mut h = TestHarness::new().with_config(config).with_windows(3);
let pump = |h: &mut TestHarness, c: Command| {
h.app
.world_mut()
.write_message::<Event>(Event::Command { command: c });
for _ in 0..8 {
h.app.update();
for e in h.mock_state.drain_events() {
h.app.world_mut().write_message::<Event>(e);
}
}
};
pump(&mut h, Command::PrintState);
pump(&mut h, Command::Window(Operation::Focus(Direction::East)));
pump(&mut h, Command::Window(Operation::Stack(true)));
for _ in 0..20 {
h.app.update();
for e in h.mock_state.drain_events() {
h.app.world_mut().write_message::<Event>(e);
}
}
let stack_member = find_window_entity(1, h.app.world_mut());
let strip_entity = {
let world = h.app.world_mut();
let mut q = world.query_filtered::<Entity, With<ActiveWorkspaceMarker>>();
q.single(world).expect("exactly one active strip")
};
assert!(
h.app
.world_mut()
.get::<RepositionMarker>(stack_member)
.is_none(),
"test setup: the stack must have fully settled before perturbing it"
);
h.app
.world_mut()
.get_mut::<Position>(stack_member)
.expect("stack member has a Position")
.0
.y -= 300;
h.app
.world_mut()
.get_mut::<Position>(strip_entity)
.expect("strip has a Position")
.set_changed();
let mut saw_reposition_marker = false;
for _ in 0..5 {
h.app.update();
for e in h.mock_state.drain_events() {
h.app.world_mut().write_message::<Event>(e);
}
if h.app
.world_mut()
.get::<RepositionMarker>(stack_member)
.is_some()
{
saw_reposition_marker = true;
break;
}
}
assert!(
saw_reposition_marker,
"without a SnapStripMarker, the under-threshold move must still animate"
);
}
#[test]
fn test_virtual_workspace_switch_restores_focus_without_animations() {
let config: Config = (
MainOptions {
virtual_workspace_animations: Some(false),
..Default::default()
},
vec![],
)
.into();
let mut h = TestHarness::new().with_config(config).with_windows(3);
let pump_event = |h: &mut TestHarness, ev: Event| {
h.app.world_mut().write_message::<Event>(ev);
for _ in 0..8 {
h.app.update();
for e in h.mock_state.drain_events() {
h.app.world_mut().write_message::<Event>(e);
}
}
};
let pump = |h: &mut TestHarness, c: Command| pump_event(h, Event::Command { command: c });
pump(&mut h, Command::PrintState);
pump(
&mut h,
Command::Window(Operation::VirtualMoveNumber(1, MoveFocus::Stay)),
);
let focused_on_vw0 = {
let world = h.app.world_mut();
let mut query = world.query_filtered::<&crate::manager::Window, With<FocusedMarker>>();
query.iter(world).next().map(|w| w.id())
};
assert_eq!(
focused_on_vw0,
Some(1),
"focus should remain on VW0 (shifting to Window 1) after MoveFocus::Stay"
);
pump(&mut h, Command::Window(Operation::VirtualNumber(1)));
let focused_on_vw1 = {
let world = h.app.world_mut();
let mut query = world.query_filtered::<&crate::manager::Window, With<FocusedMarker>>();
query.iter(world).next().map(|w| w.id())
};
assert_eq!(
focused_on_vw1,
Some(0),
"focus should switch to Window 0 when activating VW1 with animations disabled"
);
pump(&mut h, Command::Window(Operation::VirtualNumber(0)));
let focused_back_on_vw0 = {
let world = h.app.world_mut();
let mut query = world.query_filtered::<&crate::manager::Window, With<FocusedMarker>>();
query.iter(world).next().map(|w| w.id())
};
assert_eq!(
focused_back_on_vw0,
Some(1),
"focus should switch back to Window 1 when activating VW0 with animations disabled"
);
}
#[test]
fn test_virtual_workspace_switch_restores_focus_with_animations() {
let config: Config = (
MainOptions {
virtual_workspace_animations: Some(true),
animation_speed: Some(30.0),
..Default::default()
},
vec![],
)
.into();
let mut h = TestHarness::new().with_config(config).with_windows(3);
let pump_event = |h: &mut TestHarness, ev: Event| {
h.app.world_mut().write_message::<Event>(ev);
for _ in 0..8 {
h.app.update();
for e in h.mock_state.drain_events() {
h.app.world_mut().write_message::<Event>(e);
}
}
};
let pump = |h: &mut TestHarness, c: Command| pump_event(h, Event::Command { command: c });
pump(&mut h, Command::PrintState);
pump(
&mut h,
Command::Window(Operation::VirtualMoveNumber(1, MoveFocus::Stay)),
);
let focused_on_vw0 = {
let world = h.app.world_mut();
let mut query = world.query_filtered::<&crate::manager::Window, With<FocusedMarker>>();
query.iter(world).next().map(|w| w.id())
};
assert_eq!(focused_on_vw0, Some(1));
pump(&mut h, Command::Window(Operation::VirtualNumber(1)));
let (focused_on_vw1, guard_count) = {
let world = h.app.world_mut();
let mut query = world.query_filtered::<&crate::manager::Window, With<FocusedMarker>>();
let focused = query.iter(world).next().map(|w| w.id());
let mut guards = world.query::<&crate::ecs::workspace::RestoreFocusMarker>();
let count = guards.iter(world).count();
(focused, count)
};
assert_eq!(
focused_on_vw1,
Some(0),
"focus should switch to Window 0 when activating VW1 with animations enabled"
);
assert_eq!(
guard_count, 1,
"exactly one restore focus guard should be spawned during workspace switch"
);
}
#[test]
fn test_virtual_workspace_switch_stops_in_flight_strip_animation() {
let config: Config = (
MainOptions {
virtual_workspace_animations: Some(false),
animation_speed: Some(12.0),
swipe_gesture_fingers: Some(3),
..Default::default()
},
vec![],
)
.into();
let mut h = TestHarness::new().with_config(config).with_windows(5);
let pump_n = |h: &mut TestHarness, n: usize, ev: Event| {
h.app.world_mut().write_message::<Event>(ev);
for _ in 0..n {
h.app.update();
for e in h.mock_state.drain_events() {
h.app.world_mut().write_message::<Event>(e);
}
}
};
let pump = |h: &mut TestHarness, c: Command| {
pump_n(h, 8, Event::Command { command: c });
};
pump(&mut h, Command::PrintState);
h.app.world_mut().write_message::<Event>(Event::Swipe {
delta: 0.3,
fingers: 3,
});
h.app.update();
for e in h.mock_state.drain_events() {
h.app.world_mut().write_message::<Event>(e);
}
pump(&mut h, Command::Window(Operation::VirtualNumber(1)));
{
let world = h.app.world_mut();
let mut q = world.query_filtered::<Entity, (
With<crate::ecs::layout::LayoutStrip>,
Without<ActiveWorkspaceMarker>,
)>();
for entity in q.iter(world) {
assert!(
world.get::<RepositionMarker>(entity).is_none(),
"hidden strip {entity:?} must not have RepositionMarker after VW switch"
);
}
}
let saved_x = {
h.app.world_mut().write_message::<Event>(Event::Command {
command: Command::Window(Operation::VirtualNumber(0)),
});
for _ in 0..10 {
h.app.update();
for e in h.mock_state.drain_events() {
h.app.world_mut().write_message::<Event>(e);
}
}
let world = h.app.world_mut();
let mut q = world.query_filtered::<&Position, With<ActiveWorkspaceMarker>>();
q.single(world)
.expect("exactly one active strip after restore")
.0
.x
};
{
let world = h.app.world_mut();
let mut q = world.query_filtered::<Entity, (
With<crate::ecs::layout::LayoutStrip>,
With<ActiveWorkspaceMarker>,
)>();
for entity in q.iter(world) {
assert!(
world.get::<RepositionMarker>(entity).is_none(),
"restored strip {entity:?} must not have RepositionMarker (no animation after no-anim VW switch)"
);
}
}
for _ in 0..5 {
h.app.update();
for e in h.mock_state.drain_events() {
h.app.world_mut().write_message::<Event>(e);
}
}
let world = h.app.world_mut();
let mut q = world.query_filtered::<&Position, With<ActiveWorkspaceMarker>>();
let final_x = q.single(world).expect("exactly one active strip").0.x;
assert_eq!(
final_x, saved_x,
"strip x drifted after restore: was {saved_x}, now {final_x}"
);
}
#[test]
fn test_virtual_workspace_switch_focus_echo_does_not_recenter_strip() {
let config: Config = (
MainOptions {
auto_center: Some(true),
virtual_workspace_animations: Some(false),
animation_speed: Some(12.0),
..Default::default()
},
vec![],
)
.into();
let mut h = TestHarness::new().with_config(config).with_windows(6);
let settle = |h: &mut TestHarness| h.advance(Duration::from_secs(1));
let pump = |h: &mut TestHarness, c: Command| {
h.app
.world_mut()
.write_message::<Event>(Event::Command { command: c });
settle(h);
};
let strip_x = |h: &mut TestHarness| -> i32 {
let world = h.app.world_mut();
let mut q = world.query_filtered::<&Position, With<ActiveWorkspaceMarker>>();
q.single(world).expect("exactly one active strip").0.x
};
pump(&mut h, Command::PrintState);
h.mock_state.focus_window(5);
settle(&mut h);
pump(
&mut h,
Command::Window(Operation::VirtualMoveNumber(1, MoveFocus::Stay)),
);
h.mock_state.focus_window(0);
settle(&mut h);
let centered_x = strip_x(&mut h);
let saved_x = centered_x - 250;
{
let world = h.app.world_mut();
let mut q = world.query_filtered::<&mut Position, With<ActiveWorkspaceMarker>>();
q.single_mut(world).expect("exactly one active strip").0.x = saved_x;
}
settle(&mut h);
assert_eq!(
strip_x(&mut h),
saved_x,
"test setup: the displaced strip position must stick"
);
pump(&mut h, Command::Window(Operation::VirtualNumber(1)));
h.mock_state.focus_window(5);
settle(&mut h);
pump(&mut h, Command::Window(Operation::VirtualNumber(0)));
assert_eq!(
strip_x(&mut h),
saved_x,
"strip must restore to its saved position on switch-back"
);
h.mock_state.focus_window(0);
settle(&mut h);
let final_x = strip_x(&mut h);
assert_eq!(
final_x, saved_x,
"late focus acknowledgment re-centered the strip (wiggle): \
restored to {saved_x}, ended at {final_x} (centered would be {centered_x})"
);
}
#[test]
fn test_reshuffle_leftmost_pins_strip_to_left_edge_with_stale_frame() {
use crate::ecs::{Position, ReshuffleAroundMarker};
let config: Config = (
MainOptions {
auto_center: Some(false),
animation_speed: Some(30.0),
continuous_swipe: Some(false),
..Default::default()
},
vec![],
)
.into();
let mut h = TestHarness::new().with_config(config).with_windows(5);
let pump = |h: &mut TestHarness, c: Command| {
h.app
.world_mut()
.write_message::<Event>(Event::Command { command: c });
for _ in 0..10 {
h.app.update();
for e in h.mock_state.drain_events() {
h.app.world_mut().write_message::<Event>(e);
}
}
};
pump(&mut h, Command::PrintState);
let leftmost = find_window_entity(0, h.app.world_mut());
{
let world = h.app.world_mut();
if let Ok(mut e) = world.get_entity_mut(leftmost) {
e.insert(Position(Origin::new(
TEST_DISPLAY_WIDTH - 5,
TEST_MENUBAR_HEIGHT,
)));
e.remove::<RepositionMarker>();
e.insert(ReshuffleAroundMarker);
}
}
for _ in 0..15 {
h.app.update();
for e in h.mock_state.drain_events() {
h.app.world_mut().write_message::<Event>(e);
}
}
let world = h.app.world_mut();
let mut q = world.query_filtered::<&Position, With<ActiveWorkspaceMarker>>();
let strip_x = q.single(world).expect("exactly one active strip").0.x;
assert_eq!(
strip_x, 0,
"reshuffle around leftmost window must pin strip to left edge (offset 0), got {strip_x}"
);
}
#[test]
fn test_virtual_workspace_switch_preserves_scroll_with_animations() {
use Position;
let config: Config = (
MainOptions {
auto_center: Some(false),
animation_speed: Some(30.0),
swipe_gesture_fingers: Some(3),
virtual_workspace_animations: Some(true),
..Default::default()
},
vec![],
)
.into();
let mut h = TestHarness::new().with_config(config).with_windows(5);
let pump_event = |h: &mut TestHarness, ev: Event| {
h.app.world_mut().write_message::<Event>(ev);
for _ in 0..14 {
h.app.update();
for e in h.mock_state.drain_events() {
h.app.world_mut().write_message::<Event>(e);
}
}
};
let pump = |h: &mut TestHarness, c: Command| pump_event(h, Event::Command { command: c });
pump(&mut h, Command::PrintState);
pump_event(
&mut h,
Event::Swipe {
delta: 0.4,
fingers: 3,
},
);
let strip_x_after_scroll = {
let world = h.app.world_mut();
let mut q = world.query_filtered::<&Position, With<ActiveWorkspaceMarker>>();
q.single(world).expect("active strip after scroll").0.x
};
assert_ne!(
strip_x_after_scroll, 0,
"test setup: strip should be scrolled off the left edge, got 0"
);
pump(&mut h, Command::Window(Operation::VirtualNumber(1)));
pump(&mut h, Command::Window(Operation::VirtualNumber(0)));
let strip_x_restored = {
let world = h.app.world_mut();
let mut q = world.query_filtered::<&Position, With<ActiveWorkspaceMarker>>();
q.single(world).expect("active strip after restore").0.x
};
assert_eq!(
strip_x_restored, strip_x_after_scroll,
"animated VW restore must preserve the saved scroll position. \
Expected {strip_x_after_scroll}, got {strip_x_restored}"
);
}
#[test]
fn test_virtual_workspace_switch_hides_old_strip_with_animations() {
use crate::ecs::{Position, layout::LayoutStrip};
let config: Config = (
MainOptions {
auto_center: Some(false),
animation_speed: Some(30.0),
swipe_gesture_fingers: Some(3),
virtual_workspace_animations: Some(true),
..Default::default()
},
vec![],
)
.into();
let mut h = TestHarness::new().with_config(config).with_windows(5);
let pump = |h: &mut TestHarness, c: Command| {
h.app
.world_mut()
.write_message::<Event>(Event::Command { command: c });
for _ in 0..14 {
h.app.update();
for e in h.mock_state.drain_events() {
h.app.world_mut().write_message::<Event>(e);
}
}
};
pump(&mut h, Command::PrintState);
pump(
&mut h,
Command::Window(Operation::VirtualMoveNumber(1, MoveFocus::Stay)),
);
pump(&mut h, Command::Window(Operation::VirtualNumber(1)));
let world = h.app.world_mut();
let mut q = world.query::<(&LayoutStrip, &Position, Has<ActiveWorkspaceMarker>)>();
let inactive_x = q
.iter(world)
.find_map(|(_, pos, active)| (!active).then_some(pos.0.x))
.expect("an inactive strip exists after switching workspaces");
assert!(
inactive_x >= TEST_DISPLAY_WIDTH - 10,
"inactive strip must be parked off-screen (>= {}), got {inactive_x}",
TEST_DISPLAY_WIDTH - 10
);
}
#[test]
fn test_stack_unstack_brings_focused_window_into_view() {
fn check_if_offscreen(world: &mut World, _state: MockState) {
let mut q = world.query_filtered::<(&Window, &Position), With<crate::ecs::FocusedMarker>>();
let (_, position) = q.single(world).expect("a focused window");
assert!(
position.x < -(TEST_WINDOW_WIDTH / 4),
"focused window should be somewhat offscreen after the scroll."
);
}
let config: Config = (
MainOptions {
focus_follows_mouse: Some(false),
auto_center: Some(false),
animation_speed: Some(10000.0),
swipe_gesture_fingers: Some(3),
..Default::default()
},
vec![],
)
.into();
let harness = TestHarness::new().with_config(config).with_windows(5);
let commands = vec![
Event::MenuOpened { window_id: 0 },
Event::Command {
command: Command::Window(Operation::Focus(Direction::East)),
},
Event::Swipe {
delta: 0.3,
fingers: 3,
},
Event::MenuOpened { window_id: 0 },
Event::Command {
command: Command::Window(Operation::Stack(true)),
},
Event::Swipe {
delta: 0.1,
fingers: 3,
},
Event::MenuOpened { window_id: 0 },
Event::Command {
command: Command::Window(Operation::Stack(false)),
},
];
harness
.on_iteration(3, check_if_offscreen)
.on_iteration(4, |world, _state| {
assert_window_at!(world, 0, 0, 20);
assert_window_at!(world, 1, 0, 394);
})
.on_iteration(5, check_if_offscreen)
.on_iteration(7, |world, _state| {
assert_window_at!(world, 1, 0, 20);
})
.run(commands);
}
#[test]
fn test_app_self_activation_keeps_window_parked_on_hidden_virtual_row() {
fn parked_state(world: &mut World) -> (Origin, Origin) {
let entity = find_window_entity(0, world);
let mut strips = world.query::<(&LayoutStrip, &Position, Has<ActiveWorkspaceMarker>)>();
let (strip_position, active) = strips
.iter(world)
.find_map(|(strip, position, active)| {
(strip.virtual_index == 1 && strip.contains(entity)).then_some((position.0, active))
})
.expect("window 0 parked on the hidden virtual row");
assert!(!active, "virtual row 1 must not be the active one");
let mut windows = world.query_filtered::<&Position, With<Window>>();
let window_position = windows.get(world, entity).expect("window 0 position").0;
(window_position, strip_position)
}
let commands = vec![
Event::MenuOpened { window_id: 0 },
Event::Command {
command: Command::Window(Operation::VirtualMoveNumber(1, MoveFocus::Stay)),
},
Event::Command {
command: Command::PrintState,
},
Event::ApplicationHidden {
pid: TEST_PROCESS_ID,
},
Event::ApplicationVisible {
pid: TEST_PROCESS_ID,
},
Event::Command {
command: Command::PrintState,
},
];
let parked = std::rc::Rc::new(std::cell::RefCell::new(None));
let parked_after = parked.clone();
TestHarness::new()
.with_windows(2)
.on_iteration(2, move |world, _state| {
parked.replace(Some(parked_state(world)));
})
.on_iteration(5, move |world, _state| {
let (window_before, strip_before) =
parked_after.borrow().expect("parked state was captured");
let (window_after, strip_after) = parked_state(world);
assert_eq!(
window_after, window_before,
"parked window must keep its off-screen frame across the hide/show cycle"
);
assert_eq!(
strip_after, strip_before,
"hidden virtual row must not be dragged back on screen"
);
})
.run(commands);
}
#[test]
fn test_foreign_window_move_is_adopted() {
let commands = vec![
Event::MenuOpened { window_id: 0 },
Event::Command {
command: Command::PrintState,
},
Event::Command {
command: Command::PrintState,
},
];
let config: Config = (
MainOptions {
animation_speed: Some(10000.0),
..Default::default()
},
vec![],
)
.into();
TestHarness::new()
.with_config(config)
.with_windows(2)
.on_iteration(0, |_world, state| {
state.os_move_window(0, Origin::new(77, 88));
})
.on_iteration(2, |world, _state| {
let entity = find_window_entity(0, world);
let position = world.get::<Position>(entity).expect("window position");
assert_eq!(
position.0,
Origin::new(77, 88),
"a move paneru did not make must be read back into the layout"
);
})
.run(commands);
}
#[test]
fn test_own_window_move_echo_is_ignored() {
use bevy::ecs::system::RunSystemOnce as _;
let mut harness = TestHarness::new().with_windows(2);
harness.app.update();
let state = harness.mock_state.clone();
let world = harness.world();
let entity = find_window_entity(0, world);
let before = world.get::<Position>(entity).expect("window position").0;
world
.entity_mut(entity)
.insert(RepositionMarker(Origin::new(5000, before.y)));
state.os_move_window(0, Origin::new(before.x, before.y + 888));
world.write_message(Event::WindowMoved { window_id: 0 });
world
.run_system_once(crate::ecs::systems::window_moved_update_frame)
.expect("running window_moved_update_frame");
assert_eq!(
world.get::<Position>(entity).expect("window position").0,
before,
"the echo of our own move must not be read back over the animation"
);
}
#[test]
fn test_virtual_directions_first_last_east_west() {
use crate::config::{Config, MainOptions};
let config: Config = (
MainOptions {
reap_empty_workspaces: Some(false),
..Default::default()
},
vec![],
)
.into();
let commands = vec![
Event::Command {
command: Command::Window(Operation::VirtualAdd),
},
Event::Command {
command: Command::Window(Operation::VirtualAdd),
},
Event::Command {
command: Command::Window(Operation::Virtual(Direction::First)),
},
Event::Command {
command: Command::Window(Operation::Virtual(Direction::East)),
},
Event::Command {
command: Command::Window(Operation::Virtual(Direction::Last)),
},
Event::Command {
command: Command::Window(Operation::Virtual(Direction::West)),
},
Event::Command {
command: Command::Window(Operation::Virtual(Direction::First)),
},
Event::Command {
command: Command::Window(Operation::VirtualMove(Direction::Last, MoveFocus::Follow)),
},
Event::Command {
command: Command::Window(Operation::VirtualMove(Direction::First, MoveFocus::Follow)),
},
];
let assert_active_vw = |expected: u32| {
move |world: &mut World, _state: MockState| {
let mut query = world.query::<(&LayoutStrip, Has<ActiveWorkspaceMarker>)>();
let active = query
.iter(world)
.find_map(|(strip, active)| active.then_some(strip.virtual_index))
.expect("an active virtual strip");
assert_eq!(active, expected);
}
};
TestHarness::new()
.with_config(config)
.with_windows(6)
.on_iteration(2, assert_active_vw(0))
.on_iteration(3, assert_active_vw(1))
.on_iteration(4, assert_active_vw(2))
.on_iteration(5, assert_active_vw(1))
.on_iteration(6, assert_active_vw(0))
.on_iteration(7, assert_active_vw(2))
.on_iteration(8, assert_active_vw(0))
.run(commands);
}
#[test]
fn test_auto_discover_unmanaged_focused_window() {
let events = vec![
Event::Command {
command: Command::PrintState,
},
Event::WindowFocused { window_id: 1 },
];
TestHarness::new()
.with_windows(1)
.on_iteration(0, |world, state| {
let mut query = world.query::<&crate::manager::Window>();
let is_managed = query.iter(world).any(|w| w.id() == 1);
assert!(!is_managed, "Window 1 should not be managed yet");
state.spawn_window(
TEST_PROCESS_ID,
TEST_WORKSPACE_ID,
1,
bevy::math::IRect::from_corners(
bevy::math::IVec2::new(0, 0),
bevy::math::IVec2::new(400, 400),
),
);
state.focus_window(1);
})
.on_iteration(1, |world, _state| {
let mut query = world.query::<&crate::manager::Window>();
let is_managed = query.iter(world).any(|w| w.id() == 1);
assert!(
is_managed,
"Window 1 should be auto-discovered and managed in ECS after receiving focus"
);
})
.run(events);
}
fn manual_offset_config() -> Config {
(
MainOptions {
auto_center: Some(false),
continuous_swipe: Some(false),
animation_speed: Some(10000.0),
swipe_gesture_fingers: Some(3),
..Default::default()
},
vec![],
)
.into()
}
const CENTERED_X: i32 = (TEST_DISPLAY_WIDTH - TEST_WINDOW_WIDTH) / 2;
fn active_strip_entity(world: &mut World) -> Entity {
let mut query =
world.query_filtered::<Entity, (With<LayoutStrip>, With<ActiveWorkspaceMarker>)>();
query.single(world).expect("exactly one active strip")
}
fn has_manual_offset(world: &mut World) -> bool {
let entity = active_strip_entity(world);
world.get::<ManualStripOffset>(entity).is_some()
}
fn window_x(world: &mut World, id: WinID) -> i32 {
let mut query = world.query::<&Window>();
query
.iter(world)
.find(|window| window.id() == id)
.expect("window not found")
.frame()
.min
.x
}
#[test]
fn test_center_survives_repeated_focus_event() {
let commands = vec![
Event::MenuOpened { window_id: 0 },
Event::Command {
command: Command::Window(Operation::Center),
},
Event::Command {
command: Command::PrintState,
},
];
TestHarness::new()
.with_config(manual_offset_config())
.with_windows(5)
.on_iteration(1, |world, state| {
assert_eq!(window_x(world, 0), CENTERED_X, "window 0 must be centered");
state.focus_window(0);
})
.on_iteration(2, |world, _state| {
assert_eq!(
window_x(world, 0),
CENTERED_X,
"a repeated focus event must not undo the centering"
);
assert!(
has_manual_offset(world),
"the manual offset must survive a repeated focus event"
);
})
.run(commands);
}
#[test]
fn test_center_dropped_when_window_added() {
let commands = vec![
Event::MenuOpened { window_id: 0 },
Event::Command {
command: Command::Window(Operation::Center),
},
Event::Command {
command: Command::PrintState,
},
Event::Command {
command: Command::Window(Operation::Focus(Direction::East)),
},
];
TestHarness::new()
.with_config(manual_offset_config())
.with_windows(5)
.on_iteration(1, |world, state| {
assert_eq!(window_x(world, 0), CENTERED_X, "window 0 must be centered");
let window = state.spawn_window(
TEST_PROCESS_ID,
TEST_WORKSPACE_ID,
5,
IRect::new(0, 0, TEST_WINDOW_WIDTH, TEST_WINDOW_HEIGHT),
);
world.trigger(SpawnWindowTrigger(vec![window]));
})
.on_iteration(3, |world, _state| {
assert_eq!(
window_x(world, 0),
0,
"a reshuffle on a changed layout must re-derive the offset"
);
assert!(
!has_manual_offset(world),
"adding a window must invalidate the manual offset"
);
})
.run(commands);
}
#[test]
fn test_partly_hidden_window_still_scrolled_into_view_after_center() {
let commands = vec![
Event::MenuOpened { window_id: 0 },
Event::Command {
command: Command::Window(Operation::Center),
},
Event::Command {
command: Command::PrintState,
},
Event::Command {
command: Command::PrintState,
},
];
TestHarness::new()
.with_config(manual_offset_config())
.with_windows(5)
.on_iteration(1, |world, _state| {
assert_eq!(window_x(world, 0), CENTERED_X, "window 0 must be centered");
let strip_entity = active_strip_entity(world);
let displaced = {
let position = world.get::<Position>(strip_entity).expect("strip position");
Origin::new(position.0.x - TEST_WINDOW_WIDTH, position.0.y)
};
world.entity_mut(strip_entity).insert(Position(displaced));
})
.on_iteration(2, |_world, state| {
state.focus_window(0);
})
.on_iteration(3, |world, _state| {
assert!(
window_x(world, 0) >= 0,
"a window scrolled off the left edge must be brought back into view, got {}",
window_x(world, 0)
);
assert!(
!has_manual_offset(world),
"a manual offset that hides its own window is stale"
);
})
.run(commands);
}
#[test]
fn test_center_dropped_on_virtual_workspace_switch() {
let commands = vec![
Event::MenuOpened { window_id: 0 },
Event::Command {
command: Command::Window(Operation::Center),
},
Event::Command {
command: Command::Window(Operation::VirtualMoveNumber(1, MoveFocus::Follow)),
},
Event::Command {
command: Command::Window(Operation::VirtualNumber(0)),
},
];
TestHarness::new()
.with_config(manual_offset_config())
.with_windows(5)
.on_iteration(1, |world, _state| {
assert!(has_manual_offset(world), "center must mark the strip");
})
.on_iteration(3, |world, _state| {
assert!(
!has_manual_offset(world),
"a workspace switch must invalidate the manual offset"
);
})
.run(commands);
}
#[test]
fn test_center_dropped_on_user_swipe() {
let commands = vec![
Event::MenuOpened { window_id: 0 },
Event::Command {
command: Command::Window(Operation::Center),
},
Event::Swipe {
delta: -0.2,
fingers: 3,
},
];
TestHarness::new()
.with_config(manual_offset_config())
.with_windows(5)
.on_iteration(1, |world, _state| {
assert!(has_manual_offset(world), "center must mark the strip");
})
.on_iteration(2, |world, _state| {
assert!(
!has_manual_offset(world),
"a user swipe must invalidate the manual offset"
);
})
.run(commands);
}
#[test]
fn test_focus_into_hidden_virtual_workspace_keeps_target_window() {
let config: Config = (
MainOptions {
animation_speed: Some(30.0),
virtual_workspace_animations: Some(true),
..Default::default()
},
vec![],
)
.into();
let commands = vec![
Event::MenuOpened { window_id: 0 },
Event::Command {
command: Command::PrintState,
},
Event::Command {
command: Command::Window(Operation::VirtualMoveNumber(1, MoveFocus::Stay)),
},
Event::Command {
command: Command::PrintState,
},
Event::Command {
command: Command::Window(Operation::VirtualMoveNumber(1, MoveFocus::Stay)),
},
Event::Command {
command: Command::Window(Operation::VirtualNumber(1)),
},
Event::Command {
command: Command::PrintState,
},
Event::Command {
command: Command::Window(Operation::VirtualNumber(0)),
},
Event::Command {
command: Command::PrintState,
},
];
TestHarness::new()
.with_config(config)
.with_windows(4)
.on_iteration(0, |_world, state| {
state.focus_window(2);
})
.on_iteration(1, |world, _state| {
assert_focused!(world, 2);
})
.on_iteration(2, |_world, state| {
state.focus_window(3);
})
.on_iteration(3, |world, _state| {
assert_focused!(world, 3);
})
.on_iteration(5, |_world, state| {
state.focus_window(3);
})
.on_iteration(6, |world, _state| {
assert_focused!(world, 3);
})
.on_iteration(7, |_world, state| {
state.focus_window(2);
})
.on_iteration(8, |world, _state| {
assert_focused!(world, 2);
})
.run(commands);
}
#[test]
fn test_focus_into_hidden_virtual_workspace_exposes_target_window() {
const OVERHANG: i32 = TEST_WINDOW_WIDTH / 4;
let config: Config = (
MainOptions {
animation_speed: Some(10000.0),
virtual_workspace_animations: Some(true),
auto_center: Some(false),
window_hidden_ratio: Some(0.5),
..Default::default()
},
vec![],
)
.into();
let mut commands = vec![Event::MenuOpened { window_id: 0 }];
for _ in 0..4 {
commands.push(Event::Command {
command: Command::PrintState,
});
commands.push(Event::Command {
command: Command::Window(Operation::VirtualMoveNumber(1, MoveFocus::Stay)),
});
}
commands.extend([
Event::Command {
command: Command::Window(Operation::VirtualNumber(1)),
},
Event::Command {
command: Command::PrintState,
},
Event::Command {
command: Command::Window(Operation::VirtualNumber(0)),
},
Event::Command {
command: Command::PrintState,
},
]);
let focus_next = |id: WinID| move |_world: &mut World, state: MockState| state.focus_window(id);
TestHarness::new()
.with_config(config)
.with_windows(6)
.on_iteration(0, focus_next(2))
.on_iteration(2, focus_next(3))
.on_iteration(4, focus_next(4))
.on_iteration(6, focus_next(5))
.on_iteration(8, focus_next(5))
.on_iteration(9, move |world, _state| {
let strip_entity = active_strip_entity(world);
world
.entity_mut(strip_entity)
.insert(Position(Origin::new(-OVERHANG, TEST_MENUBAR_HEIGHT)));
})
.on_iteration(10, move |world, _state| {
assert_eq!(
window_x(world, 2),
-OVERHANG,
"test setup: window 2 must hang off the left edge before parking"
);
})
.on_iteration(11, focus_next(2))
.on_iteration(12, |world, _state| {
assert_focused!(world, 2);
assert_eq!(
window_x(world, 2),
0,
"the workspace restore must show all of the window it was activated for"
);
})
.run(commands);
}
#[test]
fn test_copy_window_rule_command() {
let commands = vec![
Event::MenuOpened { window_id: 0 },
Event::Command {
command: Command::Window(Operation::CopyRule),
},
];
TestHarness::new()
.with_windows(1)
.on_iteration(1, |_world, _state| {
let copied = crate::pasteboard::get_test_clipboard()
.expect("clipboard should have been populated");
assert!(
copied.contains("[windows.testapp]") || copied.contains("windows = {"),
"copied snippet should contain window rule, got: {copied}"
);
assert!(
copied.contains("bundle_id = \"test\""),
"copied snippet should contain bundle id, got: {copied}"
);
assert!(
copied.contains("title = \"^Window 0$\""),
"copied snippet should contain exact anchored window title, got: {copied}"
);
})
.run(commands);
}
#[test]
fn test_virtual_number_recreates_missing_baseline_row() {
let mut h = TestHarness::new().with_windows(2);
let pump = |h: &mut TestHarness, c: Command| {
h.app
.world_mut()
.write_message::<Event>(Event::Command { command: c });
for _ in 0..8 {
h.app.update();
for e in h.mock_state.drain_events() {
h.app.world_mut().write_message::<Event>(e);
}
}
};
for _ in 0..2 {
pump(
&mut h,
Command::Window(Operation::VirtualMoveNumber(1, MoveFocus::Follow)),
);
}
let world = h.world();
let row_zero = world
.query::<(Entity, &LayoutStrip)>()
.iter(world)
.find(|(_, strip)| strip.virtual_index == 0)
.map(|(entity, _)| entity)
.expect("row 0 should still exist before it is dropped");
world.entity_mut(row_zero).despawn();
let world = h.world();
let indexes = world
.query::<&LayoutStrip>()
.iter(world)
.map(|strip| strip.virtual_index)
.collect::<Vec<_>>();
assert_eq!(indexes, vec![1], "only row 1 should be left");
pump(&mut h, Command::Window(Operation::VirtualNumber(0)));
let world = h.world();
let recreated = world
.query::<(&LayoutStrip, Has<ActiveWorkspaceMarker>)>()
.iter(world)
.filter(|(strip, _)| strip.virtual_index == 0)
.map(|(_, active)| active)
.collect::<Vec<_>>();
assert_eq!(
recreated,
vec![true],
"virtualnum 0 should recreate row 0 and make it active"
);
}
#[test]
fn test_virtual_move_number_recreates_missing_baseline_row() {
let mut h = TestHarness::new().with_windows(2);
let pump = |h: &mut TestHarness, c: Command| {
h.app
.world_mut()
.write_message::<Event>(Event::Command { command: c });
for _ in 0..8 {
h.app.update();
for e in h.mock_state.drain_events() {
h.app.world_mut().write_message::<Event>(e);
}
}
};
for _ in 0..2 {
pump(
&mut h,
Command::Window(Operation::VirtualMoveNumber(1, MoveFocus::Follow)),
);
}
let world = h.world();
let row_zero = world
.query::<(Entity, &LayoutStrip)>()
.iter(world)
.find(|(_, strip)| strip.virtual_index == 0)
.map(|(entity, _)| entity)
.expect("row 0 should still exist before it is dropped");
world.entity_mut(row_zero).despawn();
pump(
&mut h,
Command::Window(Operation::VirtualMoveNumber(0, MoveFocus::Follow)),
);
let world = h.world();
let focused = world
.query_filtered::<Entity, With<FocusedMarker>>()
.single(world)
.expect("the followed window should be focused");
let recreated = world
.query::<&LayoutStrip>()
.iter(world)
.filter(|strip| strip.virtual_index == 0)
.map(|strip| strip.contains(focused))
.collect::<Vec<_>>();
assert_eq!(
recreated,
vec![true],
"the moved window should land on a single recreated row 0"
);
}