Detailed changes
@@ -354,129 +354,117 @@ impl std::fmt::Debug for Command {
}
}
-// #[cfg(test)]
-// mod tests {
-// use std::sync::Arc;
-
-// use super::*;
-// use editor::Editor;
-// use gpui::{executor::Deterministic, TestAppContext};
-// use project::Project;
-// use workspace::{AppState, Workspace};
-
-// #[test]
-// fn test_humanize_action_name() {
-// assert_eq!(
-// humanize_action_name("editor::GoToDefinition"),
-// "editor: go to definition"
-// );
-// assert_eq!(
-// humanize_action_name("editor::Backspace"),
-// "editor: backspace"
-// );
-// assert_eq!(
-// humanize_action_name("go_to_line::Deploy"),
-// "go to line: deploy"
-// );
-// }
-
-// #[gpui::test]
-// async fn test_command_palette(deterministic: Arc<Deterministic>, cx: &mut TestAppContext) {
-// let app_state = init_test(cx);
-
-// let project = Project::test(app_state.fs.clone(), [], cx).await;
-// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
-// let workspace = window.root(cx);
-// let editor = window.add_view(cx, |cx| {
-// let mut editor = Editor::single_line(None, cx);
-// editor.set_text("abc", cx);
-// editor
-// });
-
-// workspace.update(cx, |workspace, cx| {
-// cx.focus(&editor);
-// workspace.add_item(Box::new(editor.clone()), cx)
-// });
-
-// workspace.update(cx, |workspace, cx| {
-// toggle_command_palette(workspace, &Toggle, cx);
-// });
-
-// let palette = workspace.read_with(cx, |workspace, _| {
-// workspace.modal::<CommandPalette>().unwrap()
-// });
-
-// palette
-// .update(cx, |palette, cx| {
-// // Fill up palette's command list by running an empty query;
-// // we only need it to subsequently assert that the palette is initially
-// // sorted by command's name.
-// palette.delegate_mut().update_matches("".to_string(), cx)
-// })
-// .await;
-
-// palette.update(cx, |palette, _| {
-// let is_sorted =
-// |actions: &[Command]| actions.windows(2).all(|pair| pair[0].name <= pair[1].name);
-// assert!(is_sorted(&palette.delegate().actions));
-// });
-
-// palette
-// .update(cx, |palette, cx| {
-// palette
-// .delegate_mut()
-// .update_matches("bcksp".to_string(), cx)
-// })
-// .await;
-
-// palette.update(cx, |palette, cx| {
-// assert_eq!(palette.delegate().matches[0].string, "editor: backspace");
-// palette.confirm(&Default::default(), cx);
-// });
-// deterministic.run_until_parked();
-// editor.read_with(cx, |editor, cx| {
-// assert_eq!(editor.text(cx), "ab");
-// });
-
-// // Add namespace filter, and redeploy the palette
-// cx.update(|cx| {
-// cx.update_default_global::<CommandPaletteFilter, _, _>(|filter, _| {
-// filter.filtered_namespaces.insert("editor");
-// })
-// });
-
-// workspace.update(cx, |workspace, cx| {
-// toggle_command_palette(workspace, &Toggle, cx);
-// });
-
-// // Assert editor command not present
-// let palette = workspace.read_with(cx, |workspace, _| {
-// workspace.modal::<CommandPalette>().unwrap()
-// });
-
-// palette
-// .update(cx, |palette, cx| {
-// palette
-// .delegate_mut()
-// .update_matches("bcksp".to_string(), cx)
-// })
-// .await;
-
-// palette.update(cx, |palette, _| {
-// assert!(palette.delegate().matches.is_empty())
-// });
-// }
-
-// fn init_test(cx: &mut TestAppContext) -> Arc<AppState> {
-// cx.update(|cx| {
-// let app_state = AppState::test(cx);
-// theme::init(cx);
-// language::init(cx);
-// editor::init(cx);
-// workspace::init(app_state.clone(), cx);
-// init(cx);
-// Project::init_settings(cx);
-// app_state
-// })
-// }
-// }
+#[cfg(test)]
+mod tests {
+ use std::sync::Arc;
+
+ use super::*;
+ use editor::Editor;
+ use gpui::TestAppContext;
+ use project::Project;
+ use workspace::{AppState, Workspace};
+
+ #[test]
+ fn test_humanize_action_name() {
+ assert_eq!(
+ humanize_action_name("editor::GoToDefinition"),
+ "editor: go to definition"
+ );
+ assert_eq!(
+ humanize_action_name("editor::Backspace"),
+ "editor: backspace"
+ );
+ assert_eq!(
+ humanize_action_name("go_to_line::Deploy"),
+ "go to line: deploy"
+ );
+ }
+
+ #[gpui::test]
+ async fn test_command_palette(cx: &mut TestAppContext) {
+ let app_state = init_test(cx);
+
+ let project = Project::test(app_state.fs.clone(), [], cx).await;
+ let (workspace, mut cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
+ let cx = &mut cx;
+
+ let editor = cx.build_view(|cx| {
+ let mut editor = Editor::single_line(cx);
+ editor.set_text("abc", cx);
+ editor
+ });
+
+ workspace.update(cx, |workspace, cx| {
+ workspace.add_item(Box::new(editor.clone()), cx);
+ editor.update(cx, |editor, cx| editor.focus(cx))
+ });
+
+ cx.simulate_keystrokes("cmd-shift-p");
+
+ let palette = workspace.update(cx, |workspace, cx| {
+ workspace
+ .current_modal::<CommandPalette>(cx)
+ .unwrap()
+ .read(cx)
+ .picker
+ .clone()
+ });
+
+ palette.update(cx, |palette, _| {
+ assert!(palette.delegate.commands.len() > 5);
+ let is_sorted =
+ |actions: &[Command]| actions.windows(2).all(|pair| pair[0].name <= pair[1].name);
+ assert!(is_sorted(&palette.delegate.commands));
+ });
+
+ cx.simulate_keystrokes("b c k s p");
+
+ palette.update(cx, |palette, _| {
+ assert_eq!(palette.delegate.matches[0].string, "editor: backspace");
+ });
+
+ cx.simulate_keystrokes("enter");
+
+ workspace.update(cx, |workspace, cx| {
+ assert!(workspace.current_modal::<CommandPalette>(cx).is_none());
+ assert_eq!(editor.read(cx).text(cx), "ab")
+ });
+
+ // Add namespace filter, and redeploy the palette
+ cx.update(|cx| {
+ cx.set_global(CommandPaletteFilter::default());
+ cx.update_global::<CommandPaletteFilter, _>(|filter, _| {
+ filter.filtered_namespaces.insert("editor");
+ })
+ });
+
+ cx.simulate_keystrokes("cmd-shift-p");
+ cx.simulate_keystrokes("b c k s p");
+
+ let palette = workspace.update(cx, |workspace, cx| {
+ workspace
+ .current_modal::<CommandPalette>(cx)
+ .unwrap()
+ .read(cx)
+ .picker
+ .clone()
+ });
+ palette.update(cx, |palette, _| {
+ assert!(palette.delegate.matches.is_empty())
+ });
+ }
+
+ fn init_test(cx: &mut TestAppContext) -> Arc<AppState> {
+ cx.update(|cx| {
+ let app_state = AppState::test(cx);
+ theme::init(cx);
+ language::init(cx);
+ editor::init(cx);
+ workspace::init(app_state.clone(), cx);
+ init(cx);
+ Project::init_settings(cx);
+ settings::load_default_keymap(cx);
+ app_state
+ })
+ }
+}
@@ -54,6 +54,9 @@ pub trait Action: std::fmt::Debug + 'static {
where
Self: Sized;
fn build(value: Option<serde_json::Value>) -> Result<Box<dyn Action>>
+ where
+ Self: Sized;
+ fn is_registered() -> bool
where
Self: Sized;
@@ -88,6 +91,14 @@ where
Ok(Box::new(action))
}
+ fn is_registered() -> bool {
+ ACTION_REGISTRY
+ .read()
+ .names_by_type_id
+ .get(&TypeId::of::<A>())
+ .is_some()
+ }
+
fn partial_eq(&self, action: &dyn Action) -> bool {
action
.as_any()
@@ -1,8 +1,8 @@
use crate::{
div, Action, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext,
BackgroundExecutor, Context, Div, EventEmitter, ForegroundExecutor, InputEvent, KeyDownEvent,
- Keystroke, Model, ModelContext, Render, Result, Task, TestDispatcher, TestPlatform, View,
- ViewContext, VisualContext, WindowContext, WindowHandle, WindowOptions,
+ Keystroke, Model, ModelContext, Render, Result, Task, TestDispatcher, TestPlatform, TestWindow,
+ View, ViewContext, VisualContext, WindowContext, WindowHandle, WindowOptions,
};
use anyhow::{anyhow, bail};
use futures::{Stream, StreamExt};
@@ -220,7 +220,21 @@ impl TestAppContext {
{
window
.update(self, |_, cx| cx.dispatch_action(action.boxed_clone()))
- .unwrap()
+ .unwrap();
+
+ self.background_executor.run_until_parked()
+ }
+
+ pub fn simulate_keystrokes(&mut self, window: AnyWindowHandle, keystrokes: &str) {
+ for keystroke in keystrokes
+ .split(" ")
+ .map(Keystroke::parse)
+ .map(Result::unwrap)
+ {
+ self.dispatch_keystroke(window, keystroke.into(), false);
+ }
+
+ self.background_executor.run_until_parked()
}
pub fn dispatch_keystroke(
@@ -229,15 +243,41 @@ impl TestAppContext {
keystroke: Keystroke,
is_held: bool,
) {
+ let keystroke2 = keystroke.clone();
let handled = window
.update(self, |_, cx| {
cx.dispatch_event(InputEvent::KeyDown(KeyDownEvent { keystroke, is_held }))
})
.is_ok_and(|handled| handled);
-
- if !handled {
- // todo!() simluate input here
+ if handled {
+ return;
}
+
+ let input_handler = self.update_test_window(window, |window| window.input_handler.clone());
+ let Some(input_handler) = input_handler else {
+ panic!(
+ "dispatch_keystroke {:?} failed to dispatch action or input",
+ &keystroke2
+ );
+ };
+ let text = keystroke2.ime_key.unwrap_or(keystroke2.key);
+ input_handler.lock().replace_text_in_range(None, &text);
+ }
+
+ pub fn update_test_window<R>(
+ &mut self,
+ window: AnyWindowHandle,
+ f: impl FnOnce(&mut TestWindow) -> R,
+ ) -> R {
+ window
+ .update(self, |_, cx| {
+ f(cx.window
+ .platform_window
+ .as_any_mut()
+ .downcast_mut::<TestWindow>()
+ .unwrap())
+ })
+ .unwrap()
}
pub fn notifications<T: 'static>(&mut self, entity: &Model<T>) -> impl Stream<Item = ()> {
@@ -401,12 +441,20 @@ impl<'a> VisualTestContext<'a> {
Self { cx, window }
}
+ pub fn run_until_parked(&self) {
+ self.cx.background_executor.run_until_parked();
+ }
+
pub fn dispatch_action<A>(&mut self, action: A)
where
A: Action,
{
self.cx.dispatch_action(self.window, action)
}
+
+ pub fn simulate_keystrokes(&mut self, keystrokes: &str) {
+ self.cx.simulate_keystrokes(self.window, keystrokes)
+ }
}
impl<'a> Context for VisualTestContext<'a> {
@@ -229,6 +229,20 @@ pub trait InteractiveComponent<V: 'static>: Sized + Element<V> {
mut self,
listener: impl Fn(&mut V, &A, &mut ViewContext<V>) + 'static,
) -> Self {
+ // NOTE: this debug assert has the side-effect of working around
+ // a bug where a crate consisting only of action definitions does
+ // not register the actions in debug builds:
+ //
+ // https://github.com/rust-lang/rust/issues/47384
+ // https://github.com/mmastrac/rust-ctor/issues/280
+ //
+ // if we are relying on this side-effect still, removing the debug_assert!
+ // likely breaks the command_palette tests.
+ debug_assert!(
+ A::is_registered(),
+ "{:?} is not registered as an action",
+ A::qualified_name()
+ );
self.interactivity().action_listeners.push((
TypeId::of::<A>(),
Box::new(move |view, action, phase, cx| {
@@ -22,7 +22,7 @@ pub struct TestWindow {
bounds: WindowBounds,
current_scene: Mutex<Option<Scene>>,
display: Rc<dyn PlatformDisplay>,
- input_handler: Option<Box<dyn PlatformInputHandler>>,
+ pub(crate) input_handler: Option<Arc<Mutex<Box<dyn PlatformInputHandler>>>>,
handlers: Mutex<Handlers>,
platform: Weak<TestPlatform>,
sprite_atlas: Arc<dyn PlatformAtlas>,
@@ -80,11 +80,11 @@ impl PlatformWindow for TestWindow {
}
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
- todo!()
+ self
}
fn set_input_handler(&mut self, input_handler: Box<dyn crate::PlatformInputHandler>) {
- self.input_handler = Some(input_handler);
+ self.input_handler = Some(Arc::new(Mutex::new(input_handler)));
}
fn prompt(
@@ -189,7 +189,7 @@ impl Drop for FocusHandle {
pub struct Window {
pub(crate) handle: AnyWindowHandle,
pub(crate) removed: bool,
- platform_window: Box<dyn PlatformWindow>,
+ pub(crate) platform_window: Box<dyn PlatformWindow>,
display_id: DisplayId,
sprite_atlas: Arc<dyn PlatformAtlas>,
rem_size: Pixels,