Bound scenario value authoring to focused paged fields
This commit is contained in:
@@ -84,6 +84,35 @@ pub fn choices(
|
||||
.collect())
|
||||
}
|
||||
|
||||
/// Direct carried fields only: an unrelated deep branch must not prevent
|
||||
/// browsing a root field. Preserve the core resolver's first carried owner.
|
||||
pub fn roots(project: &Project, exchange: &Relation, phase: &MessageKind) -> Vec<FieldPathChoice> {
|
||||
let mut seen = std::collections::BTreeSet::new();
|
||||
exchange
|
||||
.body
|
||||
.items()
|
||||
.iter()
|
||||
.flat_map(|item| {
|
||||
data::effective_fields(project, *item)
|
||||
.into_iter()
|
||||
.filter_map(|field| {
|
||||
let SharedBody::DataField { direction, .. } = field.shared()? else {
|
||||
return None;
|
||||
};
|
||||
Some(data::CarriedFieldPath {
|
||||
item: *item,
|
||||
path: vec![],
|
||||
field: field.id,
|
||||
direction: *direction,
|
||||
})
|
||||
})
|
||||
})
|
||||
.filter(|entry| seen.insert(entry.field))
|
||||
.filter(|entry| phase_allows(project, entry, phase))
|
||||
.map(|entry| named_choice(project, entry))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Resolve authored finite paths as well as the finite representative picker list.
|
||||
/// In particular, optional recursive Value records may be traversed repeatedly.
|
||||
pub fn resolve(
|
||||
@@ -113,9 +142,11 @@ pub fn children(
|
||||
phase: &MessageKind,
|
||||
key: &FieldPathKey,
|
||||
) -> Result<Vec<FieldPathChoice>, String> {
|
||||
if resolve(project, exchange, phase, key).is_none() {
|
||||
let Some(entry) = data::carried_value_path(project, exchange, &key.path, key.field)
|
||||
.filter(|entry| phase_allows(project, entry, phase))
|
||||
else {
|
||||
return Ok(vec![]);
|
||||
}
|
||||
};
|
||||
let Some(SharedBody::DataField {
|
||||
type_ref: TypeRef::Named(record),
|
||||
storage: FieldStorage::Value,
|
||||
@@ -137,14 +168,14 @@ pub fn children(
|
||||
path.push(key.field);
|
||||
Ok(data::effective_fields(project, *record)
|
||||
.into_iter()
|
||||
.filter_map(|field| {
|
||||
resolve(
|
||||
.map(|field| {
|
||||
named_choice(
|
||||
project,
|
||||
exchange,
|
||||
phase,
|
||||
&FieldPathKey {
|
||||
data::CarriedFieldPath {
|
||||
item: entry.item,
|
||||
path: path.clone(),
|
||||
field: field.id,
|
||||
direction: entry.direction,
|
||||
},
|
||||
)
|
||||
})
|
||||
@@ -337,6 +368,85 @@ pub(crate) mod tests {
|
||||
validate_candidate(&f.project).unwrap();
|
||||
(f, next)
|
||||
}
|
||||
pub fn wide_payload() -> Payload {
|
||||
let mut f = payload();
|
||||
let record = f.project.elements[&f.leaf].parent.unwrap();
|
||||
for (owner, template, count) in [(f.item, f.origin, 95), (record, f.leaf, 127)] {
|
||||
for index in 0..count {
|
||||
let body = f.project.elements[&template].shared().unwrap().clone();
|
||||
let id = f
|
||||
.project
|
||||
.add_element(
|
||||
Kind::DataField,
|
||||
Layer::Oa,
|
||||
&format!("Field {template} {index}"),
|
||||
Some(owner),
|
||||
)
|
||||
.unwrap();
|
||||
*f.project
|
||||
.elements
|
||||
.get_mut(&id)
|
||||
.unwrap()
|
||||
.shared_mut()
|
||||
.unwrap() = body;
|
||||
}
|
||||
}
|
||||
f
|
||||
}
|
||||
#[test]
|
||||
fn roots_and_one_hop_children_do_not_expand_unrelated_payload_branches() {
|
||||
let mut f = wide_payload();
|
||||
let exchange = &f.project.relations[&f.exchange];
|
||||
let direct = roots(&f.project, exchange, &MessageKind::Call);
|
||||
assert_eq!(direct.len(), 96);
|
||||
assert!(direct.iter().all(|choice| choice.key.path.is_empty()));
|
||||
let representative = choices(&f.project, exchange, &MessageKind::Call).unwrap();
|
||||
assert_eq!(representative.len(), 96 * 129);
|
||||
for root in &direct {
|
||||
assert_eq!(
|
||||
resolve(&f.project, exchange, &MessageKind::Call, &root.key),
|
||||
Some(root.clone())
|
||||
);
|
||||
}
|
||||
let nested = children(&f.project, exchange, &MessageKind::Call, &direct[0].key).unwrap();
|
||||
assert_eq!(nested.len(), 128);
|
||||
for child in &nested {
|
||||
assert_eq!(
|
||||
resolve(&f.project, exchange, &MessageKind::Call, &child.key),
|
||||
Some(child.clone())
|
||||
);
|
||||
assert!(child.label.ends_with(" · In"));
|
||||
}
|
||||
// A temporarily over-limit model still exposes its unrelated root fields;
|
||||
// the editor must not expand the whole graph before showing a choice.
|
||||
for index in 0..32 {
|
||||
let body = f.project.elements[&f.origin].shared().unwrap().clone();
|
||||
let id = f
|
||||
.project
|
||||
.add_element(
|
||||
Kind::DataField,
|
||||
Layer::Oa,
|
||||
&format!("Extra {index}"),
|
||||
Some(f.item),
|
||||
)
|
||||
.unwrap();
|
||||
*f.project
|
||||
.elements
|
||||
.get_mut(&id)
|
||||
.unwrap()
|
||||
.shared_mut()
|
||||
.unwrap() = body;
|
||||
}
|
||||
let exchange = &f.project.relations[&f.exchange];
|
||||
assert!(choices(&f.project, exchange, &MessageKind::Call).is_err());
|
||||
assert_eq!(roots(&f.project, exchange, &MessageKind::Call).len(), 128);
|
||||
assert_eq!(
|
||||
children(&f.project, exchange, &MessageKind::Call, &direct[0].key)
|
||||
.unwrap()
|
||||
.len(),
|
||||
128
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn recursive_records_can_be_drilled_into_and_valid_imported_paths_resolve() {
|
||||
let (f, next) = recursive_payload();
|
||||
|
||||
Reference in New Issue
Block a user