use super::*;
use presentar_core::{
Brick, BrickAssertion, BrickBudget, BrickVerification, Canvas, Color, Event, LayoutResult,
Size, TypeId,
};
use std::any::Any;
use std::time::Duration;
struct TestWidget {
assertions: Vec<BrickAssertion>,
}
impl TestWidget {
fn new() -> Self {
Self {
assertions: vec![BrickAssertion::max_latency_ms(16)],
}
}
fn without_assertions() -> Self {
Self { assertions: vec![] }
}
}
impl Brick for TestWidget {
fn brick_name(&self) -> &'static str {
"test_widget"
}
fn assertions(&self) -> &[BrickAssertion] {
&self.assertions
}
fn budget(&self) -> BrickBudget {
BrickBudget::default()
}
fn verify(&self) -> BrickVerification {
BrickVerification {
passed: self.assertions.clone(),
failed: vec![],
verification_time: Duration::from_micros(10),
}
}
fn to_html(&self) -> String {
String::new()
}
fn to_css(&self) -> String {
String::new()
}
}
impl Widget for TestWidget {
fn type_id(&self) -> TypeId {
TypeId::of::<Self>()
}
fn measure(&self, constraints: Constraints) -> Size {
constraints.constrain(Size::new(10.0, 5.0))
}
fn layout(&mut self, bounds: Rect) -> LayoutResult {
LayoutResult {
size: Size::new(bounds.width, bounds.height),
}
}
fn paint(&self, canvas: &mut dyn Canvas) {
canvas.fill_rect(Rect::new(0.0, 0.0, 10.0, 5.0), Color::BLUE);
}
fn event(&mut self, _event: &Event) -> Option<Box<dyn Any + Send>> {
None
}
fn children(&self) -> &[Box<dyn Widget>] {
&[]
}
fn children_mut(&mut self) -> &mut [Box<dyn Widget>] {
&mut []
}
}
#[test]
fn test_tui_app_creation() {
let widget = TestWidget::new();
let app = TuiApp::new(widget);
assert!(app.is_ok());
}
#[test]
fn test_tui_app_rejects_empty_assertions() {
let widget = TestWidget::without_assertions();
let app = TuiApp::new(widget);
assert!(app.is_err());
let err = app.expect_err("expected error");
assert!(matches!(err, TuiError::InvalidBrick(_)));
}
#[test]
fn test_config_default() {
let config = TuiConfig::default();
assert_eq!(config.tick_rate_ms, 250);
assert_eq!(config.target_fps, 60);
assert!(!config.enable_mouse);
assert!(!config.skip_verification);
assert!(config.color_mode.is_none());
}
#[test]
fn test_config_high_performance() {
let config = TuiConfig::high_performance();
assert_eq!(config.tick_rate_ms, 16);
assert_eq!(config.target_fps, 60);
}
#[test]
fn test_config_power_saving() {
let config = TuiConfig::power_saving();
assert_eq!(config.tick_rate_ms, 100);
assert_eq!(config.target_fps, 30);
}
#[test]
fn test_tui_app_with_config() {
let widget = TestWidget::new();
let mut app = TuiApp::new(widget).unwrap();
let config = TuiConfig {
tick_rate_ms: 50,
enable_mouse: true,
color_mode: Some(ColorMode::Color256),
skip_verification: false,
target_fps: 30,
};
app = app.with_config(config);
assert!(app.metrics().frame_count == 0);
}
#[test]
fn test_tui_app_with_input_handler() {
let widget = TestWidget::new();
let mut app = TuiApp::new(widget).unwrap();
let mut handler = InputHandler::new();
handler.add_binding(crate::input::KeyBinding::simple(
crossterm::event::KeyCode::Char('q'),
"quit",
));
app = app.with_input_handler(handler);
assert!(app.root().assertions().len() == 1);
}
#[test]
fn test_tui_app_root_accessors() {
let widget = TestWidget::new();
let mut app = TuiApp::new(widget).unwrap();
assert_eq!(app.root().brick_name(), "test_widget");
assert_eq!(app.root_mut().brick_name(), "test_widget");
}
#[test]
fn test_tui_app_metrics() {
let widget = TestWidget::new();
let app = TuiApp::new(widget).unwrap();
let metrics = app.metrics();
assert_eq!(metrics.frame_count, 0);
assert_eq!(metrics.total_time, Duration::ZERO);
}
#[test]
fn test_tui_app_quit() {
let widget = TestWidget::new();
let mut app = TuiApp::new(widget).unwrap();
assert!(!app.should_quit);
app.quit();
assert!(app.should_quit);
}
#[test]
fn test_frame_metrics_default() {
let metrics = FrameMetrics::default();
assert_eq!(metrics.frame_count, 0);
assert_eq!(metrics.verify_time, Duration::ZERO);
assert_eq!(metrics.measure_time, Duration::ZERO);
assert_eq!(metrics.layout_time, Duration::ZERO);
assert_eq!(metrics.paint_time, Duration::ZERO);
assert_eq!(metrics.total_time, Duration::ZERO);
}
#[test]
fn test_config_with_color_mode_override() {
let widget = TestWidget::new();
let app = TuiApp::new(widget).unwrap();
let config = TuiConfig {
color_mode: Some(ColorMode::Mono),
..Default::default()
};
let app = app.with_config(config);
assert_eq!(app.color_mode, ColorMode::Mono);
}
#[test]
fn test_config_without_color_mode() {
let widget = TestWidget::new();
let app = TuiApp::new(widget).unwrap();
let original_mode = app.color_mode;
let config = TuiConfig {
color_mode: None,
..Default::default()
};
let app = app.with_config(config);
assert_eq!(app.color_mode, original_mode);
}
#[test]
#[allow(clippy::nonminimal_bool)]
fn test_render_frame() {
let widget = TestWidget::new();
let mut app = TuiApp::new(widget).unwrap();
let mut buffer = CellBuffer::new(80, 24);
app.render_frame(&mut buffer);
assert!(app.metrics.measure_time >= Duration::ZERO);
assert!(app.metrics.layout_time >= Duration::ZERO);
assert!(app.metrics.paint_time >= Duration::ZERO);
}
#[test]
fn test_render_frame_updates_metrics() {
let widget = TestWidget::new();
let mut app = TuiApp::new(widget).unwrap();
let mut buffer = CellBuffer::new(40, 10);
for _ in 0..3 {
app.render_frame(&mut buffer);
}
let metrics = app.metrics();
assert_eq!(metrics.frame_count, 0); }
#[test]
fn test_render_frame_with_different_buffer_sizes() {
let widget = TestWidget::new();
let mut app = TuiApp::new(widget).unwrap();
let mut small_buffer = CellBuffer::new(10, 5);
app.render_frame(&mut small_buffer);
let mut large_buffer = CellBuffer::new(200, 50);
app.render_frame(&mut large_buffer);
}
#[test]
fn test_frame_metrics_clone() {
let metrics = FrameMetrics {
verify_time: Duration::from_millis(1),
measure_time: Duration::from_millis(2),
layout_time: Duration::from_millis(3),
paint_time: Duration::from_millis(4),
total_time: Duration::from_millis(10),
frame_count: 100,
};
let cloned = metrics;
assert_eq!(cloned.frame_count, 100);
assert_eq!(cloned.verify_time, Duration::from_millis(1));
}
#[test]
fn test_frame_metrics_debug() {
let metrics = FrameMetrics::default();
let debug_str = format!("{:?}", metrics);
assert!(debug_str.contains("FrameMetrics"));
assert!(debug_str.contains("frame_count"));
}
#[test]
fn test_tui_config_clone() {
let config = TuiConfig::high_performance();
let cloned = config;
assert_eq!(cloned.tick_rate_ms, 16);
assert_eq!(cloned.target_fps, 60);
}
#[test]
fn test_tui_config_debug() {
let config = TuiConfig::default();
let debug_str = format!("{:?}", config);
assert!(debug_str.contains("TuiConfig"));
assert!(debug_str.contains("tick_rate_ms"));
}
struct FailingWidget;
impl Brick for FailingWidget {
fn brick_name(&self) -> &'static str {
"failing_widget"
}
fn assertions(&self) -> &[BrickAssertion] {
static ASSERTIONS: &[BrickAssertion] = &[BrickAssertion::max_latency_ms(16)];
ASSERTIONS
}
fn budget(&self) -> BrickBudget {
BrickBudget::default()
}
fn verify(&self) -> BrickVerification {
BrickVerification {
passed: vec![],
failed: vec![(
BrickAssertion::max_latency_ms(16),
"Intentional failure".to_string(),
)],
verification_time: Duration::from_micros(10),
}
}
fn to_html(&self) -> String {
String::new()
}
fn to_css(&self) -> String {
String::new()
}
}
impl Widget for FailingWidget {
fn type_id(&self) -> TypeId {
TypeId::of::<Self>()
}
fn measure(&self, constraints: Constraints) -> Size {
constraints.constrain(Size::new(10.0, 5.0))
}
fn layout(&mut self, bounds: Rect) -> LayoutResult {
LayoutResult {
size: Size::new(bounds.width, bounds.height),
}
}
fn paint(&self, _canvas: &mut dyn Canvas) {}
fn event(&mut self, _event: &Event) -> Option<Box<dyn Any + Send>> {
None
}
fn children(&self) -> &[Box<dyn Widget>] {
&[]
}
fn children_mut(&mut self) -> &mut [Box<dyn Widget>] {
&mut []
}
}
#[test]
fn test_tui_app_with_failing_widget() {
let widget = FailingWidget;
let app = TuiApp::new(widget);
assert!(app.is_ok());
}
#[test]
fn test_tui_config_all_fields() {
let config = TuiConfig {
tick_rate_ms: 100,
enable_mouse: true,
color_mode: Some(ColorMode::Color16),
skip_verification: true,
target_fps: 30,
};
assert_eq!(config.tick_rate_ms, 100);
assert!(config.enable_mouse);
assert_eq!(config.color_mode, Some(ColorMode::Color16));
assert!(config.skip_verification);
assert_eq!(config.target_fps, 30);
}
#[test]
fn test_frame_metrics_all_fields() {
let metrics = FrameMetrics {
verify_time: Duration::from_millis(1),
measure_time: Duration::from_millis(2),
layout_time: Duration::from_millis(3),
paint_time: Duration::from_millis(4),
total_time: Duration::from_millis(10),
frame_count: 42,
};
assert_eq!(metrics.verify_time, Duration::from_millis(1));
assert_eq!(metrics.measure_time, Duration::from_millis(2));
assert_eq!(metrics.layout_time, Duration::from_millis(3));
assert_eq!(metrics.paint_time, Duration::from_millis(4));
assert_eq!(metrics.total_time, Duration::from_millis(10));
assert_eq!(metrics.frame_count, 42);
}
#[test]
fn test_tui_app_skip_verification_config() {
let widget = TestWidget::new();
let app = TuiApp::new(widget).unwrap();
let config = TuiConfig {
skip_verification: true,
..Default::default()
};
let app = app.with_config(config);
assert!(app.config.skip_verification);
}
#[test]
fn test_tui_app_enable_mouse_config() {
let widget = TestWidget::new();
let app = TuiApp::new(widget).unwrap();
let config = TuiConfig {
enable_mouse: true,
..Default::default()
};
let app = app.with_config(config);
assert!(app.config.enable_mouse);
}
#[test]
fn test_render_frame_zero_size_buffer() {
let widget = TestWidget::new();
let mut app = TuiApp::new(widget).unwrap();
let mut buffer = CellBuffer::new(1, 1);
app.render_frame(&mut buffer);
}
#[test]
fn test_render_frame_metrics_populated() {
let widget = TestWidget::new();
let mut app = TuiApp::new(widget).unwrap();
let mut buffer = CellBuffer::new(80, 24);
app.render_frame(&mut buffer);
assert!(app.metrics.measure_time >= Duration::ZERO);
assert!(app.metrics.layout_time >= Duration::ZERO);
assert!(app.metrics.paint_time >= Duration::ZERO);
}
#[test]
fn test_tui_config_color_modes() {
for mode in [
ColorMode::TrueColor,
ColorMode::Color256,
ColorMode::Color16,
ColorMode::Mono,
] {
let widget = TestWidget::new();
let app = TuiApp::new(widget).unwrap();
let config = TuiConfig {
color_mode: Some(mode),
..Default::default()
};
let app = app.with_config(config);
assert_eq!(app.color_mode, mode);
}
}
#[test]
fn test_test_widget_brick_methods() {
let widget = TestWidget::new();
assert_eq!(widget.brick_name(), "test_widget");
assert!(!widget.assertions().is_empty());
assert!(widget.verify().is_valid());
assert!(widget.to_html().is_empty());
assert!(widget.to_css().is_empty());
}
#[test]
fn test_test_widget_widget_methods() {
let mut widget = TestWidget::new();
let size = widget.measure(Constraints::loose(Size::new(100.0, 100.0)));
assert!(size.width > 0.0);
assert!(size.height > 0.0);
let bounds = Rect::new(0.0, 0.0, 50.0, 25.0);
let result = widget.layout(bounds);
assert_eq!(result.size.width, 50.0);
assert_eq!(result.size.height, 25.0);
let event = Event::key_down(presentar_core::Key::Enter);
assert!(widget.event(&event).is_none());
assert!(widget.children().is_empty());
assert!(widget.children_mut().is_empty());
}
#[test]
fn test_tui_app_multiple_render_frames() {
let widget = TestWidget::new();
let mut app = TuiApp::new(widget).unwrap();
let mut buffer = CellBuffer::new(80, 24);
for _ in 0..10 {
app.render_frame(&mut buffer);
}
}
use std::cell::RefCell;
use std::collections::VecDeque;
struct MockTerminal {
size: (u16, u16),
events: RefCell<VecDeque<CrosstermEvent>>,
poll_results: RefCell<VecDeque<bool>>,
entered: RefCell<bool>,
left: RefCell<bool>,
mouse_enabled: RefCell<bool>,
flush_count: RefCell<u32>,
}
impl MockTerminal {
fn new(width: u16, height: u16) -> Self {
Self {
size: (width, height),
events: RefCell::new(VecDeque::new()),
poll_results: RefCell::new(VecDeque::new()),
entered: RefCell::new(false),
left: RefCell::new(false),
mouse_enabled: RefCell::new(false),
flush_count: RefCell::new(0),
}
}
fn with_events(mut self, events: Vec<CrosstermEvent>) -> Self {
self.events = RefCell::new(events.into());
self
}
fn with_polls(mut self, polls: Vec<bool>) -> Self {
self.poll_results = RefCell::new(polls.into());
self
}
}
impl Terminal for MockTerminal {
fn enter(&mut self) -> Result<(), TuiError> {
*self.entered.borrow_mut() = true;
Ok(())
}
fn leave(&mut self) -> Result<(), TuiError> {
*self.left.borrow_mut() = true;
Ok(())
}
fn size(&self) -> Result<(u16, u16), TuiError> {
Ok(self.size)
}
fn poll(&self, _timeout: Duration) -> Result<bool, TuiError> {
Ok(self.poll_results.borrow_mut().pop_front().unwrap_or(false))
}
fn read_event(&self) -> Result<CrosstermEvent, TuiError> {
self.events
.borrow_mut()
.pop_front()
.ok_or_else(|| TuiError::Io(io::Error::other("no event")))
}
fn flush(
&mut self,
_buffer: &mut CellBuffer,
_renderer: &mut DiffRenderer,
) -> Result<(), TuiError> {
*self.flush_count.borrow_mut() += 1;
Ok(())
}
fn enable_mouse(&mut self) -> Result<(), TuiError> {
*self.mouse_enabled.borrow_mut() = true;
Ok(())
}
fn disable_mouse(&mut self) -> Result<(), TuiError> {
*self.mouse_enabled.borrow_mut() = false;
Ok(())
}
}
#[test]
fn test_run_with_terminal_quit_on_q() {
let widget = TestWidget::new();
let mut app = TuiApp::new(widget).unwrap();
let terminal = MockTerminal::new(80, 24)
.with_polls(vec![true])
.with_events(vec![CrosstermEvent::Key(crossterm::event::KeyEvent::new(
KeyCode::Char('q'),
crossterm::event::KeyModifiers::NONE,
))]);
let result = app.run_with_terminal(terminal);
assert!(result.is_ok());
assert!(app.should_quit);
}
#[test]
fn test_run_with_terminal_ctrl_c() {
let widget = TestWidget::new();
let mut app = TuiApp::new(widget).unwrap();
let terminal = MockTerminal::new(80, 24)
.with_polls(vec![true])
.with_events(vec![CrosstermEvent::Key(crossterm::event::KeyEvent::new(
KeyCode::Char('c'),
crossterm::event::KeyModifiers::CONTROL,
))]);
let result = app.run_with_terminal(terminal);
assert!(result.is_ok());
assert!(app.should_quit);
}
#[test]
fn test_run_with_terminal_mouse_enabled() {
let widget = TestWidget::new();
let mut app = TuiApp::new(widget).unwrap();
app.config.enable_mouse = true;
let terminal = MockTerminal::new(80, 24)
.with_polls(vec![true])
.with_events(vec![CrosstermEvent::Key(crossterm::event::KeyEvent::new(
KeyCode::Char('q'),
crossterm::event::KeyModifiers::NONE,
))]);
let result = app.run_with_terminal(terminal);
assert!(result.is_ok());
}
#[test]
fn test_run_with_terminal_skip_verification() {
let widget = FailingWidget;
let mut app = TuiApp::new(widget).unwrap();
app.config.skip_verification = true;
let terminal = MockTerminal::new(80, 24)
.with_polls(vec![true])
.with_events(vec![CrosstermEvent::Key(crossterm::event::KeyEvent::new(
KeyCode::Char('q'),
crossterm::event::KeyModifiers::NONE,
))]);
let result = app.run_with_terminal(terminal);
assert!(result.is_ok());
}
#[test]
fn test_run_with_terminal_verification_failure() {
let widget = FailingWidget;
let mut app = TuiApp::new(widget).unwrap();
let terminal = MockTerminal::new(80, 24).with_polls(vec![false]);
let result = app.run_with_terminal(terminal);
assert!(result.is_err());
assert!(matches!(result, Err(TuiError::VerificationFailed(_))));
}
#[test]
fn test_run_with_terminal_no_events() {
let widget = TestWidget::new();
let mut app = TuiApp::new(widget).unwrap();
app.quit();
let terminal = MockTerminal::new(80, 24).with_polls(vec![false]);
let result = app.run_with_terminal(terminal);
assert!(result.is_ok());
}
#[test]
fn test_run_with_terminal_other_key() {
let widget = TestWidget::new();
let mut app = TuiApp::new(widget).unwrap();
let terminal = MockTerminal::new(80, 24)
.with_polls(vec![true, true])
.with_events(vec![
CrosstermEvent::Key(crossterm::event::KeyEvent::new(
KeyCode::Enter,
crossterm::event::KeyModifiers::NONE,
)),
CrosstermEvent::Key(crossterm::event::KeyEvent::new(
KeyCode::Char('q'),
crossterm::event::KeyModifiers::NONE,
)),
]);
let result = app.run_with_terminal(terminal);
assert!(result.is_ok());
}
#[test]
fn test_run_with_terminal_frame_count() {
let widget = TestWidget::new();
let mut app = TuiApp::new(widget).unwrap();
let terminal = MockTerminal::new(80, 24)
.with_polls(vec![false, false, true])
.with_events(vec![CrosstermEvent::Key(crossterm::event::KeyEvent::new(
KeyCode::Char('q'),
crossterm::event::KeyModifiers::NONE,
))]);
let result = app.run_with_terminal(terminal);
assert!(result.is_ok());
assert!(app.metrics.frame_count >= 1);
}
#[test]
fn test_crossterm_backend_new() {
let backend = CrosstermBackend::new();
let _ = backend;
}
#[test]
fn test_crossterm_backend_default() {
let backend = CrosstermBackend::default();
let _ = backend;
}
struct MockBackend {
size: (u16, u16),
events: RefCell<VecDeque<CrosstermEvent>>,
poll_results: RefCell<VecDeque<bool>>,
raw_mode: RefCell<bool>,
alternate_screen: RefCell<bool>,
cursor_hidden: RefCell<bool>,
mouse_captured: RefCell<bool>,
}
impl MockBackend {
fn new(width: u16, height: u16) -> Self {
Self {
size: (width, height),
events: RefCell::new(VecDeque::new()),
poll_results: RefCell::new(VecDeque::new()),
raw_mode: RefCell::new(false),
alternate_screen: RefCell::new(false),
cursor_hidden: RefCell::new(false),
mouse_captured: RefCell::new(false),
}
}
fn with_events(self, events: Vec<CrosstermEvent>) -> Self {
*self.events.borrow_mut() = events.into();
self
}
fn with_polls(self, polls: Vec<bool>) -> Self {
*self.poll_results.borrow_mut() = polls.into();
self
}
}
impl TerminalBackend for MockBackend {
fn enable_raw_mode(&mut self) -> Result<(), TuiError> {
*self.raw_mode.borrow_mut() = true;
Ok(())
}
fn disable_raw_mode(&mut self) -> Result<(), TuiError> {
*self.raw_mode.borrow_mut() = false;
Ok(())
}
fn enter_alternate_screen(&mut self) -> Result<(), TuiError> {
*self.alternate_screen.borrow_mut() = true;
Ok(())
}
fn leave_alternate_screen(&mut self) -> Result<(), TuiError> {
*self.alternate_screen.borrow_mut() = false;
Ok(())
}
fn hide_cursor(&mut self) -> Result<(), TuiError> {
*self.cursor_hidden.borrow_mut() = true;
Ok(())
}
fn show_cursor(&mut self) -> Result<(), TuiError> {
*self.cursor_hidden.borrow_mut() = false;
Ok(())
}
fn size(&self) -> Result<(u16, u16), TuiError> {
Ok(self.size)
}
fn poll(&self, _timeout: Duration) -> Result<bool, TuiError> {
Ok(self.poll_results.borrow_mut().pop_front().unwrap_or(false))
}
fn read_event(&self) -> Result<CrosstermEvent, TuiError> {
self.events
.borrow_mut()
.pop_front()
.ok_or_else(|| TuiError::Io(io::Error::other("no event")))
}
fn write_flush(
&mut self,
_buffer: &mut CellBuffer,
_renderer: &mut DiffRenderer,
) -> Result<(), TuiError> {
Ok(())
}
fn enable_mouse_capture(&mut self) -> Result<(), TuiError> {
*self.mouse_captured.borrow_mut() = true;
Ok(())
}
fn disable_mouse_capture(&mut self) -> Result<(), TuiError> {
*self.mouse_captured.borrow_mut() = false;
Ok(())
}
}
#[test]
fn test_generic_terminal_enter_leave() {
let backend = MockBackend::new(80, 24);
let mut terminal = GenericTerminal::new(backend);
terminal.enter().unwrap();
assert!(*terminal.backend.raw_mode.borrow());
assert!(*terminal.backend.alternate_screen.borrow());
assert!(*terminal.backend.cursor_hidden.borrow());
terminal.leave().unwrap();
assert!(!*terminal.backend.raw_mode.borrow());
assert!(!*terminal.backend.alternate_screen.borrow());
assert!(!*terminal.backend.cursor_hidden.borrow());
}
#[test]
fn test_generic_terminal_size() {
let backend = MockBackend::new(100, 50);
let terminal = GenericTerminal::new(backend);
let (w, h) = terminal.size().unwrap();
assert_eq!(w, 100);
assert_eq!(h, 50);
}
#[test]
fn test_generic_terminal_poll_read() {
let backend = MockBackend::new(80, 24)
.with_polls(vec![true, false])
.with_events(vec![CrosstermEvent::Key(crossterm::event::KeyEvent::new(
KeyCode::Enter,
crossterm::event::KeyModifiers::NONE,
))]);
let terminal = GenericTerminal::new(backend);
assert!(terminal.poll(Duration::from_millis(10)).unwrap());
let event = terminal.read_event().unwrap();
assert!(matches!(event, CrosstermEvent::Key(_)));
assert!(!terminal.poll(Duration::from_millis(10)).unwrap());
}
#[test]
fn test_generic_terminal_mouse() {
let backend = MockBackend::new(80, 24);
let mut terminal = GenericTerminal::new(backend);
assert!(!*terminal.backend.mouse_captured.borrow());
terminal.enable_mouse().unwrap();
assert!(*terminal.backend.mouse_captured.borrow());
terminal.disable_mouse().unwrap();
assert!(!*terminal.backend.mouse_captured.borrow());
}
#[test]
fn test_generic_terminal_flush() {
let backend = MockBackend::new(80, 24);
let mut terminal = GenericTerminal::new(backend);
let mut buffer = CellBuffer::new(80, 24);
let mut renderer = DiffRenderer::new();
terminal.flush(&mut buffer, &mut renderer).unwrap();
}
#[test]
fn test_run_with_generic_terminal() {
let widget = TestWidget::new();
let mut app = TuiApp::new(widget).unwrap();
let backend = MockBackend::new(80, 24)
.with_polls(vec![true])
.with_events(vec![CrosstermEvent::Key(crossterm::event::KeyEvent::new(
KeyCode::Char('q'),
crossterm::event::KeyModifiers::NONE,
))]);
let terminal = GenericTerminal::new(backend);
let result = app.run_with_terminal(terminal);
assert!(result.is_ok());
assert!(app.should_quit);
}
#[test]
fn test_mock_terminal_enter_leave() {
let mut terminal = MockTerminal::new(80, 24);
assert!(!*terminal.entered.borrow());
terminal.enter().unwrap();
assert!(*terminal.entered.borrow());
assert!(!*terminal.left.borrow());
terminal.leave().unwrap();
assert!(*terminal.left.borrow());
}
#[test]
fn test_mock_terminal_mouse() {
let mut terminal = MockTerminal::new(80, 24);
assert!(!*terminal.mouse_enabled.borrow());
terminal.enable_mouse().unwrap();
assert!(*terminal.mouse_enabled.borrow());
terminal.disable_mouse().unwrap();
assert!(!*terminal.mouse_enabled.borrow());
}
#[test]
fn test_mock_terminal_size() {
let terminal = MockTerminal::new(100, 50);
let (w, h) = terminal.size().unwrap();
assert_eq!(w, 100);
assert_eq!(h, 50);
}
#[test]
fn test_run_with_terminal_resize() {
let widget = TestWidget::new();
let mut app = TuiApp::new(widget).unwrap();
let terminal = MockTerminal::new(40, 12)
.with_polls(vec![true])
.with_events(vec![CrosstermEvent::Key(crossterm::event::KeyEvent::new(
KeyCode::Char('q'),
crossterm::event::KeyModifiers::NONE,
))]);
let result = app.run_with_terminal(terminal);
assert!(result.is_ok());
}
#[test]
fn test_run_with_terminal_mouse_event() {
let widget = TestWidget::new();
let mut app = TuiApp::new(widget).unwrap();
let terminal = MockTerminal::new(80, 24)
.with_polls(vec![true, true])
.with_events(vec![
CrosstermEvent::Mouse(crossterm::event::MouseEvent {
kind: crossterm::event::MouseEventKind::Down(crossterm::event::MouseButton::Left),
column: 10,
row: 5,
modifiers: crossterm::event::KeyModifiers::NONE,
}),
CrosstermEvent::Key(crossterm::event::KeyEvent::new(
KeyCode::Char('q'),
crossterm::event::KeyModifiers::NONE,
)),
]);
let result = app.run_with_terminal(terminal);
assert!(result.is_ok());
}
#[test]
fn test_run_with_terminal_non_key_event_then_quit() {
let widget = TestWidget::new();
let mut app = TuiApp::new(widget).unwrap();
let terminal = MockTerminal::new(80, 24)
.with_polls(vec![true, true])
.with_events(vec![
CrosstermEvent::Resize(100, 50),
CrosstermEvent::Key(crossterm::event::KeyEvent::new(
KeyCode::Char('q'),
crossterm::event::KeyModifiers::NONE,
)),
]);
let result = app.run_with_terminal(terminal);
assert!(result.is_ok());
}
#[test]
fn test_app_runner_metrics_update() {
let widget = TestWidget::new();
let mut app = TuiApp::new(widget).unwrap();
let terminal = MockTerminal::new(80, 24)
.with_polls(vec![false, true])
.with_events(vec![CrosstermEvent::Key(crossterm::event::KeyEvent::new(
KeyCode::Char('q'),
crossterm::event::KeyModifiers::NONE,
))]);
app.run_with_terminal(terminal).unwrap();
assert!(app.metrics.frame_count >= 1);
}
#[test]
fn test_testable_backend_new() {
let buf: Vec<u8> = Vec::new();
let backend = TestableBackend::new(buf, 80, 24);
assert_eq!(backend.size, (80, 24));
assert!(!backend.raw_mode);
assert!(!backend.alternate_screen);
assert!(!backend.cursor_hidden);
assert!(!backend.mouse_captured);
}
#[test]
fn test_testable_backend_with_events() {
let buf: Vec<u8> = Vec::new();
let backend = TestableBackend::new(buf, 80, 24).with_events(vec![CrosstermEvent::Key(
crossterm::event::KeyEvent::new(KeyCode::Char('a'), crossterm::event::KeyModifiers::NONE),
)]);
assert_eq!(backend.events.borrow().len(), 1);
}
#[test]
fn test_testable_backend_with_polls() {
let buf: Vec<u8> = Vec::new();
let backend = TestableBackend::new(buf, 80, 24).with_polls(vec![true, false, true]);
assert_eq!(backend.poll_results.borrow().len(), 3);
}
#[test]
fn test_testable_backend_enable_raw_mode() {
let buf: Vec<u8> = Vec::new();
let mut backend = TestableBackend::new(buf, 80, 24);
assert!(!backend.is_raw_mode());
backend.enable_raw_mode().unwrap();
assert!(backend.is_raw_mode());
}
#[test]
fn test_testable_backend_disable_raw_mode() {
let buf: Vec<u8> = Vec::new();
let mut backend = TestableBackend::new(buf, 80, 24);
backend.enable_raw_mode().unwrap();
assert!(backend.is_raw_mode());
backend.disable_raw_mode().unwrap();
assert!(!backend.is_raw_mode());
}
#[test]
fn test_testable_backend_enter_alternate_screen() {
let buf: Vec<u8> = Vec::new();
let mut backend = TestableBackend::new(buf, 80, 24);
assert!(!backend.is_alternate_screen());
backend.enter_alternate_screen().unwrap();
assert!(backend.is_alternate_screen());
let output = backend.into_writer();
assert!(!output.is_empty());
assert!(output.starts_with(b"\x1b["));
}
#[test]
fn test_testable_backend_leave_alternate_screen() {
let buf: Vec<u8> = Vec::new();
let mut backend = TestableBackend::new(buf, 80, 24);
backend.enter_alternate_screen().unwrap();
backend.leave_alternate_screen().unwrap();
assert!(!backend.is_alternate_screen());
}
#[test]
fn test_testable_backend_hide_cursor() {
let buf: Vec<u8> = Vec::new();
let mut backend = TestableBackend::new(buf, 80, 24);
assert!(!backend.is_cursor_hidden());
backend.hide_cursor().unwrap();
assert!(backend.is_cursor_hidden());
let output = backend.into_writer();
assert!(!output.is_empty());
}
#[test]
fn test_testable_backend_show_cursor() {
let buf: Vec<u8> = Vec::new();
let mut backend = TestableBackend::new(buf, 80, 24);
backend.hide_cursor().unwrap();
backend.show_cursor().unwrap();
assert!(!backend.is_cursor_hidden());
}
#[test]
fn test_testable_backend_size() {
let buf: Vec<u8> = Vec::new();
let backend = TestableBackend::new(buf, 120, 40);
assert_eq!(backend.size().unwrap(), (120, 40));
}
#[test]
fn test_testable_backend_poll() {
let buf: Vec<u8> = Vec::new();
let backend = TestableBackend::new(buf, 80, 24).with_polls(vec![true, false]);
assert!(backend.poll(Duration::from_millis(100)).unwrap());
assert!(!backend.poll(Duration::from_millis(100)).unwrap());
assert!(!backend.poll(Duration::from_millis(100)).unwrap());
}
#[test]
fn test_testable_backend_read_event() {
let buf: Vec<u8> = Vec::new();
let backend = TestableBackend::new(buf, 80, 24).with_events(vec![CrosstermEvent::Key(
crossterm::event::KeyEvent::new(KeyCode::Char('x'), crossterm::event::KeyModifiers::NONE),
)]);
let event = backend.read_event().unwrap();
assert!(matches!(event, CrosstermEvent::Key(_)));
}
#[test]
fn test_testable_backend_read_event_empty() {
let buf: Vec<u8> = Vec::new();
let backend = TestableBackend::new(buf, 80, 24);
let result = backend.read_event();
assert!(result.is_err());
}
#[test]
fn test_testable_backend_enable_mouse_capture() {
let buf: Vec<u8> = Vec::new();
let mut backend = TestableBackend::new(buf, 80, 24);
assert!(!backend.is_mouse_captured());
backend.enable_mouse_capture().unwrap();
assert!(backend.is_mouse_captured());
let output = backend.into_writer();
assert!(!output.is_empty());
}
#[test]
fn test_testable_backend_disable_mouse_capture() {
let buf: Vec<u8> = Vec::new();
let mut backend = TestableBackend::new(buf, 80, 24);
backend.enable_mouse_capture().unwrap();
backend.disable_mouse_capture().unwrap();
assert!(!backend.is_mouse_captured());
}
#[test]
fn test_testable_backend_write_flush() {
use crate::direct::Cell;
let buf: Vec<u8> = Vec::new();
let mut backend = TestableBackend::new(buf, 80, 24);
let mut buffer = CellBuffer::new(80, 24);
let mut renderer = DiffRenderer::new();
let mut cell_a = Cell::default();
cell_a.update(
"A",
presentar_core::Color::WHITE,
presentar_core::Color::BLACK,
crate::direct::Modifiers::empty(),
);
buffer.set(0, 0, cell_a);
let mut cell_b = Cell::default();
cell_b.update(
"B",
presentar_core::Color::WHITE,
presentar_core::Color::BLACK,
crate::direct::Modifiers::empty(),
);
buffer.set(1, 0, cell_b);
buffer.mark_all_dirty();
backend.write_flush(&mut buffer, &mut renderer).unwrap();
let output = backend.into_writer();
assert!(!output.is_empty());
}
#[test]
fn test_testable_backend_full_lifecycle() {
let buf: Vec<u8> = Vec::new();
let mut backend = TestableBackend::new(buf, 80, 24);
backend.enable_raw_mode().unwrap();
backend.enter_alternate_screen().unwrap();
backend.hide_cursor().unwrap();
assert!(backend.is_raw_mode());
assert!(backend.is_alternate_screen());
assert!(backend.is_cursor_hidden());
backend.show_cursor().unwrap();
backend.leave_alternate_screen().unwrap();
backend.disable_raw_mode().unwrap();
assert!(!backend.is_raw_mode());
assert!(!backend.is_alternate_screen());
assert!(!backend.is_cursor_hidden());
}
#[test]
fn test_testable_backend_escape_sequences() {
let buf: Vec<u8> = Vec::new();
let mut backend = TestableBackend::new(buf, 80, 24);
backend.enter_alternate_screen().unwrap();
backend.hide_cursor().unwrap();
backend.enable_mouse_capture().unwrap();
let output = backend.into_writer();
let output_str = String::from_utf8_lossy(&output);
assert!(
output_str.contains("\x1b["),
"Expected ANSI escape sequences"
);
}
#[test]
fn test_generic_terminal_with_testable_backend() {
let buf: Vec<u8> = Vec::new();
let backend = TestableBackend::new(buf, 80, 24)
.with_polls(vec![true])
.with_events(vec![CrosstermEvent::Key(crossterm::event::KeyEvent::new(
KeyCode::Char('q'),
crossterm::event::KeyModifiers::NONE,
))]);
let mut terminal = GenericTerminal::new(backend);
terminal.enter().unwrap();
assert_eq!(terminal.size().unwrap(), (80, 24));
assert!(terminal.poll(Duration::from_millis(10)).unwrap());
let event = terminal.read_event().unwrap();
assert!(matches!(event, CrosstermEvent::Key(_)));
terminal.leave().unwrap();
}
#[test]
fn test_testable_backend_with_tui_app() {
let widget = TestWidget::new();
let mut app = TuiApp::new(widget).unwrap();
let buf: Vec<u8> = Vec::new();
let backend = TestableBackend::new(buf, 80, 24)
.with_polls(vec![true])
.with_events(vec![CrosstermEvent::Key(crossterm::event::KeyEvent::new(
KeyCode::Char('q'),
crossterm::event::KeyModifiers::NONE,
))]);
let terminal = GenericTerminal::new(backend);
let result = app.run_with_terminal(terminal);
assert!(result.is_ok());
}
#[test]
fn test_testable_backend_captures_render_output() {
let widget = TestWidget::new();
let _app = TuiApp::new(widget).unwrap();
let buf: Vec<u8> = Vec::new();
let backend = TestableBackend::new(buf, 40, 10)
.with_polls(vec![true])
.with_events(vec![CrosstermEvent::Key(crossterm::event::KeyEvent::new(
KeyCode::Char('q'),
crossterm::event::KeyModifiers::NONE,
))]);
let mut terminal = GenericTerminal::new(backend);
terminal.enter().unwrap();
let (width, height) = terminal.size().unwrap();
assert_eq!((width, height), (40, 10));
terminal.leave().unwrap();
}