use proptest::prelude::*;
use crate::platform::tab_manager::WindowTabManager;
use crate::{AnyWindowHandle, EmptyView, WindowHandle, WindowId};
use std::collections::HashSet;
fn make_handle(raw: u32) -> AnyWindowHandle {
let id: WindowId = slotmap::KeyData::from_ffi(raw as u64).into();
WindowHandle::<EmptyView>::new(id).into()
}
fn identifier_strategy() -> impl Strategy<Value = String> {
"[a-z][a-z0-9\\-]{0,19}"
}
fn windows_with_identifiers_strategy() -> impl Strategy<Value = Vec<(u32, String)>> {
let identifiers = prop::collection::vec(identifier_strategy(), 1..=4);
let count = 2u32..=10;
(identifiers, count).prop_flat_map(|(ids, n)| {
let id_count = ids.len();
prop::collection::vec((0..id_count).prop_map(move |i| ids[i].clone()), n as usize).prop_map(
|chosen_ids| {
chosen_ids
.into_iter()
.enumerate()
.map(|(i, id)| {
let raw = (i as u32) + 1;
(raw, id)
})
.collect::<Vec<_>>()
},
)
})
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(100))]
#[test]
fn grouping_correctness(entries in windows_with_identifiers_strategy()) {
let state = WindowTabManager::shared_state();
let managers: Vec<(AnyWindowHandle, WindowTabManager, String)> = entries
.iter()
.map(|(raw, id)| {
let handle = make_handle(*raw);
let mgr = WindowTabManager::new(handle, state.clone());
mgr.set_tabbing_identifier(Some(id.clone()));
(handle, mgr, id.clone())
})
.collect();
let mut expected_groups: std::collections::HashMap<String, HashSet<WindowId>> =
std::collections::HashMap::new();
for (handle, _, id) in &managers {
expected_groups
.entry(id.clone())
.or_default()
.insert(handle.window_id());
}
for (handle, mgr, id) in &managers {
let tabs = mgr.tabbed_windows();
prop_assert!(
tabs.is_some(),
"window {:?} with identifier '{}' must have tabbed_windows",
handle.window_id(),
id
);
let tab_ids: HashSet<WindowId> =
tabs.unwrap().iter().map(|t| t.handle.window_id()).collect();
let expected = &expected_groups[id];
prop_assert_eq!(
&tab_ids,
expected,
"tab group for identifier '{}' must contain exactly the windows with that identifier",
id
);
}
}
#[test]
fn merge_all_windows_preserves_groups(entries in windows_with_identifiers_strategy()) {
let state = WindowTabManager::shared_state();
let managers: Vec<(AnyWindowHandle, WindowTabManager, String)> = entries
.iter()
.map(|(raw, id)| {
let handle = make_handle(*raw);
let mgr = WindowTabManager::new(handle, state.clone());
mgr.set_tabbing_identifier(Some(id.clone()));
(handle, mgr, id.clone())
})
.collect();
let mut expected_groups: std::collections::HashMap<String, HashSet<WindowId>> =
std::collections::HashMap::new();
for (handle, _, id) in &managers {
expected_groups
.entry(id.clone())
.or_default()
.insert(handle.window_id());
}
for (_, mgr, _) in &managers {
mgr.merge_all_windows();
}
for (handle, mgr, id) in &managers {
let tabs = mgr.tabbed_windows();
prop_assert!(
tabs.is_some(),
"window {:?} must still have tabbed_windows after merge",
handle.window_id()
);
let tab_ids: HashSet<WindowId> =
tabs.unwrap().iter().map(|t| t.handle.window_id()).collect();
let expected = &expected_groups[id];
prop_assert_eq!(
&tab_ids,
expected,
"after merge_all_windows, group for '{}' must contain all windows with that identifier",
id
);
}
}
#[test]
fn move_tab_to_new_window_invariant(entries in windows_with_identifiers_strategy()) {
let state = WindowTabManager::shared_state();
let managers: Vec<(AnyWindowHandle, WindowTabManager, String)> = entries
.iter()
.map(|(raw, id)| {
let handle = make_handle(*raw);
let mgr = WindowTabManager::new(handle, state.clone());
mgr.set_tabbing_identifier(Some(id.clone()));
(handle, mgr, id.clone())
})
.collect();
let (moved_handle, moved_mgr, moved_id) = &managers[0];
let group_size_before = managers
.iter()
.filter(|(_, _, id)| id == moved_id)
.count();
moved_mgr.move_tab_to_new_window();
prop_assert!(
moved_mgr.tabbed_windows().is_none(),
"moved window {:?} must be standalone (tabbed_windows returns None)",
moved_handle.window_id()
);
prop_assert_eq!(
moved_mgr.tab_count(),
0,
"moved window must have tab_count 0"
);
let remaining: Vec<&(AnyWindowHandle, WindowTabManager, String)> = managers
.iter()
.filter(|(h, _, id)| id == moved_id && h.window_id() != moved_handle.window_id())
.collect();
if group_size_before > 1 {
for (_, mgr, _) in &remaining {
prop_assert_eq!(
mgr.tab_count(),
group_size_before - 1,
"remaining group members must have N-1 tabs after move"
);
}
}
}
}