Rust checks / check (ubuntu-latest) (push) Canceled after 0s
Rust checks / check (windows-latest) (push) Canceled after 0s
Preserve current core, source, REST, desktop, recovery, documentation and validation work, including AGENTS.md and the development summary. This is a work-in-progress checkpoint: the new compact palette has outstanding compilation errors and native acceptance; earlier passing gates retain their documented scope.
831 lines
30 KiB
Rust
831 lines
30 KiB
Rust
use super::{tests::*, *};
|
|
use crate::{app::App, document::Document};
|
|
|
|
struct Fixture {
|
|
p: Project,
|
|
owners: [Id; 2],
|
|
trace: Id,
|
|
facet: FacetSelector,
|
|
sibling: FacetSelector,
|
|
locals: [Id; 2],
|
|
}
|
|
fn errors(s: &State) -> String {
|
|
s.error
|
|
.iter()
|
|
.chain(s.plan.iter().flat_map(|p| &p.conflicts))
|
|
.cloned()
|
|
.collect::<Vec<_>>()
|
|
.join("; ")
|
|
}
|
|
fn add(p: &mut Project, kind: Kind, layer: Layer, parent: Option<Id>) -> Id {
|
|
p.add_element(kind, layer, kind.label(), parent).unwrap()
|
|
}
|
|
fn fixture(binding: bool) -> Fixture {
|
|
let mut p = Project::empty();
|
|
let mut owners = vec![];
|
|
let mut keys = vec![];
|
|
let mut sibling_keys = vec![];
|
|
let mut locals = vec![];
|
|
for layer in [Layer::Oa, Layer::Sa] {
|
|
let scenario = add(&mut p, Kind::Scenario, layer, None);
|
|
let owner = add(&mut p, Kind::Occurrence, layer, Some(scenario));
|
|
p.elements
|
|
.get_mut(&scenario)
|
|
.unwrap()
|
|
.steps_mut()
|
|
.unwrap()
|
|
.push(owner);
|
|
let body = if binding {
|
|
let reused = add(&mut p, Kind::Scenario, layer, None);
|
|
let referenced = add(&mut p, Kind::Lifeline, layer, Some(reused));
|
|
let sibling = add(&mut p, Kind::Lifeline, layer, Some(reused));
|
|
let local = add(&mut p, Kind::Lifeline, layer, Some(scenario));
|
|
let sibling_local = add(&mut p, Kind::Lifeline, layer, Some(scenario));
|
|
keys.push(AnnotationKey {
|
|
path: vec![],
|
|
field: referenced,
|
|
});
|
|
sibling_keys.push(AnnotationKey {
|
|
path: vec![],
|
|
field: sibling,
|
|
});
|
|
locals.push(local);
|
|
OccurrenceBody::ScenarioUse {
|
|
scenario: Some(reused),
|
|
bindings: vec![
|
|
LifelineBinding { referenced, local },
|
|
LifelineBinding {
|
|
referenced: sibling,
|
|
local: sibling_local,
|
|
},
|
|
],
|
|
}
|
|
} else {
|
|
let work = if layer == Layer::Oa {
|
|
Kind::Activity
|
|
} else {
|
|
Kind::Function
|
|
};
|
|
let from = add(&mut p, work, layer, None);
|
|
let to = add(&mut p, work, layer, None);
|
|
let item = add(&mut p, Kind::ExchangeItem, layer, None);
|
|
let record = add(&mut p, Kind::DataType, layer, None);
|
|
*p.elements.get_mut(&record).unwrap().shared_mut().unwrap() = SharedBody::DataType {
|
|
definition: DataTypeDefinition::Record { supertype: None },
|
|
default: None,
|
|
unit: String::new(),
|
|
};
|
|
let first = add(&mut p, Kind::DataField, layer, Some(item));
|
|
let second = add(&mut p, Kind::DataField, layer, Some(item));
|
|
for field in [first, second] {
|
|
if let Some(SharedBody::DataField { type_ref, .. }) =
|
|
p.elements.get_mut(&field).unwrap().shared_mut()
|
|
{
|
|
*type_ref = TypeRef::Named(record);
|
|
}
|
|
}
|
|
let leaf = add(&mut p, Kind::DataField, layer, Some(record));
|
|
let exchange = p.relate(
|
|
if layer == Layer::Oa {
|
|
RelationKind::Interaction
|
|
} else {
|
|
RelationKind::FunctionalExchange
|
|
},
|
|
"Carrier",
|
|
from,
|
|
to,
|
|
);
|
|
let ports = match &mut p.relations.get_mut(&exchange).unwrap().body {
|
|
RelationBody::Interaction { items, .. } => {
|
|
items.push(item);
|
|
None
|
|
}
|
|
RelationBody::FunctionalExchange {
|
|
items,
|
|
source_port,
|
|
target_port,
|
|
..
|
|
} => {
|
|
items.push(item);
|
|
Some((*source_port, *target_port))
|
|
}
|
|
_ => unreachable!(),
|
|
};
|
|
if let Some((output, input)) = ports {
|
|
if let Some(SharedBody::Port {
|
|
contract: PortContract::FunctionOutput { outgoing_items },
|
|
..
|
|
}) = p.elements.get_mut(&output).unwrap().shared_mut()
|
|
{
|
|
outgoing_items.push(item);
|
|
}
|
|
if let Some(SharedBody::Port {
|
|
contract: PortContract::FunctionInput { incoming_items },
|
|
..
|
|
}) = p.elements.get_mut(&input).unwrap().shared_mut()
|
|
{
|
|
incoming_items.push(item);
|
|
}
|
|
}
|
|
keys.push(AnnotationKey {
|
|
path: vec![first],
|
|
field: leaf,
|
|
});
|
|
sibling_keys.push(AnnotationKey {
|
|
path: vec![second],
|
|
field: leaf,
|
|
});
|
|
locals.push(leaf);
|
|
OccurrenceBody::Exchange {
|
|
operation: None,
|
|
exchange: Some(exchange),
|
|
sender: None,
|
|
receiver: None,
|
|
message_kind: MessageKind::Asynchronous,
|
|
reply_to: None,
|
|
values: vec![
|
|
MessageValue {
|
|
path: vec![first],
|
|
field: leaf,
|
|
expression: if layer == Layer::Oa {
|
|
"operational"
|
|
} else {
|
|
"system"
|
|
}
|
|
.into(),
|
|
},
|
|
MessageValue {
|
|
path: vec![second],
|
|
field: leaf,
|
|
expression: "sibling stays".into(),
|
|
},
|
|
],
|
|
}
|
|
};
|
|
*p.elements.get_mut(&owner).unwrap().shared_mut().unwrap() = SharedBody::Occurrence {
|
|
body,
|
|
precondition: "Retain precondition".into(),
|
|
postcondition: "Retain postcondition".into(),
|
|
};
|
|
owners.push(owner);
|
|
}
|
|
let trace = add_trace(&mut p, owners[0], owners[1], "Individual row review");
|
|
let facet = |keys: &[AnnotationKey]| {
|
|
if binding {
|
|
FacetSelector::Binding(KeyPair {
|
|
oa: Some(keys[0].field),
|
|
sa: Some(keys[1].field),
|
|
})
|
|
} else {
|
|
FacetSelector::Annotation(KeyPair {
|
|
oa: Some(keys[0].clone()),
|
|
sa: Some(keys[1].clone()),
|
|
})
|
|
}
|
|
};
|
|
validate_candidate(&p).unwrap();
|
|
Fixture {
|
|
p,
|
|
owners: [owners[0], owners[1]],
|
|
trace,
|
|
facet: facet(&keys),
|
|
sibling: facet(&sibling_keys),
|
|
locals: [locals[0], locals[1]],
|
|
}
|
|
}
|
|
fn ready(f: &Fixture, reverse: bool) -> State {
|
|
let mut s = State::new(&f.p, f.owners[usize::from(reverse)]).unwrap();
|
|
s.stage_row_references(&f.facet).unwrap();
|
|
s.references.insert(f.locals[0], f.locals[1]);
|
|
s.update(&f.p, RowEvent::Select(f.facet.clone()));
|
|
s
|
|
}
|
|
|
|
#[test]
|
|
fn independent_rows_keep_take_candidate_match_core_and_preserve_siblings_both_directions() {
|
|
for binding in [false, true] {
|
|
for reverse in [false, true] {
|
|
for decision in [
|
|
SemanticDecision::KeepTarget,
|
|
SemanticDecision::TakeSource,
|
|
SemanticDecision::Candidate,
|
|
] {
|
|
let f = fixture(binding);
|
|
let mut s = ready(&f, reverse);
|
|
let layer = s.direction.target_layer();
|
|
let before_sibling = scalar::value(
|
|
&capture_facet(&f.p, f.owners[0], f.owners[1], &f.sibling).unwrap(),
|
|
layer,
|
|
);
|
|
s.update(&f.p, RowEvent::Decide(f.facet.clone(), Some(decision)));
|
|
if decision == SemanticDecision::Candidate {
|
|
s.update(&f.p, RowEvent::Presence(f.facet.clone(), !binding));
|
|
if !binding {
|
|
s.update(
|
|
&f.p,
|
|
RowEvent::RowExpression(f.facet.clone(), f.locals[0].to_string()),
|
|
);
|
|
}
|
|
}
|
|
// Force the GUI to compose the row alongside another candidate,
|
|
// so Take/Keep also exercise adapter scope and mapped references.
|
|
let header = FacetSelector::Header(HeaderFacet::Description);
|
|
s.update(&f.p, RowEvent::Select(header.clone()));
|
|
s.update(
|
|
&f.p,
|
|
RowEvent::Decide(header, Some(SemanticDecision::Candidate)),
|
|
);
|
|
s.update(
|
|
&f.p,
|
|
RowEvent::Input("/description".into(), "Reviewed one row".into()),
|
|
);
|
|
assert!(
|
|
s.can_apply(),
|
|
"{binding}/{reverse}/{decision:?}: {}",
|
|
errors(&s)
|
|
);
|
|
let proposed = s.proposed.as_ref().unwrap().clone();
|
|
assert_eq!(
|
|
scalar::value(
|
|
&capture_facet(&proposed, f.owners[0], f.owners[1], &f.sibling).unwrap(),
|
|
layer
|
|
),
|
|
before_sibling
|
|
);
|
|
let current = scalar::value(
|
|
&capture_facet(&proposed, f.owners[0], f.owners[1], &f.facet).unwrap(),
|
|
layer,
|
|
)
|
|
.unwrap();
|
|
if decision == SemanticDecision::Candidate {
|
|
if binding {
|
|
assert!(current.is_null());
|
|
} else {
|
|
assert_eq!(current["expression"], f.locals[0].to_string());
|
|
}
|
|
}
|
|
assert_eq!(
|
|
prepare_command(&f.p, &s.command(&f.p).unwrap())
|
|
.unwrap()
|
|
.project,
|
|
proposed
|
|
);
|
|
let _ = s.view(&f.p);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn keyed_row_drafts_retain_expression_local_and_presence_across_decisions_and_staging() {
|
|
for binding in [false, true] {
|
|
let f = fixture(binding);
|
|
let mut s = ready(&f, false);
|
|
s.staged.insert(f.facet.clone());
|
|
s.update(
|
|
&f.p,
|
|
RowEvent::Decide(f.facet.clone(), Some(SemanticDecision::Candidate)),
|
|
);
|
|
if binding {
|
|
s.update(
|
|
&f.p,
|
|
RowEvent::RowLocal(f.facet.clone(), view::choice(&f.p, f.locals[1])),
|
|
);
|
|
} else {
|
|
s.update(
|
|
&f.p,
|
|
RowEvent::RowExpression(f.facet.clone(), "unfinished (\n literal UUID text".into()),
|
|
);
|
|
}
|
|
s.update(&f.p, RowEvent::Presence(f.facet.clone(), false));
|
|
s.update(
|
|
&f.p,
|
|
RowEvent::Decide(f.facet.clone(), Some(SemanticDecision::KeepTarget)),
|
|
);
|
|
s.update(&f.p, RowEvent::DropStaged(f.facet.clone()));
|
|
s.update(&f.p, RowEvent::Select(f.facet.clone()));
|
|
s.update(
|
|
&f.p,
|
|
RowEvent::Decide(f.facet.clone(), Some(SemanticDecision::Candidate)),
|
|
);
|
|
assert!(!s.presence[&f.facet]);
|
|
if binding {
|
|
assert_eq!(
|
|
s.row_drafts[&f.facet].local.as_ref().unwrap().id,
|
|
f.locals[1]
|
|
);
|
|
} else {
|
|
assert_eq!(
|
|
s.row_drafts[&f.facet].expression,
|
|
"unfinished (\n literal UUID text"
|
|
);
|
|
}
|
|
assert!(s.can_apply(), "{}", errors(&s));
|
|
// A forged event for an unselected sibling cannot alter its row draft.
|
|
s.update(
|
|
&f.p,
|
|
RowEvent::RowExpression(f.sibling.clone(), "wrong row".into()),
|
|
);
|
|
assert!(s.error.as_ref().unwrap().contains("Select this row"));
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn annotation_address_browser_descends_qualified_fields_without_changing_rows() {
|
|
let f = fixture(false);
|
|
let mut s = ready(&f, false);
|
|
s.update(&f.p, RowEvent::MemberFamily(keyed::Family::Annotation));
|
|
for layer in [Layer::Oa, Layer::Sa] {
|
|
let roots = s.annotation_choices(&f.p, layer).unwrap();
|
|
assert_eq!(roots.len(), 2);
|
|
let key = roots[0].key.clone();
|
|
s.update(
|
|
&f.p,
|
|
RowEvent::AnnotationBrowse(layer, Some(key.clone()), 0),
|
|
);
|
|
let nested = s.annotation_choices(&f.p, layer).unwrap();
|
|
assert_eq!(nested.len(), 1);
|
|
assert_eq!(nested[0].key.path, vec![key.field]);
|
|
s.update(
|
|
&f.p,
|
|
RowEvent::AnnotationAddress(layer, Some(nested[0].key.clone())),
|
|
);
|
|
}
|
|
s.update(&f.p, RowEvent::StageMember);
|
|
assert!(s.error.is_none(), "{}", errors(&s));
|
|
assert_eq!(s.staged.len(), 1);
|
|
assert!(s.decisions.is_empty());
|
|
let _ = s.view(&f.p);
|
|
}
|
|
|
|
#[test]
|
|
fn binding_choices_separate_reused_keys_from_local_lifelines() {
|
|
let f = fixture(true);
|
|
let mut s = ready(&f, false);
|
|
s.update(&f.p, RowEvent::MemberFamily(keyed::Family::Binding));
|
|
for (index, layer) in [Layer::Oa, Layer::Sa].into_iter().enumerate() {
|
|
assert_eq!(s.member_choices(&f.p, layer).len(), 2);
|
|
assert!(
|
|
!s.member_choices(&f.p, layer)
|
|
.iter()
|
|
.any(|c| c.id == f.locals[index])
|
|
);
|
|
let local_choices = s.binding_choices(&f.p, layer, false);
|
|
assert_eq!(local_choices.len(), 2);
|
|
assert!(local_choices.iter().any(|c| c.id == f.locals[index]));
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn binding_local_picker_follows_a_candidate_parent_before_the_review_is_valid() {
|
|
let mut f = fixture(true);
|
|
let destination = add(&mut f.p, Kind::Scenario, Layer::Sa, None);
|
|
let local = add(&mut f.p, Kind::Lifeline, Layer::Sa, Some(destination));
|
|
let mut s = ready(&f, false);
|
|
let parent = FacetSelector::Header(HeaderFacet::Parent);
|
|
s.update(&f.p, RowEvent::Select(parent.clone()));
|
|
s.update(
|
|
&f.p,
|
|
RowEvent::Decide(parent, Some(SemanticDecision::Candidate)),
|
|
);
|
|
s.update(
|
|
&f.p,
|
|
RowEvent::Shape("/parent".into(), serde_json::json!(destination)),
|
|
);
|
|
assert_eq!(
|
|
s.binding_choices(&f.p, Layer::Sa, false)
|
|
.iter()
|
|
.map(|c| c.id)
|
|
.collect::<Vec<_>>(),
|
|
vec![local]
|
|
);
|
|
assert!(
|
|
!s.binding_choices(&f.p, Layer::Sa, false)
|
|
.iter()
|
|
.any(|c| c.id == f.locals[1])
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn row_review_apply_preserves_exact_source_and_semantic_history_through_undo_redo() {
|
|
for binding in [false, true] {
|
|
let f = fixture(binding);
|
|
let mut app = App::with_project(f.p);
|
|
let mut bundle = app.document.bundle().clone();
|
|
bundle
|
|
.files
|
|
.get_mut(&bundle.entry.clone())
|
|
.unwrap()
|
|
.insert_str(0, "// Exact row rationale\r\n");
|
|
app.document.apply_bundle(bundle).unwrap();
|
|
let before = app.document.snapshot();
|
|
start(&mut app, f.owners[0]);
|
|
send(&mut app, RowEvent::EditPair(f.facet.clone()));
|
|
send(&mut app, RowEvent::StageMember);
|
|
send(&mut app, RowEvent::Select(f.facet.clone()));
|
|
send(
|
|
&mut app,
|
|
RowEvent::Decide(f.facet.clone(), Some(SemanticDecision::Candidate)),
|
|
);
|
|
send(&mut app, RowEvent::Presence(f.facet.clone(), false));
|
|
assert!(state(&app).can_apply(), "{}", errors(state(&app)));
|
|
let expected = state(&app).proposed.as_ref().unwrap().clone();
|
|
apply(&mut app);
|
|
assert_eq!(app.project(), &expected);
|
|
assert!(
|
|
app.project().traces[&f.trace]
|
|
.baseline
|
|
.as_ref()
|
|
.unwrap()
|
|
.semantic
|
|
.as_ref()
|
|
.unwrap()
|
|
.facets
|
|
.iter()
|
|
.any(|r| r.selector() == f.facet)
|
|
);
|
|
let after = app.document.snapshot();
|
|
assert!(
|
|
app.document.bundle().files[&app.document.bundle().entry]
|
|
.starts_with("// Exact row rationale\r\n")
|
|
);
|
|
let _ = app.update(Message::Undo);
|
|
assert_eq!(app.document.bundle(), &before.bundle);
|
|
assert_eq!(app.project(), &before.decoded.project);
|
|
let _ = app.update(Message::Redo);
|
|
assert_eq!(app.document.bundle(), &after.bundle);
|
|
assert_eq!(app.project(), &after.decoded.project);
|
|
assert_eq!(
|
|
Document::from_bundle(
|
|
after.bundle.clone(),
|
|
source_project::ProjectLimits::default()
|
|
)
|
|
.unwrap()
|
|
.project(),
|
|
&expected
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn keyed_rows_distinguish_unpaired_source_from_explicit_absence_and_require_exact_reset() {
|
|
for binding in [false, true] {
|
|
let mut f = fixture(binding);
|
|
let (source_only, target_only) = match &f.facet {
|
|
FacetSelector::Annotation(keys) => (
|
|
FacetSelector::Annotation(KeyPair {
|
|
oa: keys.oa.clone(),
|
|
sa: None,
|
|
}),
|
|
FacetSelector::Annotation(KeyPair {
|
|
oa: None,
|
|
sa: keys.sa.clone(),
|
|
}),
|
|
),
|
|
FacetSelector::Binding(keys) => (
|
|
FacetSelector::Binding(KeyPair {
|
|
oa: keys.oa,
|
|
sa: None,
|
|
}),
|
|
FacetSelector::Binding(KeyPair {
|
|
oa: None,
|
|
sa: keys.sa,
|
|
}),
|
|
),
|
|
_ => unreachable!(),
|
|
};
|
|
assert!(keyed::overlap(&source_only, &f.facet));
|
|
assert!(!keyed::overlap(&f.sibling, &f.facet));
|
|
let prior = capture_facet(&f.p, f.owners[0], f.owners[1], &source_only).unwrap();
|
|
f.p.traces.get_mut(&f.trace).unwrap().baseline = Some(TraceBaseline {
|
|
oa: f.p.properties(f.owners[0]).unwrap(),
|
|
sa: f.p.properties(f.owners[1]).unwrap(),
|
|
semantic: Some(SemanticBaseline {
|
|
facets: vec![prior],
|
|
}),
|
|
});
|
|
let mut s = ready(&f, false);
|
|
s.update(
|
|
&f.p,
|
|
RowEvent::Decide(f.facet.clone(), Some(SemanticDecision::KeepTarget)),
|
|
);
|
|
assert!(
|
|
!s.can_apply(),
|
|
"An old one-sided address cannot silently acquire a paired history"
|
|
);
|
|
s.update(&f.p, RowEvent::Reset(source_only.clone(), true));
|
|
assert!(s.can_apply(), "{}", errors(&s));
|
|
let next = s.proposed.as_ref().unwrap();
|
|
let facts = &next.traces[&f.trace]
|
|
.baseline
|
|
.as_ref()
|
|
.unwrap()
|
|
.semantic
|
|
.as_ref()
|
|
.unwrap()
|
|
.facets;
|
|
assert!(
|
|
facts
|
|
.iter()
|
|
.any(|f| f.selector() == s.selected.clone().unwrap())
|
|
);
|
|
assert!(!facts.iter().any(|f| f.selector() == source_only));
|
|
|
|
f.p.traces.get_mut(&f.trace).unwrap().baseline = None;
|
|
if let Some(SharedBody::Occurrence { body, .. }) =
|
|
f.p.elements.get_mut(&f.owners[0]).unwrap().shared_mut()
|
|
{
|
|
match body {
|
|
OccurrenceBody::Exchange { values, .. } => values.clear(),
|
|
OccurrenceBody::ScenarioUse { bindings, .. } => bindings.clear(),
|
|
_ => unreachable!(),
|
|
}
|
|
}
|
|
for (facet, expect_absent) in [(target_only, false), (f.facet.clone(), true)] {
|
|
let mut s = ready(&f, false);
|
|
s.update(
|
|
&f.p,
|
|
RowEvent::Decide(facet, Some(SemanticDecision::TakeSource)),
|
|
);
|
|
let header = FacetSelector::Header(HeaderFacet::Description);
|
|
s.update(
|
|
&f.p,
|
|
RowEvent::Decide(header.clone(), Some(SemanticDecision::Candidate)),
|
|
);
|
|
s.update(&f.p, RowEvent::Select(header));
|
|
s.update(
|
|
&f.p,
|
|
RowEvent::Input("/description".into(), "Compose explicit absence".into()),
|
|
);
|
|
assert!(s.can_apply(), "{}", errors(&s));
|
|
let fact = capture_facet(
|
|
s.proposed.as_ref().unwrap(),
|
|
f.owners[0],
|
|
f.owners[1],
|
|
&f.facet,
|
|
)
|
|
.unwrap();
|
|
assert_eq!(
|
|
scalar::value(&fact, Layer::Sa).unwrap().is_null(),
|
|
expect_absent
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn binding_take_requires_local_correspondence_and_candidate_rejects_wrong_scenario() {
|
|
let f = fixture(true);
|
|
let mut s = ready(&f, false);
|
|
s.references.remove(&f.locals[0]);
|
|
s.update(
|
|
&f.p,
|
|
RowEvent::Decide(f.facet.clone(), Some(SemanticDecision::TakeSource)),
|
|
);
|
|
assert!(!s.can_apply());
|
|
s.references.insert(f.locals[0], f.locals[1]);
|
|
s.update(&f.p, RowEvent::Preview);
|
|
assert!(s.can_apply(), "{}", errors(&s));
|
|
s.update(
|
|
&f.p,
|
|
RowEvent::Decide(f.facet.clone(), Some(SemanticDecision::Candidate)),
|
|
);
|
|
let wrong = s.binding_choices(&f.p, Layer::Sa, true)[0].clone();
|
|
s.update(&f.p, RowEvent::RowLocal(f.facet.clone(), wrong));
|
|
assert!(!s.can_apply());
|
|
assert!(
|
|
s.row_drafts[&f.facet].local.is_some(),
|
|
"Invalid typed selection remains reviewable"
|
|
);
|
|
s.update(
|
|
&f.p,
|
|
RowEvent::RowLocal(f.facet.clone(), view::choice(&f.p, f.locals[1])),
|
|
);
|
|
assert!(s.can_apply(), "{}", errors(&s));
|
|
}
|
|
|
|
#[test]
|
|
fn annotation_observations_keep_uuid_shaped_expression_text_in_both_sides_and_history() {
|
|
let mut f = fixture(false);
|
|
for owner in f.owners {
|
|
if let Some(SharedBody::Occurrence {
|
|
body: OccurrenceBody::Exchange { values, .. },
|
|
..
|
|
}) = f.p.elements.get_mut(&owner).unwrap().shared_mut()
|
|
{
|
|
values[0].expression = values[0].field.to_string();
|
|
}
|
|
}
|
|
let historical = capture_facet(&f.p, f.owners[0], f.owners[1], &f.facet).unwrap();
|
|
for layer in [Layer::Oa, Layer::Sa] {
|
|
let expression = scalar::value(&historical, layer).unwrap()["expression"]
|
|
.as_str()
|
|
.unwrap()
|
|
.to_owned();
|
|
let display = view::observed(&f.p, Some(&historical), layer);
|
|
assert!(
|
|
display.contains(&format!("Expression: {expression:?}")),
|
|
"{display}"
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn keyed_recovery_retains_raw_rows_and_staged_pairs_both_directions_without_applying() {
|
|
for binding in [false, true] {
|
|
for reverse in [false, true] {
|
|
let f = fixture(binding);
|
|
let mut s = ready(&f, reverse);
|
|
s.staged.insert(f.facet.clone());
|
|
s.update(
|
|
&f.p,
|
|
RowEvent::Decide(f.facet.clone(), Some(SemanticDecision::Candidate)),
|
|
);
|
|
if !binding {
|
|
s.update(
|
|
&f.p,
|
|
RowEvent::RowExpression(
|
|
f.facet.clone(),
|
|
"unfinished (\n exact raw draft".into(),
|
|
),
|
|
);
|
|
}
|
|
s.update(&f.p, RowEvent::Presence(f.facet.clone(), false));
|
|
s.update(&f.p, RowEvent::EditPair(f.facet.clone()));
|
|
let saved = s.recovery_snapshot().unwrap();
|
|
let restored = State::from_recovery(&f.p, saved).unwrap();
|
|
assert_eq!(restored.direction, s.direction);
|
|
assert_eq!(restored.decisions, s.decisions);
|
|
assert_eq!(restored.references, s.references);
|
|
assert_eq!(restored.presence, s.presence);
|
|
assert_eq!(restored.staged, s.staged);
|
|
assert_eq!(
|
|
restored.row_drafts[&f.facet].expression,
|
|
s.row_drafts[&f.facet].expression
|
|
);
|
|
assert_eq!(
|
|
restored.row_drafts[&f.facet].local,
|
|
s.row_drafts[&f.facet].local
|
|
);
|
|
assert_eq!(restored.proposed, s.proposed);
|
|
assert_eq!(restored.annotation_oa.selected, s.annotation_oa.selected);
|
|
assert_eq!(restored.annotation_sa.selected, s.annotation_sa.selected);
|
|
assert!(restored.can_apply(), "{}", errors(&restored));
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn multiple_new_rows_compose_in_empty_collections_using_destination_key_order() {
|
|
for binding in [false, true] {
|
|
for reverse in [false, true] {
|
|
let mut f = fixture(binding);
|
|
let target_layer = if reverse { Layer::Oa } else { Layer::Sa };
|
|
let target = f.owners[usize::from(!reverse)];
|
|
let facets = [f.facet.clone(), f.sibling.clone()];
|
|
let original: Vec<_> = facets
|
|
.iter()
|
|
.map(|facet| {
|
|
scalar::value(
|
|
&capture_facet(&f.p, f.owners[0], f.owners[1], facet).unwrap(),
|
|
target_layer,
|
|
)
|
|
.unwrap()
|
|
})
|
|
.collect();
|
|
if let Some(SharedBody::Occurrence { body, .. }) =
|
|
f.p.elements.get_mut(&target).unwrap().shared_mut()
|
|
{
|
|
match body {
|
|
OccurrenceBody::Exchange { values, .. } => values.clear(),
|
|
OccurrenceBody::ScenarioUse { bindings, .. } => bindings.clear(),
|
|
_ => unreachable!(),
|
|
}
|
|
}
|
|
let object_count = f.p.elements.len();
|
|
let mut s = ready(&f, reverse);
|
|
for (facet, row) in facets.iter().zip(&original).rev() {
|
|
s.update(&f.p, RowEvent::EditPair(facet.clone()));
|
|
s.update(&f.p, RowEvent::StageMember);
|
|
s.update(
|
|
&f.p,
|
|
RowEvent::Decide(facet.clone(), Some(SemanticDecision::Candidate)),
|
|
);
|
|
s.update(&f.p, RowEvent::Presence(facet.clone(), true));
|
|
if binding {
|
|
let local = row["local"].as_str().unwrap().parse().unwrap();
|
|
s.update(
|
|
&f.p,
|
|
RowEvent::RowLocal(facet.clone(), view::choice(&f.p, local)),
|
|
);
|
|
} else {
|
|
s.update(
|
|
&f.p,
|
|
RowEvent::RowExpression(facet.clone(), "new independent value".into()),
|
|
);
|
|
}
|
|
}
|
|
assert!(s.can_apply(), "{binding}/{reverse}: {}", errors(&s));
|
|
let proposed = s.proposed.as_ref().unwrap();
|
|
assert_eq!(proposed.elements.len(), object_count);
|
|
for facet in facets {
|
|
assert!(
|
|
!scalar::value(
|
|
&capture_facet(proposed, f.owners[0], f.owners[1], &facet).unwrap(),
|
|
target_layer
|
|
)
|
|
.unwrap()
|
|
.is_null()
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn batch_keyed_rows_compare_exact_target_slots_and_retain_independent_values() {
|
|
for binding in [false, true] {
|
|
let mut f = fixture(binding);
|
|
let other = Id::new_v4();
|
|
let mut source = f.p.elements[&f.owners[0]].clone();
|
|
source.id = other;
|
|
source.alias = format!("other_{}", other.simple());
|
|
source.name = "Alternate operational rationale".into();
|
|
let parent = source.parent.unwrap();
|
|
f.p.elements.insert(other, source);
|
|
f.p.elements
|
|
.get_mut(&parent)
|
|
.unwrap()
|
|
.steps_mut()
|
|
.unwrap()
|
|
.push(other);
|
|
let second = add_trace(&mut f.p, other, f.owners[1], "Alternate keyed rationale");
|
|
validate_candidate(&f.p).unwrap();
|
|
let mut state = ready(&f, false);
|
|
state.update(
|
|
&f.p,
|
|
RowEvent::Decide(f.facet.clone(), Some(SemanticDecision::Candidate)),
|
|
);
|
|
if binding {
|
|
state.update(&f.p, RowEvent::Presence(f.facet.clone(), false));
|
|
} else {
|
|
state.update(
|
|
&f.p,
|
|
RowEvent::RowExpression(f.facet.clone(), f.locals[0].to_string()),
|
|
);
|
|
}
|
|
assert!(state.can_apply(), "{}", errors(&state));
|
|
let first_value = state.proposed.clone().unwrap();
|
|
state.update(&f.p, Event::ChooseTrace(view::choice(&f.p, second)));
|
|
state.update(&f.p, Event::OpenTrace);
|
|
state.update(&f.p, RowEvent::Select(f.facet.clone()));
|
|
state.update(
|
|
&f.p,
|
|
RowEvent::Decide(f.facet.clone(), Some(SemanticDecision::Candidate)),
|
|
);
|
|
if !binding {
|
|
state.update(
|
|
&f.p,
|
|
RowEvent::RowExpression(f.facet.clone(), "Another value".into()),
|
|
);
|
|
}
|
|
assert!(!state.can_apply());
|
|
assert!(errors(&state).contains("disagree"), "{}", errors(&state));
|
|
let saved = state.recovery_snapshot().unwrap();
|
|
let restored = State::from_recovery(&f.p, saved.clone()).unwrap();
|
|
assert_eq!(restored.recovery_snapshot().unwrap(), saved);
|
|
assert!(!restored.can_apply());
|
|
if binding {
|
|
state.update(&f.p, RowEvent::Presence(f.facet.clone(), false));
|
|
} else {
|
|
state.update(
|
|
&f.p,
|
|
RowEvent::RowExpression(f.facet.clone(), f.locals[0].to_string()),
|
|
);
|
|
}
|
|
assert!(state.can_apply(), "{}", errors(&state));
|
|
assert_eq!(state.plan.as_ref().unwrap().request.candidates.len(), 1);
|
|
assert_eq!(
|
|
state.proposed.as_ref().unwrap().elements[&f.owners[1]],
|
|
first_value.elements[&f.owners[1]]
|
|
);
|
|
assert_eq!(
|
|
scalar::value(
|
|
&capture_facet(
|
|
state.proposed.as_ref().unwrap(),
|
|
f.owners[0],
|
|
f.owners[1],
|
|
&f.sibling
|
|
)
|
|
.unwrap(),
|
|
Layer::Sa
|
|
),
|
|
scalar::value(
|
|
&capture_facet(&f.p, f.owners[0], f.owners[1], &f.sibling).unwrap(),
|
|
Layer::Sa
|
|
)
|
|
);
|
|
let _ = state.view(&f.p);
|
|
}
|
|
}
|