Integrate diagnostic decorations and current-source Text input boundaries
This commit is contained in:
@@ -165,6 +165,7 @@ fn captured_behavior_toolbar_cannot_retire_a_new_documents_navigation() {
|
||||
first_measurement(&mut app, &found);
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
#[test]
|
||||
fn stale_compact_drawer_controls_preserve_current_navigation_and_evidence() {
|
||||
use crate::app::text_workspace::{DrawerTab, Event as TextEvent};
|
||||
@@ -218,25 +219,37 @@ fn stale_compact_drawer_controls_preserve_current_navigation_and_evidence() {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
#[test]
|
||||
fn current_compact_drawer_controls_and_mounted_resize_remain_live() {
|
||||
use crate::app::text_workspace::{DrawerTab, Event as TextEvent};
|
||||
let mut app = navigation_fixture();
|
||||
let accepted = app.document.snapshot();
|
||||
let raw = app.source_workspace.bundle();
|
||||
navigate(&mut app);
|
||||
app.page = Page::Text;
|
||||
assert!(app.pending_diagram_reveal.is_some());
|
||||
let epoch = app.allocation_drag.epoch;
|
||||
let show = Message::TextWorkspace(TextEvent::ShowDrawer(
|
||||
app.text_drawer_context(),
|
||||
DrawerTab::Status,
|
||||
));
|
||||
send(&mut app, show);
|
||||
assert_eq!(app.text_workspace.drawer(), Some(DrawerTab::Status));
|
||||
assert!(app.pending_diagram_reveal.is_none());
|
||||
assert!(app.diagram_navigation_focus.is_none());
|
||||
assert!(app.allocation_drag.epoch > epoch);
|
||||
let epoch = app.allocation_drag.epoch;
|
||||
let height = app.text_workspace.drawer_height();
|
||||
let resize = crate::views::captured_text_drawer_resize(&app);
|
||||
send(&mut app, resize);
|
||||
assert_eq!(app.text_workspace.drawer_height(), height + 60.);
|
||||
assert!(app.allocation_drag.epoch > epoch);
|
||||
let epoch = app.allocation_drag.epoch;
|
||||
let hide = Message::TextWorkspace(TextEvent::HideDrawer(app.text_drawer_context()));
|
||||
send(&mut app, hide);
|
||||
assert!(app.text_workspace.drawer().is_none());
|
||||
assert!(app.allocation_drag.epoch > epoch);
|
||||
assert_eq!(app.source_workspace.bundle(), raw);
|
||||
assert!(Arc::ptr_eq(&accepted, &app.document.snapshot()));
|
||||
assert!(!app.document.can_undo() && !app.document.can_redo());
|
||||
|
||||
@@ -512,3 +512,45 @@ async fn details_remount_restores_wheel_offset_before_any_rescroll() {
|
||||
assert_eq!(evidence(&app), before);
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn diagnostic_publication_during_rename_keeps_actual_preview_report_passive() {
|
||||
let mut app = paged_fixture();
|
||||
app.publish_source_diagnostics(app.source_diagnostics.clone());
|
||||
let publication = app.text_workspace.diagnostic_publication().unwrap();
|
||||
let before = evidence(&app);
|
||||
let interaction = app.source_symbols.interaction;
|
||||
let camera = app.camera_boundary();
|
||||
let allocation = app.allocation_boundary();
|
||||
let cursor = app.source_editor.cursor();
|
||||
let mut mounted = Mounted::new(&app);
|
||||
let reports = mounted.wheel(&app);
|
||||
assert_eq!(reports.len(), 1);
|
||||
let Message::SourceRename(Event::PreviewScrolled(_, _, _, offset)) = &reports[0] else {
|
||||
panic!("actual wheel must publish the retained preview measurement");
|
||||
};
|
||||
assert!(offset.is_finite() && *offset > 0.);
|
||||
app.publish_source_diagnostics(app.source_diagnostics.clone());
|
||||
let current = app.text_workspace.diagnostic_publication().unwrap();
|
||||
assert_ne!(current.generation(), publication.generation());
|
||||
assert_eq!(current.source(), publication.source());
|
||||
assert!(app.source_annotation_snapshot().is_some());
|
||||
assert!(
|
||||
!app.source_navigation_available(),
|
||||
"rename still owns modal input"
|
||||
);
|
||||
for message in reports.iter().cloned() {
|
||||
let task = app.update(message);
|
||||
assert_eq!(mounted.execute(&app, task).await, 0);
|
||||
}
|
||||
assert_eq!(
|
||||
app.source_rename.form.as_ref().unwrap().preview_offset,
|
||||
*offset
|
||||
);
|
||||
assert_eq!(mounted.offset(&app), offset.round());
|
||||
assert_eq!(app.source_symbols.interaction, interaction);
|
||||
assert!(app.camera_boundary() == camera);
|
||||
assert!(app.allocation_boundary() == allocation);
|
||||
assert_eq!(app.source_editor.cursor(), cursor);
|
||||
assert_eq!(evidence(&app), before);
|
||||
}
|
||||
|
||||
@@ -126,6 +126,20 @@ impl App {
|
||||
&& self.with_source_input(|input| snapshot.matches(input))
|
||||
}
|
||||
|
||||
/// Retained rows remain readable after departure, but are current only while
|
||||
/// their captured source bytes still match the complete workspace.
|
||||
pub fn source_results_current(&self) -> bool {
|
||||
self.source_symbols
|
||||
.results
|
||||
.as_ref()
|
||||
.is_some_and(|results| results.stamp.snapshot == self.source_key())
|
||||
&& self
|
||||
.source_symbols
|
||||
.cache
|
||||
.as_ref()
|
||||
.is_some_and(|cache| self.source_snapshot_matches(cache))
|
||||
}
|
||||
|
||||
pub fn source_navigation_available(&self) -> bool {
|
||||
self.page == Page::Text
|
||||
&& !self.source_rename.open()
|
||||
|
||||
@@ -74,190 +74,6 @@ fn select_effective_pump(app: &mut App, line: bool) -> String {
|
||||
selected
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn effective_selection_survives_file_roundtrip_and_definition_back() {
|
||||
for line in [false, true] {
|
||||
for definition in [false, true] {
|
||||
let mut app = fixture();
|
||||
let selected = select_effective_pump(&mut app, line);
|
||||
let accepted = app.document.snapshot();
|
||||
let bundle = app.source_workspace.bundle();
|
||||
let revision = app.source_workspace.revision();
|
||||
if definition {
|
||||
let results = finish(&mut app, query::Action::Definition);
|
||||
assert_eq!(results.disposition, query::Disposition::Direct);
|
||||
reveal(&mut app);
|
||||
assert_eq!(app.source_workspace.active, DEFINITIONS);
|
||||
symbol(&mut app, nav::SymbolEvent::Back);
|
||||
reveal(&mut app);
|
||||
assert!(app.source_symbols.back.is_empty());
|
||||
} else {
|
||||
show(&mut app, DEFINITIONS);
|
||||
show(&mut app, USES);
|
||||
}
|
||||
assert_eq!(app.source_workspace.active, USES);
|
||||
assert_eq!(
|
||||
app.source_editor.selection().as_deref(),
|
||||
Some(selected.as_str())
|
||||
);
|
||||
assert_eq!(app.source_workspace.bundle(), bundle);
|
||||
assert_eq!(app.source_workspace.revision(), revision);
|
||||
assert!(Arc::ptr_eq(&accepted, &app.document.snapshot()));
|
||||
assert!(!app.source_workspace.undo(false) && !app.source_workspace.undo(true));
|
||||
assert!(!app.document.can_undo() && !app.document.can_redo());
|
||||
let expected = if line {
|
||||
bundle.files[USES].replacen(&selected, "x", 1)
|
||||
} else {
|
||||
bundle.files[USES].replacen("source @pump", "source @x", 1)
|
||||
};
|
||||
send(
|
||||
&mut app,
|
||||
Message::SourceEdit(text_editor::Action::Edit(text_editor::Edit::Insert('x'))),
|
||||
);
|
||||
assert_eq!(app.source_workspace.text(), expected);
|
||||
assert_eq!(app.source_editor.text(), expected);
|
||||
assert!(Arc::ptr_eq(&accepted, &app.document.snapshot()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn effective_selection_is_untouched_by_noop_missing_invalid_or_cancelled_navigation() {
|
||||
for variant in 0..4 {
|
||||
let mut app = fixture();
|
||||
let selected = select_effective_pump(&mut app, false);
|
||||
let raw_cursor = app.source_editor.cursor();
|
||||
let bundle = app.source_workspace.bundle();
|
||||
let revision = app.source_workspace.revision();
|
||||
let accepted = app.document.snapshot();
|
||||
match variant {
|
||||
0 => send(&mut app, Message::SourceFile(USES.into())),
|
||||
1 => send(&mut app, Message::SourceFile("missing.arcadia".into())),
|
||||
2 => app.source_symbol_jump(
|
||||
DEFINITIONS,
|
||||
query::Position {
|
||||
line: 999,
|
||||
byte_column: 0,
|
||||
},
|
||||
query::Position {
|
||||
line: 999,
|
||||
byte_column: 1,
|
||||
},
|
||||
),
|
||||
_ => {
|
||||
let request = begin(&mut app, query::Action::Definition);
|
||||
let ready = completion(&app, request);
|
||||
assert_eq!(
|
||||
app.source_editor.selection().as_deref(),
|
||||
Some(selected.as_str())
|
||||
);
|
||||
send(&mut app, Message::SourceFile(USES.into()));
|
||||
send(&mut app, Message::SourceSymbolsReady(ready));
|
||||
reveal(&mut app);
|
||||
}
|
||||
}
|
||||
assert_eq!(app.source_workspace.active, USES);
|
||||
assert_eq!(
|
||||
app.source_editor.cursor(),
|
||||
raw_cursor,
|
||||
"live selection mode must not normalize"
|
||||
);
|
||||
assert_eq!(
|
||||
app.source_editor.selection().as_deref(),
|
||||
Some(selected.as_str())
|
||||
);
|
||||
assert_eq!(app.source_workspace.bundle(), bundle);
|
||||
assert_eq!(app.source_workspace.revision(), revision);
|
||||
assert!(Arc::ptr_eq(&accepted, &app.document.snapshot()));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn changed_active_origin_back_keeps_current_effective_selection() {
|
||||
let mut app = fixture();
|
||||
let old_generation = app.source_workspace.files[USES].generation();
|
||||
let old_cursor = app.source_editor.cursor();
|
||||
let original = app.source_workspace.text().to_owned();
|
||||
app.source_editor
|
||||
.perform(text_editor::Action::Move(text_editor::Motion::Left));
|
||||
app.source_editor.move_to(text_editor::Cursor {
|
||||
position: text_editor::Position { line: 0, column: 3 },
|
||||
selection: None,
|
||||
});
|
||||
send(
|
||||
&mut app,
|
||||
Message::SourceEdit(text_editor::Action::Edit(text_editor::Edit::Insert('x'))),
|
||||
);
|
||||
let mut expected_edit = original;
|
||||
expected_edit.insert(3, 'x');
|
||||
assert_eq!(app.source_workspace.text(), expected_edit);
|
||||
let selected = select_effective_pump(&mut app, true);
|
||||
let bundle = app.source_workspace.bundle();
|
||||
let revision = app.source_workspace.revision();
|
||||
app.source_symbols.back.push(nav::HistoryEntry {
|
||||
session: app.source_workspace.session(),
|
||||
path: USES.into(),
|
||||
generation: old_generation,
|
||||
cursor: old_cursor,
|
||||
});
|
||||
symbol(&mut app, nav::SymbolEvent::Back);
|
||||
reveal(&mut app);
|
||||
assert_eq!(
|
||||
app.source_editor.selection().as_deref(),
|
||||
Some(selected.as_str())
|
||||
);
|
||||
assert!(
|
||||
app.source_symbols
|
||||
.notice
|
||||
.as_deref()
|
||||
.unwrap()
|
||||
.contains("draft changed")
|
||||
);
|
||||
assert_eq!(app.source_workspace.bundle(), bundle);
|
||||
assert_eq!(app.source_workspace.revision(), revision);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn source_creation_preserves_outgoing_selection_and_undisplayed_entry_cursor() {
|
||||
let mut app = fixture();
|
||||
let selected = select_effective_pump(&mut app, true);
|
||||
let bundle = app.source_workspace.bundle();
|
||||
let entry_cursor = app.source_workspace.files[&bundle.entry].cursor;
|
||||
let accepted = app.document.snapshot();
|
||||
send(&mut app, Message::SourceNewPath("new.arcadia".into()));
|
||||
send(&mut app, Message::SourceCreateFile);
|
||||
assert_eq!(app.source_workspace.active, "new.arcadia");
|
||||
assert_eq!(
|
||||
app.source_workspace.files[&bundle.entry].cursor,
|
||||
entry_cursor
|
||||
);
|
||||
assert_eq!(app.source_workspace.files[USES].text, bundle.files[USES]);
|
||||
assert!(
|
||||
app.source_workspace.files[&bundle.entry]
|
||||
.text
|
||||
.contains("include \"new.arcadia\";")
|
||||
);
|
||||
assert!(Arc::ptr_eq(&accepted, &app.document.snapshot()));
|
||||
show(&mut app, USES);
|
||||
assert_eq!(
|
||||
app.source_editor.selection().as_deref(),
|
||||
Some(selected.as_str())
|
||||
);
|
||||
select_effective_pump(&mut app, true);
|
||||
let raw = app.source_editor.cursor();
|
||||
send(
|
||||
&mut app,
|
||||
Message::SourceNewPath("../invalid.arcadia".into()),
|
||||
);
|
||||
send(&mut app, Message::SourceCreateFile);
|
||||
assert_eq!(app.source_workspace.active, USES);
|
||||
assert_eq!(app.source_editor.cursor(), raw);
|
||||
assert_eq!(
|
||||
app.source_editor.selection().as_deref(),
|
||||
Some(selected.as_str())
|
||||
);
|
||||
}
|
||||
|
||||
fn send(app: &mut App, message: Message) {
|
||||
// Dropping the returned Task does not poll its worker/lint future. Completion
|
||||
// below is deterministic and still traverses the real App message handler.
|
||||
@@ -2272,3 +2088,31 @@ fn combined_reference_chooser_blocks_navigation_and_completion_precedes_reconcil
|
||||
assert_eq!(app.source_workspace.bundle(), raw);
|
||||
assert!(Arc::ptr_eq(&accepted, &app.document.snapshot()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn same_key_changed_bytes_mark_retained_rows_stale_before_choice_boundaries() {
|
||||
for path in [USES, DEFINITIONS] {
|
||||
let mut app = fixture();
|
||||
let results = finish(&mut app, query::Action::References);
|
||||
assert!(app.source_results_current());
|
||||
#[cfg(debug_assertions)]
|
||||
assert!(!crate::views::captured_source_reference_stale_summary(&app));
|
||||
let captured = choice(&app, results.stamp, true, 0);
|
||||
let key = app.source_key();
|
||||
// Isolate byte authority from revision authority in both the displayed
|
||||
// and an inactive buffer. Views share this exact currentness decision.
|
||||
app.source_workspace
|
||||
.files
|
||||
.get_mut(path)
|
||||
.unwrap()
|
||||
.text
|
||||
.push_str("\r\n// exact bytes changed");
|
||||
assert_eq!(app.source_key(), key);
|
||||
assert!(!app.source_results_current());
|
||||
#[cfg(debug_assertions)]
|
||||
assert!(crate::views::captured_source_reference_stale_summary(&app));
|
||||
app.queue_source_reveal(app.source_editor.cursor(), false);
|
||||
assert!(app.source_reveal_ticket().is_some());
|
||||
assert_stale_envelopes_preserve_current(&mut app, &[captured]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -399,6 +399,24 @@ impl App {
|
||||
))
|
||||
})
|
||||
}
|
||||
pub(crate) fn source_annotation_snapshot(
|
||||
&self,
|
||||
) -> Option<crate::source_annotations::Snapshot<'_>> {
|
||||
let publication = self.text_workspace.diagnostic_publication?;
|
||||
let buffer = self
|
||||
.source_workspace
|
||||
.files
|
||||
.get(&self.source_workspace.active)?;
|
||||
if publication.source != self.source_key() || buffer.text != self.source_snapshot.as_ref() {
|
||||
return None;
|
||||
}
|
||||
Some(crate::source_annotations::Snapshot {
|
||||
publication: publication.generation,
|
||||
source: self.source_snapshot.clone(),
|
||||
path: &self.source_workspace.active,
|
||||
diagnostics: &self.source_diagnostics,
|
||||
})
|
||||
}
|
||||
fn text_command_action(&self, command: Command) -> Option<Action> {
|
||||
// Menu has been taken before dispatch-time admission. This intentionally
|
||||
// uses navigation admission, not rename admission, for Find/text history.
|
||||
@@ -468,6 +486,7 @@ impl App {
|
||||
let Some(popup_token) = self.menu_sequence.checked_add(1) else {
|
||||
return Task::none();
|
||||
};
|
||||
self.text_presentation_boundary(Event::OpenMenu(request.clone()));
|
||||
self.source_interaction();
|
||||
let commands = [
|
||||
Command::Find,
|
||||
@@ -513,6 +532,7 @@ impl App {
|
||||
.as_ref()
|
||||
.is_some_and(|menu| menu.token == token)
|
||||
{
|
||||
self.text_presentation_boundary(Event::DismissMenu(token));
|
||||
self.text_workspace.menu = None;
|
||||
self.source_interaction();
|
||||
}
|
||||
@@ -551,12 +571,14 @@ impl App {
|
||||
if context != self.text_drawer_context() || !self.source_navigation_available() {
|
||||
return Task::none();
|
||||
}
|
||||
self.text_presentation_boundary(Event::ShowDrawer(context, tab));
|
||||
return self.show_text_drawer(Some(tab));
|
||||
}
|
||||
Event::HideDrawer(context) => {
|
||||
if context != self.text_drawer_context() || !self.source_navigation_available() {
|
||||
return Task::none();
|
||||
}
|
||||
self.text_presentation_boundary(Event::HideDrawer(context));
|
||||
return self.show_text_drawer(None);
|
||||
}
|
||||
Event::ResizeDrawer(context, height) => {
|
||||
@@ -567,15 +589,22 @@ impl App {
|
||||
// The view applies the measured editor-allocation bound.
|
||||
let height = height.clamp(180., 1200.);
|
||||
if height != self.text_workspace.drawer_height {
|
||||
self.text_presentation_boundary(Event::ResizeDrawer(context, height));
|
||||
self.source_interaction();
|
||||
self.text_workspace.drawer_height = height;
|
||||
}
|
||||
}
|
||||
}
|
||||
Event::ToggleFile { stamp, visit, path } => {
|
||||
let captured = Event::ToggleFile {
|
||||
stamp,
|
||||
visit,
|
||||
path: path.clone(),
|
||||
};
|
||||
if self.source_navigation_available()
|
||||
&& self.text_workspace.toggle_file(stamp, visit, path)
|
||||
{
|
||||
self.text_presentation_boundary(captured);
|
||||
self.source_interaction();
|
||||
}
|
||||
}
|
||||
@@ -598,6 +627,7 @@ impl App {
|
||||
{
|
||||
return Task::none();
|
||||
}
|
||||
self.text_presentation_boundary(Event::Diagnostic(target.clone()));
|
||||
self.source_interaction();
|
||||
self.jump_to_source(target.location);
|
||||
}
|
||||
@@ -607,9 +637,22 @@ impl App {
|
||||
fn dispatch_text_action(&mut self, action: Action) -> Task<Message> {
|
||||
match action {
|
||||
Action::Dispatch(message) => self.update(*message),
|
||||
Action::Drawer(tab) => self.show_text_drawer(Some(tab)),
|
||||
Action::Drawer(tab) => {
|
||||
self.text_presentation_boundary(Event::ShowDrawer(self.text_drawer_context(), tab));
|
||||
self.show_text_drawer(Some(tab))
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Local Text presentation changes do not mutate a camera, document or route.
|
||||
/// After their own authority check, they still supersede pending navigation
|
||||
/// and allocation through the root's existing input boundary hooks. Source
|
||||
/// commands use update instead; passive measurements never enter this hook.
|
||||
fn text_presentation_boundary(&mut self, event: Event) {
|
||||
let message = Message::TextWorkspace(event);
|
||||
self.reconcile_pending_diagram_reveal(&message);
|
||||
self.allocation_before(&message);
|
||||
}
|
||||
|
||||
pub(super) fn show_text_drawer(&mut self, drawer: Option<DrawerTab>) -> Task<Message> {
|
||||
self.source_interaction();
|
||||
self.text_workspace.show_drawer(drawer);
|
||||
|
||||
@@ -670,3 +670,208 @@ fn queued_group_toggle_cannot_mutate_a_reopened_or_rotated_visit() {
|
||||
assert!(!app.document.can_undo() && !app.document.can_redo());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn annotation_snapshot_requires_exact_current_bytes_and_publication() {
|
||||
let mut app = fixture();
|
||||
let original_model = app.document.snapshot();
|
||||
let original_source = app.source_snapshot.clone();
|
||||
let publication = app.source_annotation_snapshot().unwrap().publication;
|
||||
let initial = app.source_annotation_snapshot().unwrap();
|
||||
assert!(Arc::ptr_eq(&initial.source, &original_source));
|
||||
assert_eq!(initial.path, app.source_workspace.active);
|
||||
assert!(std::ptr::eq(
|
||||
initial.diagnostics,
|
||||
app.source_diagnostics.as_slice()
|
||||
));
|
||||
|
||||
// Republish identical diagnostics: old cached geometry must get a new identity.
|
||||
app.publish_source_diagnostics(app.source_diagnostics.clone());
|
||||
assert_ne!(
|
||||
app.source_annotation_snapshot().unwrap().publication,
|
||||
publication
|
||||
);
|
||||
|
||||
app.source_workspace
|
||||
.edit("// newer draft\n".into(), app.source_editor.cursor());
|
||||
assert!(
|
||||
app.source_annotation_snapshot().is_none(),
|
||||
"old publication after a real draft edit"
|
||||
);
|
||||
// A current source key alone must not bind new ranges to the old displayed text.
|
||||
app.publish_source_diagnostics(Vec::new());
|
||||
assert!(
|
||||
app.source_annotation_snapshot().is_none(),
|
||||
"displayed snapshot still contains old bytes"
|
||||
);
|
||||
app.show_source_file(false);
|
||||
let current = app.source_annotation_snapshot().unwrap();
|
||||
assert_eq!(current.source.as_ref(), "// newer draft\n");
|
||||
assert!(current.diagnostics.is_empty());
|
||||
assert!(Arc::ptr_eq(&original_model, &app.document.snapshot()));
|
||||
app.text_workspace.reset();
|
||||
assert!(app.source_annotation_snapshot().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn annotation_snapshot_tracks_real_file_round_trips_without_changing_model() {
|
||||
let mut project = Project::empty();
|
||||
let element = project
|
||||
.add_element(
|
||||
oa_core::Kind::Activity,
|
||||
oa_core::Layer::Oa,
|
||||
"Assess incident",
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
let mut app = App::with_project(project);
|
||||
app.page = Page::Text;
|
||||
app.publish_source_diagnostics(app.source_diagnostics.clone());
|
||||
let model = app.document.snapshot();
|
||||
let entry = app.source_workspace.entry.clone();
|
||||
let fragment = app.document.compiled().index.declarations[&element]
|
||||
.path
|
||||
.clone();
|
||||
assert_ne!(entry, fragment);
|
||||
for path in [&entry, &fragment, &entry] {
|
||||
let _ = app.update(Message::SourceFile(path.clone()));
|
||||
let current = app.source_annotation_snapshot().unwrap();
|
||||
assert_eq!(current.path, path);
|
||||
assert_eq!(
|
||||
current.source.as_ref(),
|
||||
app.source_workspace.files[path].text
|
||||
);
|
||||
assert!(Arc::ptr_eq(¤t.source, &app.source_snapshot));
|
||||
assert!(Arc::ptr_eq(&model, &app.document.snapshot()));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn drawer_document_session_and_visit_guards_are_independently_required() {
|
||||
for component in 0..3 {
|
||||
for action in 0..3 {
|
||||
let mut app = fixture();
|
||||
app.text_workspace.show_drawer(Some(DrawerTab::Status));
|
||||
let mut captured = app.text_drawer_context();
|
||||
match component {
|
||||
0 => captured.document = Id::new_v4(),
|
||||
1 => captured.session = Id::new_v4(),
|
||||
_ => captured.visit = Id::new_v4(),
|
||||
}
|
||||
app.queue_source_reveal(app.source_editor.cursor(), false);
|
||||
let reveal = app.source_reveal_ticket();
|
||||
assert!(reveal.is_some());
|
||||
let interaction = app.source_symbols.interaction;
|
||||
let cursor = app.source_editor.cursor();
|
||||
let accepted = app.document.snapshot();
|
||||
let raw = app.source_workspace.bundle();
|
||||
let current = app.text_drawer_context();
|
||||
let height = app.text_workspace.drawer_height();
|
||||
let event = match action {
|
||||
0 => Event::ShowDrawer(captured, DrawerTab::Problems),
|
||||
1 => Event::HideDrawer(captured),
|
||||
_ => Event::ResizeDrawer(captured, height + 60.),
|
||||
};
|
||||
send(&mut app, event);
|
||||
assert_eq!(app.text_drawer_context(), current);
|
||||
assert_eq!(app.text_workspace.drawer(), Some(DrawerTab::Status));
|
||||
assert_eq!(app.text_workspace.drawer_height(), height);
|
||||
assert_eq!(app.source_reveal_ticket(), reveal);
|
||||
assert_eq!(app.source_symbols.interaction, interaction);
|
||||
assert_eq!(app.source_editor.cursor(), cursor);
|
||||
assert_eq!(app.source_workspace.bundle(), raw);
|
||||
assert!(Arc::ptr_eq(&accepted, &app.document.snapshot()));
|
||||
assert!(!app.document.can_undo() && !app.document.can_redo());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
#[test]
|
||||
fn actual_chooser_blocks_diagnostic_actions_and_mounted_hover_until_closed() {
|
||||
use oa_core::{language::Diagnostic, source_project::SourceDiagnostic, validation::Severity};
|
||||
for republish in [false, true] {
|
||||
let mut app = fixture();
|
||||
assert!(app.source_workspace.text().starts_with("arcadia"));
|
||||
app.publish_source_diagnostics(vec![SourceDiagnostic {
|
||||
path: app.source_workspace.active.clone(),
|
||||
diagnostic: Diagnostic {
|
||||
severity: Severity::Warning,
|
||||
code: "union".into(),
|
||||
message: "Union diagnostic details".into(),
|
||||
object_id: None,
|
||||
field: None,
|
||||
line: 1,
|
||||
column: 1,
|
||||
span: Span { start: 0, end: 7 },
|
||||
},
|
||||
related: Vec::new(),
|
||||
}]);
|
||||
let show = Event::ShowDrawer(app.text_drawer_context(), DrawerTab::Problems);
|
||||
send(&mut app, show);
|
||||
let old_target = app.text_diagnostic_message(location(&app, 0, 7)).unwrap();
|
||||
let mut mounted = crate::source_annotations::AppMounted::new(
|
||||
app.source_annotation_snapshot().unwrap(),
|
||||
app.source_navigation_available(),
|
||||
app.source_symbols.raw_interaction.clone(),
|
||||
);
|
||||
mounted.assert_hover(true);
|
||||
let open = app.diagram_usage_message(app.project().id);
|
||||
let _ = app.update(open);
|
||||
let token = app
|
||||
.diagram_usages
|
||||
.as_ref()
|
||||
.expect("actual chooser opened")
|
||||
.token;
|
||||
assert!(!app.source_navigation_available());
|
||||
let accepted = app.document.snapshot();
|
||||
let raw = app.source_workspace.bundle();
|
||||
let cursor = app.source_editor.cursor();
|
||||
let interaction = app.source_symbols.interaction;
|
||||
let reveal = app.source_reveal_ticket();
|
||||
if republish {
|
||||
app.publish_source_diagnostics(app.source_diagnostics.clone());
|
||||
}
|
||||
mounted.refresh(
|
||||
app.source_annotation_snapshot().unwrap(),
|
||||
app.source_navigation_available(),
|
||||
);
|
||||
mounted.assert_hover(false);
|
||||
let _ = app.update(old_target.clone());
|
||||
assert_eq!(app.source_symbols.interaction, interaction);
|
||||
assert_eq!(app.source_reveal_ticket(), reveal);
|
||||
assert_eq!(app.diagram_usages.as_ref().unwrap().token, token);
|
||||
assert_eq!(app.diagram_lookup_in_flight, Some(token));
|
||||
assert_eq!(app.source_editor.cursor(), cursor);
|
||||
assert_eq!(app.source_workspace.bundle(), raw);
|
||||
assert!(Arc::ptr_eq(&accepted, &app.document.snapshot()));
|
||||
let _ = app.update(Message::DiagramUsage(crate::diagram_usage::Event::Close(
|
||||
token,
|
||||
)));
|
||||
assert!(app.source_navigation_available());
|
||||
mounted.refresh(
|
||||
app.source_annotation_snapshot().unwrap(),
|
||||
app.source_navigation_available(),
|
||||
);
|
||||
mounted.assert_hover(true);
|
||||
if republish {
|
||||
let interaction = app.source_symbols.interaction;
|
||||
let reveal = app.source_reveal_ticket();
|
||||
let _ = app.update(old_target);
|
||||
assert_eq!(
|
||||
app.source_symbols.interaction, interaction,
|
||||
"old publication stays stale after modal closes"
|
||||
);
|
||||
assert_eq!(app.source_reveal_ticket(), reveal);
|
||||
}
|
||||
let target = app.text_diagnostic_message(location(&app, 0, 7)).unwrap();
|
||||
let _ = app.update(target);
|
||||
assert!(
|
||||
app.source_reveal_ticket().is_some(),
|
||||
"fresh admitted location still navigates"
|
||||
);
|
||||
assert_eq!(app.source_workspace.bundle(), raw);
|
||||
assert!(Arc::ptr_eq(&accepted, &app.document.snapshot()));
|
||||
assert!(!app.document.can_undo() && !app.document.can_redo());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user