paneru 0.4.0

A sliding, tiling window manager for MacOS.
use std::sync::atomic::{AtomicU32, Ordering};
use std::sync::{Arc, RwLock};
use std::time::Duration;

use bevy::prelude::*;
use bevy::time::TimeUpdateStrategy;

use crate::commands::{Command, MoveFocus, Operation};
use crate::ecs::Timeout;
use crate::ecs::layout::LayoutStrip;
use crate::events::Event;
use crate::manager::{Display, Origin, Size, Window};
use crate::{assert_not_on_workspace, assert_on_workspace, 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| {
            let mut query = world.query_filtered::<Entity, With<Display>>();
            query.single(world).expect("should have one display");
        })
        .on_iteration(2, |world| {
            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"
            );
        })
        .on_iteration(3, |world| {
            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 mock_app = setup_process(setup_world().world_mut());
    let internal_queue = Arc::new(RwLock::new(Vec::new()));
    let spawner = window_spawner(1, internal_queue.clone(), mock_app);
    let wm = MockWindowManager {
        windows: spawner,
        workspaces: vec![TEST_WORKSPACE_ID, TEST_WORKSPACE_ID + 1],
    };

    TestHarness::new()
        .with_wm(wm)
        .on_iteration(1, |world| {
            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);
            }
        })
        .on_iteration(2, |world| {
            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 active_display = Arc::new(AtomicU32::new(EXT_DISPLAY_ID));
    let ad_clone = active_display.clone();

    let mut harness = TestHarness::new();
    let mock_app = setup_process(harness.app.world_mut());
    let internal_queue = harness.internal_queue.clone();

    let eq1 = internal_queue.clone();
    let eq2 = internal_queue.clone();
    let app1 = mock_app.clone();
    let app2 = mock_app;
    let windows: TestWindowSpawner = Box::new(move |workspace_id| {
        if workspace_id == EXT_WORKSPACE_ID {
            let origin = Origin::new(0, 0);
            let size = Size::new(TEST_WINDOW_WIDTH, TEST_WINDOW_HEIGHT);
            vec![Window::new(Box::new(MockWindow::new(
                100,
                IRect::from_corners(origin, origin + size),
                eq1.clone(),
                app1.clone(),
            )))]
        } else if workspace_id == TEST_WORKSPACE_ID {
            let origin = Origin::new(0, 0);
            let size = Size::new(TEST_WINDOW_WIDTH, TEST_WINDOW_HEIGHT);
            vec![Window::new(Box::new(MockWindow::new(
                200,
                IRect::from_corners(origin, origin + size),
                eq2.clone(),
                app2.clone(),
            )))]
        } else {
            vec![]
        }
    });

    let wm = TwoDisplayMock {
        windows,
        active_display: active_display.clone(),
    };
    harness = harness.with_wm(wm);

    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| {
            assert_window_size!(world, 100, TEST_WINDOW_WIDTH, ext_usable_height);
            ad_clone.store(TEST_DISPLAY_ID, Ordering::Relaxed);
        })
        .on_iteration(2, |world| {
            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| {
            assert_window_size!(world, 100, TEST_WINDOW_WIDTH, ext_usable_height);
        })
        .run(commands);
}

#[test]
fn test_next_display_inserts_into_target_strip() {
    let active_display = Arc::new(AtomicU32::new(EXT_DISPLAY_ID));

    let mut harness = TestHarness::new();
    let mock_app = setup_process(harness.app.world_mut());
    let internal_queue = harness.internal_queue.clone();

    let eq = internal_queue.clone();
    let app = mock_app;
    let windows: TestWindowSpawner = Box::new(move |workspace_id| {
        if workspace_id == EXT_WORKSPACE_ID {
            let origin = Origin::new(0, 0);
            let size = Size::new(TEST_WINDOW_WIDTH, TEST_WINDOW_HEIGHT);
            vec![Window::new(Box::new(MockWindow::new(
                100,
                IRect::from_corners(origin, origin + size),
                eq.clone(),
                app.clone(),
            )))]
        } else {
            vec![]
        }
    });

    let wm = TwoDisplayMock {
        windows,
        active_display: active_display.clone(),
    };
    harness = harness.with_wm(wm);

    let commands = vec![
        Event::MenuOpened { window_id: 100 },
        Event::Command {
            command: Command::PrintState,
        },
        Event::Command {
            command: Command::Window(Operation::ToNextDisplay(MoveFocus::Follow)),
        },
        Event::Command {
            command: Command::PrintState,
        },
    ];

    harness
        .on_iteration(1, move |world| {
            assert_on_workspace!(world, 100, EXT_WORKSPACE_ID);
        })
        .on_iteration(2, move |world| {
            assert_on_workspace!(world, 100, TEST_WORKSPACE_ID);
            assert_not_on_workspace!(world, 100, EXT_WORKSPACE_ID);
        })
        .run(commands);
}

#[test]
fn test_send_next_display_stays_on_source() {
    let active_display = Arc::new(AtomicU32::new(EXT_DISPLAY_ID));
    let ad_clone = active_display.clone();

    let mut harness = TestHarness::new();
    let mock_app = setup_process(harness.app.world_mut());
    let internal_queue = harness.internal_queue.clone();

    let eq = internal_queue.clone();
    let app = mock_app;
    let windows: TestWindowSpawner = Box::new(move |workspace_id| {
        if workspace_id == EXT_WORKSPACE_ID {
            let origin = Origin::new(0, 0);
            let size = Size::new(TEST_WINDOW_WIDTH, TEST_WINDOW_HEIGHT);
            vec![
                Window::new(Box::new(MockWindow::new(
                    100,
                    IRect::from_corners(origin, origin + size),
                    eq.clone(),
                    app.clone(),
                ))),
                Window::new(Box::new(MockWindow::new(
                    101,
                    IRect::from_corners(origin, origin + size),
                    eq.clone(),
                    app.clone(),
                ))),
            ]
        } else {
            vec![]
        }
    });

    let wm = TwoDisplayMock {
        windows,
        active_display: active_display.clone(),
    };
    harness = harness.with_wm(wm);

    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| {
            assert_on_workspace!(world, 101, EXT_WORKSPACE_ID);
        })
        .on_iteration(2, move |world| {
            assert_on_workspace!(world, 101, TEST_WORKSPACE_ID);
            assert_not_on_workspace!(world, 101, EXT_WORKSPACE_ID);
            assert_eq!(
                ad_clone.load(Ordering::Relaxed),
                EXT_DISPLAY_ID,
                "active display should still be the external display after sendnextdisplay"
            );
        })
        .run(commands);
}