use opentui::buffer::{ClipRect, OptimizedBuffer};
use opentui::cell::Cell;
use opentui::color::Rgba;
use opentui::style::Style;
use opentui_rust as opentui;
mod boundary_conditions {
use super::*;
#[test]
fn zero_size_buffer_no_panic() {
let buf = OptimizedBuffer::new(0, 10);
assert_eq!(buf.size(), (1, 10));
assert!(buf.get(0, 0).is_some());
let buf = OptimizedBuffer::new(10, 0);
assert_eq!(buf.size(), (10, 1));
assert!(buf.get(0, 0).is_some());
let buf = OptimizedBuffer::new(0, 0);
assert_eq!(buf.size(), (1, 1));
assert!(buf.get(0, 0).is_some());
}
#[test]
fn single_cell_buffer() {
let mut buf = OptimizedBuffer::new(1, 1);
assert_eq!(buf.size(), (1, 1));
assert!(buf.get(0, 0).is_some());
assert!(buf.get(1, 0).is_none());
assert!(buf.get(0, 1).is_none());
assert!(buf.get(1, 1).is_none());
let cell = Cell::new('X', Style::fg(Rgba::RED));
buf.set(0, 0, cell);
assert_eq!(buf.get(0, 0).unwrap().content.as_char(), Some('X'));
}
#[test]
fn very_wide_buffer() {
let width = 10000u32;
let buf = OptimizedBuffer::new(width, 1);
assert_eq!(buf.size(), (width, 1));
assert!(buf.get(0, 0).is_some());
assert!(buf.get(width - 1, 0).is_some());
assert!(buf.get(width, 0).is_none());
}
#[test]
fn very_tall_buffer() {
let height = 10000u32;
let buf = OptimizedBuffer::new(1, height);
assert_eq!(buf.size(), (1, height));
assert!(buf.get(0, 0).is_some());
assert!(buf.get(0, height - 1).is_some());
assert!(buf.get(0, height).is_none());
}
#[test]
fn large_buffer_allocation() {
let buf = OptimizedBuffer::new(1000, 1000);
assert_eq!(buf.size(), (1000, 1000));
let byte_size = buf.byte_size();
assert!(byte_size > 0);
assert!(byte_size >= 1_000_000);
}
}
mod drawing_edge_cases {
use super::*;
#[test]
fn draw_at_exact_boundary() {
let mut buf = OptimizedBuffer::new(10, 10);
let cell = Cell::new('X', Style::fg(Rgba::RED));
buf.set(9, 9, cell);
assert_eq!(buf.get(9, 9).unwrap().content.as_char(), Some('X'));
}
#[test]
fn draw_past_boundary_no_panic() {
let mut buf = OptimizedBuffer::new(10, 10);
let cell = Cell::new('X', Style::fg(Rgba::RED));
buf.set(10, 0, cell);
buf.set(0, 10, cell);
buf.set(100, 100, cell);
buf.set(u32::MAX, u32::MAX, cell);
assert!(buf.get(9, 9).unwrap().content.is_empty());
}
#[test]
fn fill_rect_past_boundary() {
let mut buf = OptimizedBuffer::new(10, 10);
buf.fill_rect(5, 5, 100, 100, Rgba::RED);
let cell = buf.get(9, 9).unwrap();
assert!(!cell.bg.is_transparent());
buf.fill_rect(0, 0, 5, 5, Rgba::BLUE);
}
#[test]
fn fill_rect_zero_dimensions() {
let mut buf = OptimizedBuffer::new(10, 10);
buf.clear(Rgba::WHITE);
buf.fill_rect(0, 0, 0, 10, Rgba::RED);
buf.fill_rect(0, 0, 10, 0, Rgba::RED);
buf.fill_rect(0, 0, 0, 0, Rgba::RED);
assert!(approx_eq_rgba(buf.get(0, 0).unwrap().bg, Rgba::WHITE));
}
#[test]
fn fill_rect_completely_outside() {
let mut buf = OptimizedBuffer::new(10, 10);
buf.clear(Rgba::WHITE);
buf.fill_rect(100, 100, 10, 10, Rgba::RED);
assert!(approx_eq_rgba(buf.get(0, 0).unwrap().bg, Rgba::WHITE));
}
#[test]
fn draw_text_at_boundary() {
let mut buf = OptimizedBuffer::new(10, 10);
buf.draw_text(0, 9, "Hello", Style::fg(Rgba::RED));
assert_eq!(buf.get(0, 9).unwrap().content.as_char(), Some('H'));
buf.draw_text(9, 0, "Hello", Style::fg(Rgba::RED));
assert_eq!(buf.get(9, 0).unwrap().content.as_char(), Some('H'));
}
#[test]
fn draw_text_completely_outside() {
let mut buf = OptimizedBuffer::new(10, 10);
buf.clear(Rgba::WHITE);
buf.draw_text(100, 100, "Hello", Style::fg(Rgba::RED));
assert!(buf.get(0, 0).unwrap().content.is_empty());
}
#[test]
fn clear_on_zero_buffer() {
let mut buf = OptimizedBuffer::new(0, 0);
assert_eq!(buf.size(), (1, 1));
buf.clear(Rgba::RED);
assert!(approx_eq_rgba(buf.get(0, 0).unwrap().bg, Rgba::RED));
}
}
mod scissor_edge_cases {
use super::*;
#[test]
fn scissor_larger_than_buffer() {
let mut buf = OptimizedBuffer::new(10, 10);
buf.push_scissor(ClipRect::new(0, 0, 100, 100));
let cell = Cell::new('X', Style::fg(Rgba::RED));
buf.set(5, 5, cell);
assert_eq!(buf.get(5, 5).unwrap().content.as_char(), Some('X'));
buf.pop_scissor();
}
#[test]
fn scissor_with_negative_coords() {
let mut buf = OptimizedBuffer::new(10, 10);
buf.push_scissor(ClipRect::new(-5, -5, 15, 15));
let cell = Cell::new('X', Style::fg(Rgba::RED));
buf.set(0, 0, cell);
assert_eq!(buf.get(0, 0).unwrap().content.as_char(), Some('X'));
buf.pop_scissor();
}
#[test]
fn deeply_nested_scissors() {
let mut buf = OptimizedBuffer::new(100, 100);
for i in 0..50u32 {
let offset = i32::try_from(i).expect("loop index fits in i32");
let size = 100 - (i * 2);
buf.push_scissor(ClipRect::new(offset, offset, size, size));
}
let cell = Cell::new('X', Style::fg(Rgba::RED));
buf.set(50, 50, cell);
for _ in 0..50 {
buf.pop_scissor();
}
assert!(buf.get(50, 50).is_some());
}
#[test]
fn scissor_empty_area() {
let mut buf = OptimizedBuffer::new(10, 10);
buf.push_scissor(ClipRect::new(5, 5, 0, 0));
let cell = Cell::new('X', Style::fg(Rgba::RED));
buf.set(5, 5, cell);
assert!(buf.get(5, 5).unwrap().content.is_empty());
buf.pop_scissor();
}
#[test]
fn scissor_outside_buffer() {
let mut buf = OptimizedBuffer::new(10, 10);
buf.push_scissor(ClipRect::new(100, 100, 10, 10));
let cell = Cell::new('X', Style::fg(Rgba::RED));
buf.set(5, 5, cell);
assert!(buf.get(5, 5).unwrap().content.is_empty());
buf.pop_scissor();
}
#[test]
fn pop_more_scissors_than_pushed() {
let mut buf = OptimizedBuffer::new(10, 10);
buf.push_scissor(ClipRect::new(0, 0, 10, 10));
buf.pop_scissor();
buf.pop_scissor();
buf.pop_scissor();
let cell = Cell::new('X', Style::fg(Rgba::RED));
buf.set(5, 5, cell);
assert_eq!(buf.get(5, 5).unwrap().content.as_char(), Some('X'));
}
#[test]
fn scissor_intersection() {
let mut buf = OptimizedBuffer::new(20, 20);
buf.push_scissor(ClipRect::new(0, 0, 15, 15));
buf.push_scissor(ClipRect::new(5, 5, 15, 15));
let cell = Cell::new('X', Style::fg(Rgba::RED));
buf.set(10, 10, cell);
assert_eq!(buf.get(10, 10).unwrap().content.as_char(), Some('X'));
buf.set(2, 2, cell);
assert!(buf.get(2, 2).unwrap().content.is_empty());
buf.pop_scissor();
buf.pop_scissor();
}
}
mod opacity_edge_cases {
use super::*;
#[test]
fn opacity_greater_than_one() {
let mut buf = OptimizedBuffer::new(10, 10);
buf.clear(Rgba::BLACK);
buf.push_opacity(2.0);
let cell = Cell::new('X', Style::fg(Rgba::RED).with_bg(Rgba::RED));
buf.set(5, 5, cell);
let result = buf.get(5, 5).unwrap();
assert!(result.bg.a >= 0.0 && result.bg.a <= 1.0);
buf.pop_opacity();
}
#[test]
fn opacity_less_than_zero() {
let mut buf = OptimizedBuffer::new(10, 10);
buf.clear(Rgba::WHITE);
buf.push_opacity(-0.5);
let cell = Cell::new('X', Style::fg(Rgba::RED).with_bg(Rgba::RED));
buf.set(5, 5, cell);
buf.pop_opacity();
}
#[test]
fn opacity_zero() {
let mut buf = OptimizedBuffer::new(10, 10);
buf.clear(Rgba::WHITE);
buf.push_opacity(0.0);
let cell = Cell::new('X', Style::fg(Rgba::RED).with_bg(Rgba::RED));
buf.set(5, 5, cell);
let result = buf.get(5, 5).unwrap();
assert!(result.bg.a <= 0.001 || approx_eq_rgba(result.bg, Rgba::WHITE));
buf.pop_opacity();
}
#[test]
fn very_small_opacity() {
let mut buf = OptimizedBuffer::new(10, 10);
buf.clear(Rgba::WHITE);
buf.push_opacity(1e-10);
let cell = Cell::new('X', Style::fg(Rgba::RED).with_bg(Rgba::RED));
buf.set(5, 5, cell);
buf.pop_opacity();
}
#[test]
fn deeply_nested_opacity() {
let mut buf = OptimizedBuffer::new(10, 10);
buf.clear(Rgba::WHITE);
for _ in 0..100 {
buf.push_opacity(0.99);
}
let cell = Cell::new('X', Style::fg(Rgba::RED).with_bg(Rgba::RED));
buf.set(5, 5, cell);
for _ in 0..100 {
buf.pop_opacity();
}
assert!(buf.get(5, 5).is_some());
}
#[test]
fn pop_more_opacities_than_pushed() {
let mut buf = OptimizedBuffer::new(10, 10);
buf.push_opacity(0.5);
buf.pop_opacity();
buf.pop_opacity();
buf.pop_opacity();
let cell = Cell::new('X', Style::fg(Rgba::RED).with_bg(Rgba::RED));
buf.set(5, 5, cell);
let result = buf.get(5, 5).unwrap();
assert!(result.bg.a > 0.9);
}
#[test]
fn opacity_interaction_with_blending() {
let mut buf = OptimizedBuffer::new(10, 10);
buf.clear(Rgba::BLACK);
buf.set_blended(5, 5, Cell::new(' ', Style::NONE.with_bg(Rgba::WHITE)));
buf.push_opacity(0.5);
let cell = Cell::new('X', Style::fg(Rgba::RED).with_bg(Rgba::RED));
buf.set_blended(5, 5, cell);
buf.pop_opacity();
let result = buf.get(5, 5).unwrap();
assert!(result.bg.r > 0.3);
}
}
mod wide_char_edge_cases {
use super::*;
#[test]
fn wide_char_at_right_edge() {
let mut buf = OptimizedBuffer::new(10, 10);
buf.draw_text(9, 0, "中", Style::fg(Rgba::RED));
let _ = buf.get(9, 0);
}
#[test]
fn wide_char_at_second_to_last() {
let mut buf = OptimizedBuffer::new(10, 10);
buf.draw_text(8, 0, "中", Style::fg(Rgba::RED));
}
#[test]
fn many_wide_chars() {
let mut buf = OptimizedBuffer::new(100, 10);
buf.draw_text(0, 0, "中文日本語한국어", Style::fg(Rgba::RED));
}
}
fn approx_eq_rgba(a: Rgba, b: Rgba) -> bool {
(a.r - b.r).abs() < 0.01
&& (a.g - b.g).abs() < 0.01
&& (a.b - b.b).abs() < 0.01
&& (a.a - b.a).abs() < 0.01
}