Skip to main content

teksilo_automation/
recording_ops.rs

1// SPDX-License-Identifier: MPL-2.0
2// SPDX-FileCopyrightText: 2026 FernTech
3
4//! A [`WindowOps`] implementation for the headless server.
5//!
6//! [`NoopWindowOps`](teksilo_core::NoopWindowOps) *panics* on `open_window`.
7//! That is the right behaviour for a standalone test tree, but it is wrong
8//! for automation: an AT action (a menu item, a "New window" button) can
9//! legitimately call `open_window`, and crashing the whole server on it
10//! would be a terrible failure mode. [`RecordingWindowOps`] instead
11//! **records a summary of the requested window and returns a synthetic
12//! id**, so the action completes and the harness can observe that a window
13//! was requested via [`RecordingWindowOps::opened`].
14
15use teksilo_core::{TeksiloWindowId, WindowConfig, WindowOps, WindowState};
16
17/// A summary of an `open_window` request. `WindowConfig` is not `Clone`
18/// (it holds `FnOnce` root builders), so the recorder keeps only its
19/// inspectable scalar fields.
20#[derive(Debug, Clone, PartialEq, Eq)]
21pub struct RecordedWindow {
22    /// The requested window title.
23    pub title: String,
24    /// The requested stable `string_id`, if any.
25    pub string_id: Option<String>,
26    /// The requested `(width, height)`.
27    pub size: (u32, u32),
28    /// The synthetic id this recorder returned for the request.
29    pub assigned_id: TeksiloWindowId,
30}
31
32/// A non-panicking `WindowOps` for the headless automation server: it
33/// records `open_window` requests and hands back synthetic ids; every
34/// other method no-ops (matching `NoopWindowOps`).
35#[derive(Debug, Default)]
36pub struct RecordingWindowOps {
37    next_id: u64,
38    /// Every `open_window` request seen, in order.
39    pub opened: Vec<RecordedWindow>,
40    /// Window ids passed to `focus_window`, in order.
41    pub focused: Vec<TeksiloWindowId>,
42    /// Window ids passed to `close_window_by_id`, in order.
43    pub closed: Vec<TeksiloWindowId>,
44}
45
46impl RecordingWindowOps {
47    pub fn new() -> Self {
48        Self::default()
49    }
50}
51
52impl WindowOps for RecordingWindowOps {
53    fn open_window(&mut self, config: WindowConfig) -> TeksiloWindowId {
54        // Synthetic ids start at a high base so they never collide with a
55        // real window manager's allocator in a mixed test.
56        let id = TeksiloWindowId::new(0x1000_0000 + self.next_id);
57        self.next_id += 1;
58        self.opened.push(RecordedWindow {
59            title: config.title.clone(),
60            string_id: config.string_id.clone(),
61            size: config.size,
62            assigned_id: id,
63        });
64        id
65    }
66
67    fn find_window(&self, string_id: &str) -> Option<TeksiloWindowId> {
68        self.opened
69            .iter()
70            .find(|w| w.string_id.as_deref() == Some(string_id))
71            .map(|w| w.assigned_id)
72    }
73
74    fn window_state(&self, _id: TeksiloWindowId) -> Option<WindowState> {
75        None
76    }
77
78    fn windows(&self) -> Vec<WindowState> {
79        Vec::new()
80    }
81
82    fn focus_window(&mut self, id: TeksiloWindowId) {
83        self.focused.push(id);
84    }
85
86    fn close_window_by_id(&mut self, id: TeksiloWindowId) {
87        self.closed.push(id);
88    }
89}