Files
ARCADIA-Studio/crates/oa-desktop/src/app/scenario_diagram/adoption_tests.rs
T

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);
}