thedes_tui_core/input/device/
mock.rs

1use std::{collections::VecDeque, mem, sync::Arc, time::Duration};
2
3use crate::event::InternalEvent;
4
5use super::{Error, InputDevice};
6
7#[derive(Debug)]
8struct State {
9    events: VecDeque<Result<InternalEvent, Error>>,
10    timeout_log: Option<Vec<Duration>>,
11    device_open: bool,
12}
13
14impl State {
15    pub fn new() -> Self {
16        Self { events: VecDeque::new(), timeout_log: None, device_open: false }
17    }
18
19    pub fn enable_timeout_log(&mut self) {
20        if self.timeout_log.is_none() {
21            self.timeout_log = Some(Vec::new());
22        }
23    }
24
25    pub fn disable_timeout_log(&mut self) -> Option<Vec<Duration>> {
26        self.timeout_log.take()
27    }
28
29    pub fn take_timeout_log(&mut self) -> Option<Vec<Duration>> {
30        self.timeout_log.as_mut().map(mem::take)
31    }
32
33    pub fn log_timeout(&mut self, duration: Duration) {
34        if let Some(log) = &mut self.timeout_log {
35            log.push(duration);
36        }
37    }
38
39    pub fn publish(
40        &mut self,
41        events: impl IntoIterator<Item = Result<InternalEvent, Error>>,
42    ) {
43        self.events.extend(events);
44    }
45
46    pub fn read_one(&mut self) -> Result<Option<InternalEvent>, Error> {
47        self.events.pop_front().transpose()
48    }
49
50    pub fn mark_device_open(&mut self) -> bool {
51        mem::replace(&mut self.device_open, true)
52    }
53}
54
55#[derive(Debug, Clone)]
56pub struct InputDeviceMock {
57    state: Arc<std::sync::Mutex<State>>,
58}
59
60impl InputDeviceMock {
61    pub fn new() -> Self {
62        Self { state: Arc::new(std::sync::Mutex::new(State::new())) }
63    }
64
65    pub fn open(&self) -> Box<dyn InputDevice> {
66        if self.with_state(|state| state.mark_device_open()) {
67            panic!("Mocked input device was already open for this mock");
68        }
69        Box::new(MockedInputDevice::new(self.clone()))
70    }
71
72    pub fn enable_timeout_log(&self) {
73        self.with_state(|state| state.enable_timeout_log())
74    }
75
76    pub fn disable_timeout_log(&self) -> Option<Vec<Duration>> {
77        self.with_state(|state| state.disable_timeout_log())
78    }
79
80    pub fn take_timeout_log(&self) -> Option<Vec<Duration>> {
81        self.with_state(|state| state.take_timeout_log())
82    }
83
84    pub fn publish(
85        &self,
86        events: impl IntoIterator<Item = Result<InternalEvent, Error>>,
87    ) {
88        self.with_state(|state| state.publish(events))
89    }
90
91    pub fn publish_ok<T>(&self, events: impl IntoIterator<Item = T>)
92    where
93        T: Into<InternalEvent>,
94    {
95        self.publish(events.into_iter().map(T::into).map(Ok));
96    }
97
98    pub fn publish_err(&self, events: impl IntoIterator<Item = Error>) {
99        self.publish(events.into_iter().map(Err));
100    }
101
102    fn read_one(
103        &mut self,
104        timeout: Duration,
105    ) -> Result<Option<InternalEvent>, Error> {
106        self.with_state(|state| {
107            state.log_timeout(timeout);
108            state.read_one()
109        })
110    }
111
112    fn with_state<F, T>(&self, scope: F) -> T
113    where
114        F: FnOnce(&mut State) -> T,
115    {
116        let mut state = self.state.lock().expect("poisoned lock");
117        scope(&mut state)
118    }
119}
120
121#[derive(Debug)]
122struct MockedInputDevice {
123    mock: InputDeviceMock,
124}
125
126impl MockedInputDevice {
127    pub fn new(mock: InputDeviceMock) -> Self {
128        Self { mock }
129    }
130}
131
132impl InputDevice for MockedInputDevice {
133    fn blocking_read(
134        &mut self,
135        timeout: std::time::Duration,
136    ) -> Result<Option<InternalEvent>, Error> {
137        self.mock.read_one(timeout)
138    }
139}