• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

jzombie / term-wm / 20885255367

10 Jan 2026 10:20PM UTC coverage: 57.056% (+10.0%) from 47.071%
20885255367

Pull #20

github

web-flow
Merge 123a43984 into bfecf0f75
Pull Request #20: Initial clipboard and offscreen buffer support

2045 of 3183 new or added lines in 26 files covered. (64.25%)

76 existing lines in 15 files now uncovered.

6788 of 11897 relevant lines covered (57.06%)

9.62 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

7.25
/src/runner.rs
1
use std::io;
2
use std::time::Duration;
3

4
use crossterm::event::{Event, KeyEventKind};
5
use ratatui::buffer::Buffer;
6
use ratatui::prelude::{Constraint, Direction, Rect};
7
use ratatui::style::Style;
8

9
use crate::components::{Component, ComponentContext, ConfirmAction};
10
use crate::drivers::{InputDriver, OutputDriver};
11
use crate::event_loop::{ControlFlow, EventLoop};
12
use crate::layout::{LayoutNode, TilingLayout};
13
use crate::ui::UiFrame;
14
use crate::window::decorator::WindowDecorator;
15
use crate::window::{
16
    AppWindowDraw, LayoutContract, WindowDrawTask, WindowId, WindowManager, WindowSurface,
17
    WmMenuAction,
18
};
19

20
pub trait HasWindowManager<W: Copy + Eq + Ord, R: Copy + Eq + Ord> {
21
    fn windows(&mut self) -> &mut WindowManager<W, R>;
22
    fn wm_new_window(&mut self) -> std::io::Result<()> {
×
23
        Ok(())
×
24
    }
×
25
    fn wm_close_window(&mut self, _id: R) -> std::io::Result<()> {
×
26
        Ok(())
×
27
    }
×
NEW
28
    fn set_clipboard_enabled(&mut self, _enabled: bool) {}
×
29
}
30

31
pub trait WindowApp<W: Copy + Eq + Ord, R: Copy + Eq + Ord>: HasWindowManager<W, R> {
32
    fn enumerate_windows(&mut self) -> Vec<R>;
33
    fn render_window(&mut self, frame: &mut UiFrame<'_>, window: AppWindowDraw<R>);
34

35
    fn empty_window_message(&self) -> &str {
×
36
        "No windows"
×
37
    }
×
38

39
    fn layout_for_windows(&mut self, windows: &[R]) -> Option<TilingLayout<R>> {
×
40
        auto_layout_for_windows(windows)
×
41
    }
×
42

NEW
43
    fn window_component(&mut self, _id: R) -> Option<&mut dyn Component> {
×
NEW
44
        None
×
NEW
45
    }
×
46
}
47

NEW
48
fn handle_focused_app_event<A, W, R>(event: &Event, app: &mut A) -> bool
×
NEW
49
where
×
NEW
50
    A: WindowApp<W, R>,
×
NEW
51
    W: Copy + Eq + Ord,
×
NEW
52
    R: Copy + Eq + Ord + PartialEq<W> + std::fmt::Debug,
×
53
{
NEW
54
    let mut pending_focus: Option<R> = None;
×
NEW
55
    let mut pending_event: Option<Event> = None;
×
NEW
56
    let consumed = {
×
NEW
57
        let windows = app.windows();
×
NEW
58
        windows.dispatch_focused_event(event, |focus_id, localized| {
×
NEW
59
            pending_focus = Some(focus_id);
×
NEW
60
            pending_event = Some(localized.clone());
×
NEW
61
            false
×
NEW
62
        })
×
63
    };
64

NEW
65
    if let (Some(focus_id), Some(localized)) = (pending_focus, pending_event) {
×
NEW
66
        if let Some(component) = app.window_component(focus_id) {
×
NEW
67
            let ctx = ComponentContext::new(true);
×
NEW
68
            component.handle_event(&localized, &ctx)
×
69
        } else {
NEW
70
            false
×
71
        }
72
    } else {
NEW
73
        consumed
×
74
    }
UNCOV
75
}
×
76

77
#[allow(clippy::too_many_arguments)]
NEW
78
pub fn run_app<O, D, A, W, R, FDraw, FMap, FFocus>(
×
79
    output: &mut O,
×
80
    driver: &mut D,
×
81
    app: &mut A,
×
82
    focus_regions: &[R],
×
83
    map_region: FMap,
×
84
    _map_focus: FFocus,
×
85
    poll_interval: Duration,
×
86
    mut draw: FDraw,
×
87
) -> io::Result<()>
×
88
where
×
89
    O: OutputDriver,
×
90
    D: InputDriver,
×
NEW
91
    A: WindowApp<W, R>,
×
92
    W: Copy + Eq + Ord,
×
93
    R: Copy + Eq + Ord + PartialEq<W> + std::fmt::Debug,
×
94
    FDraw: for<'frame> FnMut(UiFrame<'frame>, &mut A),
×
95
    FMap: Fn(R) -> W + Copy,
×
96
    FFocus: Fn(W) -> Option<R>,
×
97
{
98
    let mut event_loop = EventLoop::new(driver, poll_interval);
×
99
    event_loop
×
100
        .driver()
×
101
        .set_mouse_capture(app.windows().mouse_capture_enabled())?;
×
102

103
    // The WindowManager now provides `take_closed_app_windows()` to drain app ids
104
    // whose windows were closed; we'll poll that each loop and call `app.wm_close_window`.
105
    // No additional setup required here.
106

107
    event_loop.run(|driver, event| {
×
108
        let handler = || -> io::Result<ControlFlow> {
×
109
            // Check if a panic occurred (e.g. in a background thread or previous iteration)
110
            // and force the debug window open if so.
111
            if crate::components::sys::debug_log::take_panic_pending() {
×
112
                app.windows().open_debug_window();
×
113
            }
×
114
            // Also check for reported non-fatal errors that should pop the debug log
115
            if crate::components::sys::debug_log::take_error_pending() {
×
116
                app.windows().open_debug_window();
×
117
            }
×
118

119
            // Drain any pending closed app ids recorded by the WindowManager and invoke app cleanup.
120
            for id in app.windows().take_closed_app_windows() {
×
121
                app.wm_close_window(id)?;
×
122
            }
NEW
123
            let mut flush_state_changes = |app: &mut A, flow: ControlFlow| {
×
124
                if let Some(enabled) = app.windows().take_mouse_capture_change() {
×
125
                    let _ = driver.set_mouse_capture(enabled);
×
126
                }
×
NEW
127
                if let Some(clipboard) = app.windows().take_clipboard_change() {
×
NEW
128
                    app.set_clipboard_enabled(clipboard);
×
NEW
129
                }
×
130
                Ok(flow)
×
131
            };
×
132
            if let Some(evt) = event {
×
133
                // Map key events to high-level `Action`s once to prefer action-based handling
134
                let mapped_action = match &evt {
×
135
                    Event::Key(key) => {
×
136
                        crate::keybindings::KeyBindings::default().action_for_key(key)
×
137
                    }
138
                    _ => None,
×
139
                };
140
                if app.windows().exit_confirm_visible() {
×
141
                    if let Some(action) = app.windows().handle_exit_confirm_event(&evt) {
×
142
                        match action {
×
143
                            ConfirmAction::Confirm => return Ok(ControlFlow::Quit),
×
144
                            ConfirmAction::Cancel => app.windows().close_exit_confirm(),
×
145
                        }
146
                    }
×
NEW
147
                    return flush_state_changes(app, ControlFlow::Continue);
×
148
                }
×
149

150
                if app.windows().help_overlay_visible() {
×
151
                    let _ = app.windows().handle_help_event(&evt);
×
NEW
152
                    return flush_state_changes(app, ControlFlow::Continue);
×
153
                }
×
154
                let wm_mode = app.windows().layout_contract() == LayoutContract::WindowManaged;
×
155
                if wm_mode
×
156
                    && let Event::Key(key) = &evt
×
157
                    && key.kind == KeyEventKind::Press
×
158
                    && crate::keybindings::KeyBindings::default()
×
159
                        .matches(crate::keybindings::Action::WmToggleOverlay, key)
×
160
                {
161
                    if app.windows().wm_overlay_visible() {
×
162
                        let passthrough = app.windows().esc_passthrough_active();
×
163
                        app.windows().close_wm_overlay();
×
164
                        if passthrough {
×
NEW
165
                            let passthrough_event = Event::Key(*key);
×
NEW
166
                            let _ = handle_focused_app_event(&passthrough_event, app);
×
167
                        }
×
168
                    } else {
×
169
                        app.windows().open_wm_overlay();
×
170
                    }
×
NEW
171
                    return flush_state_changes(app, ControlFlow::Continue);
×
172
                }
×
173
                if wm_mode && app.windows().wm_overlay_visible() {
×
174
                    if let Some(action) = app.windows().handle_wm_menu_event(&evt) {
×
175
                        match action {
×
176
                            WmMenuAction::CloseMenu => {
×
177
                                app.windows().close_wm_overlay();
×
178
                            }
×
179
                            WmMenuAction::ToggleMouseCapture => {
×
180
                                app.windows().toggle_mouse_capture();
×
181
                            }
×
NEW
182
                            WmMenuAction::ToggleClipboardMode => {
×
NEW
183
                                app.windows().toggle_clipboard_enabled();
×
NEW
184
                            }
×
185
                            WmMenuAction::MinimizeWindow => {
×
186
                                let id = app.windows().wm_focus();
×
187
                                app.windows().minimize_window(id);
×
188
                                app.windows().close_wm_overlay();
×
189
                            }
×
190
                            WmMenuAction::MaximizeWindow => {
×
191
                                let id = app.windows().wm_focus();
×
192
                                app.windows().toggle_maximize(id);
×
193
                                app.windows().close_wm_overlay();
×
194
                            }
×
195
                            WmMenuAction::CloseWindow => {
×
196
                                let id = app.windows().wm_focus();
×
197
                                app.windows().close_window(id);
×
198
                                app.windows().close_wm_overlay();
×
199
                            }
×
200
                            WmMenuAction::NewWindow => {
201
                                app.wm_new_window()?;
×
202
                                app.windows().close_wm_overlay();
×
203
                            }
204
                            WmMenuAction::ToggleDebugWindow => {
×
205
                                app.windows().toggle_debug_window();
×
206
                                app.windows().close_wm_overlay();
×
207
                            }
×
208
                            WmMenuAction::Help => {
×
209
                                app.windows().open_help_overlay();
×
210
                                app.windows().close_wm_overlay();
×
211
                            }
×
212
                            WmMenuAction::BringFloatingFront => {
×
213
                                app.windows().bring_all_floating_to_front();
×
214
                                app.windows().close_wm_overlay();
×
215
                            }
×
216
                            WmMenuAction::ExitUi => {
217
                                app.windows().close_wm_overlay();
×
218
                                app.windows().open_exit_confirm();
×
NEW
219
                                return flush_state_changes(app, ControlFlow::Continue);
×
220
                            }
221
                        }
NEW
222
                        return flush_state_changes(app, ControlFlow::Continue);
×
223
                    }
×
224
                    if app.windows().wm_menu_consumes_event(&evt) {
×
NEW
225
                        return flush_state_changes(app, ControlFlow::Continue);
×
226
                    }
×
227
                    if let Event::Key(_key) = &evt
×
228
                        && mapped_action == Some(crate::keybindings::Action::NewWindow)
×
229
                    {
230
                        app.wm_new_window()?;
×
231
                        app.windows().close_wm_overlay();
×
NEW
232
                        return flush_state_changes(app, ControlFlow::Continue);
×
233
                    }
×
234
                    if let Event::Key(_key) = &evt
×
235
                        && (mapped_action == Some(crate::keybindings::Action::FocusNext)
×
236
                            || mapped_action == Some(crate::keybindings::Action::FocusPrev))
×
237
                    {
238
                        let _ = app.windows().handle_focus_event(
×
239
                            &evt,
×
240
                            focus_regions,
×
241
                            &map_region,
×
242
                            &_map_focus,
×
243
                        );
×
NEW
244
                        return flush_state_changes(app, ControlFlow::Continue);
×
245
                    }
×
246

247
                    if let Event::Key(_) = &evt {
×
NEW
248
                        return flush_state_changes(app, ControlFlow::Continue);
×
249
                    }
×
250
                }
×
251

252
                if matches!(evt, Event::Mouse(_)) && !app.windows().mouse_capture_enabled() {
×
NEW
253
                    return flush_state_changes(app, ControlFlow::Continue);
×
NEW
254
                }
×
NEW
255
                if let Event::Key(key) = &evt
×
NEW
256
                    && key.kind == KeyEventKind::Press
×
NEW
257
                    && crate::keybindings::KeyBindings::default()
×
NEW
258
                        .matches(crate::keybindings::Action::Quit, key)
×
259
                {
NEW
260
                    return flush_state_changes(app, ControlFlow::Quit);
×
261
                }
×
262
                match &evt {
×
263
                    Event::Key(_) if app.windows().capture_active() => {
×
264
                        app.windows().clear_capture();
×
NEW
265
                        let _ = handle_focused_app_event(&evt, app);
×
266
                    }
×
267
                    _ => {
×
NEW
268
                        let _ = handle_focused_app_event(&evt, app);
×
269
                    }
×
270
                }
271
            } else {
272
                // Quit only when the application reports no app windows and
273
                // there are no active system windows/overlays. Minimized
274
                // windows should not cause the app to exit.
NEW
275
                if app.enumerate_windows().is_empty() && !app.windows().has_active_system_windows()
×
276
                {
NEW
277
                    return flush_state_changes(app, ControlFlow::Quit);
×
278
                }
×
279
                app.windows().begin_frame();
×
280
                output.draw(|frame| draw(frame, app))?;
×
281
            }
NEW
282
            flush_state_changes(app, ControlFlow::Continue)
×
283
        };
×
284

285
        match std::panic::catch_unwind(std::panic::AssertUnwindSafe(handler)) {
×
286
            Ok(result) => result,
×
287
            Err(_) => {
288
                // TODO: This needs to be improved; currently requires resizing the terminal window to
289
                // "stabilize" the messages, to produce them in a debug log window. Also, directly setting
290
                // the mouse capture here bypasses the state, and the UI is not reflected. It might be better
291
                // to just turn off mouse capturing and crash the app naturally if this cannot be improved.
292

293
                // A panic occurred; stop mouse capture to avoid terminal spam
294
                let _ = driver.set_mouse_capture(false);
×
295
                // Attempt to immediately redraw the UI so the debug log (populated by the panic hook)
296
                // is visible to the user without waiting for another input event like a resize.
297
                let mut redraw = || -> io::Result<()> {
×
298
                    app.windows().begin_frame();
×
299
                    output.draw(|frame| draw(frame, app))
×
300
                };
×
301
                let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
×
302
                    let _ = redraw();
×
303
                }));
×
304
                // Let the panic hook have recorded details into the debug log; continue event loop.
305
                Ok(ControlFlow::Continue)
×
306
            }
307
        }
308
    })?;
×
309

310
    Ok(())
×
311
}
×
312

313
#[allow(clippy::too_many_arguments)]
NEW
314
pub fn run_window_app<O, D, A, W, R, FMap, FFocus>(
×
315
    output: &mut O,
×
316
    driver: &mut D,
×
317
    app: &mut A,
×
318
    focus_regions: &[R],
×
319
    map_region: FMap,
×
320
    _map_focus: FFocus,
×
321
) -> io::Result<()>
×
322
where
×
323
    O: OutputDriver,
×
324
    D: InputDriver,
×
325
    A: WindowApp<W, R>,
×
326
    W: Copy + Eq + Ord,
×
327
    R: Copy + Eq + Ord + PartialEq<W> + std::fmt::Debug,
×
328
    FMap: Fn(R) -> W + Copy,
×
329
    FFocus: Fn(W) -> Option<R>,
×
330
{
331
    let draw_map = map_region;
×
332
    let mut draw_state = WindowDrawState::default();
×
NEW
333
    let poll_interval = Duration::from_millis(16);
×
334
    run_app(
×
335
        output,
×
336
        driver,
×
337
        app,
×
338
        focus_regions,
×
339
        map_region,
×
340
        _map_focus,
×
341
        poll_interval,
×
342
        move |frame, app| {
×
343
            let mut frame = frame;
×
344
            draw_window_app(&mut frame, app, &mut draw_state, draw_map);
×
345
        },
×
346
    )
347
}
×
348

349
struct WindowDrawState<R> {
350
    known: Vec<R>,
351
}
352

353
impl<R> Default for WindowDrawState<R> {
UNCOV
354
    fn default() -> Self {
×
UNCOV
355
        Self { known: Vec::new() }
×
UNCOV
356
    }
×
357
}
358

359
impl<R: Copy + Eq> WindowDrawState<R> {
UNCOV
360
    fn update(&mut self, windows: &[R]) -> bool {
×
UNCOV
361
        if self.known == windows {
×
UNCOV
362
            false
×
363
        } else {
UNCOV
364
            self.known = windows.to_vec();
×
UNCOV
365
            true
×
366
        }
UNCOV
367
    }
×
368
}
369

370
fn draw_window_app<A, W, R, FMap>(
×
371
    frame: &mut UiFrame<'_>,
×
372
    app: &mut A,
×
373
    state: &mut WindowDrawState<R>,
×
374
    map_region: FMap,
×
375
) where
×
376
    A: WindowApp<W, R>,
×
377
    W: Copy + Eq + Ord,
×
378
    R: Copy + Eq + Ord + PartialEq<W> + std::fmt::Debug,
×
379
    FMap: Fn(R) -> W,
×
380
{
381
    let area = frame.area();
×
382
    let windows = app.enumerate_windows();
×
383
    let windows_changed = state.update(&windows);
×
384
    // If no application windows, we still might need to draw system windows (e.g. debug log).
385
    // The previous check `if windows.is_empty() { return }` prevented this.
386
    // We now allow proceeding, ensuring the layout is handled gracefully.
387

388
    if windows_changed {
×
389
        if let Some(layout) = app.layout_for_windows(&windows) {
×
390
            app.windows().set_managed_layout(layout);
×
391
        } else if windows.is_empty() {
×
392
            // Force a layout update to reflect empty state, but don't clear system windows
×
393
            // that the WindowManager might inject. passing None usually clears the app layout.
×
394
            app.windows().set_managed_layout_none();
×
395
        }
×
396
    }
×
397

398
    if windows.is_empty() {
×
399
        // If app windows are empty, we might still have system windows.
400
        // We render the "empty" message underneath, then let the window manager draw its overlays/system windows on top.
401
        let message = app.empty_window_message();
×
402
        if !message.is_empty() {
×
403
            frame
×
404
                .buffer_mut()
×
405
                .set_string(area.x, area.y, message, Style::default());
×
406
        }
×
407
    }
×
408

409
    let focus_order: Vec<W> = windows.iter().copied().map(map_region).collect();
×
410
    if !focus_order.is_empty() {
×
411
        app.windows().set_focus_order(focus_order);
×
412
    }
×
413
    app.windows().register_managed_layout(area);
×
414
    let plan = app.windows().window_draw_plan(frame);
×
415
    for task in plan {
×
416
        match task {
×
NEW
417
            WindowDrawTask::App(window) => {
×
NEW
418
                let (title, decorator) = {
×
NEW
419
                    let wm = app.windows();
×
NEW
420
                    let title = wm.window_title(WindowId::App(window.id));
×
NEW
421
                    let decorator = wm.decorator();
×
NEW
422
                    (title, decorator)
×
NEW
423
                };
×
NEW
424
                composite_window(
×
NEW
425
                    frame,
×
NEW
426
                    &window.surface,
×
NEW
427
                    window.focused,
×
NEW
428
                    &title,
×
NEW
429
                    decorator.as_ref(),
×
NEW
430
                    |subframe| {
×
NEW
431
                        app.render_window(subframe, window);
×
NEW
432
                    },
×
433
                );
434
            }
NEW
435
            WindowDrawTask::System(window) => {
×
NEW
436
                let (title, decorator) = {
×
NEW
437
                    let wm = app.windows();
×
NEW
438
                    let title = wm.window_title(WindowId::System(window.id));
×
NEW
439
                    let decorator = wm.decorator();
×
NEW
440
                    (title, decorator)
×
NEW
441
                };
×
NEW
442
                composite_window(
×
NEW
443
                    frame,
×
NEW
444
                    &window.surface,
×
NEW
445
                    window.focused,
×
NEW
446
                    &title,
×
NEW
447
                    decorator.as_ref(),
×
NEW
448
                    |subframe| {
×
NEW
449
                        app.windows().render_system_window(subframe, window);
×
NEW
450
                    },
×
451
                );
452
            }
453
        }
454
    }
455
    app.windows().render_overlays(frame);
×
456
}
×
457

NEW
458
fn composite_window<F>(
×
NEW
459
    frame: &mut UiFrame<'_>,
×
NEW
460
    surface: &WindowSurface,
×
NEW
461
    focused: bool,
×
NEW
462
    title: &str,
×
NEW
463
    decorator: &dyn WindowDecorator,
×
NEW
464
    mut render_content: F,
×
NEW
465
) where
×
NEW
466
    F: FnMut(&mut UiFrame<'_>),
×
467
{
NEW
468
    if surface.dest.width == 0 || surface.dest.height == 0 {
×
NEW
469
        return;
×
NEW
470
    }
×
NEW
471
    let local_area = Rect {
×
NEW
472
        x: 0,
×
NEW
473
        y: 0,
×
NEW
474
        width: surface.dest.width,
×
NEW
475
        height: surface.dest.height,
×
NEW
476
    };
×
NEW
477
    let mut buffer = Buffer::empty(local_area);
×
NEW
478
    {
×
NEW
479
        let mut offscreen = UiFrame::from_parts(local_area, &mut buffer);
×
NEW
480
        decorator.render_window(&mut offscreen, local_area, title, focused);
×
NEW
481
        render_content(&mut offscreen);
×
NEW
482
    }
×
NEW
483
    frame.blit_from_signed(&buffer, surface.dest);
×
NEW
484
}
×
485

486
fn auto_layout_for_windows<R: Copy + Eq + Ord>(windows: &[R]) -> Option<TilingLayout<R>> {
2✔
487
    let node = match windows.len() {
2✔
488
        0 => return None,
1✔
489
        1 => LayoutNode::leaf(windows[0]),
1✔
490
        2 => LayoutNode::split(
×
491
            Direction::Horizontal,
×
492
            vec![Constraint::Percentage(50), Constraint::Percentage(50)],
×
493
            vec![LayoutNode::leaf(windows[0]), LayoutNode::leaf(windows[1])],
×
494
        ),
UNCOV
495
        len => {
×
UNCOV
496
            let mut constraints = Vec::with_capacity(len);
×
UNCOV
497
            let base = (100 / len as u16).max(1);
×
UNCOV
498
            for idx in 0..len {
×
UNCOV
499
                if idx == len - 1 {
×
UNCOV
500
                    let used = base.saturating_mul((len - 1) as u16);
×
UNCOV
501
                    constraints.push(Constraint::Percentage(100u16.saturating_sub(used)));
×
UNCOV
502
                } else {
×
UNCOV
503
                    constraints.push(Constraint::Percentage(base));
×
UNCOV
504
                }
×
505
            }
UNCOV
506
            let children = windows.iter().map(|&id| LayoutNode::leaf(id)).collect();
×
UNCOV
507
            LayoutNode::split(Direction::Vertical, constraints, children)
×
508
        }
509
    };
510
    Some(TilingLayout::new(node))
1✔
511
}
2✔
512

513
#[cfg(test)]
514
mod tests {
515
    use super::*;
516

517
    #[test]
518
    fn auto_layout_empty_and_multiple() {
1✔
519
        let empty: Vec<u8> = vec![];
1✔
520
        assert!(auto_layout_for_windows(&empty).is_none());
1✔
521

522
        let one = vec![1u8];
1✔
523
        let layout = auto_layout_for_windows(&one).unwrap();
1✔
524
        // single node should be a leaf
525
        assert!(matches!(layout.root(), crate::layout::LayoutNode::Leaf(_)));
1✔
526
    }
1✔
527

528
    #[test]
529
    fn runner_does_not_quit_when_app_reports_windows_but_wm_has_no_active_regions() {
1✔
530
        use crate::window::WindowManager;
531

532
        // Create an empty WindowManager (no active regions/z-order).
533
        let wm: WindowManager<usize, usize> = WindowManager::new_managed(0);
1✔
534
        assert!(!wm.has_any_active_windows());
1✔
535

536
        // Create a fake app that enumerates windows (i.e., app-level windows still exist)
537
        // while the WM reports no active windows.
538
        struct FakeApp {
539
            wm: WindowManager<usize, usize>,
540
        }
541
        impl super::HasWindowManager<usize, usize> for FakeApp {
542
            fn windows(&mut self) -> &mut WindowManager<usize, usize> {
1✔
543
                &mut self.wm
1✔
544
            }
1✔
545
        }
546
        impl super::WindowApp<usize, usize> for FakeApp {
547
            fn enumerate_windows(&mut self) -> Vec<usize> {
2✔
548
                vec![1]
2✔
549
            }
2✔
NEW
550
            fn render_window(
×
NEW
551
                &mut self,
×
NEW
552
                _frame: &mut crate::ui::UiFrame<'_>,
×
NEW
553
                _window: AppWindowDraw<usize>,
×
NEW
554
            ) {
×
NEW
555
            }
×
556
        }
557

558
        let mut app = FakeApp { wm };
1✔
559

560
        // Sanity: the app-level enumerate shows a window, but the WM reports no active regions.
561
        assert!(!app.enumerate_windows().is_empty());
1✔
562
        assert!(!app.windows().has_active_system_windows());
1✔
563

564
        // The runner's quit condition should NOT trigger a quit here:
565
        // quit_if_no_windows = app.enumerate_windows().is_empty() && !app.windows().has_active_system_windows()
566
        let quit_if_no_windows =
1✔
567
            app.enumerate_windows().is_empty() && !app.windows().has_active_system_windows();
1✔
568
        assert!(
1✔
569
            !quit_if_no_windows,
1✔
NEW
570
            "Runner would quit even though app reports windows"
×
571
        );
572
    }
1✔
573
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc