basic impl + more comprehensive group tests
This commit is contained in:
parent
0c161d6890
commit
e594541e5e
3 changed files with 292 additions and 187 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -7432,6 +7432,7 @@ dependencies = [
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"pathfinder_geometry",
|
"pathfinder_geometry",
|
||||||
"postage",
|
"postage",
|
||||||
|
"pretty_assertions",
|
||||||
"profiling",
|
"profiling",
|
||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
"raw-window-handle",
|
"raw-window-handle",
|
||||||
|
|
|
@ -229,9 +229,10 @@ collections = { workspace = true, features = ["test-support"] }
|
||||||
env_logger.workspace = true
|
env_logger.workspace = true
|
||||||
http_client = { workspace = true, features = ["test-support"] }
|
http_client = { workspace = true, features = ["test-support"] }
|
||||||
lyon = { version = "1.0", features = ["extra"] }
|
lyon = { version = "1.0", features = ["extra"] }
|
||||||
|
pretty_assertions.workspace = true
|
||||||
rand.workspace = true
|
rand.workspace = true
|
||||||
unicode-segmentation.workspace = true
|
|
||||||
reqwest_client = { workspace = true, features = ["test-support"] }
|
reqwest_client = { workspace = true, features = ["test-support"] }
|
||||||
|
unicode-segmentation.workspace = true
|
||||||
util = { workspace = true, features = ["test-support"] }
|
util = { workspace = true, features = ["test-support"] }
|
||||||
|
|
||||||
[target.'cfg(target_os = "windows")'.build-dependencies]
|
[target.'cfg(target_os = "windows")'.build-dependencies]
|
||||||
|
|
|
@ -6,6 +6,13 @@ use crate::{FocusHandle, FocusId};
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub(crate) struct TabHandles {
|
pub(crate) struct TabHandles {
|
||||||
pub(crate) handles: Vec<FocusHandle>,
|
pub(crate) handles: Vec<FocusHandle>,
|
||||||
|
groups: Vec<GroupDef>,
|
||||||
|
group_depth: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct GroupDef {
|
||||||
|
index: isize,
|
||||||
|
offset: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TabHandles {
|
impl TabHandles {
|
||||||
|
@ -14,7 +21,10 @@ impl TabHandles {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let focus_handle = focus_handle.clone();
|
let mut focus_handle = focus_handle.clone();
|
||||||
|
for group in self.groups.iter().rev().take(self.group_depth) {
|
||||||
|
focus_handle.tab_index += group.index;
|
||||||
|
}
|
||||||
|
|
||||||
// Insert handle with same tab_index last
|
// Insert handle with same tab_index last
|
||||||
if let Some(ix) = self
|
if let Some(ix) = self
|
||||||
|
@ -67,6 +77,18 @@ impl TabHandles {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn begin_group(&mut self, tab_index: isize) {
|
||||||
|
self.groups.push(GroupDef {
|
||||||
|
index: tab_index,
|
||||||
|
offset: 0,
|
||||||
|
});
|
||||||
|
self.group_depth += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn end_group(&mut self) {
|
||||||
|
self.group_depth -= 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -107,7 +129,7 @@ mod tests {
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
enum NodeType {
|
enum NodeType {
|
||||||
TabStop {
|
TabStop {
|
||||||
tab_index: Option<isize>,
|
tab_index: isize,
|
||||||
actual: usize, // Required for tab stops
|
actual: usize, // Required for tab stops
|
||||||
},
|
},
|
||||||
NonTabStop {
|
NonTabStop {
|
||||||
|
@ -115,7 +137,7 @@ mod tests {
|
||||||
// No actual field - these aren't in the tab order
|
// No actual field - these aren't in the tab order
|
||||||
},
|
},
|
||||||
Group {
|
Group {
|
||||||
tab_index: Option<isize>,
|
tab_index: isize,
|
||||||
children: Vec<TreeNode>,
|
children: Vec<TreeNode>,
|
||||||
},
|
},
|
||||||
FocusTrap {
|
FocusTrap {
|
||||||
|
@ -129,7 +151,7 @@ mod tests {
|
||||||
xml_tag: "root".to_string(),
|
xml_tag: "root".to_string(),
|
||||||
handle: None,
|
handle: None,
|
||||||
node_type: NodeType::Group {
|
node_type: NodeType::Group {
|
||||||
tab_index: None,
|
tab_index: isize::MIN,
|
||||||
children: Vec::new(),
|
children: Vec::new(),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -220,6 +242,8 @@ mod tests {
|
||||||
if actual.is_some() {
|
if actual.is_some() {
|
||||||
panic!("tab-group elements should not have 'actual' attribute");
|
panic!("tab-group elements should not have 'actual' attribute");
|
||||||
}
|
}
|
||||||
|
let tab_index = tab_index
|
||||||
|
.expect("tab-group elements should have 'tab-index' attribute");
|
||||||
NodeType::Group {
|
NodeType::Group {
|
||||||
tab_index,
|
tab_index,
|
||||||
children: Vec::new(),
|
children: Vec::new(),
|
||||||
|
@ -236,18 +260,16 @@ mod tests {
|
||||||
_ => {
|
_ => {
|
||||||
// Determine if it's a tab stop based on tab-stop attribute
|
// Determine if it's a tab stop based on tab-stop attribute
|
||||||
let is_tab_stop = tab_stop.unwrap_or(true);
|
let is_tab_stop = tab_stop.unwrap_or(true);
|
||||||
|
|
||||||
if is_tab_stop {
|
if is_tab_stop {
|
||||||
// Tab stops must have an actual value
|
// Tab stops must have an actual value
|
||||||
match actual {
|
let tab_index =
|
||||||
Some(actual_value) => NodeType::TabStop {
|
tab_index.expect("Tab stop must have a 'tab-index' attribute");
|
||||||
tab_index,
|
let actual = actual.expect(&format!(
|
||||||
actual: actual_value,
|
"Tab stop with tab-index={} must have an 'actual' attribute",
|
||||||
},
|
tab_index
|
||||||
None => panic!(
|
));
|
||||||
"Tab stop with tab-index={} must have an 'actual' attribute",
|
NodeType::TabStop { tab_index, actual }
|
||||||
tab_index.map_or("None".to_string(), |v| v.to_string())
|
|
||||||
),
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// Non-tab stops should not have an actual value
|
// Non-tab stops should not have an actual value
|
||||||
if actual.is_some() {
|
if actual.is_some() {
|
||||||
|
@ -307,8 +329,8 @@ mod tests {
|
||||||
NodeType::TabStop { tab_index, actual } => {
|
NodeType::TabStop { tab_index, actual } => {
|
||||||
let mut handle = FocusHandle::new(focus_map);
|
let mut handle = FocusHandle::new(focus_map);
|
||||||
|
|
||||||
if let Some(idx) = tab_index {
|
if *tab_index != isize::MIN {
|
||||||
handle = handle.tab_index(*idx);
|
handle = handle.tab_index(*tab_index);
|
||||||
}
|
}
|
||||||
|
|
||||||
handle = handle.tab_stop(true);
|
handle = handle.tab_stop(true);
|
||||||
|
@ -332,11 +354,16 @@ mod tests {
|
||||||
|
|
||||||
node.handle = Some(handle);
|
node.handle = Some(handle);
|
||||||
}
|
}
|
||||||
NodeType::Group { children, .. } => {
|
NodeType::Group {
|
||||||
|
children,
|
||||||
|
tab_index,
|
||||||
|
} => {
|
||||||
// For now, just process children without special group handling
|
// For now, just process children without special group handling
|
||||||
|
tab_handles.begin_group(*tab_index);
|
||||||
for child in children {
|
for child in children {
|
||||||
construct_recursive(child, focus_map, tab_handles, actual_to_handle);
|
construct_recursive(child, focus_map, tab_handles, actual_to_handle);
|
||||||
}
|
}
|
||||||
|
tab_handles.end_group();
|
||||||
}
|
}
|
||||||
NodeType::FocusTrap { children, .. } => {
|
NodeType::FocusTrap { children, .. } => {
|
||||||
// TODO: Implement focus trap behavior
|
// TODO: Implement focus trap behavior
|
||||||
|
@ -454,30 +481,25 @@ mod tests {
|
||||||
handle_contexts.sort_by_key(|c| c.actual);
|
handle_contexts.sort_by_key(|c| c.actual);
|
||||||
|
|
||||||
// Helper function to format tree structure as XML for error messages
|
// Helper function to format tree structure as XML for error messages
|
||||||
fn format_tree_structure(
|
fn format_tree_structure(node: &TreeNode, tab_handles: &TabHandles) -> String {
|
||||||
node: &TreeNode,
|
let mut result = String::new();
|
||||||
label: &str,
|
|
||||||
actual_map: &HashMap<FocusId, usize>,
|
|
||||||
) -> String {
|
|
||||||
let mut result = format!("{}:\n", label);
|
|
||||||
|
|
||||||
fn format_node(
|
fn format_node(node: &TreeNode, tab_handles: &TabHandles, indent: usize) -> String {
|
||||||
node: &TreeNode,
|
|
||||||
actual_map: &HashMap<FocusId, usize>,
|
|
||||||
indent: usize,
|
|
||||||
) -> String {
|
|
||||||
let mut result = String::new();
|
let mut result = String::new();
|
||||||
let indent_str = " ".repeat(indent);
|
let indent_str = " ".repeat(indent);
|
||||||
|
|
||||||
match &node.node_type {
|
match &node.node_type {
|
||||||
NodeType::TabStop { tab_index, actual } => {
|
NodeType::TabStop { tab_index, actual } => {
|
||||||
|
let actual = node
|
||||||
|
.handle
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|handle| tab_handles.current_index(Some(&handle.id)))
|
||||||
|
.unwrap_or(*actual);
|
||||||
let actual_str = format!(" actual={}", actual);
|
let actual_str = format!(" actual={}", actual);
|
||||||
|
|
||||||
result.push_str(&format!(
|
result.push_str(&format!(
|
||||||
"{}<tab-index={}{}>\n",
|
"{}<tab-index={}{}>\n",
|
||||||
indent_str,
|
indent_str, tab_index, actual_str
|
||||||
tab_index.map_or("None".to_string(), |v| v.to_string()),
|
|
||||||
actual_str
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
NodeType::NonTabStop { tab_index } => {
|
NodeType::NonTabStop { tab_index } => {
|
||||||
|
@ -493,18 +515,17 @@ mod tests {
|
||||||
} => {
|
} => {
|
||||||
result.push_str(&format!(
|
result.push_str(&format!(
|
||||||
"{}<tab-group tab-index={}>\n",
|
"{}<tab-group tab-index={}>\n",
|
||||||
indent_str,
|
indent_str, tab_index
|
||||||
tab_index.map_or("None".to_string(), |v| v.to_string())
|
|
||||||
));
|
));
|
||||||
for child in children {
|
for child in children {
|
||||||
result.push_str(&format_node(child, actual_map, indent + 1));
|
result.push_str(&format_node(child, tab_handles, indent + 1));
|
||||||
}
|
}
|
||||||
result.push_str(&format!("{}</tab-group>\n", indent_str));
|
result.push_str(&format!("{}</tab-group>\n", indent_str));
|
||||||
}
|
}
|
||||||
NodeType::FocusTrap { children } => {
|
NodeType::FocusTrap { children } => {
|
||||||
result.push_str(&format!("{}<focus-trap>\n", indent_str));
|
result.push_str(&format!("{}<focus-trap>\n", indent_str));
|
||||||
for child in children {
|
for child in children {
|
||||||
result.push_str(&format_node(child, actual_map, indent + 1));
|
result.push_str(&format_node(child, tab_handles, indent + 1));
|
||||||
}
|
}
|
||||||
result.push_str(&format!("{}</focus-trap>\n", indent_str));
|
result.push_str(&format!("{}</focus-trap>\n", indent_str));
|
||||||
}
|
}
|
||||||
|
@ -516,7 +537,7 @@ mod tests {
|
||||||
// Skip the root node and format its children
|
// Skip the root node and format its children
|
||||||
if let NodeType::Group { children, .. } = &node.node_type {
|
if let NodeType::Group { children, .. } = &node.node_type {
|
||||||
for child in children {
|
for child in children {
|
||||||
result.push_str(&format_node(child, actual_map, 0));
|
result.push_str(&format_node(child, tab_handles, 0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -530,12 +551,17 @@ mod tests {
|
||||||
current_id: FocusId,
|
current_id: FocusId,
|
||||||
actual_id: Option<FocusId>,
|
actual_id: Option<FocusId>,
|
||||||
expected_id: FocusId,
|
expected_id: FocusId,
|
||||||
|
tab_handles: &TabHandles,
|
||||||
) {
|
) {
|
||||||
if actual_id != Some(expected_id) {
|
if actual_id != Some(expected_id) {
|
||||||
panic!(
|
panic!(
|
||||||
"Tab navigation error!\n\n{}\n\n{}",
|
"Tab navigation error!\n\n{}\n\n{}\n\n{}",
|
||||||
error_label,
|
error_label,
|
||||||
format_tree_with_navigation(tree, current_id, actual_id, expected_id)
|
format_tree_with_navigation(tree, current_id, actual_id, expected_id),
|
||||||
|
pretty_assertions::StrComparison::new(
|
||||||
|
&format_tree_structure(tree, tab_handles),
|
||||||
|
&format_tree_structure(tree, &TabHandles::default()),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -584,10 +610,7 @@ mod tests {
|
||||||
|
|
||||||
result.push_str(&format!(
|
result.push_str(&format!(
|
||||||
"{}<tab-index={}{}>{}\n",
|
"{}<tab-index={}{}>{}\n",
|
||||||
indent_str,
|
indent_str, tab_index, actual_str, nav_comment
|
||||||
tab_index.map_or("None".to_string(), |v| v.to_string()),
|
|
||||||
actual_str,
|
|
||||||
nav_comment
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
NodeType::NonTabStop { tab_index } => {
|
NodeType::NonTabStop { tab_index } => {
|
||||||
|
@ -607,8 +630,7 @@ mod tests {
|
||||||
} => {
|
} => {
|
||||||
result.push_str(&format!(
|
result.push_str(&format!(
|
||||||
"{}<tab-group tab-index={}>\n",
|
"{}<tab-group tab-index={}>\n",
|
||||||
indent_str,
|
indent_str, tab_index
|
||||||
tab_index.map_or("None".to_string(), |v| v.to_string())
|
|
||||||
));
|
));
|
||||||
for child in children {
|
for child in children {
|
||||||
result.push_str(&format_node_with_nav(
|
result.push_str(&format_node_with_nav(
|
||||||
|
@ -682,11 +704,13 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
panic!(
|
panic!(
|
||||||
"Number of tab stops doesn't match! Expected {} but found {}\n\n{}\n{}",
|
"Number of tab stops doesn't match! Expected {} but found {}\n\n{}",
|
||||||
actual_to_handle.len(),
|
actual_to_handle.len(),
|
||||||
handle_contexts.len(),
|
handle_contexts.len(),
|
||||||
format_tree_structure(tree, "Got", &actual_map),
|
pretty_assertions::StrComparison::new(
|
||||||
format_tree_structure(tree, "Expected", &expected_map)
|
&format_tree_structure(tree, tab_handles),
|
||||||
|
&format_tree_structure(tree, &TabHandles::default()),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -742,10 +766,11 @@ mod tests {
|
||||||
|
|
||||||
check_navigation(
|
check_navigation(
|
||||||
tree,
|
tree,
|
||||||
&format!("Tab order mismatch at position {}:\n\n", position),
|
&format!("Tab order mismatch at position {}", position),
|
||||||
started_id,
|
started_id,
|
||||||
went_to_id,
|
went_to_id,
|
||||||
expected_handle.id,
|
expected_handle.id,
|
||||||
|
tab_handles,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -764,6 +789,7 @@ mod tests {
|
||||||
last_id,
|
last_id,
|
||||||
actual_next_id,
|
actual_next_id,
|
||||||
first_id,
|
first_id,
|
||||||
|
tab_handles,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Test prev wraps from first to last
|
// Test prev wraps from first to last
|
||||||
|
@ -775,6 +801,7 @@ mod tests {
|
||||||
first_id,
|
first_id,
|
||||||
actual_prev_id,
|
actual_prev_id,
|
||||||
last_id,
|
last_id,
|
||||||
|
tab_handles,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -836,6 +863,7 @@ mod tests {
|
||||||
current_id,
|
current_id,
|
||||||
actual_next_id,
|
actual_next_id,
|
||||||
expected_next,
|
expected_next,
|
||||||
|
tab_handles,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Test prev navigation
|
// Test prev navigation
|
||||||
|
@ -847,6 +875,7 @@ mod tests {
|
||||||
current_id,
|
current_id,
|
||||||
actual_prev_id,
|
actual_prev_id,
|
||||||
expected_prev,
|
expected_prev,
|
||||||
|
tab_handles,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -865,168 +894,242 @@ mod tests {
|
||||||
eval(&tree, &tab_handles, &actual_to_handle);
|
eval(&tree, &tab_handles, &actual_to_handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
macro_rules! xml_test {
|
||||||
fn test_check_helper() {
|
($test_name:ident, $xml:expr) => {
|
||||||
// Test simple ordering
|
#[test]
|
||||||
let xml = r#"
|
fn $test_name() {
|
||||||
<tab-index=0 actual=0>
|
let xml = $xml;
|
||||||
<tab-index=1 actual=1>
|
check(xml);
|
||||||
<tab-index=2 actual=2>
|
}
|
||||||
"#;
|
};
|
||||||
check(xml);
|
|
||||||
|
|
||||||
// Test with duplicate tab indices (should maintain insertion order within same index)
|
|
||||||
let xml2 = r#"
|
|
||||||
<tab-index=0 actual=0>
|
|
||||||
<tab-index=0 actual=1>
|
|
||||||
<tab-index=1 actual=2>
|
|
||||||
<tab-index=1 actual=3>
|
|
||||||
<tab-index=2 actual=4>
|
|
||||||
"#;
|
|
||||||
check(xml2);
|
|
||||||
|
|
||||||
// Test with negative and positive indices
|
|
||||||
let xml3 = r#"
|
|
||||||
<tab-index=1 actual=2>
|
|
||||||
<tab-index=-1 actual=0>
|
|
||||||
<tab-index=0 actual=1>
|
|
||||||
<tab-index=2 actual=3>
|
|
||||||
"#;
|
|
||||||
check(xml3);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
mod test_helper {
|
||||||
fn test_check_helper_with_nested_structures() {
|
use super::*;
|
||||||
// Test parsing and structure with nested groups and focus traps
|
|
||||||
let xml = r#"
|
|
||||||
<tab-index=0 actual=0>
|
|
||||||
<tab-group tab-index=1>
|
|
||||||
<tab-index=0 actual=1>
|
|
||||||
<focus-trap>
|
|
||||||
<tab-index=0 actual=2>
|
|
||||||
<tab-index=1 actual=3>
|
|
||||||
</focus-trap>
|
|
||||||
<tab-index=1 actual=4>
|
|
||||||
</tab-group>
|
|
||||||
<tab-index=2 actual=5>
|
|
||||||
"#;
|
|
||||||
|
|
||||||
// This should parse successfully even though navigation won't work correctly yet
|
xml_test!(
|
||||||
// The test verifies that our tree structure correctly represents nested elements
|
test_simple_ordering,
|
||||||
check(xml);
|
r#"
|
||||||
}
|
<tab-index=0 actual=0>
|
||||||
|
<tab-index=1 actual=1>
|
||||||
|
<tab-index=2 actual=2>
|
||||||
|
"#
|
||||||
|
);
|
||||||
|
|
||||||
#[test]
|
xml_test!(
|
||||||
fn test_tab_group_functionality() {
|
test_duplicate_indices_maintain_insertion_order,
|
||||||
// This test defines the expected behavior for tab-group
|
r#"
|
||||||
// Tab-group should create a nested tab context where inner elements
|
<tab-index=0 actual=0>
|
||||||
// have tab indices relative to the group
|
|
||||||
let xml = r#"
|
|
||||||
<tab-index=0 actual=0>
|
|
||||||
<tab-index=1 actual=1>
|
|
||||||
<tab-group tab-index=2>
|
|
||||||
<tab-index=0 actual=2>
|
|
||||||
<tab-index=1 actual=3>
|
|
||||||
</tab-group>
|
|
||||||
<tab-index=3 actual=4>
|
|
||||||
"#;
|
|
||||||
check(xml);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_focus_trap_functionality() {
|
|
||||||
// This test defines the expected behavior for focus-trap
|
|
||||||
// Focus-trap should trap navigation within its boundaries
|
|
||||||
let xml = r#"
|
|
||||||
<tab-index=0 actual=0>
|
|
||||||
<focus-trap tab-index=1>
|
|
||||||
<tab-index=0 actual=1>
|
<tab-index=0 actual=1>
|
||||||
<tab-index=1 actual=2>
|
<tab-index=1 actual=2>
|
||||||
</focus-trap>
|
<tab-index=1 actual=3>
|
||||||
<tab-index=2 actual=3>
|
|
||||||
"#;
|
|
||||||
check(xml);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_nested_groups_and_traps() {
|
|
||||||
// This test defines the expected behavior for nested structures
|
|
||||||
let xml = r#"
|
|
||||||
<tab-index=0 actual=0>
|
|
||||||
<tab-group tab-index=1>
|
|
||||||
<tab-index=0 actual=1>
|
|
||||||
<focus-trap tab-index=1>
|
|
||||||
<tab-index=0 actual=2>
|
|
||||||
<tab-index=1 actual=3>
|
|
||||||
</focus-trap>
|
|
||||||
<tab-index=2 actual=4>
|
<tab-index=2 actual=4>
|
||||||
</tab-group>
|
"#
|
||||||
<tab-index=2 actual=5>
|
);
|
||||||
"#;
|
|
||||||
check(xml);
|
xml_test!(
|
||||||
|
test_positive_and_negative_indices,
|
||||||
|
r#"
|
||||||
|
<tab-index=1 actual=2>
|
||||||
|
<tab-index=-1 actual=0>
|
||||||
|
<tab-index=0 actual=1>
|
||||||
|
<tab-index=2 actual=3>
|
||||||
|
"#
|
||||||
|
);
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic(
|
||||||
|
expected = "Non-tab stop (tab-stop=false) should not have an 'actual' attribute"
|
||||||
|
)]
|
||||||
|
fn test_non_tab_stop_with_actual_panics() {
|
||||||
|
let xml = r#"
|
||||||
|
<tab-index=0 actual=0>
|
||||||
|
<tab-index=1 tab-stop=false actual=1>
|
||||||
|
<tab-index=2 actual=2>
|
||||||
|
"#;
|
||||||
|
check(xml);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic(expected = "Tab stop with tab-index=1 must have an 'actual' attribute")]
|
||||||
|
fn test_tab_stop_without_actual_panics() {
|
||||||
|
// Tab stops must have an actual value
|
||||||
|
let xml = r#"
|
||||||
|
<tab-index=0 actual=0>
|
||||||
|
<tab-index=1>
|
||||||
|
<tab-index=2 actual=2>
|
||||||
|
"#;
|
||||||
|
check(xml);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic(expected = "Tab order mismatch at position")]
|
||||||
|
fn test_incorrect_tab_order_shows_xml_format() {
|
||||||
|
// This test intentionally has wrong expected order to demonstrate error reporting
|
||||||
|
// The actual tab order will be: tab-index=-1, 0, 1, 2 (positions 0, 1, 2, 3)
|
||||||
|
// But we're expecting them at wrong positions
|
||||||
|
let xml = r#"
|
||||||
|
<tab-index=0 actual=0>
|
||||||
|
<tab-index=-1 actual=1>
|
||||||
|
<tab-index=2 actual=2>
|
||||||
|
<tab-index=1 actual=3>
|
||||||
|
"#;
|
||||||
|
check(xml);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
mod basic {
|
||||||
fn test_with_disabled_tab_stops() {
|
use super::*;
|
||||||
// Test with mixed tab-stop values
|
|
||||||
let xml = r#"
|
xml_test!(
|
||||||
|
test_with_disabled_tab_stop,
|
||||||
|
r#"
|
||||||
<tab-index=0 actual=0>
|
<tab-index=0 actual=0>
|
||||||
<tab-index=1 tab-stop=false>
|
<tab-index=1 tab-stop=false>
|
||||||
<tab-index=2 actual=1>
|
<tab-index=2 actual=1>
|
||||||
<tab-index=3 actual=2>
|
<tab-index=3 actual=2>
|
||||||
"#;
|
"#
|
||||||
check(xml);
|
);
|
||||||
|
|
||||||
// Test with all disabled except specific ones
|
xml_test!(
|
||||||
let xml2 = r#"
|
test_with_disabled_tab_stops,
|
||||||
|
r#"
|
||||||
<tab-index=0 tab-stop=false>
|
<tab-index=0 tab-stop=false>
|
||||||
<tab-index=1 actual=0>
|
<tab-index=1 actual=0>
|
||||||
<tab-index=2 tab-stop=false>
|
<tab-index=2 tab-stop=false>
|
||||||
<tab-index=3 actual=1>
|
<tab-index=3 actual=1>
|
||||||
<tab-index=4 tab-stop=false>
|
<tab-index=4 tab-stop=false>
|
||||||
"#;
|
"#
|
||||||
check(xml2);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
mod tab_group {
|
||||||
#[should_panic(expected = "Tab stop with tab-index=1 must have an 'actual' attribute")]
|
use super::*;
|
||||||
fn test_tab_stop_without_actual_panics() {
|
|
||||||
// Tab stops must have an actual value
|
// This test defines the expected behavior for tab-group
|
||||||
let xml = r#"
|
// Tab-group should create a nested tab context where inner elements
|
||||||
<tab-index=0 actual=0>
|
// have tab indices relative to the group
|
||||||
<tab-index=1>
|
xml_test!(
|
||||||
<tab-index=2 actual=2>
|
test_tab_group_functionality,
|
||||||
"#;
|
r#"
|
||||||
check(xml);
|
<tab-index=0 actual=0>
|
||||||
|
<tab-index=1 actual=1>
|
||||||
|
<tab-group tab-index=2>
|
||||||
|
<tab-index=0 actual=2>
|
||||||
|
<tab-index=1 actual=3>
|
||||||
|
</tab-group>
|
||||||
|
<tab-index=3 actual=4>
|
||||||
|
<tab-index=4 actual=5>
|
||||||
|
"#
|
||||||
|
);
|
||||||
|
|
||||||
|
xml_test!(
|
||||||
|
test_sibling_groups,
|
||||||
|
r#"
|
||||||
|
<tab-index=0 actual=0>
|
||||||
|
<tab-index=1 actual=1>
|
||||||
|
<tab-group tab-index=2>
|
||||||
|
<tab-index=0 actual=2>
|
||||||
|
<tab-index=1 actual=3>
|
||||||
|
</tab-group>
|
||||||
|
<tab-index=3 actual=4>
|
||||||
|
<tab-index=4 actual=5>
|
||||||
|
<tab-group tab-index=6>
|
||||||
|
<tab-index=0 actual=6>
|
||||||
|
<tab-index=1 actual=7>
|
||||||
|
</tab-group>
|
||||||
|
<tab-index=7 actual=8>
|
||||||
|
<tab-index=8 actual=9>
|
||||||
|
"#
|
||||||
|
);
|
||||||
|
|
||||||
|
xml_test!(
|
||||||
|
test_nested_group,
|
||||||
|
r#"
|
||||||
|
<tab-index=0 actual=0>
|
||||||
|
<tab-index=1 actual=1>
|
||||||
|
<tab-group tab-index=2>
|
||||||
|
<tab-group tab-index=0>
|
||||||
|
<tab-index=0 actual=2>
|
||||||
|
<tab-index=1 actual=3>
|
||||||
|
</tab-group>
|
||||||
|
</tab-group>
|
||||||
|
<tab-index=3 actual=4>
|
||||||
|
<tab-index=4 actual=5>
|
||||||
|
"#
|
||||||
|
);
|
||||||
|
|
||||||
|
xml_test!(
|
||||||
|
test_sibling_nested_groups,
|
||||||
|
r#"
|
||||||
|
<tab-index=0 actual=0>
|
||||||
|
<tab-index=1 actual=1>
|
||||||
|
<tab-group tab-index=2>
|
||||||
|
<tab-index=0 actual=2>
|
||||||
|
<tab-group tab-index=1>
|
||||||
|
<tab-index=0 actual=3>
|
||||||
|
<tab-index=1 actual=4>
|
||||||
|
</tab-group>
|
||||||
|
<tab-index=2 actual=5>
|
||||||
|
<tab-group tab-index=3>
|
||||||
|
<tab-index=0 actual=6>
|
||||||
|
<tab-index=1 actual=7>
|
||||||
|
</tab-group>
|
||||||
|
</tab-group>
|
||||||
|
<tab-index=3 actual=8>
|
||||||
|
<tab-index=4 actual=9>
|
||||||
|
"#
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
mod focus_trap {
|
||||||
#[should_panic(
|
use super::*;
|
||||||
expected = "Non-tab stop (tab-stop=false) should not have an 'actual' attribute"
|
|
||||||
)]
|
|
||||||
fn test_non_tab_stop_with_actual_panics() {
|
|
||||||
// Non-tab stops should not have an actual value
|
|
||||||
let xml = r#"
|
|
||||||
<tab-index=0 actual=0>
|
|
||||||
<tab-index=1 tab-stop=false actual=1>
|
|
||||||
<tab-index=2 actual=2>
|
|
||||||
"#;
|
|
||||||
check(xml);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
xml_test!(
|
||||||
#[should_panic(expected = "Tab order mismatch at position")]
|
test_focus_trap_in_group,
|
||||||
fn test_incorrect_tab_order_shows_xml_format() {
|
r#"
|
||||||
// This test intentionally has wrong expected order to demonstrate error reporting
|
<tab-index=0 actual=0>
|
||||||
// The actual tab order will be: tab-index=-1, 0, 1, 2 (positions 0, 1, 2, 3)
|
<tab-group tab-index=1>
|
||||||
// But we're expecting them at wrong positions
|
<tab-index=0 actual=1>
|
||||||
let xml = r#"
|
<focus-trap>
|
||||||
<tab-index=0 actual=0>
|
<tab-index=0 actual=2>
|
||||||
<tab-index=-1 actual=1>
|
<tab-index=1 actual=3>
|
||||||
<tab-index=2 actual=2>
|
</focus-trap>
|
||||||
<tab-index=1 actual=3>
|
<tab-index=1 actual=4>
|
||||||
"#;
|
</tab-group>
|
||||||
check(xml);
|
<tab-index=2 actual=5>
|
||||||
|
"#
|
||||||
|
);
|
||||||
|
|
||||||
|
// This test defines the expected behavior for focus-trap
|
||||||
|
// Focus-trap should trap navigation within its boundaries
|
||||||
|
xml_test!(
|
||||||
|
test_focus_trap_functionality,
|
||||||
|
r#"
|
||||||
|
<tab-index=0 actual=0>
|
||||||
|
<focus-trap tab-index=1>
|
||||||
|
<tab-index=0 actual=1>
|
||||||
|
<tab-index=1 actual=2>
|
||||||
|
</focus-trap>
|
||||||
|
<tab-index=2 actual=3>
|
||||||
|
"#
|
||||||
|
);
|
||||||
|
|
||||||
|
xml_test!(
|
||||||
|
test_nested_groups_and_traps,
|
||||||
|
r#"
|
||||||
|
<tab-index=0 actual=0>
|
||||||
|
<tab-group tab-index=1>
|
||||||
|
<tab-index=0 actual=1>
|
||||||
|
<focus-trap tab-index=1>
|
||||||
|
<tab-index=0 actual=2>
|
||||||
|
<tab-index=1 actual=3>
|
||||||
|
</focus-trap>
|
||||||
|
<tab-index=2 actual=4>
|
||||||
|
</tab-group>
|
||||||
|
<tab-index=2 actual=5>
|
||||||
|
"#
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue