220 lines
6.1 KiB
Rust
220 lines
6.1 KiB
Rust
//! 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<String>,
|
|
model: Option<String>,
|
|
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<String> {
|
|
self.connector.clone()
|
|
}
|
|
|
|
pub fn model(&self) -> Option<String> {
|
|
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 {
|
|
#[allow(clippy::extra_unused_type_parameters)]
|
|
pub fn downcast<T>(self) -> Result<Monitor, Self> {
|
|
match self {
|
|
Self::Monitor(monitor) => Ok(monitor),
|
|
other => Err(other),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug, Default)]
|
|
pub struct MonitorList {
|
|
pub items: Vec<Object>,
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct Display {
|
|
monitors: MonitorList,
|
|
}
|
|
|
|
impl Display {
|
|
pub fn default() -> Option<Self> {
|
|
DISPLAY.with(|slot| slot.borrow().clone())
|
|
}
|
|
|
|
pub fn monitors(&self) -> MonitorList {
|
|
self.monitors.clone()
|
|
}
|
|
}
|
|
|
|
thread_local! {
|
|
static DISPLAY: RefCell<Option<Display>> = const { RefCell::new(None) };
|
|
}
|
|
|
|
pub fn set_mock_display(display: Option<Display>) {
|
|
DISPLAY.with(|slot| {
|
|
*slot.borrow_mut() = display;
|
|
});
|
|
}
|
|
|
|
pub fn display_from_items(items: Vec<Object>) -> 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<Object>;
|
|
}
|
|
|
|
impl ListModelExt for MonitorList {
|
|
fn n_items(&self) -> u32 {
|
|
self.items.len() as u32
|
|
}
|
|
|
|
fn item(&self, idx: u32) -> Option<Object> {
|
|
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);
|
|
}
|
|
}
|