Files
ARCADIA-Studio/crates/oa-desktop/src/diagram_usage/coverage_tests.rs
T

387 lines
13 KiB
Rust

//! Independent allocation membership oracles. Expected targets and coordinates
//! come from handwritten fixtures, never from the usage predicate under test.
use super::{Surface, Target, Usage, saved_usages, saved_usages_for};
use iced::Rectangle;
use oa_core::{
Id, Kind, Layer, Position, Project, ProjectionKind, RelationKind, Size, projection, validation,
};
struct AllocationFixture {
project: Project,
diagram: Id,
performer: Id,
work: Id,
relation: Id,
performer_node: Id,
work_node: Id,
}
fn assert_valid(project: &Project) {
let errors: Vec<_> = validation::validate(project)
.into_iter()
.filter(|issue| issue.severity == validation::Severity::Error)
.collect();
assert!(
errors.is_empty(),
"fixture must be structurally valid: {errors:?}"
);
}
fn fixture() -> AllocationFixture {
let mut project = Project::empty();
let performer = project
.add_element(Kind::Entity, Layer::Oa, "Rescue team", None)
.unwrap();
let work = project
.add_element(Kind::Activity, Layer::Oa, "Dispatch", None)
.unwrap();
let relation = project.relate(RelationKind::Allocation, "Performs", performer, work);
let diagram = project.ensure_diagram(Layer::Oa);
project.diagrams.get_mut(&diagram).unwrap().projection = ProjectionKind::Allocation;
let performer_node = project.place(diagram, performer, Position { x: 20., y: 40. });
let work_node = project.place(diagram, work, Position { x: 240., y: 320. });
project
.diagrams
.get_mut(&diagram)
.unwrap()
.nodes
.get_mut(&work_node)
.unwrap()
.size = Size {
width: 320.,
height: 110.,
};
assert_valid(&project);
AllocationFixture {
project,
diagram,
performer,
work,
relation,
performer_node,
work_node,
}
}
fn expected(f: &AllocationFixture) -> Usage {
Usage {
subject: f.relation,
surface: Surface::Saved(f.diagram),
target: Target::AllocationGrouping {
relation: f.relation,
work_placement: f.work_node,
performer_placement: f.performer_node,
},
role: "allocation grouping".into(),
bounds: Rectangle {
x: 240.,
y: 320.,
width: 320.,
height: 64.,
},
}
}
/// Two semantic decomposition levels, both summaries of the one allocated leaf.
/// No Allocation relation targets either summary.
fn nest_under_summaries(f: &mut AllocationFixture) -> (Id, Id, Id, Id) {
let top = f
.project
.add_element(Kind::Activity, Layer::Oa, "Respond", None)
.unwrap();
let middle = f
.project
.add_element(Kind::Activity, Layer::Oa, "Coordinate", Some(top))
.unwrap();
f.project.elements.get_mut(&f.work).unwrap().parent = Some(middle);
let top_node = f
.project
.place(f.diagram, top, Position { x: 70., y: 120. });
let middle_node = f
.project
.place(f.diagram, middle, Position { x: 130., y: 210. });
assert_valid(&f.project);
(top, middle, top_node, middle_node)
}
#[test]
fn allocation_grouping_has_exact_relation_placements_and_readable_work_geometry() {
let f = fixture();
let before = f.project.clone();
let view = projection::project_view(&f.project, f.diagram);
let work = view
.nodes
.iter()
.find(|node| node.id == f.work_node)
.unwrap();
assert_eq!(work.visual_parent, Some(f.performer_node));
assert_eq!(work.position, Position { x: 240., y: 320. });
assert_eq!(
work.size,
Size {
width: 320.,
height: 110.
}
);
assert!(!view.edges.iter().any(|edge| edge.id == f.relation));
assert_eq!(saved_usages(&f.project, f.relation), vec![expected(&f)]);
assert_eq!(
saved_usages_for(&f.project, f.relation, Some(f.diagram)),
vec![expected(&f)]
);
assert!(saved_usages_for(&f.project, f.relation, Some(Id::new_v4())).is_empty());
// The semantic work and its placement remain ordinary node selections.
for subject in [f.work, f.work_node] {
let usages = saved_usages(&f.project, subject);
assert_eq!(usages.len(), 1);
assert_eq!(usages[0].target, Target::Placement(f.work_node));
}
assert_eq!(f.project, before);
}
#[test]
fn allocation_grouping_walks_two_visible_summary_levels_without_inventing_allocations() {
let mut f = fixture();
let (top, middle, top_node, middle_node) = nest_under_summaries(&mut f);
let before = f.project.clone();
let view = projection::project_view(&f.project, f.diagram);
for (node_id, parent, summary) in [
(f.work_node, Some(middle_node), false),
(middle_node, Some(top_node), true),
(top_node, Some(f.performer_node), true),
] {
let node = view.nodes.iter().find(|node| node.id == node_id).unwrap();
assert_eq!(node.visual_parent, parent);
assert_eq!(node.allocation_summary, summary);
}
assert_eq!(f.project.relations.len(), 1);
assert_eq!(saved_usages(&f.project, f.relation), vec![expected(&f)]);
for (element, node) in [(top, top_node), (middle, middle_node)] {
let usages = saved_usages(&f.project, element);
assert_eq!(usages.len(), 1);
assert_eq!(usages[0].target, Target::Placement(node));
}
assert_eq!(f.project, before);
}
#[test]
fn folded_summary_or_performer_does_not_substitute_a_visible_ancestor_for_the_leaf() {
let mut f = fixture();
let (_, _, top_node, middle_node) = nest_under_summaries(&mut f);
for folded in [f.performer_node, top_node, middle_node] {
let mut project = f.project.clone();
project
.diagrams
.get_mut(&f.diagram)
.unwrap()
.nodes
.get_mut(&folded)
.unwrap()
.collapsed = true;
assert_valid(&project);
let before = project.clone();
let view = projection::project_view(&project, f.diagram);
assert!(view.nodes.iter().any(|node| node.id == folded));
assert!(!view.nodes.iter().any(|node| node.id == f.work_node));
assert!(saved_usages(&project, f.relation).is_empty());
assert_eq!(project, before);
}
}
#[test]
fn absent_endpoint_placements_and_hidden_performer_do_not_create_grouping_targets() {
let mut f = fixture();
let (_, _, top_node, _) = nest_under_summaries(&mut f);
for absent in [f.performer_node, f.work_node] {
let mut project = f.project.clone();
project
.diagrams
.get_mut(&f.diagram)
.unwrap()
.nodes
.remove(&absent);
assert_valid(&project);
let before = project.clone();
let view = projection::project_view(&project, f.diagram);
// The aggregate card remains visible even when the actual leaf or its
// actual performer has no placement. It cannot stand in for that end.
assert!(
view.nodes
.iter()
.any(|node| node.id == top_node && node.allocation_summary)
);
assert!(saved_usages(&project, f.relation).is_empty());
assert_eq!(project, before);
}
let outer = f
.project
.add_element(Kind::Entity, Layer::Oa, "Organization", None)
.unwrap();
f.project.elements.get_mut(&f.performer).unwrap().parent = Some(outer);
let outer_node = f
.project
.place(f.diagram, outer, Position { x: -40., y: -60. });
f.project
.diagrams
.get_mut(&f.diagram)
.unwrap()
.nodes
.get_mut(&outer_node)
.unwrap()
.collapsed = true;
assert_valid(&f.project);
let before = f.project.clone();
let view = projection::project_view(&f.project, f.diagram);
assert!(view.nodes.iter().any(|node| node.id == outer_node));
assert!(!view.nodes.iter().any(|node| node.id == f.performer_node));
assert!(saved_usages(&f.project, f.relation).is_empty());
assert_eq!(f.project, before);
}
#[test]
fn collapsed_leaf_and_edge_filter_still_leave_the_actual_nesting_visible() {
let mut f = fixture();
let diagram = f.project.diagrams.get_mut(&f.diagram).unwrap();
diagram.nodes.get_mut(&f.work_node).unwrap().collapsed = true;
// hidden_edges controls arrows. Allocation is drawn as nesting, which is
// still visible; do not mistake the filter for a hidden node/grouping.
diagram.view_options.hidden_edges.push(f.relation);
assert_valid(&f.project);
let before = f.project.clone();
let view = projection::project_view(&f.project, f.diagram);
let work = view
.nodes
.iter()
.find(|node| node.id == f.work_node)
.unwrap();
assert!(work.collapsed);
assert_eq!(work.visual_parent, Some(f.performer_node));
assert_eq!(saved_usages(&f.project, f.relation), vec![expected(&f)]);
assert_eq!(f.project, before);
}
#[test]
fn other_projections_do_not_report_allocation_nesting_when_only_an_arrow_or_nothing_is_drawn() {
let mut f = fixture();
f.project.diagrams.get_mut(&f.diagram).unwrap().projection = ProjectionKind::Overview;
assert_valid(&f.project);
let before = f.project.clone();
let view = projection::project_view(&f.project, f.diagram);
assert!(view.edges.iter().any(|edge| edge.id == f.relation));
assert!(view.nodes.iter().all(|node| node.visual_parent.is_none()));
let usages = saved_usages(&f.project, f.relation);
assert_eq!(usages.len(), 1);
assert_eq!(usages[0].subject, f.relation);
assert_eq!(usages[0].surface, Surface::Saved(f.diagram));
assert_eq!(usages[0].target, Target::Edge(f.relation));
assert_eq!(f.project, before);
f.project.diagrams.get_mut(&f.diagram).unwrap().projection = ProjectionKind::FunctionalDataflow;
assert_valid(&f.project);
let before = f.project.clone();
let view = projection::project_view(&f.project, f.diagram);
assert!(view.nodes.iter().any(|node| node.id == f.work_node));
assert!(!view.nodes.iter().any(|node| node.id == f.performer_node));
assert!(saved_usages(&f.project, f.relation).is_empty());
assert_eq!(f.project, before);
}
#[test]
fn repeated_saved_views_keep_allocation_placement_identity_and_geometry_independent() {
let mut f = fixture();
// The first diagram is Allocation, so this creates a different Overview.
let second = f.project.ensure_diagram(Layer::Oa);
assert_ne!(second, f.diagram);
f.project.diagrams.get_mut(&second).unwrap().projection = ProjectionKind::Allocation;
let second_performer = f
.project
.place(second, f.performer, Position { x: 900., y: 600. });
let second_work = f
.project
.place(second, f.work, Position { x: 1000., y: 700. });
f.project
.diagrams
.get_mut(&second)
.unwrap()
.nodes
.get_mut(&second_work)
.unwrap()
.size = Size {
width: 520.,
height: 130.,
};
assert_ne!(second_performer, f.performer_node);
assert_ne!(second_work, f.work_node);
assert_valid(&f.project);
let before = f.project.clone();
let other = Usage {
subject: f.relation,
surface: Surface::Saved(second),
target: Target::AllocationGrouping {
relation: f.relation,
work_placement: second_work,
performer_placement: second_performer,
},
role: "allocation grouping".into(),
bounds: Rectangle {
x: 1000.,
y: 700.,
width: 420.,
height: 64.,
},
};
let usages = saved_usages(&f.project, f.relation);
assert_eq!(usages.len(), 2);
assert!(usages.contains(&expected(&f)));
assert!(usages.contains(&other));
assert_eq!(
saved_usages_for(&f.project, f.relation, Some(second)),
vec![other]
);
assert_eq!(f.project, before);
}
#[test]
fn role_derived_nesting_without_a_direct_allocation_does_not_invent_one() {
let mut f = fixture();
f.project.relations.remove(&f.relation);
let role = f
.project
.add_element(Kind::Role, Layer::Oa, "Dispatcher", None)
.unwrap();
let realization = f.project.relate(
RelationKind::RoleRealization,
"Realizes dispatcher",
f.performer,
role,
);
let assignment = f
.project
.relate(RelationKind::RoleAssignment, "Dispatches", role, f.work);
assert_valid(&f.project);
let before = f.project.clone();
let view = projection::project_view(&f.project, f.diagram);
let work = view
.nodes
.iter()
.find(|node| node.id == f.work_node)
.unwrap();
assert_eq!(work.visual_parent, Some(f.performer_node));
assert!(work.allocation_summary);
assert!(
f.project
.relations
.values()
.all(|relation| relation.kind() != RelationKind::Allocation)
);
for subject in [f.relation, realization, assignment] {
assert!(saved_usages(&f.project, subject).is_empty());
}
let usages = saved_usages(&f.project, f.work);
assert_eq!(usages.len(), 1);
assert_eq!(usages[0].target, Target::Placement(f.work_node));
assert_eq!(f.project, before);
}