607 lines
23 KiB
Rust
607 lines
23 KiB
Rust
use super::*;
|
|
|
|
struct Roots {
|
|
f: Fixture,
|
|
c: Id,
|
|
cn: Id,
|
|
y: Id,
|
|
yn: Id,
|
|
z: Id,
|
|
zn: Id,
|
|
ax: Id,
|
|
by: Id,
|
|
}
|
|
|
|
fn roots(layer: Layer) -> Roots {
|
|
let mut f = fixture(layer);
|
|
let kind = f.p.elements[&f.w].kind();
|
|
let c =
|
|
f.p.add_element(
|
|
if layer == Layer::Oa {
|
|
Kind::Actor
|
|
} else {
|
|
Kind::SystemActor
|
|
},
|
|
layer,
|
|
"C",
|
|
None,
|
|
)
|
|
.unwrap();
|
|
let y = f.p.add_element(kind, layer, "Y", None).unwrap();
|
|
let z = f.p.add_element(kind, layer, "Z", None).unwrap();
|
|
let cn = f.p.place(f.d, c, xy(1500., 100.));
|
|
let yn = f.p.place(f.d, y, xy(140., 800.));
|
|
let zn = f.p.place(f.d, z, xy(1540., 1400.));
|
|
let d = f.p.diagrams.get_mut(&f.d).unwrap();
|
|
d.nodes.get_mut(&f.n).unwrap().position = xy(140., 200.);
|
|
d.nodes
|
|
.values_mut()
|
|
.find(|n| n.element == f.b)
|
|
.unwrap()
|
|
.position = xy(100., 700.);
|
|
let ax = f.p.relate(
|
|
RelationKind::Allocation,
|
|
"Original X responsibility",
|
|
f.a,
|
|
f.w,
|
|
);
|
|
let by = f.p.relate(
|
|
RelationKind::Allocation,
|
|
"Original Y responsibility",
|
|
f.b,
|
|
y,
|
|
);
|
|
f.p.relations.get_mut(&ax).unwrap().description = "Reviewed X identity must survive".into();
|
|
f.p.relations.get_mut(&by).unwrap().description = "Reviewed Y identity must survive".into();
|
|
if layer == Layer::Oa {
|
|
let r =
|
|
f.p.add_element(Kind::Role, layer, "Responsibility", None)
|
|
.unwrap();
|
|
f.p.relate(RelationKind::RoleAssignment, "Z role", r, z);
|
|
f.p.relate(RelationKind::RoleRealization, "C role", c, r);
|
|
} else {
|
|
f.p.relate(RelationKind::Allocation, "Z already assigned", c, z);
|
|
}
|
|
validate_candidate(&f.p).unwrap();
|
|
Roots {
|
|
f,
|
|
c,
|
|
cn,
|
|
y,
|
|
yn,
|
|
z,
|
|
zn,
|
|
ax,
|
|
by,
|
|
}
|
|
}
|
|
|
|
fn wide(p: &Project, d: Id, target: Option<Id>) -> PresentationContext {
|
|
let mut context = presentation(p, d, target);
|
|
context.viewport.size = Size {
|
|
width: 5000.,
|
|
height: 3000.,
|
|
};
|
|
context
|
|
}
|
|
|
|
fn selected(r: &Roots, with_z: bool) -> BTreeMap<Id, Id> {
|
|
let mut ids = BTreeMap::from([(r.f.n, r.f.w), (r.yn, r.y)]);
|
|
if with_z {
|
|
ids.insert(r.zn, r.z);
|
|
}
|
|
ids
|
|
}
|
|
|
|
fn retarget(p: &mut Project, relation: Id, target: Id) {
|
|
let r = p.relations.get_mut(&relation).unwrap();
|
|
r.body = RelationBody::Allocation {
|
|
source: target,
|
|
target: r.target(),
|
|
};
|
|
}
|
|
|
|
#[test]
|
|
fn group_assignment_independent_performers_preserves_already_correct_provenance() {
|
|
for layer in [Layer::Oa, Layer::Sa] {
|
|
let r = roots(layer);
|
|
let selected = selected(&r, true);
|
|
let scope = assignment::capture(&r.f.p, r.f.d, &selected, r.f.n).unwrap();
|
|
let plan =
|
|
assignment::plan_semantics(&scope, Destination::Performer(r.c), &BTreeMap::new())
|
|
.unwrap();
|
|
assert_eq!(plan.changed_count(), 2);
|
|
assert_eq!(plan.already_correct_count(), 1);
|
|
let placed =
|
|
assignment::place(&plan, xy(1540., 200.), wide(&r.f.p, r.f.d, Some(r.c))).unwrap();
|
|
let mut expected = r.f.p.clone();
|
|
retarget(&mut expected, r.ax, r.c);
|
|
retarget(&mut expected, r.by, r.c);
|
|
let d = expected.diagrams.get_mut(&r.f.d).unwrap();
|
|
d.nodes.get_mut(&r.f.n).unwrap().position = xy(1540., 200.);
|
|
d.nodes.get_mut(&r.yn).unwrap().position = xy(1540., 800.);
|
|
d.nodes.get_mut(&r.zn).unwrap().position = xy(2940., 1400.);
|
|
assert_eq!(
|
|
roundtrip(
|
|
&r.f.p,
|
|
assignment::finish(&plan, &r.f.p, &placed).unwrap().unwrap()
|
|
),
|
|
expected
|
|
);
|
|
if layer == Layer::Oa {
|
|
assert!(
|
|
!plan
|
|
.project()
|
|
.relations
|
|
.values()
|
|
.any(|relation| relation.kind() == RelationKind::Allocation
|
|
&& relation.target() == r.z)
|
|
);
|
|
assert!(matches!(
|
|
assignment::plan_semantics(
|
|
&scope,
|
|
Destination::OutsidePerformers,
|
|
&BTreeMap::new()
|
|
),
|
|
Err(Refusal::RoleExitRequired { .. })
|
|
));
|
|
}
|
|
assert!(
|
|
assignment::plan_semantics(&scope, Destination::LayoutOnly, &BTreeMap::new()).is_err()
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn group_assignment_zero_delta_changes_semantics_once_and_then_is_noop() {
|
|
for layer in [Layer::Oa, Layer::Sa] {
|
|
let mut f = fixture(layer);
|
|
let d = f.p.diagrams.get_mut(&f.d).unwrap();
|
|
d.nodes
|
|
.values_mut()
|
|
.find(|n| n.element == f.a)
|
|
.unwrap()
|
|
.size = Size {
|
|
width: 600.,
|
|
height: 400.,
|
|
};
|
|
d.nodes.get_mut(&f.n).unwrap().position = xy(140., 200.);
|
|
let scope = assignment::capture(&f.p, f.d, &BTreeMap::from([(f.n, f.w)]), f.n).unwrap();
|
|
let id = Id::new_v4();
|
|
let plan = assignment::plan_semantics(
|
|
&scope,
|
|
Destination::Performer(f.a),
|
|
&BTreeMap::from([(f.w, id)]),
|
|
)
|
|
.unwrap();
|
|
let placed = assignment::place(&plan, xy(140., 200.), wide(&f.p, f.d, Some(f.a))).unwrap();
|
|
assert_eq!(placed.delta, xy(0., 0.));
|
|
let mut expected = f.p.clone();
|
|
expected.relations.insert(
|
|
id,
|
|
Relation {
|
|
id,
|
|
alias: "a_performs_work".into(),
|
|
name: "A performs Work".into(),
|
|
description: String::new(),
|
|
body: RelationBody::Allocation {
|
|
source: f.a,
|
|
target: f.w,
|
|
},
|
|
},
|
|
);
|
|
assert_eq!(
|
|
roundtrip(
|
|
&f.p,
|
|
assignment::finish(&plan, &f.p, &placed).unwrap().unwrap()
|
|
),
|
|
expected
|
|
);
|
|
let scope =
|
|
assignment::capture(&expected, f.d, &BTreeMap::from([(f.n, f.w)]), f.n).unwrap();
|
|
let noop =
|
|
assignment::plan_semantics(&scope, Destination::Performer(f.a), &BTreeMap::new())
|
|
.unwrap();
|
|
let placed =
|
|
assignment::place(&noop, xy(140., 200.), wide(&expected, f.d, Some(f.a))).unwrap();
|
|
assert!(
|
|
assignment::finish(&noop, &expected, &placed)
|
|
.unwrap()
|
|
.is_none()
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn group_assignment_unfolds_only_changed_destination_and_refuses_newly_exposed_overlap() {
|
|
for layer in [Layer::Oa, Layer::Sa] {
|
|
for obstacle in [false, true] {
|
|
let mut r = roots(layer);
|
|
let d = r.f.p.diagrams.get_mut(&r.f.d).unwrap();
|
|
d.nodes
|
|
.values_mut()
|
|
.find(|n| n.element == r.f.b)
|
|
.unwrap()
|
|
.position = xy(100., 400.);
|
|
d.nodes.get_mut(&r.yn).unwrap().position = xy(140., 500.);
|
|
d.nodes.get_mut(&r.zn).unwrap().position = xy(1540., 800.);
|
|
d.nodes.get_mut(&r.cn).unwrap().collapsed = true;
|
|
if obstacle {
|
|
let k =
|
|
r.f.p
|
|
.add_element(r.f.p.elements[&r.f.w].kind(), layer, "Hidden overlap", None)
|
|
.unwrap();
|
|
r.f.p
|
|
.relate(RelationKind::Allocation, "C also owns K", r.c, k);
|
|
r.f.p.place(r.f.d, k, xy(1540., 800.));
|
|
}
|
|
assert!(
|
|
!projection::project_view(&r.f.p, r.f.d)
|
|
.nodes
|
|
.iter()
|
|
.any(|n| n.id == r.zn)
|
|
);
|
|
assert!(
|
|
assignment::capture(&r.f.p, r.f.d, &selected(&r, true), r.f.n).is_err(),
|
|
"hidden selected member is not normalized away"
|
|
);
|
|
let scope = assignment::capture(&r.f.p, r.f.d, &selected(&r, false), r.f.n).unwrap();
|
|
let plan =
|
|
assignment::plan_semantics(&scope, Destination::Performer(r.c), &BTreeMap::new())
|
|
.unwrap();
|
|
let placed = assignment::place(&plan, xy(1540., 120.), wide(&r.f.p, r.f.d, Some(r.c)));
|
|
if obstacle {
|
|
assert!(matches!(placed, Err(Refusal::LayoutRequired(_))));
|
|
continue;
|
|
}
|
|
let placed = placed.unwrap();
|
|
assert_eq!(placed.unfolded, Some(r.cn));
|
|
let mut expected = r.f.p.clone();
|
|
retarget(&mut expected, r.ax, r.c);
|
|
retarget(&mut expected, r.by, r.c);
|
|
let d = expected.diagrams.get_mut(&r.f.d).unwrap();
|
|
d.nodes.get_mut(&r.cn).unwrap().collapsed = false;
|
|
d.nodes.get_mut(&r.f.n).unwrap().position = xy(1540., 120.);
|
|
d.nodes.get_mut(&r.yn).unwrap().position = xy(1540., 420.);
|
|
assert_eq!(
|
|
roundtrip(
|
|
&r.f.p,
|
|
assignment::finish(&plan, &r.f.p, &placed).unwrap().unwrap()
|
|
),
|
|
expected
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn group_assignment_accumulates_stable_ids_and_aliases_without_partial_reuse() {
|
|
let mut f = fixture(Layer::Oa);
|
|
let y =
|
|
f.p.add_element(Kind::Activity, Layer::Oa, "Work", None)
|
|
.unwrap();
|
|
let yn = f.p.place(f.d, y, xy(400., 500.));
|
|
let selected = BTreeMap::from([(f.n, f.w), (yn, y)]);
|
|
let scope = assignment::capture(&f.p, f.d, &selected, f.n).unwrap();
|
|
let ids = BTreeMap::from([(f.w, Id::new_v4()), (y, Id::new_v4())]);
|
|
let first = assignment::plan_semantics(&scope, Destination::Performer(f.a), &ids).unwrap();
|
|
let other = assignment::plan_semantics(&scope, Destination::Performer(f.b), &ids).unwrap();
|
|
let repeated = assignment::plan_semantics(&scope, Destination::Performer(f.a), &ids).unwrap();
|
|
assert_eq!(first.project(), repeated.project());
|
|
assert_eq!(first.changed_count(), 2);
|
|
let mut expected = f.p.clone();
|
|
let aliases: BTreeSet<_> = ids
|
|
.values()
|
|
.map(|id| first.project().relations[id].alias.clone())
|
|
.collect();
|
|
assert_eq!(aliases.len(), 2);
|
|
assert!(aliases.contains("a_performs_work"));
|
|
for (ordinal, (work, id)) in ids.iter().enumerate() {
|
|
let relation = &first.project().relations[id];
|
|
let expected_relation = Relation {
|
|
id: *id,
|
|
alias: if ordinal == 0 {
|
|
"a_performs_work".into()
|
|
} else {
|
|
format!("a_performs_work_{}", &id.simple().to_string()[..8])
|
|
},
|
|
name: "A performs Work".into(),
|
|
description: String::new(),
|
|
body: RelationBody::Allocation {
|
|
source: f.a,
|
|
target: *work,
|
|
},
|
|
};
|
|
assert_eq!(relation, &expected_relation);
|
|
assert_eq!(
|
|
other.project().relations[id].body,
|
|
RelationBody::Allocation {
|
|
source: f.b,
|
|
target: *work
|
|
}
|
|
);
|
|
expected.relations.insert(*id, expected_relation);
|
|
}
|
|
assert_eq!(first.project(), &expected);
|
|
let duplicate = Id::new_v4();
|
|
for bad in [
|
|
BTreeMap::from([(f.w, duplicate), (y, duplicate)]),
|
|
BTreeMap::from([(f.w, Id::nil()), (y, ids[&y])]),
|
|
BTreeMap::from([(f.w, f.a), (y, ids[&y])]),
|
|
] {
|
|
assert!(assignment::plan_semantics(&scope, Destination::Performer(f.a), &bad).is_err());
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn group_assignment_source_failure_preserves_existing_history_and_retry_ids() {
|
|
for layer in [Layer::Oa, Layer::Sa] {
|
|
let mut f = fixture(layer);
|
|
let y =
|
|
f.p.add_element(f.p.elements[&f.w].kind(), layer, "Y", None)
|
|
.unwrap();
|
|
let yn = f.p.place(f.d, y, xy(400., 800.));
|
|
let mut bundle = format_bundle(&f.p);
|
|
bundle
|
|
.files
|
|
.get_mut(&bundle.entry.clone())
|
|
.unwrap()
|
|
.insert_str(
|
|
0,
|
|
"// Keep reviewed source through a refused group assignment.\n",
|
|
);
|
|
let mut limits = ProjectLimits::default();
|
|
limits.model.max_source_bytes = bundle.files.values().map(String::len).sum::<usize>() + 128;
|
|
let mut document = Document::from_bundle(bundle, limits).unwrap();
|
|
document
|
|
.execute(Command::Describe(f.w, "Reviewed".into()))
|
|
.unwrap();
|
|
document
|
|
.execute(Command::Rename(y, "Future Y".into()))
|
|
.unwrap();
|
|
let redo_project = document.project().clone();
|
|
let redo_source = document.bundle().clone();
|
|
assert!(document.undo());
|
|
assert!(document.can_undo() && document.can_redo());
|
|
let accepted = document.project().clone();
|
|
let source = document.bundle().clone();
|
|
let revision = document.revision();
|
|
let ids = BTreeMap::from([(f.w, Id::new_v4()), (y, Id::new_v4())]);
|
|
let scope =
|
|
assignment::capture(&accepted, f.d, &BTreeMap::from([(f.n, f.w), (yn, y)]), f.n)
|
|
.unwrap();
|
|
let plan = assignment::plan_semantics(&scope, Destination::Performer(f.a), &ids).unwrap();
|
|
let placed =
|
|
assignment::place(&plan, xy(150., 200.), wide(&accepted, f.d, Some(f.a))).unwrap();
|
|
let command = assignment::finish(&plan, &accepted, &placed)
|
|
.unwrap()
|
|
.unwrap();
|
|
assert!(matches!(
|
|
document.execute(command.clone()),
|
|
Err(crate::document::DocumentError::Source(_))
|
|
));
|
|
assert_eq!(document.project(), &accepted);
|
|
assert_eq!(document.bundle(), &source);
|
|
assert_eq!(document.revision(), revision);
|
|
assert!(document.can_undo() && document.can_redo());
|
|
assert!(document.redo());
|
|
assert_eq!(document.project(), &redo_project);
|
|
assert_eq!(document.bundle(), &redo_source);
|
|
assert!(document.undo());
|
|
assert_eq!(document.project(), &accepted);
|
|
assert_eq!(document.bundle(), &source);
|
|
let mut expected = accepted.clone();
|
|
for (work, name, alias) in [
|
|
(f.w, "A performs Work", "a_performs_work"),
|
|
(y, "A performs Y", "a_performs_y"),
|
|
] {
|
|
expected.relations.insert(
|
|
ids[&work],
|
|
Relation {
|
|
id: ids[&work],
|
|
alias: alias.into(),
|
|
name: name.into(),
|
|
description: String::new(),
|
|
body: RelationBody::Allocation {
|
|
source: f.a,
|
|
target: work,
|
|
},
|
|
},
|
|
);
|
|
}
|
|
let d = expected.diagrams.get_mut(&f.d).unwrap();
|
|
d.nodes.get_mut(&f.n).unwrap().position = xy(150., 200.);
|
|
d.nodes.get_mut(&yn).unwrap().position = xy(450., 500.);
|
|
assert_eq!(roundtrip(&accepted, command), expected);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn group_assignment_collateral_surviving_constraint_refuses_whole_semantic_candidate() {
|
|
for layer in [Layer::Oa, Layer::Sa] {
|
|
let mut r = roots(layer);
|
|
let k =
|
|
r.f.p
|
|
.add_element(Kind::Constraint, layer, "Keep reviewed assignment", None)
|
|
.unwrap();
|
|
*r.f.p.elements.get_mut(&k).unwrap().shared_mut().unwrap() = SharedBody::Constraint {
|
|
expression: "reviewed assignment".into(),
|
|
constrained: vec![r.ax, r.f.w],
|
|
};
|
|
let core = prepare_command(&r.f.p, &Command::Delete(r.ax))
|
|
.unwrap()
|
|
.project;
|
|
validate_candidate(&core).unwrap();
|
|
assert!(core.elements.contains_key(&k));
|
|
assert_ne!(core.elements[&k], r.f.p.elements[&k]);
|
|
let scope = assignment::capture(&r.f.p, r.f.d, &selected(&r, false), r.f.n).unwrap();
|
|
let Err(Refusal::CollateralChangeRequired(changed)) =
|
|
assignment::plan_semantics(&scope, Destination::OutsidePerformers, &BTreeMap::new())
|
|
else {
|
|
panic!("surviving constraint edit must refuse the entire plan");
|
|
};
|
|
assert!(changed.iter().any(|record| record.id == k));
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn group_assignment_uses_vacated_space_and_one_complete_route_owner_map() {
|
|
for layer in [Layer::Oa, Layer::Sa] {
|
|
let mut f = fixture(layer);
|
|
let kind = f.p.elements[&f.w].kind();
|
|
let y = f.p.add_element(kind, layer, "Y", None).unwrap();
|
|
let outside = f.p.add_element(kind, layer, "Outside", None).unwrap();
|
|
let yn = f.p.place(f.d, y, xy(400., 500.));
|
|
f.p.place(f.d, outside, xy(1600., 500.));
|
|
let d = f.p.diagrams.get_mut(&f.d).unwrap();
|
|
let a = d.nodes.values_mut().find(|n| n.element == f.a).unwrap();
|
|
a.position = xy(350., 350.);
|
|
a.size = Size {
|
|
width: 1000.,
|
|
height: 600.,
|
|
};
|
|
let edge_kind = if layer == Layer::Oa {
|
|
RelationKind::Interaction
|
|
} else {
|
|
RelationKind::FunctionalExchange
|
|
};
|
|
let internal = f.p.relate(edge_kind, "Internal", f.w, y);
|
|
let external = f.p.relate(edge_kind, "External", y, outside);
|
|
let mut internal_route = route(internal);
|
|
internal_route.waypoints = vec![xy(370., 550.), xy(370., 650.)];
|
|
internal_route.source_attachment = Some(EdgeAttachment {
|
|
side: PortAnchorSide::Bottom,
|
|
offset: 0.3,
|
|
});
|
|
internal_route.target_attachment = Some(EdgeAttachment {
|
|
side: PortAnchorSide::Top,
|
|
offset: 0.7,
|
|
});
|
|
let mut external_route = internal_route.clone();
|
|
external_route.edge = external;
|
|
external_route.waypoints = vec![xy(1200., 550.), xy(1300., 650.)];
|
|
for diagram in [f.d, f.second] {
|
|
f.p.diagrams.get_mut(&diagram).unwrap().routes =
|
|
vec![internal_route.clone(), external_route.clone()];
|
|
}
|
|
if let RelationBody::FunctionalExchange { source_port, .. } = f.p.relations[&internal].body
|
|
{
|
|
f.p.diagrams
|
|
.get_mut(&f.d)
|
|
.unwrap()
|
|
.nodes
|
|
.get_mut(&f.n)
|
|
.unwrap()
|
|
.port_anchors
|
|
.push(PortAnchor {
|
|
port: source_port,
|
|
side: PortAnchorSide::Bottom,
|
|
offset: 0.4,
|
|
});
|
|
}
|
|
validate_candidate(&f.p).unwrap();
|
|
let ids = BTreeMap::from([(f.w, Id::new_v4()), (y, Id::new_v4())]);
|
|
// The singleton candidate really occupies stationary Y's card. The
|
|
// combined candidate must not inherit that sequential-planning veto.
|
|
let mut singleton = f.p.clone();
|
|
singleton
|
|
.diagrams
|
|
.get_mut(&f.d)
|
|
.unwrap()
|
|
.nodes
|
|
.get_mut(&f.n)
|
|
.unwrap()
|
|
.position = xy(400., 500.);
|
|
let projected = projection::project_view(&singleton, f.d);
|
|
let x_card = projected.nodes.iter().find(|n| n.id == f.n).unwrap();
|
|
let y_card = projected.nodes.iter().find(|n| n.id == yn).unwrap();
|
|
assert_eq!(
|
|
overlap(x_card, y_card),
|
|
if layer == Layer::Oa { 21056. } else { 29568. }
|
|
);
|
|
let single = assignment::capture(&f.p, f.d, &BTreeMap::from([(f.n, f.w)]), f.n).unwrap();
|
|
let single =
|
|
assignment::plan_semantics(&single, Destination::Performer(f.a), &ids).unwrap();
|
|
assert!(matches!(
|
|
assignment::place(&single, xy(400., 500.), wide(&f.p, f.d, Some(f.a))),
|
|
Err(Refusal::LayoutRequired(_))
|
|
));
|
|
let scope =
|
|
assignment::capture(&f.p, f.d, &BTreeMap::from([(f.n, f.w), (yn, y)]), f.n).unwrap();
|
|
let plan = assignment::plan_semantics(&scope, Destination::Performer(f.a), &ids).unwrap();
|
|
let placed = assignment::place(&plan, xy(400., 500.), wide(&f.p, f.d, Some(f.a))).unwrap();
|
|
let mut expected = f.p.clone();
|
|
for (work, name, alias) in [
|
|
(f.w, "A performs Work", "a_performs_work"),
|
|
(y, "A performs Y", "a_performs_y"),
|
|
] {
|
|
expected.relations.insert(
|
|
ids[&work],
|
|
Relation {
|
|
id: ids[&work],
|
|
alias: alias.into(),
|
|
name: name.into(),
|
|
description: String::new(),
|
|
body: RelationBody::Allocation {
|
|
source: f.a,
|
|
target: work,
|
|
},
|
|
},
|
|
);
|
|
}
|
|
let d = expected.diagrams.get_mut(&f.d).unwrap();
|
|
d.nodes.get_mut(&f.n).unwrap().position = xy(400., 500.);
|
|
d.nodes.get_mut(&yn).unwrap().position = xy(700., 500.);
|
|
d.routes[0].waypoints = vec![xy(670., 550.), xy(670., 650.)];
|
|
assert_eq!(placed.diagram.routes[1], external_route);
|
|
assert_eq!(
|
|
roundtrip(
|
|
&f.p,
|
|
assignment::finish(&plan, &f.p, &placed).unwrap().unwrap()
|
|
),
|
|
expected
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn group_assignment_deletes_only_own_routes_across_views_and_keeps_internal_flow() {
|
|
for layer in [Layer::Oa, Layer::Sa] {
|
|
let mut r = roots(layer);
|
|
let kind = if layer == Layer::Oa {
|
|
RelationKind::Interaction
|
|
} else {
|
|
RelationKind::FunctionalExchange
|
|
};
|
|
let internal = r.f.p.relate(kind, "Keep internal flow", r.f.w, r.y);
|
|
for diagram in [r.f.d, r.f.second] {
|
|
r.f.p.diagrams.get_mut(&diagram).unwrap().routes =
|
|
vec![route(r.ax), route(internal), route(r.by)];
|
|
}
|
|
validate_candidate(&r.f.p).unwrap();
|
|
let scope = assignment::capture(&r.f.p, r.f.d, &selected(&r, false), r.f.n).unwrap();
|
|
let plan =
|
|
assignment::plan_semantics(&scope, Destination::OutsidePerformers, &BTreeMap::new())
|
|
.unwrap();
|
|
assert_eq!(plan.changed_count(), 2);
|
|
let placed = assignment::place(&plan, xy(640., 200.), wide(&r.f.p, r.f.d, None)).unwrap();
|
|
let mut expected = r.f.p.clone();
|
|
expected.relations.remove(&r.ax);
|
|
expected.relations.remove(&r.by);
|
|
for diagram in [r.f.d, r.f.second] {
|
|
expected.diagrams.get_mut(&diagram).unwrap().routes = vec![route(internal)];
|
|
}
|
|
let d = expected.diagrams.get_mut(&r.f.d).unwrap();
|
|
d.nodes.get_mut(&r.f.n).unwrap().position = xy(640., 200.);
|
|
d.nodes.get_mut(&r.yn).unwrap().position = xy(640., 800.);
|
|
d.routes[0].waypoints = vec![xy(920., 400.), xy(920., 550.)];
|
|
assert_eq!(
|
|
roundtrip(
|
|
&r.f.p,
|
|
assignment::finish(&plan, &r.f.p, &placed).unwrap().unwrap()
|
|
),
|
|
expected
|
|
);
|
|
}
|
|
}
|