diff options
Diffstat (limited to 'src/input/testutil.rs')
-rw-r--r-- | src/input/testutil.rs | 159 |
1 files changed, 159 insertions, 0 deletions
diff --git a/src/input/testutil.rs b/src/input/testutil.rs new file mode 100644 index 0000000..ff41007 --- /dev/null +++ b/src/input/testutil.rs @@ -0,0 +1,159 @@ +//! Utilities for writing tests that interact with input events. + +use anyhow::Result; +use crossterm::event as c_event; + +use crate::input::{ + map_keybindings, + thread::State, + Event, + EventHandler, + EventReaderFn, + KeyBindings, + KeyCode, + KeyEvent, + KeyModifiers, +}; + +#[cfg(test)] +pub(crate) mod local { + use anyhow::Result; + + use crate::input::{CustomEvent, CustomKeybinding, EventReaderFn}; + + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd)] + pub(crate) enum TestEvent {} + impl CustomEvent for TestEvent {} + + pub(crate) struct TestKeybinding; + impl CustomKeybinding for TestKeybinding { + fn new(_: &crate::config::KeyBindings) -> Self { + Self {} + } + } + + pub(crate) type Event = crate::input::Event<TestEvent>; + pub(crate) type EventHandler = crate::input::EventHandler<TestKeybinding, TestEvent>; + pub(crate) type KeyBindings = crate::input::KeyBindings<TestKeybinding, TestEvent>; + + pub(crate) fn create_test_keybindings() -> KeyBindings { + super::create_test_keybindings::<TestKeybinding, TestEvent>(TestKeybinding {}) + } + + pub(crate) fn create_event_reader<EventGeneratorFunction>( + event_generator: EventGeneratorFunction, + ) -> impl EventReaderFn + where EventGeneratorFunction: Fn() -> Result<Option<Event>> + Sync + Send + 'static { + super::create_event_reader(event_generator) + } +} + +/// Create a mocked version of `KeyBindings`. +#[inline] +#[must_use] +pub(crate) fn create_test_keybindings< + TestKeybinding: crate::input::CustomKeybinding, + CustomEvent: crate::input::CustomEvent, +>( + custom_key_bindings: TestKeybinding, +) -> KeyBindings<TestKeybinding, CustomEvent> { + KeyBindings { + redo: vec![Event::from(KeyEvent::new(KeyCode::Char('y'), KeyModifiers::CONTROL))], + undo: vec![Event::from(KeyEvent::new(KeyCode::Char('z'), KeyModifiers::CONTROL))], + scroll_down: map_keybindings(&[String::from("Down")]), + scroll_end: map_keybindings(&[String::from("End")]), + scroll_home: map_keybindings(&[String::from("Home")]), + scroll_left: map_keybindings(&[String::from("Left")]), + scroll_right: map_keybindings(&[String::from("Right")]), + scroll_up: map_keybindings(&[String::from("Up")]), + scroll_step_down: map_keybindings(&[String::from("PageDown")]), + scroll_step_up: map_keybindings(&[String::from("PageUp")]), + help: map_keybindings(&[String::from("?")]), + search_start: map_keybindings(&[String::from("/")]), + search_next: map_keybindings(&[String::from("n")]), + search_previous: map_keybindings(&[String::from("N")]), + custom: custom_key_bindings, + } +} + +/// Context for a `EventHandler` based test. +#[derive(Debug)] +#[non_exhaustive] +pub(crate) struct TestContext<TestKeybinding: crate::input::CustomKeybinding, CustomEvent: crate::input::CustomEvent> { + /// The `EventHandler` instance. + pub(crate) event_handler: EventHandler<TestKeybinding, CustomEvent>, + /// The sender instance. + pub(crate) state: State<CustomEvent>, + /// The number of known available events. + pub(crate) number_events: usize, +} + +/// Provide an `EventHandler` instance for use within a test. +#[inline] +#[allow(clippy::missing_panics_doc)] +pub(crate) fn with_event_handler< + C, + TestKeybinding: crate::input::CustomKeybinding, + CustomEvent: crate::input::CustomEvent, +>( + custom_key_bindings: TestKeybinding, + events: &[Event<CustomEvent>], + callback: C, +) where + C: FnOnce(TestContext<TestKeybinding, CustomEvent>), +{ + let event_handler = EventHandler::new(create_test_keybindings(custom_key_bindings)); + let state = State::new(); + + for event in events { + state.enqueue_event(*event); + } + + callback(TestContext { + event_handler, + state, + number_events: events.len(), + }); +} + +/// Create an event reader that will map the provided events to the internal representation of the +/// events. This allows for mocking of event input when testing at the highest level of the application. +/// +/// This function does not accept any `Event::MetaEvent` or `Event::StandardEvent` event types, instead +/// use other event types that will map to the expected value using the keybindings. +/// +/// This function should be used sparingly, and instead `with_event_handler` should be used where possible. +/// +/// # Panics +/// If provided an event generator that returns a `Event::MetaEvent` or `Event::StandardEvent` event type. +#[allow(clippy::panic)] +#[inline] +pub(crate) fn create_event_reader<EventGeneratorFunction, CustomEvent>( + event_generator: EventGeneratorFunction, +) -> impl EventReaderFn +where + EventGeneratorFunction: Fn() -> Result<Option<Event<CustomEvent>>> + Sync + Send + 'static, + CustomEvent: crate::input::CustomEvent, +{ + move || { + match event_generator()? { + None => Ok(None), + Some(event) => { + match event { + Event::Key(key) => { + Ok(Some(c_event::Event::Key(c_event::KeyEvent::new( + key.code, + key.modifiers, + )))) + }, + Event::Mouse(mouse_event) => Ok(Some(c_event::Event::Mouse(mouse_event))), + Event::None => Ok(None), + Event::Resize(width, height) => Ok(Some(c_event::Event::Resize(width, height))), + Event::MetaEvent(_) | Event::Standard(_) => { + panic!("MetaEvent and Standard are not supported, please use other event types") + }, + } + }, + } + } +} |