600 lines
22 KiB
Rust
600 lines
22 KiB
Rust
use super::*;
|
|
use crate::document::Document;
|
|
use oa_core::source_project::ProjectLimits;
|
|
|
|
fn start(p: &Project, layer: Layer, family: Family, name: &str) -> State {
|
|
let mut state = State::default();
|
|
assert!(state.update(p, Event::Open(layer)).unwrap().is_none());
|
|
state.update(p, Event::Choose(family)).unwrap();
|
|
state.update(p, Event::Name(name.into())).unwrap();
|
|
state
|
|
}
|
|
|
|
#[test]
|
|
fn mounted_creation_keeps_actions_visible_and_renders_only_six_owner_candidates() {
|
|
use iced::advanced::{Layout, layout, widget};
|
|
use iced::{Rectangle, Size, Vector};
|
|
struct Geometry {
|
|
candidates: Vec<widget::Id>,
|
|
rendered: Vec<widget::Id>,
|
|
actions: Option<Rectangle>,
|
|
fields: Option<Rectangle>,
|
|
}
|
|
impl widget::Operation for Geometry {
|
|
fn traverse(&mut self, visit: &mut dyn FnMut(&mut dyn widget::Operation)) {
|
|
visit(self);
|
|
}
|
|
fn container(&mut self, id: Option<&widget::Id>, bounds: Rectangle) {
|
|
if id == Some(&widget::Id::new("behavior-creation-actions")) {
|
|
self.actions = Some(bounds);
|
|
}
|
|
if let Some(id) = id.filter(|id| self.candidates.contains(id)) {
|
|
self.rendered.push(id.clone());
|
|
}
|
|
}
|
|
fn scrollable(
|
|
&mut self,
|
|
id: Option<&widget::Id>,
|
|
bounds: Rectangle,
|
|
_: Rectangle,
|
|
_: Vector,
|
|
_: &mut dyn widget::operation::Scrollable,
|
|
) {
|
|
if id == Some(&widget::Id::new("behavior-creation-fields")) {
|
|
self.fields = Some(bounds);
|
|
}
|
|
}
|
|
}
|
|
let mut p = Project::empty();
|
|
for index in 0..105 {
|
|
p.add_element(
|
|
Kind::Entity,
|
|
Layer::Oa,
|
|
&format!(
|
|
"{index:03} Long engineering participant name {}",
|
|
"雨 extended ".repeat(20)
|
|
),
|
|
None,
|
|
)
|
|
.unwrap();
|
|
}
|
|
let candidates: Vec<_> = p
|
|
.elements
|
|
.keys()
|
|
.map(|id| widget::Id::from(format!("behavior-creation-candidate-{id}")))
|
|
.collect();
|
|
for family in [Family::Sequence, Family::Scenario, Family::Machine] {
|
|
let state = start(&p, Layer::Oa, family, "Very long exact behavior name 雨");
|
|
for size in [
|
|
Size::new(500., 570.),
|
|
Size::new(1050., 570.),
|
|
Size::new(500., 320.),
|
|
] {
|
|
let mut view: UiElement<'_, Event, iced::Theme, ()> = state.view_with(&p).unwrap();
|
|
let mut tree = widget::Tree::new(view.as_widget());
|
|
let node =
|
|
view.as_widget_mut()
|
|
.layout(&mut tree, &(), &layout::Limits::new(Size::ZERO, size));
|
|
let mut geometry = Geometry {
|
|
candidates: candidates.clone(),
|
|
rendered: Vec::new(),
|
|
actions: None,
|
|
fields: None,
|
|
};
|
|
view.as_widget_mut()
|
|
.operate(&mut tree, Layout::new(&node), &(), &mut geometry);
|
|
let actions = geometry.actions.unwrap();
|
|
let fields = geometry.fields.unwrap();
|
|
assert!(actions.x >= 0. && actions.y >= 0.);
|
|
assert!(
|
|
actions.x + actions.width <= size.width
|
|
&& actions.y + actions.height <= size.height
|
|
);
|
|
assert!(fields.height >= 100. && fields.y + fields.height <= actions.y);
|
|
assert_eq!(
|
|
geometry.rendered.len(),
|
|
if family == Family::Machine { 6 } else { 0 }
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn roundtrip(p: Project, state: &mut State) -> Project {
|
|
let command = state.update(&p, Event::Submit).unwrap().unwrap();
|
|
assert_eq!(
|
|
command,
|
|
state.prepare_pending(&p).unwrap(),
|
|
"preparation must retain reserved IDs"
|
|
);
|
|
let api: Command = serde_json::from_value(serde_json::to_value(&command).unwrap()).unwrap();
|
|
assert_eq!(api, command);
|
|
let expected = execute_batch(&p, &[api]).unwrap();
|
|
let target = state.created().unwrap();
|
|
let mut document = Document::from_project(p.clone(), ProjectLimits::default()).unwrap();
|
|
let before_source = document.bundle().clone();
|
|
document.execute(command).unwrap();
|
|
assert_eq!(document.project(), &expected);
|
|
assert!(
|
|
state.has_pending(),
|
|
"only successful acknowledgement clears raw fields"
|
|
);
|
|
state.acknowledge_commit();
|
|
assert!(!state.has_pending());
|
|
assert!(document.project().elements.contains_key(&target.behavior));
|
|
let after_source = document.bundle().clone();
|
|
assert!(document.undo());
|
|
assert_eq!(document.project(), &p);
|
|
assert_eq!(document.bundle(), &before_source);
|
|
assert!(!document.can_undo(), "one creation must be one Undo entry");
|
|
assert!(document.redo());
|
|
assert_eq!(document.project(), &expected);
|
|
assert_eq!(document.bundle(), &after_source);
|
|
let reopened = Document::from_bundle(after_source, ProjectLimits::default()).unwrap();
|
|
assert_eq!(reopened.project(), &expected);
|
|
expected
|
|
}
|
|
|
|
#[test]
|
|
fn oa_process_and_sa_chain_keep_exact_names_root_ownership_and_one_history_entry() {
|
|
for layer in [Layer::Oa, Layer::Sa] {
|
|
let p = Project::empty();
|
|
let name = " Supply \"water\" — режим ";
|
|
let mut state = start(&p, layer, Family::Sequence, name);
|
|
let target = state.created().unwrap();
|
|
let result = roundtrip(p, &mut state);
|
|
let element = &result.elements[&target.behavior];
|
|
assert_eq!(element.name, name);
|
|
assert_eq!(element.layer(), layer);
|
|
assert_eq!(
|
|
element.kind(),
|
|
if layer == Layer::Oa {
|
|
Kind::Process
|
|
} else {
|
|
Kind::FunctionalChain
|
|
}
|
|
);
|
|
assert_eq!(element.parent, None);
|
|
assert!(element.steps().unwrap().is_empty());
|
|
assert!(result.diagrams.is_empty());
|
|
assert!(result.relations.is_empty());
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn all_four_viewpoints_are_explicit_and_survive_source_history_reopen() {
|
|
for layer in [Layer::Oa, Layer::Sa] {
|
|
for viewpoint in viewpoints(layer) {
|
|
let mut p = Project::empty();
|
|
let context = p
|
|
.add_element(
|
|
if layer == Layer::Oa {
|
|
Kind::Capability
|
|
} else {
|
|
Kind::SystemCapability
|
|
},
|
|
layer,
|
|
"Water supply",
|
|
None,
|
|
)
|
|
.unwrap();
|
|
let mut state = start(&p, layer, Family::Scenario, "Nominal supply");
|
|
assert!(
|
|
state
|
|
.prepare_pending(&p)
|
|
.unwrap_err()
|
|
.contains("explicitly")
|
|
);
|
|
assert!(
|
|
state
|
|
.update(&p, Event::Viewpoint(ScenarioViewpoint::MixedLegacy))
|
|
.is_err()
|
|
);
|
|
let wrong = viewpoints(if layer == Layer::Oa {
|
|
Layer::Sa
|
|
} else {
|
|
Layer::Oa
|
|
})[0];
|
|
assert!(state.update(&p, Event::Viewpoint(wrong)).is_err());
|
|
state.update(&p, Event::Viewpoint(viewpoint)).unwrap();
|
|
state.update(&p, Event::Context(Some(context))).unwrap();
|
|
let id = state.created().unwrap().behavior;
|
|
let result = roundtrip(p, &mut state);
|
|
assert_eq!(
|
|
result.elements[&id].parent, None,
|
|
"context is not containment"
|
|
);
|
|
assert!(
|
|
matches!(result.elements[&id].shared(), Some(SharedBody::Scenario { viewpoint: actual, context: Some(actual_context), steps, .. }) if *actual == viewpoint && *actual_context == context && steps.is_empty())
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn machine_requires_valid_owner_and_first_view_adds_no_region_or_state() {
|
|
for layer in [Layer::Oa, Layer::Sa] {
|
|
for first_view in [false, true] {
|
|
let mut p = Project::empty();
|
|
let owner = p
|
|
.add_element(
|
|
if layer == Layer::Oa {
|
|
Kind::Entity
|
|
} else {
|
|
Kind::System
|
|
},
|
|
layer,
|
|
"Pump",
|
|
None,
|
|
)
|
|
.unwrap();
|
|
let wrong_kind = p
|
|
.add_element(
|
|
if layer == Layer::Oa {
|
|
Kind::Activity
|
|
} else {
|
|
Kind::Function
|
|
},
|
|
layer,
|
|
"Pump water",
|
|
None,
|
|
)
|
|
.unwrap();
|
|
let opposite = if layer == Layer::Oa {
|
|
Layer::Sa
|
|
} else {
|
|
Layer::Oa
|
|
};
|
|
let wrong_layer = p
|
|
.add_element(
|
|
if opposite == Layer::Oa {
|
|
Kind::Entity
|
|
} else {
|
|
Kind::System
|
|
},
|
|
opposite,
|
|
"Other layer pump",
|
|
None,
|
|
)
|
|
.unwrap();
|
|
let mut state = start(&p, layer, Family::Machine, "Pump modes");
|
|
assert!(
|
|
state
|
|
.prepare_pending(&p)
|
|
.unwrap_err()
|
|
.contains("participant")
|
|
);
|
|
assert!(state.update(&p, Event::Owner(wrong_kind)).is_err());
|
|
assert!(state.update(&p, Event::Owner(wrong_layer)).is_err());
|
|
state.update(&p, Event::Owner(owner)).unwrap();
|
|
state.update(&p, Event::FirstView(first_view)).unwrap();
|
|
let target = state.created().unwrap();
|
|
let expected_count = p.elements.len() + 1;
|
|
let result = roundtrip(p, &mut state);
|
|
assert_eq!(result.elements.len(), expected_count);
|
|
assert_eq!(result.elements[&target.behavior].parent, Some(owner));
|
|
assert_eq!(result.elements[&target.behavior].kind(), Kind::StateMachine);
|
|
assert_eq!(result.diagrams.len(), usize::from(first_view));
|
|
if let Some(view) = target.diagram {
|
|
let diagram = &result.diagrams[&view];
|
|
assert_eq!(
|
|
diagram.projection,
|
|
ProjectionKind::StateMachine {
|
|
machine: target.behavior
|
|
}
|
|
);
|
|
assert_eq!(diagram.nodes.len(), 1);
|
|
assert_eq!(
|
|
diagram.nodes.values().next().unwrap().element,
|
|
target.behavior
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn cancel_and_illegal_family_layer_switches_never_create_model_objects() {
|
|
let p = Project::empty();
|
|
let mut state = start(&p, Layer::Oa, Family::Scenario, "Raw name");
|
|
state
|
|
.update(
|
|
&p,
|
|
Event::Viewpoint(ScenarioViewpoint::ParticipantInteraction),
|
|
)
|
|
.unwrap();
|
|
let snapshot = state.recovery_snapshot().unwrap();
|
|
assert!(state.update(&p, Event::Open(Layer::Sa)).is_err());
|
|
assert!(state.update(&p, Event::Choose(Family::Sequence)).is_err());
|
|
assert_eq!(state.recovery_snapshot().unwrap(), snapshot);
|
|
assert!(state.update(&p, Event::Cancel).unwrap().is_none());
|
|
assert!(!state.has_pending());
|
|
assert!(state.created().is_none());
|
|
assert!(state.prepare_pending(&p).is_err());
|
|
assert!(p.elements.is_empty());
|
|
assert!(p.diagrams.is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn recovery_preserves_exact_raw_inputs_reserved_ids_and_revalidates_stale_owner() {
|
|
let mut p = Project::empty();
|
|
let owner = p
|
|
.add_element(Kind::Entity, Layer::Oa, "Plant", None)
|
|
.unwrap();
|
|
let mut state = start(&p, Layer::Oa, Family::Machine, " \nRaw rejected name\0 ");
|
|
state.update(&p, Event::Owner(owner)).unwrap();
|
|
state.update(&p, Event::FirstView(true)).unwrap();
|
|
state
|
|
.update(&p, Event::Search(Picker::Owner, " Plant ".into()))
|
|
.unwrap();
|
|
let raw = state.recovery_snapshot().unwrap().unwrap();
|
|
let mut restored = State::from_recovery(raw.clone()).unwrap();
|
|
assert_eq!(restored.recovery_snapshot().unwrap(), Some(raw.clone()));
|
|
assert_eq!(restored.created(), state.created());
|
|
assert_eq!(restored.pending_layer(), Some(Layer::Oa));
|
|
assert!(restored.prepare_pending(&p).is_err());
|
|
restored
|
|
.update(&p, Event::Name("Plant modes".into()))
|
|
.unwrap();
|
|
let command = restored.prepare_pending(&p).unwrap();
|
|
let again = State::from_recovery(restored.recovery_snapshot().unwrap().unwrap()).unwrap();
|
|
assert_eq!(again.prepare_pending(&p).unwrap(), command);
|
|
p.elements.remove(&owner);
|
|
let before = restored.recovery_snapshot().unwrap();
|
|
assert!(
|
|
restored
|
|
.prepare_pending(&p)
|
|
.unwrap_err()
|
|
.contains("participant")
|
|
);
|
|
assert_eq!(restored.recovery_snapshot().unwrap(), before);
|
|
let different = Project::empty();
|
|
assert!(
|
|
restored
|
|
.prepare_pending(&different)
|
|
.unwrap_err()
|
|
.contains("another document")
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn rejected_source_transaction_keeps_draft_and_every_accepted_source_byte() {
|
|
let p = Project::empty();
|
|
let initial = Document::from_project(p.clone(), ProjectLimits::default()).unwrap();
|
|
let bytes: usize = initial.bundle().files.values().map(String::len).sum();
|
|
let limits = ProjectLimits {
|
|
model: language::ParseLimits {
|
|
max_source_bytes: bytes + 32,
|
|
..language::ParseLimits::UNLIMITED
|
|
},
|
|
..ProjectLimits::default()
|
|
};
|
|
let mut doc = Document::from_bundle(initial.bundle().clone(), limits).unwrap();
|
|
let mut state = start(
|
|
&p,
|
|
Layer::Oa,
|
|
Family::Sequence,
|
|
"A long exact name retained after the source budget refuses this new process",
|
|
);
|
|
let raw = state.recovery_snapshot().unwrap();
|
|
let command = state.prepare_pending(&p).unwrap();
|
|
assert!(doc.execute(command).is_err());
|
|
assert_eq!(doc.project(), &p);
|
|
assert_eq!(doc.bundle(), initial.bundle());
|
|
assert!(!doc.can_undo());
|
|
assert_eq!(state.recovery_snapshot().unwrap(), raw);
|
|
state.update(&p, Event::Cancel).unwrap();
|
|
}
|
|
|
|
#[test]
|
|
fn owner_search_is_name_ordered_filtered_and_page_sized_not_a_catalog() {
|
|
let mut p = Project::empty();
|
|
for index in (0..105).rev() {
|
|
p.add_element(Kind::Entity, Layer::Oa, &format!("Pump {index:03}"), None)
|
|
.unwrap();
|
|
}
|
|
p.add_element(Kind::System, Layer::Sa, "Pump 999", None)
|
|
.unwrap();
|
|
p.add_element(Kind::Activity, Layer::Oa, "Pump work", None)
|
|
.unwrap();
|
|
let mut state = start(&p, Layer::Oa, Family::Machine, "Pump modes");
|
|
let all = choices(&p, Layer::Oa, Picker::Owner, "");
|
|
assert_eq!(all.len(), 105);
|
|
assert_eq!(all[0].name, "Pump 000");
|
|
state
|
|
.update(&p, Event::Page(Picker::Owner, usize::MAX))
|
|
.unwrap();
|
|
assert!(
|
|
state.view(&p).is_some(),
|
|
"stale pages are clamped before slicing"
|
|
);
|
|
state
|
|
.update(&p, Event::Search(Picker::Owner, "Pump 104".into()))
|
|
.unwrap();
|
|
assert_eq!(state.draft.as_ref().unwrap().browsers[0].page, 0);
|
|
let found = choices(&p, Layer::Oa, Picker::Owner, "Pump 104");
|
|
assert_eq!(found.len(), 1);
|
|
state.update(&p, Event::Owner(found[0].id)).unwrap();
|
|
assert_eq!(state.draft.as_ref().unwrap().owner, Some(found[0].id));
|
|
}
|
|
|
|
#[test]
|
|
fn repeated_owner_names_remain_distinguishable_by_searchable_parent_context_and_alias() {
|
|
let mut p = Project::empty();
|
|
let north = p
|
|
.add_element(Kind::Entity, Layer::Oa, "North station", None)
|
|
.unwrap();
|
|
let south = p
|
|
.add_element(Kind::Entity, Layer::Oa, "South station", None)
|
|
.unwrap();
|
|
let first = p
|
|
.add_element(Kind::Entity, Layer::Oa, "Pump", Some(north))
|
|
.unwrap();
|
|
let second = p
|
|
.add_element(Kind::Entity, Layer::Oa, "Pump", Some(south))
|
|
.unwrap();
|
|
let label = reference_label(&p, second);
|
|
assert_ne!(reference_label(&p, first), label);
|
|
assert!(label.contains("South station") && label.contains("Pump"));
|
|
assert!(label.contains(&p.elements[&second].alias));
|
|
let found = choices(&p, Layer::Oa, Picker::Owner, "south station");
|
|
assert!(found.iter().any(|e| e.id == second));
|
|
assert!(!found.iter().any(|e| e.id == first));
|
|
let mut state = start(&p, Layer::Oa, Family::Machine, "Pump states");
|
|
state.update(&p, Event::Owner(second)).unwrap();
|
|
let result = execute_batch(&p, &[state.prepare_pending(&p).unwrap()]).unwrap();
|
|
assert_eq!(
|
|
result.elements[&state.created().unwrap().behavior].parent,
|
|
Some(second)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn corrupted_recovery_and_reserved_identity_collision_refuse_without_repairing_raw_values() {
|
|
let p = Project::empty();
|
|
let state = start(&p, Layer::Sa, Family::Sequence, "Supply");
|
|
let raw = state.recovery_snapshot().unwrap().unwrap();
|
|
for (field, value) in [
|
|
("version", serde_json::json!(99)),
|
|
("unexpected", serde_json::json!(true)),
|
|
] {
|
|
let mut bad = raw.clone();
|
|
bad[field] = value;
|
|
assert!(State::from_recovery(bad).is_err());
|
|
}
|
|
let mut bad = raw;
|
|
bad["element"] = serde_json::json!(p.id);
|
|
let restored = State::from_recovery(bad.clone()).unwrap();
|
|
assert!(
|
|
restored
|
|
.prepare_pending(&p)
|
|
.unwrap_err()
|
|
.contains("identity")
|
|
);
|
|
assert_eq!(restored.recovery_snapshot().unwrap(), Some(bad));
|
|
}
|
|
|
|
#[test]
|
|
fn post_commit_selection_is_correlated_with_exact_created_behavior_and_view() {
|
|
let mut p = Project::empty();
|
|
let owner = p
|
|
.add_element(Kind::Entity, Layer::Oa, "Pump", None)
|
|
.unwrap();
|
|
let mut state = start(&p, Layer::Oa, Family::Machine, "Pump modes");
|
|
state.update(&p, Event::Owner(owner)).unwrap();
|
|
state.update(&p, Event::FirstView(true)).unwrap();
|
|
let command = state.prepare_pending(&p).unwrap();
|
|
assert_eq!(state.created_by(&p, &command), state.created());
|
|
assert_eq!(
|
|
state.created_by(&p, &Command::Rename(owner, "Unrelated change".into())),
|
|
None
|
|
);
|
|
for edit in 0..4 {
|
|
let mut changed = command.clone();
|
|
let Command::Batch(ref mut commands) = changed else {
|
|
panic!("creation batch");
|
|
};
|
|
if edit < 3 {
|
|
let Command::CreateElement(element) = &mut commands[0] else {
|
|
panic!("new behavior");
|
|
};
|
|
match edit {
|
|
0 => element.id = Id::new_v4(),
|
|
1 => element.name = "A different authored draft".into(),
|
|
_ => element.parent = None,
|
|
}
|
|
} else {
|
|
let Command::CreateDiagram(diagram) = &mut commands[1] else {
|
|
panic!("new view");
|
|
};
|
|
diagram.projection = ProjectionKind::StateMachine { machine: owner };
|
|
}
|
|
assert_eq!(state.created_by(&p, &changed), None);
|
|
}
|
|
let Command::Batch(commands) = command else {
|
|
panic!("creation batch");
|
|
};
|
|
assert_eq!(
|
|
state.created_by(&p, &commands[0]),
|
|
None,
|
|
"an opt-in view must be part of the actual committed batch"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn optional_context_collapses_without_losing_authored_reference_or_raw_search() {
|
|
let mut p = Project::empty();
|
|
let context = p
|
|
.add_element(Kind::Capability, Layer::Oa, "Water supply", None)
|
|
.unwrap();
|
|
let mut state = start(&p, Layer::Oa, Family::Scenario, "Supply nominal");
|
|
assert!(!state.draft.as_ref().unwrap().context_open);
|
|
state
|
|
.update(&p, Event::Viewpoint(ScenarioViewpoint::OperationalActivity))
|
|
.unwrap();
|
|
state.update(&p, Event::ContextOptions(true)).unwrap();
|
|
state
|
|
.update(&p, Event::Search(Picker::Context, " Water ".into()))
|
|
.unwrap();
|
|
state.update(&p, Event::Context(Some(context))).unwrap();
|
|
let command = state.prepare_pending(&p).unwrap();
|
|
state.update(&p, Event::ContextOptions(false)).unwrap();
|
|
let restored = State::from_recovery(state.recovery_snapshot().unwrap().unwrap()).unwrap();
|
|
assert!(!restored.draft.as_ref().unwrap().context_open);
|
|
assert_eq!(
|
|
restored.draft.as_ref().unwrap().browsers[1].query,
|
|
" Water "
|
|
);
|
|
assert_eq!(restored.prepare_pending(&p).unwrap(), command);
|
|
assert_eq!(restored.created_by(&p, &command), restored.created());
|
|
}
|
|
|
|
#[test]
|
|
fn creation_selection_rejects_changed_scenario_body_and_post_creation_operations() {
|
|
let mut p = Project::empty();
|
|
let capability = p
|
|
.add_element(Kind::SystemCapability, Layer::Sa, "Supply", None)
|
|
.unwrap();
|
|
let mut state = start(&p, Layer::Sa, Family::Scenario, "Nominal functions");
|
|
state
|
|
.update(&p, Event::Viewpoint(ScenarioViewpoint::Functional))
|
|
.unwrap();
|
|
let command = state.prepare_pending(&p).unwrap();
|
|
assert_eq!(state.created_by(&p, &command), state.created());
|
|
for edit in 0..4 {
|
|
let mut altered = command.clone();
|
|
let Command::Batch(commands) = &mut altered else {
|
|
panic!("creation batch");
|
|
};
|
|
match edit {
|
|
0 => {
|
|
let Command::CreateElement(element) = &mut commands[0] else {
|
|
panic!("scenario");
|
|
};
|
|
let Some(SharedBody::Scenario { viewpoint, .. }) = element.shared_mut() else {
|
|
panic!("scenario body");
|
|
};
|
|
*viewpoint = ScenarioViewpoint::BoundaryInterface;
|
|
}
|
|
1 => {
|
|
let Command::CreateElement(element) = &mut commands[0] else {
|
|
panic!("scenario");
|
|
};
|
|
let Some(SharedBody::Scenario { context, .. }) = element.shared_mut() else {
|
|
panic!("scenario body");
|
|
};
|
|
*context = Some(capability);
|
|
}
|
|
2 => commands.push(Command::Rename(
|
|
state.created().unwrap().behavior,
|
|
"Changed afterward".into(),
|
|
)),
|
|
_ => commands.push(Command::Delete(state.created().unwrap().behavior)),
|
|
}
|
|
assert!(
|
|
execute_batch(&p, std::slice::from_ref(&altered)).is_ok(),
|
|
"the alternate command is valid, but is not this authored creation"
|
|
);
|
|
assert_eq!(state.created_by(&p, &altered), None);
|
|
}
|
|
}
|