#![allow(unused_must_use)]
use slt::{context::ModalOptions, Color, EventBuilder, KeyCode, Spacing, TestBackend, Theme};
#[test]
fn theme_override_applies_inside_subtree() {
let mut tb = TestBackend::new(40, 6);
let dark = Theme::dark();
let light = Theme::light();
tb.render(|ui| {
assert!(ui.theme().is_dark);
let outer_primary = ui.theme().primary;
assert_eq!(outer_primary, dark.primary);
let _ = ui.container().theme(light).col(|ui| {
assert!(!ui.theme().is_dark, "theme override should swap is_dark");
assert_eq!(ui.theme().primary, light.primary);
});
assert!(ui.theme().is_dark);
assert_eq!(ui.theme().primary, dark.primary);
});
}
#[test]
fn theme_override_nests_correctly() {
let mut tb = TestBackend::new(40, 6);
let dark = Theme::dark();
let light = Theme::light();
let dracula = Theme::dracula();
tb.render(|ui| {
let _ = ui.container().theme(light).col(|ui| {
assert_eq!(ui.theme().primary, light.primary);
let _ = ui.container().theme(dracula).col(|ui| {
assert_eq!(ui.theme().primary, dracula.primary);
assert_eq!(ui.theme().bg, dracula.bg);
});
assert_eq!(ui.theme().primary, light.primary);
});
assert_eq!(ui.theme().primary, dark.primary);
});
}
#[test]
fn theme_override_restored_on_panic() {
let mut tb = TestBackend::new(40, 4);
let dark = Theme::dark();
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
tb.render(|ui| {
let _ = ui.container().theme(Theme::light()).col(|_| {
panic!("widget panic inside theme override");
});
assert!(ui.theme().is_dark);
});
}));
assert!(result.is_err(), "closure should propagate panic");
tb.render(|ui| {
assert_eq!(ui.theme().primary, dark.primary);
assert!(ui.theme().is_dark);
});
}
#[test]
fn theme_override_updates_dark_mode_flag() {
let mut tb = TestBackend::new(40, 4);
tb.render(|ui| {
assert!(ui.is_dark_mode(), "default theme is dark");
let _ = ui.container().theme(Theme::light()).col(|ui| {
assert!(
!ui.is_dark_mode(),
"light theme override should flip dark_mode flag inside subtree"
);
});
assert!(ui.is_dark_mode(), "dark mode restored after override");
});
}
#[test]
fn modal_options_default_is_trap_on() {
let opts = ModalOptions::default();
assert!(
opts.tab_trap,
"ModalOptions::default() must enable tab_trap"
);
}
#[test]
fn modal_with_tab_trap_clamps_focus_into_modal_range() {
let mut tb = TestBackend::new(60, 12);
tb.render(|ui| {
ui.modal_with(ModalOptions { tab_trap: true }, |ui| {
ui.button("Yes");
ui.button("No");
});
});
let final_idx = std::cell::Cell::new(usize::MAX);
tb.render_with_events(Vec::new(), 99, 2, |ui| {
ui.modal_with(ModalOptions { tab_trap: true }, |ui| {
ui.button("Yes");
ui.button("No");
});
final_idx.set(ui.focus_index());
});
let after = final_idx.get();
assert!(
after < 2,
"tab_trap must clamp focus_index into [0, 2); got {after}"
);
}
#[test]
fn modal_with_tab_trap_off_allows_focus_outside() {
let mut tb = TestBackend::new(60, 12);
tb.render(|ui| {
ui.modal_with(ModalOptions { tab_trap: false }, |ui| {
ui.button("OK");
});
});
let final_idx = std::cell::Cell::new(usize::MAX);
tb.render_with_events(Vec::new(), 99, 1, |ui| {
ui.modal_with(ModalOptions { tab_trap: false }, |ui| {
ui.button("OK");
});
final_idx.set(ui.focus_index());
});
assert_eq!(
final_idx.get(),
99,
"tab_trap=false must leave focus_index untouched"
);
}
#[test]
fn modal_legacy_method_preserves_legacy_behavior() {
let mut tb = TestBackend::new(60, 8);
tb.render(|ui| {
ui.modal(|ui| {
ui.button("OK");
});
});
let final_idx = std::cell::Cell::new(usize::MAX);
tb.render_with_events(Vec::new(), 99, 1, |ui| {
ui.modal(|ui| {
ui.button("OK");
});
final_idx.set(ui.focus_index());
});
assert_eq!(
final_idx.get(),
99,
"legacy modal() must not introduce a tab trap"
);
}
#[test]
fn modal_with_tab_trap_tab_cycles_inside_modal() {
let mut tb = TestBackend::new(80, 12);
tb.render(|ui| {
ui.modal_with(ModalOptions { tab_trap: true }, |ui| {
ui.button("Yes");
ui.button("No");
});
});
let events = EventBuilder::new().key_code(KeyCode::Tab).build();
let final_idx = std::cell::Cell::new(usize::MAX);
tb.render_with_events(events, 0, 2, |ui| {
ui.modal_with(ModalOptions { tab_trap: true }, |ui| {
ui.button("Yes");
ui.button("No");
});
final_idx.set(ui.focus_index());
});
let after = final_idx.get();
assert!(
after < 2,
"Tab cycle inside trapped modal must stay in [0, 2); got {after}"
);
}
#[test]
fn theme_compact_has_base_one() {
let t = Theme::compact();
assert_eq!(t.spacing.xs(), 1);
assert_eq!(t.spacing.sm(), 2);
assert_eq!(t.spacing.md(), 3);
}
#[test]
fn theme_comfortable_has_base_two() {
let t = Theme::comfortable();
assert_eq!(t.spacing.xs(), 2);
assert_eq!(t.spacing.sm(), 4);
assert_eq!(t.spacing.md(), 6);
}
#[test]
fn theme_spacious_has_base_three() {
let t = Theme::spacious();
assert_eq!(t.spacing.xs(), 3);
assert_eq!(t.spacing.sm(), 6);
assert_eq!(t.spacing.md(), 9);
}
#[test]
fn theme_with_spacing_preserves_colors() {
let nord = Theme::nord();
let dense_nord = Theme::nord().with_spacing(Spacing::new(2));
assert_eq!(dense_nord.bg, nord.bg);
assert_eq!(dense_nord.primary, nord.primary);
assert_eq!(dense_nord.text, nord.text);
assert_eq!(dense_nord.spacing.xs(), 2);
}
#[test]
fn spacing_change_widens_code_block_padding() {
let code = "let x = 1;";
let mut tb_c = TestBackend::new(40, 8);
tb_c.render(|ui| {
ui.set_theme(Theme::compact());
ui.code_block(code);
});
let compact_dump: Vec<String> = (0..tb_c.height()).map(|y| tb_c.line(y)).collect();
let compact_first_content_row = (0..tb_c.height())
.find(|y| tb_c.line(*y).contains("let"))
.unwrap_or_else(|| {
panic!(
"compact theme should render code; buffer:\n{}",
compact_dump.join("\n")
)
});
let mut tb_cz = TestBackend::new(40, 10);
tb_cz.render(|ui| {
ui.set_theme(Theme::comfortable());
ui.code_block(code);
});
let comfortable_dump: Vec<String> = (0..tb_cz.height()).map(|y| tb_cz.line(y)).collect();
let comfortable_first_content_row = (0..tb_cz.height())
.find(|y| tb_cz.line(*y).contains("let"))
.unwrap_or_else(|| {
panic!(
"comfortable theme should render code; buffer:\n{}",
comfortable_dump.join("\n")
)
});
assert!(
comfortable_first_content_row > compact_first_content_row,
"comfortable spacing should push code further down (compact={compact_first_content_row}, comfortable={comfortable_first_content_row})"
);
}
#[test]
fn spacing_change_visible_via_subtree_theme() {
let mut tb = TestBackend::new(80, 10);
let mut compact_row = u32::MAX;
let mut comfortable_row = u32::MAX;
tb.render(|ui| {
let _ = ui.container().theme(Theme::compact()).col(|ui| {
ui.code_block("a");
});
let _ = ui.container().theme(Theme::comfortable()).col(|ui| {
ui.code_block("b");
});
});
let mut found_a = false;
let mut found_b = false;
for y in 0..tb.height() {
let line = tb.line(y);
if line.contains("a") {
found_a = true;
if compact_row == u32::MAX {
compact_row = y;
}
}
if line.contains("b") {
found_b = true;
if comfortable_row == u32::MAX {
comfortable_row = y;
}
}
}
let _ = (compact_row, comfortable_row);
assert!(found_a, "compact subtree must render its content");
assert!(found_b, "comfortable subtree must render its content");
}
#[test]
fn theme_override_changes_widget_color() {
let mut tb = TestBackend::new(20, 3);
let exotic = Theme::builder()
.primary(Color::Rgb(255, 0, 255))
.text(Color::Rgb(255, 255, 0))
.accent(Color::Rgb(255, 0, 255))
.build();
tb.render(|ui| {
let _ = ui.container().theme(exotic).col(|ui| {
ui.button("Hi");
});
});
let mut fg_colors = std::collections::HashSet::new();
for y in 0..tb.height() {
for x in 0..tb.width() {
let cell = tb.buffer().get(x, y);
if let Some(c) = cell.style.fg {
fg_colors.insert(c);
}
}
}
assert!(
fg_colors.contains(&Color::Rgb(255, 0, 255))
|| fg_colors.contains(&Color::Rgb(255, 255, 0)),
"button rendered inside `theme(exotic).col(...)` must use exotic colors; observed fg: {fg_colors:?}"
);
}