thedes_tui_core/screen/device/
mock.rs1use 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}