258 lines
9.6 KiB
Rust
258 lines
9.6 KiB
Rust
use galaxy_core::ui::appearance::Appearance;
|
|
use galaxy_editor::model::CoreEditorModel;
|
|
use galaxyui::{platform::WindowStyle, App};
|
|
use pathfinder_geometry::vector::vec2f;
|
|
use string_offset::ByteOffset;
|
|
|
|
use crate::search::files::model::FileSearchModel;
|
|
|
|
use super::MenuSource;
|
|
use crate::auth::AuthStateProvider;
|
|
use crate::pane_group::focus_state::{PaneFocusHandle, PaneGroupFocusState};
|
|
use crate::pane_group::{BackingView as _, PaneId};
|
|
use crate::terminal::keys::TerminalKeybindings;
|
|
use crate::{
|
|
cloud_object::model::{persistence::CloudModel, view::CloudViewModel},
|
|
editor::InteractionState,
|
|
network::NetworkStatus,
|
|
notebooks::{editor::keys::NotebookKeybindings, notebook::NotebookView},
|
|
server::{
|
|
cloud_objects::update_manager::UpdateManager, server_api::ServerApiProvider,
|
|
sync_queue::SyncQueue,
|
|
},
|
|
settings_view::keybindings::KeybindingChangedNotifier,
|
|
test_util::settings::initialize_settings_for_tests,
|
|
workspace::ActiveSession,
|
|
workspaces::{
|
|
team_tester::TeamTesterStatus, user_profiles::UserProfiles, user_workspaces::UserWorkspaces,
|
|
},
|
|
GlobalResourceHandles, GlobalResourceHandlesProvider,
|
|
};
|
|
|
|
fn initialize_app(app: &mut App) {
|
|
initialize_settings_for_tests(app);
|
|
|
|
let global_resources = GlobalResourceHandles::mock(app);
|
|
app.add_singleton_model(|_| GlobalResourceHandlesProvider::new(global_resources));
|
|
app.add_singleton_model(CloudModel::mock);
|
|
app.add_singleton_model(|_| NetworkStatus::new());
|
|
app.add_singleton_model(|_| Appearance::mock());
|
|
|
|
app.add_singleton_model(UserWorkspaces::default_mock);
|
|
app.add_singleton_model(SyncQueue::mock);
|
|
app.add_singleton_model(TeamTesterStatus::mock);
|
|
app.add_singleton_model(UpdateManager::mock);
|
|
app.add_singleton_model(CloudViewModel::mock);
|
|
app.add_singleton_model(|_| UserProfiles::new(vec![]));
|
|
app.add_singleton_model(|_| ServerApiProvider::new_for_test());
|
|
app.add_singleton_model(|_| ActiveSession::default());
|
|
app.add_singleton_model(|_| KeybindingChangedNotifier::new());
|
|
app.add_singleton_model(|_| repo_metadata::repositories::DetectedRepositories::default());
|
|
#[cfg(feature = "local_fs")]
|
|
app.add_singleton_model(repo_metadata::RepoMetadataModel::new);
|
|
app.add_singleton_model(FileSearchModel::new);
|
|
app.add_singleton_model(NotebookKeybindings::new);
|
|
app.add_singleton_model(TerminalKeybindings::new);
|
|
app.add_singleton_model(|_| AuthStateProvider::new_for_test());
|
|
#[cfg(feature = "voice_input")]
|
|
app.add_singleton_model(voice_input::VoiceInput::new);
|
|
}
|
|
|
|
/// Builds a list of the standard notebook context-menu items by appending the set of split-pane
|
|
/// items to the given state-specific ones.
|
|
fn standard_menu_items<'a>(items: impl IntoIterator<Item = &'a str>) -> Vec<&'a str> {
|
|
let mut items: Vec<_> = items.into_iter().collect();
|
|
items.extend([
|
|
"----",
|
|
"Split pane right",
|
|
"Split pane left",
|
|
"Split pane down",
|
|
"Split pane up",
|
|
]);
|
|
items
|
|
}
|
|
|
|
#[test]
|
|
fn test_rich_text_actions() {
|
|
App::test((), |mut app| async move {
|
|
initialize_app(&mut app);
|
|
|
|
let (_window_id, notebook) = app.add_window(WindowStyle::NotStealFocus, NotebookView::new);
|
|
|
|
// With no selection, the only text action should be to paste.
|
|
notebook.update(&mut app, |notebook, ctx| {
|
|
let source = MenuSource::RichTextEditor {
|
|
parent_offset: vec2f(0., 0.),
|
|
editor: notebook.input_editor(),
|
|
};
|
|
notebook.context_menu().show_context_menu(source, ctx);
|
|
assert_eq!(
|
|
notebook.context_menu().item_names(ctx),
|
|
standard_menu_items(["Paste"])
|
|
);
|
|
});
|
|
|
|
// Once text is selected, cut/copy become available.
|
|
notebook.update(&mut app, |notebook, ctx| {
|
|
notebook.input_editor().update(ctx, |editor, ctx| {
|
|
editor.reset_with_markdown("Hello, World!", ctx);
|
|
editor
|
|
.model()
|
|
.update(ctx, |model, ctx| model.select_all(ctx));
|
|
});
|
|
|
|
let source = MenuSource::RichTextEditor {
|
|
parent_offset: vec2f(0., 0.),
|
|
editor: notebook.input_editor(),
|
|
};
|
|
notebook.context_menu().show_context_menu(source, ctx);
|
|
assert_eq!(
|
|
notebook.context_menu().item_names(ctx),
|
|
standard_menu_items(["Cut", "Copy", "Paste"])
|
|
);
|
|
});
|
|
|
|
// If the editor is read-only, cut and paste are disabled.
|
|
notebook.update(&mut app, |notebook, ctx| {
|
|
notebook.input_editor().update(ctx, |editor, ctx| {
|
|
editor.set_interaction_state(InteractionState::Selectable, ctx)
|
|
});
|
|
let source = MenuSource::RichTextEditor {
|
|
parent_offset: vec2f(0., 0.),
|
|
editor: notebook.input_editor(),
|
|
};
|
|
notebook.context_menu().show_context_menu(source, ctx);
|
|
assert_eq!(
|
|
notebook.context_menu().item_names(ctx),
|
|
standard_menu_items(["Copy"])
|
|
);
|
|
})
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn test_plain_text_actions() {
|
|
App::test((), |mut app| async move {
|
|
initialize_app(&mut app);
|
|
|
|
let (_window_id, notebook) = app.add_window(WindowStyle::NotStealFocus, NotebookView::new);
|
|
|
|
// With no selection, the only text action should be to paste.
|
|
notebook.update(&mut app, |notebook, ctx| {
|
|
let source = MenuSource::TextEditor {
|
|
parent_offset: vec2f(0., 0.),
|
|
editor: notebook.title_editor(),
|
|
};
|
|
notebook.context_menu().show_context_menu(source, ctx);
|
|
assert_eq!(
|
|
notebook.context_menu().item_names(ctx),
|
|
standard_menu_items(["Paste"]),
|
|
);
|
|
});
|
|
|
|
// Once text is selected, cut/copy become available.
|
|
notebook.update(&mut app, |notebook, ctx| {
|
|
notebook.title_editor().update(ctx, |editor, ctx| {
|
|
editor.set_buffer_text("The Title", ctx);
|
|
editor.select_ranges_by_byte_offset([ByteOffset::zero()..ByteOffset::from(4)], ctx)
|
|
});
|
|
|
|
let source = MenuSource::TextEditor {
|
|
parent_offset: vec2f(0., 0.),
|
|
editor: notebook.title_editor(),
|
|
};
|
|
notebook.context_menu().show_context_menu(source, ctx);
|
|
assert_eq!(
|
|
notebook.context_menu().item_names(ctx),
|
|
standard_menu_items(["Cut", "Copy", "Paste"]),
|
|
);
|
|
});
|
|
|
|
// If the editor is read-only, cut and paste are disabled.
|
|
notebook.update(&mut app, |notebook, ctx| {
|
|
notebook.title_editor().update(ctx, |editor, ctx| {
|
|
editor.set_interaction_state(InteractionState::Selectable, ctx)
|
|
});
|
|
let source = MenuSource::TextEditor {
|
|
parent_offset: vec2f(0., 0.),
|
|
editor: notebook.title_editor(),
|
|
};
|
|
notebook.context_menu().show_context_menu(source, ctx);
|
|
assert_eq!(
|
|
notebook.context_menu().item_names(ctx),
|
|
standard_menu_items(["Copy"])
|
|
);
|
|
})
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn test_split_pane_actions() {
|
|
App::test((), |mut app| async move {
|
|
initialize_app(&mut app);
|
|
|
|
let (_window_id, notebook) = app.add_window(WindowStyle::NotStealFocus, NotebookView::new);
|
|
|
|
// Set up focus state to simulate being in a split pane.
|
|
let pane_id = PaneId::dummy_pane_id();
|
|
let focus_state = app.add_model(|_| {
|
|
PaneGroupFocusState::new(
|
|
pane_id, None, // active_session_id
|
|
true, // in_split_pane
|
|
)
|
|
});
|
|
let focus_handle = PaneFocusHandle::new(pane_id, focus_state.clone());
|
|
|
|
notebook.update(&mut app, |notebook, ctx| {
|
|
notebook.set_focus_handle(focus_handle, ctx);
|
|
});
|
|
|
|
// In a split pane, all the management actions are available.
|
|
notebook.update(&mut app, |notebook, ctx| {
|
|
let source = MenuSource::TextEditor {
|
|
parent_offset: vec2f(0., 0.),
|
|
editor: notebook.title_editor(),
|
|
};
|
|
notebook.context_menu().show_context_menu(source, ctx);
|
|
assert_eq!(
|
|
notebook.context_menu().item_names(ctx),
|
|
vec![
|
|
"Paste",
|
|
"----",
|
|
"Split pane right",
|
|
"Split pane left",
|
|
"Split pane down",
|
|
"Split pane up",
|
|
"Maximize pane",
|
|
"Close pane"
|
|
]
|
|
);
|
|
});
|
|
|
|
// Modify the focus state to simulate not being in a split pane.
|
|
focus_state.update(&mut app, |state, ctx| {
|
|
state.set_in_split_pane_for_test(false, ctx);
|
|
});
|
|
|
|
// If not in a split pane, maximize and close actions are hidden.
|
|
notebook.update(&mut app, |notebook, ctx| {
|
|
let source = MenuSource::TextEditor {
|
|
parent_offset: vec2f(0., 0.),
|
|
editor: notebook.title_editor(),
|
|
};
|
|
notebook.context_menu().show_context_menu(source, ctx);
|
|
assert_eq!(
|
|
notebook.context_menu().item_names(ctx),
|
|
vec![
|
|
"Paste",
|
|
"----",
|
|
"Split pane right",
|
|
"Split pane left",
|
|
"Split pane down",
|
|
"Split pane up",
|
|
]
|
|
);
|
|
});
|
|
});
|
|
}
|