Files
galaxy/app/src/notebooks/file/mod_tests.rs
T

233 lines
8.8 KiB
Rust

use std::{path::Path, sync::Arc};
use pathfinder_geometry::vector::vec2f;
use galaxy_core::ui::appearance::Appearance;
#[cfg(feature = "local_fs")]
use galaxy_files::FileModel;
use galaxyui::{platform::WindowStyle, App, SingletonEntity, View};
#[cfg(feature = "local_fs")]
use repo_metadata::RepoMetadataModel;
use repo_metadata::{repositories::DetectedRepositories, watcher::DirectoryWatcher};
use crate::server::server_api::team::MockTeamClient;
use crate::server::server_api::workspace::MockWorkspaceClient;
use crate::server::telemetry::context_provider::AppTelemetryContextProvider;
use crate::terminal::keys::TerminalKeybindings;
use crate::{
auth::{auth_manager::AuthManager, AuthStateProvider},
cloud_object::model::persistence::CloudModel,
notebooks::{editor::keys::NotebookKeybindings, file::is_markdown_file},
search::files::model::FileSearchModel,
server::server_api::ServerApiProvider,
settings_view::keybindings::KeybindingChangedNotifier,
terminal::model::session::Session,
test_util::settings::initialize_settings_for_tests,
workspace::ActiveSession,
workspaces::user_workspaces::UserWorkspaces,
GlobalResourceHandles, GlobalResourceHandlesProvider,
};
use crate::notebooks::context_menu::MenuSource;
use super::{FileNotebookView, FileState};
fn init_app(app: &mut App) {
initialize_settings_for_tests(app);
let global_resource_handles = GlobalResourceHandles::mock(app);
app.add_singleton_model(|_| GlobalResourceHandlesProvider::new(global_resource_handles));
app.add_singleton_model(|_| Appearance::mock());
app.add_singleton_model(|_| ActiveSession::default());
app.add_singleton_model(|_| KeybindingChangedNotifier::new());
app.add_singleton_model(DirectoryWatcher::new);
app.add_singleton_model(|_| DetectedRepositories::default());
#[cfg(feature = "local_fs")]
app.add_singleton_model(RepoMetadataModel::new);
app.add_singleton_model(FileSearchModel::new);
app.add_singleton_model(FileModel::new);
app.add_singleton_model(NotebookKeybindings::new);
app.add_singleton_model(TerminalKeybindings::new);
app.add_singleton_model(CloudModel::mock);
app.add_singleton_model(|_| ServerApiProvider::new_for_test());
app.add_singleton_model(|_| AuthStateProvider::new_for_test());
app.add_singleton_model(AppTelemetryContextProvider::new_context_provider);
app.add_singleton_model(AuthManager::new_for_test);
let team_client_mock = Arc::new(MockTeamClient::new());
let workspace_client_mock = Arc::new(MockWorkspaceClient::new());
app.add_singleton_model(|ctx| {
UserWorkspaces::mock(
team_client_mock.clone(),
workspace_client_mock.clone(),
vec![],
ctx,
)
});
#[cfg(feature = "voice_input")]
app.add_singleton_model(voice_input::VoiceInput::new);
}
#[test]
fn test_load_local() {
App::test((), |mut app| async move {
init_app(&mut app);
let (_, handle) = app.add_window(WindowStyle::NotStealFocus, FileNotebookView::new);
let session = Arc::new(Session::test());
handle
.update(&mut app, |file_notebook, ctx| {
file_notebook.open_local("../README.md", Some(session), ctx);
let file_id = file_notebook
.file_id
.expect("File should be opened and have a file_id");
let future_handle = FileModel::as_ref(ctx)
.get_future_handle(file_id)
.expect("Loading future should be present");
ctx.await_spawned_future(future_handle.future_id())
})
.await;
app.read(|ctx| {
assert_eq!(&handle.as_ref(ctx).title(), "README.md");
let location = handle
.as_ref(ctx)
.location
.as_ref()
.expect("Location should be set");
assert_eq!(location.breadcrumbs, "..");
let editor = handle.as_ref(ctx).editor.as_ref(ctx);
assert!(!editor.is_editable(ctx));
// We don't want to check the actual README contents, but it should be clearly non-empty.
assert!(editor.markdown(ctx).len() > 4);
// Rendering should not panic.
handle.as_ref(ctx).render(ctx);
});
});
}
#[test]
fn test_load_before_session() {
// There might not be a session if:
// * Restoring a file notebook, since terminal panes won't have bootstrapped yet
// * Only notebooks are open
App::test((), |mut app| async move {
init_app(&mut app);
let (window_id, handle) = app.add_window(WindowStyle::NotStealFocus, FileNotebookView::new);
// Open a file we know exists to verify that the view can render.
handle
.update(&mut app, |file_notebook, ctx| {
file_notebook.open_local("../README.md", None, ctx);
match &file_notebook.file_state {
FileState::Loading(source) => {
assert_eq!(source.local_path(), Some(Path::new("../README.md")))
}
other => panic!("Expected FileState::Loading, got {other:?}"),
}
let file_id = file_notebook
.file_id
.expect("File should be opened and have a file_id");
let future_handle = FileModel::as_ref(ctx)
.get_future_handle(file_id)
.expect("Loading future should be present");
ctx.await_spawned_future(future_handle.future_id())
})
.await;
handle.read(&app, |view, _| {
let expected_path = dunce::canonicalize("../README.md").expect("Path exists");
assert_eq!(view.title(), expected_path.display().to_string());
assert!(view.location.is_none());
match &view.file_state {
FileState::Loaded(source) => {
assert_eq!(source.local_path(), Some(expected_path.as_path()));
}
other => panic!("Expected FileState::Loaded, got {other:?}"),
};
});
// Once a local session is available, the view should use it.
let session = Arc::new(Session::test());
ActiveSession::handle(&app).update(&mut app, |active_session, ctx| {
active_session.set_session_for_test(window_id, session.clone(), Some("."), None, ctx);
});
handle.read(&app, |view, _| {
assert_eq!(&view.title(), "README.md");
// The location should be set, but the exact breadcrumbs depend on where the repo
// is located.
assert!(view.location.is_some());
});
});
}
#[test]
fn test_load_static() {
App::test((), |mut app| async move {
init_app(&mut app);
let (_, handle) = app.add_window(WindowStyle::NotStealFocus, FileNotebookView::new);
handle.update(&mut app, |file_notebook, ctx| {
file_notebook.open_static("Test Title", "Test Content", ctx);
assert!(file_notebook.file_id.is_none());
assert!(matches!(file_notebook.file_state, FileState::Loaded(_)));
assert_eq!(file_notebook.title(), "Test Title");
assert!(file_notebook.location.is_none());
let editor = file_notebook.editor.as_ref(ctx);
assert!(!editor.is_editable(ctx));
// We don't want to check the actual README contents, but it should be clearly non-empty.
assert!(editor.markdown(ctx).len() > 4);
// Rendering should not panic.
file_notebook.render(ctx);
});
});
}
#[test]
fn test_markdown_file_detection() {
assert!(is_markdown_file("README.md"));
assert!(is_markdown_file("DATABASE.MD"));
assert!(is_markdown_file("notes.markdown"));
assert!(is_markdown_file("README"));
assert!(is_markdown_file("license"));
assert!(is_markdown_file("CHANGELOG"));
assert!(is_markdown_file("ReadMe"));
assert!(!is_markdown_file("README.txt"));
assert!(!is_markdown_file("main.rs"));
assert!(!is_markdown_file("notes"));
}
#[test]
fn test_file_notebook_mermaid_context_menu_does_not_show_copy_image() {
App::test((), |mut app| async move {
init_app(&mut app);
let (_, handle) = app.add_window(WindowStyle::NotStealFocus, FileNotebookView::new);
handle.update(&mut app, |file_notebook, ctx| {
file_notebook.open_static("Test Title", "```mermaid\ngraph TD\nA --> B\n```", ctx);
let source = MenuSource::RichTextEditor {
parent_offset: vec2f(0., 0.),
editor: file_notebook.editor.clone(),
};
file_notebook.context_menu.show_context_menu(source, ctx);
let item_names = file_notebook.context_menu.item_names(ctx);
assert!(!item_names.contains(&"Copy image"));
});
});
}