Files
ARCADIA-Studio/crates/oa-desktop/src/app/text_workspace/tests.rs
T

878 lines
34 KiB
Rust

use super::*;
use crate::app::Page;
use oa_core::{Project, language::Span};
use std::sync::Arc;
fn fixture() -> App {
let mut app = App::with_project(Project::empty());
app.page = Page::Text;
let diagnostics = app.source_diagnostics.clone();
app.publish_source_diagnostics(diagnostics);
assert!(app.source_navigation_available());
app
}
fn send(app: &mut App, event: Event) {
// Exercise the real envelope path; worker/lint futures are deliberately not
// polled here. The parent owns serialized compile/runtime verification.
let _ = app.update(Message::TextWorkspace(event));
}
fn toggle(app: &App, stamp: QueryStamp, path: String) -> Event {
Event::ToggleFile {
stamp,
visit: app.text_workspace.drawer_visit(),
path,
}
}
fn open(app: &mut App) -> Id {
let message = app.text_menu_message(Point::new(10., 20.));
let _ = app.update(message);
app.text_workspace.menu().unwrap().token()
}
fn invoke(app: &mut App, token: Id, command: Command) {
send(app, Event::Invoke { token, command });
}
fn enabled(app: &App, command: Command) -> bool {
app.text_workspace
.menu
.as_ref()
.unwrap()
.entries
.iter()
.find(|entry| entry.command == command)
.is_some_and(|entry| entry.action.is_some())
}
fn back_entry(app: &App) -> nav::HistoryEntry {
nav::HistoryEntry {
session: app.source_workspace.session(),
path: app.source_workspace.active.clone(),
generation: app.source_workspace.files[&app.source_workspace.active].generation(),
cursor: app.source_editor.cursor(),
}
}
fn stamp(app: &App, serial: u64) -> QueryStamp {
QueryStamp {
snapshot: app.source_key(),
serial,
}
}
fn location(app: &App, start: usize, end: usize) -> SourceLocation {
SourceLocation {
path: app.source_workspace.active.clone(),
span: Span { start, end },
}
}
#[test]
fn menu_own_open_invalidation_does_not_stale_find_and_invoke_is_once() {
let mut app = fixture();
let old_interaction = app.source_symbols.interaction;
let token = open(&mut app);
assert_ne!(old_interaction, app.source_symbols.interaction);
assert_eq!(
app.text_workspace.menu.as_ref().unwrap().context,
app.text_command_context()
);
assert!(enabled(&app, Command::Find));
invoke(&mut app, token, Command::Find);
assert!(app.source_navigation.open);
assert!(app.text_workspace.menu().is_none());
let instance = app.source_find_instance;
invoke(&mut app, token, Command::Find);
assert_eq!(
app.source_find_instance, instance,
"replay must not reopen Find"
);
}
#[test]
fn disabled_entry_cannot_become_a_new_action_under_the_same_menu_token() {
let mut app = fixture();
let token = open(&mut app);
assert!(!enabled(&app, Command::Back));
app.source_symbols.back.push(back_entry(&app));
assert!(
!enabled(&app, Command::Back),
"frozen presentation must remain disabled"
);
invoke(&mut app, token, Command::Back);
assert_eq!(
app.source_symbols.back.len(),
1,
"a newly enabled action cannot be substituted"
);
assert!(app.source_symbols.reveal.is_none());
}
#[test]
fn enabled_entry_is_readmitted_at_dispatch_and_blocked_by_busy() {
let mut app = fixture();
let token = open(&mut app);
assert!(enabled(&app, Command::Find));
app.busy = true;
assert!(enabled(&app, Command::Find));
invoke(&mut app, token, Command::Find);
assert!(!app.source_navigation.open);
assert!(app.text_workspace.menu().is_none());
}
#[test]
fn old_dismiss_and_invoke_do_not_consume_a_newer_menu() {
let mut app = fixture();
let old = open(&mut app);
send(&mut app, Event::DismissMenu(old));
let new = open(&mut app);
send(&mut app, Event::DismissMenu(old));
invoke(&mut app, old, Command::Find);
assert_eq!(app.text_workspace.menu().unwrap().token(), new);
assert!(!app.source_navigation.open);
}
#[test]
fn menu_source_caret_document_path_and_interaction_changes_refuse_stale_find() {
for change in 0..6 {
let mut app = fixture();
let token = open(&mut app);
match change {
0 => {
let value = format!("{}\n// changed", app.source_workspace.text());
app.source_workspace.edit(value, app.source_editor.cursor());
}
1 => {
let mut cursor = app.source_editor.cursor();
cursor.position.column += 1;
app.source_editor.move_to(cursor);
}
2 => app.canvas_document = Id::new_v4(),
3 => app.source_workspace.active = "other.arcadia".into(),
4 => app.source_symbols.interaction = Id::new_v4(),
_ => {
let mut bundle = app.document.bundle().clone();
bundle
.files
.get_mut(&bundle.entry)
.unwrap()
.push_str("\n// accepted revision");
assert!(app.document.apply_bundle(bundle).unwrap());
}
}
invoke(&mut app, token, Command::Find);
assert!(!app.source_navigation.open, "stale context case {change}");
}
}
#[test]
fn source_and_property_drafts_keep_find_and_text_undo_without_model_application() {
let mut app = fixture();
let accepted = app.document.bundle().clone();
let revision = app.document.revision();
let pending_name = "Pending property draft";
// Use the actual property input route before source edits coexist. Changing
// Draft::value alone is overwritten by its authoritative raw input buffer.
let _ = app.update(Message::Field("/name".into(), pending_name.into()));
let assert_pending_property = |app: &App| {
let draft = app.draft.as_ref().unwrap();
assert!(draft.dirty());
assert_eq!(draft.inputs["/name"].raw, pending_name);
assert_eq!(draft.inputs["/name"].editor.text(), pending_name);
assert_eq!(
draft.materialize().unwrap()["name"].as_str(),
Some(pending_name)
);
};
assert_pending_property(&app);
let initial = app.source_workspace.text().to_owned();
app.source_workspace
.edit(format!("{initial}\n// draft"), app.source_editor.cursor());
app.show_source_file(false);
assert!(app.source_dirty() && app.draft_dirty());
let token = open(&mut app);
assert!(enabled(&app, Command::Find) && enabled(&app, Command::UndoText));
assert!(!enabled(&app, Command::RenameAlias) && !enabled(&app, Command::UndoModel));
invoke(&mut app, token, Command::Find);
assert!(app.source_navigation.open);
assert_pending_property(&app);
let token = open(&mut app);
invoke(&mut app, token, Command::UndoText);
assert_eq!(app.source_workspace.text(), initial);
assert_pending_property(&app);
assert_eq!(app.document.revision(), revision);
assert_eq!(app.document.bundle(), &accepted);
}
#[test]
fn queued_toolbar_request_cannot_retarget_after_source_change() {
let mut app = fixture();
let request = app.text_command_message(Command::Find);
app.source_workspace
.edit("// changed".into(), app.source_editor.cursor());
let _ = app.update(request);
assert!(!app.source_navigation.open);
}
#[test]
fn equal_diagnostic_republication_invalidates_primary_and_related_targets() {
let mut app = fixture();
{
let captured = Event::ShowDrawer(app.text_drawer_context(), DrawerTab::Problems);
send(&mut app, captured);
};
let primary = app.text_diagnostic_message(location(&app, 0, 1)).unwrap();
let related = app.text_diagnostic_message(location(&app, 1, 2)).unwrap();
let previous = app.text_workspace.diagnostic_publication().unwrap();
app.publish_source_diagnostics(app.source_diagnostics.clone());
let current = app.text_workspace.diagnostic_publication().unwrap();
assert_eq!(previous.source(), current.source());
assert_ne!(previous.generation(), current.generation());
for message in [primary, related] {
let interaction = app.source_symbols.interaction;
let _ = app.update(message);
assert_eq!(app.source_symbols.interaction, interaction);
assert!(app.source_symbols.reveal.is_none());
}
}
#[test]
fn current_diagnostic_target_navigates_but_changed_source_or_clear_refuses_it() {
let mut app = fixture();
{
let captured = Event::ShowDrawer(app.text_drawer_context(), DrawerTab::Problems);
send(&mut app, captured);
};
let message = app.text_diagnostic_message(location(&app, 0, 1)).unwrap();
let _ = app.update(message);
assert!(app.source_symbols.reveal.is_some());
app.source_interaction();
let message = app.text_diagnostic_message(location(&app, 0, 1)).unwrap();
app.source_workspace
.edit("// newer".into(), app.source_editor.cursor());
let _ = app.update(message);
assert!(app.source_symbols.reveal.is_none());
assert!(app.text_diagnostic_message(location(&app, 0, 1)).is_none());
app.publish_source_diagnostics(Vec::new());
let message = app.text_diagnostic_message(location(&app, 0, 1)).unwrap();
app.publish_source_diagnostics(Vec::new());
let _ = app.update(message);
assert!(app.source_symbols.reveal.is_none());
}
#[test]
fn diagnostic_span_must_be_loaded_ordered_and_utf8_exact() {
let mut app = fixture();
{
let captured = Event::ShowDrawer(app.text_drawer_context(), DrawerTab::Problems);
send(&mut app, captured);
};
app.source_workspace
.edit("a雨b".into(), app.source_editor.cursor());
app.show_source_file(false);
app.publish_source_diagnostics(Vec::new());
let mut missing = location(&app, 0, 1);
missing.path = "missing.arcadia".into();
for target in [
location(&app, 3, 2),
location(&app, 0, 6),
location(&app, 2, 4),
location(&app, 1, 3),
missing,
] {
let message = app.text_diagnostic_message(target).unwrap();
let _ = app.update(message);
assert!(app.source_symbols.reveal.is_none());
}
let message = app.text_diagnostic_message(location(&app, 1, 4)).unwrap();
let _ = app.update(message);
assert!(app.source_symbols.reveal.is_some());
}
#[test]
fn reference_groups_and_scroll_survive_tabs_and_reject_old_visits_and_queries() {
let mut app = fixture();
let first = stamp(&app, 1);
app.text_workspace
.begin_references(first, "b/use.arcadia".into());
app.text_workspace
.install_reference_files(first, ["a/use.arcadia".into(), "b/use.arcadia".into()]);
assert!(app.text_workspace.references().collapsed("a/use.arcadia"));
assert!(!app.text_workspace.references().collapsed("b/use.arcadia"));
let event = toggle(&app, first, "a/use.arcadia".into());
send(&mut app, event);
let old_visit = app.text_workspace.drawer_visit();
let old_scroll_id = app.text_workspace.reference_scroll_id();
let interaction = app.source_symbols.interaction;
send(
&mut app,
Event::ReferenceScrolled {
stamp: first,
visit: old_visit,
y: 123.,
},
);
assert_eq!(
app.source_symbols.interaction, interaction,
"scroll is passive"
);
{
let captured = Event::ShowDrawer(app.text_drawer_context(), DrawerTab::Status);
send(&mut app, captured);
};
send(
&mut app,
Event::ReferenceScrolled {
stamp: first,
visit: old_visit,
y: 0.,
},
);
{
let captured = Event::ShowDrawer(app.text_drawer_context(), DrawerTab::References);
send(&mut app, captured);
};
assert_eq!(app.text_workspace.references().scroll_y(), 123.);
assert!(!app.text_workspace.references().collapsed("a/use.arcadia"));
send(
&mut app,
Event::ReferenceScrolled {
stamp: first,
visit: old_visit,
y: 0.,
},
);
assert_eq!(app.text_workspace.references().scroll_y(), 123.);
assert_ne!(app.text_workspace.reference_scroll_id(), old_scroll_id);
let second = stamp(&app, 2);
app.text_workspace
.begin_references(second, "absent.arcadia".into());
app.text_workspace
.install_reference_files(first, ["old.arcadia".into()]);
app.text_workspace
.install_reference_files(second, ["b/use.arcadia".into(), "a/use.arcadia".into()]);
assert_eq!(app.text_workspace.references().scroll_y(), 0.);
assert!(!app.text_workspace.references().collapsed("a/use.arcadia"));
assert!(app.text_workspace.references().collapsed("b/use.arcadia"));
let event = toggle(&app, first, "b/use.arcadia".into());
send(&mut app, event);
assert!(app.text_workspace.references().collapsed("b/use.arcadia"));
}
#[test]
fn reset_removes_ui_and_publication_authority_and_resize_rejects_nan() {
let mut app = fixture();
open(&mut app);
app.text_workspace.reset();
assert!(app.text_workspace.menu().is_none());
assert!(app.text_workspace.diagnostic_publication().is_none());
assert!(app.text_workspace.drawer().is_none());
{
let captured = Event::ResizeDrawer(app.text_drawer_context(), f32::NAN);
send(&mut app, captured);
};
assert_eq!(app.text_workspace.drawer_height(), 180.);
{
let captured = Event::ResizeDrawer(app.text_drawer_context(), f32::MAX);
send(&mut app, captured);
};
assert_eq!(app.text_workspace.drawer_height(), 1200.);
}
#[test]
fn only_valid_explicit_group_and_resize_intent_cancel_pending_navigation() {
let mut app = fixture();
let current = stamp(&app, 1);
app.text_workspace
.begin_references(current, "uses.arcadia".into());
app.text_workspace
.install_reference_files(current, ["uses.arcadia".into()]);
let request = nav::Request {
job: Id::new_v4(),
interaction: app.source_symbols.interaction,
raw_interaction: 0,
stamp: current,
path: app.source_workspace.active.clone(),
offset: 0,
cursor: app.source_editor.cursor(),
action: nav::query::Action::References,
};
app.source_symbols.request = Some(request.clone());
app.source_symbols.in_flight = Some(request.job);
let interaction = app.source_symbols.interaction;
let event = toggle(&app, current, "not-a-group.arcadia".into());
send(&mut app, event);
let old = QueryStamp {
serial: 0,
..current
};
let event = toggle(&app, old, "uses.arcadia".into());
send(&mut app, event);
{
let captured = Event::ResizeDrawer(app.text_drawer_context(), f32::NAN);
send(&mut app, captured);
};
{
let captured = Event::ResizeDrawer(app.text_drawer_context(), 180.);
send(&mut app, captured);
};
assert_eq!(app.source_symbols.interaction, interaction);
assert!(app.source_symbols.request.is_some());
let event = toggle(&app, current, "uses.arcadia".into());
send(&mut app, event);
assert!(app.source_symbols.request.is_none());
assert_ne!(app.source_symbols.interaction, interaction);
assert_eq!(
app.source_symbols.in_flight,
Some(request.job),
"intent does not release an occupied worker"
);
app.source_symbols.request = Some(request.clone());
{
let captured = Event::ResizeDrawer(app.text_drawer_context(), 240.);
send(&mut app, captured);
};
assert!(app.source_symbols.request.is_none());
app.source_symbols.request = Some(request.clone());
{
let captured = Event::HideDrawer(app.text_drawer_context());
send(&mut app, captured);
};
assert!(app.source_symbols.request.is_none());
assert_eq!(app.source_symbols.in_flight, Some(request.job));
assert_eq!(app.text_workspace.references().stamp(), Some(current));
}
#[test]
fn stale_retained_group_can_expand_without_navigating_or_applying_drafts() {
let mut app = fixture();
let current = stamp(&app, 1);
let path = app.source_workspace.active.clone();
app.text_workspace.begin_references(current, path.clone());
app.text_workspace
.install_reference_files(current, [path.clone()]);
let event = toggle(&app, current, path.clone());
send(&mut app, event);
assert!(app.text_workspace.references().collapsed(&path));
let accepted = app.document.snapshot();
let _ = app.update(Message::SourceEdit(widget::text_editor::Action::Edit(
widget::text_editor::Edit::Insert(' '),
)));
assert_ne!(current.snapshot, app.source_key());
let raw = app.source_workspace.bundle();
let cursor = app.source_editor.cursor();
let event = toggle(&app, current, path.clone());
send(&mut app, event);
assert!(!app.text_workspace.references().collapsed(&path));
assert_eq!(app.source_workspace.active, path);
assert_eq!(app.source_workspace.bundle(), raw);
assert_eq!(app.source_editor.cursor(), cursor);
assert!(app.source_symbols.reveal.is_none() && app.source_symbols.back.is_empty());
assert!(std::sync::Arc::ptr_eq(&accepted, &app.document.snapshot()));
}
#[test]
fn document_reset_preserves_popup_token_sequence_and_drops_old_menu_authority() {
let mut app = fixture();
let old = open(&mut app);
let sequence = app.text_workspace_menu().unwrap().token;
app.reset(Project::empty(), None);
assert!(app.text_workspace.menu().is_none());
app.page = Page::Text;
let fresh = open(&mut app);
assert_ne!(old, fresh);
assert!(app.text_workspace_menu().unwrap().token > sequence);
invoke(&mut app, old, Command::Find);
assert!(!app.source_navigation.open);
assert_eq!(app.text_workspace.menu().unwrap().token(), fresh);
}
#[test]
fn retained_primary_and_related_targets_cannot_replay_after_hide_or_problems_references_problems() {
for hide in [false, true] {
let mut app = fixture();
{
let captured = Event::ShowDrawer(app.text_drawer_context(), DrawerTab::Problems);
send(&mut app, captured);
};
let old_visit = app.text_workspace.drawer_visit();
let publication = app.text_workspace.diagnostic_publication();
let targets = [location(&app, 0, 1), location(&app, 1, 2)];
let old = targets
.iter()
.cloned()
.map(|target| app.text_diagnostic_message(target).unwrap())
.collect::<Vec<_>>();
let accepted = app.document.snapshot();
let raw = app.source_workspace.bundle();
let history = (
app.document.revision(),
app.document.can_undo(),
app.document.can_redo(),
app.document.dirty(),
);
let event = if hide {
Event::HideDrawer(app.text_drawer_context())
} else {
Event::ShowDrawer(app.text_drawer_context(), DrawerTab::References)
};
send(&mut app, event);
for reopened in [false, true] {
if reopened {
{
let captured =
Event::ShowDrawer(app.text_drawer_context(), DrawerTab::Problems);
send(&mut app, captured);
};
} else {
assert!(app.text_diagnostic_message(targets[0].clone()).is_none());
}
assert_ne!(app.text_workspace.drawer_visit(), old_visit);
assert_eq!(
app.text_workspace.diagnostic_publication(),
publication,
"same list and source isolate the visit guard from publication freshness"
);
let context = app.text_command_context();
let drawer = app.text_workspace.drawer();
let visit = app.text_workspace.drawer_visit();
for message in old.iter().cloned() {
let _ = app.update(message);
assert_eq!(
app.text_command_context(),
context,
"stale diagnostic must fail before interaction invalidation"
);
assert_eq!(app.text_workspace.drawer(), drawer);
assert_eq!(app.text_workspace.drawer_visit(), visit);
assert!(app.source_symbols.reveal.is_none() && app.source_jump.is_none());
assert!(app.source_symbols.back.is_empty());
assert_eq!(app.source_workspace.bundle(), raw);
assert!(Arc::ptr_eq(&accepted, &app.document.snapshot()));
assert_eq!(
(
app.document.revision(),
app.document.can_undo(),
app.document.can_redo(),
app.document.dirty()
),
history
);
}
}
for target in targets {
let fresh = app.text_diagnostic_message(target).unwrap();
let _ = app.update(fresh);
assert!(
app.source_symbols.reveal.is_some(),
"fresh primary and related targets remain usable"
);
app.source_interaction();
}
assert_eq!(app.source_workspace.bundle(), raw);
assert!(Arc::ptr_eq(&accepted, &app.document.snapshot()));
}
}
#[test]
fn queued_group_toggle_cannot_mutate_a_reopened_or_rotated_visit() {
for replacement in 0..3 {
let mut app = fixture();
let current = stamp(&app, 1);
app.text_workspace
.begin_references(current, "a.arcadia".into());
app.text_workspace
.install_reference_files(current, ["a.arcadia".into(), "b.arcadia".into()]);
let old = toggle(&app, current, "a.arcadia".into());
let old_visit = app.text_workspace.drawer_visit();
match replacement {
0 => {
{
let captured = Event::HideDrawer(app.text_drawer_context());
send(&mut app, captured);
};
{
let captured =
Event::ShowDrawer(app.text_drawer_context(), DrawerTab::References);
send(&mut app, captured);
};
}
1 => {
{
let captured =
Event::ShowDrawer(app.text_drawer_context(), DrawerTab::Problems);
send(&mut app, captured);
};
{
let captured =
Event::ShowDrawer(app.text_drawer_context(), DrawerTab::References);
send(&mut app, captured);
};
}
_ => {
let event = toggle(&app, current, "b.arcadia".into());
send(&mut app, event);
}
}
let visit = app.text_workspace.drawer_visit();
assert_ne!(visit, old_visit);
send(
&mut app,
Event::ReferenceScrolled {
stamp: current,
visit,
y: 84.,
},
);
let scroll_id = app.text_workspace.reference_scroll_id();
let request = nav::Request {
job: Id::new_v4(),
interaction: app.source_symbols.interaction,
raw_interaction: 0,
stamp: current,
path: app.source_workspace.active.clone(),
offset: 0,
cursor: app.source_editor.cursor(),
action: nav::query::Action::References,
};
app.source_symbols.request = Some(request.clone());
app.source_symbols.in_flight = Some(request.job);
let groups =
["a.arcadia", "b.arcadia"].map(|path| app.text_workspace.references().collapsed(path));
let context = app.text_command_context();
let accepted = app.document.snapshot();
let raw = app.source_workspace.bundle();
for _ in 0..2 {
send(&mut app, old.clone());
assert_eq!(app.text_workspace.drawer_visit(), visit);
assert_eq!(app.text_workspace.references().scroll_y(), 84.);
assert_eq!(app.text_workspace.reference_scroll_id(), scroll_id);
assert_eq!(app.text_workspace.references().stamp(), Some(current));
assert_eq!(
["a.arcadia", "b.arcadia"]
.map(|path| app.text_workspace.references().collapsed(path)),
groups,
);
assert_eq!(app.text_command_context(), context);
assert_eq!(app.source_symbols.request.as_ref(), Some(&request));
assert_eq!(app.source_symbols.in_flight, Some(request.job));
assert!(app.source_symbols.reveal.is_none() && app.source_jump.is_none());
}
let fresh = toggle(&app, current, "a.arcadia".into());
send(&mut app, fresh);
assert_eq!(
app.text_workspace.references().collapsed("a.arcadia"),
!groups[0]
);
assert_ne!(app.text_workspace.drawer_visit(), visit);
assert_ne!(app.source_symbols.interaction, context.interaction);
assert!(app.source_symbols.request.is_none());
assert_eq!(app.source_symbols.in_flight, Some(request.job));
assert!(app.source_symbols.reveal.is_none() && app.source_jump.is_none());
assert!(app.source_symbols.back.is_empty());
assert_eq!(app.source_workspace.bundle(), raw);
assert!(Arc::ptr_eq(&accepted, &app.document.snapshot()));
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(&current.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());
}
}