use super::*;
fn node(id: u32, children: Vec<u32>) -> QuickSpatialNodeEntry {
QuickSpatialNodeEntry {
express_id: id,
type_name: "IfcSpace".to_string(),
name: format!("#{id}"),
elevation: None,
children,
contained: vec![],
elements: vec![],
named_as_child: false,
}
}
#[test]
fn parse_step_string_un_doubles_exactly_once() {
assert_eq!(parse_step_string(b"'O''Brien'").as_deref(), Some("O'Brien"));
assert_eq!(parse_step_string(b"''''''").as_deref(), Some("''"));
assert_eq!(parse_step_string(br"'C:\\temp'").as_deref(), Some(r"C:\temp"));
assert_eq!(parse_step_string(br"'caf\X2\00E9\X0\'").as_deref(), Some("caf\u{e9}"));
assert_eq!(parse_step_string(b"'Plain Name'").as_deref(), Some("Plain Name"));
}
#[test]
fn cyclic_aggregate_graph_does_not_stack_overflow() {
let mut nodes = HashMap::new();
nodes.insert(1, node(1, vec![2]));
nodes.insert(2, node(2, vec![1]));
let summaries = HashMap::new();
let tree = build_quick_spatial_tree_node(1, &nodes, &summaries);
assert!(tree.is_ok(), "cyclic tree should build (cycle pruned), got {tree:?}");
let two = &tree.unwrap().0.children[0];
assert!(two.children.is_empty() && !two.summary.has_children);
}
#[test]
fn storey_elevation_prefers_index_9_over_index_8() {
let args: Vec<&[u8]> = vec![
b"$", b"$", b"$", b"$", b"$", b"$", b"$", b"$", b"3.5", b"7.25",
];
assert_eq!(
extract_storey_elevation_from_args(&args),
Some(7.25),
"index 9 (the real Elevation attribute) must win over index 8"
);
}
#[test]
fn depth_limited_child_is_placed_by_a_shorter_path() {
let deep = MAX_QUICK_SPATIAL_TREE_DEPTH as u32;
let leaf = deep + 1;
let summaries = HashMap::new();
for root_children in [vec![1, leaf], vec![leaf, 1]] {
let mut nodes: HashMap<u32, QuickSpatialNodeEntry> =
(1..=deep).map(|id| (id, node(id, vec![id + 1]))).collect();
nodes.insert(0, node(0, root_children.clone()));
nodes.insert(leaf, node(leaf, vec![]));
let (root, pruned) = build_quick_spatial_tree_node(0, &nodes, &summaries).unwrap();
assert!(
root.children.iter().any(|c| c.summary.express_id == leaf),
"root children {root_children:?}: #{leaf} is placed under the root"
);
assert_eq!(
pruned.len(),
1,
"root children {root_children:?}: {pruned:?}"
);
assert_eq!(
(pruned[0].parent_express_id, pruned[0].child_express_id),
(deep, leaf)
);
}
}
#[test]
fn fallback_containment_past_the_depth_limit_does_not_hide_a_shallow_one() {
let last = 10 + MAX_QUICK_SPATIAL_TREE_DEPTH as u32 + 9;
let (shallow, space, orphan) = (300, 500, 600);
let mut nodes: HashMap<u32, QuickSpatialNodeEntry> =
(10..last).map(|id| (id, node(id, vec![id + 1]))).collect();
let mut deep_end = node(last, vec![]);
deep_end.contained = vec![space];
nodes.insert(last, deep_end);
let mut shallow_node = node(shallow, vec![]);
shallow_node.contained = vec![space];
nodes.insert(shallow, shallow_node);
nodes.insert(space, node(space, vec![]));
nodes.insert(orphan, node(orphan, vec![space]));
nodes.insert(0, node(0, vec![shallow, 10]));
let (root, _) = build_quick_spatial_tree_node(0, &nodes, &HashMap::new()).unwrap();
let shallow_node = root
.children
.iter()
.find(|c| c.summary.express_id == shallow);
assert!(
shallow_node.is_some_and(|n| n.children.iter().any(|c| c.summary.express_id == space)),
"#{space} is placed under #{shallow}"
);
}
#[test]
fn unsettled_space_keeps_a_shallow_containment_found_late() {
fn contains(node: &QuickMetadataSpatialNode, id: u32) -> bool {
node.summary.express_id == id || node.children.iter().any(|c| contains(c, id))
}
let last = 10 + MAX_QUICK_SPATIAL_TREE_DEPTH as u32 + 9;
let with_contained = |id: u32, contained: Vec<u32>| QuickSpatialNodeEntry {
contained,
..node(id, vec![])
};
let mut nodes: HashMap<u32, QuickSpatialNodeEntry> =
(10..last).map(|id| (id, node(id, vec![id + 1]))).collect();
nodes.insert(last, with_contained(last, vec![500]));
nodes.insert(1, with_contained(1, vec![400]));
nodes.insert(400, with_contained(400, vec![300]));
nodes.insert(300, with_contained(300, vec![500]));
nodes.insert(500, node(500, vec![]));
nodes.insert(600, node(600, vec![400, 300]));
nodes.insert(601, node(601, vec![500]));
nodes.insert(0, node(0, vec![1, 10]));
let (root, _) = build_quick_spatial_tree_node(0, &nodes, &HashMap::new()).unwrap();
assert!(contains(&root, 500), "#500 is in the tree");
}
#[test]
fn quick_spatial_predicate_matches_the_generated_spatial_branch() {
use ifc_lite_core::{IfcType, IFC_TYPES};
fn rule(ty: IfcType) -> bool {
ty == IfcType::IfcProject
|| (ty.is_subtype_of(IfcType::IfcSpatialElement)
&& !ty.is_subtype_of(IfcType::IfcExternalSpatialStructureElement))
}
let mut expected_true = 0usize;
let mut missing = Vec::new();
let mut extra = Vec::new();
for ty in IFC_TYPES {
let name = ty.as_str();
let want = rule(*ty);
if want {
expected_true += 1;
}
let got = is_quick_spatial_type_ci(name);
if want && !got {
missing.push(name);
}
if !want && got {
extra.push(name);
}
}
assert!(
IFC_TYPES.len() > 800,
"generated IFC_TYPES looks truncated: {} entries",
IFC_TYPES.len()
);
assert!(
expected_true >= 17,
"the spatial branch should cover at least 17 types, got {expected_true}"
);
assert!(
missing.is_empty() && extra.is_empty(),
"quick-metadata spatial predicate has drifted from the generated schema\n \
missing (severed from the spatial tree): {missing:?}\n \
extra (invented spatial nodes): {extra:?}"
);
}
#[test]
fn issue_4687_step_arguments_treat_comments_as_trivia() {
for record in [
&b"#50=IFCRELAGGREGATES('0YvctVUKr0kugbFTf53O9L',$,$,/* a, b */$,#1,(#2,#3));"[..],
b"#50=IFCRELAGGREGATES('0YvctVUKr0kugbFTf53O9L',$,$,/* it's */$,#1,(#2,#3));",
b"#50=IFCRELAGGREGATES('0YvctVUKr0kugbFTf53O9L',$,$,$,#1 /* x */,(#2, /* door */ #3));",
] {
let args = parse_step_arguments(record);
let text = String::from_utf8_lossy(record);
assert_eq!(args.len(), 6, "{text}");
assert_eq!(args.get(4).and_then(|token| parse_step_ref(token)), Some(1), "{text}");
assert_eq!(parse_step_ref_list(args[5]), [2, 3], "{text}");
}
}
#[test]
fn quick_spatial_predicate_controls() {
for name in ["IFCWALL", "IFCRELAGGREGATES", "IFCPROJECTLIBRARY", "IFCZONE"] {
assert!(!is_quick_spatial_type_ci(name), "{name} must not be a spatial node");
}
for name in ["IFCEXTERNALSPATIALELEMENT", "IFCEXTERNALSPATIALSTRUCTUREELEMENT"] {
assert!(!is_quick_spatial_type_ci(name), "{name} must not be a spatial node");
}
for name in ["IfcMarineFacility", "IFCMARINEFACILITY", "ifcmarinefacility"] {
assert!(is_quick_spatial_type_ci(name), "{name} must be a spatial node");
}
}