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