use std::collections::BTreeMap;
use azul_core::{
dom::{DomId, DomNodeId, NodeId},
selection::{
CursorAffinity, GraphemeClusterId, Selection, SelectionState, SelectionVec, TextCursor,
},
styled_dom::NodeHierarchyItemId,
};
use azul_layout::{
callbacks::FocusUpdateRequest,
managers::{focus_cursor::FocusManager, selection::SelectionManager},
window::LayoutWindow,
};
fn create_test_font_cache() -> rust_fontconfig::FcFontCache {
rust_fontconfig::FcFontCache::default()
}
#[test]
fn test_focus_manager_basic_operations() {
let mut manager = FocusManager::new();
assert_eq!(manager.get_focused_node(), None);
let node1 = DomNodeId {
dom: DomId::ROOT_ID,
node: NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(1))),
};
manager.set_focused_node(Some(node1.clone()));
assert_eq!(manager.get_focused_node(), Some(&node1));
let node2 = DomNodeId {
dom: DomId::ROOT_ID,
node: NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(2))),
};
manager.set_focused_node(Some(node2.clone()));
assert_eq!(manager.get_focused_node(), Some(&node2));
manager.set_focused_node(None);
assert_eq!(manager.get_focused_node(), None);
}
#[test]
fn test_focus_update_request_enum() {
let node = DomNodeId {
dom: DomId::ROOT_ID,
node: NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(5))),
};
let req = FocusUpdateRequest::FocusNode(node.clone());
assert!(req.is_change());
assert_eq!(req.to_focused_node(), Some(Some(node)));
let req = FocusUpdateRequest::ClearFocus;
assert!(req.is_change());
assert_eq!(req.to_focused_node(), Some(None));
let req = FocusUpdateRequest::NoChange;
assert!(!req.is_change());
assert_eq!(req.to_focused_node(), None);
}
#[test]
fn test_focus_update_request_from_optional() {
let node = DomNodeId {
dom: DomId::ROOT_ID,
node: NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(3))),
};
let req = FocusUpdateRequest::from_optional(Some(Some(node.clone())));
assert!(matches!(req, FocusUpdateRequest::FocusNode(_)));
assert!(req.is_change());
let req = FocusUpdateRequest::from_optional(Some(None));
assert!(matches!(req, FocusUpdateRequest::ClearFocus));
assert!(req.is_change());
let req = FocusUpdateRequest::from_optional(None);
assert!(matches!(req, FocusUpdateRequest::NoChange));
assert!(!req.is_change());
}
#[test]
fn test_selection_manager_clear_all() {
let mut selection_manager = SelectionManager::new();
let dom1 = DomId::ROOT_ID;
let dom2 = DomId { inner: 1 };
let node1 = DomNodeId {
dom: dom1,
node: NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(1))),
};
let node2 = DomNodeId {
dom: dom2,
node: NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(2))),
};
let sel_state1 = SelectionState {
selections: vec![Selection::Cursor(TextCursor {
cluster_id: GraphemeClusterId {
source_run: 0,
start_byte_in_run: 0,
},
affinity: CursorAffinity::Leading,
})]
.into(),
node_id: node1.clone(),
};
let sel_state2 = SelectionState {
selections: vec![Selection::Cursor(TextCursor {
cluster_id: GraphemeClusterId {
source_run: 0,
start_byte_in_run: 5,
},
affinity: CursorAffinity::Trailing,
})]
.into(),
node_id: node2.clone(),
};
selection_manager.set_selection(dom1, sel_state1);
selection_manager.set_selection(dom2, sel_state2);
assert!(selection_manager.get_selection(&dom1).is_some());
assert!(selection_manager.get_selection(&dom2).is_some());
assert!(selection_manager.has_any_selection());
selection_manager.clear_all();
assert!(selection_manager.get_selection(&dom1).is_none());
assert!(selection_manager.get_selection(&dom2).is_none());
assert!(!selection_manager.has_any_selection());
}
#[test]
fn test_focus_change_clears_selections() {
let mut focus_manager = FocusManager::new();
let mut selection_manager = SelectionManager::new();
let node1 = DomNodeId {
dom: DomId::ROOT_ID,
node: NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(1))),
};
focus_manager.set_focused_node(Some(node1.clone()));
let sel_state = SelectionState {
selections: vec![Selection::Cursor(TextCursor {
cluster_id: GraphemeClusterId {
source_run: 0,
start_byte_in_run: 0,
},
affinity: CursorAffinity::Leading,
})]
.into(),
node_id: node1.clone(),
};
selection_manager.set_selection(DomId::ROOT_ID, sel_state);
assert!(selection_manager.get_selection(&DomId::ROOT_ID).is_some());
let old_focus = focus_manager.get_focused_node().copied();
let node2 = DomNodeId {
dom: DomId::ROOT_ID,
node: NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(2))),
};
focus_manager.set_focused_node(Some(node2.clone()));
let new_focus = focus_manager.get_focused_node();
assert_ne!(old_focus.as_ref(), new_focus);
if old_focus.as_ref() != new_focus {
selection_manager.clear_all();
}
assert!(selection_manager.get_selection(&DomId::ROOT_ID).is_none());
}
#[test]
fn test_focus_manager_with_layout_window() {
let fc_cache = create_test_font_cache();
let mut layout_window = LayoutWindow::new(fc_cache).expect("Failed to create LayoutWindow");
assert_eq!(layout_window.focus_manager.get_focused_node(), None);
let node = DomNodeId {
dom: DomId::ROOT_ID,
node: NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(1))),
};
layout_window
.focus_manager
.set_focused_node(Some(node.clone()));
assert_eq!(layout_window.focus_manager.get_focused_node(), Some(&node));
layout_window.focus_manager.set_focused_node(None);
assert_eq!(layout_window.focus_manager.get_focused_node(), None);
}
#[test]
fn test_recursive_focus_change_detection() {
let mut focus_manager = FocusManager::new();
let mut recursion_count = 0;
const MAX_RECURSION: usize = 5;
let nodes = vec![
DomNodeId {
dom: DomId::ROOT_ID,
node: NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(0))),
},
DomNodeId {
dom: DomId::ROOT_ID,
node: NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(1))),
},
DomNodeId {
dom: DomId::ROOT_ID,
node: NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(2))),
},
DomNodeId {
dom: DomId::ROOT_ID,
node: NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(3))),
},
DomNodeId {
dom: DomId::ROOT_ID,
node: NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(4))),
},
DomNodeId {
dom: DomId::ROOT_ID,
node: NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(5))),
},
];
focus_manager.set_focused_node(Some(nodes[0].clone()));
for i in 1..nodes.len() {
if recursion_count >= MAX_RECURSION {
break;
}
let old_focus = focus_manager.get_focused_node().copied();
focus_manager.set_focused_node(Some(nodes[i].clone()));
let new_focus = focus_manager.get_focused_node();
assert_ne!(old_focus.as_ref(), new_focus);
recursion_count += 1;
}
assert_eq!(recursion_count, MAX_RECURSION);
assert_eq!(
focus_manager.get_focused_node(),
Some(&nodes[MAX_RECURSION])
);
}
#[test]
fn test_focus_change_cascade_with_selection_clearing() {
let mut focus_manager = FocusManager::new();
let mut selection_manager = SelectionManager::new();
let dom_id = DomId::ROOT_ID;
let nodes = vec![
DomNodeId {
dom: dom_id,
node: NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(0))),
},
DomNodeId {
dom: dom_id,
node: NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(1))),
},
DomNodeId {
dom: dom_id,
node: NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(2))),
},
];
focus_manager.set_focused_node(Some(nodes[0].clone()));
let sel_state = SelectionState {
selections: vec![Selection::Cursor(TextCursor {
cluster_id: GraphemeClusterId {
source_run: 0,
start_byte_in_run: 0,
},
affinity: CursorAffinity::Leading,
})]
.into(),
node_id: nodes[0].clone(),
};
selection_manager.set_selection(dom_id, sel_state);
let old_focus = focus_manager.get_focused_node().copied();
focus_manager.set_focused_node(Some(nodes[1].clone()));
let new_focus = focus_manager.get_focused_node().copied();
assert_ne!(old_focus, new_focus);
selection_manager.clear_all();
assert!(selection_manager.get_selection(&dom_id).is_none());
let sel_state = SelectionState {
selections: vec![Selection::Cursor(TextCursor {
cluster_id: GraphemeClusterId {
source_run: 0,
start_byte_in_run: 5,
},
affinity: CursorAffinity::Leading,
})]
.into(),
node_id: nodes[1].clone(),
};
selection_manager.set_selection(dom_id, sel_state);
let old_focus = focus_manager.get_focused_node().copied();
focus_manager.set_focused_node(Some(nodes[2].clone()));
let new_focus = focus_manager.get_focused_node().copied();
assert_ne!(old_focus, new_focus);
selection_manager.clear_all();
assert!(selection_manager.get_selection(&dom_id).is_none());
assert!(!selection_manager.has_any_selection());
assert_eq!(focus_manager.get_focused_node(), Some(&nodes[2]));
}
#[test]
fn test_focus_clear_then_set() {
let mut focus_manager = FocusManager::new();
let node1 = DomNodeId {
dom: DomId::ROOT_ID,
node: NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(1))),
};
focus_manager.set_focused_node(Some(node1.clone()));
assert_eq!(focus_manager.get_focused_node(), Some(&node1));
focus_manager.set_focused_node(None);
assert_eq!(focus_manager.get_focused_node(), None);
let node2 = DomNodeId {
dom: DomId::ROOT_ID,
node: NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(2))),
};
focus_manager.set_focused_node(Some(node2.clone()));
assert_eq!(focus_manager.get_focused_node(), Some(&node2));
}
#[test]
fn test_multiple_selection_clear_operations() {
let mut selection_manager = SelectionManager::new();
let doms = vec![
DomId::ROOT_ID,
DomId { inner: 1 },
DomId { inner: 2 },
DomId { inner: 3 },
];
for (i, dom_id) in doms.iter().enumerate() {
let node = DomNodeId {
dom: *dom_id,
node: NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(i))),
};
let sel_state = SelectionState {
selections: vec![Selection::Cursor(TextCursor {
cluster_id: GraphemeClusterId {
source_run: 0,
start_byte_in_run: (i * 5) as u32,
},
affinity: CursorAffinity::Leading,
})]
.into(),
node_id: node,
};
selection_manager.set_selection(*dom_id, sel_state);
}
for dom_id in &doms {
assert!(selection_manager.get_selection(dom_id).is_some());
}
assert!(selection_manager.has_any_selection());
selection_manager.clear_all();
for dom_id in &doms {
assert!(selection_manager.get_selection(dom_id).is_none());
}
assert!(!selection_manager.has_any_selection());
for (i, dom_id) in doms.iter().enumerate() {
let node = DomNodeId {
dom: *dom_id,
node: NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(i + 10))),
};
let sel_state = SelectionState {
selections: vec![Selection::Cursor(TextCursor {
cluster_id: GraphemeClusterId {
source_run: 0,
start_byte_in_run: (i * 10) as u32,
},
affinity: CursorAffinity::Trailing,
})]
.into(),
node_id: node,
};
selection_manager.set_selection(*dom_id, sel_state);
}
selection_manager.clear_all();
for dom_id in &doms {
assert!(selection_manager.get_selection(dom_id).is_none());
}
assert!(!selection_manager.has_any_selection());
}
#[test]
fn test_focus_update_request_conversion_edge_cases() {
let root_node = DomNodeId {
dom: DomId::ROOT_ID,
node: NodeHierarchyItemId::from_crate_internal(Some(NodeId::ZERO)),
};
let req = FocusUpdateRequest::FocusNode(root_node.clone());
assert!(req.is_change());
assert_eq!(req.to_focused_node(), Some(Some(root_node)));
let req1 = FocusUpdateRequest::ClearFocus;
let opt1 = req1.to_focused_node();
let req2 = FocusUpdateRequest::from_optional(opt1);
assert!(matches!(req2, FocusUpdateRequest::ClearFocus));
let req1 = FocusUpdateRequest::NoChange;
let opt1 = req1.to_focused_node();
let req2 = FocusUpdateRequest::from_optional(opt1);
assert!(matches!(req2, FocusUpdateRequest::NoChange));
}
#[test]
fn test_focus_manager_integration_with_all_managers() {
let fc_cache = create_test_font_cache();
let mut layout_window = LayoutWindow::new(fc_cache).expect("Failed to create LayoutWindow");
let dom_id = DomId::ROOT_ID;
let node1 = DomNodeId {
dom: dom_id,
node: NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(1))),
};
let node2 = DomNodeId {
dom: dom_id,
node: NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(2))),
};
layout_window
.focus_manager
.set_focused_node(Some(node1.clone()));
let sel_state = SelectionState {
selections: vec![Selection::Cursor(TextCursor {
cluster_id: GraphemeClusterId {
source_run: 0,
start_byte_in_run: 0,
},
affinity: CursorAffinity::Leading,
})]
.into(),
node_id: node1.clone(),
};
layout_window
.selection_manager
.set_selection(dom_id, sel_state);
assert_eq!(layout_window.focus_manager.get_focused_node(), Some(&node1));
assert!(layout_window
.selection_manager
.get_selection(&dom_id)
.is_some());
let old_focus = layout_window.focus_manager.get_focused_node().copied();
layout_window
.focus_manager
.set_focused_node(Some(node2.clone()));
let new_focus = layout_window.focus_manager.get_focused_node();
assert_ne!(old_focus.as_ref(), new_focus);
if old_focus.as_ref() != new_focus {
layout_window.selection_manager.clear_all();
}
assert!(layout_window
.selection_manager
.get_selection(&dom_id)
.is_none());
assert_eq!(layout_window.focus_manager.get_focused_node(), Some(&node2));
}
#[test]
fn test_recursion_depth_limit_enforcement() {
const MAX_DEPTH: usize = 5;
let mut focus_manager = FocusManager::new();
let mut depth = 0;
let nodes: Vec<DomNodeId> = (0..=MAX_DEPTH + 2)
.map(|i| DomNodeId {
dom: DomId::ROOT_ID,
node: NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(i))),
})
.collect();
focus_manager.set_focused_node(Some(nodes[0].clone()));
for i in 1..nodes.len() {
if depth >= MAX_DEPTH {
break;
}
let old_focus = focus_manager.get_focused_node().copied();
focus_manager.set_focused_node(Some(nodes[i].clone()));
let new_focus = focus_manager.get_focused_node();
if old_focus.as_ref() != new_focus {
depth += 1;
}
}
assert_eq!(depth, MAX_DEPTH);
assert_eq!(focus_manager.get_focused_node(), Some(&nodes[MAX_DEPTH]));
assert_ne!(
focus_manager.get_focused_node(),
Some(&nodes[MAX_DEPTH + 1])
);
}
#[test]
fn test_selection_persistence_without_focus_change() {
let mut focus_manager = FocusManager::new();
let mut selection_manager = SelectionManager::new();
let dom_id = DomId::ROOT_ID;
let node = DomNodeId {
dom: dom_id,
node: NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(1))),
};
focus_manager.set_focused_node(Some(node.clone()));
let sel_state = SelectionState {
selections: vec![Selection::Cursor(TextCursor {
cluster_id: GraphemeClusterId {
source_run: 0,
start_byte_in_run: 5,
},
affinity: CursorAffinity::Leading,
})]
.into(),
node_id: node.clone(),
};
selection_manager.set_selection(dom_id, sel_state.clone());
let old_focus = focus_manager.get_focused_node().copied();
focus_manager.set_focused_node(Some(node.clone()));
let new_focus = focus_manager.get_focused_node().copied();
assert_eq!(old_focus, new_focus);
if old_focus != new_focus {
selection_manager.clear_all();
}
assert!(selection_manager.get_selection(&dom_id).is_some());
let current_sel = selection_manager.get_selection(&dom_id).unwrap();
assert_eq!(current_sel.node_id, node);
assert_eq!(current_sel.selections.len(), 1);
}
#[test]
fn test_focus_update_request_equality() {
let node1 = DomNodeId {
dom: DomId::ROOT_ID,
node: NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(1))),
};
let node2 = DomNodeId {
dom: DomId::ROOT_ID,
node: NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(2))),
};
let req1 = FocusUpdateRequest::FocusNode(node1.clone());
let req2 = FocusUpdateRequest::FocusNode(node1.clone());
let req3 = FocusUpdateRequest::FocusNode(node2.clone());
assert_eq!(req1, req2);
assert_ne!(req1, req3);
let req1 = FocusUpdateRequest::ClearFocus;
let req2 = FocusUpdateRequest::ClearFocus;
assert_eq!(req1, req2);
let req1 = FocusUpdateRequest::NoChange;
let req2 = FocusUpdateRequest::NoChange;
assert_eq!(req1, req2);
let req1 = FocusUpdateRequest::FocusNode(node1.clone());
let req2 = FocusUpdateRequest::ClearFocus;
let req3 = FocusUpdateRequest::NoChange;
assert_ne!(req1, req2);
assert_ne!(req2, req3);
assert_ne!(req1, req3);
}