git_ui: Dismiss modal when viewing stash (#48491)

Dino created

Ensure that `git_ui::stash_picker::StashList.handle_show_toast` emits a
`DismissEvent` so that, when the user confirms they wish to view a stash
entry, the modal is actually dismissed and does not block the user's
view.

Release Notes:

- Fixed issue where viewing a stash entry would not dismiss the modal

Change summary

crates/git_ui/src/stash_picker.rs | 94 ++++++++++++++++++++++++++++++++
1 file changed, 93 insertions(+), 1 deletion(-)

Detailed changes

crates/git_ui/src/stash_picker.rs 🔗

@@ -183,7 +183,8 @@ impl StashList {
                 .delegate
                 .show_stash_at(picker.delegate.selected_index(), window, cx);
         });
-        cx.notify();
+
+        cx.emit(DismissEvent);
     }
 
     pub fn handle_modifiers_changed(
@@ -582,3 +583,94 @@ impl PickerDelegate for StashListDelegate {
         )
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use std::str::FromStr;
+
+    use super::*;
+    use git::{Oid, stash::StashEntry};
+    use gpui::{TestAppContext, VisualTestContext, rems};
+    use picker::PickerDelegate;
+    use project::{FakeFs, Project};
+    use settings::SettingsStore;
+    use workspace::Workspace;
+
+    fn init_test(cx: &mut TestAppContext) {
+        cx.update(|cx| {
+            let settings_store = SettingsStore::test(cx);
+            cx.set_global(settings_store);
+
+            theme::init(theme::LoadThemes::JustBase, cx);
+            editor::init(cx);
+        })
+    }
+
+    /// Convenience function for creating `StashEntry` instances during tests.
+    /// Feel free to update in case you need to provide extra fields.
+    fn stash_entry(index: usize, message: &str, branch: Option<&str>) -> StashEntry {
+        let oid = Oid::from_str(&format!("{:0>40x}", index)).unwrap();
+
+        StashEntry {
+            index,
+            oid,
+            message: message.to_string(),
+            branch: branch.map(Into::into),
+            timestamp: 1000 - index as i64,
+        }
+    }
+
+    #[gpui::test]
+    async fn test_show_stash_dismisses(cx: &mut TestAppContext) {
+        init_test(cx);
+
+        let fs = FakeFs::new(cx.executor());
+        let project = Project::test(fs, [], cx).await;
+        let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
+        let cx = &mut VisualTestContext::from_window(*workspace, cx);
+        let stash_entries = vec![
+            stash_entry(0, "stash #0", Some("main")),
+            stash_entry(1, "stash #1", Some("develop")),
+        ];
+
+        let stash_list = workspace
+            .update(cx, |workspace, window, cx| {
+                let weak_workspace = workspace.weak_handle();
+
+                workspace.toggle_modal(window, cx, move |window, cx| {
+                    StashList::new(None, weak_workspace, rems(34.), window, cx)
+                });
+
+                assert!(workspace.active_modal::<StashList>(cx).is_some());
+                workspace.active_modal::<StashList>(cx).unwrap()
+            })
+            .unwrap();
+
+        cx.run_until_parked();
+        stash_list.update(cx, |stash_list, cx| {
+            stash_list.picker.update(cx, |picker, _| {
+                picker.delegate.all_stash_entries = Some(stash_entries);
+            });
+        });
+
+        stash_list
+            .update_in(cx, |stash_list, window, cx| {
+                stash_list.picker.update(cx, |picker, cx| {
+                    picker.delegate.update_matches(String::new(), window, cx)
+                })
+            })
+            .await;
+
+        cx.run_until_parked();
+        stash_list.update_in(cx, |stash_list, window, cx| {
+            assert_eq!(stash_list.picker.read(cx).delegate.matches.len(), 2);
+            stash_list.handle_show_stash(&Default::default(), window, cx);
+        });
+
+        workspace
+            .update(cx, |workspace, _, cx| {
+                assert!(workspace.active_modal::<StashList>(cx).is_none());
+            })
+            .unwrap();
+    }
+}