ElyMark: navigation tested through the whole window (C14.8)
desk::open_in takes the data folder, so gpui tests start ElyMark in a temp folder: open, outline, jump, recent list, tabs, folder listing. The web page strings, left from C14.5, go. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
71c5d809f5
commit
53ce8eec0b
8 files changed
+194
-42
No files matched your search
+41
-26
@@ -145,8 +145,19 @@ impl Host for Desk {
|
||||
|
||||
/// The window root; files given at launch open.
|
||||
pub fn open(app: AppId, window: &mut Window, cx: &mut App) -> Entity<Root> {
|
||||
let data = ely_platform::data_dir(app)
|
||||
.unwrap_or_else(|error| startup::fail(app, &format!("{error:#}")));
|
||||
open_in(app, data, window, cx).0
|
||||
}
|
||||
|
||||
/// The window root and its desk, kept in `data`.
|
||||
pub(crate) fn open_in(
|
||||
app: AppId,
|
||||
data: PathBuf,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> (Entity<Root>, Entity<Desk>) {
|
||||
let fail = |error: String| -> ! { startup::fail(app, &error) };
|
||||
let data = ely_platform::data_dir(app).unwrap_or_else(|error| fail(format!("{error:#}")));
|
||||
let work = ely_office::fresh_work(&data).unwrap_or_else(|error| fail(format!("{error}")));
|
||||
crate::keys::bind(cx);
|
||||
let recent_file = data.join("recent.json");
|
||||
@@ -274,7 +285,7 @@ pub fn open(app: AppId, window: &mut Window, cx: &mut App) -> Entity<Root> {
|
||||
desk.should_close(window, cx)
|
||||
})
|
||||
});
|
||||
root
|
||||
(root, desk)
|
||||
}
|
||||
|
||||
impl Desk {
|
||||
@@ -391,30 +402,31 @@ impl Render for Desk {
|
||||
.status(self.status(cx)),
|
||||
}
|
||||
.child(body);
|
||||
let root = div()
|
||||
.size_full()
|
||||
.key_context(commands::CONTEXT)
|
||||
.track_focus(&self.core.focus)
|
||||
.on_action(cx.listener(Self::open_picked))
|
||||
.on_action(cx.listener(Self::on_new))
|
||||
.on_action(cx.listener(Self::on_save))
|
||||
.on_action(cx.listener(Self::on_save_as))
|
||||
.on_action(cx.listener(Self::on_rename))
|
||||
.on_action(cx.listener(Self::on_close_tab))
|
||||
.on_action(cx.listener(|desk, _: &CheckDisk, _, cx| desk.look_all(cx)))
|
||||
.on_action(cx.listener(|desk, _: &VisualMode, window, cx| {
|
||||
desk.switch(Mode::Visual, window, cx)
|
||||
}))
|
||||
.on_action(cx.listener(|desk, _: &SourceMode, window, cx| {
|
||||
desk.switch(Mode::Source, window, cx)
|
||||
}))
|
||||
.on_action(cx.listener(|desk, _: &PreviewMode, window, cx| {
|
||||
desk.switch(Mode::Preview, window, cx)
|
||||
}))
|
||||
.child(shell)
|
||||
.children(self.core_dialog(cx))
|
||||
.children(self.naming_element(cx))
|
||||
.children(self.compare_dialog(cx));
|
||||
let root =
|
||||
div()
|
||||
.size_full()
|
||||
.key_context(commands::CONTEXT)
|
||||
.track_focus(&self.core.focus)
|
||||
.on_action(cx.listener(Self::open_picked))
|
||||
.on_action(cx.listener(Self::on_new))
|
||||
.on_action(cx.listener(Self::on_save))
|
||||
.on_action(cx.listener(Self::on_save_as))
|
||||
.on_action(cx.listener(Self::on_rename))
|
||||
.on_action(cx.listener(Self::on_close_tab))
|
||||
.on_action(cx.listener(|desk, _: &CheckDisk, _, cx| desk.look_all(cx)))
|
||||
.on_action(cx.listener(|desk, _: &VisualMode, window, cx| {
|
||||
desk.switch(Mode::Visual, window, cx)
|
||||
}))
|
||||
.on_action(cx.listener(|desk, _: &SourceMode, window, cx| {
|
||||
desk.switch(Mode::Source, window, cx)
|
||||
}))
|
||||
.on_action(cx.listener(|desk, _: &PreviewMode, window, cx| {
|
||||
desk.switch(Mode::Preview, window, cx)
|
||||
}))
|
||||
.child(shell)
|
||||
.children(self.core_dialog(cx))
|
||||
.children(self.naming_element(cx))
|
||||
.children(self.compare_dialog(cx));
|
||||
Self::look_actions(root, cx)
|
||||
}
|
||||
}
|
||||
@@ -435,3 +447,6 @@ fn large_banner(
|
||||
said,
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
@@ -0,0 +1,143 @@
|
||||
use std::{cell::RefCell, path::Path, rc::Rc};
|
||||
|
||||
use ely_platform::AppId;
|
||||
use gpui::{Entity, TestAppContext, VisualTestContext};
|
||||
|
||||
use super::{Desk, open_in};
|
||||
use crate::view::Mode;
|
||||
|
||||
/// ElyMark over a fresh data folder, as `main` opens it.
|
||||
fn start(cx: &mut TestAppContext, data: &Path) -> (Entity<Desk>, &'static mut VisualTestContext) {
|
||||
// The file watcher runs on a real thread.
|
||||
cx.executor().allow_parking();
|
||||
cx.update(ely_gpui_component::init_for_tests);
|
||||
cx.update(|cx| {
|
||||
ely_ui::settings::install_defaults(cx);
|
||||
ely_ui::i18n::install(cx);
|
||||
ely_ui::install_no_launch_files(cx);
|
||||
});
|
||||
let held = Rc::new(RefCell::new(None));
|
||||
let kept = held.clone();
|
||||
let data = data.to_path_buf();
|
||||
let window = cx.update(|cx| {
|
||||
cx.open_window(Default::default(), |window, cx| {
|
||||
let (root, desk) = open_in(AppId::Mark, data, window, cx);
|
||||
kept.replace(Some(desk));
|
||||
root
|
||||
})
|
||||
.expect("a window")
|
||||
});
|
||||
let cx = VisualTestContext::from_window(window.into(), cx).into_mut();
|
||||
cx.run_until_parked();
|
||||
let desk = held.take().expect("the desk");
|
||||
(desk, cx)
|
||||
}
|
||||
|
||||
fn open(desk: &Entity<Desk>, path: &Path, cx: &mut VisualTestContext) {
|
||||
desk.update(cx, |desk, cx| desk.open_path(path.to_path_buf(), cx));
|
||||
cx.run_until_parked();
|
||||
}
|
||||
|
||||
fn outline(desk: &Entity<Desk>, cx: &mut VisualTestContext) -> Vec<(usize, String, usize)> {
|
||||
desk.read_with(cx, |desk, cx| {
|
||||
let view = desk.active_view().expect("a tab");
|
||||
let outline = view.read(cx).outline(cx);
|
||||
outline
|
||||
.into_iter()
|
||||
.map(|heading| (heading.level, heading.title, heading.line))
|
||||
.collect()
|
||||
})
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn a_file_opens_with_its_outline_and_joins_the_recent_list(cx: &mut TestAppContext) {
|
||||
let (data, files) = (
|
||||
tempfile::tempdir().expect("data"),
|
||||
tempfile::tempdir().expect("files"),
|
||||
);
|
||||
let path = files.path().join("a.md");
|
||||
std::fs::write(&path, "# One\n\nText.\n\n## Two\n\nMore.\n").expect("a file");
|
||||
let (desk, cx) = start(cx, data.path());
|
||||
open(&desk, &path, cx);
|
||||
assert_eq!(
|
||||
outline(&desk, cx),
|
||||
[(1, "One".into(), 0), (2, "Two".into(), 4)]
|
||||
);
|
||||
let view = desk.read_with(cx, |desk, _| desk.active_view().expect("a tab"));
|
||||
view.update_in(cx, |view, window, cx| view.reveal(4, window, cx));
|
||||
cx.run_until_parked();
|
||||
assert_eq!(
|
||||
view.read_with(cx, |view, cx| view.place(cx).line),
|
||||
4,
|
||||
"the jump lands"
|
||||
);
|
||||
let real = std::fs::canonicalize(&path).expect("real");
|
||||
assert!(desk.read_with(cx, |desk, _| desk.recent.contains(&real)));
|
||||
let kept = std::fs::read_to_string(data.path().join("recent.json")).expect("the list");
|
||||
assert!(kept.contains("a.md"), "{kept}");
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn the_outline_follows_typing(cx: &mut TestAppContext) {
|
||||
let (data, files) = (
|
||||
tempfile::tempdir().expect("data"),
|
||||
tempfile::tempdir().expect("files"),
|
||||
);
|
||||
let path = files.path().join("a.md");
|
||||
std::fs::write(&path, "Text.\n").expect("a file");
|
||||
let (desk, cx) = start(cx, data.path());
|
||||
open(&desk, &path, cx);
|
||||
let view = desk.read_with(cx, |desk, _| desk.active_view().expect("a tab"));
|
||||
view.update_in(cx, |view, window, cx| view.switch(Mode::Source, window, cx));
|
||||
view.update(cx, |view, cx| view.adopt("# New\n\nText.\n".into(), cx));
|
||||
cx.executor()
|
||||
.advance_clock(std::time::Duration::from_secs(1));
|
||||
cx.run_until_parked();
|
||||
assert_eq!(outline(&desk, cx), [(1, "New".into(), 0)]);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn two_files_are_two_tabs_each_with_its_outline(cx: &mut TestAppContext) {
|
||||
let (data, files) = (
|
||||
tempfile::tempdir().expect("data"),
|
||||
tempfile::tempdir().expect("files"),
|
||||
);
|
||||
let (a, b) = (files.path().join("a.md"), files.path().join("b.md"));
|
||||
std::fs::write(&a, "# A\n").expect("a");
|
||||
std::fs::write(&b, "# B\n").expect("b");
|
||||
let (desk, cx) = start(cx, data.path());
|
||||
open(&desk, &a, cx);
|
||||
open(&desk, &b, cx);
|
||||
assert_eq!(desk.read_with(cx, |desk, _| desk.core.tabs.len()), 2);
|
||||
assert_eq!(outline(&desk, cx), [(1, "B".into(), 0)]);
|
||||
let first = desk.read_with(cx, |desk, _| desk.core.tabs[0].id);
|
||||
desk.update(cx, |desk, cx| {
|
||||
desk.core.active = Some(first);
|
||||
cx.notify();
|
||||
});
|
||||
assert_eq!(outline(&desk, cx), [(1, "A".into(), 0)]);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn a_folder_lists_its_markdown_files(cx: &mut TestAppContext) {
|
||||
let (data, files) = (
|
||||
tempfile::tempdir().expect("data"),
|
||||
tempfile::tempdir().expect("files"),
|
||||
);
|
||||
std::fs::create_dir(files.path().join("sub")).expect("sub");
|
||||
std::fs::write(files.path().join("a.md"), "a").expect("a");
|
||||
std::fs::write(files.path().join("sub/b.markdown"), "b").expect("b");
|
||||
std::fs::write(files.path().join("c.txt"), "c").expect("c");
|
||||
let (desk, cx) = start(cx, data.path());
|
||||
open(&desk, files.path(), cx);
|
||||
let mut listed = desk.read_with(cx, |desk, _| {
|
||||
let folder = desk.folder.as_ref().expect("a folder");
|
||||
folder
|
||||
.files
|
||||
.iter()
|
||||
.map(|file| file.replace('\\', "/"))
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
listed.sort();
|
||||
assert_eq!(listed, ["a.md", "sub/b.markdown"]);
|
||||
}
|
||||
@@ -253,7 +253,7 @@ impl Side {
|
||||
let Some(heading) = key.parse::<usize>().ok().and_then(|at| headings.get(at)) else {
|
||||
return log::error!("mark: the outline chose no heading");
|
||||
};
|
||||
// Keys go to the document, at the heading.
|
||||
// The heading in view, the caret on it.
|
||||
let line = heading.line;
|
||||
view.update(cx, |view, cx| view.reveal(line, window, cx));
|
||||
})
|
||||
|
||||
Reference in new issue
Block a user