Files
ARCADIA-Studio/crates/oa-desktop/src/behavior/scenario/usage/tests.rs
T

277 lines
9.1 KiB
Rust

use super::*;
fn add_occurrence(p: &mut Project, owner: Id, name: &str, body: OccurrenceBody) -> Id {
let layer = p.elements[&owner].layer();
let id = p
.add_element(Kind::Occurrence, layer, name, Some(owner))
.unwrap();
*p.elements.get_mut(&id).unwrap().shared_mut().unwrap() = SharedBody::Occurrence {
body,
precondition: String::new(),
postcondition: String::new(),
};
if let Some(steps) = p.elements.get_mut(&owner).unwrap().steps_mut()
&& !steps.contains(&id)
{
steps.push(id);
}
id
}
fn targets(state: &State, p: &Project, owner: Id, subject: Id) -> BTreeSet<Id> {
state
.diagram_usages(p, owner, subject)
.into_iter()
.filter_map(|u| match u.target {
Target::ScenarioObject(id) => Some(id),
_ => None,
})
.collect()
}
#[test]
fn scenario_usage_counts_nested_occurrences_and_lifelines_but_keeps_reuse_opaque() {
for (layer, work_kind, participant_kind) in [
(Layer::Oa, Kind::Activity, Kind::Entity),
(Layer::Sa, Kind::Function, Kind::SystemActor),
] {
let mut p = Project::empty();
let owner = p
.add_element(Kind::Scenario, layer, "Caller", None)
.unwrap();
let other = p
.add_element(Kind::Scenario, layer, "Definition", None)
.unwrap();
let work = p.add_element(work_kind, layer, "Respond", None).unwrap();
let unused = p.add_element(work_kind, layer, "Respond", None).unwrap();
let participant = p
.add_element(participant_kind, layer, "Operator", None)
.unwrap();
let lanes = ["First role", "Second role"].map(|name| {
let id = p
.add_element(Kind::Lifeline, layer, name, Some(owner))
.unwrap();
*p.elements.get_mut(&id).unwrap().shared_mut().unwrap() = SharedBody::Lifeline {
represented: Some(participant),
};
id
});
let first = add_occurrence(
&mut p,
owner,
"First action",
OccurrenceBody::Action {
action: Some(work),
lifeline: Some(lanes[0]),
},
);
let fragment = add_occurrence(
&mut p,
owner,
"Decision",
OccurrenceBody::Fragment {
operator: FragmentOperator::Alternative,
branches: vec![],
},
);
let branch = add_occurrence(
&mut p,
fragment,
"When ready",
OccurrenceBody::Branch {
guard: "ready".into(),
occurrences: vec![],
},
);
if let Some(SharedBody::Occurrence {
body: OccurrenceBody::Fragment { branches, .. },
..
}) = p.elements.get_mut(&fragment).unwrap().shared_mut()
{
branches.push(branch);
}
let nested = add_occurrence(
&mut p,
branch,
"Again",
OccurrenceBody::Action {
action: Some(work),
lifeline: Some(lanes[1]),
},
);
let inside = add_occurrence(
&mut p,
other,
"Hidden inside definition",
OccurrenceBody::Action {
action: Some(unused),
lifeline: None,
},
);
let use_a = add_occurrence(
&mut p,
owner,
"Use A",
OccurrenceBody::ScenarioUse {
scenario: Some(other),
bindings: vec![],
},
);
let use_b = add_occurrence(
&mut p,
owner,
"Use B",
OccurrenceBody::ScenarioUse {
scenario: Some(other),
bindings: vec![],
},
);
let before = p.clone();
let state = State::default();
assert_eq!(
targets(&state, &p, owner, work),
BTreeSet::from([first, nested])
);
assert_eq!(
targets(&state, &p, owner, participant),
BTreeSet::from(lanes)
);
assert_eq!(
targets(&state, &p, owner, other),
BTreeSet::from([use_a, use_b])
);
assert!(targets(&state, &p, owner, unused).is_empty());
assert!(targets(&state, &p, owner, inside).is_empty());
assert_eq!(targets(&state, &p, owner, branch), BTreeSet::from([branch]));
assert_eq!(
state.diagram_usages(&p, owner, owner)[0].target,
Target::Whole
);
assert_eq!(p, before);
}
}
#[test]
fn scenario_usage_keeps_repeated_messages_and_reveal_is_read_only_and_revalidated() {
let f = super::super::direct_tests::fixture();
let mut p = f.project;
let mut state = State::default();
assert_eq!(
targets(&state, &p, f.owner, f.exchange),
BTreeSet::from([f.call, f.reply])
);
let before = p.clone();
let target = Target::ScenarioObject(f.reply);
state.viewport(Size::new(900., 500.));
assert!(state.reveal_usage(&p, Id::new_v4(), f.owner, &target));
let usage = state
.diagram_usages(&p, f.owner, f.reply)
.into_iter()
.find(|u| u.target == target)
.unwrap();
let zoom = state
.presentation
.scale(Scene::sequence(&p, f.owner, f.reply).size);
let centered = Point::new(
usage.bounds.center_x() * zoom,
usage.bounds.center_y() * zoom,
) + state.presentation.pan;
assert!((centered.x - 450.).abs() < 0.01 && (centered.y - 250.).abs() < 0.01);
assert!(zoom >= 0.5);
assert_eq!(p, before);
// Keeping an orphan semantic occurrence does not keep its painted target.
p.elements
.get_mut(&f.owner)
.unwrap()
.steps_mut()
.unwrap()
.retain(|id| *id != f.reply);
let camera = state.camera_boundary();
assert!(!state.reveal_usage(&p, Id::new_v4(), f.owner, &target));
assert!(camera == state.camera_boundary());
assert!(!state.reveal_usage(&p, Id::new_v4(), f.owner, &Target::Edge(f.exchange)));
// A message whose endpoints are incomplete paints only its repair box.
if let Some(SharedBody::Occurrence {
body: OccurrenceBody::Exchange { receiver, .. },
..
}) = p.elements.get_mut(&f.call).unwrap().shared_mut()
{
*receiver = None;
}
assert!(targets(&state, &p, f.owner, f.exchange).is_empty());
assert_eq!(
targets(&state, &p, f.owner, f.call),
BTreeSet::from([f.call])
);
}
#[test]
fn scenario_operation_label_membership_excludes_ellipsis_only_and_normalizes_whitespace() {
assert!(label_field_visible(
"1. call · X · Operation: I / O",
"1. call · X · Operation: ",
"I"
));
assert!(!label_field_visible(
"1234567890123456789012345678901Ixyz",
"1234567890123456789012345678901",
"Ixyz"
));
assert!(!label_field_visible(
"A very long occurrence label · Operation: I",
"A very long occurrence label · Operation: ",
"I"
));
assert!(label_field_visible(
"call\n\t· Operation: 界",
"call\n\t· Operation: ",
"界"
));
assert!(!label_field_visible("Operation: ", "Operation: ", ""));
}
#[test]
fn scenario_usage_only_reports_the_resolved_operation_fields_actually_in_the_label() {
let mut p = persistence::decode(include_bytes!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/../../harness/fixtures/interface-operation-probe/output/boundary-only.json"
)))
.unwrap();
let id = |n| Id::parse_str(&format!("52060906-0000-4000-8000-{n:012x}")).unwrap();
let (owner, call, exchange, allocation) = (id(40), id(43), id(34), id(30));
let (interface, item) = (
p.relations[&allocation].source(),
p.relations[&allocation].target(),
);
for (id, name) in [(call, "x"), (interface, "I"), (item, "O")] {
p.elements.get_mut(&id).unwrap().name = name.into();
}
p.relations.get_mut(&exchange).unwrap().name = "x".into();
p.relations.get_mut(&allocation).unwrap().name = "A".into();
if let Some(SharedBody::Occurrence {
body: OccurrenceBody::Exchange { operation, .. },
..
}) = p.elements.get_mut(&call).unwrap().shared_mut()
{
*operation = Some(OperationUse {
allocation: Some(allocation),
});
}
assert!(matches!(
operations::resolve(&p, call),
Ok(operations::OperationResolution::Selected(_))
));
let state = State::default();
let before = p.clone();
assert_eq!(
targets(&state, &p, owner, interface),
BTreeSet::from([call])
);
assert_eq!(targets(&state, &p, owner, item), BTreeSet::from([call]));
assert!(targets(&state, &p, owner, allocation).is_empty());
assert_eq!(p, before);
p.elements.get_mut(&call).unwrap().name = "This occurrence has a deliberately long name".into();
assert!(targets(&state, &p, owner, interface).is_empty());
assert!(targets(&state, &p, owner, item).is_empty());
}