use bevy::prelude::*;
use crate::assert_window_size;
use crate::commands::{Command, MoveFocus, Operation};
use crate::config::{Config, MainOptions};
use crate::ecs::layout::LayoutStrip;
use crate::ecs::{ActiveWorkspaceMarker, Bounds, SpawnWindowTrigger};
use crate::events::Event;
use crate::platform::WinID;
use super::*;
fn spawn_matching_native_tab(world: &mut World, state: &MockState, window_id: WinID) {
let leader = find_window_entity(window_id, world);
let frame = world
.get::<Window>(leader)
.map(|window| window.frame())
.expect("frame should exist");
let window = state.spawn_window(TEST_PROCESS_ID, TEST_WORKSPACE_ID, window_id + 1, frame);
state.window_visible(window_id, false);
world.trigger(SpawnWindowTrigger(vec![window]));
}
#[test]
fn test_native_tab_detection() {
let commands = vec![
Event::MenuOpened { window_id: 0 }, Event::Command {
command: Command::PrintState,
}, ];
TestHarness::new()
.with_windows(1)
.on_iteration(0, move |world, state| {
spawn_matching_native_tab(world, &state, 0);
})
.on_iteration(1, move |world, _state| {
let follower = find_window_entity(0, world);
let leader = find_window_entity(1, world);
let strip = world
.query::<&LayoutStrip>()
.single(world)
.expect("getting layout strip");
assert!(strip.tabbed(follower));
assert!(strip.tabbed(leader));
})
.run(commands);
}
#[test]
fn test_native_tab_resize_syncs_sibling_size() {
let commands = vec![
Event::MenuOpened { window_id: 0 },
Event::Command {
command: Command::PrintState,
},
Event::Command {
command: Command::PrintState,
},
];
TestHarness::new()
.with_windows(1)
.on_iteration(0, move |world, state| {
spawn_matching_native_tab(world, &state, 0);
})
.on_iteration(1, move |world, _state| {
let tab_one = find_window_entity(1, world);
let mut query = world.query::<&mut Bounds>();
let mut bounds = query.get_mut(world, tab_one).expect("tab bounds missing");
bounds.0.x = TEST_WINDOW_WIDTH + 160;
})
.on_iteration(2, move |world, _state| {
assert_window_size!(
world,
0,
TEST_WINDOW_WIDTH + 160,
TEST_DISPLAY_HEIGHT - TEST_MENUBAR_HEIGHT
);
assert_window_size!(
world,
1,
TEST_WINDOW_WIDTH + 160,
TEST_DISPLAY_HEIGHT - TEST_MENUBAR_HEIGHT
);
})
.run(commands);
}
#[test]
fn test_native_tab_virtual_move_moves_all_tabs() {
let commands = vec![
Event::MenuOpened { window_id: 0 },
Event::Command {
command: Command::PrintState,
},
Event::Command {
command: Command::Window(Operation::VirtualMoveNumber(1, MoveFocus::Follow)),
},
Event::Command {
command: Command::PrintState,
},
];
TestHarness::new()
.with_windows(1)
.on_iteration(0, move |world, state| {
spawn_matching_native_tab(world, &state, 0);
})
.on_iteration(3, move |world, _state| {
let tab_zero = find_window_entity(0, world);
let tab_one = find_window_entity(1, world);
let mut query = world.query::<(&LayoutStrip, Has<ActiveWorkspaceMarker>)>();
let active = query
.iter(world)
.find_map(|(strip, active)| active.then_some(strip))
.expect("active strip not found");
assert_eq!(active.virtual_index, 1);
assert_eq!(active.len(), 1);
assert_eq!(active.index_of(tab_zero).unwrap(), 0);
assert_eq!(active.index_of(tab_one).unwrap(), 0);
assert_eq!(active.tab_group(tab_zero), Some(vec![tab_one, tab_zero]));
let mut query = world.query::<&LayoutStrip>();
let source = query
.iter(world)
.find(|strip| strip.virtual_index == 0)
.expect("source strip not found");
assert!(!source.contains(tab_zero));
assert!(!source.contains(tab_one));
})
.run(commands);
}
#[test]
fn test_native_tab_removal_keeps_remaining_window_column() {
let commands = vec![
Event::MenuOpened { window_id: 0 },
Event::Command {
command: Command::PrintState,
},
Event::Command {
command: Command::PrintState,
},
];
TestHarness::new()
.with_windows(1)
.on_iteration(0, move |world, state| {
spawn_matching_native_tab(world, &state, 0);
})
.on_iteration(1, move |world, _state| {
let tab_one = find_window_entity(1, world);
world.entity_mut(tab_one).despawn();
})
.on_iteration(2, move |world, _state| {
let tab_zero = find_window_entity(0, world);
let mut query = world.query::<(&LayoutStrip, Has<ActiveWorkspaceMarker>)>();
let strip = query
.iter(world)
.find_map(|(strip, active)| active.then_some(strip))
.expect("active strip not found");
assert_eq!(strip.len(), 1, "removing a tab should not empty the column");
assert_eq!(strip.tab_group(tab_zero), None);
assert_eq!(strip.index_of(tab_zero).unwrap(), 0);
})
.run(commands);
}
#[test]
fn test_offscreen_same_app_same_width_different_frame_not_tabbed() {
let commands = vec![
Event::MenuOpened { window_id: 0 },
Event::Command {
command: Command::PrintState,
},
];
TestHarness::new()
.with_windows(1)
.on_iteration(0, move |world, state| {
let leader = find_window_entity(0, world);
let leader_frame = world
.get::<Window>(leader)
.map(|window| window.frame())
.expect("frame should exist");
let mut frame = leader_frame;
frame.min.y += 50;
frame.max.y = frame.min.y + leader_frame.height() / 2;
let window = state.spawn_window(TEST_PROCESS_ID, TEST_WORKSPACE_ID, 1, frame);
state.window_visible(0, false);
world.trigger(SpawnWindowTrigger(vec![window]));
})
.on_iteration(1, move |world, _state| {
let leader = find_window_entity(0, world);
let new_window = find_window_entity(1, world);
let strip = world
.query::<&LayoutStrip>()
.single(world)
.expect("getting layout strip");
assert!(!strip.tabbed(leader));
assert!(!strip.tabbed(new_window));
assert_eq!(
strip.len(),
2,
"non-matching frames must get their own column"
);
})
.run(commands);
}
#[test]
fn test_disable_native_tabs_skips_detection() {
let commands = vec![
Event::MenuOpened { window_id: 0 },
Event::Command {
command: Command::PrintState,
},
];
let options = MainOptions {
disable_native_tabs: Some(true),
..MainOptions::default()
};
let config: Config = (options, vec![]).into();
TestHarness::new()
.with_config(config)
.with_windows(1)
.on_iteration(0, move |world, state| {
spawn_matching_native_tab(world, &state, 0);
})
.on_iteration(1, move |world, _state| {
let leader = find_window_entity(0, world);
let new_window = find_window_entity(1, world);
let strip = world
.query::<&LayoutStrip>()
.single(world)
.expect("getting layout strip");
assert!(!strip.tabbed(leader));
assert!(!strip.tabbed(new_window));
assert_eq!(
strip.len(),
2,
"detector must be disabled — windows stay in separate columns"
);
})
.run(commands);
}
#[test]
fn test_same_app_same_frame_native_tab_reuses_existing_column() {
let commands = vec![
Event::MenuOpened { window_id: 0 },
Event::Command {
command: Command::PrintState,
},
];
TestHarness::new()
.with_windows(1)
.on_iteration(0, move |world, state| {
spawn_matching_native_tab(world, &state, 0);
})
.on_iteration(1, move |world, _state| {
let tab_zero = find_window_entity(0, world);
let tab_one = find_window_entity(1, world);
let mut query = world.query::<(&LayoutStrip, Has<ActiveWorkspaceMarker>)>();
let strip = query
.iter(world)
.find_map(|(strip, active)| active.then_some(strip))
.expect("active strip not found");
assert_eq!(strip.len(), 1, "native tab should not add a column");
assert_eq!(strip.tab_group(tab_zero), Some(vec![tab_one, tab_zero]));
assert_eq!(strip.tab_group(tab_one), Some(vec![tab_one, tab_zero]));
assert_window_size!(
world,
0,
TEST_WINDOW_WIDTH,
TEST_DISPLAY_HEIGHT - TEST_MENUBAR_HEIGHT
);
assert_window_size!(
world,
1,
TEST_WINDOW_WIDTH,
TEST_DISPLAY_HEIGHT - TEST_MENUBAR_HEIGHT
);
})
.run(commands);
}