Add tests

Kirill Bulatov created

Change summary

crates/project_panel/src/project_panel.rs | 459 ++++++++++++++++++++++++
1 file changed, 457 insertions(+), 2 deletions(-)

Detailed changes

crates/project_panel/src/project_panel.rs 🔗

@@ -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>,