input/
testutil.rs

1//! Utilities for writing tests that interact with input events.
2
3use anyhow::Result;
4use crossterm::event as c_event;
5
6use crate::{
7	map_keybindings,
8	thread::State,
9	Event,
10	EventHandler,
11	EventReaderFn,
12	KeyBindings,
13	KeyCode,
14	KeyEvent,
15	KeyModifiers,
16};
17
18#[cfg(test)]
19pub(crate) mod local {
20	use anyhow::Result;
21
22	use crate::{CustomEvent, CustomKeybinding, EventReaderFn};
23
24	#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd)]
25	pub(crate) enum TestEvent {}
26	impl CustomEvent for TestEvent {}
27
28	pub(crate) struct TestKeybinding;
29	impl CustomKeybinding for TestKeybinding {
30		fn new(_: &config::KeyBindings) -> Self {
31			Self {}
32		}
33	}
34
35	pub(crate) type Event = super::Event<TestEvent>;
36	pub(crate) type EventHandler = super::EventHandler<TestKeybinding, TestEvent>;
37	pub(crate) type KeyBindings = super::KeyBindings<TestKeybinding, TestEvent>;
38
39	pub(crate) fn create_test_keybindings() -> KeyBindings {
40		super::create_test_keybindings::<TestKeybinding, TestEvent>(TestKeybinding {})
41	}
42
43	pub(crate) fn create_event_reader<EventGeneratorFunction>(
44		event_generator: EventGeneratorFunction,
45	) -> impl EventReaderFn
46	where EventGeneratorFunction: Fn() -> Result<Option<Event>> + Sync + Send + 'static {
47		super::create_event_reader(event_generator)
48	}
49}
50
51/// Create a mocked version of `KeyBindings`.
52#[inline]
53#[must_use]
54pub fn create_test_keybindings<TestKeybinding: crate::CustomKeybinding, CustomEvent: crate::CustomEvent>(
55	custom_key_bindings: TestKeybinding,
56) -> KeyBindings<TestKeybinding, CustomEvent> {
57	KeyBindings {
58		redo: vec![Event::from(KeyEvent::new(KeyCode::Char('y'), KeyModifiers::CONTROL))],
59		undo: vec![Event::from(KeyEvent::new(KeyCode::Char('z'), KeyModifiers::CONTROL))],
60		scroll_down: map_keybindings(&[String::from("Down")]),
61		scroll_end: map_keybindings(&[String::from("End")]),
62		scroll_home: map_keybindings(&[String::from("Home")]),
63		scroll_left: map_keybindings(&[String::from("Left")]),
64		scroll_right: map_keybindings(&[String::from("Right")]),
65		scroll_up: map_keybindings(&[String::from("Up")]),
66		scroll_step_down: map_keybindings(&[String::from("PageDown")]),
67		scroll_step_up: map_keybindings(&[String::from("PageUp")]),
68		help: map_keybindings(&[String::from("?")]),
69		search_start: map_keybindings(&[String::from("/")]),
70		search_next: map_keybindings(&[String::from("n")]),
71		search_previous: map_keybindings(&[String::from("N")]),
72		custom: custom_key_bindings,
73	}
74}
75
76/// Context for a `EventHandler` based test.
77#[derive(Debug)]
78#[non_exhaustive]
79pub struct TestContext<TestKeybinding: crate::CustomKeybinding, CustomEvent: crate::CustomEvent> {
80	/// The `EventHandler` instance.
81	pub event_handler: EventHandler<TestKeybinding, CustomEvent>,
82	/// The sender instance.
83	pub state: State<CustomEvent>,
84	/// The number of known available events.
85	pub number_events: usize,
86}
87
88/// Provide an `EventHandler` instance for use within a test.
89#[inline]
90#[allow(clippy::missing_panics_doc)]
91pub fn with_event_handler<C, TestKeybinding: crate::CustomKeybinding, CustomEvent: crate::CustomEvent>(
92	custom_key_bindings: TestKeybinding,
93	events: &[Event<CustomEvent>],
94	callback: C,
95) where
96	C: FnOnce(TestContext<TestKeybinding, CustomEvent>),
97{
98	let event_handler = EventHandler::new(create_test_keybindings(custom_key_bindings));
99	let state = State::new();
100
101	for event in events {
102		state.enqueue_event(*event);
103	}
104
105	callback(TestContext {
106		event_handler,
107		state,
108		number_events: events.len(),
109	});
110}
111
112/// Create an event reader that will map the provided events to the internal representation of the
113/// events. This allows for mocking of event input when testing at the highest level of the application.
114///
115/// This function does not accept any `Event::MetaEvent` or `Event::StandardEvent` event types, instead
116/// use other event types that will map to the expected value using the keybindings.
117///
118/// This function should be used sparingly, and instead `with_event_handler` should be used where possible.
119///
120/// # Panics
121/// If provided an event generator that returns a `Event::MetaEvent` or `Event::StandardEvent` event type.
122#[allow(clippy::panic)]
123#[inline]
124pub fn create_event_reader<EventGeneratorFunction, CustomEvent>(
125	event_generator: EventGeneratorFunction,
126) -> impl EventReaderFn
127where
128	EventGeneratorFunction: Fn() -> Result<Option<Event<CustomEvent>>> + Sync + Send + 'static,
129	CustomEvent: crate::CustomEvent,
130{
131	move || {
132		match event_generator()? {
133			None => Ok(None),
134			Some(event) => {
135				match event {
136					Event::Key(key) => {
137						Ok(Some(c_event::Event::Key(c_event::KeyEvent::new(
138							key.code,
139							key.modifiers,
140						))))
141					},
142					Event::Mouse(mouse_event) => Ok(Some(c_event::Event::Mouse(mouse_event))),
143					Event::None => Ok(None),
144					Event::Resize(width, height) => Ok(Some(c_event::Event::Resize(width, height))),
145					Event::MetaEvent(_) | Event::Standard(_) => {
146						panic!("MetaEvent and Standard are not supported, please use other event types")
147					},
148				}
149			},
150		}
151	}
152}