@@ -1532,7 +1532,6 @@ impl ProjectPanel {
.into_any_named("project panel entry")
}
- // TODO kb tests
fn reveal_entry(
&mut self,
project: ModelHandle<Project>,
@@ -2982,6 +2981,445 @@ mod tests {
);
}
+ #[gpui::test]
+ async fn test_autoreveal_and_gitignored_files(cx: &mut gpui::TestAppContext) {
+ init_test_with_editor(cx);
+ cx.update(|cx| {
+ cx.update_global::<SettingsStore, _, _>(|store, cx| {
+ store.update_user_settings::<ProjectSettings>(cx, |project_settings| {
+ project_settings.file_scan_exclusions = Some(Vec::new());
+ });
+ store.update_user_settings::<ProjectPanelSettings>(cx, |project_panel_settings| {
+ project_panel_settings.auto_reveal_entries = Some(false)
+ });
+ })
+ });
+
+ let fs = FakeFs::new(cx.background());
+ fs.insert_tree(
+ "/project_root",
+ json!({
+ ".git": {},
+ ".gitignore": "**/gitignored_dir",
+ "dir_1": {
+ "file_1.py": "# File 1_1 contents",
+ "file_2.py": "# File 1_2 contents",
+ "file_3.py": "# File 1_3 contents",
+ "gitignored_dir": {
+ "file_a.py": "# File contents",
+ "file_b.py": "# File contents",
+ "file_c.py": "# File contents",
+ },
+ },
+ "dir_2": {
+ "file_1.py": "# File 2_1 contents",
+ "file_2.py": "# File 2_2 contents",
+ "file_3.py": "# File 2_3 contents",
+ }
+ }),
+ )
+ .await;
+
+ let project = Project::test(fs.clone(), ["/project_root".as_ref()], cx).await;
+ let workspace = cx
+ .add_window(|cx| Workspace::test_new(project.clone(), cx))
+ .root(cx);
+ let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
+
+ assert_eq!(
+ visible_entries_as_strings(&panel, 0..20, cx),
+ &[
+ "v project_root",
+ " > .git",
+ " > dir_1",
+ " > dir_2",
+ " .gitignore",
+ ]
+ );
+
+ let dir_1_file = find_project_entry(&panel, "project_root/dir_1/file_1.py", cx)
+ .expect("dir 1 file is not ignored and should have an entry");
+ let dir_2_file = find_project_entry(&panel, "project_root/dir_2/file_1.py", cx)
+ .expect("dir 2 file is not ignored and should have an entry");
+ let gitignored_dir_file =
+ find_project_entry(&panel, "project_root/dir_1/gitignored_dir/file_a.py", cx);
+ assert_eq!(
+ gitignored_dir_file, None,
+ "File in the gitignored dir should not have an entry before its dir is toggled"
+ );
+
+ toggle_expand_dir(&panel, "project_root/dir_1", cx);
+ toggle_expand_dir(&panel, "project_root/dir_1/gitignored_dir", cx);
+ cx.foreground().run_until_parked();
+ assert_eq!(
+ visible_entries_as_strings(&panel, 0..20, cx),
+ &[
+ "v project_root",
+ " > .git",
+ " v dir_1",
+ " v gitignored_dir <== selected",
+ " file_a.py",
+ " file_b.py",
+ " file_c.py",
+ " file_1.py",
+ " file_2.py",
+ " file_3.py",
+ " > dir_2",
+ " .gitignore",
+ ],
+ "Should show gitignored dir file list in the project panel"
+ );
+ let gitignored_dir_file =
+ find_project_entry(&panel, "project_root/dir_1/gitignored_dir/file_a.py", cx)
+ .expect("after gitignored dir got opened, a file entry should be present");
+
+ toggle_expand_dir(&panel, "project_root/dir_1/gitignored_dir", cx);
+ toggle_expand_dir(&panel, "project_root/dir_1", cx);
+ assert_eq!(
+ visible_entries_as_strings(&panel, 0..20, cx),
+ &[
+ "v project_root",
+ " > .git",
+ " > dir_1 <== selected",
+ " > dir_2",
+ " .gitignore",
+ ],
+ "Should hide all dir contents again and prepare for the auto reveal test"
+ );
+
+ for file_entry in [dir_1_file, dir_2_file, gitignored_dir_file] {
+ panel.update(cx, |panel, cx| {
+ panel.project.update(cx, |_, cx| {
+ cx.emit(project::Event::ActiveEntryChanged(Some(file_entry)))
+ })
+ });
+ cx.foreground().run_until_parked();
+ assert_eq!(
+ visible_entries_as_strings(&panel, 0..20, cx),
+ &[
+ "v project_root",
+ " > .git",
+ " > dir_1 <== selected",
+ " > dir_2",
+ " .gitignore",
+ ],
+ "When no auto reveal is enabled, the selected entry should not be revealed in the project panel"
+ );
+ }
+
+ cx.update(|cx| {
+ cx.update_global::<SettingsStore, _, _>(|store, cx| {
+ store.update_user_settings::<ProjectPanelSettings>(cx, |project_panel_settings| {
+ project_panel_settings.auto_reveal_entries = Some(true)
+ });
+ })
+ });
+
+ panel.update(cx, |panel, cx| {
+ panel.project.update(cx, |_, cx| {
+ cx.emit(project::Event::ActiveEntryChanged(Some(dir_1_file)))
+ })
+ });
+ cx.foreground().run_until_parked();
+ assert_eq!(
+ visible_entries_as_strings(&panel, 0..20, cx),
+ &[
+ "v project_root",
+ " > .git",
+ " v dir_1",
+ " > gitignored_dir",
+ " file_1.py <== selected",
+ " file_2.py",
+ " file_3.py",
+ " > dir_2",
+ " .gitignore",
+ ],
+ "When auto reveal is enabled, not ignored dir_1 entry should be revealed"
+ );
+
+ panel.update(cx, |panel, cx| {
+ panel.project.update(cx, |_, cx| {
+ cx.emit(project::Event::ActiveEntryChanged(Some(dir_2_file)))
+ })
+ });
+ cx.foreground().run_until_parked();
+ assert_eq!(
+ visible_entries_as_strings(&panel, 0..20, cx),
+ &[
+ "v project_root",
+ " > .git",
+ " v dir_1",
+ " > gitignored_dir",
+ " file_1.py",
+ " file_2.py",
+ " file_3.py",
+ " v dir_2",
+ " file_1.py <== selected",
+ " file_2.py",
+ " file_3.py",
+ " .gitignore",
+ ],
+ "When auto reveal is enabled, not ignored dir_2 entry should be revealed"
+ );
+
+ panel.update(cx, |panel, cx| {
+ panel.project.update(cx, |_, cx| {
+ cx.emit(project::Event::ActiveEntryChanged(Some(
+ gitignored_dir_file,
+ )))
+ })
+ });
+ cx.foreground().run_until_parked();
+ assert_eq!(
+ visible_entries_as_strings(&panel, 0..20, cx),
+ &[
+ "v project_root",
+ " > .git",
+ " v dir_1",
+ " > gitignored_dir",
+ " file_1.py",
+ " file_2.py",
+ " file_3.py",
+ " v dir_2",
+ " file_1.py <== selected",
+ " file_2.py",
+ " file_3.py",
+ " .gitignore",
+ ],
+ "When auto reveal is enabled, a gitignored selected entry should not be revealed in the project panel"
+ );
+
+ panel.update(cx, |panel, cx| {
+ panel.project.update(cx, |_, cx| {
+ cx.emit(project::Event::RevealInProjectPanel(gitignored_dir_file))
+ })
+ });
+ cx.foreground().run_until_parked();
+ assert_eq!(
+ visible_entries_as_strings(&panel, 0..20, cx),
+ &[
+ "v project_root",
+ " > .git",
+ " v dir_1",
+ " v gitignored_dir",
+ " file_a.py <== selected",
+ " file_b.py",
+ " file_c.py",
+ " file_1.py",
+ " file_2.py",
+ " file_3.py",
+ " v dir_2",
+ " file_1.py",
+ " file_2.py",
+ " file_3.py",
+ " .gitignore",
+ ],
+ "When a gitignored entry is explicitly revealed, it should be shown in the project tree"
+ );
+ }
+
+ #[gpui::test]
+ async fn test_explicit_reveal(cx: &mut gpui::TestAppContext) {
+ init_test_with_editor(cx);
+ cx.update(|cx| {
+ cx.update_global::<SettingsStore, _, _>(|store, cx| {
+ store.update_user_settings::<ProjectSettings>(cx, |project_settings| {
+ project_settings.file_scan_exclusions = Some(Vec::new());
+ });
+ store.update_user_settings::<ProjectPanelSettings>(cx, |project_panel_settings| {
+ project_panel_settings.auto_reveal_entries = Some(false)
+ });
+ })
+ });
+
+ let fs = FakeFs::new(cx.background());
+ fs.insert_tree(
+ "/project_root",
+ json!({
+ ".git": {},
+ ".gitignore": "**/gitignored_dir",
+ "dir_1": {
+ "file_1.py": "# File 1_1 contents",
+ "file_2.py": "# File 1_2 contents",
+ "file_3.py": "# File 1_3 contents",
+ "gitignored_dir": {
+ "file_a.py": "# File contents",
+ "file_b.py": "# File contents",
+ "file_c.py": "# File contents",
+ },
+ },
+ "dir_2": {
+ "file_1.py": "# File 2_1 contents",
+ "file_2.py": "# File 2_2 contents",
+ "file_3.py": "# File 2_3 contents",
+ }
+ }),
+ )
+ .await;
+
+ let project = Project::test(fs.clone(), ["/project_root".as_ref()], cx).await;
+ let workspace = cx
+ .add_window(|cx| Workspace::test_new(project.clone(), cx))
+ .root(cx);
+ let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
+
+ assert_eq!(
+ visible_entries_as_strings(&panel, 0..20, cx),
+ &[
+ "v project_root",
+ " > .git",
+ " > dir_1",
+ " > dir_2",
+ " .gitignore",
+ ]
+ );
+
+ let dir_1_file = find_project_entry(&panel, "project_root/dir_1/file_1.py", cx)
+ .expect("dir 1 file is not ignored and should have an entry");
+ let dir_2_file = find_project_entry(&panel, "project_root/dir_2/file_1.py", cx)
+ .expect("dir 2 file is not ignored and should have an entry");
+ let gitignored_dir_file =
+ find_project_entry(&panel, "project_root/dir_1/gitignored_dir/file_a.py", cx);
+ assert_eq!(
+ gitignored_dir_file, None,
+ "File in the gitignored dir should not have an entry before its dir is toggled"
+ );
+
+ toggle_expand_dir(&panel, "project_root/dir_1", cx);
+ toggle_expand_dir(&panel, "project_root/dir_1/gitignored_dir", cx);
+ cx.foreground().run_until_parked();
+ assert_eq!(
+ visible_entries_as_strings(&panel, 0..20, cx),
+ &[
+ "v project_root",
+ " > .git",
+ " v dir_1",
+ " v gitignored_dir <== selected",
+ " file_a.py",
+ " file_b.py",
+ " file_c.py",
+ " file_1.py",
+ " file_2.py",
+ " file_3.py",
+ " > dir_2",
+ " .gitignore",
+ ],
+ "Should show gitignored dir file list in the project panel"
+ );
+ let gitignored_dir_file =
+ find_project_entry(&panel, "project_root/dir_1/gitignored_dir/file_a.py", cx)
+ .expect("after gitignored dir got opened, a file entry should be present");
+
+ toggle_expand_dir(&panel, "project_root/dir_1/gitignored_dir", cx);
+ toggle_expand_dir(&panel, "project_root/dir_1", cx);
+ assert_eq!(
+ visible_entries_as_strings(&panel, 0..20, cx),
+ &[
+ "v project_root",
+ " > .git",
+ " > dir_1 <== selected",
+ " > dir_2",
+ " .gitignore",
+ ],
+ "Should hide all dir contents again and prepare for the explicit reveal test"
+ );
+
+ for file_entry in [dir_1_file, dir_2_file, gitignored_dir_file] {
+ panel.update(cx, |panel, cx| {
+ panel.project.update(cx, |_, cx| {
+ cx.emit(project::Event::ActiveEntryChanged(Some(file_entry)))
+ })
+ });
+ cx.foreground().run_until_parked();
+ assert_eq!(
+ visible_entries_as_strings(&panel, 0..20, cx),
+ &[
+ "v project_root",
+ " > .git",
+ " > dir_1 <== selected",
+ " > dir_2",
+ " .gitignore",
+ ],
+ "When no auto reveal is enabled, the selected entry should not be revealed in the project panel"
+ );
+ }
+
+ panel.update(cx, |panel, cx| {
+ panel.project.update(cx, |_, cx| {
+ cx.emit(project::Event::RevealInProjectPanel(dir_1_file))
+ })
+ });
+ cx.foreground().run_until_parked();
+ assert_eq!(
+ visible_entries_as_strings(&panel, 0..20, cx),
+ &[
+ "v project_root",
+ " > .git",
+ " v dir_1",
+ " > gitignored_dir",
+ " file_1.py <== selected",
+ " file_2.py",
+ " file_3.py",
+ " > dir_2",
+ " .gitignore",
+ ],
+ "With no auto reveal, explicit reveal should show the dir_1 entry in the project panel"
+ );
+
+ panel.update(cx, |panel, cx| {
+ panel.project.update(cx, |_, cx| {
+ cx.emit(project::Event::RevealInProjectPanel(dir_2_file))
+ })
+ });
+ cx.foreground().run_until_parked();
+ assert_eq!(
+ visible_entries_as_strings(&panel, 0..20, cx),
+ &[
+ "v project_root",
+ " > .git",
+ " v dir_1",
+ " > gitignored_dir",
+ " file_1.py",
+ " file_2.py",
+ " file_3.py",
+ " v dir_2",
+ " file_1.py <== selected",
+ " file_2.py",
+ " file_3.py",
+ " .gitignore",
+ ],
+ "With no auto reveal, explicit reveal should show the dir_2 entry in the project panel"
+ );
+
+ panel.update(cx, |panel, cx| {
+ panel.project.update(cx, |_, cx| {
+ cx.emit(project::Event::RevealInProjectPanel(gitignored_dir_file))
+ })
+ });
+ cx.foreground().run_until_parked();
+ assert_eq!(
+ visible_entries_as_strings(&panel, 0..20, cx),
+ &[
+ "v project_root",
+ " > .git",
+ " v dir_1",
+ " v gitignored_dir",
+ " file_a.py <== selected",
+ " file_b.py",
+ " file_c.py",
+ " file_1.py",
+ " file_2.py",
+ " file_3.py",
+ " v dir_2",
+ " file_1.py",
+ " file_2.py",
+ " file_3.py",
+ " .gitignore",
+ ],
+ "With no auto reveal, explicit reveal should show the gitignored entry in the project panel"
+ );
+ }
+
fn toggle_expand_dir(
panel: &ViewHandle<ProjectPanel>,
path: impl AsRef<Path>,
@@ -3019,10 +3457,27 @@ mod tests {
return;
}
}
- panic!("no worktree for path {:?}", path);
+ panic!("no worktree for path {path:?}");
});
}
+ fn find_project_entry(
+ panel: &ViewHandle<ProjectPanel>,
+ path: impl AsRef<Path>,
+ cx: &mut TestAppContext,
+ ) -> Option<ProjectEntryId> {
+ let path = path.as_ref();
+ panel.update(cx, |panel, cx| {
+ for worktree in panel.project.read(cx).worktrees(cx).collect::<Vec<_>>() {
+ let worktree = worktree.read(cx);
+ if let Ok(relative_path) = path.strip_prefix(worktree.root_name()) {
+ return worktree.entry_for_path(relative_path).map(|entry| entry.id);
+ }
+ }
+ panic!("no worktree for path {path:?}");
+ })
+ }
+
fn visible_entries_as_strings(
panel: &ViewHandle<ProjectPanel>,
range: Range<usize>,