Integrate process, scenario and typed history authoring checkpoint

This commit is contained in:
2026-09-10 11:14:43 -07:00
22 changed files with 4093 additions and 166 deletions
@@ -279,6 +279,166 @@ fn architecture_cancelled_gesture_cannot_commit_a_later_named_draft() {
assert!(app.architecture.is_some());
}
#[test]
fn architecture_f2_rename_preserves_identity_placement_and_atomic_history() {
use iced::{keyboard, mouse, widget::canvas::Program};
let mut app = setup(Layer::Oa);
send(
&mut app,
Event::Start(Kind::Entity, Some(Position { x: 200., y: 120. })),
);
send(&mut app, Event::Name("Response team".into()));
send(&mut app, Event::Submit);
let before = app.document.bundle().clone();
let element = app.selected;
let node = app.selected_node;
let diagrams = app.project().diagrams.clone();
let camera = (app.zoom, app.pan);
let canvas = crate::diagram::DiagramCanvas {
project: app.project(),
view_index: None,
diagram: app.active_diagram.unwrap(),
selected: element,
zoom: app.zoom,
pan: app.pan,
context: app.route_context().unwrap(),
selected_bend: None,
editable: true,
placing: false,
connection: None,
};
let bounds = iced::Rectangle::with_size(iced::Size::new(800., 600.));
let cursor = mouse::Cursor::Available(iced::Point::new(500., 400.));
let mut state = crate::diagram::State::default();
let key = iced::widget::canvas::Event::Keyboard(keyboard::Event::KeyPressed {
key: keyboard::Key::Named(keyboard::key::Named::F2),
modified_key: keyboard::Key::Named(keyboard::key::Named::F2),
physical_key: keyboard::key::Physical::Code(keyboard::key::Code::F2),
location: keyboard::Location::Standard,
modifiers: keyboard::Modifiers::empty(),
text: None,
repeat: false,
});
assert!(canvas.update(&mut state, &key, bounds, cursor).is_none());
let _ = canvas.update(
&mut state,
&iced::widget::canvas::Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)),
bounds,
cursor,
);
let message = canvas
.update(&mut state, &key, bounds, cursor)
.expect("focused F2 opens the selected semantic name")
.into_inner()
.0
.unwrap();
let _ = app.update(message);
assert!(app.architecture.as_ref().unwrap().inline());
send(&mut app, Event::Name(" Emergency team 雨 ".into()));
assert_eq!(app.document.bundle(), &before);
send(&mut app, Event::Submit);
assert!(app.architecture.is_none());
assert_eq!((app.selected, app.selected_node), (element, node));
assert_eq!(
app.project().elements[&element].name,
" Emergency team 雨 "
);
assert_eq!(app.project().diagrams, diagrams);
assert_eq!((app.zoom, app.pan), camera);
let accepted = app.document.bundle().clone();
let _ = app.update(Message::Undo);
assert_eq!(app.document.bundle(), &before);
let _ = app.update(Message::Redo);
assert_eq!(app.document.bundle(), &accepted);
let context = app.route_context().unwrap();
send(
&mut app,
Event::Rename(context, element, Position { x: 200., y: 120. }),
);
send(&mut app, Event::Name("Cancelled name".into()));
let _ = app.update(Message::Escape);
assert!(app.architecture.is_none());
assert_eq!(app.document.bundle(), &accepted);
}
#[test]
fn architecture_contextual_allocation_creates_work_relation_and_node_in_one_undo() {
for (layer, performer_kind, work_kind) in [
(Layer::Oa, Kind::Entity, Kind::Activity),
(Layer::Sa, Kind::System, Kind::Function),
] {
let mut app = setup(layer);
send(
&mut app,
Event::Start(performer_kind, Some(Position { x: 20., y: 20. })),
);
send(&mut app, Event::Name("Control team".into()));
send(&mut app, Event::Submit);
let performer = app.selected;
let before = app.document.bundle().clone();
send(
&mut app,
Event::Allocate(work_kind, performer, Some(Position { x: 55., y: 90. })),
);
send(&mut app, Event::Name("Assess pressure".into()));
assert_eq!(app.document.bundle(), &before);
send(&mut app, Event::Submit);
assert!(app.architecture.is_none(), "{}", app.status);
let work = app.selected;
assert_eq!(app.project().elements[&work].parent, None);
assert!(
app.project()
.relations
.values()
.any(|r| r.kind() == RelationKind::Allocation
&& r.source() == performer
&& r.target() == work)
);
let node = &app.project().diagrams[&app.active_diagram.unwrap()].nodes
[&app.selected_node.unwrap()];
assert_eq!(node.position, Position { x: 55., y: 90. });
let accepted = app.document.bundle().clone();
let _ = app.update(Message::Undo);
assert_eq!(app.document.bundle(), &before);
let _ = app.update(Message::Redo);
assert_eq!(app.document.bundle(), &accepted);
}
}
#[test]
fn architecture_rejected_submission_keeps_inline_name_visible_and_invalid_click_is_ignored() {
let mut app = setup(Layer::Oa);
send(&mut app, Event::Start(Kind::Entity, None));
let context = app.route_context().unwrap();
send(
&mut app,
Event::Click(
context,
Position {
x: f32::NAN,
y: 30.,
},
),
);
assert!(app.architecture.as_ref().unwrap().unnamed_tool());
send(&mut app, Event::Click(context, Position { x: 40., y: 30. }));
send(&mut app, Event::Name("Retained team".into()));
// A replaced document must not accept a queued draft submission.
app.canvas_document = Id::new_v4();
let before = app.document.bundle().clone();
send(&mut app, Event::Submit);
let draft = app.architecture.as_ref().unwrap();
assert!(draft.inline());
assert!(draft.error.is_some());
assert!(
matches!(&draft.saved.intent, architecture_authoring::Intent::Create { name, .. } if name == "Retained team")
);
assert_eq!(app.document.bundle(), &before);
let _ = app.update(Message::Escape);
assert!(app.architecture.is_none());
assert_eq!(app.document.bundle(), &before);
}
#[test]
fn basic_transition_explicit_overview_focus_wins_over_an_already_populated_architecture() {
use crate::workflow::{Event as W, Workflow};