use std::time::Duration;
use bevy::prelude::*;
use bevy::time::TimeUpdateStrategy;
use crate::commands::{Command, MouseMove, MoveFocus, Operation};
use crate::config::Config;
use crate::ecs::layout::LayoutStrip;
use crate::ecs::{ActiveWorkspaceMarker, DockPosition, RefreshWindowSizes, Timeout};
use crate::events::Event;
use crate::manager::{Display, Origin, Size};
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 commands = vec![
Event::MenuOpened { window_id: 0 },
Event::Command {
command: Command::PrintState,
},
Event::DisplayRemoved {
display_id: TEST_DISPLAY_ID,
},
];
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(2, |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_wake_refreshes_active_workspace() {
let harness = TestHarness::new().with_windows(1);
let commands = vec![
Event::MenuOpened { window_id: 0 },
Event::SystemWoke { msg: String::new() },
];
harness
.on_iteration(1, |world, _state| {
let refreshed = world
.query_filtered::<Has<RefreshWindowSizes>, With<ActiveWorkspaceMarker>>()
.iter(world)
.any(|has| has);
assert!(
refreshed,
"wake should mark the active workspace for a window-size refresh"
);
})
.run(commands);
}