use std::time::Duration;
use bevy::prelude::*;
use bevy::time::TimeUpdateStrategy;
use crate::commands::{Command, Direction, MouseMove, MoveFocus, Operation};
use crate::config::{Config, MainOptions};
use crate::ecs::layout::{LayoutStrip, PARKED_STRIP_SLIVER};
use crate::ecs::{DockPosition, Timeout};
use crate::events::Event;
use crate::manager::{Display, Origin, Size, Window};
use crate::platform::WinID;
use crate::{assert_not_on_workspace, assert_on_workspace, assert_window_at, assert_window_size};
use super::*;
#[test]
fn test_multi_display_lifecycle() {
let commands = vec![
Event::MenuOpened { window_id: 0 },
Event::Command {
command: Command::PrintState,
},
Event::DisplayRemoved {
display_id: TEST_DISPLAY_ID,
},
Event::DisplayAdded {
display_id: TEST_DISPLAY_ID,
},
];
let mut harness = TestHarness::new().with_windows(1);
harness
.app
.insert_resource(TimeUpdateStrategy::ManualDuration(Duration::from_millis(
500,
)));
harness
.on_iteration(1, |world, state| {
let mut query = world.query_filtered::<Entity, With<Display>>();
query.single(world).expect("should have one display");
state.remove_display(TEST_DISPLAY_ID);
})
.on_iteration(2, |world, mut state| {
assert!(
world
.query_filtered::<Entity, With<Display>>()
.single(world)
.is_err(),
"display should be despawned"
);
let workspace_entity = {
let mut query = world.query_filtered::<Entity, With<LayoutStrip>>();
query.single(world).expect("should have one workspace")
};
let workspace = world.entity(workspace_entity);
assert!(
workspace.get::<Timeout>().is_some(),
"orphaned workspace should have a timeout"
);
assert!(
workspace.get::<ChildOf>().is_none(),
"orphaned workspace should have no parent"
);
state.add_display(
TEST_DISPLAY_ID,
IRect::new(0, 0, TEST_DISPLAY_WIDTH, TEST_DISPLAY_HEIGHT),
vec![TEST_WORKSPACE_ID],
);
})
.on_iteration(3, |world, _state| {
let new_display_entity = world
.query_filtered::<Entity, With<Display>>()
.single(world)
.expect("display should be spawned again");
let workspace_entity = {
let mut query = world.query_filtered::<Entity, With<LayoutStrip>>();
query.single(world).expect("should have one workspace")
};
let workspace = world.entity(workspace_entity);
assert!(
workspace.get::<Timeout>().is_none(),
"re-parented workspace should no longer have a timeout"
);
let child_of: &ChildOf = workspace
.get::<ChildOf>()
.expect("re-parented workspace should have a parent");
assert_eq!(
child_of.parent(),
new_display_entity,
"workspace should be child of the new display"
);
})
.run(commands);
}
#[test]
fn test_multi_workspace_orphaning() {
let mut commands = vec![
Event::MenuOpened { window_id: 0 },
Event::Command {
command: Command::PrintState,
},
Event::DisplayRemoved {
display_id: TEST_DISPLAY_ID,
},
];
commands.extend((0..6).map(|_| Event::Command {
command: Command::PrintState,
}));
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,
);
harness
.on_iteration(1, |world, state| {
let display_entity = world
.query_filtered::<Entity, With<Display>>()
.single(world)
.expect("should have one display");
let workspace_entities = world
.query_filtered::<Entity, With<LayoutStrip>>()
.iter(world)
.collect::<Vec<_>>();
assert_eq!(workspace_entities.len(), 2, "should have two workspaces");
for &ws in &workspace_entities {
let child_of: &ChildOf = world
.entity(ws)
.get::<ChildOf>()
.expect("workspace should have parent");
assert_eq!(child_of.parent(), display_entity);
}
state.remove_display(TEST_DISPLAY_ID);
})
.on_iteration(8, |world, _state| {
let workspace_entities = world
.query_filtered::<Entity, With<LayoutStrip>>()
.iter(world)
.collect::<Vec<_>>();
for &ws in &workspace_entities {
let entity: EntityRef = world.entity(ws);
assert!(
entity.get::<Timeout>().is_some(),
"each workspace should have a timeout"
);
assert!(
entity.get::<ChildOf>().is_none(),
"each workspace should have no parent"
);
}
})
.run(commands);
}
#[test]
fn test_multi_display_no_height_crosstalk() {
let mut harness = TestHarness::new();
harness.mock_state.add_display(
EXT_DISPLAY_ID,
IRect::new(0, -EXT_DISPLAY_HEIGHT, EXT_DISPLAY_WIDTH, 0),
vec![EXT_WORKSPACE_ID],
);
let origin = Origin::new(0, 0);
let ext_origin = Origin::new(0, -EXT_DISPLAY_HEIGHT + TEST_MENUBAR_HEIGHT);
let size = Size::new(TEST_WINDOW_WIDTH, TEST_WINDOW_HEIGHT);
let frame = IRect::from_corners(origin, origin + size);
let ext_frame = IRect::from_corners(ext_origin, ext_origin + size);
harness
.mock_state
.spawn_window(TEST_PROCESS_ID, EXT_WORKSPACE_ID, 100, ext_frame);
harness
.mock_state
.spawn_window(TEST_PROCESS_ID, TEST_WORKSPACE_ID, 200, frame);
let ext_usable_height = EXT_DISPLAY_HEIGHT - TEST_MENUBAR_HEIGHT;
let commands = vec![
Event::MenuOpened { window_id: 100 },
Event::Command {
command: Command::PrintState,
},
Event::DisplayChanged,
Event::MenuOpened { window_id: 100 },
Event::Command {
command: Command::PrintState,
},
];
harness
.on_iteration(1, move |world, _state| {
assert_window_size!(world, 100, TEST_WINDOW_WIDTH, ext_usable_height);
})
.on_iteration(2, |world, _state| {
use crate::ecs::ActiveWorkspaceMarker;
let mut strip_query =
world.query_filtered::<&mut LayoutStrip, Without<ActiveWorkspaceMarker>>();
for mut strip in strip_query.iter_mut(world) {
strip.set_changed();
}
})
.on_iteration(4, move |world, _state| {
assert_window_size!(world, 100, TEST_WINDOW_WIDTH, ext_usable_height);
})
.run(commands);
}
#[test]
fn test_next_display_inserts_into_target_strip() {
let commands = vec![
Event::MenuOpened { window_id: 0 },
Event::Command {
command: Command::PrintState,
},
Event::Command {
command: Command::Window(Operation::ToNextDisplay(MoveFocus::Follow)),
},
Event::Command {
command: Command::PrintState,
},
];
TestHarness::new()
.with_windows(1)
.with_display(
EXT_DISPLAY_ID,
IRect::new(0, -EXT_DISPLAY_HEIGHT, EXT_DISPLAY_WIDTH, 0),
vec![EXT_WORKSPACE_ID],
)
.on_iteration(1, move |world, _state| {
assert_on_workspace!(world, 0, TEST_WORKSPACE_ID);
})
.on_iteration(2, move |world, _state| {
assert_on_workspace!(world, 0, EXT_WORKSPACE_ID);
assert_not_on_workspace!(world, 0, TEST_WORKSPACE_ID);
})
.run(commands);
}
#[test]
fn test_send_next_display_stays_on_source() {
let mut harness = TestHarness::new();
harness.mock_state.add_display(
EXT_DISPLAY_ID,
IRect::new(0, -EXT_DISPLAY_HEIGHT, EXT_DISPLAY_WIDTH, 0),
vec![EXT_WORKSPACE_ID],
);
let origin = Origin::new(0, 0);
let size = Size::new(TEST_WINDOW_WIDTH, TEST_WINDOW_HEIGHT);
let frame = IRect::from_corners(origin, origin + size);
harness
.mock_state
.spawn_window(TEST_PROCESS_ID, TEST_WORKSPACE_ID, 101, frame);
harness
.mock_state
.spawn_window(TEST_PROCESS_ID, TEST_WORKSPACE_ID, 100, frame);
let commands = vec![
Event::MenuOpened { window_id: 101 },
Event::Command {
command: Command::PrintState,
},
Event::Command {
command: Command::Window(Operation::ToNextDisplay(MoveFocus::Stay)),
},
Event::Command {
command: Command::PrintState,
},
];
harness
.on_iteration(1, move |world, _state| {
assert_on_workspace!(world, 100, TEST_WORKSPACE_ID);
})
.on_iteration(2, move |world, state| {
assert_on_workspace!(world, 100, EXT_WORKSPACE_ID);
assert_not_on_workspace!(world, 100, TEST_WORKSPACE_ID);
assert_eq!(state.active_display(), TEST_DISPLAY_ID);
})
.run(commands);
}
#[test]
fn test_mouse_to_next_display() {
let commands = vec![
Event::MenuOpened { window_id: 101 },
Event::Command {
command: Command::PrintState,
},
Event::Command {
command: Command::Mouse(MouseMove::ToNextDisplay),
},
Event::Command {
command: Command::PrintState,
},
];
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 display_bounds = IRect::new(0, -EXT_DISPLAY_HEIGHT, EXT_DISPLAY_WIDTH, 0);
TestHarness::new()
.with_display(EXT_DISPLAY_ID, display_bounds, vec![EXT_WORKSPACE_ID])
.with_window(100, |data| {
data.pid = TEST_PROCESS_ID;
data.workspace_id = TEST_WORKSPACE_ID;
data.frame = frame;
})
.on_iteration(1, move |world, state| {
let entity = find_window_entity(100, world);
let window = world.get::<Window>(entity).expect("need window");
assert_eq!(state.cursor_position(), window.frame().center());
})
.on_iteration(3, move |world, state| {
let mut query = world.query::<(&Display, Option<&DockPosition>)>();
let (display, dock) = query
.iter(world)
.find(|display| display.0.id() == EXT_DISPLAY_ID)
.expect("need display");
let config = world.resource::<Config>();
let bounds = display.actual_display_bounds(dock, config);
assert_eq!(state.cursor_position(), bounds.center());
})
.run(commands);
}
#[test]
fn test_init_keeps_windows_on_their_real_displays() {
let mut harness = TestHarness::new();
harness.mock_state.add_display(
EXT_DISPLAY_ID,
IRect::new(0, -EXT_DISPLAY_HEIGHT, EXT_DISPLAY_WIDTH, 0),
vec![EXT_WORKSPACE_ID],
);
let origin = Origin::new(0, 0);
let ext_origin = Origin::new(0, -EXT_DISPLAY_HEIGHT + TEST_MENUBAR_HEIGHT);
let size = Size::new(TEST_WINDOW_WIDTH, TEST_WINDOW_HEIGHT);
let frame = IRect::from_corners(origin, origin + size);
let ext_frame = IRect::from_corners(ext_origin, ext_origin + size);
harness
.mock_state
.spawn_window(TEST_PROCESS_ID, TEST_WORKSPACE_ID, 200, ext_frame);
harness
.mock_state
.spawn_window(TEST_PROCESS_ID, EXT_WORKSPACE_ID, 100, frame);
let commands = vec![
Event::Command {
command: Command::PrintState,
},
Event::Command {
command: Command::PrintState,
},
];
harness
.on_iteration(0, move |world, _state| {
assert_on_workspace!(world, 100, EXT_WORKSPACE_ID);
assert_not_on_workspace!(world, 100, TEST_WORKSPACE_ID);
assert_on_workspace!(world, 200, TEST_WORKSPACE_ID);
assert_not_on_workspace!(world, 200, EXT_WORKSPACE_ID);
assert_window_at!(world, 100, ext_origin.x, ext_origin.y);
})
.run(commands);
}
#[test]
fn test_wake_reconciles_unplugged_display() {
let harness = TestHarness::new().with_display(
EXT_DISPLAY_ID,
IRect::new(0, -EXT_DISPLAY_HEIGHT, EXT_DISPLAY_WIDTH, 0),
vec![EXT_WORKSPACE_ID],
);
let ext_origin = Origin::new(0, -EXT_DISPLAY_HEIGHT + TEST_MENUBAR_HEIGHT);
let size = Size::new(TEST_WINDOW_WIDTH, TEST_WINDOW_HEIGHT);
let ext_frame = IRect::from_corners(ext_origin, ext_origin + size);
harness
.mock_state
.spawn_window(TEST_PROCESS_ID, EXT_WORKSPACE_ID, 100, ext_frame);
let commands = vec![
Event::MenuOpened { window_id: 100 },
Event::Command {
command: Command::PrintState,
},
Event::SystemWoke { msg: String::new() },
];
harness
.on_iteration(1, |world, state| {
let displays = world
.query_filtered::<Entity, With<Display>>()
.iter(world)
.count();
assert_eq!(displays, 2, "should start with two displays");
state.remove_display(EXT_DISPLAY_ID);
})
.on_iteration(2, |world, _state| {
let displays = world
.query_filtered::<Entity, With<Display>>()
.iter(world)
.count();
assert_eq!(displays, 1, "reconcile should despawn the vanished display");
let orphan = world
.query::<(&LayoutStrip, Option<&ChildOf>, Has<Timeout>)>()
.iter(world)
.find(|(strip, _, _)| strip.id() == EXT_WORKSPACE_ID)
.map(|(_, child, timeout)| (child.is_some(), timeout));
let (has_parent, has_timeout) =
orphan.expect("external workspace strip should still exist");
assert!(!has_parent, "orphaned workspace should have no parent");
assert!(has_timeout, "orphaned workspace should carry a timeout");
})
.run(commands);
}
#[test]
fn test_display_configuration_reapplies_layout_after_geometry_change() {
let mut harness = TestHarness::new().with_windows(1);
harness.advance(Duration::from_millis(500));
let window_entity = find_window_entity(0, harness.world());
let original = harness
.world()
.get::<Window>(window_entity)
.unwrap()
.frame();
let new_height = TEST_DISPLAY_HEIGHT - 100;
harness.mock_state.set_display_bounds(
TEST_DISPLAY_ID,
IRect::new(120, 0, TEST_DISPLAY_WIDTH + 120, new_height),
);
harness
.world()
.write_message::<Event>(Event::DisplayConfigured {
display_id: TEST_DISPLAY_ID,
});
harness.advance(Duration::from_millis(200));
let world = harness.world();
let display = world.query::<&Display>().single(world).unwrap();
assert_eq!(display.bounds().min.x, 0, "wait for the display to settle");
harness.advance(Duration::from_millis(1000));
let updated = harness
.world()
.get::<Window>(window_entity)
.unwrap()
.frame();
assert_eq!(updated.min.x, original.min.x + 120);
assert_eq!(updated.height(), new_height - TEST_MENUBAR_HEIGHT);
}
#[test]
fn test_display_configuration_catches_late_geometry_change() {
let mut harness = TestHarness::new().with_windows(1);
harness.advance(Duration::from_millis(500));
let window_entity = find_window_entity(0, harness.world());
let original_x = harness
.world()
.get::<Window>(window_entity)
.unwrap()
.frame()
.min
.x;
harness
.world()
.write_message::<Event>(Event::DisplayConfigured {
display_id: TEST_DISPLAY_ID,
});
harness.advance(Duration::from_millis(600));
harness.mock_state.set_display_bounds(
TEST_DISPLAY_ID,
IRect::new(90, 0, TEST_DISPLAY_WIDTH + 90, TEST_DISPLAY_HEIGHT),
);
harness.advance(Duration::from_millis(1300));
let world = harness.world();
let display = world.query::<&Display>().single(world).unwrap();
assert_eq!(display.bounds().min.x, 90);
assert_eq!(
world.get::<Window>(window_entity).unwrap().frame().min.x,
original_x + 90
);
}
#[test]
fn test_transient_empty_display_list_keeps_layout_and_retries() {
let mut harness = TestHarness::new().with_windows(1);
harness.advance(Duration::from_millis(500));
harness.mock_state.remove_display(TEST_DISPLAY_ID);
harness
.world()
.write_message::<Event>(Event::DisplayConfigured {
display_id: TEST_DISPLAY_ID,
});
harness.advance(Duration::from_millis(600));
let world = harness.world();
let display_entity = world
.query_filtered::<Entity, With<Display>>()
.single(world)
.unwrap();
let strip_entity = world
.query_filtered::<Entity, With<LayoutStrip>>()
.single(world)
.unwrap();
assert_eq!(
world.get::<ChildOf>(strip_entity).unwrap().parent(),
display_entity
);
harness.mock_state.add_display(
TEST_DISPLAY_ID,
IRect::new(100, 0, TEST_DISPLAY_WIDTH + 100, TEST_DISPLAY_HEIGHT),
vec![TEST_WORKSPACE_ID],
);
harness.advance(Duration::from_millis(1600));
let world = harness.world();
let display = world.query::<&Display>().single(world).unwrap();
assert_eq!(
display.bounds().min.x,
100,
"retry should use the recovered display"
);
}
#[test]
fn test_unplug_rehomes_workspace_and_window_on_surviving_display() {
let mut harness = TestHarness::new().with_display(
EXT_DISPLAY_ID,
IRect::new(0, -EXT_DISPLAY_HEIGHT, EXT_DISPLAY_WIDTH, 0),
vec![EXT_WORKSPACE_ID],
);
let ext_origin = Origin::new(0, -EXT_DISPLAY_HEIGHT + TEST_MENUBAR_HEIGHT);
let frame = IRect::from_corners(
ext_origin,
ext_origin + Size::new(TEST_WINDOW_WIDTH, TEST_WINDOW_HEIGHT),
);
harness
.mock_state
.spawn_window(TEST_PROCESS_ID, EXT_WORKSPACE_ID, 100, frame);
harness.advance(Duration::from_millis(500));
harness.mock_state.remove_display(EXT_DISPLAY_ID);
harness.mock_state.add_display(
TEST_DISPLAY_ID,
IRect::new(0, 0, TEST_DISPLAY_WIDTH, TEST_DISPLAY_HEIGHT),
vec![TEST_WORKSPACE_ID, EXT_WORKSPACE_ID],
);
harness
.world()
.write_message::<Event>(Event::DisplayRemoved {
display_id: EXT_DISPLAY_ID,
});
harness.advance(Duration::from_millis(1000));
let world = harness.world();
let main = world.query::<(&Display, Entity)>().single(world).unwrap().1;
let external_parent = world
.query::<(&LayoutStrip, &ChildOf)>()
.iter(world)
.find(|(strip, _)| strip.id() == EXT_WORKSPACE_ID)
.map(|(_, child)| child.parent())
.expect("external workspace should remain managed");
assert_eq!(external_parent, main);
let window_entity = find_window_entity(100, world);
let window = world.get::<Window>(window_entity).unwrap();
assert!(window.frame().min.y >= TEST_MENUBAR_HEIGHT);
}
#[test]
fn test_late_space_rehome_reapplies_window_frame() {
let mut harness = TestHarness::new().with_display(
EXT_DISPLAY_ID,
IRect::new(0, -EXT_DISPLAY_HEIGHT, EXT_DISPLAY_WIDTH, 0),
vec![EXT_WORKSPACE_ID],
);
let ext_origin = Origin::new(0, -EXT_DISPLAY_HEIGHT + TEST_MENUBAR_HEIGHT);
harness.mock_state.spawn_window(
TEST_PROCESS_ID,
EXT_WORKSPACE_ID,
100,
IRect::from_corners(
ext_origin,
ext_origin + Size::new(TEST_WINDOW_WIDTH, TEST_WINDOW_HEIGHT),
),
);
harness.advance(Duration::from_millis(500));
harness.mock_state.remove_display(EXT_DISPLAY_ID);
harness
.world()
.write_message::<Event>(Event::DisplayRemoved {
display_id: EXT_DISPLAY_ID,
});
harness.advance(Duration::from_millis(650));
let world = harness.world();
let orphan = world
.query::<(&LayoutStrip, Option<&ChildOf>)>()
.iter(world)
.find(|(strip, _)| strip.id() == EXT_WORKSPACE_ID)
.expect("external workspace should be retained");
assert!(orphan.1.is_none(), "workspace should await its new display");
harness.mock_state.add_display(
TEST_DISPLAY_ID,
IRect::new(0, 0, TEST_DISPLAY_WIDTH, TEST_DISPLAY_HEIGHT),
vec![TEST_WORKSPACE_ID, EXT_WORKSPACE_ID],
);
harness.advance(Duration::from_millis(1200));
let world = harness.world();
let external_parent = world
.query::<(&LayoutStrip, &ChildOf)>()
.iter(world)
.find(|(strip, _)| strip.id() == EXT_WORKSPACE_ID)
.map(|(_, child)| child.parent())
.expect("external workspace should be reparented");
let main = world.query::<(&Display, Entity)>().single(world).unwrap().1;
assert_eq!(external_parent, main);
let window_entity = find_window_entity(100, world);
assert!(world.get::<Window>(window_entity).unwrap().frame().min.y >= TEST_MENUBAR_HEIGHT);
}
#[test]
fn test_wake_reapplies_window_frame_without_geometry_change() {
let mut harness = TestHarness::new().with_windows(1);
harness.advance(Duration::from_millis(500));
let window_entity = find_window_entity(0, harness.world());
let original = harness
.world()
.get::<Window>(window_entity)
.unwrap()
.frame();
harness.mock_state.update_window(0, |window| {
window.frame.min.x += 200;
window.frame.max.x += 200;
});
harness
.world()
.write_message::<Event>(Event::SystemWoke { msg: String::new() });
harness.advance(Duration::from_millis(1000));
let restored = harness
.world()
.get::<Window>(window_entity)
.unwrap()
.frame();
assert_eq!(restored, original);
}
#[test]
fn test_vertical_swap_within_stack_stays_on_display() {
let mut harness = TestHarness::new().with_windows(2);
harness.mock_state.add_display(
EXT_DISPLAY_ID,
IRect::new(
0,
TEST_DISPLAY_HEIGHT,
EXT_DISPLAY_WIDTH,
TEST_DISPLAY_HEIGHT + EXT_DISPLAY_HEIGHT,
),
vec![EXT_WORKSPACE_ID],
);
let commands = vec![
Event::MenuOpened { window_id: 0 },
Event::Command {
command: Command::Window(Operation::Focus(Direction::Last)),
},
Event::Command {
command: Command::Window(Operation::Stack(true)),
},
Event::Command {
command: Command::Window(Operation::Focus(Direction::North)),
},
Event::Command {
command: Command::PrintState,
},
Event::Command {
command: Command::Window(Operation::Swap(Direction::South)),
},
Event::Command {
command: Command::PrintState,
},
];
harness
.on_iteration(4, |world, state| {
assert_on_workspace!(world, 0, TEST_WORKSPACE_ID);
assert_on_workspace!(world, 1, TEST_WORKSPACE_ID);
assert_eq!(state.active_display(), TEST_DISPLAY_ID);
})
.on_iteration(6, |world, state| {
assert_on_workspace!(world, 0, TEST_WORKSPACE_ID);
assert_on_workspace!(world, 1, TEST_WORKSPACE_ID);
assert_not_on_workspace!(world, 0, EXT_WORKSPACE_ID);
assert_not_on_workspace!(world, 1, EXT_WORKSPACE_ID);
assert_eq!(
state.active_display(),
TEST_DISPLAY_ID,
"swapping inside a stack must not move focus to another display"
);
})
.run(commands);
}
#[test]
fn test_hidden_stack_stays_off_the_display_below() {
let mut harness = TestHarness::new().with_windows(2);
harness.mock_state.add_display(
EXT_DISPLAY_ID,
IRect::new(
0,
TEST_DISPLAY_HEIGHT,
EXT_DISPLAY_WIDTH,
TEST_DISPLAY_HEIGHT + EXT_DISPLAY_HEIGHT,
),
vec![EXT_WORKSPACE_ID],
);
let commands = vec![
Event::MenuOpened { window_id: 0 },
Event::Command {
command: Command::Window(Operation::Focus(Direction::Last)),
},
Event::Command {
command: Command::Window(Operation::Stack(true)),
},
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,
},
];
harness
.on_iteration(4, |world, _state| {
let mut query = world.query::<&crate::manager::Window>();
for window in query.iter(world) {
let frame = window.frame();
assert!(
frame.min.y < TEST_DISPLAY_HEIGHT,
"window {} parked at {:?}, inside the display below",
window.id(),
frame
);
assert_eq!(
frame.min.y,
TEST_DISPLAY_HEIGHT - PARKED_STRIP_SLIVER,
"window {} should park on the corner sliver",
window.id()
);
}
})
.on_iteration(6, |world, _state| {
assert_on_workspace!(world, 0, TEST_WORKSPACE_ID);
assert_on_workspace!(world, 1, TEST_WORKSPACE_ID);
assert_not_on_workspace!(world, 0, EXT_WORKSPACE_ID);
assert_not_on_workspace!(world, 1, EXT_WORKSPACE_ID);
assert_window_at!(world, 0, 400, TEST_MENUBAR_HEIGHT);
assert_window_at!(world, 1, 400, 394);
})
.run(commands);
}
#[test]
fn test_center_survives_display_round_trip() {
let config: Config = (
MainOptions {
auto_center: Some(false),
continuous_swipe: Some(false),
animation_speed: Some(10000.0),
..Default::default()
},
vec![],
)
.into();
let centered = (TEST_DISPLAY_WIDTH - TEST_WINDOW_WIDTH) / 2;
let 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
};
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(config)
.with_display(
EXT_DISPLAY_ID,
IRect::new(0, -EXT_DISPLAY_HEIGHT, EXT_DISPLAY_WIDTH, 0),
vec![EXT_WORKSPACE_ID],
)
.with_windows(4)
.with_workspace_window(100, EXT_WORKSPACE_ID, |window| {
window.workspace_id = EXT_WORKSPACE_ID;
})
.on_iteration(1, move |world, state| {
assert_eq!(window_x(world, 0), centered, "window 0 must be centered");
state.focus_window(100);
})
.on_iteration(2, move |_world, state| {
state.focus_window(0);
})
.on_iteration(3, move |world, _state| {
assert_eq!(
window_x(world, 0),
centered,
"returning from another display must not undo the centering"
);
})
.run(commands);
}
#[test]
fn test_empty_baseline_row_survives_display_removal() {
let mut commands = vec![
Event::Command {
command: Command::PrintState,
},
Event::DisplayRemoved {
display_id: TEST_DISPLAY_ID,
},
];
commands.extend((0..6).map(|_| Event::Command {
command: Command::PrintState,
}));
commands.push(Event::DisplayAdded {
display_id: TEST_DISPLAY_ID,
});
TestHarness::new()
.on_iteration(0, |world, state| {
let strips = world
.query::<&LayoutStrip>()
.iter(world)
.map(|strip| strip.virtual_index)
.collect::<Vec<_>>();
assert_eq!(strips, vec![0], "the space starts with an empty row 0");
state.remove_display(TEST_DISPLAY_ID);
})
.on_iteration(7, |world, mut state| {
let entity = world
.query_filtered::<Entity, With<LayoutStrip>>()
.single(world)
.expect("empty row 0 should be orphaned, not despawned");
assert!(
world.entity(entity).get::<Timeout>().is_some(),
"orphaned row 0 should carry a timeout"
);
state.add_display(
TEST_DISPLAY_ID,
IRect::new(0, 0, TEST_DISPLAY_WIDTH, TEST_DISPLAY_HEIGHT),
vec![TEST_WORKSPACE_ID],
);
})
.on_iteration(8, |world, _state| {
let entity = world
.query_filtered::<Entity, With<LayoutStrip>>()
.single(world)
.expect("row 0 should still exist after the display returns");
assert!(
world.entity(entity).get::<ChildOf>().is_some(),
"row 0 should be re-parented to the returning display"
);
})
.run(commands);
}