thedes_tui_core/screen/device/
mock.rs

1use std::{collections::VecDeque, mem, sync::Arc};
2
3use thedes_async_util::dyn_async_trait;
4
5use crate::geometry::CoordPair;
6
7use super::{Command, Error, ScreenDevice};
8
9#[derive(Debug)]
10struct State {
11    term_size: CoordPair,
12    term_size_count: usize,
13    term_size_results: VecDeque<Result<(), Error>>,
14    send_results: VecDeque<Result<usize, Error>>,
15    command_log: Option<Vec<Vec<Command>>>,
16    flush_results: VecDeque<Result<(), Error>>,
17    flush_count: usize,
18    device_open: bool,
19}
20
21impl State {
22    pub fn new(term_size: CoordPair) -> Self {
23        Self {
24            term_size,
25            term_size_count: 0,
26            term_size_results: VecDeque::new(),
27            send_results: VecDeque::new(),
28            command_log: None,
29            flush_results: VecDeque::new(),
30            flush_count: 0,
31            device_open: false,
32        }
33    }
34
35    pub fn register_term_size_results(
36        &mut self,
37        results: impl IntoIterator<Item = Result<(), Error>>,
38    ) {
39        self.term_size_results.extend(results);
40    }
41
42    pub fn blocking_term_size(&mut self) -> Result<CoordPair, Error> {
43        self.term_size_count += 1;
44        self.term_size_results.pop_front().unwrap_or(Ok(()))?;
45        Ok(self.term_size)
46    }
47
48    pub fn blocking_get_size_count(&self) -> usize {
49        self.term_size_count
50    }
51
52    pub fn enable_command_log(&mut self) {
53        if self.command_log.is_none() {
54            self.command_log = Some(vec![vec![]]);
55        }
56    }
57
58    pub fn disable_command_log(&mut self) -> Option<Vec<Vec<Command>>> {
59        self.command_log.take()
60    }
61
62    pub fn take_command_log(&mut self) -> Option<Vec<Vec<Command>>> {
63        self.command_log
64            .as_mut()
65            .map(|flushes| mem::replace(flushes, vec![vec![]]))
66    }
67
68    pub fn log_command(&mut self, command: Command) {
69        if let Some(log) = &mut self.command_log {
70            log.last_mut().unwrap().push(command);
71        }
72    }
73
74    pub fn register_send_results(
75        &mut self,
76        results: impl IntoIterator<Item = Result<usize, Error>>,
77    ) {
78        let mut prev_ok = None;
79        for result in results {
80            match result {
81                Ok(count) => {
82                    prev_ok = Some(match prev_ok {
83                        Some(prev) => prev + count,
84                        None => count,
85                    });
86                },
87                Err(error) => {
88                    if let Some(prev_count) =
89                        prev_ok.filter(|count| *count != 0)
90                    {
91                        self.send_results.push_back(Ok(prev_count));
92                    }
93                    self.send_results.push_back(Err(error));
94                },
95            }
96        }
97        if let Some(prev_count) = prev_ok.filter(|count| *count != 0) {
98            self.send_results.push_back(Ok(prev_count));
99        }
100    }
101
102    pub fn send(
103        &mut self,
104        commands: &mut (dyn Iterator<Item = Command> + Send + Sync),
105    ) -> Result<(), Error> {
106        for command in commands {
107            match self.send_results.pop_front().unwrap_or(Ok(1)) {
108                Ok(mut count) => {
109                    count -= 1;
110                    if count > 0 {
111                        self.send_results.push_front(Ok(count));
112                    }
113                    self.log_command(command);
114                },
115                Err(error) => Err(error)?,
116            }
117        }
118        Ok(())
119    }
120
121    pub fn register_flush_results(
122        &mut self,
123        results: impl IntoIterator<Item = Result<(), Error>>,
124    ) {
125        self.flush_results.extend(results);
126    }
127
128    pub fn flush(&mut self) -> Result<(), Error> {
129        self.flush_count += 1;
130        self.flush_results.pop_front().unwrap_or(Ok(()))?;
131        if let Some(log) = &mut self.command_log {
132            if log.last().is_some_and(|buf| !buf.is_empty()) {
133                log.push(Vec::new());
134            }
135        }
136        Ok(())
137    }
138
139    pub fn flush_count(&self) -> usize {
140        self.flush_count
141    }
142
143    pub fn mark_device_open(&mut self) -> bool {
144        mem::replace(&mut self.device_open, true)
145    }
146}
147
148#[derive(Debug, Clone)]
149pub struct ScreenDeviceMock {
150    state: Arc<std::sync::Mutex<State>>,
151}
152
153impl ScreenDeviceMock {
154    pub fn new(term_size: CoordPair) -> Self {
155        Self { state: Arc::new(std::sync::Mutex::new(State::new(term_size))) }
156    }
157
158    pub fn open(&self) -> Box<dyn ScreenDevice> {
159        if self.with_state(|state| state.mark_device_open()) {
160            panic!("Mocked screen device was already open for this mock");
161        }
162        Box::new(MockedScreenDevice::new(self.clone()))
163    }
164
165    pub fn enable_command_log(&self) {
166        self.with_state(|state| state.enable_command_log())
167    }
168
169    pub fn disable_command_log(&self) -> Option<Vec<Vec<Command>>> {
170        self.with_state(|state| state.disable_command_log())
171    }
172
173    pub fn take_command_log(&self) -> Option<Vec<Vec<Command>>> {
174        self.with_state(|state| state.take_command_log())
175    }
176
177    pub fn register_term_size_results(
178        &self,
179        results: impl IntoIterator<Item = Result<(), Error>>,
180    ) {
181        self.with_state(|state| state.register_term_size_results(results))
182    }
183
184    pub fn blocking_get_size_count(&self) -> usize {
185        self.with_state(|state| state.blocking_get_size_count())
186    }
187
188    pub fn register_send_results(
189        &self,
190        results: impl IntoIterator<Item = Result<usize, Error>>,
191    ) {
192        self.with_state(|state| state.register_send_results(results))
193    }
194
195    pub fn register_flush_results(
196        &self,
197        results: impl IntoIterator<Item = Result<(), Error>>,
198    ) {
199        self.with_state(|state| state.register_flush_results(results))
200    }
201
202    pub fn flush_count(&self) -> usize {
203        self.with_state(|state| state.flush_count())
204    }
205
206    fn send(
207        &self,
208        commands: &mut (dyn Iterator<Item = Command> + Send + Sync),
209    ) -> Result<(), Error> {
210        self.with_state(|state| state.send(commands))
211    }
212
213    async fn flush(&self) -> Result<(), Error> {
214        self.with_state(|state| state.flush())
215    }
216
217    pub fn blocking_get_size(&self) -> Result<CoordPair, Error> {
218        self.with_state(|state| state.blocking_term_size())
219    }
220
221    fn with_state<F, T>(&self, scope: F) -> T
222    where
223        F: FnOnce(&mut State) -> T,
224    {
225        let mut state = self.state.lock().expect("poisoned lock");
226        scope(&mut state)
227    }
228}
229
230#[derive(Debug)]
231struct MockedScreenDevice {
232    mock: ScreenDeviceMock,
233}
234
235impl MockedScreenDevice {
236    pub fn new(mock: ScreenDeviceMock) -> Self {
237        Self { mock }
238    }
239}
240
241#[dyn_async_trait]
242impl ScreenDevice for MockedScreenDevice {
243    fn send_raw(
244        &mut self,
245        commands: &mut (dyn Iterator<Item = Command> + Send + Sync),
246    ) -> Result<(), Error> {
247        self.mock.send(commands)
248    }
249
250    async fn flush(&mut self) -> Result<(), Error> {
251        self.mock.flush().await
252    }
253
254    fn blocking_get_size(&mut self) -> Result<CoordPair, Error> {
255        self.mock.blocking_get_size()
256    }
257}