Implement simulated prompts in TestPlatform

Max Brunsfeld created

Change summary

crates/gpui2/src/app/test_context.rs       | 19 ++---
crates/gpui2/src/platform/test/platform.rs | 78 ++++++++++++++++++++---
crates/gpui2/src/platform/test/window.rs   | 28 ++++---
crates/project_panel2/src/project_panel.rs |  8 -
4 files changed, 96 insertions(+), 37 deletions(-)

Detailed changes

crates/gpui2/src/app/test_context.rs 🔗

@@ -14,6 +14,7 @@ pub struct TestAppContext {
     pub background_executor: BackgroundExecutor,
     pub foreground_executor: ForegroundExecutor,
     pub dispatcher: TestDispatcher,
+    pub test_platform: Rc<TestPlatform>,
 }
 
 impl Context for TestAppContext {
@@ -77,17 +78,15 @@ impl TestAppContext {
         let arc_dispatcher = Arc::new(dispatcher.clone());
         let background_executor = BackgroundExecutor::new(arc_dispatcher.clone());
         let foreground_executor = ForegroundExecutor::new(arc_dispatcher);
-        let platform = Rc::new(TestPlatform::new(
-            background_executor.clone(),
-            foreground_executor.clone(),
-        ));
+        let platform = TestPlatform::new(background_executor.clone(), foreground_executor.clone());
         let asset_source = Arc::new(());
         let http_client = util::http::FakeHttpClient::with_404_response();
         Self {
-            app: AppContext::new(platform, asset_source, http_client),
+            app: AppContext::new(platform.clone(), asset_source, http_client),
             background_executor,
             foreground_executor,
             dispatcher: dispatcher.clone(),
+            test_platform: platform,
         }
     }
 
@@ -154,17 +153,17 @@ impl TestAppContext {
 
     pub fn simulate_new_path_selection(
         &self,
-        _select_path: impl FnOnce(&std::path::Path) -> Option<std::path::PathBuf>,
+        select_path: impl FnOnce(&std::path::Path) -> Option<std::path::PathBuf>,
     ) {
-        //
+        self.test_platform.simulate_new_path_selection(select_path);
     }
 
-    pub fn simulate_prompt_answer(&self, _button_ix: usize) {
-        //
+    pub fn simulate_prompt_answer(&self, button_ix: usize) {
+        self.test_platform.simulate_prompt_answer(button_ix);
     }
 
     pub fn has_pending_prompt(&self) -> bool {
-        false
+        self.test_platform.has_pending_prompt()
     }
 
     pub fn spawn<Fut, R>(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task<R>

crates/gpui2/src/platform/test/platform.rs 🔗

@@ -3,8 +3,15 @@ use crate::{
     PlatformDisplay, PlatformTextSystem, TestDisplay, TestWindow, WindowOptions,
 };
 use anyhow::{anyhow, Result};
+use collections::VecDeque;
+use futures::channel::oneshot;
 use parking_lot::Mutex;
-use std::{rc::Rc, sync::Arc};
+use std::{
+    cell::RefCell,
+    path::PathBuf,
+    rc::{Rc, Weak},
+    sync::Arc,
+};
 
 pub struct TestPlatform {
     background_executor: BackgroundExecutor,
@@ -13,18 +20,60 @@ pub struct TestPlatform {
     active_window: Arc<Mutex<Option<AnyWindowHandle>>>,
     active_display: Rc<dyn PlatformDisplay>,
     active_cursor: Mutex<CursorStyle>,
+    pub(crate) prompts: RefCell<TestPrompts>,
+    weak: Weak<Self>,
+}
+
+#[derive(Default)]
+pub(crate) struct TestPrompts {
+    multiple_choice: VecDeque<oneshot::Sender<usize>>,
+    new_path: VecDeque<(PathBuf, oneshot::Sender<Option<PathBuf>>)>,
 }
 
 impl TestPlatform {
-    pub fn new(executor: BackgroundExecutor, foreground_executor: ForegroundExecutor) -> Self {
-        TestPlatform {
+    pub fn new(executor: BackgroundExecutor, foreground_executor: ForegroundExecutor) -> Rc<Self> {
+        Rc::new_cyclic(|weak| TestPlatform {
             background_executor: executor,
             foreground_executor,
-
+            prompts: Default::default(),
             active_cursor: Default::default(),
             active_display: Rc::new(TestDisplay::new()),
             active_window: Default::default(),
-        }
+            weak: weak.clone(),
+        })
+    }
+
+    pub(crate) fn simulate_new_path_selection(
+        &self,
+        select_path: impl FnOnce(&std::path::Path) -> Option<std::path::PathBuf>,
+    ) {
+        let (path, tx) = self
+            .prompts
+            .borrow_mut()
+            .new_path
+            .pop_front()
+            .expect("no pending new path prompt");
+        tx.send(select_path(&path)).ok();
+    }
+
+    pub(crate) fn simulate_prompt_answer(&self, response_ix: usize) {
+        let tx = self
+            .prompts
+            .borrow_mut()
+            .multiple_choice
+            .pop_front()
+            .expect("no pending multiple choice prompt");
+        tx.send(response_ix).ok();
+    }
+
+    pub(crate) fn has_pending_prompt(&self) -> bool {
+        !self.prompts.borrow().multiple_choice.is_empty()
+    }
+
+    pub(crate) fn prompt(&self) -> oneshot::Receiver<usize> {
+        let (tx, rx) = oneshot::channel();
+        self.prompts.borrow_mut().multiple_choice.push_back(tx);
+        rx
     }
 }
 
@@ -88,7 +137,11 @@ impl Platform for TestPlatform {
         options: WindowOptions,
     ) -> Box<dyn crate::PlatformWindow> {
         *self.active_window.lock() = Some(handle);
-        Box::new(TestWindow::new(options, self.active_display.clone()))
+        Box::new(TestWindow::new(
+            options,
+            self.weak.clone(),
+            self.active_display.clone(),
+        ))
     }
 
     fn set_display_link_output_callback(
@@ -118,15 +171,20 @@ impl Platform for TestPlatform {
     fn prompt_for_paths(
         &self,
         _options: crate::PathPromptOptions,
-    ) -> futures::channel::oneshot::Receiver<Option<Vec<std::path::PathBuf>>> {
+    ) -> oneshot::Receiver<Option<Vec<std::path::PathBuf>>> {
         unimplemented!()
     }
 
     fn prompt_for_new_path(
         &self,
-        _directory: &std::path::Path,
-    ) -> futures::channel::oneshot::Receiver<Option<std::path::PathBuf>> {
-        unimplemented!()
+        directory: &std::path::Path,
+    ) -> oneshot::Receiver<Option<std::path::PathBuf>> {
+        let (tx, rx) = oneshot::channel();
+        self.prompts
+            .borrow_mut()
+            .new_path
+            .push_back((directory.to_path_buf(), tx));
+        rx
     }
 
     fn reveal_path(&self, _path: &std::path::Path) {

crates/gpui2/src/platform/test/window.rs 🔗

@@ -1,15 +1,13 @@
-use std::{
-    rc::Rc,
-    sync::{self, Arc},
+use crate::{
+    px, AtlasKey, AtlasTextureId, AtlasTile, Pixels, PlatformAtlas, PlatformDisplay,
+    PlatformInputHandler, PlatformWindow, Point, Scene, Size, TestPlatform, TileId,
+    WindowAppearance, WindowBounds, WindowOptions,
 };
-
 use collections::HashMap;
 use parking_lot::Mutex;
-
-use crate::{
-    px, AtlasKey, AtlasTextureId, AtlasTile, Pixels, PlatformAtlas, PlatformDisplay,
-    PlatformInputHandler, PlatformWindow, Point, Scene, Size, TileId, WindowAppearance,
-    WindowBounds, WindowOptions,
+use std::{
+    rc::{Rc, Weak},
+    sync::{self, Arc},
 };
 
 #[derive(Default)]
@@ -25,16 +23,22 @@ pub struct TestWindow {
     current_scene: Mutex<Option<Scene>>,
     display: Rc<dyn PlatformDisplay>,
     input_handler: Option<Box<dyn PlatformInputHandler>>,
-
     handlers: Mutex<Handlers>,
+    platform: Weak<TestPlatform>,
     sprite_atlas: Arc<dyn PlatformAtlas>,
 }
+
 impl TestWindow {
-    pub fn new(options: WindowOptions, display: Rc<dyn PlatformDisplay>) -> Self {
+    pub fn new(
+        options: WindowOptions,
+        platform: Weak<TestPlatform>,
+        display: Rc<dyn PlatformDisplay>,
+    ) -> Self {
         Self {
             bounds: options.bounds,
             current_scene: Default::default(),
             display,
+            platform,
             input_handler: None,
             sprite_atlas: Arc::new(TestAtlas::new()),
             handlers: Default::default(),
@@ -89,7 +93,7 @@ impl PlatformWindow for TestWindow {
         _msg: &str,
         _answers: &[&str],
     ) -> futures::channel::oneshot::Receiver<usize> {
-        todo!()
+        self.platform.upgrade().expect("platform dropped").prompt()
     }
 
     fn activate(&self) {

crates/project_panel2/src/project_panel.rs 🔗

@@ -2033,7 +2033,7 @@ mod tests {
         );
     }
 
-    #[gpui::test(iterations = 30)]
+    #[gpui::test(iterations = 10)]
     async fn test_adding_directories_via_file(cx: &mut gpui::TestAppContext) {
         init_test(cx);
 
@@ -2653,17 +2653,15 @@ mod tests {
             .unwrap();
 
         // "Save as"" the buffer, creating a new backing file for it
-        workspace
+        let save_task = workspace
             .update(cx, |workspace, cx| {
                 workspace.save_active_item(workspace::SaveIntent::Save, cx)
             })
-            .unwrap()
-            .await
             .unwrap();
 
         cx.executor().run_until_parked();
         cx.simulate_new_path_selection(|_| Some(PathBuf::from("/root/new")));
-        cx.executor().run_until_parked();
+        save_task.await.unwrap();
 
         // Rename the file
         select_path(&panel, "root/new", cx);