//! Include-based coverage for client monitor enumeration logic. //! //! Scope: include `client/src/output/display.rs` with deterministic GTK/GDK //! stubs to exercise sorting and filtering branches. //! Targets: `client/src/output/display.rs`. //! Why: monitor-layout selection should remain stable even when CI has no real //! display server attached. #[allow(dead_code)] mod gtk { pub mod gdk { use std::cell::RefCell; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Rectangle { pub x: i32, pub y: i32, pub w: i32, pub h: i32, } impl Rectangle { pub fn new(x: i32, y: i32, w: i32, h: i32) -> Self { Self { x, y, w, h } } pub fn width(&self) -> i32 { self.w } pub fn height(&self) -> i32 { self.h } } #[derive(Clone, Debug)] pub struct Monitor { connector: Option, model: Option, geometry: Rectangle, scale_factor: i32, } impl Monitor { pub fn new( connector: Option<&str>, model: Option<&str>, geometry: Rectangle, scale_factor: i32, ) -> Self { Self { connector: connector.map(str::to_owned), model: model.map(str::to_owned), geometry, scale_factor, } } pub fn connector(&self) -> Option { self.connector.clone() } pub fn model(&self) -> Option { self.model.clone() } pub fn geometry(&self) -> Rectangle { self.geometry } pub fn scale_factor(&self) -> i32 { self.scale_factor } } #[derive(Clone, Debug)] pub enum Object { Monitor(Monitor), Other, } impl Object { pub fn downcast(self) -> Result { match self { Self::Monitor(monitor) => Ok(monitor), other => Err(other), } } } #[derive(Clone, Debug, Default)] pub struct MonitorList { pub items: Vec, } #[derive(Clone, Debug)] pub struct Display { monitors: MonitorList, } impl Display { pub fn default() -> Option { DISPLAY.with(|slot| slot.borrow().clone()) } pub fn monitors(&self) -> MonitorList { self.monitors.clone() } } thread_local! { static DISPLAY: RefCell> = const { RefCell::new(None) }; } pub fn set_mock_display(display: Option) { DISPLAY.with(|slot| { *slot.borrow_mut() = display; }); } pub fn display_from_items(items: Vec) -> Display { Display { monitors: MonitorList { items }, } } } pub mod prelude { use super::gdk::{MonitorList, Object}; pub trait ListModelExt { fn n_items(&self) -> u32; fn item(&self, idx: u32) -> Option; } impl ListModelExt for MonitorList { fn n_items(&self) -> u32 { self.items.len() as u32 } fn item(&self, idx: u32) -> Option { self.items.get(idx as usize).cloned() } } } } #[allow(warnings)] mod display_include_contract { use crate::gtk; include!(env!("LESAVKA_CLIENT_OUTPUT_DISPLAY_SRC")); use crate::gtk::gdk as mock_gdk; use serial_test::serial; #[test] #[serial] fn enumerate_monitors_falls_back_when_display_is_missing() { mock_gdk::set_mock_display(None); let monitors = enumerate_monitors(); assert_eq!(monitors.len(), 1); assert_eq!(monitors[0].geometry.width(), 1920); assert_eq!(monitors[0].geometry.height(), 1080); assert!(!monitors[0].is_internal); } #[test] #[serial] fn enumerate_monitors_sorts_external_monitors_first() { let items = vec![ mock_gdk::Object::Monitor(mock_gdk::Monitor::new( Some("eDP-1"), Some("internal"), mock_gdk::Rectangle::new(0, 0, 1920, 1200), 2, )), mock_gdk::Object::Monitor(mock_gdk::Monitor::new( Some("HDMI-A-1"), Some("external"), mock_gdk::Rectangle::new(1920, 0, 1920, 1080), 1, )), mock_gdk::Object::Monitor(mock_gdk::Monitor::new( Some("my-internal-panel"), Some("alt"), mock_gdk::Rectangle::new(-1920, 0, 1280, 720), 1, )), ]; mock_gdk::set_mock_display(Some(mock_gdk::display_from_items(items))); let monitors = enumerate_monitors(); assert_eq!(monitors.len(), 3); assert!(!monitors[0].is_internal, "external monitor should be first"); assert!(monitors[1].is_internal); assert!(monitors[2].is_internal); } #[test] #[serial] fn enumerate_monitors_ignores_non_monitor_objects() { let items = vec![ mock_gdk::Object::Other, mock_gdk::Object::Monitor(mock_gdk::Monitor::new( Some("DP-1"), Some("dock"), mock_gdk::Rectangle::new(0, 0, 2560, 1440), 1, )), ]; mock_gdk::set_mock_display(Some(mock_gdk::display_from_items(items))); let monitors = enumerate_monitors(); assert_eq!(monitors.len(), 1); assert_eq!(monitors[0].geometry.width(), 2560); assert_eq!(monitors[0].scale_factor, 1); } }