Add command palette tests and simulate_keystrokes
This commit is contained in:
parent
e37d7f5b0e
commit
91b54b352b
6 changed files with 180 additions and 119 deletions
|
@ -354,129 +354,117 @@ impl std::fmt::Debug for Command {
|
|||
}
|
||||
}
|
||||
|
||||
// #[cfg(test)]
|
||||
// mod tests {
|
||||
// use std::sync::Arc;
|
||||
#[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};
|
||||
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"
|
||||
// );
|
||||
// }
|
||||
#[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);
|
||||
#[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 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
|
||||
// });
|
||||
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;
|
||||
|
||||
// workspace.update(cx, |workspace, cx| {
|
||||
// cx.focus(&editor);
|
||||
// workspace.add_item(Box::new(editor.clone()), 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| {
|
||||
// toggle_command_palette(workspace, &Toggle, cx);
|
||||
// });
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
workspace.add_item(Box::new(editor.clone()), cx);
|
||||
editor.update(cx, |editor, cx| editor.focus(cx))
|
||||
});
|
||||
|
||||
// let palette = workspace.read_with(cx, |workspace, _| {
|
||||
// workspace.modal::<CommandPalette>().unwrap()
|
||||
// });
|
||||
cx.simulate_keystrokes("cmd-shift-p");
|
||||
|
||||
// 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;
|
||||
let palette = workspace.update(cx, |workspace, cx| {
|
||||
workspace
|
||||
.current_modal::<CommandPalette>(cx)
|
||||
.unwrap()
|
||||
.read(cx)
|
||||
.picker
|
||||
.clone()
|
||||
});
|
||||
|
||||
// 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, _| {
|
||||
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));
|
||||
});
|
||||
|
||||
// palette
|
||||
// .update(cx, |palette, cx| {
|
||||
// palette
|
||||
// .delegate_mut()
|
||||
// .update_matches("bcksp".to_string(), cx)
|
||||
// })
|
||||
// .await;
|
||||
cx.simulate_keystrokes("b c k s p");
|
||||
|
||||
// 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");
|
||||
// });
|
||||
palette.update(cx, |palette, _| {
|
||||
assert_eq!(palette.delegate.matches[0].string, "editor: backspace");
|
||||
});
|
||||
|
||||
// // Add namespace filter, and redeploy the palette
|
||||
// cx.update(|cx| {
|
||||
// cx.update_default_global::<CommandPaletteFilter, _, _>(|filter, _| {
|
||||
// filter.filtered_namespaces.insert("editor");
|
||||
// })
|
||||
// });
|
||||
cx.simulate_keystrokes("enter");
|
||||
|
||||
// workspace.update(cx, |workspace, cx| {
|
||||
// toggle_command_palette(workspace, &Toggle, cx);
|
||||
// });
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
assert!(workspace.current_modal::<CommandPalette>(cx).is_none());
|
||||
assert_eq!(editor.read(cx).text(cx), "ab")
|
||||
});
|
||||
|
||||
// // Assert editor command not present
|
||||
// let palette = workspace.read_with(cx, |workspace, _| {
|
||||
// workspace.modal::<CommandPalette>().unwrap()
|
||||
// });
|
||||
// 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");
|
||||
})
|
||||
});
|
||||
|
||||
// palette
|
||||
// .update(cx, |palette, cx| {
|
||||
// palette
|
||||
// .delegate_mut()
|
||||
// .update_matches("bcksp".to_string(), cx)
|
||||
// })
|
||||
// .await;
|
||||
cx.simulate_keystrokes("cmd-shift-p");
|
||||
cx.simulate_keystrokes("b c k s p");
|
||||
|
||||
// palette.update(cx, |palette, _| {
|
||||
// assert!(palette.delegate().matches.is_empty())
|
||||
// });
|
||||
// }
|
||||
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);
|
||||
// app_state
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
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,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue