Files
ARCADIA-Studio/crates/oa-desktop/src/allocation_drag/arrange_tests.rs
T

444 lines
16 KiB
Rust

use super::*;
struct Distributed {
f: Fixture,
a: Id,
b: Id,
x: Id,
y: Id,
internal: Id,
}
fn distributed(layer: Layer, folded: bool) -> Distributed {
let mut f = fixture(layer);
let kind = f.p.elements[&f.w].kind();
let x = f.p.add_element(kind, layer, "X", Some(f.w)).unwrap();
let y = f.p.add_element(kind, layer, "Y", Some(f.w)).unwrap();
let outside = f.p.add_element(kind, layer, "Outside", None).unwrap();
let xn = f.p.place(f.d, x, xy(140., 200.));
let yn = f.p.place(f.d, y, xy(940., 200.));
f.p.place(f.d, outside, xy(1800., 800.));
f.p.relate(RelationKind::Allocation, "A owns X", f.a, x);
f.p.relate(RelationKind::Allocation, "B owns Y", f.b, y);
let exchange = match layer {
Layer::Oa => RelationKind::Interaction,
Layer::Sa => RelationKind::FunctionalExchange,
};
let internal = f.p.relate(exchange, "X to Y", x, y);
let external = f.p.relate(exchange, "Y to Outside", y, outside);
let d = f.p.diagrams.get_mut(&f.d).unwrap();
let a = d.nodes.values().find(|n| n.element == f.a).unwrap().id;
let b = d.nodes.values().find(|n| n.element == f.b).unwrap().id;
d.nodes.get_mut(&a).unwrap().collapsed = folded;
d.nodes.get_mut(&f.n).unwrap().position = xy(100., 800.);
d.routes = vec![route(internal), route(external)];
validate_candidate(&f.p).unwrap();
Distributed {
f,
a,
b,
x: xn,
y: yn,
internal,
}
}
fn identities(m: &Distributed, ids: &[Id]) -> BTreeMap<Id, Id> {
ids.iter()
.map(|id| (*id, m.f.p.diagrams[&m.f.d].nodes[id].element))
.collect()
}
fn expected(m: &Distributed, include_a: bool) -> Project {
let mut p = m.f.p.clone();
let d = p.diagrams.get_mut(&m.f.d).unwrap();
for (id, position) in [
(m.f.n, xy(500., 800.)),
(m.x, xy(540., 200.)),
(m.y, xy(1340., 200.)),
] {
d.nodes.get_mut(&id).unwrap().position = position;
}
if include_a {
d.nodes.get_mut(&m.a).unwrap().position = xy(500., 100.);
}
d.routes
.iter_mut()
.find(|r| r.edge == m.internal)
.unwrap()
.waypoints = vec![xy(820., 400.), xy(820., 550.)];
p
}
#[test]
fn explicit_arrange_distributed_oa_sa_exact_scope_routes_source_history_and_anchor() {
for layer in [Layer::Oa, Layer::Sa] {
for folded in [false, true] {
for include_a in [false, true] {
let m = distributed(layer, folded);
let members = if include_a {
vec![m.f.n, m.a]
} else {
vec![m.f.n]
};
let selected = identities(&m, &members);
for anchor in selected.keys().copied() {
let plan = arrange::capture(&m.f.p, m.f.d, &selected, anchor).unwrap();
assert_eq!(plan.selected(), &selected);
let mut affected = BTreeSet::from([m.f.n, m.x, m.y]);
if include_a {
affected.insert(m.a);
}
assert_eq!(
plan.affected().keys().copied().collect::<BTreeSet<_>>(),
affected
);
let requested = if anchor == m.a {
xy(503., 102.)
} else {
xy(503., 802.)
};
let placed =
arrange::place(&plan, requested, presentation(&m.f.p, m.f.d, None))
.unwrap();
assert_eq!(placed.delta, xy(400., 0.));
assert_eq!(placed.unfolded, None);
let command = arrange::finish(&plan, &m.f.p, &placed).unwrap().unwrap();
assert_eq!(roundtrip(&m.f.p, command), expected(&m, include_a));
// Explicit Arrange does not widen ordinary distributed assignment.
assert!(matches!(
plan_semantics(
&m.f.p,
&scope(&m.f),
Destination::Performer(m.f.a),
&BTreeMap::new()
),
Err(Refusal::MixedLayoutRequired)
));
}
}
}
}
}
#[test]
fn explicit_arrange_performer_closure_does_not_acquire_incidental_semantic_root() {
for layer in [Layer::Oa, Layer::Sa] {
let m = distributed(layer, true);
let selected = identities(&m, &[m.a]);
let plan = arrange::capture(&m.f.p, m.f.d, &selected, m.a).unwrap();
assert_eq!(
plan.affected().keys().copied().collect::<BTreeSet<_>>(),
BTreeSet::from([m.a, m.x])
);
let placed =
arrange::place(&plan, xy(500., 100.), presentation(&m.f.p, m.f.d, None)).unwrap();
let mut expected = m.f.p.clone();
let d = expected.diagrams.get_mut(&m.f.d).unwrap();
d.nodes.get_mut(&m.a).unwrap().position = xy(500., 100.);
d.nodes.get_mut(&m.x).unwrap().position = xy(540., 200.);
// X-to-Y is now one-ended; all saved pins remain byte-for-byte unchanged.
assert_eq!(
roundtrip(
&m.f.p,
arrange::finish(&plan, &m.f.p, &placed).unwrap().unwrap()
),
expected
);
}
}
#[test]
fn explicit_arrange_validates_all_original_members_before_normalizing() {
let mut m = distributed(Layer::Oa, false);
let selected = identities(&m, &[m.f.n, m.x]);
let plan = arrange::capture(&m.f.p, m.f.d, &selected, m.x).unwrap();
assert_eq!(plan.selected(), &selected);
assert_eq!(plan.affected().len(), 3);
let placed = arrange::place(&plan, xy(540., 200.), presentation(&m.f.p, m.f.d, None)).unwrap();
assert_eq!(
roundtrip(
&m.f.p,
arrange::finish(&plan, &m.f.p, &placed).unwrap().unwrap()
),
expected(&m, false)
);
let mut stale = selected.clone();
stale.insert(m.x, m.f.w);
assert!(arrange::capture(&m.f.p, m.f.d, &stale, m.f.n).is_err());
m.f.p
.diagrams
.get_mut(&m.f.d)
.unwrap()
.nodes
.get_mut(&m.a)
.unwrap()
.collapsed = true;
assert!(
arrange::capture(&m.f.p, m.f.d, &selected, m.f.n).is_err(),
"covered selected X is hidden and cannot disappear in normalization"
);
assert!(arrange::capture(&m.f.p, m.f.d, &BTreeMap::new(), m.f.n).is_err());
assert!(arrange::capture(&m.f.p, m.f.d, &identities(&m, &[m.f.n]), m.a).is_err());
assert!(arrange::capture(&m.f.p, m.f.second, &identities(&m, &[m.f.n]), m.f.n).is_err());
let foreign = Id::new_v4();
assert!(
arrange::capture(
&m.f.p,
m.f.d,
&BTreeMap::from([(m.f.n, m.f.w), (foreign, m.f.w)]),
m.f.n
)
.is_err()
);
m.f.p.diagrams.get_mut(&m.f.d).unwrap().projection = ProjectionKind::Overview;
assert!(arrange::capture(&m.f.p, m.f.d, &identities(&m, &[m.f.n]), m.f.n).is_err());
}
#[test]
fn explicit_arrange_simultaneous_roots_may_enter_each_others_vacated_bounds() {
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, "Second root", None).unwrap();
let yn = f.p.place(f.d, y, xy(400., 500.));
let relation = f.p.relate(
if layer == Layer::Oa {
RelationKind::Interaction
} else {
RelationKind::FunctionalExchange
},
"Between roots",
f.w,
y,
);
f.p.diagrams.get_mut(&f.d).unwrap().routes = vec![route(relation)];
let context = presentation(&f.p, f.d, None);
let single = arrange::capture(&f.p, f.d, &BTreeMap::from([(f.n, f.w)]), f.n).unwrap();
assert!(
arrange::place(&single, xy(400., 500.), context).is_err(),
"singleton would collide with stationary Y"
);
let plan =
arrange::capture(&f.p, f.d, &BTreeMap::from([(f.n, f.w), (yn, y)]), f.n).unwrap();
let placed = arrange::place(&plan, xy(400., 500.), context).unwrap();
let mut expected = f.p.clone();
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(720., 400.), xy(720., 550.)];
assert_eq!(
roundtrip(
&f.p,
arrange::finish(&plan, &f.p, &placed).unwrap().unwrap()
),
expected
);
}
}
#[test]
fn explicit_arrange_preserves_roles_and_does_not_invent_unplaced_descendants() {
let mut f = fixture(Layer::Oa);
let a = f.a;
role(&mut f, Some(a));
f.p.diagrams
.get_mut(&f.d)
.unwrap()
.nodes
.get_mut(&f.n)
.unwrap()
.position = xy(150., 200.);
let plan = arrange::capture(&f.p, f.d, &BTreeMap::from([(f.n, f.w)]), f.n).unwrap();
let placed = arrange::place(&plan, xy(160., 210.), presentation(&f.p, f.d, None)).unwrap();
let mut role_expected = f.p.clone();
role_expected
.diagrams
.get_mut(&f.d)
.unwrap()
.nodes
.get_mut(&f.n)
.unwrap()
.position = xy(160., 210.);
assert_eq!(
roundtrip(
&f.p,
arrange::finish(&plan, &f.p, &placed).unwrap().unwrap()
),
role_expected
);
for layer in [Layer::Oa, Layer::Sa] {
let mut m = distributed(layer, true);
let kind = m.f.p.elements[&m.f.w].kind();
m.f.p
.add_element(kind, layer, "Unplaced leaf", Some(m.f.w))
.unwrap();
let plan = arrange::capture(&m.f.p, m.f.d, &identities(&m, &[m.f.n]), m.f.n).unwrap();
assert_eq!(plan.affected().len(), 3);
let placed =
arrange::place(&plan, xy(500., 800.), presentation(&m.f.p, m.f.d, None)).unwrap();
assert_eq!(
roundtrip(
&m.f.p,
arrange::finish(&plan, &m.f.p, &placed).unwrap().unwrap()
),
expected(&m, false)
);
}
}
#[test]
fn explicit_arrange_detects_hidden_moved_child_and_stationary_enclosure_growth() {
for layer in [Layer::Oa, Layer::Sa] {
for hidden in [false, true] {
let mut m = distributed(layer, hidden);
let kind = m.f.p.elements[&m.f.w].kind();
let z = m.f.p.add_element(kind, layer, "Obstacle", None).unwrap();
let zn = m.f.p.place(
m.f.d,
z,
if hidden {
xy(550., 230.)
} else {
xy(1200., 100.)
},
);
// The independent candidate differs only by literal admitted edits.
// Establish which geometric case the real planner must reject.
let candidate = expected(&m, true);
let before = projection::project_view(&m.f.p, m.f.d);
let after = projection::project_view(&candidate, m.f.d);
let find =
|view: &ProjectedView, id| view.nodes.iter().find(|n| n.id == id).unwrap().clone();
if hidden {
assert!(!overlaps(&before, &after));
assert!(!after.nodes.iter().any(|n| n.id == m.x));
let mut diagnostic = candidate.diagrams[&m.f.d].clone();
diagnostic.nodes.get_mut(&m.a).unwrap().collapsed = false;
let expanded = projection::project_diagram(&candidate, &diagnostic);
assert_eq!(
overlap(&find(&expanded, m.x), &find(&expanded, zn)),
if layer == Layer::Oa { 13696. } else { 20116. }
);
} else {
assert_eq!(overlap(&find(&before, m.b), &find(&before, zn)), 0.);
assert_eq!(overlap(&find(&after, m.b), &find(&after, zn)), 21056.);
for id in [m.a, m.f.n, m.x, m.y] {
assert_eq!(overlap(&find(&after, id), &find(&after, zn)), 0.);
}
assert_eq!(
candidate.diagrams[&m.f.d].nodes[&m.b],
m.f.p.diagrams[&m.f.d].nodes[&m.b]
);
}
let plan =
arrange::capture(&m.f.p, m.f.d, &identities(&m, &[m.a, m.f.n]), m.f.n).unwrap();
assert!(matches!(
arrange::place(&plan, xy(500., 800.), presentation(&m.f.p, m.f.d, None)),
Err(Refusal::LayoutRequired(_))
));
}
}
}
#[test]
fn explicit_arrange_preserves_existing_overlap_but_refuses_its_growth() {
let mut f = fixture(Layer::Oa);
let y =
f.p.add_element(Kind::Activity, Layer::Oa, "Existing overlap", None)
.unwrap();
let yn = f.p.place(f.d, y, xy(250., 500.));
let context = presentation(&f.p, f.d, None);
let single = arrange::capture(&f.p, f.d, &BTreeMap::from([(f.n, f.w)]), f.n).unwrap();
assert!(arrange::place(&single, xy(110., 500.), context).is_err());
let plan = arrange::capture(&f.p, f.d, &BTreeMap::from([(f.n, f.w), (yn, y)]), f.n).unwrap();
let placed = arrange::place(&plan, xy(400., 500.), context).unwrap();
let mut expected = f.p.clone();
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(550., 500.);
assert_eq!(
roundtrip(
&f.p,
arrange::finish(&plan, &f.p, &placed).unwrap().unwrap()
),
expected
);
}
#[test]
fn explicit_arrange_preserves_unrelated_folded_interior_diagnostic_boundary() {
for layer in [Layer::Oa, Layer::Sa] {
let mut m = distributed(layer, true);
let performer = if layer == Layer::Oa {
Kind::Actor
} else {
Kind::SystemActor
};
let d = m.f.p.add_element(performer, layer, "D", None).unwrap();
let z =
m.f.p
.add_element(m.f.p.elements[&m.f.w].kind(), layer, "Hidden Z", None)
.unwrap();
m.f.p.relate(RelationKind::Allocation, "D owns Z", d, z);
let dn = m.f.p.place(m.f.d, d, xy(2500., 100.));
m.f.p.place(m.f.d, z, xy(550., 230.));
m.f.p
.diagrams
.get_mut(&m.f.d)
.unwrap()
.nodes
.get_mut(&dn)
.unwrap()
.collapsed = true;
let plan = arrange::capture(&m.f.p, m.f.d, &identities(&m, &[m.a, m.f.n]), m.f.n).unwrap();
let placed =
arrange::place(&plan, xy(500., 800.), presentation(&m.f.p, m.f.d, None)).unwrap();
assert_eq!(
roundtrip(
&m.f.p,
arrange::finish(&plan, &m.f.p, &placed).unwrap().unwrap()
),
expected(&m, true)
);
}
}
#[test]
fn explicit_arrange_noop_stale_tamper_target_and_all_selected_headers() {
let m = distributed(Layer::Sa, false);
let selected = identities(&m, &[m.a, m.f.n]);
let plan = arrange::capture(&m.f.p, m.f.d, &selected, m.f.n).unwrap();
let context = presentation(&m.f.p, m.f.d, None);
let noop = arrange::place(&plan, xy(103., 802.), context).unwrap();
assert!(arrange::finish(&plan, &m.f.p, &noop).unwrap().is_none());
let mut placed = arrange::place(&plan, xy(500., 800.), context).unwrap();
let mut stale = m.f.p.clone();
stale.name.push_str(" changed");
assert!(matches!(
arrange::finish(&plan, &stale, &placed),
Err(Refusal::StaleScope)
));
placed.diagram.nodes.get_mut(&m.b).unwrap().position.x += 1.;
assert!(matches!(
arrange::finish(&plan, &m.f.p, &placed),
Err(Refusal::StaleScope)
));
assert!(
arrange::place(
&plan,
xy(500., 800.),
presentation(&m.f.p, m.f.d, Some(m.f.b))
)
.is_err()
);
let mut clipped = context;
clipped.viewport.position = xy(400., 700.);
assert!(
arrange::place(&plan, xy(500., 800.), clipped).is_err(),
"anchor C visible, other selected A header clipped"
);
assert!(arrange::place(&plan, xy(f32::NAN, 800.), context).is_err());
assert!(arrange::place(&plan, xy(MAX_COORDINATE, 800.), context).is_err());
}