1use std::cell::RefCell;
19use std::path::PathBuf;
20
21pub trait Frontlight {
26 fn get(&self) -> u32;
27 fn set(&self, brightness: u32);
28 fn restore(&self, brightness: u32);
29}
30
31pub struct MockFrontlight {
35 pub sets: RefCell<Vec<u32>>,
36 pub scripted_gets: Vec<u32>,
37 get_idx: std::cell::Cell<usize>,
38}
39
40impl MockFrontlight {
41 pub fn new(gets: Vec<u32>) -> Self {
42 MockFrontlight {
43 sets: RefCell::new(Vec::new()),
44 scripted_gets: gets,
45 get_idx: std::cell::Cell::new(0),
46 }
47 }
48
49 pub fn last_set(&self) -> Option<u32> {
50 self.sets.borrow().last().copied()
51 }
52
53 pub fn set_count(&self) -> usize {
54 self.sets.borrow().len()
55 }
56}
57
58impl Frontlight for MockFrontlight {
59 fn get(&self) -> u32 {
60 let i = self.get_idx.get();
61 let v = self
62 .scripted_gets
63 .get(i)
64 .copied()
65 .unwrap_or_else(|| self.scripted_gets.last().copied().unwrap_or(0));
66 self.get_idx.set(i + 1);
67 v
68 }
69
70 fn set(&self, brightness: u32) {
71 self.sets.borrow_mut().push(brightness);
72 }
73
74 fn restore(&self, brightness: u32) {
75 self.set(brightness);
76 }
77}
78
79pub trait Battery {
84 fn pct(&self) -> i32;
85 fn is_charging(&self) -> bool {
86 false
87 }
88}
89
90#[derive(Debug, Clone, Default)]
91pub struct MockBattery {
92 pub pct: i32,
93 pub charging: bool,
94}
95
96impl Battery for MockBattery {
97 fn pct(&self) -> i32 {
98 self.pct
99 }
100 fn is_charging(&self) -> bool {
101 self.charging
102 }
103}
104
105pub trait Wifi {
110 fn connected(&self) -> bool;
111 fn ssid(&self) -> Option<String>;
112 fn toggle(&self, on: bool);
113}
114
115#[derive(Debug, Clone, Default)]
116pub struct MockWifi {
117 pub connected: bool,
118 pub ssid: Option<String>,
119}
120
121impl Wifi for MockWifi {
122 fn connected(&self) -> bool {
123 self.connected
124 }
125 fn ssid(&self) -> Option<String> {
126 self.ssid.clone()
127 }
128 fn toggle(&self, _on: bool) {}
129}
130
131pub trait Bluetooth {
136 fn connected(&self) -> bool;
137 fn name(&self) -> Option<String>;
138 fn toggle(&self, on: bool);
139}
140
141#[derive(Debug, Clone, Default)]
142pub struct MockBluetooth {
143 pub connected: bool,
144 pub name: Option<String>,
145}
146
147impl Bluetooth for MockBluetooth {
148 fn connected(&self) -> bool {
149 self.connected
150 }
151 fn name(&self) -> Option<String> {
152 self.name.clone()
153 }
154 fn toggle(&self, _on: bool) {}
155}
156
157pub trait SystemControl {
162 fn suspend(&self, state: &str);
163 fn clock(&self) -> String;
164}
165
166#[derive(Debug, Clone, Default)]
167pub struct MockSystemControl {
168 pub clock: String,
169}
170
171impl SystemControl for MockSystemControl {
172 fn suspend(&self, _state: &str) {}
173 fn clock(&self) -> String {
174 if self.clock.is_empty() {
175 "--:--".to_string()
176 } else {
177 self.clock.clone()
178 }
179 }
180}
181
182pub trait Framebuffer {
187 fn resolution(&self) -> (usize, usize);
188 fn present(
189 &self,
190 buf: &[u8],
191 w: usize,
192 h: usize,
193 full: bool,
194 top: usize,
195 rh: usize,
196 waveform: u32,
197 );
198}
199
200pub struct MockFramebuffer {
201 pub width: usize,
202 pub height: usize,
203 pub present_count: std::cell::Cell<usize>,
204}
205
206impl MockFramebuffer {
207 pub fn new(width: usize, height: usize) -> Self {
208 MockFramebuffer {
209 width,
210 height,
211 present_count: std::cell::Cell::new(0),
212 }
213 }
214}
215
216impl Framebuffer for MockFramebuffer {
217 fn resolution(&self) -> (usize, usize) {
218 (self.width, self.height)
219 }
220 fn present(
221 &self,
222 _buf: &[u8],
223 _w: usize,
224 _h: usize,
225 _full: bool,
226 _top: usize,
227 _rh: usize,
228 _waveform: u32,
229 ) {
230 self.present_count.set(self.present_count.get() + 1);
231 }
232}
233
234pub struct FrontlightPaths {
240 pub brightness: PathBuf,
241 pub mixer: Option<PathBuf>,
242}
243
244#[cfg(test)]
245mod tests {
246 use super::*;
247
248 #[test]
249 fn mock_battery_round_trip() {
250 let b = MockBattery {
251 pct: 73,
252 charging: true,
253 };
254 assert_eq!(b.pct(), 73);
255 assert!(b.is_charging());
256 }
257
258 #[test]
259 fn mock_frontlight_scripts_gets() {
260 let fl = MockFrontlight::new(vec![0, 0, 60]);
261 assert_eq!(fl.get(), 0);
262 assert_eq!(fl.get(), 0);
263 fl.set(60);
264 assert_eq!(fl.get(), 60);
265 assert_eq!(fl.set_count(), 1);
266 assert_eq!(fl.last_set(), Some(60));
267 }
268
269 #[test]
270 fn mock_frontlight_repeats_last_get() {
271 let fl = MockFrontlight::new(vec![42]);
272 assert_eq!(fl.get(), 42);
273 assert_eq!(fl.get(), 42);
274 }
275
276 #[test]
277 fn mock_wifi_and_bt() {
278 let w = MockWifi {
279 connected: true,
280 ssid: Some("HomeNet".into()),
281 };
282 assert!(w.connected());
283 assert_eq!(w.ssid().as_deref(), Some("HomeNet"));
284
285 let bt = MockBluetooth {
286 connected: false,
287 name: None,
288 };
289 assert!(!bt.connected());
290 assert!(bt.name().is_none());
291 }
292
293 #[test]
294 fn mock_system_clock() {
295 let sc = MockSystemControl {
296 clock: "9:41 PM".into(),
297 };
298 assert_eq!(sc.clock(), "9:41 PM");
299 assert_eq!(MockSystemControl::default().clock(), "--:--");
300 }
301
302 #[test]
303 fn mock_framebuffer_counts_presents() {
304 let fb = MockFramebuffer::new(100, 200);
305 assert_eq!(fb.resolution(), (100, 200));
306 fb.present(&[], 100, 200, true, 0, 200, 2);
307 fb.present(&[], 100, 200, false, 0, 100, 2);
308 assert_eq!(fb.present_count.get(), 2);
309 }
310}