Complete TUI coverage and security audit

This commit is contained in:
Hermes Agent
2026-08-10 11:39:45 +00:00
parent 71bbff934e
commit c2632656bc
10 changed files with 958 additions and 44 deletions

View File

@@ -18,6 +18,29 @@ use crate::{
const MINIMUM_WIDTH: u16 = 40;
const MINIMUM_HEIGHT: u16 = 8;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ColorCapability {
Color,
Monochrome,
}
impl ColorCapability {
pub fn detect() -> Self {
Self::from_environment(
std::env::var_os("TERM").as_deref(),
std::env::var_os("NO_COLOR").is_some(),
)
}
fn from_environment(term: Option<&std::ffi::OsStr>, no_color: bool) -> Self {
if no_color || term.is_some_and(|term| term == "dumb") {
Self::Monochrome
} else {
Self::Color
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum LayoutClass {
TooSmall,
@@ -39,6 +62,19 @@ pub fn layout_class(area: Rect) -> LayoutClass {
}
pub fn draw(frame: &mut Frame, app: &App) {
draw_with_color_capability(frame, app, ColorCapability::Color);
}
pub fn draw_with_color_capability(frame: &mut Frame, app: &App, capability: ColorCapability) {
draw_inner(frame, app);
if capability == ColorCapability::Monochrome {
for cell in &mut frame.buffer_mut().content {
cell.set_fg(Color::Reset).set_bg(Color::Reset);
}
}
}
fn draw_inner(frame: &mut Frame, app: &App) {
let area = frame.area();
if layout_class(area) == LayoutClass::TooSmall {
frame.render_widget(
@@ -77,7 +113,8 @@ fn render_content(frame: &mut Frame, app: &App, area: Rect) {
return;
}
if let Some(matrix) = app.qr_popup() {
if let Some(popup) = app.qr_popup() {
let matrix = popup.matrix();
let padded_width = matrix.width() + 8;
let needed_width =
u16::try_from(padded_width.saturating_mul(2).saturating_add(2)).unwrap_or(u16::MAX);
@@ -98,7 +135,7 @@ fn render_content(frame: &mut Frame, app: &App, area: Rect) {
};
frame.render_widget(
Paragraph::new(text)
.block(Block::bordered().title("OTP QR — Esc closes"))
.block(Block::bordered().title(format!("{} — Esc closes", popup.title())))
.wrap(Wrap { trim: false }),
area,
);
@@ -654,6 +691,8 @@ fn prompt_line(app: &App) -> Paragraph<'static> {
#[cfg(test)]
mod tests {
use std::ffi::OsStr;
use ratatui::{Terminal, backend::TestBackend};
use super::*;
@@ -685,6 +724,49 @@ mod tests {
assert_eq!(layout_class(Rect::new(0, 0, 140, 20)), LayoutClass::Wide);
}
#[test]
fn color_capability_has_a_complete_monochrome_fallback() {
assert_eq!(
ColorCapability::from_environment(Some(OsStr::new("xterm-256color")), false),
ColorCapability::Color
);
assert_eq!(
ColorCapability::from_environment(Some(OsStr::new("dumb")), false),
ColorCapability::Monochrome
);
assert_eq!(
ColorCapability::from_environment(Some(OsStr::new("xterm")), true),
ColorCapability::Monochrome
);
let app = App::new();
let backend = TestBackend::new(100, 20);
let mut terminal = Terminal::new(backend).expect("test terminal");
terminal
.draw(|frame| draw_with_color_capability(frame, &app, ColorCapability::Monochrome))
.expect("monochrome draw");
assert!(
terminal
.backend()
.buffer()
.content
.iter()
.all(|cell| { cell.fg == Color::Reset && cell.bg == Color::Reset })
);
terminal
.draw(|frame| draw_with_color_capability(frame, &app, ColorCapability::Color))
.expect("color draw");
assert!(
terminal
.backend()
.buffer()
.content
.iter()
.any(|cell| { cell.fg != Color::Reset || cell.bg != Color::Reset })
);
}
#[test]
fn minimum_size_message_is_clear() {
let output = render(39, 8, &App::new());