Use gpui::test in async tests in zed
Co-Authored-By: Max Brunsfeld <max@zed.dev>
This commit is contained in:
co-authored by
Max Brunsfeld
parent
1c2b381f74
commit
ab04d71508
+192
-204
@@ -453,220 +453,208 @@ impl FileFinder {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{editor, settings, test::temp_tree, workspace::Workspace};
|
||||
use gpui::App;
|
||||
use serde_json::json;
|
||||
use std::fs;
|
||||
use tempdir::TempDir;
|
||||
|
||||
#[test]
|
||||
fn test_matching_paths() {
|
||||
App::test_async((), |mut app| async move {
|
||||
let tmp_dir = TempDir::new("example").unwrap();
|
||||
fs::create_dir(tmp_dir.path().join("a")).unwrap();
|
||||
fs::write(tmp_dir.path().join("a/banana"), "banana").unwrap();
|
||||
fs::write(tmp_dir.path().join("a/bandana"), "bandana").unwrap();
|
||||
app.update(|ctx| {
|
||||
super::init(ctx);
|
||||
editor::init(ctx);
|
||||
});
|
||||
|
||||
let settings = settings::channel(&app.font_cache()).unwrap().1;
|
||||
let (window_id, workspace) = app.add_window(|ctx| {
|
||||
let mut workspace = Workspace::new(0, settings, ctx);
|
||||
workspace.add_worktree(tmp_dir.path(), ctx);
|
||||
workspace
|
||||
});
|
||||
app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx))
|
||||
.await;
|
||||
app.dispatch_action(
|
||||
window_id,
|
||||
vec![workspace.id()],
|
||||
"file_finder:toggle".into(),
|
||||
(),
|
||||
);
|
||||
|
||||
let finder = app.read(|ctx| {
|
||||
workspace
|
||||
.read(ctx)
|
||||
.modal()
|
||||
.cloned()
|
||||
.unwrap()
|
||||
.downcast::<FileFinder>()
|
||||
.unwrap()
|
||||
});
|
||||
let query_buffer = app.read(|ctx| finder.read(ctx).query_buffer.clone());
|
||||
|
||||
let chain = vec![finder.id(), query_buffer.id()];
|
||||
app.dispatch_action(window_id, chain.clone(), "buffer:insert", "b".to_string());
|
||||
app.dispatch_action(window_id, chain.clone(), "buffer:insert", "n".to_string());
|
||||
app.dispatch_action(window_id, chain.clone(), "buffer:insert", "a".to_string());
|
||||
finder
|
||||
.condition(&app, |finder, _| finder.matches.len() == 2)
|
||||
.await;
|
||||
|
||||
let active_pane = app.read(|ctx| workspace.read(ctx).active_pane().clone());
|
||||
app.dispatch_action(
|
||||
window_id,
|
||||
vec![workspace.id(), finder.id()],
|
||||
"menu:select_next",
|
||||
(),
|
||||
);
|
||||
app.dispatch_action(
|
||||
window_id,
|
||||
vec![workspace.id(), finder.id()],
|
||||
"file_finder:confirm",
|
||||
(),
|
||||
);
|
||||
active_pane
|
||||
.condition(&app, |pane, _| pane.active_item().is_some())
|
||||
.await;
|
||||
app.read(|ctx| {
|
||||
let active_item = active_pane.read(ctx).active_item().unwrap();
|
||||
assert_eq!(active_item.title(ctx), "bandana");
|
||||
});
|
||||
#[gpui::test]
|
||||
async fn test_matching_paths(mut app: gpui::TestAppContext) {
|
||||
let tmp_dir = TempDir::new("example").unwrap();
|
||||
fs::create_dir(tmp_dir.path().join("a")).unwrap();
|
||||
fs::write(tmp_dir.path().join("a/banana"), "banana").unwrap();
|
||||
fs::write(tmp_dir.path().join("a/bandana"), "bandana").unwrap();
|
||||
app.update(|ctx| {
|
||||
super::init(ctx);
|
||||
editor::init(ctx);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_matching_cancellation() {
|
||||
App::test_async((), |mut app| async move {
|
||||
let tmp_dir = temp_tree(json!({
|
||||
"hello": "",
|
||||
"goodbye": "",
|
||||
"halogen-light": "",
|
||||
"happiness": "",
|
||||
"height": "",
|
||||
"hi": "",
|
||||
"hiccup": "",
|
||||
}));
|
||||
let settings = settings::channel(&app.font_cache()).unwrap().1;
|
||||
let (_, workspace) = app.add_window(|ctx| {
|
||||
let mut workspace = Workspace::new(0, settings.clone(), ctx);
|
||||
workspace.add_worktree(tmp_dir.path(), ctx);
|
||||
workspace
|
||||
});
|
||||
app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx))
|
||||
.await;
|
||||
let (_, finder) =
|
||||
app.add_window(|ctx| FileFinder::new(settings, workspace.clone(), ctx));
|
||||
|
||||
let query = "hi".to_string();
|
||||
finder.update(&mut app, |f, ctx| f.spawn_search(query.clone(), ctx));
|
||||
finder.condition(&app, |f, _| f.matches.len() == 5).await;
|
||||
|
||||
finder.update(&mut app, |finder, ctx| {
|
||||
let matches = finder.matches.clone();
|
||||
|
||||
// Simulate a search being cancelled after the time limit,
|
||||
// returning only a subset of the matches that would have been found.
|
||||
finder.spawn_search(query.clone(), ctx);
|
||||
finder.update_matches(
|
||||
(
|
||||
finder.latest_search_id,
|
||||
true, // did-cancel
|
||||
query.clone(),
|
||||
vec![matches[1].clone(), matches[3].clone()],
|
||||
),
|
||||
ctx,
|
||||
);
|
||||
|
||||
// Simulate another cancellation.
|
||||
finder.spawn_search(query.clone(), ctx);
|
||||
finder.update_matches(
|
||||
(
|
||||
finder.latest_search_id,
|
||||
true, // did-cancel
|
||||
query.clone(),
|
||||
vec![matches[0].clone(), matches[2].clone(), matches[3].clone()],
|
||||
),
|
||||
ctx,
|
||||
);
|
||||
|
||||
assert_eq!(finder.matches, matches[0..4])
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_single_file_worktrees() {
|
||||
App::test_async((), |mut app| async move {
|
||||
let temp_dir = TempDir::new("test-single-file-worktrees").unwrap();
|
||||
let dir_path = temp_dir.path().join("the-parent-dir");
|
||||
let file_path = dir_path.join("the-file");
|
||||
fs::create_dir(&dir_path).unwrap();
|
||||
fs::write(&file_path, "").unwrap();
|
||||
|
||||
let settings = settings::channel(&app.font_cache()).unwrap().1;
|
||||
let (_, workspace) = app.add_window(|ctx| {
|
||||
let mut workspace = Workspace::new(0, settings.clone(), ctx);
|
||||
workspace.add_worktree(&file_path, ctx);
|
||||
workspace
|
||||
});
|
||||
app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx))
|
||||
.await;
|
||||
let (_, finder) =
|
||||
app.add_window(|ctx| FileFinder::new(settings, workspace.clone(), ctx));
|
||||
|
||||
// Even though there is only one worktree, that worktree's filename
|
||||
// is included in the matching, because the worktree is a single file.
|
||||
finder.update(&mut app, |f, ctx| f.spawn_search("thf".into(), ctx));
|
||||
finder.condition(&app, |f, _| f.matches.len() == 1).await;
|
||||
|
||||
app.read(|ctx| {
|
||||
let finder = finder.read(ctx);
|
||||
let (file_name, file_name_positions, full_path, full_path_positions) =
|
||||
finder.labels_for_match(&finder.matches[0], ctx).unwrap();
|
||||
|
||||
assert_eq!(file_name, "the-file");
|
||||
assert_eq!(file_name_positions, &[0, 1, 4]);
|
||||
assert_eq!(full_path, "the-file");
|
||||
assert_eq!(full_path_positions, &[0, 1, 4]);
|
||||
});
|
||||
|
||||
// Since the worktree root is a file, searching for its name followed by a slash does
|
||||
// not match anything.
|
||||
finder.update(&mut app, |f, ctx| f.spawn_search("thf/".into(), ctx));
|
||||
finder.condition(&app, |f, _| f.matches.len() == 0).await;
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multiple_matches_with_same_relative_path() {
|
||||
App::test_async((), |mut app| async move {
|
||||
let tmp_dir = temp_tree(json!({
|
||||
"dir1": { "a.txt": "" },
|
||||
"dir2": { "a.txt": "" }
|
||||
}));
|
||||
let settings = settings::channel(&app.font_cache()).unwrap().1;
|
||||
|
||||
let (_, workspace) = app.add_window(|ctx| Workspace::new(0, settings.clone(), ctx));
|
||||
|
||||
let settings = settings::channel(&app.font_cache()).unwrap().1;
|
||||
let (window_id, workspace) = app.add_window(|ctx| {
|
||||
let mut workspace = Workspace::new(0, settings, ctx);
|
||||
workspace.add_worktree(tmp_dir.path(), ctx);
|
||||
workspace
|
||||
.update(&mut app, |workspace, ctx| {
|
||||
workspace.open_paths(
|
||||
&[tmp_dir.path().join("dir1"), tmp_dir.path().join("dir2")],
|
||||
ctx,
|
||||
)
|
||||
})
|
||||
.await;
|
||||
app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx))
|
||||
.await;
|
||||
});
|
||||
app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx))
|
||||
.await;
|
||||
app.dispatch_action(
|
||||
window_id,
|
||||
vec![workspace.id()],
|
||||
"file_finder:toggle".into(),
|
||||
(),
|
||||
);
|
||||
|
||||
let (_, finder) =
|
||||
app.add_window(|ctx| FileFinder::new(settings, workspace.clone(), ctx));
|
||||
let finder = app.read(|ctx| {
|
||||
workspace
|
||||
.read(ctx)
|
||||
.modal()
|
||||
.cloned()
|
||||
.unwrap()
|
||||
.downcast::<FileFinder>()
|
||||
.unwrap()
|
||||
});
|
||||
let query_buffer = app.read(|ctx| finder.read(ctx).query_buffer.clone());
|
||||
|
||||
// Run a search that matches two files with the same relative path.
|
||||
finder.update(&mut app, |f, ctx| f.spawn_search("a.t".into(), ctx));
|
||||
finder.condition(&app, |f, _| f.matches.len() == 2).await;
|
||||
let chain = vec![finder.id(), query_buffer.id()];
|
||||
app.dispatch_action(window_id, chain.clone(), "buffer:insert", "b".to_string());
|
||||
app.dispatch_action(window_id, chain.clone(), "buffer:insert", "n".to_string());
|
||||
app.dispatch_action(window_id, chain.clone(), "buffer:insert", "a".to_string());
|
||||
finder
|
||||
.condition(&app, |finder, _| finder.matches.len() == 2)
|
||||
.await;
|
||||
|
||||
// Can switch between different matches with the same relative path.
|
||||
finder.update(&mut app, |f, ctx| {
|
||||
assert_eq!(f.selected_index(), 0);
|
||||
f.select_next(&(), ctx);
|
||||
assert_eq!(f.selected_index(), 1);
|
||||
f.select_prev(&(), ctx);
|
||||
assert_eq!(f.selected_index(), 0);
|
||||
});
|
||||
let active_pane = app.read(|ctx| workspace.read(ctx).active_pane().clone());
|
||||
app.dispatch_action(
|
||||
window_id,
|
||||
vec![workspace.id(), finder.id()],
|
||||
"menu:select_next",
|
||||
(),
|
||||
);
|
||||
app.dispatch_action(
|
||||
window_id,
|
||||
vec![workspace.id(), finder.id()],
|
||||
"file_finder:confirm",
|
||||
(),
|
||||
);
|
||||
active_pane
|
||||
.condition(&app, |pane, _| pane.active_item().is_some())
|
||||
.await;
|
||||
app.read(|ctx| {
|
||||
let active_item = active_pane.read(ctx).active_item().unwrap();
|
||||
assert_eq!(active_item.title(ctx), "bandana");
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_matching_cancellation(mut app: gpui::TestAppContext) {
|
||||
let tmp_dir = temp_tree(json!({
|
||||
"hello": "",
|
||||
"goodbye": "",
|
||||
"halogen-light": "",
|
||||
"happiness": "",
|
||||
"height": "",
|
||||
"hi": "",
|
||||
"hiccup": "",
|
||||
}));
|
||||
let settings = settings::channel(&app.font_cache()).unwrap().1;
|
||||
let (_, workspace) = app.add_window(|ctx| {
|
||||
let mut workspace = Workspace::new(0, settings.clone(), ctx);
|
||||
workspace.add_worktree(tmp_dir.path(), ctx);
|
||||
workspace
|
||||
});
|
||||
app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx))
|
||||
.await;
|
||||
let (_, finder) = app.add_window(|ctx| FileFinder::new(settings, workspace.clone(), ctx));
|
||||
|
||||
let query = "hi".to_string();
|
||||
finder.update(&mut app, |f, ctx| f.spawn_search(query.clone(), ctx));
|
||||
finder.condition(&app, |f, _| f.matches.len() == 5).await;
|
||||
|
||||
finder.update(&mut app, |finder, ctx| {
|
||||
let matches = finder.matches.clone();
|
||||
|
||||
// Simulate a search being cancelled after the time limit,
|
||||
// returning only a subset of the matches that would have been found.
|
||||
finder.spawn_search(query.clone(), ctx);
|
||||
finder.update_matches(
|
||||
(
|
||||
finder.latest_search_id,
|
||||
true, // did-cancel
|
||||
query.clone(),
|
||||
vec![matches[1].clone(), matches[3].clone()],
|
||||
),
|
||||
ctx,
|
||||
);
|
||||
|
||||
// Simulate another cancellation.
|
||||
finder.spawn_search(query.clone(), ctx);
|
||||
finder.update_matches(
|
||||
(
|
||||
finder.latest_search_id,
|
||||
true, // did-cancel
|
||||
query.clone(),
|
||||
vec![matches[0].clone(), matches[2].clone(), matches[3].clone()],
|
||||
),
|
||||
ctx,
|
||||
);
|
||||
|
||||
assert_eq!(finder.matches, matches[0..4])
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_single_file_worktrees(mut app: gpui::TestAppContext) {
|
||||
let temp_dir = TempDir::new("test-single-file-worktrees").unwrap();
|
||||
let dir_path = temp_dir.path().join("the-parent-dir");
|
||||
let file_path = dir_path.join("the-file");
|
||||
fs::create_dir(&dir_path).unwrap();
|
||||
fs::write(&file_path, "").unwrap();
|
||||
|
||||
let settings = settings::channel(&app.font_cache()).unwrap().1;
|
||||
let (_, workspace) = app.add_window(|ctx| {
|
||||
let mut workspace = Workspace::new(0, settings.clone(), ctx);
|
||||
workspace.add_worktree(&file_path, ctx);
|
||||
workspace
|
||||
});
|
||||
app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx))
|
||||
.await;
|
||||
let (_, finder) = app.add_window(|ctx| FileFinder::new(settings, workspace.clone(), ctx));
|
||||
|
||||
// Even though there is only one worktree, that worktree's filename
|
||||
// is included in the matching, because the worktree is a single file.
|
||||
finder.update(&mut app, |f, ctx| f.spawn_search("thf".into(), ctx));
|
||||
finder.condition(&app, |f, _| f.matches.len() == 1).await;
|
||||
|
||||
app.read(|ctx| {
|
||||
let finder = finder.read(ctx);
|
||||
let (file_name, file_name_positions, full_path, full_path_positions) =
|
||||
finder.labels_for_match(&finder.matches[0], ctx).unwrap();
|
||||
|
||||
assert_eq!(file_name, "the-file");
|
||||
assert_eq!(file_name_positions, &[0, 1, 4]);
|
||||
assert_eq!(full_path, "the-file");
|
||||
assert_eq!(full_path_positions, &[0, 1, 4]);
|
||||
});
|
||||
|
||||
// Since the worktree root is a file, searching for its name followed by a slash does
|
||||
// not match anything.
|
||||
finder.update(&mut app, |f, ctx| f.spawn_search("thf/".into(), ctx));
|
||||
finder.condition(&app, |f, _| f.matches.len() == 0).await;
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_multiple_matches_with_same_relative_path(mut app: gpui::TestAppContext) {
|
||||
let tmp_dir = temp_tree(json!({
|
||||
"dir1": { "a.txt": "" },
|
||||
"dir2": { "a.txt": "" }
|
||||
}));
|
||||
let settings = settings::channel(&app.font_cache()).unwrap().1;
|
||||
|
||||
let (_, workspace) = app.add_window(|ctx| Workspace::new(0, settings.clone(), ctx));
|
||||
|
||||
workspace
|
||||
.update(&mut app, |workspace, ctx| {
|
||||
workspace.open_paths(
|
||||
&[tmp_dir.path().join("dir1"), tmp_dir.path().join("dir2")],
|
||||
ctx,
|
||||
)
|
||||
})
|
||||
.await;
|
||||
app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx))
|
||||
.await;
|
||||
|
||||
let (_, finder) = app.add_window(|ctx| FileFinder::new(settings, workspace.clone(), ctx));
|
||||
|
||||
// Run a search that matches two files with the same relative path.
|
||||
finder.update(&mut app, |f, ctx| f.spawn_search("a.t".into(), ctx));
|
||||
finder.condition(&app, |f, _| f.matches.len() == 2).await;
|
||||
|
||||
// Can switch between different matches with the same relative path.
|
||||
finder.update(&mut app, |f, ctx| {
|
||||
assert_eq!(f.selected_index(), 0);
|
||||
f.select_next(&(), ctx);
|
||||
assert_eq!(f.selected_index(), 1);
|
||||
f.select_prev(&(), ctx);
|
||||
assert_eq!(f.selected_index(), 0);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+293
-302
@@ -729,7 +729,6 @@ impl WorkspaceHandle for ViewHandle<Workspace> {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{editor::BufferView, settings, test::temp_tree};
|
||||
use gpui::App;
|
||||
use serde_json::json;
|
||||
use std::collections::HashSet;
|
||||
use tempdir::TempDir;
|
||||
@@ -793,332 +792,324 @@ mod tests {
|
||||
assert_eq!(app.window_ids().count(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_open_entry() {
|
||||
App::test_async((), |mut app| async move {
|
||||
let dir = temp_tree(json!({
|
||||
"a": {
|
||||
"file1": "contents 1",
|
||||
"file2": "contents 2",
|
||||
"file3": "contents 3",
|
||||
},
|
||||
}));
|
||||
#[gpui::test]
|
||||
async fn test_open_entry(mut app: gpui::TestAppContext) {
|
||||
let dir = temp_tree(json!({
|
||||
"a": {
|
||||
"file1": "contents 1",
|
||||
"file2": "contents 2",
|
||||
"file3": "contents 3",
|
||||
},
|
||||
}));
|
||||
|
||||
let settings = settings::channel(&app.font_cache()).unwrap().1;
|
||||
let settings = settings::channel(&app.font_cache()).unwrap().1;
|
||||
|
||||
let (_, workspace) = app.add_window(|ctx| {
|
||||
let mut workspace = Workspace::new(0, settings, ctx);
|
||||
workspace.add_worktree(dir.path(), ctx);
|
||||
workspace
|
||||
});
|
||||
|
||||
app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx))
|
||||
.await;
|
||||
let entries = app.read(|ctx| workspace.file_entries(ctx));
|
||||
let file1 = entries[0].clone();
|
||||
let file2 = entries[1].clone();
|
||||
let file3 = entries[2].clone();
|
||||
|
||||
// Open the first entry
|
||||
let (_, workspace) = app.add_window(|ctx| {
|
||||
let mut workspace = Workspace::new(0, settings, ctx);
|
||||
workspace.add_worktree(dir.path(), ctx);
|
||||
workspace
|
||||
.update(&mut app, |w, ctx| w.open_entry(file1.clone(), ctx))
|
||||
.unwrap()
|
||||
.await;
|
||||
app.read(|ctx| {
|
||||
let pane = workspace.read(ctx).active_pane().read(ctx);
|
||||
assert_eq!(
|
||||
pane.active_item().unwrap().entry_id(ctx),
|
||||
Some(file1.clone())
|
||||
);
|
||||
assert_eq!(pane.items().len(), 1);
|
||||
});
|
||||
|
||||
// Open the second entry
|
||||
workspace
|
||||
.update(&mut app, |w, ctx| w.open_entry(file2.clone(), ctx))
|
||||
.unwrap()
|
||||
.await;
|
||||
app.read(|ctx| {
|
||||
let pane = workspace.read(ctx).active_pane().read(ctx);
|
||||
assert_eq!(
|
||||
pane.active_item().unwrap().entry_id(ctx),
|
||||
Some(file2.clone())
|
||||
);
|
||||
assert_eq!(pane.items().len(), 2);
|
||||
});
|
||||
|
||||
// Open the first entry again. The existing pane item is activated.
|
||||
workspace.update(&mut app, |w, ctx| {
|
||||
assert!(w.open_entry(file1.clone(), ctx).is_none())
|
||||
});
|
||||
app.read(|ctx| {
|
||||
let pane = workspace.read(ctx).active_pane().read(ctx);
|
||||
assert_eq!(
|
||||
pane.active_item().unwrap().entry_id(ctx),
|
||||
Some(file1.clone())
|
||||
);
|
||||
assert_eq!(pane.items().len(), 2);
|
||||
});
|
||||
|
||||
// Split the pane with the first entry, then open the second entry again.
|
||||
workspace.update(&mut app, |w, ctx| {
|
||||
w.split_pane(w.active_pane().clone(), SplitDirection::Right, ctx);
|
||||
assert!(w.open_entry(file2.clone(), ctx).is_none());
|
||||
assert_eq!(
|
||||
w.active_pane()
|
||||
.read(ctx)
|
||||
.active_item()
|
||||
.unwrap()
|
||||
.entry_id(ctx.as_ref()),
|
||||
Some(file2.clone())
|
||||
);
|
||||
});
|
||||
|
||||
// Open the third entry twice concurrently. Two pane items
|
||||
// are added.
|
||||
let (t1, t2) = workspace.update(&mut app, |w, ctx| {
|
||||
(
|
||||
w.open_entry(file3.clone(), ctx).unwrap(),
|
||||
w.open_entry(file3.clone(), ctx).unwrap(),
|
||||
)
|
||||
});
|
||||
t1.await;
|
||||
t2.await;
|
||||
app.read(|ctx| {
|
||||
let pane = workspace.read(ctx).active_pane().read(ctx);
|
||||
assert_eq!(
|
||||
pane.active_item().unwrap().entry_id(ctx),
|
||||
Some(file3.clone())
|
||||
);
|
||||
let pane_entries = pane
|
||||
.items()
|
||||
.iter()
|
||||
.map(|i| i.entry_id(ctx).unwrap())
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(pane_entries, &[file1, file2, file3.clone(), file3]);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_open_paths() {
|
||||
App::test_async((), |mut app| async move {
|
||||
let dir1 = temp_tree(json!({
|
||||
"a.txt": "",
|
||||
}));
|
||||
let dir2 = temp_tree(json!({
|
||||
"b.txt": "",
|
||||
}));
|
||||
|
||||
let settings = settings::channel(&app.font_cache()).unwrap().1;
|
||||
let (_, workspace) = app.add_window(|ctx| {
|
||||
let mut workspace = Workspace::new(0, settings, ctx);
|
||||
workspace.add_worktree(dir1.path(), ctx);
|
||||
workspace
|
||||
});
|
||||
app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx))
|
||||
.await;
|
||||
|
||||
// Open a file within an existing worktree.
|
||||
app.update(|ctx| {
|
||||
workspace.update(ctx, |view, ctx| {
|
||||
view.open_paths(&[dir1.path().join("a.txt")], ctx)
|
||||
})
|
||||
})
|
||||
app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx))
|
||||
.await;
|
||||
app.read(|ctx| {
|
||||
assert_eq!(
|
||||
workspace
|
||||
.read(ctx)
|
||||
.active_pane()
|
||||
.read(ctx)
|
||||
.active_item()
|
||||
.unwrap()
|
||||
.title(ctx),
|
||||
"a.txt"
|
||||
);
|
||||
});
|
||||
let entries = app.read(|ctx| workspace.file_entries(ctx));
|
||||
let file1 = entries[0].clone();
|
||||
let file2 = entries[1].clone();
|
||||
let file3 = entries[2].clone();
|
||||
|
||||
// Open a file outside of any existing worktree.
|
||||
app.update(|ctx| {
|
||||
workspace.update(ctx, |view, ctx| {
|
||||
view.open_paths(&[dir2.path().join("b.txt")], ctx)
|
||||
})
|
||||
})
|
||||
// Open the first entry
|
||||
workspace
|
||||
.update(&mut app, |w, ctx| w.open_entry(file1.clone(), ctx))
|
||||
.unwrap()
|
||||
.await;
|
||||
app.read(|ctx| {
|
||||
let worktree_roots = workspace
|
||||
app.read(|ctx| {
|
||||
let pane = workspace.read(ctx).active_pane().read(ctx);
|
||||
assert_eq!(
|
||||
pane.active_item().unwrap().entry_id(ctx),
|
||||
Some(file1.clone())
|
||||
);
|
||||
assert_eq!(pane.items().len(), 1);
|
||||
});
|
||||
|
||||
// Open the second entry
|
||||
workspace
|
||||
.update(&mut app, |w, ctx| w.open_entry(file2.clone(), ctx))
|
||||
.unwrap()
|
||||
.await;
|
||||
app.read(|ctx| {
|
||||
let pane = workspace.read(ctx).active_pane().read(ctx);
|
||||
assert_eq!(
|
||||
pane.active_item().unwrap().entry_id(ctx),
|
||||
Some(file2.clone())
|
||||
);
|
||||
assert_eq!(pane.items().len(), 2);
|
||||
});
|
||||
|
||||
// Open the first entry again. The existing pane item is activated.
|
||||
workspace.update(&mut app, |w, ctx| {
|
||||
assert!(w.open_entry(file1.clone(), ctx).is_none())
|
||||
});
|
||||
app.read(|ctx| {
|
||||
let pane = workspace.read(ctx).active_pane().read(ctx);
|
||||
assert_eq!(
|
||||
pane.active_item().unwrap().entry_id(ctx),
|
||||
Some(file1.clone())
|
||||
);
|
||||
assert_eq!(pane.items().len(), 2);
|
||||
});
|
||||
|
||||
// Split the pane with the first entry, then open the second entry again.
|
||||
workspace.update(&mut app, |w, ctx| {
|
||||
w.split_pane(w.active_pane().clone(), SplitDirection::Right, ctx);
|
||||
assert!(w.open_entry(file2.clone(), ctx).is_none());
|
||||
assert_eq!(
|
||||
w.active_pane()
|
||||
.read(ctx)
|
||||
.worktrees()
|
||||
.iter()
|
||||
.map(|w| w.read(ctx).abs_path())
|
||||
.collect::<HashSet<_>>();
|
||||
assert_eq!(
|
||||
worktree_roots,
|
||||
vec![dir1.path(), &dir2.path().join("b.txt")]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
);
|
||||
assert_eq!(
|
||||
workspace
|
||||
.read(ctx)
|
||||
.active_pane()
|
||||
.read(ctx)
|
||||
.active_item()
|
||||
.unwrap()
|
||||
.title(ctx),
|
||||
"b.txt"
|
||||
);
|
||||
});
|
||||
.active_item()
|
||||
.unwrap()
|
||||
.entry_id(ctx.as_ref()),
|
||||
Some(file2.clone())
|
||||
);
|
||||
});
|
||||
|
||||
// Open the third entry twice concurrently. Two pane items
|
||||
// are added.
|
||||
let (t1, t2) = workspace.update(&mut app, |w, ctx| {
|
||||
(
|
||||
w.open_entry(file3.clone(), ctx).unwrap(),
|
||||
w.open_entry(file3.clone(), ctx).unwrap(),
|
||||
)
|
||||
});
|
||||
t1.await;
|
||||
t2.await;
|
||||
app.read(|ctx| {
|
||||
let pane = workspace.read(ctx).active_pane().read(ctx);
|
||||
assert_eq!(
|
||||
pane.active_item().unwrap().entry_id(ctx),
|
||||
Some(file3.clone())
|
||||
);
|
||||
let pane_entries = pane
|
||||
.items()
|
||||
.iter()
|
||||
.map(|i| i.entry_id(ctx).unwrap())
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(pane_entries, &[file1, file2, file3.clone(), file3]);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_open_and_save_new_file() {
|
||||
App::test_async((), |mut app| async move {
|
||||
let dir = TempDir::new("test-new-file").unwrap();
|
||||
let settings = settings::channel(&app.font_cache()).unwrap().1;
|
||||
let (_, workspace) = app.add_window(|ctx| {
|
||||
let mut workspace = Workspace::new(0, settings, ctx);
|
||||
workspace.add_worktree(dir.path(), ctx);
|
||||
workspace
|
||||
});
|
||||
let tree = app.read(|ctx| {
|
||||
#[gpui::test]
|
||||
async fn test_open_paths(mut app: gpui::TestAppContext) {
|
||||
let dir1 = temp_tree(json!({
|
||||
"a.txt": "",
|
||||
}));
|
||||
let dir2 = temp_tree(json!({
|
||||
"b.txt": "",
|
||||
}));
|
||||
|
||||
let settings = settings::channel(&app.font_cache()).unwrap().1;
|
||||
let (_, workspace) = app.add_window(|ctx| {
|
||||
let mut workspace = Workspace::new(0, settings, ctx);
|
||||
workspace.add_worktree(dir1.path(), ctx);
|
||||
workspace
|
||||
});
|
||||
app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx))
|
||||
.await;
|
||||
|
||||
// Open a file within an existing worktree.
|
||||
app.update(|ctx| {
|
||||
workspace.update(ctx, |view, ctx| {
|
||||
view.open_paths(&[dir1.path().join("a.txt")], ctx)
|
||||
})
|
||||
})
|
||||
.await;
|
||||
app.read(|ctx| {
|
||||
assert_eq!(
|
||||
workspace
|
||||
.read(ctx)
|
||||
.worktrees()
|
||||
.iter()
|
||||
.next()
|
||||
.active_pane()
|
||||
.read(ctx)
|
||||
.active_item()
|
||||
.unwrap()
|
||||
.clone()
|
||||
});
|
||||
tree.flush_fs_events(&app).await;
|
||||
.title(ctx),
|
||||
"a.txt"
|
||||
);
|
||||
});
|
||||
|
||||
// Create a new untitled buffer
|
||||
let editor = workspace.update(&mut app, |workspace, ctx| {
|
||||
workspace.open_new_file(&(), ctx);
|
||||
workspace
|
||||
.active_item(ctx)
|
||||
.unwrap()
|
||||
.to_any()
|
||||
.downcast::<BufferView>()
|
||||
.unwrap()
|
||||
});
|
||||
editor.update(&mut app, |editor, ctx| {
|
||||
assert!(!editor.is_dirty(ctx.as_ref()));
|
||||
assert_eq!(editor.title(ctx.as_ref()), "untitled");
|
||||
editor.insert(&"hi".to_string(), ctx);
|
||||
assert!(editor.is_dirty(ctx.as_ref()));
|
||||
});
|
||||
|
||||
// Save the buffer. This prompts for a filename.
|
||||
workspace.update(&mut app, |workspace, ctx| {
|
||||
workspace.save_active_item(&(), ctx)
|
||||
});
|
||||
app.simulate_new_path_selection(|parent_dir| {
|
||||
assert_eq!(parent_dir, dir.path());
|
||||
Some(parent_dir.join("the-new-name"))
|
||||
});
|
||||
app.read(|ctx| {
|
||||
assert!(editor.is_dirty(ctx));
|
||||
assert_eq!(editor.title(ctx), "untitled");
|
||||
});
|
||||
|
||||
// When the save completes, the buffer's title is updated.
|
||||
tree.update(&mut app, |tree, ctx| tree.next_scan_complete(ctx))
|
||||
.await;
|
||||
app.read(|ctx| {
|
||||
assert!(!editor.is_dirty(ctx));
|
||||
assert_eq!(editor.title(ctx), "the-new-name");
|
||||
});
|
||||
|
||||
// Edit the file and save it again. This time, there is no filename prompt.
|
||||
editor.update(&mut app, |editor, ctx| {
|
||||
editor.insert(&" there".to_string(), ctx);
|
||||
assert_eq!(editor.is_dirty(ctx.as_ref()), true);
|
||||
});
|
||||
workspace.update(&mut app, |workspace, ctx| {
|
||||
workspace.save_active_item(&(), ctx)
|
||||
});
|
||||
assert!(!app.did_prompt_for_new_path());
|
||||
editor
|
||||
.condition(&app, |editor, ctx| !editor.is_dirty(ctx))
|
||||
.await;
|
||||
app.read(|ctx| assert_eq!(editor.title(ctx), "the-new-name"));
|
||||
|
||||
// Open the same newly-created file in another pane item. The new editor should reuse
|
||||
// the same buffer.
|
||||
workspace.update(&mut app, |workspace, ctx| {
|
||||
workspace.open_new_file(&(), ctx);
|
||||
workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Right, ctx);
|
||||
assert!(workspace
|
||||
.open_entry((tree.id(), Path::new("the-new-name").into()), ctx)
|
||||
.is_none());
|
||||
});
|
||||
let editor2 = workspace.update(&mut app, |workspace, ctx| {
|
||||
workspace
|
||||
.active_item(ctx)
|
||||
.unwrap()
|
||||
.to_any()
|
||||
.downcast::<BufferView>()
|
||||
.unwrap()
|
||||
});
|
||||
app.read(|ctx| {
|
||||
assert_eq!(editor2.read(ctx).buffer(), editor.read(ctx).buffer());
|
||||
// Open a file outside of any existing worktree.
|
||||
app.update(|ctx| {
|
||||
workspace.update(ctx, |view, ctx| {
|
||||
view.open_paths(&[dir2.path().join("b.txt")], ctx)
|
||||
})
|
||||
})
|
||||
.await;
|
||||
app.read(|ctx| {
|
||||
let worktree_roots = workspace
|
||||
.read(ctx)
|
||||
.worktrees()
|
||||
.iter()
|
||||
.map(|w| w.read(ctx).abs_path())
|
||||
.collect::<HashSet<_>>();
|
||||
assert_eq!(
|
||||
worktree_roots,
|
||||
vec![dir1.path(), &dir2.path().join("b.txt")]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
);
|
||||
assert_eq!(
|
||||
workspace
|
||||
.read(ctx)
|
||||
.active_pane()
|
||||
.read(ctx)
|
||||
.active_item()
|
||||
.unwrap()
|
||||
.title(ctx),
|
||||
"b.txt"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pane_actions() {
|
||||
App::test_async((), |mut app| async move {
|
||||
app.update(|ctx| pane::init(ctx));
|
||||
|
||||
let dir = temp_tree(json!({
|
||||
"a": {
|
||||
"file1": "contents 1",
|
||||
"file2": "contents 2",
|
||||
"file3": "contents 3",
|
||||
},
|
||||
}));
|
||||
|
||||
let settings = settings::channel(&app.font_cache()).unwrap().1;
|
||||
let (window_id, workspace) = app.add_window(|ctx| {
|
||||
let mut workspace = Workspace::new(0, settings, ctx);
|
||||
workspace.add_worktree(dir.path(), ctx);
|
||||
workspace
|
||||
});
|
||||
app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx))
|
||||
.await;
|
||||
let entries = app.read(|ctx| workspace.file_entries(ctx));
|
||||
let file1 = entries[0].clone();
|
||||
|
||||
let pane_1 = app.read(|ctx| workspace.read(ctx).active_pane().clone());
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_open_and_save_new_file(mut app: gpui::TestAppContext) {
|
||||
let dir = TempDir::new("test-new-file").unwrap();
|
||||
let settings = settings::channel(&app.font_cache()).unwrap().1;
|
||||
let (_, workspace) = app.add_window(|ctx| {
|
||||
let mut workspace = Workspace::new(0, settings, ctx);
|
||||
workspace.add_worktree(dir.path(), ctx);
|
||||
workspace
|
||||
.update(&mut app, |w, ctx| w.open_entry(file1.clone(), ctx))
|
||||
});
|
||||
let tree = app.read(|ctx| {
|
||||
workspace
|
||||
.read(ctx)
|
||||
.worktrees()
|
||||
.iter()
|
||||
.next()
|
||||
.unwrap()
|
||||
.await;
|
||||
app.read(|ctx| {
|
||||
assert_eq!(
|
||||
pane_1.read(ctx).active_item().unwrap().entry_id(ctx),
|
||||
Some(file1.clone())
|
||||
);
|
||||
});
|
||||
.clone()
|
||||
});
|
||||
tree.flush_fs_events(&app).await;
|
||||
|
||||
app.dispatch_action(window_id, vec![pane_1.id()], "pane:split_right", ());
|
||||
app.update(|ctx| {
|
||||
let pane_2 = workspace.read(ctx).active_pane().clone();
|
||||
assert_ne!(pane_1, pane_2);
|
||||
// Create a new untitled buffer
|
||||
let editor = workspace.update(&mut app, |workspace, ctx| {
|
||||
workspace.open_new_file(&(), ctx);
|
||||
workspace
|
||||
.active_item(ctx)
|
||||
.unwrap()
|
||||
.to_any()
|
||||
.downcast::<BufferView>()
|
||||
.unwrap()
|
||||
});
|
||||
editor.update(&mut app, |editor, ctx| {
|
||||
assert!(!editor.is_dirty(ctx.as_ref()));
|
||||
assert_eq!(editor.title(ctx.as_ref()), "untitled");
|
||||
editor.insert(&"hi".to_string(), ctx);
|
||||
assert!(editor.is_dirty(ctx.as_ref()));
|
||||
});
|
||||
|
||||
let pane2_item = pane_2.read(ctx).active_item().unwrap();
|
||||
assert_eq!(pane2_item.entry_id(ctx.as_ref()), Some(file1.clone()));
|
||||
// Save the buffer. This prompts for a filename.
|
||||
workspace.update(&mut app, |workspace, ctx| {
|
||||
workspace.save_active_item(&(), ctx)
|
||||
});
|
||||
app.simulate_new_path_selection(|parent_dir| {
|
||||
assert_eq!(parent_dir, dir.path());
|
||||
Some(parent_dir.join("the-new-name"))
|
||||
});
|
||||
app.read(|ctx| {
|
||||
assert!(editor.is_dirty(ctx));
|
||||
assert_eq!(editor.title(ctx), "untitled");
|
||||
});
|
||||
|
||||
ctx.dispatch_action(window_id, vec![pane_2.id()], "pane:close_active_item", ());
|
||||
let workspace_view = workspace.read(ctx);
|
||||
assert_eq!(workspace_view.panes.len(), 1);
|
||||
assert_eq!(workspace_view.active_pane(), &pane_1);
|
||||
});
|
||||
// When the save completes, the buffer's title is updated.
|
||||
tree.update(&mut app, |tree, ctx| tree.next_scan_complete(ctx))
|
||||
.await;
|
||||
app.read(|ctx| {
|
||||
assert!(!editor.is_dirty(ctx));
|
||||
assert_eq!(editor.title(ctx), "the-new-name");
|
||||
});
|
||||
|
||||
// Edit the file and save it again. This time, there is no filename prompt.
|
||||
editor.update(&mut app, |editor, ctx| {
|
||||
editor.insert(&" there".to_string(), ctx);
|
||||
assert_eq!(editor.is_dirty(ctx.as_ref()), true);
|
||||
});
|
||||
workspace.update(&mut app, |workspace, ctx| {
|
||||
workspace.save_active_item(&(), ctx)
|
||||
});
|
||||
assert!(!app.did_prompt_for_new_path());
|
||||
editor
|
||||
.condition(&app, |editor, ctx| !editor.is_dirty(ctx))
|
||||
.await;
|
||||
app.read(|ctx| assert_eq!(editor.title(ctx), "the-new-name"));
|
||||
|
||||
// Open the same newly-created file in another pane item. The new editor should reuse
|
||||
// the same buffer.
|
||||
workspace.update(&mut app, |workspace, ctx| {
|
||||
workspace.open_new_file(&(), ctx);
|
||||
workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Right, ctx);
|
||||
assert!(workspace
|
||||
.open_entry((tree.id(), Path::new("the-new-name").into()), ctx)
|
||||
.is_none());
|
||||
});
|
||||
let editor2 = workspace.update(&mut app, |workspace, ctx| {
|
||||
workspace
|
||||
.active_item(ctx)
|
||||
.unwrap()
|
||||
.to_any()
|
||||
.downcast::<BufferView>()
|
||||
.unwrap()
|
||||
});
|
||||
app.read(|ctx| {
|
||||
assert_eq!(editor2.read(ctx).buffer(), editor.read(ctx).buffer());
|
||||
})
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_pane_actions(mut app: gpui::TestAppContext) {
|
||||
app.update(|ctx| pane::init(ctx));
|
||||
|
||||
let dir = temp_tree(json!({
|
||||
"a": {
|
||||
"file1": "contents 1",
|
||||
"file2": "contents 2",
|
||||
"file3": "contents 3",
|
||||
},
|
||||
}));
|
||||
|
||||
let settings = settings::channel(&app.font_cache()).unwrap().1;
|
||||
let (window_id, workspace) = app.add_window(|ctx| {
|
||||
let mut workspace = Workspace::new(0, settings, ctx);
|
||||
workspace.add_worktree(dir.path(), ctx);
|
||||
workspace
|
||||
});
|
||||
app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx))
|
||||
.await;
|
||||
let entries = app.read(|ctx| workspace.file_entries(ctx));
|
||||
let file1 = entries[0].clone();
|
||||
|
||||
let pane_1 = app.read(|ctx| workspace.read(ctx).active_pane().clone());
|
||||
|
||||
workspace
|
||||
.update(&mut app, |w, ctx| w.open_entry(file1.clone(), ctx))
|
||||
.unwrap()
|
||||
.await;
|
||||
app.read(|ctx| {
|
||||
assert_eq!(
|
||||
pane_1.read(ctx).active_item().unwrap().entry_id(ctx),
|
||||
Some(file1.clone())
|
||||
);
|
||||
});
|
||||
|
||||
app.dispatch_action(window_id, vec![pane_1.id()], "pane:split_right", ());
|
||||
app.update(|ctx| {
|
||||
let pane_2 = workspace.read(ctx).active_pane().clone();
|
||||
assert_ne!(pane_1, pane_2);
|
||||
|
||||
let pane2_item = pane_2.read(ctx).active_item().unwrap();
|
||||
assert_eq!(pane2_item.entry_id(ctx.as_ref()), Some(file1.clone()));
|
||||
|
||||
ctx.dispatch_action(window_id, vec![pane_2.id()], "pane:close_active_item", ());
|
||||
let workspace_view = workspace.read(ctx);
|
||||
assert_eq!(workspace_view.panes.len(), 1);
|
||||
assert_eq!(workspace_view.active_pane(), &pane_1);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+206
-218
@@ -1353,7 +1353,6 @@ mod tests {
|
||||
use crate::editor::Buffer;
|
||||
use crate::test::*;
|
||||
use anyhow::Result;
|
||||
use gpui::App;
|
||||
use rand::prelude::*;
|
||||
use serde_json::json;
|
||||
use std::env;
|
||||
@@ -1361,248 +1360,237 @@ mod tests {
|
||||
use std::os::unix;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
#[test]
|
||||
fn test_populate_and_search() {
|
||||
App::test_async((), |mut app| async move {
|
||||
let dir = temp_tree(json!({
|
||||
"root": {
|
||||
"apple": "",
|
||||
"banana": {
|
||||
"carrot": {
|
||||
"date": "",
|
||||
"endive": "",
|
||||
}
|
||||
},
|
||||
"fennel": {
|
||||
"grape": "",
|
||||
#[gpui::test]
|
||||
async fn test_populate_and_search(mut app: gpui::TestAppContext) {
|
||||
let dir = temp_tree(json!({
|
||||
"root": {
|
||||
"apple": "",
|
||||
"banana": {
|
||||
"carrot": {
|
||||
"date": "",
|
||||
"endive": "",
|
||||
}
|
||||
},
|
||||
"fennel": {
|
||||
"grape": "",
|
||||
}
|
||||
}));
|
||||
}
|
||||
}));
|
||||
|
||||
let root_link_path = dir.path().join("root_link");
|
||||
unix::fs::symlink(&dir.path().join("root"), &root_link_path).unwrap();
|
||||
unix::fs::symlink(
|
||||
&dir.path().join("root/fennel"),
|
||||
&dir.path().join("root/finnochio"),
|
||||
let root_link_path = dir.path().join("root_link");
|
||||
unix::fs::symlink(&dir.path().join("root"), &root_link_path).unwrap();
|
||||
unix::fs::symlink(
|
||||
&dir.path().join("root/fennel"),
|
||||
&dir.path().join("root/finnochio"),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let tree = app.add_model(|ctx| Worktree::new(root_link_path, ctx));
|
||||
|
||||
app.read(|ctx| tree.read(ctx).scan_complete()).await;
|
||||
app.read(|ctx| {
|
||||
let tree = tree.read(ctx);
|
||||
assert_eq!(tree.file_count(), 5);
|
||||
|
||||
assert_eq!(
|
||||
tree.inode_for_path("fennel/grape"),
|
||||
tree.inode_for_path("finnochio/grape")
|
||||
);
|
||||
|
||||
let results = match_paths(
|
||||
Some(tree.snapshot()).iter(),
|
||||
"bna",
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
10,
|
||||
Default::default(),
|
||||
ctx.thread_pool().clone(),
|
||||
)
|
||||
.into_iter()
|
||||
.map(|result| result.path)
|
||||
.collect::<Vec<Arc<Path>>>();
|
||||
assert_eq!(
|
||||
results,
|
||||
vec![
|
||||
PathBuf::from("banana/carrot/date").into(),
|
||||
PathBuf::from("banana/carrot/endive").into(),
|
||||
]
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_save_file(mut app: gpui::TestAppContext) {
|
||||
let dir = temp_tree(json!({
|
||||
"file1": "the old contents",
|
||||
}));
|
||||
|
||||
let tree = app.add_model(|ctx| Worktree::new(dir.path(), ctx));
|
||||
app.read(|ctx| tree.read(ctx).scan_complete()).await;
|
||||
app.read(|ctx| assert_eq!(tree.read(ctx).file_count(), 1));
|
||||
|
||||
let buffer =
|
||||
app.add_model(|ctx| Buffer::new(1, "a line of text.\n".repeat(10 * 1024), ctx));
|
||||
|
||||
let path = tree.update(&mut app, |tree, ctx| {
|
||||
let path = tree.files(0).next().unwrap().path().clone();
|
||||
assert_eq!(path.file_name().unwrap(), "file1");
|
||||
smol::block_on(tree.save(&path, buffer.read(ctx).snapshot(), ctx.as_ref())).unwrap();
|
||||
path
|
||||
});
|
||||
|
||||
let history = app
|
||||
.read(|ctx| tree.read(ctx).load_history(&path, ctx))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let tree = app.add_model(|ctx| Worktree::new(root_link_path, ctx));
|
||||
|
||||
app.read(|ctx| tree.read(ctx).scan_complete()).await;
|
||||
app.read(|ctx| {
|
||||
let tree = tree.read(ctx);
|
||||
assert_eq!(tree.file_count(), 5);
|
||||
|
||||
assert_eq!(
|
||||
tree.inode_for_path("fennel/grape"),
|
||||
tree.inode_for_path("finnochio/grape")
|
||||
);
|
||||
|
||||
let results = match_paths(
|
||||
Some(tree.snapshot()).iter(),
|
||||
"bna",
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
10,
|
||||
Default::default(),
|
||||
ctx.thread_pool().clone(),
|
||||
)
|
||||
.into_iter()
|
||||
.map(|result| result.path)
|
||||
.collect::<Vec<Arc<Path>>>();
|
||||
assert_eq!(
|
||||
results,
|
||||
vec![
|
||||
PathBuf::from("banana/carrot/date").into(),
|
||||
PathBuf::from("banana/carrot/endive").into(),
|
||||
]
|
||||
);
|
||||
})
|
||||
app.read(|ctx| {
|
||||
assert_eq!(history.base_text.as_ref(), buffer.read(ctx).text());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_save_file() {
|
||||
App::test_async((), |mut app| async move {
|
||||
let dir = temp_tree(json!({
|
||||
"file1": "the old contents",
|
||||
}));
|
||||
#[gpui::test]
|
||||
async fn test_save_in_single_file_worktree(mut app: gpui::TestAppContext) {
|
||||
let dir = temp_tree(json!({
|
||||
"file1": "the old contents",
|
||||
}));
|
||||
|
||||
let tree = app.add_model(|ctx| Worktree::new(dir.path(), ctx));
|
||||
app.read(|ctx| tree.read(ctx).scan_complete()).await;
|
||||
app.read(|ctx| assert_eq!(tree.read(ctx).file_count(), 1));
|
||||
let tree = app.add_model(|ctx| Worktree::new(dir.path().join("file1"), ctx));
|
||||
app.read(|ctx| tree.read(ctx).scan_complete()).await;
|
||||
app.read(|ctx| assert_eq!(tree.read(ctx).file_count(), 1));
|
||||
|
||||
let buffer =
|
||||
app.add_model(|ctx| Buffer::new(1, "a line of text.\n".repeat(10 * 1024), ctx));
|
||||
let buffer =
|
||||
app.add_model(|ctx| Buffer::new(1, "a line of text.\n".repeat(10 * 1024), ctx));
|
||||
|
||||
let path = tree.update(&mut app, |tree, ctx| {
|
||||
let path = tree.files(0).next().unwrap().path().clone();
|
||||
assert_eq!(path.file_name().unwrap(), "file1");
|
||||
smol::block_on(tree.save(&path, buffer.read(ctx).snapshot(), ctx.as_ref()))
|
||||
.unwrap();
|
||||
path
|
||||
});
|
||||
|
||||
let history = app
|
||||
.read(|ctx| tree.read(ctx).load_history(&path, ctx))
|
||||
.await
|
||||
.unwrap();
|
||||
app.read(|ctx| {
|
||||
assert_eq!(history.base_text.as_ref(), buffer.read(ctx).text());
|
||||
});
|
||||
let file = app.read(|ctx| tree.file("", ctx));
|
||||
app.update(|ctx| {
|
||||
assert_eq!(file.path().file_name(), None);
|
||||
smol::block_on(file.save(buffer.read(ctx).snapshot(), ctx.as_ref())).unwrap();
|
||||
});
|
||||
|
||||
let history = app.read(|ctx| file.load_history(ctx)).await.unwrap();
|
||||
app.read(|ctx| assert_eq!(history.base_text.as_ref(), buffer.read(ctx).text()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_save_in_single_file_worktree() {
|
||||
App::test_async((), |mut app| async move {
|
||||
let dir = temp_tree(json!({
|
||||
"file1": "the old contents",
|
||||
}));
|
||||
|
||||
let tree = app.add_model(|ctx| Worktree::new(dir.path().join("file1"), ctx));
|
||||
app.read(|ctx| tree.read(ctx).scan_complete()).await;
|
||||
app.read(|ctx| assert_eq!(tree.read(ctx).file_count(), 1));
|
||||
|
||||
let buffer =
|
||||
app.add_model(|ctx| Buffer::new(1, "a line of text.\n".repeat(10 * 1024), ctx));
|
||||
|
||||
let file = app.read(|ctx| tree.file("", ctx));
|
||||
app.update(|ctx| {
|
||||
assert_eq!(file.path().file_name(), None);
|
||||
smol::block_on(file.save(buffer.read(ctx).snapshot(), ctx.as_ref())).unwrap();
|
||||
});
|
||||
|
||||
let history = app.read(|ctx| file.load_history(ctx)).await.unwrap();
|
||||
app.read(|ctx| assert_eq!(history.base_text.as_ref(), buffer.read(ctx).text()));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rescan_simple() {
|
||||
App::test_async((), |mut app| async move {
|
||||
let dir = temp_tree(json!({
|
||||
"a": {
|
||||
"file1": "",
|
||||
"file2": "",
|
||||
"file3": "",
|
||||
},
|
||||
"b": {
|
||||
"c": {
|
||||
"file4": "",
|
||||
"file5": "",
|
||||
}
|
||||
#[gpui::test]
|
||||
async fn test_rescan_simple(mut app: gpui::TestAppContext) {
|
||||
let dir = temp_tree(json!({
|
||||
"a": {
|
||||
"file1": "",
|
||||
"file2": "",
|
||||
"file3": "",
|
||||
},
|
||||
"b": {
|
||||
"c": {
|
||||
"file4": "",
|
||||
"file5": "",
|
||||
}
|
||||
}));
|
||||
}
|
||||
}));
|
||||
|
||||
let tree = app.add_model(|ctx| Worktree::new(dir.path(), ctx));
|
||||
let (file2, file3, file4, file5, non_existent_file) = app.read(|ctx| {
|
||||
(
|
||||
tree.file("a/file2", ctx),
|
||||
tree.file("a/file3", ctx),
|
||||
tree.file("b/c/file4", ctx),
|
||||
tree.file("b/c/file5", ctx),
|
||||
tree.file("a/filex", ctx),
|
||||
)
|
||||
});
|
||||
let tree = app.add_model(|ctx| Worktree::new(dir.path(), ctx));
|
||||
let (file2, file3, file4, file5, non_existent_file) = app.read(|ctx| {
|
||||
(
|
||||
tree.file("a/file2", ctx),
|
||||
tree.file("a/file3", ctx),
|
||||
tree.file("b/c/file4", ctx),
|
||||
tree.file("b/c/file5", ctx),
|
||||
tree.file("a/filex", ctx),
|
||||
)
|
||||
});
|
||||
|
||||
// The worktree hasn't scanned the directories containing these paths,
|
||||
// so it can't determine that the paths are deleted.
|
||||
// The worktree hasn't scanned the directories containing these paths,
|
||||
// so it can't determine that the paths are deleted.
|
||||
assert!(!file2.is_deleted());
|
||||
assert!(!file3.is_deleted());
|
||||
assert!(!file4.is_deleted());
|
||||
assert!(!file5.is_deleted());
|
||||
assert!(!non_existent_file.is_deleted());
|
||||
|
||||
// After scanning, the worktree knows which files exist and which don't.
|
||||
app.read(|ctx| tree.read(ctx).scan_complete()).await;
|
||||
assert!(!file2.is_deleted());
|
||||
assert!(!file3.is_deleted());
|
||||
assert!(!file4.is_deleted());
|
||||
assert!(!file5.is_deleted());
|
||||
assert!(non_existent_file.is_deleted());
|
||||
|
||||
tree.flush_fs_events(&app).await;
|
||||
std::fs::rename(dir.path().join("a/file3"), dir.path().join("b/c/file3")).unwrap();
|
||||
std::fs::remove_file(dir.path().join("b/c/file5")).unwrap();
|
||||
std::fs::rename(dir.path().join("b/c"), dir.path().join("d")).unwrap();
|
||||
std::fs::rename(dir.path().join("a/file2"), dir.path().join("a/file2.new")).unwrap();
|
||||
tree.update(&mut app, |tree, ctx| tree.next_scan_complete(ctx))
|
||||
.await;
|
||||
|
||||
app.read(|ctx| {
|
||||
assert_eq!(
|
||||
tree.read(ctx)
|
||||
.paths()
|
||||
.map(|p| p.to_str().unwrap())
|
||||
.collect::<Vec<_>>(),
|
||||
vec![
|
||||
"a",
|
||||
"a/file1",
|
||||
"a/file2.new",
|
||||
"b",
|
||||
"d",
|
||||
"d/file3",
|
||||
"d/file4"
|
||||
]
|
||||
);
|
||||
|
||||
assert_eq!(file2.path().to_str().unwrap(), "a/file2.new");
|
||||
assert_eq!(file4.path().as_ref(), Path::new("d/file4"));
|
||||
assert_eq!(file5.path().as_ref(), Path::new("d/file5"));
|
||||
assert!(!file2.is_deleted());
|
||||
assert!(!file3.is_deleted());
|
||||
assert!(!file4.is_deleted());
|
||||
assert!(!file5.is_deleted());
|
||||
assert!(!non_existent_file.is_deleted());
|
||||
assert!(file5.is_deleted());
|
||||
|
||||
// After scanning, the worktree knows which files exist and which don't.
|
||||
app.read(|ctx| tree.read(ctx).scan_complete()).await;
|
||||
assert!(!file2.is_deleted());
|
||||
assert!(!file3.is_deleted());
|
||||
assert!(!file4.is_deleted());
|
||||
assert!(!file5.is_deleted());
|
||||
assert!(non_existent_file.is_deleted());
|
||||
|
||||
tree.flush_fs_events(&app).await;
|
||||
std::fs::rename(dir.path().join("a/file3"), dir.path().join("b/c/file3")).unwrap();
|
||||
std::fs::remove_file(dir.path().join("b/c/file5")).unwrap();
|
||||
std::fs::rename(dir.path().join("b/c"), dir.path().join("d")).unwrap();
|
||||
std::fs::rename(dir.path().join("a/file2"), dir.path().join("a/file2.new")).unwrap();
|
||||
tree.update(&mut app, |tree, ctx| tree.next_scan_complete(ctx))
|
||||
.await;
|
||||
|
||||
app.read(|ctx| {
|
||||
assert_eq!(
|
||||
tree.read(ctx)
|
||||
.paths()
|
||||
.map(|p| p.to_str().unwrap())
|
||||
.collect::<Vec<_>>(),
|
||||
vec![
|
||||
"a",
|
||||
"a/file1",
|
||||
"a/file2.new",
|
||||
"b",
|
||||
"d",
|
||||
"d/file3",
|
||||
"d/file4"
|
||||
]
|
||||
);
|
||||
|
||||
assert_eq!(file2.path().to_str().unwrap(), "a/file2.new");
|
||||
assert_eq!(file4.path().as_ref(), Path::new("d/file4"));
|
||||
assert_eq!(file5.path().as_ref(), Path::new("d/file5"));
|
||||
assert!(!file2.is_deleted());
|
||||
assert!(!file4.is_deleted());
|
||||
assert!(file5.is_deleted());
|
||||
|
||||
// Right now, this rename isn't detected because the target path
|
||||
// no longer exists on the file system by the time we process the
|
||||
// rename event.
|
||||
assert_eq!(file3.path().as_ref(), Path::new("a/file3"));
|
||||
assert!(file3.is_deleted());
|
||||
});
|
||||
// Right now, this rename isn't detected because the target path
|
||||
// no longer exists on the file system by the time we process the
|
||||
// rename event.
|
||||
assert_eq!(file3.path().as_ref(), Path::new("a/file3"));
|
||||
assert!(file3.is_deleted());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rescan_with_gitignore() {
|
||||
App::test_async((), |mut app| async move {
|
||||
let dir = temp_tree(json!({
|
||||
".git": {},
|
||||
".gitignore": "ignored-dir\n",
|
||||
"tracked-dir": {
|
||||
"tracked-file1": "tracked contents",
|
||||
},
|
||||
"ignored-dir": {
|
||||
"ignored-file1": "ignored contents",
|
||||
}
|
||||
}));
|
||||
#[gpui::test]
|
||||
async fn test_rescan_with_gitignore(mut app: gpui::TestAppContext) {
|
||||
let dir = temp_tree(json!({
|
||||
".git": {},
|
||||
".gitignore": "ignored-dir\n",
|
||||
"tracked-dir": {
|
||||
"tracked-file1": "tracked contents",
|
||||
},
|
||||
"ignored-dir": {
|
||||
"ignored-file1": "ignored contents",
|
||||
}
|
||||
}));
|
||||
|
||||
let tree = app.add_model(|ctx| Worktree::new(dir.path(), ctx));
|
||||
app.read(|ctx| tree.read(ctx).scan_complete()).await;
|
||||
tree.flush_fs_events(&app).await;
|
||||
app.read(|ctx| {
|
||||
let tree = tree.read(ctx);
|
||||
let tracked = tree.entry_for_path("tracked-dir/tracked-file1").unwrap();
|
||||
let ignored = tree.entry_for_path("ignored-dir/ignored-file1").unwrap();
|
||||
assert_eq!(tracked.is_ignored(), false);
|
||||
assert_eq!(ignored.is_ignored(), true);
|
||||
});
|
||||
let tree = app.add_model(|ctx| Worktree::new(dir.path(), ctx));
|
||||
app.read(|ctx| tree.read(ctx).scan_complete()).await;
|
||||
tree.flush_fs_events(&app).await;
|
||||
app.read(|ctx| {
|
||||
let tree = tree.read(ctx);
|
||||
let tracked = tree.entry_for_path("tracked-dir/tracked-file1").unwrap();
|
||||
let ignored = tree.entry_for_path("ignored-dir/ignored-file1").unwrap();
|
||||
assert_eq!(tracked.is_ignored(), false);
|
||||
assert_eq!(ignored.is_ignored(), true);
|
||||
});
|
||||
|
||||
fs::write(dir.path().join("tracked-dir/tracked-file2"), "").unwrap();
|
||||
fs::write(dir.path().join("ignored-dir/ignored-file2"), "").unwrap();
|
||||
tree.update(&mut app, |tree, ctx| tree.next_scan_complete(ctx))
|
||||
.await;
|
||||
app.read(|ctx| {
|
||||
let tree = tree.read(ctx);
|
||||
let dot_git = tree.entry_for_path(".git").unwrap();
|
||||
let tracked = tree.entry_for_path("tracked-dir/tracked-file2").unwrap();
|
||||
let ignored = tree.entry_for_path("ignored-dir/ignored-file2").unwrap();
|
||||
assert_eq!(tracked.is_ignored(), false);
|
||||
assert_eq!(ignored.is_ignored(), true);
|
||||
assert_eq!(dot_git.is_ignored(), true);
|
||||
});
|
||||
fs::write(dir.path().join("tracked-dir/tracked-file2"), "").unwrap();
|
||||
fs::write(dir.path().join("ignored-dir/ignored-file2"), "").unwrap();
|
||||
tree.update(&mut app, |tree, ctx| tree.next_scan_complete(ctx))
|
||||
.await;
|
||||
app.read(|ctx| {
|
||||
let tree = tree.read(ctx);
|
||||
let dot_git = tree.entry_for_path(".git").unwrap();
|
||||
let tracked = tree.entry_for_path("tracked-dir/tracked-file2").unwrap();
|
||||
let ignored = tree.entry_for_path("ignored-dir/ignored-file2").unwrap();
|
||||
assert_eq!(tracked.is_ignored(), false);
|
||||
assert_eq!(ignored.is_ignored(), true);
|
||||
assert_eq!(dot_git.is_ignored(), true);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user