use scrin::animation::{Animation, PlayState, Timeline};
use scrin::command_palette::{Command, CommandPalette, PaletteState};
use scrin::core::buffer::{Buffer, Cell};
use scrin::core::color::Color;
use scrin::core::rect::Rect;
use scrin::effects::easing::EasingFn;
use scrin::effects::{EffectPlayer, LoaderPlayer};
use scrin::input::{AppAction, EventRouter};
use scrin::layout::{Constraint, Layout};
use scrin::overlays::modal::Modal;
use scrin::overlays::toast::{Toast, ToastKind};
use scrin::overlays::Overlay;
use scrin::status_bar::StatusBar;
use scrin::status_bar::StatusBarPosition;
use scrin::style::Style;
use scrin::theme::Theme;
use scrin::widgets::{CodeBlock, MarkdownOutput, Widget};
use std::time::Duration;
#[test]
fn buffer_new_creates_correct_dimensions() {
let buf = Buffer::new(20, 10);
assert_eq!(buf.width(), 20);
assert_eq!(buf.height(), 10);
}
#[test]
fn buffer_set_and_get() {
let mut buf = Buffer::new(10, 10);
let cell = Cell::new('X', Color::RED, Some(Color::BLUE));
buf.set(5, 3, cell);
let got = buf.get(5, 3).unwrap();
assert_eq!(got.ch, 'X');
assert_eq!(got.fg, Color::RED);
assert_eq!(got.bg, Some(Color::BLUE));
}
#[test]
fn buffer_get_out_of_bounds_returns_none() {
let buf = Buffer::new(5, 5);
assert!(buf.get(5, 0).is_none());
assert!(buf.get(0, 5).is_none());
assert!(buf.get(100, 100).is_none());
}
#[test]
fn buffer_fill() {
let mut buf = Buffer::new(10, 10);
let area = Rect::new(2, 2, 4, 3);
buf.fill(area, 'F', Color::GREEN, Some(Color::BLACK));
assert_eq!(buf.get(2, 2).unwrap().ch, 'F');
assert_eq!(buf.get(5, 4).unwrap().ch, 'F');
assert_eq!(buf.get(1, 1).unwrap().ch, ' ');
assert_eq!(buf.get(6, 2).unwrap().ch, ' ');
}
#[test]
fn buffer_to_plain_string() {
let mut buf = Buffer::new(3, 2);
buf.set_str(0, 0, "Hi", Color::WHITE, None);
buf.set(2, 0, Cell::new('!', Color::WHITE, None));
let s = buf.to_plain_string();
assert_eq!(s, "Hi!\n ");
}
#[test]
fn buffer_merge_from() {
let mut target = Buffer::new(10, 10);
let mut source = Buffer::new(3, 3);
source.set_str(0, 0, "ABC", Color::WHITE, None);
source.set(0, 1, Cell::new('D', Color::RED, None));
target.merge_from(&source, 2, 2);
assert_eq!(target.get(2, 2).unwrap().ch, 'A');
assert_eq!(target.get(4, 2).unwrap().ch, 'C');
assert_eq!(target.get(2, 3).unwrap().ch, 'D');
}
#[test]
fn buffer_resize_preserves_data() {
let mut buf = Buffer::new(10, 10);
buf.set_str(0, 0, "Hello", Color::WHITE, None);
buf.resize(20, 20);
assert_eq!(buf.width(), 20);
assert_eq!(buf.height(), 20);
assert_eq!(buf.get(0, 0).unwrap().ch, 'H');
assert_eq!(buf.get(4, 0).unwrap().ch, 'o');
}
#[test]
fn buffer_resize_shrinks() {
let mut buf = Buffer::new(10, 10);
buf.set_str(0, 0, "Hello World!!!", Color::WHITE, None);
buf.resize(5, 5);
assert_eq!(buf.width(), 5);
assert_eq!(buf.height(), 5);
assert_eq!(buf.get(0, 0).unwrap().ch, 'H');
assert_eq!(buf.get(4, 0).unwrap().ch, 'o');
assert!(buf.get(5, 0).is_none());
}
#[test]
fn buffer_with_background() {
let buf = Buffer::with_background(5, 5, Some(Color::RED));
for y in 0..5 {
for x in 0..5 {
let cell = buf.get(x, y).unwrap();
assert_eq!(cell.bg, Some(Color::RED));
}
}
}
#[test]
fn buffer_blend_cell_non_space_overwrites() {
let mut buf = Buffer::new(5, 5);
buf.set(0, 0, Cell::new('A', Color::WHITE, None));
buf.blend_cell(0, 0, Cell::new('B', Color::RED, None));
assert_eq!(buf.get(0, 0).unwrap().ch, 'B');
}
#[test]
fn buffer_blend_cell_space_preserves_char() {
let mut buf = Buffer::new(5, 5);
buf.set(0, 0, Cell::new('A', Color::WHITE, None));
buf.blend_cell(0, 0, Cell::new(' ', Color::WHITE, Some(Color::RED)));
assert_eq!(buf.get(0, 0).unwrap().ch, 'A');
assert_eq!(buf.get(0, 0).unwrap().bg, Some(Color::RED));
}
#[test]
fn rect_area() {
let r = Rect::new(0, 0, 10, 5);
assert_eq!(r.area(), 50);
}
#[test]
fn rect_inner() {
let r = Rect::new(0, 0, 20, 10);
let inner = r.inner(Rect::new(2, 1, 2, 1));
assert_eq!(inner, Rect::new(2, 1, 16, 8));
}
#[test]
fn rect_inner_saturates() {
let r = Rect::new(5, 5, 3, 3);
let inner = r.inner(Rect::new(10, 10, 10, 10));
assert_eq!(inner.width, 0);
assert_eq!(inner.height, 0);
}
#[test]
fn rect_intersects_overlapping() {
let a = Rect::new(0, 0, 10, 10);
let b = Rect::new(5, 5, 10, 10);
assert!(a.intersects(&b));
assert!(b.intersects(&a));
}
#[test]
fn rect_intersects_not_overlapping() {
let a = Rect::new(0, 0, 5, 5);
let b = Rect::new(10, 10, 5, 5);
assert!(!a.intersects(&b));
assert!(!b.intersects(&a));
}
#[test]
fn rect_intersects_adjacent_no_overlap() {
let a = Rect::new(0, 0, 5, 5);
let b = Rect::new(5, 0, 5, 5);
assert!(!a.intersects(&b));
}
#[test]
fn rect_intersection() {
let a = Rect::new(0, 0, 10, 10);
let b = Rect::new(5, 5, 10, 10);
assert_eq!(a.intersection(&b), Rect::new(5, 5, 5, 5));
}
#[test]
fn rect_intersection_no_overlap() {
let a = Rect::new(0, 0, 5, 5);
let b = Rect::new(10, 10, 5, 5);
let ix = a.intersection(&b);
assert_eq!(ix.width, 0);
assert_eq!(ix.height, 0);
}
#[test]
fn rect_offset() {
let r = Rect::new(5, 5, 10, 10);
let o = r.offset(3, 7);
assert_eq!(o, Rect::new(8, 12, 10, 10));
}
#[test]
fn rect_offset_saturates() {
let r = Rect::new(5, 5, 10, 10);
let o = r.offset(u16::MAX, u16::MAX);
assert_eq!(o.x, u16::MAX);
assert_eq!(o.y, u16::MAX);
}
#[test]
fn rect_default() {
let r = Rect::default();
assert_eq!(r, Rect::ZERO);
}
#[test]
fn color_lerp_midpoint() {
let c1 = Color::BLACK;
let c2 = Color::WHITE;
let mid = c1.lerp(&c2, 0.5);
assert_eq!(mid.r, 127);
assert_eq!(mid.g, 127);
assert_eq!(mid.b, 127);
}
#[test]
fn color_lerp_clamps() {
let c1 = Color::BLACK;
let c2 = Color::WHITE;
let over = c1.lerp(&c2, 2.0);
assert_eq!(over, Color::WHITE);
let under = c1.lerp(&c2, -1.0);
assert_eq!(under, Color::BLACK);
}
#[test]
fn color_brighten() {
let c = Color::rgb(50, 100, 150);
let b = c.brighten(0.5);
assert!(b.r > c.r);
assert!(b.g > c.g);
assert!(b.b > c.b);
}
#[test]
fn color_brighten_full() {
let c = Color::rgb(50, 100, 150);
let b = c.brighten(1.0);
assert_eq!(b, Color::WHITE);
}
#[test]
fn color_dim() {
let c = Color::rgb(200, 200, 200);
let d = c.dim(0.5);
assert!(d.r < c.r);
assert!(d.g < c.g);
assert!(d.b < c.b);
}
#[test]
fn color_dim_full() {
let c = Color::rgb(200, 100, 50);
let d = c.dim(1.0);
assert_eq!(d, Color::BLACK);
}
#[test]
fn color_from_hsl_red() {
let c = Color::from_hsl(0.0, 1.0, 0.5);
assert_eq!(c.r, 255);
assert_eq!(c.g, 0);
assert_eq!(c.b, 0);
}
#[test]
fn color_from_hsl_green() {
let c = Color::from_hsl(120.0, 1.0, 0.5);
assert_eq!(c.g, 255);
}
#[test]
fn color_from_hsl_blue() {
let c = Color::from_hsl(240.0, 1.0, 0.5);
assert_eq!(c.b, 255);
}
#[test]
fn color_distance_same() {
let c = Color::rgb(100, 100, 100);
assert_eq!(c.distance(&c), 0.0);
}
#[test]
fn color_distance_different() {
let c1 = Color::BLACK;
let c2 = Color::WHITE;
let d = c1.distance(&c2);
assert!((d - 441.673).abs() < 1.0);
}
#[test]
fn color_index() {
assert_eq!(Color::index(0), Color::BLACK);
assert_eq!(Color::index(1), Color::RED);
assert_eq!(Color::index(7), Color::WHITE);
}
#[test]
fn color_to_ansi_fg() {
let c = Color::rgb(100, 200, 50);
let s = c.to_ansi_fg();
assert!(s.starts_with("\x1b[38;2;"));
assert!(s.contains("100"));
assert!(s.contains("200"));
assert!(s.contains("50"));
}
#[test]
fn color_to_ansi_bg() {
let c = Color::rgb(10, 20, 30);
let s = c.to_ansi_bg();
assert!(s.starts_with("\x1b[48;2;"));
}
#[test]
fn layout_vertical_split() {
let layout = Layout::vertical(vec![
Constraint::Length(5),
Constraint::Length(3),
Constraint::Min(0),
]);
let area = Rect::new(0, 0, 40, 20);
let rects = layout.split(area);
assert_eq!(rects.len(), 3);
assert_eq!(rects[0].height, 5);
assert_eq!(rects[1].height, 3);
assert_eq!(rects[2].y, 8);
}
#[test]
fn layout_horizontal_split() {
let layout = Layout::horizontal(vec![Constraint::Percentage(50), Constraint::Percentage(50)]);
let area = Rect::new(0, 0, 100, 20);
let rects = layout.split(area);
assert_eq!(rects.len(), 2);
assert_eq!(rects[0].width, 50);
assert_eq!(rects[1].width, 50);
assert_eq!(rects[0].height, 20);
}
#[test]
fn layout_with_margin() {
let layout = Layout::vertical(vec![Constraint::Length(5)]).margin(Rect::new(1, 1, 1, 1));
let area = Rect::new(0, 0, 20, 10);
let rects = layout.split(area);
assert_eq!(rects.len(), 1);
assert_eq!(rects[0].y, 1);
assert_eq!(rects[0].x, 1);
assert_eq!(rects[0].height, 5);
}
#[test]
fn layout_empty_constraints() {
let layout = Layout::vertical(vec![]);
let area = Rect::new(0, 0, 20, 10);
let rects = layout.split(area);
assert_eq!(rects.len(), 0);
}
#[test]
fn animation_new_state_playing() {
let anim = Animation::new(0.0, 100.0, Duration::from_secs(1));
assert_eq!(anim.state, PlayState::Playing);
assert_eq!(anim.current, 0.0);
}
#[test]
fn animation_update_progresses() {
let mut anim = Animation::new(0.0, 100.0, Duration::from_secs(1));
anim.update(Duration::from_millis(500));
assert!(anim.current > 0.0);
assert!(anim.current < 100.0);
}
#[test]
fn animation_completes() {
let mut anim = Animation::new(0.0, 100.0, Duration::from_secs(1));
anim.update(Duration::from_secs(1));
assert_eq!(anim.state, PlayState::Completed);
assert_eq!(anim.current, 100.0);
}
#[test]
fn animation_pause_and_resume() {
let mut anim = Animation::new(0.0, 100.0, Duration::from_secs(1));
anim.pause();
assert_eq!(anim.state, PlayState::Paused);
anim.update(Duration::from_millis(500));
assert_eq!(anim.current, 0.0);
anim.resume();
assert_eq!(anim.state, PlayState::Playing);
anim.update(Duration::from_millis(500));
assert!(anim.current > 0.0);
}
#[test]
fn animation_restart() {
let mut anim = Animation::new(0.0, 100.0, Duration::from_secs(1));
anim.update(Duration::from_secs(1));
assert_eq!(anim.state, PlayState::Completed);
anim.restart();
assert_eq!(anim.state, PlayState::Playing);
assert_eq!(anim.current, 0.0);
}
#[test]
fn animation_with_loop() {
let mut anim = Animation::new(0.0, 100.0, Duration::from_secs(1)).with_loop();
anim.update(Duration::from_secs(1));
assert_eq!(anim.state, PlayState::Playing);
assert!(anim.progress() < 1.0);
}
#[test]
fn animation_with_easing() {
let mut anim = Animation::new(0.0, 100.0, Duration::from_secs(1)).with_easing(EasingFn::InQuad);
anim.update(Duration::from_millis(500));
assert!(anim.current > 0.0);
}
#[test]
fn animation_progress_zero_duration() {
let mut anim = Animation::new(0.0, 100.0, Duration::ZERO);
anim.update(Duration::from_millis(100));
assert_eq!(anim.progress(), 1.0);
}
#[test]
fn timeline_new_empty() {
let tl = Timeline::new();
assert!(tl.items.is_empty());
assert_eq!(tl.current_time, Duration::ZERO);
assert!(!tl.is_playing);
}
#[test]
fn timeline_add_and_get_value() {
let mut tl = Timeline::new();
let anim = Animation::new(0.0, 1.0, Duration::from_secs(1));
tl.add(Duration::ZERO, anim, Some("alpha".into()));
assert_eq!(tl.items.len(), 1);
assert_eq!(tl.duration, Duration::from_secs(1));
}
#[test]
fn timeline_play_advances_time() {
let mut tl = Timeline::new();
let anim = Animation::new(0.0, 10.0, Duration::from_secs(1));
tl.add(Duration::ZERO, anim, None);
tl.play();
assert!(tl.is_playing);
tl.update(Duration::from_millis(500));
let val = tl.get_value(0).unwrap();
assert!(val > 0.0);
assert!(val < 10.0);
}
#[test]
fn timeline_play_completes() {
let mut tl = Timeline::new();
let anim = Animation::new(0.0, 10.0, Duration::from_secs(1));
tl.add(Duration::ZERO, anim, None);
tl.play();
tl.update(Duration::from_secs(2));
assert!(!tl.is_playing);
assert!(tl.is_complete());
}
#[test]
fn timeline_get_label_value() {
let mut tl = Timeline::new();
let anim = Animation::new(0.0, 5.0, Duration::from_secs(1));
tl.add(Duration::ZERO, anim, Some("move".into()));
tl.play();
tl.update(Duration::from_millis(500));
let val = tl.get_label_value("move").unwrap();
assert!(val > 0.0 && val < 5.0);
assert!(tl.get_label_value("nonexistent").is_none());
}
#[test]
fn timeline_loop() {
let mut tl = Timeline::new().with_loop();
let anim = Animation::new(0.0, 10.0, Duration::from_secs(1));
tl.add(Duration::ZERO, anim, None);
tl.play();
tl.update(Duration::from_secs(2));
assert!(tl.is_playing);
assert_eq!(tl.current_time, Duration::ZERO);
}
#[test]
fn timeline_pause() {
let mut tl = Timeline::new();
let anim = Animation::new(0.0, 10.0, Duration::from_secs(1));
tl.add(Duration::ZERO, anim, None);
tl.play();
tl.update(Duration::from_millis(500));
tl.pause();
assert!(!tl.is_playing);
let val_before = tl.get_value(0).unwrap();
tl.update(Duration::from_millis(500));
let val_after = tl.get_value(0).unwrap();
assert_eq!(val_before, val_after);
}
#[test]
fn palette_new_is_closed() {
let palette = CommandPalette::new();
assert_eq!(palette.state, PaletteState::Closed);
assert!(!palette.is_open());
}
#[test]
fn palette_open() {
let mut palette = CommandPalette::new();
palette.open();
assert_eq!(palette.state, PaletteState::Open);
assert!(palette.is_open());
}
#[test]
fn palette_close() {
let mut palette = CommandPalette::new();
palette.open();
palette.close();
assert_eq!(palette.state, PaletteState::Closed);
assert!(!palette.is_open());
}
#[test]
fn palette_filter() {
let mut palette = CommandPalette::new().with_commands(vec![
Command {
name: "New File".into(),
shortcut: None,
description: "Create new".into(),
action_id: "new".into(),
},
Command {
name: "Open File".into(),
shortcut: None,
description: "Open existing".into(),
action_id: "open".into(),
},
Command {
name: "Save".into(),
shortcut: None,
description: "Save changes".into(),
action_id: "save".into(),
},
]);
palette.open();
assert_eq!(palette.filtered.len(), 3);
palette.input_char('z');
assert!(palette.filtered.is_empty());
}
#[test]
fn palette_select_next_prev() {
let mut palette = CommandPalette::new().with_commands(vec![
Command {
name: "A".into(),
shortcut: None,
description: "a".into(),
action_id: "a".into(),
},
Command {
name: "B".into(),
shortcut: None,
description: "b".into(),
action_id: "b".into(),
},
]);
palette.open();
assert_eq!(palette.selected_index, 0);
palette.select_next();
assert_eq!(palette.selected_index, 1);
palette.select_next();
assert_eq!(palette.selected_index, 0);
palette.select_prev();
assert_eq!(palette.selected_index, 1);
}
#[test]
fn palette_execute_selected() {
let mut palette = CommandPalette::new().with_commands(vec![Command {
name: "Quit".into(),
shortcut: None,
description: "Exit".into(),
action_id: "quit".into(),
}]);
palette.open();
assert_eq!(palette.execute_selected(), Some("quit"));
}
#[test]
fn palette_backspace() {
let mut palette = CommandPalette::new().with_commands(vec![Command {
name: "Test".into(),
shortcut: None,
description: "test".into(),
action_id: "test".into(),
}]);
palette.open();
palette.input_char('x');
assert_eq!(palette.query, "x");
palette.backspace();
assert_eq!(palette.query, "");
}
#[test]
fn palette_input_when_closed_does_nothing() {
let mut palette = CommandPalette::new();
palette.input_char('x');
assert_eq!(palette.query, "");
}
#[test]
fn palette_add_command() {
let mut palette = CommandPalette::new();
palette.add_command(Command {
name: "Test".into(),
shortcut: None,
description: "test".into(),
action_id: "test".into(),
});
assert_eq!(palette.commands.len(), 1);
}
#[test]
fn router_has_default_bindings() {
let router = EventRouter::new();
assert!(!router.bindings().is_empty());
}
#[test]
fn router_match_ctrl_c() {
let mut router = EventRouter::new();
let event = crossterm::event::Event::Key(crossterm::event::KeyEvent::new(
crossterm::event::KeyCode::Char('c'),
crossterm::event::KeyModifiers::CONTROL,
));
let action = router.handle_event(&event);
assert_eq!(action, Some(AppAction::Quit));
}
#[test]
fn router_match_ctrl_q() {
let mut router = EventRouter::new();
let event = crossterm::event::Event::Key(crossterm::event::KeyEvent::new(
crossterm::event::KeyCode::Char('q'),
crossterm::event::KeyModifiers::CONTROL,
));
let action = router.handle_event(&event);
assert_eq!(action, Some(AppAction::Quit));
}
#[test]
fn router_match_tab() {
let mut router = EventRouter::new();
let event = crossterm::event::Event::Key(crossterm::event::KeyEvent::new(
crossterm::event::KeyCode::Tab,
crossterm::event::KeyModifiers::NONE,
));
let action = router.handle_event(&event);
assert_eq!(action, Some(AppAction::TabNext));
}
#[test]
fn router_match_enter() {
let mut router = EventRouter::new();
let event = crossterm::event::Event::Key(crossterm::event::KeyEvent::new(
crossterm::event::KeyCode::Enter,
crossterm::event::KeyModifiers::NONE,
));
let action = router.handle_event(&event);
assert_eq!(action, Some(AppAction::Select));
}
#[test]
fn router_match_escape() {
let mut router = EventRouter::new();
let event = crossterm::event::Event::Key(crossterm::event::KeyEvent::new(
crossterm::event::KeyCode::Esc,
crossterm::event::KeyModifiers::NONE,
));
let action = router.handle_event(&event);
assert_eq!(action, Some(AppAction::Back));
}
#[test]
fn router_custom_binding() {
let mut router = EventRouter::new();
router.bind(
crossterm::event::KeyModifiers::NONE,
crossterm::event::KeyCode::Char('x'),
AppAction::Custom("test_action".into()),
"Test action",
);
let event = crossterm::event::Event::Key(crossterm::event::KeyEvent::new(
crossterm::event::KeyCode::Char('x'),
crossterm::event::KeyModifiers::NONE,
));
let action = router.handle_event(&event);
match action {
Some(AppAction::Custom(s)) => assert_eq!(s, "test_action"),
_ => panic!("Expected custom action"),
}
}
#[test]
fn router_unbound_key_returns_none() {
let mut router = EventRouter::new();
let event = crossterm::event::Event::Key(crossterm::event::KeyEvent::new(
crossterm::event::KeyCode::F(12),
crossterm::event::KeyModifiers::NONE,
));
assert!(router.handle_event(&event).is_none());
}
#[test]
fn router_resize_event() {
let mut router = EventRouter::new();
let event = crossterm::event::Event::Resize(80, 24);
assert_eq!(router.handle_event(&event), Some(AppAction::Resize));
}
#[test]
fn toast_new_is_visible() {
let toast = Toast::new("Hello", ToastKind::Info);
assert!(toast.is_visible());
assert!(!toast.is_expired());
}
#[test]
fn toast_hide() {
let mut toast = Toast::new("Hello", ToastKind::Info);
toast.hide();
assert!(!toast.is_visible());
}
#[test]
fn toast_show_resets() {
let mut toast = Toast::new("Hello", ToastKind::Info);
toast.hide();
assert!(!toast.is_visible());
toast.show();
assert!(toast.is_visible());
assert_eq!(toast.elapsed, Duration::ZERO);
}
#[test]
fn toast_expiry() {
let mut toast = Toast::new("Hello", ToastKind::Info).with_lifetime(Duration::from_millis(100));
toast.update(Duration::from_millis(50));
assert!(!toast.is_expired());
assert!(toast.is_visible());
toast.update(Duration::from_millis(60));
assert!(toast.is_expired());
}
#[test]
fn toast_kinds_icons() {
assert_eq!(ToastKind::Info.icon(), "ℹ");
assert_eq!(ToastKind::Success.icon(), "✓");
assert_eq!(ToastKind::Warning.icon(), "⚠");
assert_eq!(ToastKind::Error.icon(), "✗");
assert_eq!(ToastKind::Copy.icon(), "📋");
}
#[test]
fn toast_update_fade() {
let mut toast = Toast::new("Hello", ToastKind::Info).with_lifetime(Duration::from_secs(3));
toast.update(Duration::from_secs(3));
assert!(toast.opacity < 1.0);
}
#[test]
fn modal_new_is_hidden() {
let modal = Modal::new("Title", "Content");
assert!(!modal.is_visible());
}
#[test]
fn modal_show_hide() {
let mut modal = Modal::new("Title", "Content");
modal.show();
assert!(modal.is_visible());
modal.hide();
assert!(!modal.is_visible());
}
#[test]
fn modal_select_next_prev() {
let mut modal =
Modal::new("Title", "Content").with_options(vec!["A".into(), "B".into(), "C".into()]);
modal.show();
assert_eq!(modal.selected_index, 0);
modal.select_next();
assert_eq!(modal.selected_index, 1);
modal.select_next();
assert_eq!(modal.selected_index, 2);
modal.select_next();
assert_eq!(modal.selected_index, 0);
modal.select_prev();
assert_eq!(modal.selected_index, 2);
}
#[test]
fn modal_selected_option() {
let mut modal =
Modal::new("Title", "Content").with_options(vec!["First".into(), "Second".into()]);
modal.show();
assert_eq!(modal.selected_option(), Some("First"));
modal.select_next();
assert_eq!(modal.selected_option(), Some("Second"));
}
#[test]
fn modal_no_options() {
let mut modal = Modal::new("Title", "Content");
modal.show();
modal.select_next();
assert_eq!(modal.selected_index, 0);
modal.select_prev();
assert_eq!(modal.selected_index, 0);
assert!(modal.selected_option().is_none());
}
#[test]
fn status_bar_new_empty() {
let sb = StatusBar::new();
assert!(sb.left_sections.is_empty());
assert!(sb.center_sections.is_empty());
assert!(sb.right_sections.is_empty());
}
#[test]
fn status_bar_set_left() {
let mut sb = StatusBar::new();
sb.set_left("Hello", Color::WHITE);
assert_eq!(sb.left_sections.len(), 1);
assert_eq!(sb.left_sections[0].text, "Hello");
}
#[test]
fn status_bar_set_right() {
let mut sb = StatusBar::new();
sb.set_right("World", Color::RED);
assert_eq!(sb.right_sections.len(), 1);
assert_eq!(sb.right_sections[0].text, "World");
}
#[test]
fn status_bar_clear() {
let mut sb = StatusBar::new();
sb.set_left("A", Color::WHITE);
sb.set_center("B", Color::WHITE);
sb.set_right("C", Color::WHITE);
sb.clear();
assert!(sb.left_sections.is_empty());
assert!(sb.center_sections.is_empty());
assert!(sb.right_sections.is_empty());
}
#[test]
fn status_bar_push_left() {
let mut sb = StatusBar::new();
sb.push_left("A", Color::WHITE);
sb.push_left("B", Color::WHITE);
assert_eq!(sb.left_sections.len(), 2);
}
#[test]
fn status_bar_push_right() {
let mut sb = StatusBar::new();
sb.push_right("A", Color::WHITE);
sb.push_right("B", Color::WHITE);
assert_eq!(sb.right_sections.len(), 2);
}
#[test]
fn status_bar_set_left_replaces() {
let mut sb = StatusBar::new();
sb.set_left("A", Color::WHITE);
sb.set_left("B", Color::RED);
assert_eq!(sb.left_sections.len(), 1);
assert_eq!(sb.left_sections[0].text, "B");
}
#[test]
fn status_bar_position() {
let sb = StatusBar::new().with_position(StatusBarPosition::Top);
assert_eq!(sb.position, StatusBarPosition::Top);
}
#[test]
fn status_bar_render_does_not_panic() {
let mut sb = StatusBar::new();
sb.set_left("Test", Color::WHITE);
let mut buf = Buffer::new(80, 5);
let area = Rect::new(0, 0, 80, 5);
sb.render(&mut buf, area);
}
#[test]
fn status_bar_one_row_renders_text() {
let mut sb = StatusBar::new();
sb.set_left("Test", Color::WHITE);
let mut buf = Buffer::new(20, 1);
sb.render(&mut buf, Rect::new(0, 0, 20, 1));
assert_eq!(buf.get(2, 0).unwrap().ch, 'T');
}
#[test]
fn style_new_defaults() {
let s = Style::new();
assert_eq!(s.fg, None);
assert_eq!(s.bg, None);
assert!(!s.bold);
assert!(!s.italic);
assert!(!s.underlined);
assert!(!s.dim);
}
#[test]
fn style_builder() {
let s = Style::new().fg(Color::RED).bg(Color::BLUE).bold().italic();
assert_eq!(s.fg, Some(Color::RED));
assert_eq!(s.bg, Some(Color::BLUE));
assert!(s.bold);
assert!(s.italic);
}
#[test]
fn style_merge() {
let base = Style::new().fg(Color::RED);
let overlay = Style::new().bg(Color::BLUE).bold();
let merged = base.merge(&overlay);
assert_eq!(merged.fg, Some(Color::RED));
assert_eq!(merged.bg, Some(Color::BLUE));
assert!(merged.bold);
}
#[test]
fn style_from_color() {
let s = Style::from(Color::GREEN);
assert_eq!(s.fg, Some(Color::GREEN));
}
#[test]
fn theme_dark_constants() {
let t = Theme::DARK;
assert_eq!(t.bg, Color::rgb(13, 17, 23));
assert_eq!(t.fg, Color::rgb(201, 209, 217));
assert_eq!(t.accent, Color::rgb(88, 166, 255));
}
#[test]
fn theme_default_is_dark() {
assert_eq!(Theme::default(), Theme::DARK);
}
#[test]
fn theme_accent_for() {
let t = Theme::DARK;
assert_eq!(t.accent_for(0), t.accent);
assert_eq!(t.accent_for(1), t.success);
assert_eq!(t.accent_for(3), t.error);
}
#[test]
fn theme_all_themes_have_valid_colors() {
for theme in &[
Theme::DARK,
Theme::CYBERPUNK,
Theme::MONOKAI,
Theme::SOLARIZED,
] {
assert!(theme.bg != theme.fg);
assert!(theme.border != theme.bg);
}
}
#[test]
fn block_render_no_panic() {
let block = scrin::widgets::Block::new("Test");
let mut buf = Buffer::new(40, 10);
block.render(&mut buf, Rect::new(0, 0, 40, 10));
}
#[test]
fn paragraph_render_no_panic() {
let p = scrin::widgets::Paragraph::new("Hello\nWorld");
let mut buf = Buffer::new(40, 10);
p.render(&mut buf, Rect::new(0, 0, 40, 10));
}
#[test]
fn code_block_render_no_panic() {
let code = CodeBlock::new("fn main() {\n let value = 42;\n}").with_language("rust");
let mut buf = Buffer::new(48, 6);
code.render(&mut buf, Rect::new(0, 0, 48, 6));
assert!(buf.get(0, 0).unwrap().bg.is_some());
}
#[test]
fn markdown_output_render_no_panic() {
let md = MarkdownOutput::new("# Output\n- item\n```rust\nfn main() {}\n```")
.with_code_line_numbers(true);
let mut buf = Buffer::new(50, 8);
md.render(&mut buf, Rect::new(0, 0, 50, 8));
assert!(buf.get(0, 2).unwrap().bg.is_some());
}
#[test]
fn list_render_no_panic() {
let items = vec![
scrin::widgets::list::ListItem::new("A"),
scrin::widgets::list::ListItem::new("B"),
];
let list = scrin::widgets::List::new(&items);
let mut buf = Buffer::new(40, 10);
list.render(&mut buf, Rect::new(0, 0, 40, 10));
}
#[test]
fn gauge_render_no_panic() {
let g = scrin::widgets::Gauge::new()
.with_ratio(0.5)
.with_label("Test");
let mut buf = Buffer::new(40, 3);
g.render(&mut buf, Rect::new(0, 0, 40, 3));
}
#[test]
fn sparkline_render_no_panic() {
let sp = scrin::widgets::Sparkline::new()
.with_data(vec![1, 2, 3, 4, 5])
.with_max(5);
let mut buf = Buffer::new(40, 3);
sp.render(&mut buf, Rect::new(0, 0, 40, 3));
}
#[test]
fn tabs_render_no_panic() {
let tabs = scrin::widgets::Tabs::new(&["A", "B", "C"]);
let mut buf = Buffer::new(40, 1);
tabs.render(&mut buf, Rect::new(0, 0, 40, 1));
}
#[test]
fn table_render_no_panic() {
use scrin::widgets::table::{Cell as TCell, Row, Table};
let header = Row::new(vec![TCell::new("Name"), TCell::new("Value")]);
let rows = vec![Row::new(vec![TCell::new("A"), TCell::new("1")])];
let widths = &[10, 10];
let table = Table::new(&rows, widths).with_header(&header);
let mut buf = Buffer::new(40, 10);
table.render(&mut buf, Rect::new(0, 0, 40, 10));
}
#[test]
fn barchart_render_no_panic() {
use scrin::widgets::barchart::{Bar, BarChart};
let bc = BarChart::new().with_bars(vec![
Bar::new("A", 10),
Bar::new("B", 20),
Bar::new("C", 30),
]);
let mut buf = Buffer::new(40, 10);
bc.render(&mut buf, Rect::new(0, 0, 40, 10));
}
#[test]
fn chart_render_no_panic() {
use scrin::widgets::chart::{Chart, Dataset};
let ds = Dataset::new("test", vec![(0.0, 0.0), (5.0, 5.0), (10.0, 3.0)]);
let chart = Chart::new(vec![ds]);
let mut buf = Buffer::new(40, 15);
chart.render(&mut buf, Rect::new(0, 0, 40, 15));
}
#[test]
fn scrollbar_render_no_panic() {
use scrin::widgets::scrollbar::{ScrollBar, ScrollBarOrientation};
let sb = ScrollBar::new(ScrollBarOrientation::Vertical)
.with_position(5)
.with_total(50)
.with_viewport(10);
let mut buf = Buffer::new(5, 20);
sb.render(&mut buf, Rect::new(0, 0, 5, 20));
}
#[test]
fn aisling_effect_inventory_available() {
assert!(EffectPlayer::all_kinds().len() >= 37);
let mut buf = Buffer::new(24, 6);
let effect = EffectPlayer::new(EffectPlayer::all_kinds()[0], "scrin");
effect.render_to_buffer(&mut buf, Rect::new(0, 0, 24, 6));
}
#[test]
fn aisling_loader_inventory_available() {
assert!(LoaderPlayer::all_kinds().len() >= 61);
let mut buf = Buffer::new(24, 6);
let loader = LoaderPlayer::new(LoaderPlayer::all_kinds()[0]);
loader.render(
0,
LoaderPlayer::progress_from_fraction(0.5),
&mut buf,
Rect::new(0, 0, 24, 6),
);
}