Integrate release recovery, semantic review, coverage and Windows packaging
This commit is contained in:
@@ -0,0 +1,736 @@
|
||||
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 {
|
||||
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, Event::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, Event::Decide(f.facet.clone(), Some(decision)));
|
||||
if decision == SemanticDecision::Candidate {
|
||||
s.update(&f.p, Event::Presence(f.facet.clone(), !binding));
|
||||
if !binding {
|
||||
s.update(
|
||||
&f.p,
|
||||
Event::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, Event::Select(header.clone()));
|
||||
s.update(
|
||||
&f.p,
|
||||
Event::Decide(header, Some(SemanticDecision::Candidate)),
|
||||
);
|
||||
s.update(
|
||||
&f.p,
|
||||
Event::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,
|
||||
Event::Decide(f.facet.clone(), Some(SemanticDecision::Candidate)),
|
||||
);
|
||||
if binding {
|
||||
s.update(
|
||||
&f.p,
|
||||
Event::RowLocal(f.facet.clone(), view::choice(&f.p, f.locals[1])),
|
||||
);
|
||||
} else {
|
||||
s.update(
|
||||
&f.p,
|
||||
Event::RowExpression(f.facet.clone(), "unfinished (\n literal UUID text".into()),
|
||||
);
|
||||
}
|
||||
s.update(&f.p, Event::Presence(f.facet.clone(), false));
|
||||
s.update(
|
||||
&f.p,
|
||||
Event::Decide(f.facet.clone(), Some(SemanticDecision::KeepTarget)),
|
||||
);
|
||||
s.update(&f.p, Event::DropStaged(f.facet.clone()));
|
||||
s.update(&f.p, Event::Select(f.facet.clone()));
|
||||
s.update(
|
||||
&f.p,
|
||||
Event::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,
|
||||
Event::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, Event::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, Event::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,
|
||||
Event::AnnotationAddress(layer, Some(nested[0].key.clone())),
|
||||
);
|
||||
}
|
||||
s.update(&f.p, Event::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, Event::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, Event::Select(parent.clone()));
|
||||
s.update(
|
||||
&f.p,
|
||||
Event::Decide(parent, Some(SemanticDecision::Candidate)),
|
||||
);
|
||||
s.update(
|
||||
&f.p,
|
||||
Event::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, Event::EditPair(f.facet.clone()));
|
||||
send(&mut app, Event::StageMember);
|
||||
send(&mut app, Event::Select(f.facet.clone()));
|
||||
send(
|
||||
&mut app,
|
||||
Event::Decide(f.facet.clone(), Some(SemanticDecision::Candidate)),
|
||||
);
|
||||
send(&mut app, Event::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,
|
||||
Event::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, Event::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,
|
||||
Event::Decide(facet, Some(SemanticDecision::TakeSource)),
|
||||
);
|
||||
let header = FacetSelector::Header(HeaderFacet::Description);
|
||||
s.update(
|
||||
&f.p,
|
||||
Event::Decide(header.clone(), Some(SemanticDecision::Candidate)),
|
||||
);
|
||||
s.update(&f.p, Event::Select(header));
|
||||
s.update(
|
||||
&f.p,
|
||||
Event::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,
|
||||
Event::Decide(f.facet.clone(), Some(SemanticDecision::TakeSource)),
|
||||
);
|
||||
assert!(!s.can_apply());
|
||||
s.references.insert(f.locals[0], f.locals[1]);
|
||||
s.update(&f.p, Event::Preview);
|
||||
assert!(s.can_apply(), "{}", errors(&s));
|
||||
s.update(
|
||||
&f.p,
|
||||
Event::Decide(f.facet.clone(), Some(SemanticDecision::Candidate)),
|
||||
);
|
||||
let wrong = s.binding_choices(&f.p, Layer::Sa, true)[0].clone();
|
||||
s.update(&f.p, Event::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,
|
||||
Event::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,
|
||||
Event::Decide(f.facet.clone(), Some(SemanticDecision::Candidate)),
|
||||
);
|
||||
if !binding {
|
||||
s.update(
|
||||
&f.p,
|
||||
Event::RowExpression(f.facet.clone(), "unfinished (\n exact raw draft".into()),
|
||||
);
|
||||
}
|
||||
s.update(&f.p, Event::Presence(f.facet.clone(), false));
|
||||
s.update(&f.p, Event::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, Event::EditPair(facet.clone()));
|
||||
s.update(&f.p, Event::StageMember);
|
||||
s.update(
|
||||
&f.p,
|
||||
Event::Decide(facet.clone(), Some(SemanticDecision::Candidate)),
|
||||
);
|
||||
s.update(&f.p, Event::Presence(facet.clone(), true));
|
||||
if binding {
|
||||
let local = row["local"].as_str().unwrap().parse().unwrap();
|
||||
s.update(
|
||||
&f.p,
|
||||
Event::RowLocal(facet.clone(), view::choice(&f.p, local)),
|
||||
);
|
||||
} else {
|
||||
s.update(
|
||||
&f.p,
|
||||
Event::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()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user