#[cfg(test)]
mod tests {
use crate::event::Rect;
use crate::primitives::terminal::{Terminal, TerminalCell, TerminalCursorShape};
use crate::terminal_style::wide_glyph_x_scale;
use crate::theme::Theme;
use crate::types::{Color, WidgetId};
use crate::win::testing::HeadlessSurface;
use crate::win::text::{fill_rect, DWrite};
const W: u32 = 200;
const H: u32 = 120;
const CHAR_W: f32 = 10.0;
const LINE_H: f32 = 20.0;
fn cell(ch: char, fg: Color, bg: Color) -> TerminalCell {
TerminalCell {
text: ch.to_string(),
fg,
bg,
bold: false,
italic: false,
underline: false,
dim: false,
selected: false,
is_cursor: false,
is_find_match: false,
is_find_active: false,
cursor_shape: TerminalCursorShape::Block,
cursor_blinking: false,
}
}
fn dwrite() -> DWrite {
DWrite::new("Segoe UI", 10.0, None)
.expect("create DWrite")
.0
}
fn paint(surface: &HeadlessSurface, dwrite: &DWrite, term: &Terminal, theme: &Theme) {
surface
.paint(|target| {
let mut raw = crate::win::surface::D2dSurface {
target,
dwrite: Some(dwrite),
};
crate::primitives::terminal::paint(
term, &mut raw, theme, 0.0, 0.0, W as f32, H as f32, LINE_H, CHAR_W, None,
);
})
.expect("paint");
}
#[test]
fn cell_bg_paints_at_its_grid_position() {
let magenta = Color::rgb(200, 30, 200);
let term = Terminal {
id: WidgetId::new("term"),
cells: vec![vec![cell('A', Color::rgb(255, 255, 255), magenta)]],
scrollbar: None,
};
let surface = HeadlessSurface::new(W, H).expect("create surface");
let dwrite = dwrite();
let theme = Theme::default();
paint(&surface, &dwrite, &term, &theme);
let px = surface.pixel_at(1, 1);
assert_eq!((px.r, px.g, px.b), (magenta.r, magenta.g, magenta.b));
}
#[test]
fn wide_cell_background_spans_two_columns() {
let magenta = Color::rgb(200, 30, 200);
let cyan = Color::rgb(30, 200, 200);
let row = vec![cell('日', magenta, magenta), cell(' ', magenta, cyan)];
let term = Terminal {
id: WidgetId::new("term"),
cells: vec![row],
scrollbar: None,
};
let surface = HeadlessSurface::new(W, H).expect("create surface");
let dwrite = dwrite();
let theme = Theme::default();
paint(&surface, &dwrite, &term, &theme);
let probe_x = (CHAR_W * 1.5) as u32;
let px = surface.pixel_at(probe_x, 5);
assert_eq!(
(px.r, px.g, px.b),
(magenta.r, magenta.g, magenta.b),
"wide cell's background should span both columns, not be \
overpainted by the continuation cell's background"
);
}
#[test]
fn narrow_wide_glyph_is_stretched_to_fill_two_cells() {
let cell_w = 18.0;
let scale = wide_glyph_x_scale(15.0, cell_w);
assert!(
(scale - 1.2).abs() < 1e-9,
"15px glyph in an 18px box should scale 1.2x, got {scale}"
);
assert!((15.0 * scale - cell_w).abs() < 1e-9);
}
#[test]
fn exact_fit_wide_glyph_is_not_scaled() {
assert_eq!(wide_glyph_x_scale(18.0, 18.0), 1.0);
}
#[test]
fn over_wide_glyph_is_shrunk_into_box() {
let scale = wide_glyph_x_scale(24.0, 18.0);
assert!(
scale < 1.0,
"24px glyph in 18px box should shrink, got {scale}"
);
assert!((24.0 * scale - 18.0).abs() < 1e-9);
}
#[test]
fn narrow_cells_advance_by_single_char_width() {
let magenta = Color::rgb(200, 30, 200);
let cyan = Color::rgb(30, 200, 200);
let white = Color::rgb(255, 255, 255);
let row = vec![cell('A', white, magenta), cell('B', cyan, cyan)];
let term = Terminal {
id: WidgetId::new("term"),
cells: vec![row],
scrollbar: None,
};
let surface = HeadlessSurface::new(W, H).expect("create surface");
let dwrite = dwrite();
let theme = Theme::default();
paint(&surface, &dwrite, &term, &theme);
let probe_x = (CHAR_W * 1.5) as u32;
let px = surface.pixel_at(probe_x, 5);
assert_eq!(
(px.r, px.g, px.b),
(cyan.r, cyan.g, cyan.b),
"narrow cells must still advance by exactly char_width"
);
}
#[test]
fn cursor_cell_inverts_fg_bg() {
let fg = Color::rgb(10, 220, 30);
let bg = Color::rgb(40, 40, 40);
let mut c = cell('X', fg, bg);
c.is_cursor = true;
let term = Terminal {
id: WidgetId::new("term"),
cells: vec![vec![c]],
scrollbar: None,
};
let surface = HeadlessSurface::new(W, H).expect("create surface");
let dwrite = dwrite();
let theme = Theme::default();
paint(&surface, &dwrite, &term, &theme);
let px = surface.pixel_at(1, 1);
assert_eq!((px.r, px.g, px.b), (fg.r, fg.g, fg.b));
}
#[test]
fn find_active_cell_paints_orange() {
let mut c = cell('z', Color::rgb(255, 255, 255), Color::rgb(20, 20, 20));
c.is_find_active = true;
let term = Terminal {
id: WidgetId::new("term"),
cells: vec![vec![c]],
scrollbar: None,
};
let surface = HeadlessSurface::new(W, H).expect("create surface");
let dwrite = dwrite();
let theme = Theme::default();
paint(&surface, &dwrite, &term, &theme);
let px = surface.pixel_at(1, 1);
assert_eq!((px.r, px.g, px.b), (255, 165, 0));
}
#[test]
fn selected_cell_uses_theme_selection_bg() {
let mut c = cell('x', Color::rgb(255, 255, 255), Color::rgb(10, 10, 10));
c.selected = true;
let term = Terminal {
id: WidgetId::new("term"),
cells: vec![vec![c]],
scrollbar: None,
};
let surface = HeadlessSurface::new(W, H).expect("create surface");
let dwrite = dwrite();
let theme = Theme::default();
paint(&surface, &dwrite, &term, &theme);
let px = surface.pixel_at(1, 1);
assert_eq!(
(px.r, px.g, px.b),
(
theme.selection_bg.r,
theme.selection_bg.g,
theme.selection_bg.b
)
);
}
#[test]
fn rows_past_the_pane_bottom_are_clipped() {
let magenta = Color::rgb(200, 30, 200);
let bg_sentinel = Color::rgb(1, 2, 3);
let cells: Vec<Vec<_>> = (0..60)
.map(|_| {
(0..20)
.map(|_| cell(' ', Color::rgb(255, 255, 255), magenta))
.collect()
})
.collect();
let term = Terminal {
id: WidgetId::new("term"),
cells,
scrollbar: None,
};
let pane_h = 60.0_f32;
let surface = HeadlessSurface::new(W, H).expect("create surface");
let dwrite = dwrite();
let theme = Theme::default();
surface
.paint(|target| {
let _ = fill_rect(target, Rect::new(0.0, 0.0, W as f32, H as f32), bg_sentinel);
let mut raw = crate::win::surface::D2dSurface {
target,
dwrite: Some(&dwrite),
};
crate::primitives::terminal::paint(
&term, &mut raw, &theme, 0.0, 0.0, W as f32, pane_h, LINE_H, CHAR_W, None,
);
})
.expect("paint");
let inside = surface.pixel_at(4, 4);
assert_eq!(
(inside.r, inside.g, inside.b),
(magenta.r, magenta.g, magenta.b)
);
let below = surface.pixel_at(4, H - 4);
assert_eq!(
(below.r, below.g, below.b),
(bg_sentinel.r, bg_sentinel.g, bg_sentinel.b),
"rows below the pane bottom must be clipped, not bled"
);
}
#[test]
fn bold_cell_paints_without_error() {
let mut c = cell('B', Color::rgb(255, 255, 255), Color::rgb(20, 20, 20));
c.bold = true;
let term = Terminal {
id: WidgetId::new("term"),
cells: vec![vec![c]],
scrollbar: None,
};
let surface = HeadlessSurface::new(W, H).expect("create surface");
let dwrite = dwrite();
let theme = Theme::default();
paint(&surface, &dwrite, &term, &theme);
let px = surface.pixel_at(1, 1);
assert_eq!((px.r, px.g, px.b), (20, 20, 20));
}
#[test]
fn divider_paints_a_one_dip_line() {
let surface = HeadlessSurface::new(W, H).expect("create surface");
let theme = Theme::default();
surface
.paint(|target| {
let _ = fill_rect(
target,
Rect::new(0.0, 0.0, W as f32, H as f32),
Color::rgb(0, 0, 0),
);
let dwrite = dwrite();
let mut raw = crate::win::surface::D2dSurface {
target,
dwrite: Some(&dwrite),
};
crate::primitives::terminal::paint_divider(&mut raw, 50.0, 0.0, H as f32, &theme);
})
.expect("paint");
let on_line = surface.pixel_at(50, 10);
let off_line = surface.pixel_at(52, 10);
assert_eq!(
(on_line.r, on_line.g, on_line.b),
(theme.separator.r, theme.separator.g, theme.separator.b)
);
assert_eq!((off_line.r, off_line.g, off_line.b), (0, 0, 0));
}
}