265 lines
9.0 KiB
Rust
265 lines
9.0 KiB
Rust
//! Independent adoption oracles: expected Projects are edited directly in the
|
|
//! fixture, then admitted through the actual source editor/Document transaction.
|
|
//! Navigation is checked separately from the expected authored mutation.
|
|
use super::*;
|
|
use crate::behavior::canvas_workspace::tests::Mounted;
|
|
use iced::advanced::widget;
|
|
use iced::widget::text_editor;
|
|
use oa_core::language;
|
|
use std::sync::Arc;
|
|
|
|
struct Fixture {
|
|
app: App,
|
|
owner: Id,
|
|
same_layer: Id,
|
|
other_layer: Id,
|
|
view: Id,
|
|
spare_view: Id,
|
|
}
|
|
|
|
fn fixture() -> Fixture {
|
|
let mut project = Project::empty();
|
|
let owner = project
|
|
.add_element(Kind::Scenario, Layer::Oa, "Original owner", None)
|
|
.unwrap();
|
|
let same_layer = project
|
|
.add_element(Kind::Scenario, Layer::Oa, "Other OA owner", None)
|
|
.unwrap();
|
|
let other_layer = project
|
|
.add_element(Kind::Scenario, Layer::Sa, "SA owner", None)
|
|
.unwrap();
|
|
// A remaining topology makes silent fallback observably different from the
|
|
// required unavailable Saved view after its owner is deleted.
|
|
project
|
|
.add_element(Kind::Process, Layer::Oa, "Remaining process", None)
|
|
.unwrap();
|
|
let mut make_view = |alias: &str, name: &str, owner| {
|
|
let id = Id::new_v4();
|
|
project.diagrams.insert(
|
|
id,
|
|
Diagram {
|
|
id,
|
|
alias: alias.into(),
|
|
name: name.into(),
|
|
description: String::new(),
|
|
layer: Layer::Oa,
|
|
projection: ProjectionKind::Scenario { scenario: owner },
|
|
layout: None,
|
|
nodes: Default::default(),
|
|
routes: vec![],
|
|
view_options: Default::default(),
|
|
},
|
|
);
|
|
id
|
|
};
|
|
let view = make_view("retained_view", "Retained arrangement", owner);
|
|
let spare_view = make_view("spare_view", "Spare arrangement", same_layer);
|
|
let mut app = App::with_project(project);
|
|
let _ = app.update(Message::Select(owner));
|
|
choose(&mut app, view);
|
|
assert_saved(&app, view, owner, Layer::Oa);
|
|
assert!(!app.document.can_undo());
|
|
Fixture {
|
|
app,
|
|
owner,
|
|
same_layer,
|
|
other_layer,
|
|
view,
|
|
spare_view,
|
|
}
|
|
}
|
|
|
|
fn choose(app: &mut App, id: Id) {
|
|
let _ = app.update(Message::Diagram(Choice {
|
|
id,
|
|
label: "Previously captured picker item".into(),
|
|
}));
|
|
}
|
|
|
|
fn assert_saved(app: &App, view: Id, owner: Id, layer: Layer) {
|
|
assert_eq!(app.page, Page::Behavior);
|
|
assert_eq!(app.layer, layer);
|
|
assert_eq!(app.active_diagram, Some(view));
|
|
let context = app
|
|
.scenario_context()
|
|
.expect("saved owner remains the visible context");
|
|
assert_eq!(context.scenario, owner);
|
|
assert_eq!(
|
|
context.representation,
|
|
Representation::Saved { diagram: view }
|
|
);
|
|
assert!(!context.visit.is_nil());
|
|
}
|
|
|
|
fn adopt_from_source(app: &mut App, expected: &Project) {
|
|
let text = language::format(expected);
|
|
// Parser equality is a fixture precondition, not the navigation oracle.
|
|
assert_eq!(language::parse(&text).unwrap(), *expected);
|
|
let _ = app.update(Message::Page(Page::Text));
|
|
let _ = app.update(Message::SourceEdit(text_editor::Action::SelectAll));
|
|
let _ = app.update(Message::SourceEdit(text_editor::Action::Edit(
|
|
text_editor::Edit::Paste(Arc::new(text)),
|
|
)));
|
|
assert!(app.source_dirty());
|
|
let _ = app.update(Message::ApplySource);
|
|
assert!(!app.source_dirty(), "{}", app.status);
|
|
assert_eq!(
|
|
app.project(),
|
|
expected,
|
|
"actual source transaction must adopt exactly the fixture change"
|
|
);
|
|
let _ = app.update(Message::Page(Page::Behavior));
|
|
}
|
|
|
|
fn retarget(cross_layer: bool) {
|
|
let mut f = fixture();
|
|
let before = f.app.document.snapshot();
|
|
let old_context = f.app.scenario_context().unwrap();
|
|
let revision = f.app.document.revision();
|
|
let target = if cross_layer {
|
|
f.other_layer
|
|
} else {
|
|
f.same_layer
|
|
};
|
|
let layer = if cross_layer { Layer::Sa } else { Layer::Oa };
|
|
let mut expected = before.decoded.project.clone();
|
|
let view = expected.diagrams.get_mut(&f.view).unwrap();
|
|
view.projection = ProjectionKind::Scenario { scenario: target };
|
|
view.layer = layer;
|
|
|
|
adopt_from_source(&mut f.app, &expected);
|
|
assert_saved(&f.app, f.view, target, layer);
|
|
assert_ne!(f.app.scenario_context().unwrap().visit, old_context.visit);
|
|
assert_eq!(
|
|
f.app.document.revision(),
|
|
revision + 1,
|
|
"routing must not create a second transaction"
|
|
);
|
|
assert_eq!(
|
|
f.app.project().diagrams[&f.spare_view],
|
|
before.decoded.project.diagrams[&f.spare_view]
|
|
);
|
|
let adopted_bundle = f.app.document.bundle().clone();
|
|
|
|
let _ = f.app.update(Message::Undo);
|
|
assert_saved(&f.app, f.view, f.owner, Layer::Oa);
|
|
assert_eq!(f.app.project(), &before.decoded.project);
|
|
assert_eq!(f.app.document.bundle(), &before.bundle);
|
|
assert!(
|
|
!f.app.document.can_undo(),
|
|
"exactly one authored adoption must be undone"
|
|
);
|
|
let _ = f.app.update(Message::Redo);
|
|
assert_saved(&f.app, f.view, target, layer);
|
|
assert_eq!(f.app.project(), &expected);
|
|
assert_eq!(f.app.document.bundle(), &adopted_bundle);
|
|
assert!(!f.app.document.can_redo());
|
|
}
|
|
|
|
#[test]
|
|
fn source_same_layer_owner_retarget_undo_redo_keeps_requested_saved_uuid() {
|
|
retarget(false);
|
|
}
|
|
|
|
#[test]
|
|
fn source_cross_layer_owner_retarget_undo_redo_keeps_requested_saved_uuid() {
|
|
retarget(true);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn source_deleted_owner_and_saved_view_mount_unavailable_instead_of_another_behavior() {
|
|
let mut f = fixture();
|
|
let before = f.app.document.snapshot();
|
|
let mut expected = before.decoded.project.clone();
|
|
assert!(expected.elements.remove(&f.owner).is_some());
|
|
assert!(expected.diagrams.remove(&f.view).is_some());
|
|
assert!(expected.elements.contains_key(&f.same_layer));
|
|
assert!(
|
|
expected
|
|
.elements
|
|
.values()
|
|
.any(|e| e.kind() == Kind::Process)
|
|
);
|
|
adopt_from_source(&mut f.app, &expected);
|
|
assert_eq!(
|
|
f.app.behavior.scenario.representation(),
|
|
Some(Representation::Saved { diagram: f.view })
|
|
);
|
|
let adopted = f.app.document.snapshot();
|
|
let revision = f.app.document.revision();
|
|
let mut mounted = Mounted::new(&mut f.app).await;
|
|
let geometry = mounted.geometry(&f.app);
|
|
assert!(
|
|
geometry
|
|
.containers
|
|
.contains_key(&widget::Id::from("scenario-viewport-unavailable"))
|
|
);
|
|
assert!(
|
|
!geometry
|
|
.containers
|
|
.contains_key(&widget::Id::from("scenario-viewport"))
|
|
);
|
|
assert_eq!(f.app.project(), &expected);
|
|
assert_eq!(f.app.document.bundle(), &adopted.bundle);
|
|
assert_eq!(
|
|
f.app.document.revision(),
|
|
revision,
|
|
"mounting unavailable must remain passive"
|
|
);
|
|
|
|
let _ = f.app.update(Message::Undo);
|
|
assert_saved(&f.app, f.view, f.owner, Layer::Oa);
|
|
assert_eq!(f.app.document.bundle(), &before.bundle);
|
|
let _ = f.app.update(Message::Redo);
|
|
assert_eq!(
|
|
f.app.behavior.scenario.representation(),
|
|
Some(Representation::Saved { diagram: f.view })
|
|
);
|
|
mounted.settle(&mut f.app).await;
|
|
assert!(
|
|
mounted
|
|
.geometry(&f.app)
|
|
.containers
|
|
.contains_key(&widget::Id::from("scenario-viewport-unavailable"))
|
|
);
|
|
assert_eq!(f.app.project(), &expected);
|
|
assert_eq!(f.app.document.bundle(), &adopted.bundle);
|
|
}
|
|
|
|
#[test]
|
|
fn missing_and_deleted_picker_ids_cannot_apply_an_unrelated_valid_property_draft() {
|
|
let mut f = fixture();
|
|
let mut expected = f.app.project().clone();
|
|
expected.diagrams.remove(&f.spare_view).unwrap();
|
|
adopt_from_source(&mut f.app, &expected);
|
|
let _ = f.app.update(Message::Select(f.owner));
|
|
let _ = f.app.update(Message::Field(
|
|
"/name".into(),
|
|
"Pending valid owner rename".into(),
|
|
));
|
|
assert!(f.app.draft_dirty());
|
|
let before = f.app.document.snapshot();
|
|
let revision = f.app.document.revision();
|
|
let selected = f.app.selected;
|
|
for invalid in [Id::nil(), f.spare_view] {
|
|
choose(&mut f.app, invalid);
|
|
assert_saved(&f.app, f.view, f.owner, Layer::Oa);
|
|
assert_eq!(f.app.selected, selected);
|
|
assert!(f.app.draft_dirty());
|
|
assert_eq!(f.app.project(), &before.decoded.project);
|
|
assert_eq!(f.app.document.bundle(), &before.bundle);
|
|
assert_eq!(f.app.document.revision(), revision);
|
|
}
|
|
// Establish that the retained draft really was valid, not merely refused by
|
|
// the property validator: explicit Apply commits it in exactly one step.
|
|
assert!(f.app.apply(), "{}", f.app.status);
|
|
assert_eq!(
|
|
f.app.project().elements[&f.owner].name,
|
|
"Pending valid owner rename"
|
|
);
|
|
assert_eq!(f.app.document.revision(), revision + 1);
|
|
let _ = f.app.update(Message::Undo);
|
|
assert_eq!(f.app.project(), &before.decoded.project);
|
|
assert_eq!(f.app.document.bundle(), &before.bundle);
|
|
}
|