use escriba_core::{Action, Mode};
use escriba_render::gpu::{CellGrid, cell_grid, mode_color, splash_runs};
use escriba_ui::chrome::{ChromePalette, FleetTheme};
use escriba_ui::splash::{Splash, SplashEntry, SplashRole};
const FONT: f32 = 14.0;
const LINE: f32 = 20.0;
#[test]
fn a_typical_window_maps_to_a_sane_grid() {
let g = cell_grid(1200, 800, FONT, LINE);
assert_eq!(g.cols, 142, "1200 / 8.4 = 142");
assert_eq!(g.rows, 39, "800 / 20 = 40, minus the status row");
}
#[test]
fn exactly_one_row_is_reserved_for_the_status_line() {
for h in [200u32, 400, 800, 1080] {
let g = cell_grid(1000, h, FONT, LINE);
let unreserved = (h as f32 / LINE).floor() as u16;
assert_eq!(
g.rows,
unreserved - 1,
"height {h}: expected one reserved row",
);
}
}
#[test]
fn a_degenerate_surface_never_produces_a_zero_or_negative_grid() {
for (w, h) in [(0u32, 0u32), (1, 1), (0, 800), (1200, 0), (5, 19)] {
let g = cell_grid(w, h, FONT, LINE);
assert!(g.cols >= 1, "cols must stay positive at {w}x{h}: {g:?}");
assert!(g.rows >= 1, "rows must stay positive at {w}x{h}: {g:?}");
}
}
#[test]
fn a_degenerate_font_metric_does_not_divide_by_zero() {
for (fs, lh) in [(0.0f32, 20.0f32), (14.0, 0.0), (0.0, 0.0), (-5.0, -5.0)] {
let g = cell_grid(800, 600, fs, lh);
assert!(g.cols >= 1 && g.rows >= 1, "{fs}/{lh} → {g:?}");
}
}
#[test]
fn a_bigger_window_is_never_a_smaller_grid() {
let mut prev = CellGrid { cols: 0, rows: 0 };
for px in [200u32, 400, 800, 1600, 3200] {
let g = cell_grid(px, px, FONT, LINE);
assert!(g.cols >= prev.cols, "cols shrank at {px}");
assert!(g.rows >= prev.rows, "rows shrank at {px}");
prev = g;
}
}
#[test]
fn a_larger_font_yields_fewer_cells() {
let small = cell_grid(1200, 800, 10.0, 14.0);
let large = cell_grid(1200, 800, 28.0, 40.0);
assert!(large.cols < small.cols, "{large:?} vs {small:?}");
assert!(large.rows < small.rows, "{large:?} vs {small:?}");
}
fn splash() -> Splash {
Splash {
art: vec!["ESCRIBA".into()],
tagline: "a modal editor".into(),
entries: vec![SplashEntry {
key: 'q',
label: "quit".into(),
action: Action::Quit,
}],
facts: vec!["v0".into()],
}
}
fn runs(theme: FleetTheme) -> Vec<(String, glyphon::Color)> {
let chunks = splash().screen_chunks(80, 24);
splash_runs(&chunks, &ChromePalette::for_theme(theme))
.into_iter()
.map(|(t, c)| (t.to_string(), c))
.collect()
}
fn glyph(c: ishou_tokens::Rgb) -> glyphon::Color {
glyphon::Color::rgba(c.r, c.g, c.b, 0xFF)
}
#[test]
fn every_visible_role_resolves_to_the_palettes_own_colour() {
for theme in [FleetTheme::PlemeDark, FleetTheme::Vellum] {
let c = ChromePalette::for_theme(theme);
assert_eq!(SplashRole::Art.color(&c).hex(), c.info.hex(), "{theme:?}");
assert_eq!(SplashRole::MenuKey.color(&c).hex(), c.accent.hex());
assert_eq!(SplashRole::MenuLabel.color(&c).hex(), c.text.hex());
assert_eq!(SplashRole::Footer.color(&c).hex(), c.text_dim.hex());
}
}
#[test]
fn the_run_stream_reconstructs_the_screen_exactly() {
let stream = runs(FleetTheme::PlemeDark);
let joined: String = stream.iter().map(|(t, _)| t.as_str()).collect();
let expected: String = splash()
.screen_chunks(80, 24)
.iter()
.map(|c| c.text.as_str())
.collect();
assert_eq!(joined, expected);
assert!(
stream.iter().all(|(t, _)| !t.is_empty()),
"an empty run is a wasted span",
);
}
#[test]
fn the_wordmark_is_painted_in_the_themes_art_colour() {
for theme in [FleetTheme::PlemeDark, FleetTheme::Vellum] {
let c = ChromePalette::for_theme(theme);
let stream = runs(theme);
let (_, color) = stream
.iter()
.find(|(t, _)| t.contains("ESCRIBA"))
.expect("the wordmark is in the stream");
assert_eq!(
*color,
glyph(c.info),
"{theme:?} painted the wrong art colour"
);
}
}
#[test]
fn the_run_stream_repaints_when_the_theme_changes() {
let nord: Vec<glyphon::Color> = runs(FleetTheme::PlemeDark)
.into_iter()
.map(|(_, c)| c)
.collect();
let vellum: Vec<glyphon::Color> = runs(FleetTheme::Vellum)
.into_iter()
.map(|(_, c)| c)
.collect();
assert_eq!(nord.len(), vellum.len(), "same screen, same run count");
assert_ne!(nord, vellum, "the GPU face ignored the theme");
}
#[test]
fn mode_pills_follow_the_active_theme() {
for theme in [FleetTheme::PlemeDark, FleetTheme::Vellum] {
let c = ChromePalette::for_theme(theme);
assert_eq!(
mode_color(&c, Mode::Normal).hex(),
c.info.hex(),
"{theme:?}"
);
assert_eq!(mode_color(&c, Mode::Insert).hex(), c.success.hex());
assert_eq!(mode_color(&c, Mode::Visual).hex(), c.accent.hex());
assert_eq!(mode_color(&c, Mode::Command).hex(), c.warning.hex());
}
}
#[test]
fn the_four_pills_stay_distinguishable_under_every_theme() {
for theme in [
FleetTheme::PlemeDark,
FleetTheme::Vellum,
FleetTheme::PolarVeil,
FleetTheme::Bare,
] {
let c = ChromePalette::for_theme(theme);
let mut seen = std::collections::BTreeSet::new();
for m in [Mode::Normal, Mode::Insert, Mode::Visual, Mode::Command] {
assert!(
seen.insert(mode_color(&c, m).hex()),
"{theme:?}: {m:?} duplicates another pill — the mode stops \
being glance-readable",
);
}
}
}
mod gutter_geometry {
use escriba_render::gpu::{cell_grid, gutter_px};
use escriba_ui::gutter::gutter_width;
#[test]
fn the_pixel_offset_is_exactly_the_declared_columns() {
for font_size in [10.0_f32, 14.0, 18.0, 24.0] {
for line_count in [12_u32, 9_999, 10_000, 250_000] {
let grid = cell_grid(1920, 1080, font_size, font_size * 1.4);
let cell_w = 1920.0 / f32::from(grid.cols);
let cols = gutter_width(line_count);
let want = cell_w * cols as f32;
let got = gutter_px(font_size, line_count);
assert!(
(got - want).abs() <= cell_w,
"font {font_size}, {line_count} lines: gutter_px {got} is \
more than one cell off the {cols}-column width {want}",
);
}
}
}
#[test]
fn a_bigger_file_reserves_more_pixels() {
let small = gutter_px(14.0, 9_999);
let big = gutter_px(14.0, 10_000);
assert!(
big > small,
"crossing into five digits must widen the gutter: {small} -> {big}",
);
}
#[test]
fn the_gutter_never_swallows_the_whole_window() {
let grid = cell_grid(120, 400, 14.0, 20.0);
assert!(
u32::from(grid.cols) > 0,
"a window always has at least one column",
);
let text_cols = u32::from(grid.cols).saturating_sub(gutter_width(10) as u32);
assert!(
text_cols.max(1) >= 1,
"text columns must never reach zero — the file would vanish",
);
}
}
mod syntax_follows_the_theme {
use escriba_ui::chrome::{ChromePalette, FleetTheme};
use escriba_ui::syntax::{ALL_CLASSES, ChromeSyntax};
use hikari_core::Theme;
fn rgb(c: hikari_core::Rgb) -> (u8, u8, u8) {
(c.r, c.g, c.b)
}
#[test]
fn the_renderers_syntax_theme_is_built_from_the_chrome_it_paints() {
for theme in [
FleetTheme::PlemeDark,
FleetTheme::Vellum,
FleetTheme::PolarVeil,
FleetTheme::Bare,
] {
let chrome = ChromePalette::for_theme(theme);
let syn = ChromeSyntax::new(chrome);
assert_eq!(
syn.chrome().hex_tuple(),
chrome.hex_tuple(),
"{theme:?}: the syntax theme must resolve the chrome it was given",
);
assert_eq!(
rgb(syn.color(hikari_core::HlClass::Keyword)),
(chrome.link.r, chrome.link.g, chrome.link.b),
);
}
}
#[test]
fn switching_theme_moves_the_code_colours() {
let a = ChromeSyntax::for_theme(FleetTheme::PlemeDark);
let b = ChromeSyntax::for_theme(FleetTheme::Vellum);
assert!(
ALL_CLASSES
.iter()
.any(|c| rgb(a.color(*c)) != rgb(b.color(*c))),
"a theme switch that leaves every syntax colour identical is the \
bug this replaced",
);
}
}