Add command palette tests and simulate_keystrokes

This commit is contained in:
Conrad Irwin 2023-11-14 23:21:32 -07:00
parent e37d7f5b0e
commit 91b54b352b
6 changed files with 180 additions and 119 deletions

View file

@ -354,129 +354,117 @@ impl std::fmt::Debug for Command {
} }
} }
// #[cfg(test)] #[cfg(test)]
// mod tests { mod tests {
// use std::sync::Arc; use std::sync::Arc;
// use super::*; use super::*;
// use editor::Editor; use editor::Editor;
// use gpui::{executor::Deterministic, TestAppContext}; use gpui::TestAppContext;
// use project::Project; use project::Project;
// use workspace::{AppState, Workspace}; use workspace::{AppState, Workspace};
// #[test] #[test]
// fn test_humanize_action_name() { fn test_humanize_action_name() {
// assert_eq!( assert_eq!(
// humanize_action_name("editor::GoToDefinition"), humanize_action_name("editor::GoToDefinition"),
// "editor: go to definition" "editor: go to definition"
// ); );
// assert_eq!( assert_eq!(
// humanize_action_name("editor::Backspace"), humanize_action_name("editor::Backspace"),
// "editor: backspace" "editor: backspace"
// ); );
// assert_eq!( assert_eq!(
// humanize_action_name("go_to_line::Deploy"), humanize_action_name("go_to_line::Deploy"),
// "go to line: deploy" "go to line: deploy"
// ); );
// } }
// #[gpui::test] #[gpui::test]
// async fn test_command_palette(deterministic: Arc<Deterministic>, cx: &mut TestAppContext) { async fn test_command_palette(cx: &mut TestAppContext) {
// let app_state = init_test(cx); let app_state = init_test(cx);
// let project = Project::test(app_state.fs.clone(), [], cx).await; let project = Project::test(app_state.fs.clone(), [], cx).await;
// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); let (workspace, mut cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
// let workspace = window.root(cx); let cx = &mut 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| { let editor = cx.build_view(|cx| {
// cx.focus(&editor); let mut editor = Editor::single_line(cx);
// workspace.add_item(Box::new(editor.clone()), cx) editor.set_text("abc", cx);
// }); editor
});
// workspace.update(cx, |workspace, cx| { workspace.update(cx, |workspace, cx| {
// toggle_command_palette(workspace, &Toggle, cx); workspace.add_item(Box::new(editor.clone()), cx);
// }); editor.update(cx, |editor, cx| editor.focus(cx))
});
// let palette = workspace.read_with(cx, |workspace, _| { cx.simulate_keystrokes("cmd-shift-p");
// workspace.modal::<CommandPalette>().unwrap()
// });
// palette let palette = workspace.update(cx, |workspace, cx| {
// .update(cx, |palette, cx| { workspace
// // Fill up palette's command list by running an empty query; .current_modal::<CommandPalette>(cx)
// // we only need it to subsequently assert that the palette is initially .unwrap()
// // sorted by command's name. .read(cx)
// palette.delegate_mut().update_matches("".to_string(), cx) .picker
// }) .clone()
// .await; });
// palette.update(cx, |palette, _| { palette.update(cx, |palette, _| {
// let is_sorted = assert!(palette.delegate.commands.len() > 5);
// |actions: &[Command]| actions.windows(2).all(|pair| pair[0].name <= pair[1].name); let is_sorted =
// assert!(is_sorted(&palette.delegate().actions)); |actions: &[Command]| actions.windows(2).all(|pair| pair[0].name <= pair[1].name);
// }); assert!(is_sorted(&palette.delegate.commands));
});
// palette cx.simulate_keystrokes("b c k s p");
// .update(cx, |palette, cx| {
// palette
// .delegate_mut()
// .update_matches("bcksp".to_string(), cx)
// })
// .await;
// palette.update(cx, |palette, cx| { palette.update(cx, |palette, _| {
// assert_eq!(palette.delegate().matches[0].string, "editor: backspace"); 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.simulate_keystrokes("enter");
// cx.update(|cx| {
// cx.update_default_global::<CommandPaletteFilter, _, _>(|filter, _| {
// filter.filtered_namespaces.insert("editor");
// })
// });
// workspace.update(cx, |workspace, cx| { workspace.update(cx, |workspace, cx| {
// toggle_command_palette(workspace, &Toggle, cx); assert!(workspace.current_modal::<CommandPalette>(cx).is_none());
// }); assert_eq!(editor.read(cx).text(cx), "ab")
});
// // Assert editor command not present // Add namespace filter, and redeploy the palette
// let palette = workspace.read_with(cx, |workspace, _| { cx.update(|cx| {
// workspace.modal::<CommandPalette>().unwrap() cx.set_global(CommandPaletteFilter::default());
// }); cx.update_global::<CommandPaletteFilter, _>(|filter, _| {
filter.filtered_namespaces.insert("editor");
})
});
// palette cx.simulate_keystrokes("cmd-shift-p");
// .update(cx, |palette, cx| { cx.simulate_keystrokes("b c k s p");
// palette
// .delegate_mut()
// .update_matches("bcksp".to_string(), cx)
// })
// .await;
// palette.update(cx, |palette, _| { let palette = workspace.update(cx, |workspace, cx| {
// assert!(palette.delegate().matches.is_empty()) 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> { fn init_test(cx: &mut TestAppContext) -> Arc<AppState> {
// cx.update(|cx| { cx.update(|cx| {
// let app_state = AppState::test(cx); let app_state = AppState::test(cx);
// theme::init(cx); theme::init(cx);
// language::init(cx); language::init(cx);
// editor::init(cx); editor::init(cx);
// workspace::init(app_state.clone(), cx); workspace::init(app_state.clone(), cx);
// init(cx); init(cx);
// Project::init_settings(cx); Project::init_settings(cx);
// app_state settings::load_default_keymap(cx);
// }) app_state
// } })
// } }
}

View file

@ -54,6 +54,9 @@ pub trait Action: std::fmt::Debug + 'static {
where where
Self: Sized; Self: Sized;
fn build(value: Option<serde_json::Value>) -> Result<Box<dyn Action>> fn build(value: Option<serde_json::Value>) -> Result<Box<dyn Action>>
where
Self: Sized;
fn is_registered() -> bool
where where
Self: Sized; Self: Sized;
@ -88,6 +91,14 @@ where
Ok(Box::new(action)) 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 { fn partial_eq(&self, action: &dyn Action) -> bool {
action action
.as_any() .as_any()

View file

@ -1,8 +1,8 @@
use crate::{ use crate::{
div, Action, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext, div, Action, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext,
BackgroundExecutor, Context, Div, EventEmitter, ForegroundExecutor, InputEvent, KeyDownEvent, BackgroundExecutor, Context, Div, EventEmitter, ForegroundExecutor, InputEvent, KeyDownEvent,
Keystroke, Model, ModelContext, Render, Result, Task, TestDispatcher, TestPlatform, View, Keystroke, Model, ModelContext, Render, Result, Task, TestDispatcher, TestPlatform, TestWindow,
ViewContext, VisualContext, WindowContext, WindowHandle, WindowOptions, View, ViewContext, VisualContext, WindowContext, WindowHandle, WindowOptions,
}; };
use anyhow::{anyhow, bail}; use anyhow::{anyhow, bail};
use futures::{Stream, StreamExt}; use futures::{Stream, StreamExt};
@ -220,7 +220,21 @@ impl TestAppContext {
{ {
window window
.update(self, |_, cx| cx.dispatch_action(action.boxed_clone())) .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( pub fn dispatch_keystroke(
@ -229,15 +243,41 @@ impl TestAppContext {
keystroke: Keystroke, keystroke: Keystroke,
is_held: bool, is_held: bool,
) { ) {
let keystroke2 = keystroke.clone();
let handled = window let handled = window
.update(self, |_, cx| { .update(self, |_, cx| {
cx.dispatch_event(InputEvent::KeyDown(KeyDownEvent { keystroke, is_held })) cx.dispatch_event(InputEvent::KeyDown(KeyDownEvent { keystroke, is_held }))
}) })
.is_ok_and(|handled| handled); .is_ok_and(|handled| handled);
if handled {
if !handled { return;
// todo!() simluate input here
} }
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 = ()> { pub fn notifications<T: 'static>(&mut self, entity: &Model<T>) -> impl Stream<Item = ()> {
@ -401,12 +441,20 @@ impl<'a> VisualTestContext<'a> {
Self { cx, window } 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) pub fn dispatch_action<A>(&mut self, action: A)
where where
A: Action, A: Action,
{ {
self.cx.dispatch_action(self.window, 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> { impl<'a> Context for VisualTestContext<'a> {

View file

@ -229,6 +229,20 @@ pub trait InteractiveComponent<V: 'static>: Sized + Element<V> {
mut self, mut self,
listener: impl Fn(&mut V, &A, &mut ViewContext<V>) + 'static, listener: impl Fn(&mut V, &A, &mut ViewContext<V>) + 'static,
) -> Self { ) -> 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(( self.interactivity().action_listeners.push((
TypeId::of::<A>(), TypeId::of::<A>(),
Box::new(move |view, action, phase, cx| { Box::new(move |view, action, phase, cx| {

View file

@ -22,7 +22,7 @@ pub struct TestWindow {
bounds: WindowBounds, bounds: WindowBounds,
current_scene: Mutex<Option<Scene>>, current_scene: Mutex<Option<Scene>>,
display: Rc<dyn PlatformDisplay>, display: Rc<dyn PlatformDisplay>,
input_handler: Option<Box<dyn PlatformInputHandler>>, pub(crate) input_handler: Option<Arc<Mutex<Box<dyn PlatformInputHandler>>>>,
handlers: Mutex<Handlers>, handlers: Mutex<Handlers>,
platform: Weak<TestPlatform>, platform: Weak<TestPlatform>,
sprite_atlas: Arc<dyn PlatformAtlas>, sprite_atlas: Arc<dyn PlatformAtlas>,
@ -80,11 +80,11 @@ impl PlatformWindow for TestWindow {
} }
fn as_any_mut(&mut self) -> &mut dyn std::any::Any { 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>) { 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( fn prompt(

View file

@ -189,7 +189,7 @@ impl Drop for FocusHandle {
pub struct Window { pub struct Window {
pub(crate) handle: AnyWindowHandle, pub(crate) handle: AnyWindowHandle,
pub(crate) removed: bool, pub(crate) removed: bool,
platform_window: Box<dyn PlatformWindow>, pub(crate) platform_window: Box<dyn PlatformWindow>,
display_id: DisplayId, display_id: DisplayId,
sprite_atlas: Arc<dyn PlatformAtlas>, sprite_atlas: Arc<dyn PlatformAtlas>,
rem_size: Pixels, rem_size: Pixels,